//
// Copyright (c) Microsoft Corporation.  All rights reserved.
//
//
// Use of this source code is subject to the terms of the Microsoft shared
// source or premium shared source license agreement under which you licensed
// this source code. If you did not accept the terms of the license agreement,
// you are not authorized to use this source code. For the terms of the license,
// please see the license agreement between you and Microsoft or, if applicable,
// see the SOURCE.RTF on your install media or the root of your tools installation.
// THE SOURCE CODE IS PROVIDED "AS IS", WITH NO WARRANTIES OR INDEMNITIES.
//
/*++


Module Name:

    SrmpSend.cxx

Abstract:

    Creates an HTTP packet based on message and sends it to intented recepient.
 
--*/

#include <windows.h>
#include <wininet.h>
#include <mq.h>
#include <service.h>

#include "sc.hxx"
#include "scsrmp.hxx"
#include "scsman.hxx"
#include "scpacket.hxx"
#include "SrmpAccept.hxx"
#include "scdefs.hxx"
#include "srmpparse.hxx"
#include "fntoken.h"
#include "scutil.hxx"
#include "scqueue.hxx"
#include "scqman.hxx"
#include "SrmpFwd.hxx"

// use SVSXMLEncode::Append() on values below because we don't want to escape the quote characters.
const WCHAR cszXMLHeader[]          = L"<?xml version=\"1.0\" ?>";
const WCHAR cszEnvelopeStartNS[]    = L"<se:Envelope xmlns:se=\"http://schemas.xmlsoap.org/soap/envelope/\" xmlns=\"http://schemas.xmlsoap.org/srmp/\">";
const WCHAR cszMustUnderstandNS[]   = L" se:mustUnderstand=\"1\">";
const WCHAR cszPathNS[]             = L"<path xmlns=\"http://schemas.xmlsoap.org/rp/\"";
const WCHAR cszPropertiesNS[]       = L"<properties";
const WCHAR cszServicesNS[]         = L"<services";
const WCHAR cszStreamNS[]           = L"<Stream";
const WCHAR cszMsmqNS[]             = L"<Msmq xmlns=\"msmq.namespace.xml\">";

const WCHAR cszHeaderNS[]           = L"se:Header";
const WCHAR cszBodyNS[]             = L"se:Body";
const WCHAR cszEnvelopeNS[]         = L"se:Envelope";

DWORD CalcSrmpFwdSize(WCHAR *szQueueName, ScPacketImage *pPacketImage);

BOOL  SendPath(SVSXMLEncode *pXmlEncode, ScPacketImage *pImage);
BOOL  SendProps(SVSXMLEncode *pXmlEncode, ScPacketImage *pImage);
BOOL  SendServices(SVSXMLEncode *pXmlEncode, ScPacketImage *pImage);
BOOL  SendStream(SVSXMLEncode *pXmlEncode, ScPacketImage *pImage);
BOOL  SendStreamRcpt(SVSXMLEncode *pXmlEncode, ScPacketImage *pImage);
BOOL  SendDeliveryRcpt(SVSXMLEncode *pXmlEncode, ScPacketImage *pImage);
BOOL  SendCommitRcpt(SVSXMLEncode *pXmlEncode, ScPacketImage *pImage);
BOOL  SendMsmq(SVSXMLEncode *pXmlEncode, ScPacketImage *pImage);
BOOL  SendUserHeader(SVSXMLEncode *pXmlEncode, ScPacketImage *pImage);

DWORD GetExpiresFromPacketImage(ScPacketImage *pImage);
BOOL  SetMQCurrentTime(SVSXMLEncode *pXmlEncode, const WCHAR *szElementName);
BOOL  SetMsgID(SVSXMLEncode *pXmlEncode, OBJECTID *pMsgId);

int BuildHttpHeadersAndMimeBody(ScPacketImage *pImage, CHAR *szSoapEnv, DWORD ccSoapEnv, BOOL fSecure, PSrmpIOCTLPacket pIOPacket, DWORD ccHeaders, SVSSimpleBuffer &cBodyBuf);
CHAR* BuildSoapEnvelope(ScPacketImage *pImage, DWORD *pccSendBuffer);


BOOL StatusCodeSuccess(DWORD dwStatus) {
    return (dwStatus == 200) || (dwStatus == 201) || (dwStatus == 202) || (dwStatus == 203) || (dwStatus == 204) || (dwStatus == 205);
}

BOOL StatusCodeRetriableError(DWORD dwStatus) {
    if (!gMachine->fUntrustedNetwork) {
        // on a trusted network we're willing to take chance that there could
        // be some other network error causing the status code to be incorrect.
        return ! StatusCodeSuccess(dwStatus);
    }

    // On an untrusted net it's possible a malicious app has pointed us off to
    // a non-existant on not setup machine, in which case we'll only retry on a 
    // certain set of errors.
    return (dwStatus == 408) || (dwStatus == 500) || (dwStatus == 502) || (dwStatus == 503) || (dwStatus == 504);
}


#define SRMP_INTERNET_TIMEOUT  (30*1000)  /* 30 seconds */

class CSrmpCallBack {
    friend void CALLBACK SrmpCallback(HINTERNET hInternet,DWORD_PTR dwContext,DWORD dwInternetStatus,LPVOID lpvStatusInformation,DWORD dwStatusInformationLength);
    HANDLE hEventComplete;
    HANDLE hEventClosed;

public:
    DWORD  dwResult;
    DWORD  dwError;

    CSrmpCallBack(void) {
        memset(this,0,sizeof(*this));
        dwResult = 500;
        hEventComplete = CreateEvent(NULL,FALSE,FALSE,NULL);
        hEventClosed   = CreateEvent(NULL,FALSE,FALSE,NULL);
    }

    BOOL IsInitialized(void) { return (hEventComplete && hEventClosed ) ? TRUE : FALSE; }

    BOOL Wait(DWORD dwTimeout) {
        return (WAIT_TIMEOUT != WaitForSingleObject(hEventComplete,dwTimeout));
    }

    void Failed(void) {
        // make sure we don't block forever on deinitializing + waiting for hEventClosed
        if (hEventClosed) {
            CloseHandle(hEventClosed);
            hEventClosed = 0;
        }
    }

    ~CSrmpCallBack(void) {
        if (hEventClosed) {
            // make sure we get closed to make sure that our async caller isn't called after class is deallocated.
            WaitForSingleObject(hEventClosed,INFINITE);
            CloseHandle(hEventClosed);
        }

        if (hEventComplete)
            CloseHandle(hEventComplete);
    }
};

#if defined (InternetSetStatusCallback)
// Wininet defines this to InternetSetStatusCallback to (A|W) based on ANSI/UNICODE,
// httplite only exports InternetSetStatusCallback.
// 
#undef InternetSetStatusCallback
INTERNETAPI_(INTERNET_STATUS_CALLBACK) InternetSetStatusCallback(
                                                                 IN HINTERNET hInternet,
                                                                 IN INTERNET_STATUS_CALLBACK lpfnInternetCallback
                                                                 );
#endif

int ScSession::InitializeWininet(CSrmpCallBack *pCallback) {
    DWORD          dwTimeout = SRMP_INTERNET_TIMEOUT;
    INTERNET_PORT  iPort     = (qType == SCFILE_QP_FORMAT_HTTPS) ? INTERNET_DEFAULT_HTTPS_PORT : INTERNET_DEFAULT_HTTP_PORT;

    if (NULL == (hInternetSession = InternetOpenA(NULL,INTERNET_OPEN_TYPE_PRECONFIG,NULL,NULL,INTERNET_FLAG_ASYNC))) {
        scerror_DebugOutM(VERBOSE_MASK_SRMP,(L"InternetOpen fails, GLE=0x%08x\r\n",GetLastError()));
        fSessionState = SCSESSION_STATE_INACTIVE;
        return FALSE;
    }

    InternetSetOption(hInternetSession, INTERNET_OPTION_RECEIVE_TIMEOUT, &dwTimeout, sizeof(dwTimeout));
    InternetSetOption(hInternetSession, INTERNET_OPTION_SEND_TIMEOUT, &dwTimeout, sizeof(dwTimeout));
    InternetSetOption(hInternetSession, INTERNET_OPTION_DATA_RECEIVE_TIMEOUT, &dwTimeout, sizeof(dwTimeout));
    InternetSetOption(hInternetSession, INTERNET_OPTION_DATA_SEND_TIMEOUT, &dwTimeout, sizeof(dwTimeout));
    InternetSetOption(hInternetSession, INTERNET_OPTION_CONNECT_TIMEOUT , &dwTimeout,sizeof(dwTimeout));

    if (NULL == (hInternetConnect = InternetConnectA(hInternetSession,lpszmbHostName,iPort,NULL,NULL,INTERNET_SERVICE_HTTP,0,(DWORD)pCallback))) {
        scerror_DebugOutM(VERBOSE_MASK_SRMP,(L"InternetConnect fails, GLE=0x%08x\r\n",GetLastError()));
        fSessionState = SCSESSION_STATE_INACTIVE;
        return FALSE;
    }

    if (INTERNET_INVALID_STATUS_CALLBACK == InternetSetStatusCallback(hInternetConnect,&SrmpCallback)) {
        scerror_DebugOutM(VERBOSE_MASK_SRMP,(L"InternetConnect fails, GLE=0x%08x\r\n",GetLastError()));
        fSessionState = SCSESSION_STATE_INACTIVE;
        return FALSE;
    }

    return TRUE;
}

// We'll try to send a message this many times before we end the session.
#define  SRMP_MAX_RETRIES             5
// Wait this many milliseconds on HTTP request recoverable failures.
#define  SRMP_RETRY_WAIT              (25*1000)

void ScSession::ServiceThreadHttpW(void) {
#if defined (SC_VERBOSE)
    scerror_DebugOut (VERBOSE_MASK_SESSION, L"Sending HTTP thread for %s\r\n", lpszHostName);
#endif

    int            iStatus   = 200;
    BOOL           fClearAttrTimer = FALSE;
    int            iRetries  = 0;

    fSessionState = SCSESSION_STATE_CONNECTING;

    CSrmpCallBack connectCallback;
    if (!connectCallback.IsInitialized()) {
#if defined (SC_VERBOSE)
        scerror_DebugOut(VERBOSE_MASK_SRMP,L"ServiceThreadHttpW cannot CreateEvent, GLE=0x%08x\r\n",GetLastError());
#endif
        return;
    }

    gMem->Lock();

    int uiSCSESSION_IDLE_TIMEOUT = gMachine->uiIdleTimeout * 1000;

    if (!InitializeWininet(&connectCallback)) {
        connectCallback.Failed();
        goto done;
    }

    // connectCallback.Wait(2000);

    fSessionState = SCSESSION_STATE_OPERATING;

    SetEvent(hEvent);    // Do the first scan...
    while (fSessionState == SCSESSION_STATE_OPERATING) {
        ScPacket *pPacket = NULL;
        ScQueue  *pQueue  = NULL;
        SVSUTIL_ASSERT (gMem->IsLocked ());

        gMem->Unlock();
        int iResp = WaitForSingleObject (hEvent, uiSCSESSION_IDLE_TIMEOUT);

        gMem->Lock ();
        if (fClearAttrTimer) {
            svsutil_ClearAttrTimer(gMem->pTimer,(SVSHandle)hEvent);
            fClearAttrTimer = FALSE;
        }
        
        if (iResp == WAIT_TIMEOUT)
            break;

        if (fSessionState == SCSESSION_STATE_EXITING) {
            // don't goto done; because another thread has initiated FinishSession()
            gMem->Unlock ();
            return; 
        }

        if (! ExtractNextPacket(pPacket, pQueue, pQueue))
            continue;

        // message to remote queue
        iStatus = BuildAndSendSRMP(pPacket,pQueue);
        SVSUTIL_ASSERT (gMem->IsLocked());

        if (StatusCodeRetriableError(iStatus)) {
            iRetries++;
            SVSUTIL_ASSERT(iRetries <= SRMP_MAX_RETRIES);

            if (iRetries == SRMP_MAX_RETRIES) {
                scerror_DebugOutM(VERBOSE_MASK_SRMP,(L"ServiceThreadHttpW cannot successfully send message, closing down session to try again later\r\n"));
                break;
            }
        
            // On server errors we assume it's temporary and that we can try again later.
            BOOL fSwallowEvent = (pSentPackets || pSentRelPackets) ? TRUE : FALSE;

            ReturnUnacketPacketsToQueues();
            // we have to do this because when packet is inserted back 
            // into the queue SetEvent(hEvent) is called.
            if (fSwallowEvent)
                WaitForSingleObject(hEvent,0);
                
            if (fSessionState == SCSESSION_STATE_OPERATING) {
                fClearAttrTimer = TRUE;
                svsutil_SetAttrTimer(gMem->pTimer,(SVSHandle)hEvent,SRMP_RETRY_WAIT);
            }
            continue;
        }
        iRetries = 0;

        // either we succeeded or we encountered an non-recoverable error, in either
        // event discard the packet and move onto next message in queue.

        // Dispose of packet.  We only queue up one at a time so check is trivial.
        if (pSentPackets) {
            svsutil_FreeFixed(pSentPackets, gMem->pAckNodeMem);
            pSentPackets = NULL;
        }
        else if (pSentRelPackets) {
            svsutil_FreeFixed(pSentRelPackets, gMem->pAckNodeMem);
            pSentRelPackets = NULL;
        }
        gQueueMan->JournalPacket(pPacket);
        pQueue->DisposeOfPacket(pPacket);
        SetEvent(hEvent); // immediatly try to send next packet.
    }

done:
    SVSUTIL_ASSERT(gMem->IsLocked());

    if (fClearAttrTimer)
        svsutil_ClearAttrTimer(gMem->pTimer,(SVSHandle)hEvent);

    FinishSession();
    fSessionState = SCSESSION_STATE_INACTIVE;
    gSessionMan->ReleaseSession (this);
    gMem->Unlock();
}

// put a preceding '/' in front of name if needed.
PSTR AllocURL(PCWSTR wszURL) {
    PSTR pszOut = 0;
    int iAddSlash = (wszURL[0] != '\\' && wszURL[0] != '/') ? 1 : 0;

    int  iOutLen = WideCharToMultiByte(CP_ACP, 0, wszURL, -1, 0, 0, 0, 0);
    if(!iOutLen)
        goto error;

    iOutLen += iAddSlash;

    pszOut = (PSTR)g_funcAlloc(iOutLen*sizeof(WCHAR), g_pvAllocData);
    if(!pszOut)
        goto error;


    if (iAddSlash) {
        pszOut[0] = '/';
    }

    if(WideCharToMultiByte(CP_ACP, 0, wszURL, -1, pszOut+iAddSlash, iOutLen-iAddSlash, 0, 0))
        return pszOut;

error:
    if (pszOut)
        g_funcFree(pszOut,g_pvFreeData);
    return FALSE;
}


int ScSession::BuildAndSendSRMP(ScPacket *pPacket, ScQueue *pQueue)  {
    CHAR            *szSoapEnv = NULL;
    DWORD           ccSoapEnv;
    int             iStatusCode = 503; // if we fail it's most likely because of malloc failure, we can try again
    CHAR            *szURL     = NULL;
    CHAR            *szTarget  = NULL;
    ScPacketImage   *pNewImage = NULL;
    SrmpIOCTLPacket ioPacket;
    CHAR szHeaders[2048];
    SVSSimpleBuffer cBodyBuf;

    // SendUP() stuff...
    SVSUTIL_ASSERT (gMem->IsLocked ());
    SVSUTIL_ASSERT (! pQueue->qp.bIsIncoming);

    scerror_DebugOutM (VERBOSE_MASK_SRMP, (L"Sending user packet %08x ptr %08x queue %s to %s\n", pPacket->uiMessageID, pPacket, pQueue->lpszFormatName, lpszHostName));

    if (pQueue->qp.bTransactional) {
        SVSUTIL_ASSERT (pPacket->iDirEntry >= 0);
        SVSUTIL_ASSERT (pPacket->pNode);

        SVSTNode *pPrevNode = pQueue->pPackets->Prev (pPacket->pNode);
        ScPacket *pPrev = pPrevNode ? (ScPacket *)SVSTree::GetData(pPrevNode) : NULL;

        pPacket->uiPacketState = SCPACKET_STATE_WAITORDERACK;
        pPacket->pImage->sect.pXactHeader->SetPrevSeqN (pPrev ? pPrev->uiSeqN : 0);
    } else {
        SentPacket *pSP = (SentPacket *)svsutil_GetFixed (gMem->pAckNodeMem);
        if (NULL == pSP) {
            scerror_DebugOutM (VERBOSE_MASK_SESSION, (L"No memory - failing session %s...\n", lpszHostName));
            return iStatusCode;
        }

        pSP->pPacket      = pPacket;

        ++usPacketsSent;
        if (! usPacketsSent)
            ++usPacketsSent;

        if (pPacket->iDirEntry >= 0) {
            ++usRelPacketsSent;
            if (! usRelPacketsSent)
                ++usRelPacketsSent;

            pSP->usNum = usRelPacketsSent;
            pSP->pNext = pSentRelPackets;

            pSentRelPackets = pSP;
        } else {
            pSP->usNum = usPacketsSent;
            pSP->pNext = pSentPackets;

            pSentPackets = pSP;
        }
    }

    unsigned int tTRQ = pPacket->pImage->sect.pBaseHeader->GetAbsoluteTimeToQueue ();
    unsigned int tBas = pPacket->pImage->sect.pUserHeader->GetSentTime ();
    unsigned int tNow = scutil_now ();

    if (tTRQ != INFINITE)
        pPacket->pImage->sect.pBaseHeader->SetAbsoluteTimeToQueue (tTRQ - (tNow - tBas));


    ++gSessionMan->fBusy;
    pNewImage = scqman_DupImage(pPacket->pImage);

    pPacket->pImage->sect.pBaseHeader->SetAbsoluteTimeToQueue (tTRQ);
    pPacket->pImage->sect.pBaseHeader->IncludeSession (FALSE);

    pQueue->FreeFromMemory(pPacket);

    if (! pNewImage) {
        scerror_DebugOutM (VERBOSE_MASK_SESSION, (L"No memory - failing session %s...\n", lpszHostName));
        --gSessionMan->fBusy;
        return iStatusCode;
    }
    BOOL fSecure = IsSecure();

    // If we are to forward an SRMP message create and send it in ForwardSrmpMessage.
    if (pNewImage->sect.pUserHeader->SrmpIsIncluded()) {
        WCHAR *szQueueName;
        if (! gMachine->RouteLocalReverseLookup(pQueue->lpszQueueName,&szQueueName))
            szQueueName = pQueue->lpszQueueName;

        CSrmpFwd cSrmpFwd(this,szQueueName,pNewImage->sect.pFwdViaHeader ? (WCHAR*)pNewImage->sect.pFwdViaHeader->GetData() : L"");
        gMem->Unlock();

        if (!cSrmpFwd.IsInitailized())
            goto done;
        
        iStatusCode = ForwardSrmpMessage(&cSrmpFwd,pNewImage,pQueue->lpszQueueName);
        goto done;
    }

    gMem->Unlock();

    if (NULL == (szSoapEnv = BuildSoapEnvelope(pNewImage,&ccSoapEnv)))
        goto done;

    memset(&ioPacket,0,sizeof(ioPacket));
    ioPacket.pszHeaders = szHeaders;

    if (! BuildHttpHeadersAndMimeBody(pNewImage,szSoapEnv,ccSoapEnv,fSecure,&ioPacket,sizeof(szHeaders),cBodyBuf))
        goto done;

    if (NULL == (szURL = AllocURL(pQueue->lpszQueueName)))
        goto done;

    iStatusCode = SendHttpMsg(szURL,fSecure,&ioPacket); 
done:
    SVSUTIL_ASSERT(! gMem->IsLocked());

    if (szURL)
        g_funcFree(szURL,g_pvFreeData);

    if (szSoapEnv)
        g_funcFree(szSoapEnv,g_pvFreeData);

    if (pNewImage)
        g_funcFree(pNewImage,g_pvFreeData);
    
    gMem->Lock();
    --gSessionMan->fBusy;
    return iStatusCode;
}


CHAR *BuildSoapEnvelope(ScPacketImage *pImage, DWORD *pccSendBuffer) {
    SVSXMLEncode    xmlEncode(4096);
    CUserHeader     *pUserHeader       = pImage->sect.pUserHeader;
    CSoapSection    *pSoapBodySection  = pImage->sect.pSoapBodySection;
    DWORD           cbAlloc = 16384;
    CHAR            *szSendBuffer;

    // Initially allocate a large buffer to hopefully eliminate need for reallocs in most cases.
    if (pUserHeader->SoapIsIncluded())
        cbAlloc += pSoapBodySection->GetDataLengthInWCHARs();

    if (!xmlEncode.AllocMem(cbAlloc))
        return NULL;

    xmlEncode.Append((CHAR*)cszXMLHeader,SVSUTIL_CONSTSTRLEN(cszXMLHeader)*sizeof(WCHAR));
    xmlEncode.Append((CHAR*)cszEnvelopeStartNS,SVSUTIL_CONSTSTRLEN(cszEnvelopeStartNS)*sizeof(WCHAR));
    xmlEncode.StartTag(cszHeaderNS);

    // <HEADER> section
    if (! SendPath(&xmlEncode,pImage))
        return NULL;

    if (! SendProps(&xmlEncode,pImage))
        return NULL;

    if (! SendServices(&xmlEncode,pImage))
        return NULL;

    if (! SendStream(&xmlEncode,pImage))
        return NULL;

    if (! SendStreamRcpt(&xmlEncode,pImage))
        return NULL;

    if (! SendDeliveryRcpt(&xmlEncode,pImage))
        return NULL;

    if (! SendCommitRcpt(&xmlEncode,pImage))
        return NULL;

    if (! SendMsmq(&xmlEncode,pImage))
        return NULL;

    if (! SendUserHeader(&xmlEncode,pImage))
        return NULL;

    if (! xmlEncode.EndTag(cszHeaderNS))
        return NULL;

    // <BODY> section
    if (! xmlEncode.StartTag(cszBodyNS))
        return NULL;

    if (pUserHeader->SoapIsIncluded()) {
        if (pSoapBodySection->GetPointerToData()) {
            if (!xmlEncode.Encode(pSoapBodySection->GetPointerToData()))
                return NULL;
        }
    }
    if (! xmlEncode.EndTag(cszBodyNS))
        return NULL;

    if (! xmlEncode.EndTag(cszEnvelopeNS))
        return NULL;

    if (! xmlEncode.Append((char*)L"\0",sizeof(WCHAR)))
        return NULL;

#if defined (DEBUG) || defined (_DEBUG)
    svsutil_validateXML((BSTR)xmlEncode.pBuffer);
#endif

    // covert SOAP Envelope to UTF8
    DWORD cp = CP_UTF8;

    if (0 == (*pccSendBuffer = WideCharToMultiByte(cp, 0, (WCHAR*)xmlEncode.pBuffer, -1, NULL, 0, 0, 0))) {
        cp = CP_ACP;
        if (0 == (*pccSendBuffer = WideCharToMultiByte(cp, 0, (WCHAR*)xmlEncode.pBuffer, -1, NULL, 0, 0, 0)))
            return NULL;
    }

    if (NULL == (szSendBuffer = (CHAR*) g_funcAlloc(*pccSendBuffer,g_pvAllocData)))
        return NULL;

    WideCharToMultiByte(cp, 0, (WCHAR*)xmlEncode.pBuffer, -1, szSendBuffer, *pccSendBuffer, 0, 0);
    *pccSendBuffer -= 1;
    return szSendBuffer;
}


BOOL IsBodyIncluded(ScPacketImage *pImage) {
    CUserHeader            *pUserHeader             = pImage->sect.pUserHeader;
    CCompoundMessageHeader *pCompoundMessageHeader  = pImage->sect.pCompoundMessageHeader;
    CPropertyHeader        *pPropHeader             = pImage->sect.pPropHeader;

    if (pUserHeader->SrmpIsIncluded())
        return (pCompoundMessageHeader && pCompoundMessageHeader->GetBodySizeInBytes() != 0);
    return (pPropHeader->GetBodySize() != 0);
}


#define BOUNDARY_VALUE "MSMQ - SOAP boundary, %d"
const char cszApplicationContentType[]  = "application/octet-stream";
const char cszMultipartContentType[]    = "multipart/related";
#define MIME_ID_FMT_A        "%.*s" GUID_FORMAT_A

// MIME_ID_FMT_A

int BuildHttpHeadersAndMimeBody(ScPacketImage *pImage, CHAR *szSoapEnv, DWORD ccSoapEnv, BOOL fSecure, PSrmpIOCTLPacket pIOPacket, DWORD ccHeaders, SVSSimpleBuffer &cBodyBuf) {
    CUserHeader     *pUserHeader = pImage->sect.pUserHeader;
    CPropertyHeader *pPropHeader = pImage->sect.pPropHeader;
    const UCHAR* szBody = NULL;
    DWORD        cbBody = 0;
    const UCHAR* szExtension = NULL;
    DWORD        cbExtension = 0;
    PSTR         szLastWritten;

    pIOPacket->fSSL = fSecure;
    SVSUTIL_ASSERT(pIOPacket->pszHeaders); // write buffer passed into us by caller

    if (IsBodyIncluded(pImage)) {
        CCompoundMessageHeader *pCompoundMessageHeader  = pImage->sect.pCompoundMessageHeader;
        szBody = pUserHeader->SrmpIsIncluded() ? pCompoundMessageHeader->GetPointerToBody()   : pPropHeader->GetBodyPtr();
        cbBody = pUserHeader->SrmpIsIncluded() ? pCompoundMessageHeader->GetBodySizeInBytes() : pPropHeader->GetBodySize();
    }

    if (pPropHeader->GetMsgExtensionSize()) {
        szExtension = pPropHeader->GetMsgExtensionPtr();
        cbExtension = pPropHeader->GetMsgExtensionSize();
    }

    if (szBody || szExtension) {  // complex data type
        srand (GetTickCount());

        DWORD  dwBoundaryId;
        DWORD  cbAlloc = ccSoapEnv + cbBody + cbExtension + 2048;

        if (rand_s((UINT *)&dwBoundaryId))
            return FALSE;
        // Get all we need at once so there's no chance of needing to realloc.
        // For this reason we also don't check return values from StringCch functions.
        if (! cBodyBuf.AllocMem(cbAlloc))
            return FALSE;

        // MIME Body
        StringCchPrintfExA((CHAR*)cBodyBuf.pBuffer,cBodyBuf.BufferRemaining(),&szLastWritten,NULL,0,
                              BOUNDARY_HYPHEN BOUNDARY_VALUE "\r\n"
                              "Content-Type: %s; charset=UTF-8\r\nContent-Length: %d\r\n\r\n",
                              dwBoundaryId,cszTextXML,ccSoapEnv);

        cBodyBuf.uiNextIn = (UCHAR*)szLastWritten - cBodyBuf.pBuffer;
        cBodyBuf.Append(szSoapEnv,ccSoapEnv);

        if (szBody) {
            StringCchPrintfExA((CHAR*)cBodyBuf.pBuffer + cBodyBuf.uiNextIn,
                                cBodyBuf.BufferRemaining(),&szLastWritten,NULL,0,
                                BOUNDARY_HYPHEN BOUNDARY_VALUE "\r\n"
                                  "Content-Type: %s\r\n"
                                "Content-Length: %d\r\n"
                                "Content-Id: " MIME_ID_FMT_A "\r\n\r\n",
                                dwBoundaryId,cszApplicationContentType,
                                cbBody,
                                ccMimeBodyIdLen, cszMimeBodyId,
                                GUID_ELEMENTS((&gMachine->guid)));

            cBodyBuf.uiNextIn = (UCHAR*)szLastWritten - cBodyBuf.pBuffer;
            memcpy(cBodyBuf.pBuffer+cBodyBuf.uiNextIn,szBody,cbBody);
            cBodyBuf.uiNextIn += cbBody;
        }

        if (szExtension) {
            StringCchPrintfExA((CHAR*)cBodyBuf.pBuffer + cBodyBuf.uiNextIn,
                               cBodyBuf.BufferRemaining(),&szLastWritten,NULL,0,
                               BOUNDARY_HYPHEN BOUNDARY_VALUE "\r\n"
                                 "Content-Type: %s\r\n"
                               "Content-Length: %d\r\n"
                               "Content-Id: " MIME_ID_FMT_A "\r\n\r\n",
                               dwBoundaryId,cszApplicationContentType,
                               cbExtension,
                               ccMimeExtensionIdLen,cszMimeExtensionId,
                               GUID_ELEMENTS((&gMachine->guid)));

            cBodyBuf.uiNextIn = (UCHAR*)szLastWritten - cBodyBuf.pBuffer;
            memcpy(cBodyBuf.pBuffer+cBodyBuf.uiNextIn,szExtension,cbExtension);
            cBodyBuf.uiNextIn += cbExtension;
        }

        StringCchPrintfExA((CHAR*)cBodyBuf.pBuffer + cBodyBuf.uiNextIn,
                           cBodyBuf.BufferRemaining(),&szLastWritten,NULL,0,
                           BOUNDARY_HYPHEN BOUNDARY_VALUE BOUNDARY_HYPHEN "\r\n",dwBoundaryId);

        cBodyBuf.uiNextIn = (UCHAR*)szLastWritten - cBodyBuf.pBuffer;

        // HTTP headers
        StringCchPrintfExA(pIOPacket->pszHeaders,ccHeaders,&szLastWritten,NULL,0,
                            "Content-Type: %s; boundary=\"" BOUNDARY_VALUE "\"; type=text/xml\r\n"
                            "SOAPAction: \"MSMQMessage\"\r\n"
                            "Proxy-Accept: NonInteractiveClient\r\n",
                            cszMultipartContentType,dwBoundaryId
                            );

        pIOPacket->cbHeaders   = szLastWritten - pIOPacket->pszHeaders;
        pIOPacket->pszPost     = (CHAR*)cBodyBuf.pBuffer;
        pIOPacket->cbPost      = cBodyBuf.uiNextIn;
        pIOPacket->contentType = CONTENT_TYPE_MIME;
    }
    else { // simple type
        StringCchPrintfExA(pIOPacket->pszHeaders,ccHeaders,&szLastWritten,NULL,0,
                           "Content-Type: %s\r\n"
                           "SOAPAction: \"MSMQMessage\"\r\n" 
                           "Proxy-Accept: NonInteractiveClient\r\n",
                           cszTextXML
                           );

        pIOPacket->cbHeaders   = szLastWritten - pIOPacket->pszHeaders;
        pIOPacket->pszPost     = szSoapEnv;
        pIOPacket->cbPost      = ccSoapEnv;
        pIOPacket->contentType = CONTENT_TYPE_XML;
    }
    return TRUE;
}

int ScSession::SendHttpMsg(CHAR *szURL, BOOL fSecure, PSrmpIOCTLPacket pIOPacket) {
    HANDLE hRequest;
    DWORD  dwFlags    = 0;
    int    iStatusCode=500;

    CSrmpCallBack openCallback;

    dwFlags  =  (g_fUseWininet ? INTERNET_FLAG_KEEP_CONNECTION : 0);
    dwFlags  |= (fSecure       ? INTERNET_FLAG_SECURE          : 0);

    gMem->Lock();
    if (glServiceState != SERVICE_STATE_ON) {
        openCallback.Failed();
        gMem->Unlock();
        return 503;
    }
    hRequest = HttpOpenRequestA(hInternetConnect,"POST",szURL,NULL,NULL,NULL,dwFlags,(DWORD)&openCallback);

    if (!hRequest) {
        openCallback.Failed();
        scerror_DebugOutM(VERBOSE_MASK_SRMP,(L"HttpOpenRequest fails, GLE=0x%08x\r\n",GetLastError()));
    }
    gMem->Unlock();

    if (hRequest) {
        DWORD cbStatus = sizeof(iStatusCode);
        
        if (!HttpSendRequestA(hRequest,pIOPacket->pszHeaders,pIOPacket->cbHeaders,pIOPacket->pszPost,pIOPacket->cbPost))
            openCallback.Wait(30000);

        if (openCallback.dwError == 0)  {
            if (!HttpQueryInfo(hRequest,HTTP_QUERY_STATUS_CODE | HTTP_QUERY_FLAG_NUMBER, &iStatusCode,&cbStatus,NULL))
                iStatusCode = 500;
        }
#if defined (SC_VERBOSE)
        else 
            scerror_DebugOut(VERBOSE_MASK_SRMP,L"HttpSendRequest fails, openCallback.dwResult=%d, GLE=0x%08x\r\n",openCallback.dwResult,openCallback.dwError);
#endif
        gMem->Lock();
        InternetCloseHandle(hRequest);
        hRequest = 0;
        gMem->Unlock();
    }

    return iStatusCode;
}


void CALLBACK SrmpCallback(HINTERNET hInternet,DWORD_PTR dwContext,DWORD dwInternetStatus,LPVOID lpvStatusInformation,DWORD dwStatusInformationLength) {
    CSrmpCallBack* pThis = (CSrmpCallBack*)(dwContext);

    switch(dwInternetStatus) {
        case INTERNET_STATUS_REQUEST_COMPLETE:     {
            INTERNET_ASYNC_RESULT* pAsyncResult = reinterpret_cast<INTERNET_ASYNC_RESULT*>(lpvStatusInformation);

            pThis->dwError  = pAsyncResult->dwError;
            pThis->dwResult = pAsyncResult->dwResult;
            SetEvent(pThis->hEventComplete);
        }
        break;

        case INTERNET_STATUS_HANDLE_CLOSING:
            SetEvent(pThis->hEventClosed);
        break;
    }
}

BOOL IsDirectHttpFormatName(const QUEUE_FORMAT* pQueueFormat) {
    DIRECT_TOKEN_TYPE ddt;

    if (pQueueFormat->GetType() == QUEUE_FORMAT_TYPE_DIRECT && 
        ParseDirectTokenString(pQueueFormat->DirectID(),ddt)) {
        return (ddt == DT_HTTP || ddt == DT_HTTPS);
    }

    return FALSE;
}



BOOL QueueFormatToUri(SVSXMLEncode *pXmlEncode, QUEUE_FORMAT *pqf) {
    WCHAR szBuf[MAX_PATH];
    WCHAR *pszTrav = szBuf;
    WCHAR *pszBufEnd = szBuf + MAX_PATH;
    WCHAR *szURI   = NULL;
    WCHAR *szFormatName = NULL;
    BOOL  fRet = FALSE;
    HRESULT hr = S_OK;
    
    
    switch (pqf->GetType()) {
        case QUEUE_FORMAT_TYPE_DIRECT : {
            DWORD ccFormatName = SCUTIL_DIRECT_PREFIX_SZ + wcslen(pqf->DirectID()) + 1;
            if (ccFormatName > SVSUTIL_CONSTSTRLEN(szBuf)) {
                if (NULL == (szFormatName = (WCHAR*) g_funcAlloc(sizeof(WCHAR)*ccFormatName,g_pvAllocData)))
                    goto done;
            }
            else {
                szFormatName = szBuf;
                ccFormatName = _countof(szBuf);
            }
            hr = StringCchCopyW(szFormatName,ccFormatName,SCUTIL_DIRECT_PREFIX);
            ASSERT(SUCCEEDED(hr));
            hr = StringCchCopyW(szFormatName+SCUTIL_DIRECT_PREFIX_SZ,ccFormatName - SCUTIL_DIRECT_PREFIX_SZ,pqf->DirectID());
            ASSERT(SUCCEEDED(hr));
        }
        break;

        case QUEUE_FORMAT_TYPE_PUBLIC : {
            hr = StringCchCopyExW(szBuf,_countof(szBuf),MSMQ_SC_FORMAT_PUBLIC,&pszTrav,NULL,0);
            ASSERT(SUCCEEDED(hr));
            const GUID *pGuid = &pqf->PublicID();
            hr = StringCchPrintfW (pszTrav,pszBufEnd-pszTrav,GUID_FORMAT,GUID_ELEMENTS(pGuid));
            ASSERT(SUCCEEDED(hr));
            szFormatName = szBuf;
        }
        break;

        case QUEUE_FORMAT_TYPE_PRIVATE: {
            hr = StringCchCopyExW(szBuf,_countof(szBuf),MSMQ_SC_FORMAT_PRIVATE,&pszTrav,NULL,0);
            ASSERT(SUCCEEDED(hr));
            
            const OBJECTID *pObj = &pqf->PrivateID();
            const GUID *pGuid = &pObj->Lineage;
            hr = StringCchPrintfExW (pszTrav,pszBufEnd-pszTrav,&pszTrav,NULL,0,GUID_FORMAT FN_PRIVATE_SEPERATOR,GUID_ELEMENTS(pGuid));
            ASSERT(SUCCEEDED(hr));
            hr = StringCchPrintfW(pszTrav,pszBufEnd-pszTrav,L"%x",pObj->Uniquifier);
            ASSERT(SUCCEEDED(hr));
            szFormatName = szBuf;
        }
        break;

        default:
            SVSUTIL_ASSERT(0);
    }

    gMem->Lock();
    if (gMachine->RouteLocalReverseLookup(szFormatName,&szURI)) {
        // if we've hit a reverse in lookup table then we originally received
        // message as a free-form URI.  Send it out like this too.
        gMem->Unlock();
        fRet = pXmlEncode->Encode(szURI);
        goto done;
    }
    gMem->Unlock();

    // send direct HTTP without modifications.
    if (IsDirectHttpFormatName(pqf)) 
        fRet = pXmlEncode->Encode(pqf->DirectID());
    // append MSMQ: in front of remaining types.
    else if (pXmlEncode->Encode(cszMSMQPrefix,ccMSMQPrefix))
        fRet = pXmlEncode->Encode(szBuf);
done:
    SVSUTIL_ASSERT(! gMem->IsLocked());

    if (szFormatName != szBuf)
        g_funcFree(szFormatName,g_pvFreeData);
    
    return fRet;
}


BOOL CreateViaList(SVSXMLEncode *pXmlEncode, CDataHeader *pViaHeader, BOOL fSkipFirstElement) {
    int   iLen      = pViaHeader->GetDataLengthInWCHARs();
    WCHAR *szStart  = (WCHAR*) pViaHeader->GetData();
    WCHAR *pszTrav;

    if (fSkipFirstElement) {
        // skip past 1st entry, which is current machine (not forwarded along) in some cases.
        int ccFirstElement = wcslen(szStart) + 1;
        szStart += ccFirstElement;
        iLen    -= ccFirstElement;

        SVSUTIL_ASSERT(iLen > 0); // if there's only one element in fwd list, this function shouldn't have been called.
    }
    pszTrav  = szStart;

    // VIA list is stored as a NULL separated multi-string.  Elements
    // may be set to \0 to indicate an empty tag.
    while (pszTrav - szStart < iLen) {
        if (*pszTrav) {
            if (! pXmlEncode->SetElement(cszVia,pszTrav))
                return FALSE;
        }
        else if (! pXmlEncode->SetEmptyElement(cszVia))
            return FALSE;

        pszTrav += wcslen(pszTrav)+1;
    }
    SVSUTIL_ASSERT(pszTrav-szStart == iLen);
    return TRUE;
}


DWORD GetExpiresFromPacketImage(ScPacketImage *pImage) {
    DWORD dwDelta;
    DWORD dwTimeout;
    CBaseHeader     *pBaseHeader = pImage->sect.pBaseHeader;
    CUserHeader     *pUserHeader = pImage->sect.pUserHeader;

    dwDelta = pUserHeader->GetTimeToLiveDelta();
    if (dwDelta == INFINITE)
        dwTimeout = INFINITE;
    else
        dwTimeout = dwDelta + pBaseHeader->GetAbsoluteTimeToQueue();

    return min(dwTimeout, LONG_MAX);
}

//
// <Path> Section.
//
BOOL SendPath(SVSXMLEncode *pXmlEncode, ScPacketImage *pImage) {
    CPropertyHeader *pPropHeader = pImage->sect.pPropHeader;
    CUserHeader     *pUserHeader = pImage->sect.pUserHeader;
    QUEUE_FORMAT    qf;
    OBJECTID MessageId;

    if (! pXmlEncode->Append((CHAR*)cszPathNS,SVSUTIL_CONSTSTRLEN(cszPathNS)*sizeof(WCHAR)) ||
        ! pXmlEncode->Append((CHAR*)cszMustUnderstandNS,SVSUTIL_CONSTSTRLEN(cszMustUnderstandNS)*sizeof(WCHAR)))
        return FALSE;

    // <action>
    if (! pXmlEncode->StartTag(cszAction))
        return FALSE;

    // even if there's no title we put <action>MSMQ:</action>, required since <action> is a required field.  Like XP.
    if (! pXmlEncode->Encode(cszMSMQPrefix))
        return FALSE;

    if (pPropHeader->GetTitleLength()) {
        if (! pXmlEncode->Encode(pPropHeader->GetTitlePtr()))
            return FALSE;
    }

    if (! pXmlEncode->EndTag(cszAction))
        return FALSE;

    //<to>
    pUserHeader->GetDestinationQueue(&qf);
    if (! pXmlEncode->StartTag(cszTo))
        return FALSE;

    if (!QueueFormatToUri(pXmlEncode,&qf))
        return FALSE;

    if (! pXmlEncode->EndTag(cszTo))
        return FALSE;

    // <id>
    pUserHeader->GetMessageID(&MessageId);
    if (! SetMsgID(pXmlEncode,&MessageId))
        return FALSE;


    if (pImage->flags.fSoapExtIncluded) {
        CSoapExtSection *pSoapExt = pImage->sect.pSoapExtHeaderSection;

        if (pSoapExt->GetFromLengthInWCHARs()) {
            if (! pXmlEncode->SetElement(cszFrom,pSoapExt->GetFrom(),pSoapExt->GetFromLengthInWCHARs()))
                return FALSE;
        }

        if (pSoapExt->GetRelatesToLengthInWCHARs()) {
            if (! pXmlEncode->SetElement(cszRelatesTo,pSoapExt->GetRelatesTo(),pSoapExt->GetRelatesToLengthInWCHARs()))
                return FALSE;
        }
    }

    if (pImage->sect.pFwdViaHeader && GetNextHopOnFwdList(pImage)) {
        if (! pXmlEncode->StartTag(cszFwd) ||
            ! CreateViaList(pXmlEncode,pImage->sect.pFwdViaHeader,pImage->flags.fSRMPGenerated) ||
            ! pXmlEncode->EndTag(cszFwd))
            return FALSE;
    }

    if (pImage->sect.pRevViaHeader) {
        if (! pXmlEncode->StartTag(cszRev) ||
            ! CreateViaList(pXmlEncode,pImage->sect.pRevViaHeader,FALSE) ||
            ! pXmlEncode->EndTag(cszRev))
            return FALSE;
    }

    // <rev><via>
    // messages generated on local device (not routed) use MSMQ msg props
    pUserHeader->GetResponseQueue(&qf);

    if (qf.GetType() != QUEUE_FORMAT_TYPE_UNKNOWN) {
        if (! pXmlEncode->StartTag(cszRev) || ! pXmlEncode->StartTag(cszVia))
            return FALSE;

        if (!QueueFormatToUri(pXmlEncode,&qf))
            return FALSE;

        if (! pXmlEncode->EndTag(cszVia) || ! pXmlEncode->EndTag(cszRev))
            return FALSE;
    }
    return pXmlEncode->EndTag(cszPath);
}


//
// <properties> Section.
//
BOOL SendProps(SVSXMLEncode *pXmlEncode, ScPacketImage *pImage) {
    CUserHeader     *pUserHeader = pImage->sect.pUserHeader;
    WCHAR szBuf[MAX_PATH];
    DWORD dw;

    if (! pXmlEncode->Append((CHAR*)cszPropertiesNS,SVSUTIL_CONSTSTRLEN(cszPropertiesNS)*sizeof(WCHAR)) ||
        ! pXmlEncode->Append((CHAR*)cszMustUnderstandNS,SVSUTIL_CONSTSTRLEN(cszMustUnderstandNS)*sizeof(WCHAR)))
        return FALSE;

    // <expiresAt>
    dw = GetExpiresFromPacketImage(pImage);
    if (! UtlTimeToIso860Time(dw,szBuf,MAX_PATH))
        return FALSE;
    
    if (! pXmlEncode->SetElement(cszExpiresAt,szBuf))
        return FALSE;

    // <sentAt>
    if (! UtlTimeToIso860Time(pUserHeader->GetSentTime(),szBuf,MAX_PATH))
        return FALSE;

    if (! pXmlEncode->SetElement(cszSentAt,szBuf))
        return FALSE;

    return pXmlEncode->EndTag(cszProperties);
}


//
// <Services> Section
//
BOOL SendServices(SVSXMLEncode *pXmlEncode, ScPacketImage *pImage) {
    CPropertyHeader *pPropHeader = pImage->sect.pPropHeader;
    CUserHeader     *pUserHeader = pImage->sect.pUserHeader;

    USHORT            ackType = pPropHeader->GetAckType();
    QUEUE_FORMAT      adminQueue;
    QUEUE_FORMAT_TYPE qType;
    UCHAR             delivery = pUserHeader->GetDelivery();

    pUserHeader->GetAdminQueue(&adminQueue);
    qType = adminQueue.GetType();

    // Do we have required data to fill <services> tag, if not we're done?
    if (delivery != MQMSG_DELIVERY_RECOVERABLE && 
        (0 == (ackType & (MQMSG_DELIVERY_RECOVERABLE | MQMSG_ACKNOWLEDGMENT_POS_ARRIVAL | MQMSG_ACKNOWLEDGMENT_NEG_RECEIVE)) ||
        (qType == QUEUE_FORMAT_TYPE_UNKNOWN)))
        return TRUE;
    
    // <services...> root
    if (! pXmlEncode->Append((CHAR*)cszServicesNS,SVSUTIL_CONSTSTRLEN(cszServicesNS)*sizeof(WCHAR)) ||
        ! pXmlEncode->Append((CHAR*)cszMustUnderstandNS,SVSUTIL_CONSTSTRLEN(cszMustUnderstandNS)*sizeof(WCHAR)))
        return FALSE;

    // <durable>
    if (delivery == MQMSG_DELIVERY_RECOVERABLE) {
        if (! pXmlEncode->SetEmptyElement(cszDurable))
            return FALSE;
    }

    //
    // <deliveryReceiptRequest> section.
    //
    if ((ackType & MQMSG_ACKNOWLEDGMENT_POS_ARRIVAL) && (qType != QUEUE_FORMAT_TYPE_UNKNOWN)) {
        if (! pXmlEncode->StartTag(cszDeliveryReceiptRequest) ||
            ! pXmlEncode->StartTag(cszSendTo))
            return FALSE;

        if (! QueueFormatToUri(pXmlEncode,&adminQueue))
            return FALSE;

        if (! pXmlEncode->EndTag(cszSendTo) ||
            ! pXmlEncode->EndTag(cszDeliveryReceiptRequest))
            return FALSE;
    }

    //
    // <commitReceiptRequest> section.
    //
    if (ackType & (MQMSG_ACKNOWLEDGMENT_POS_RECEIVE | MQMSG_ACKNOWLEDGMENT_NEG_RECEIVE) && (qType != QUEUE_FORMAT_TYPE_UNKNOWN)) {
        if (! pXmlEncode->StartTag(cszCommitmentReceiptRequest))
            return FALSE;

        if (ackType & MQMSG_ACKNOWLEDGMENT_POS_RECEIVE) {
            if (! pXmlEncode->SetEmptyElement(cszPositiveOnly))
                return FALSE;
        }

        if (ackType & MQMSG_ACKNOWLEDGMENT_NEG_RECEIVE) {
            if (! pXmlEncode->SetEmptyElement(cszNegativeOnly))
                return FALSE;
        }

        if (! pXmlEncode->StartTag(cszSendTo)          ||
            ! QueueFormatToUri(pXmlEncode,&adminQueue) ||
            ! pXmlEncode->EndTag(cszSendTo))
            return FALSE;

        if (! pXmlEncode->EndTag(cszCommitmentReceiptRequest))
            return FALSE;
    }
    
    return pXmlEncode->EndTag(cszServices);
}


//
// <stream> and <stream><start>
BOOL SendStream(SVSXMLEncode *pXmlEncode, ScPacketImage *pImage) {
    CUserHeader     *pUserHeader   = pImage->sect.pUserHeader;
    CXactHeader     *pXactHeader   = pImage->sect.pXactHeader;
    HRESULT hr = S_OK;

    WCHAR szBuf[2000];
    const GUID *pGuid = NULL;

    if (! pUserHeader->IsOrdered())
        return TRUE;

    if (! pXmlEncode->Append((CHAR*)cszStreamNS,SVSUTIL_CONSTSTRLEN(cszStreamNS)*sizeof(WCHAR)) ||
        ! pXmlEncode->Append((CHAR*)cszMustUnderstandNS,SVSUTIL_CONSTSTRLEN(cszMustUnderstandNS)*sizeof(WCHAR)))
        return FALSE;

    // <streamID>
    pGuid = pUserHeader->GetSourceQM();
    StringCchPrintfW(szBuf,SVSUTIL_ARRLEN(szBuf),L"uri:" GUID_FORMAT L"\\%I64d", 
                     GUID_ELEMENTS(pGuid),pXactHeader ? pXactHeader->GetSeqID() : 0);

    if (! pXmlEncode->SetElement(cszStreamId,szBuf))
        return FALSE;

    // <current>
    hr = StringCchPrintfW(szBuf,_countof(szBuf),L"%I64d",pXactHeader ? pXactHeader->GetSeqN() : 0);
    ASSERT(SUCCEEDED(hr));
    
    if (! pXmlEncode->SetElement(cszCurrent,szBuf))
        return FALSE;

    // <previous>
    hr = StringCchPrintfW(szBuf,_countof(szBuf),L"%I64d",pXactHeader ? pXactHeader->GetPrevSeqN() : 0);
    ASSERT(SUCCEEDED(hr));
    if (! pXmlEncode->SetElement(cszPrevious,szBuf))
        return FALSE;

    //
    // <stream><start>
    //
    if (pXactHeader && (0 == pXactHeader->GetPrevSeqN())) {
        static const WCHAR szOrderQueue[] = MSMQ_SC_PATHNAME_PRIVATE L"/" MSMQ_SC_ORDERQUEUENAME;
        WCHAR *szURI;
        if (! pXmlEncode->StartTag(cszStart))
            return FALSE;

        gMem->Lock();
        const WCHAR *szVroot = gMachine->VRootList[0].wszVRoot ? gMachine->VRootList[0].wszVRoot : cszMSMQVroot;
        // Prints out in format http://ceMachineName/MSMQ\PRIVATE$/order_queue$
        // VROot is of the form "/VRootName/" so don't add extra "/"'s in sprintf.
        StringCchPrintfW(szBuf,SVSUTIL_ARRLEN(szBuf),
                         L"HTTP://%s%s%s",gMachine->lpszHostName,szVroot,szOrderQueue);

        if (! gMachine->RouteLocalReverseLookup((WCHAR*)szBuf,&szURI))
            szURI = szBuf;

        gMem->Unlock();

        if (! pXmlEncode->SetElement(cszSendReceiptsTo,szURI))
            return FALSE;

        if (! pXmlEncode->EndTag(cszStart))
            return FALSE;
    }

    return pXmlEncode->EndTag(cszStream);
}

//
// <streamReceipt> section.
//
BOOL SendStreamRcpt(SVSXMLEncode *pXmlEncode, ScPacketImage *pImage) {
    CUserHeader     *pUserHeader   = pImage->sect.pUserHeader;
    CEodAckHeader   *pEodAckHeader = pImage->sect.pEodAckHeader;
    WCHAR szBuf[MAX_PATH];
    WCHAR *szBufEnd = szBuf + MAX_PATH;
    WCHAR *szStreamID = szBuf;
    WCHAR *szTrav;
    BOOL fRet = FALSE;
    HRESULT hr = S_OK;

    if (! pUserHeader->EodAckIsIncluded())
        return TRUE;

    // <streamReceipt>
    if (! pXmlEncode->StartTag(cszStreamReceipt))
        goto done;

    SVSUTIL_ASSERT(pEodAckHeader->GetStreamIdSizeInBytes() % 2 == 0); // should be even since we're storing UNICODE

    if (pEodAckHeader->GetStreamIdSizeInBytes() > sizeof(szBuf)) {
        int cchBuf = pEodAckHeader->GetStreamIdSizeInBytes() + 1;
        if (NULL == (szStreamID = (WCHAR*) g_funcAlloc(cchBuf,g_pvAllocData)))
            goto done;
        szBufEnd = szStreamID + cchBuf;
    }
    hr = StringCchCopyExW(szStreamID,szBufEnd-szStreamID,(WCHAR*)pEodAckHeader->GetPointerToStreamId(),&szTrav,NULL,0);
    ASSERT(SUCCEEDED(hr));

    if (pEodAckHeader->GetSeqId() != i64NoneMSMQSeqId) {
        hr = StringCchPrintfW(szTrav,szBufEnd-szTrav,L"\\%I64d",pEodAckHeader->GetSeqId());
        ASSERT(SUCCEEDED(hr));
    }  
      

    if (!pXmlEncode->SetElement(cszStreamId,szStreamID))
        goto done;

    // <lastOrdinal>
    hr = StringCchPrintfW(szBuf,_countof(szBuf),L"%I64d",pEodAckHeader->GetSeqNum());
    ASSERT(SUCCEEDED(hr));
    if (!pXmlEncode->SetElement(cszlastOrdinal,szBuf))
        goto done;

    if (! pXmlEncode->EndTag(cszStreamReceipt))
        goto done;

    fRet = TRUE;
done:
    if (szStreamID && szStreamID != szBuf)
        g_funcFree(szStreamID,g_pvFreeData);

    return fRet;
}


BOOL SetMsgCurrentTime(SVSXMLEncode *pXmlEncode, const WCHAR *szElementName) {
    WCHAR szBuf[MAX_PATH];

    if (! UtlTimeToIso860Time(scutil_now(),szBuf,MAX_PATH))
        return FALSE;

    return pXmlEncode->SetElement((WCHAR*)szElementName,szBuf);
}

BOOL SetMsgID(SVSXMLEncode *pXmlEncode, OBJECTID *pMsgId) {
    WCHAR szBuf[MAX_PATH];
    const GUID *pGuid = &pMsgId->Lineage;

    if (FAILED(StringCchPrintfW(szBuf,MAX_PATH,UUIDREFERENCE_PREFIX L"%d" UUIDREFERENCE_SEPERATOR GUID_FORMAT,
               pMsgId->Uniquifier,GUID_ELEMENTS(pGuid))))
    {
        SVSUTIL_ASSERT(0);
        return FALSE;
    }

    return pXmlEncode->SetElement(cszId,szBuf);
}

//
// <deliveryReceipt> section
//
BOOL SendDeliveryRcpt(SVSXMLEncode *pXmlEncode, ScPacketImage *pImage) {
    CPropertyHeader *pPropHeader   = pImage->sect.pPropHeader;

    // cszDeliveryReceipt
    if (pPropHeader->GetClass() != MQMSG_CLASS_ACK_REACH_QUEUE)
        return TRUE;

    if (! pXmlEncode->StartTag(cszDeliveryReceipt))
        return FALSE;
    
    // <receivedAt>
    if (! SetMsgCurrentTime(pXmlEncode,cszreceivedAt))
        return FALSE;

    // <id>
    if (! SetMsgID(pXmlEncode,(OBJECTID*)pPropHeader->GetCorrelationID()))
        return FALSE;

    return pXmlEncode->EndTag(cszDeliveryReceipt);
}



//
// <commitReceipt> section
//
BOOL SendCommitRcpt(SVSXMLEncode *pXmlEncode, ScPacketImage *pImage) {
    CPropertyHeader *pPropHeader   = pImage->sect.pPropHeader;

    if (!MQCLASS_RECEIVE(pPropHeader->GetClass()))
        return TRUE;

    if (! pXmlEncode->StartTag(cszCommitmentReceipt))
        return FALSE;

    // <decidedAt>
    if (!SetMsgCurrentTime(pXmlEncode,cszDecidedAt))
        return FALSE;

    // <decision>
    const WCHAR* szDecideValue = MQCLASS_POS_RECEIVE(pPropHeader->GetClass()) ? cszPositive : cszNegative;
    if (! pXmlEncode->SetElement(cszDecision,szDecideValue))
        return FALSE;

    // <id>
    if (! SetMsgID(pXmlEncode,(OBJECTID*)pPropHeader->GetCorrelationID()))
        return FALSE;
    
    return pXmlEncode->EndTag(cszCommitmentReceipt);
}


//
// <msmq> section
//
// no <Provider> element since there's no security.
// no <DestinationMqf>, <AdminMqf>, <ResponseMqf> since there's no multiqueues.
//
BOOL SendMsmq(SVSXMLEncode *pXmlEncode, ScPacketImage *pImage) {
    CPropertyHeader *pPropHeader   = pImage->sect.pPropHeader;
    CUserHeader     *pUserHeader   = pImage->sect.pUserHeader;
    CBaseHeader     *pBaseHeader   = pImage->sect.pBaseHeader;
    CXactHeader     *pXactHeader   = pImage->sect.pXactHeader;
    WCHAR           szBuf[MAX_PATH];
    UCHAR           auditing       = pUserHeader->GetAuditing();
    WCHAR           *szCorrelation;
    DWORD           dw;
    const GUID      *pGuid;

    if (! pXmlEncode->Append((CHAR*)cszMsmqNS,SVSUTIL_CONSTSTRLEN(cszMsmqNS)*sizeof(WCHAR)))
        return FALSE;

    StringCchPrintfW(szBuf,MAX_PATH,L"%d",pPropHeader->GetClass());
    if (! pXmlEncode->SetElement(cszClass,szBuf))
        return FALSE;

    StringCchPrintfW(szBuf,MAX_PATH,L"%d",pBaseHeader->GetPriority());
    if (! pXmlEncode->SetElement(cszPriority,szBuf))
        return FALSE;

    if (auditing & MQMSG_JOURNAL) {
        if (!pXmlEncode->SetEmptyElement(cszJournal))
            return FALSE;
    }

    if (auditing & MQMSG_DEADLETTER) {
        if (!pXmlEncode->SetEmptyElement(cszDeadLetter))
            return FALSE;
    }

    if (NULL == (szCorrelation = Octet2Base64W(pPropHeader->GetCorrelationID(),PROPID_M_CORRELATIONID_SIZE,&dw)))
        return FALSE;

    if (! pXmlEncode->SetElement(cszCorrelation,szCorrelation)) {
        g_funcFree(szCorrelation,g_pvFreeData);
        return FALSE;
    }
    g_funcFree(szCorrelation,g_pvFreeData);

    if (pBaseHeader->GetTraced() & MQMSG_SEND_ROUTE_TO_REPORT_QUEUE) {
        if (! pXmlEncode->SetEmptyElement(cszTrace))
            return FALSE;
    }

    if (pUserHeader->ConnectorTypeIsIncluded()) {
        pGuid = pUserHeader->GetConnectorType();
        StringCchPrintfW(szBuf,MAX_PATH,GUID_FORMAT,GUID_ELEMENTS(pGuid));
        if (! pXmlEncode->SetElement(cszConnectorType,szBuf))
            return FALSE;
    }

    StringCchPrintfW(szBuf,MAX_PATH,L"%d",pPropHeader->GetApplicationTag());
    if (! pXmlEncode->SetElement(cszApp,szBuf))
        return FALSE;

    StringCchPrintfW(szBuf,MAX_PATH,L"%d",pPropHeader->GetBodyType());
    if (! pXmlEncode->SetElement(cszBodyType,szBuf))
        return FALSE;

    StringCchPrintfW(szBuf,MAX_PATH,L"%d",pPropHeader->GetHashAlg());
    if (! pXmlEncode->SetElement(cszHashAlgorithm,szBuf))
        return FALSE;

    // <eod> section
    if (pUserHeader->IsOrdered()) {
        if (! pXmlEncode->StartTag(cszEod))
            return FALSE;
        
        if (pXactHeader->ConnectorQMIncluded()) {
            pGuid = pXactHeader->GetConnectorQM();
            StringCchPrintfW(szBuf,MAX_PATH,GUID_FORMAT,GUID_ELEMENTS(pGuid));
            if (! pXmlEncode->SetElement(cszXConnectorId,szBuf))
                return FALSE;
        }

        if (pXactHeader->GetFirstInXact()) {
            if (! pXmlEncode->SetEmptyElement(cszFirst))
                return FALSE;
        }

        if (pXactHeader->GetLastInXact()) {
            if (! pXmlEncode->SetEmptyElement(cszLast))
                return FALSE;
        }

        if (! pXmlEncode->EndTag(cszEod))
            return FALSE;
    }

    pGuid = pUserHeader->GetSourceQM();
    StringCchPrintfW(szBuf,MAX_PATH,GUID_FORMAT,GUID_ELEMENTS(pGuid));
    if (! pXmlEncode->SetElement(cszSourceQmGuid,szBuf))
        return FALSE;

    dw = min(pBaseHeader->GetAbsoluteTimeToQueue(), LONG_MAX);
    if (! UtlTimeToIso860Time(dw,szBuf,MAX_PATH) || 
        ! pXmlEncode->SetElement(cszTTrq,szBuf))
        return FALSE;
    
    return pXmlEncode->EndTag(cszMsmq);
}


// Adds elements set by user.
BOOL SendUserHeader(SVSXMLEncode *pXmlEncode, ScPacketImage *pImage) {
    CUserHeader     *pUserHeader   = pImage->sect.pUserHeader;
    CSoapSection    *pSoapSection  = pImage->sect.pSoapHeaderSection;

    if (! pUserHeader->SoapIsIncluded() || !pSoapSection || !pSoapSection->GetPointerToData())
        return TRUE;

    // do *not* encode the soap section, as it contains XML tags in it already.
    // It's application's responsibility to make sure all is encoded correctly, just
    // as on the desktop.

    // GetDataLengthInWCHARs() returns strlen+1 for \0, don't append this to buffer.
    return pXmlEncode->Append((CHAR*)pSoapSection->GetPointerToData(),(pSoapSection->GetDataLengthInWCHARs()-1)*sizeof(WCHAR));
}
