/* ***** BEGIN LICENSE BLOCK *****
 * Source last modified: $Id: sdpmdparse.cpp,v 1.16.2.2 2004/07/09 02:05:16 hubbe Exp $
 * 
 * Portions Copyright (c) 1995-2004 RealNetworks, Inc. All Rights Reserved.
 * 
 * The contents of this file, and the files included with this file,
 * are subject to the current version of the RealNetworks Public
 * Source License (the "RPSL") available at
 * http://www.helixcommunity.org/content/rpsl unless you have licensed
 * the file under the current version of the RealNetworks Community
 * Source License (the "RCSL") available at
 * http://www.helixcommunity.org/content/rcsl, in which case the RCSL
 * will apply. You may also obtain the license terms directly from
 * RealNetworks.  You may not use this file except in compliance with
 * the RPSL or, if you have a valid RCSL with RealNetworks applicable
 * to this file, the RCSL.  Please see the applicable RPSL or RCSL for
 * the rights, obligations and limitations governing use of the
 * contents of the file.
 * 
 * Alternatively, the contents of this file may be used under the
 * terms of the GNU General Public License Version 2 or later (the
 * "GPL") in which case the provisions of the GPL are applicable
 * instead of those above. If you wish to allow use of your version of
 * this file only under the terms of the GPL, and not to allow others
 * to use your version of this file under the terms of either the RPSL
 * or RCSL, indicate your decision by deleting the provisions above
 * and replace them with the notice and other provisions required by
 * the GPL. If you do not delete the provisions above, a recipient may
 * use your version of this file under the terms of any one of the
 * RPSL, the RCSL or the GPL.
 * 
 * This file is part of the Helix DNA Technology. RealNetworks is the
 * developer of the Original Code and owns the copyrights in the
 * portions it created.
 * 
 * This file, and the files included with this file, is distributed
 * and made available on an 'AS IS' basis, WITHOUT WARRANTY OF ANY
 * KIND, EITHER EXPRESS OR IMPLIED, AND REALNETWORKS HEREBY DISCLAIMS
 * ALL SUCH WARRANTIES, INCLUDING WITHOUT LIMITATION, ANY WARRANTIES
 * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, QUIET
 * ENJOYMENT OR NON-INFRINGEMENT.
 * 
 * Technology Compatibility Kit Test Suite(s) Location:
 *    http://www.helixcommunity.org/content/tck
 * 
 * Contributor(s):
 * 
 * ***** END LICENSE BLOCK ***** */

#include "sdpmdparse.h"

#include "sdptypes.h"
#include "sdppyldinfo.h"
#include "rtptypes.h"
#include "nptime.h"
#include "rtsputil.h"
#include "safestring.h"
#include "chxfmtpparse.h"
#include "chxcharstack.h"

SDPMediaDescParser::SDPMediaDescParser(ULONG32 ulVersion) :
    m_pContext(0),
    m_pCCF(0),
    m_ulVersion(ulVersion),
    m_pFileHeader(0),
    m_bDefiniteDuration(FALSE),
    m_ulDefaultDuration(0)
{
}

SDPMediaDescParser::~SDPMediaDescParser()
{
    HX_RELEASE(m_pCCF);
    HX_RELEASE(m_pContext);

    HX_RELEASE(m_pFileHeader);
    clearStreamList();
}
    
HX_RESULT SDPMediaDescParser::Init(IUnknown* pContext)
{
    HX_RESULT res = HXR_FAILED;

    HX_RELEASE(m_pContext);
    HX_RELEASE(m_pCCF);

    if (pContext)
    {
        res = pContext->QueryInterface(IID_IHXCommonClassFactory,
                                       (void**)&m_pCCF);
        if (SUCCEEDED(res) && m_pCCF)
        {
            m_pContext = pContext;
            m_pContext->AddRef();
        }
    }

    return res;
}

HX_RESULT SDPMediaDescParser::Parse(IHXBuffer* pDescription,
                                    REF(UINT16) nValues, 
                                    REF(IHXValues**) pValueArray)
{    
    HX_RESULT theErr = HXR_OK;

    /*
     * pDescription may not be NULL terminated, so don't do ->GetBuffer()
     */
    char* pDescString = NULL;
    UINT32 ulDescStringLen = 0;
    theErr = pDescription->Get((BYTE*&)pDescString, ulDescStringLen);

    if (HXR_OK == theErr)
        theErr = fromExternalRep(pDescString, ulDescStringLen);
    
    if (HXR_OK == theErr)
    {    
        nValues = m_streams.GetCount() + 1;
        IHXValues** ppHeaders = new IHXValues*[nValues];
        ppHeaders[0] = m_pFileHeader;
        ppHeaders[0]->AddRef();
        CHXSimpleList::Iterator i;
        UINT16 j=1;
        for(i = m_streams.Begin();i != m_streams.End();++i,++j)
        {
            ppHeaders[j] = (IHXValues*)(*i);
            ppHeaders[j]->AddRef();
            TakeCareOfDefaults(ppHeaders[j]);
        }
        pValueArray = ppHeaders;
    }

    return theErr;
}

/* right now there is only ASMRuleBook */
void SDPMediaDescParser::TakeCareOfDefaults(IHXValues* pHeader)
{
    HX_ASSERT(pHeader);
    IHXBuffer* pBuf = NULL;
    if (pHeader->GetPropertyCString("ASMRuleBook", pBuf) != HXR_OK)
    {
        // there is no asmrulebook...
        UINT32 ul = 0;
        if (pHeader->GetPropertyULONG32("AvgBitRate", ul) == HXR_OK)
        {
            AddRuleBook(pHeader, ul);
        }
        else
        {
            AddRuleBook(pHeader, 0);
            // add 0 bandwidth...
            pHeader->SetPropertyULONG32("AvgBitRate", 0);
        }
    }

    HX_RELEASE(pBuf);
}

/* if ulBW is 0, we will assume TSD */
void SDPMediaDescParser::AddRuleBook(IHXValues* pHeader, UINT32 ulBW)
{
    IHXBuffer* pBuf = NULL;
    m_pCCF->CreateInstance(CLSID_IHXBuffer, (void**)&pBuf);
    if (!pBuf)
    {
        // nothing we can do...
        return;
    }

    if (ulBW)
    {    
        char rulebook[256] = {0}; /* Flawfinder: ignore */
        
        UINT32 ulBWHalf = ulBW / 2;                            
    
        SafeSprintf(rulebook,256, "marker=0,AverageBandwidth=%d;marker=1,AverageBandwidth=%d;", 
                    ulBW - ulBWHalf, ulBWHalf);
        pBuf->Set((BYTE*)rulebook, strlen(rulebook) + 1);        
    }
    else
    {
        pBuf->Set((const BYTE*)"marker=0,timestampdelivery=1;marker=1,timestampdelivery=1;", 59);        
    }    

    pHeader->SetPropertyCString("ASMRuleBook", pBuf);
    HX_RELEASE(pBuf);    
}

//
//      This is to fix a problem that causes the optimized builds
//      to cause the macintosh to crash!!!!!!!!!!!! <seanh>
//
#ifdef _MACINTOSH
#pragma peephole off
#pragma global_optimizer off
#pragma scheduling off
#endif


/*
 *  pData needs to be NULL terminated for we use strlen...
 */
HX_RESULT 
SDPMediaDescParser::fromExternalRep(char* pData)
{
    return fromExternalRep(pData, strlen(pData));     
}

HX_RESULT
SDPMediaDescParser::fromExternalRep(char* pData, UINT32 ulDataLen)
{
    const char* pCur = pData;
    const char* pEnd = pData + ulDataLen;

    ULONG32 nStreams = 0;
    CHXString extraFields;

    m_bDefiniteDuration = FALSE;
    m_ulDefaultDuration = 0;
    m_mediaType.Empty();

    m_pFileHeader = CreateHeader();

    IHXValues* pHdr = m_pFileHeader;

    HX_RESULT res = (pHdr) ? HXR_OK : HXR_OUTOFMEMORY;
    
    while((HXR_OK == res) && *pCur && (pCur < pEnd))
    {
        // Skip \n or '\r' characters
        for(; *pCur && (pCur < pEnd) && strchr("\r\n", *pCur); pCur++)
            ;

        if (*pCur && (pCur < pEnd))
        {
            char lineType = *pCur++;
            
            if (*pCur == '=')
            {
                pCur++; // skip '='

                IHXBuffer* pSDPLine = 0;
                res = GetLine(pCur, pEnd, pSDPLine);

                if (HXR_OK == res)
                {
                    char* pLine = (char*)pSDPLine->GetBuffer();
                    //printf ("line %c '%s'\n", lineType, pLine);

                    switch(lineType) {
                    case 'v':
                        res = HandleVLine(pLine);
                        break;
                    case 'm':
                        if (!extraFields.IsEmpty())
                        {
                            AddString(pHdr, "SDPData",
                                      (const char*)extraFields);
                            extraFields.Empty();
                        }

                        pHdr = CreateHeader();
                        m_streams.AddTail(pHdr);

                        AddULONG32(pHdr, "StreamNumber", nStreams);
                        nStreams++;

                        // Propagate default duration to new stream
                        if (0 != m_ulDefaultDuration)
                        {
                            AddULONG32(pHdr, "Duration", m_ulDefaultDuration);
                        }   

                        res = HandleMLine(pLine, pHdr);

                        break;
                    case 'a':
                        res = HandleALine(pLine, pHdr);
                        break;

                    case 'c':
                        res = HandleCLine(pLine, pHdr);
                        break;

                    case 'b':
                        res = HandleBLine(pLine, pHdr);
                        break;

                    case 'i':
                        AddString(pHdr, "Information", pLine);
                        res = HXR_OK;
                        break;

                    case 'o':
                    case 's':
                    case 't':
                        // ignore these lines
                        res = HXR_OK;
                        break;

                    default:
                        res = HXR_NOT_SUPPORTED;
                        break;
                    };

                    if (HXR_NOT_SUPPORTED == res)
                    {
                        extraFields += lineType;
                        extraFields += '=';
                        extraFields += pLine;
                        extraFields += "\n";
                        
                        res = HXR_OK;
                    }

                    pSDPLine->Release();
                    pSDPLine = 0;
                }
            }
            else
            {
                res = HXR_FAILED;
            }
        }
    }

    if (HXR_OK == res)
    {
        if (!extraFields.IsEmpty())
        {
            AddString(pHdr, "SDPData",
                      (const char*)extraFields);
            extraFields.Empty();
        }

        /* Only add StreamCount and LiveStream fields
         * if we have at least 1 stream. This prevents
         * the fields from appearing when we are using 
         * the SDP plugin to parse partial SDP chunks.
         */
        if (nStreams)
        {
            AddULONG32(m_pFileHeader, "StreamCount", nStreams);
            
            ULONG32 ulLive;
            if (!m_bDefiniteDuration &&
                (m_ulDefaultDuration == 0) &&
                (!SUCCEEDED(m_pFileHeader->GetPropertyULONG32("LiveStream", 
                                                              ulLive))))
            {
                AddULONG32(m_pFileHeader, "LiveStream", 1);
            }
        }
    }
    
    return res;
}

#ifdef _MACINTOSH
#pragma peephole reset
#pragma global_optimizer reset
#pragma scheduling reset
#endif


/*
* Payload table contains reserved/unassigned types as well.  So, this func
* returns if the table contains a valid info
*/
BOOL
SDPMediaDescParser::IsPayloadTableValid(UINT32 ulPayloadType)
{
    HX_ASSERT(SDPIsStaticPayload(ulPayloadType));

    if (!SDPMapPayloadToEncodingName(ulPayloadType))
    {
        return FALSE;
    }
    return TRUE;
}

HX_RESULT
SDPMediaDescParser::getRTPMapInfo(const char* pMapInfo, 
                                  REF(CHXString) strMimeType, 
                                  IHXValues* pStream)
{
    if (!pMapInfo || !strMimeType.GetLength() || !pStream)
    {
        return HXR_FAIL;
    }
    
    /* 
     *  It could be either:
     *  EncodingName
     *  EncodingName/SamplesPerSecond
     *  EncodingName/SamplesPerSecond/Channels
     */   

    strMimeType += "/";

    char* pTok = strchr((char*)pMapInfo, ' ');
    if (pTok)
    {
        *pTok = '\0';
    }   
    pTok = (char*)pMapInfo;    
    char* pTokNext = strchr(pTok, '/');


    if (pTokNext)
    {
        *pTokNext++ = '\0';

        strMimeType += pTok;
        pTok = pTokNext;
        pTokNext = strchr((char*)pTok, '/');
        if (pTokNext)
        {
            *pTokNext++ = '\0';
            AddULONG32(pStream, "SamplesPerSecond", strtol(pTok, 0, 10));

            AddULONG32(pStream, "Channels", strtol(pTokNext, 0, 10));
        }
        else
        {
            AddULONG32(pStream, "SamplesPerSecond", strtol(pTok, 0, 10));
        }
    }
    else
    {
        // this is just an encoding name
        strMimeType += pMapInfo;
    }
        
    return HXR_OK;
}

/*
*   Compare ulVersion against m_ulVersion.  and returns HXR_FAIL iff 
*   ulVersion < m_ulVersion  ---> need auto update.
*/
HX_RESULT
SDPMediaDescParser::checkVersion(UINT32 ulVersion)
{
    HX_ASSERT(m_ulVersion);
    UINT32 ulPluginMajor = HX_GET_MAJOR_VERSION(m_ulVersion);
    UINT32 ulPluginMinor = HX_GET_MINOR_VERSION(m_ulVersion);
    UINT32 ulDataMajor = HX_GET_MAJOR_VERSION(ulVersion);
    UINT32 ulDataMinor = HX_GET_MINOR_VERSION(ulVersion);

    if((ulDataMajor >  ulPluginMajor) ||
       (ulDataMajor == ulPluginMajor && ulDataMinor > ulPluginMinor))
    {
        return HXR_FAIL;
    }    
    else
    {
        return HXR_OK;
    }
}

void SDPMediaDescParser::clearStreamList()
{
    LISTPOSITION pos = m_streams.GetHeadPosition();
    while(pos)
    {
        IHXValues* pStream = (IHXValues*)m_streams.GetNext(pos);
        HX_RELEASE(pStream);
    }
    m_streams.RemoveAll();
}

IHXValues* SDPMediaDescParser::CreateHeader()
{
    IHXValues* pRet = 0;

    if (m_pCCF)
        m_pCCF->CreateInstance(IID_IHXValues, (void**)&pRet);

    return pRet;
}

void SDPMediaDescParser::AddULONG32(IHXValues* pHeader, 
                                    const char* pKey, ULONG32 ulValue)
{
    pHeader->SetPropertyULONG32(pKey, ulValue);
}

void SDPMediaDescParser::AddString(IHXValues* pHeader, 
                                   const char* pKey,
                                   const char* pValue)
{
    if ((pKey == NULL) || (pValue == NULL))
    {
        return;
    }

    IHXBuffer* pBuf = CopyBuffer((const UINT8*)pValue, strlen(pValue) + 1);

    if (pBuf)
        pHeader->SetPropertyCString(pKey, pBuf);

    HX_RELEASE(pBuf);
}

void SDPMediaDescParser::AddBuffer(IHXValues* pHeader, 
                                   const char* pKey,
                                   const UINT8* pValue,
                                   ULONG32 ulLength)
{
    IHXBuffer* pBuf = CopyBuffer(pValue, ulLength);

    if (pBuf)
        pHeader->SetPropertyBuffer(pKey, pBuf);

    HX_RELEASE(pBuf);
}

IHXBuffer* SDPMediaDescParser::CopyBuffer(const UINT8* pBuf, ULONG32 ulLength)
{
    IHXBuffer* pRet = 0;
    if (SUCCEEDED(m_pCCF->CreateInstance(IID_IHXBuffer, (void**)&pRet)))
        pRet->Set(pBuf, ulLength);

    return pRet;
}

HX_RESULT SDPMediaDescParser::GetLine(const char*& pData,
                                      const char* pEnd,
                                      IHXBuffer*& pLine) const
{
    HX_RESULT res = HXR_OK;

    CHXCharStack tok(m_pCCF);
    BOOL bInQuote = FALSE;
    BOOL bLastWasEscape = FALSE;

    for (; (HXR_OK == res) && *pData && (pData < pEnd) && (bInQuote || !strchr("\r\n", *pData)); pData++)
    {
        if (bLastWasEscape)
        {
            bLastWasEscape = FALSE;
        }
        else
        {
            if (*pData == '"')
            {
                bInQuote = !bInQuote;
            }
            else if (*pData == '\\')
            {
                bLastWasEscape = TRUE;
            }
        }

        res = tok.AddChar(*pData);
    }

    if (HXR_OK == res)
    {
        res = tok.Finish(pLine);
    }

    return res;
}

void SDPMediaDescParser::SkipSpaces(char*& pData) const
{
    for (; *pData && *pData == ' '; pData++)
        ;
}

BOOL SDPMediaDescParser::ScanForDelim(char*& pData, char delim) const
{
    BOOL bRet = FALSE;

    while(*pData && !bRet)
    {
        if (*pData == delim)
        {
            bRet = TRUE;
        }
        else
        {
            pData++;
        }
    }

    return bRet;
}

HX_RESULT SDPMediaDescParser::HandleVLine(char* pLine)
{
    HX_RESULT res = HXR_FAILED;
    
    char* pEnd = 0;

    unsigned long version = strtoul(pLine, &pEnd, 10);

    if (*pLine && !*pEnd && (version == 0))
    {
        
        res = HXR_OK;
    }

    return res;
}

HX_RESULT SDPMediaDescParser::HandleMLine(char* pLine, IHXValues* pHdr)
{
    HX_RESULT res = HXR_FAILED;

    if (*pLine)
    {
        int state = 0;
        const char* pMediaType = pLine;
        ULONG32 ulPort = 0;
        ULONG32 ulPayloadType = 0;
        

        if (ScanForDelim(pLine, ' '))
        {
            // Null terminate mimetype
            *pLine++ = '\0';

            // Save media type for later
            m_mediaType = pMediaType;

            res = HXR_OK;
        }

        while(*pLine && (HXR_OK == res))
        {
            char* pEnd = 0;

            SkipSpaces(pLine);
            
            if (*pLine)
            {
                switch(state) {
                case 0:
                    // Grab port number
                    ulPort = strtoul(pLine, &pEnd, 10);
                    if (*pEnd == ' ')
                    {
                        pLine = pEnd;
                        state = 1;
                    }
                    else
                    {
                        // No transport field or
                        // invalid port character
                        res = HXR_FAILED;
                    }
                    break;
                case 1:
                    // Skip transport. Usually RTP/AVP
                    if (ScanForDelim(pLine, ' '))
                    {
                        state = 2;
                    }
                    break;
                case 2:
                    // Grab the first payload type
                    ulPayloadType = strtoul(pLine, &pEnd, 10);
                    if ((*pEnd == ' ') || !*pEnd )
                    {
                        state = 3;
                    }
                    else
                    {
                        // There was an unexpected character
                        // the the payload type
                        res = HXR_FAILED;
                    }

                case 3:
                    // Consume the rest of the payload types
                    for (; *pLine; pLine++)
                        ;
                    break;
                }
            }
        }

        if (state == 3)
        {
            AddULONG32(pHdr, "RTPPayloadType", ulPayloadType);
            
            if (ulPort)
            {
                AddULONG32(pHdr, "Port", ulPort);
            }

            if (SDPIsStaticPayload(ulPayloadType))
            {
                if (IsPayloadTableValid(ulPayloadType))
                {
                    if (!SDPIsTimestampDeliverable(ulPayloadType))
                    {
                        ULONG32 ulBitrate = 
                            SDPMapPayloadToBitrate(ulPayloadType);

                        HX_ASSERT(ulBitrate);
                        // we have a bandwidth info in a table...
                        AddULONG32(pHdr, "AvgBitRate", ulBitrate);
                    }

                    // static payload type
                    AddString(pHdr, "MimeType", 
                              SDPMapPayloadToMimeType(ulPayloadType));
                    AddULONG32(pHdr,
                               "RTPTimestampConversionFactor", 
                               SDPMapPayloadToRTPFactor(ulPayloadType));
                    AddULONG32(pHdr,
                               "HXTimestampConversionFactor", 
                               SDPMapPayloadToRMAFactor(ulPayloadType));
                    AddULONG32(pHdr,
                               "SamplesPerSecond", 
                               SDPMapPayloadToSamplesPerSecond(ulPayloadType));
                    AddULONG32(pHdr,
                               "Channels", 
                               (ULONG32)SDPMapPayloadToChannels(ulPayloadType));

                    // deal with opaque hack...
                    const BYTE* pOpaqueData = NULL;
                    ULONG32 ulOpaqueDataSize = 0;
                    switch(ulPayloadType)
                    {
                    case RTP_PAYLOAD_GSM:
                        pOpaqueData = 
                            SDPMapPayloadToHeaderData(RTP_PAYLOAD_GSM,
                                                      ulOpaqueDataSize);
                        break;
                    }   
                    
                    if (pOpaqueData)
                    {
                        AddBuffer(pHdr, "OpaqueData",
                                  pOpaqueData, ulOpaqueDataSize);
                    }           
                }
                else
                {
                    res = HXR_REQUEST_UPGRADE;
                }
            }
        }
        else if (HXR_OK == res)
        {
            res = HXR_FAILED;
        }
    }

    return res;
}

HX_RESULT SDPMediaDescParser::HandleALine(char* pLine, IHXValues* pHdr)
{
    HX_RESULT res = HXR_FAILED;
    
    const char* pFieldName = pLine;
    char* pFieldValue = 0;
    FieldType fieldType = ftUnknown;
    
    if (*pLine)
    {
        // Collect the field name
        if (ScanForDelim(pLine, ':'))
        {
            char* pColon = pLine;

            // Must be the key/value form 
            // a=foo:bar
            
            // Null terminate field name by replacing ':' with '\0'
            *pLine++ = '\0';
                    
            pFieldValue = pLine;

            if ((HXR_OK == (res = ParseFieldValue(pFieldValue, fieldType))))
            {
                switch (fieldType) {
                case ftUnknown:
                    res = HandleSpecialFields(pFieldName, pFieldValue, pHdr);
                    break;
                case ftULONG32:
                {
                    char* pEnd = 0;
                    ULONG32 ulValue = strtoul(pFieldValue, &pEnd, 10);
                    
                    if (*pFieldValue && !*pEnd)
                    {
                        res = pHdr->SetPropertyULONG32(pFieldName, ulValue);
                    }
                } break;
                case ftString:
                    AddString(pHdr, pFieldName, pFieldValue);
                    res = HXR_OK;
                    break;
                case ftBuffer:
                {
                    int length = strlen(pFieldValue);
                    BYTE* pDecodeBuf = new BYTE[length];
                    INT32 decodeLen = BinFrom64(pFieldValue, length, 
                                                pDecodeBuf);
                    if( decodeLen != -1 )
                    {
                        AddBuffer(pHdr, pFieldName, pDecodeBuf, decodeLen);
                    }

                    res = HXR_OK;

                    delete [] pDecodeBuf;
                }break;
                }
            }
            
            if (HXR_OK != res)
            {
                // Put back the ':'
                *pColon = ':';
            }
        }
        else
        {
            // Must be the key only form
            // a=foo
            res = HXR_NOT_SUPPORTED;
        }
    }

    return res;
}

HX_RESULT SDPMediaDescParser::HandleCLine(char* pLine, IHXValues* pHdr)
{
    // Handles the following forms
    // c=IN IP4 xxx.xxx.xxx.xxx
    // c=IN IP4 xxx.xxx.xxx.xxx/xxx
    // c=IN IP4 xxx.xxx.xxx.xxx/xxx/xx
    HX_RESULT res = HXR_FAILED;
    
    char* pCur = pLine;

    if (ScanForDelim(pCur, ' '))
    {
        *pCur++ = '\0';
        
        SkipSpaces(pCur);

        char* pAddrType = pCur;

        if (!strcasecmp(pLine, "IN") && ScanForDelim(pCur, ' '))
        {
            *pCur++ = '\0';

            SkipSpaces(pCur);

            if (!strcasecmp(pAddrType, "IP4") && *pCur)
            {
                char* pAddr = pCur;

                if (ScanForDelim(pCur, '/'))
                {
                    *pCur++ = '\0';

                    if (*pCur)
                    {
                        char* pEnd = 0;
                        ULONG32 ulTTL = strtoul(pCur, &pEnd, 10);

                        if (*pCur && ((*pEnd == '/') || (!*pEnd)))
                        {
                            AddULONG32(pHdr, "MulticastTTL", ulTTL);
                            res = HXR_OK;

                            pCur = pEnd;
                            if (*pCur)
                            {
                                pCur++; // skip '/'
                                ULONG32 ulRange = strtoul(pCur, &pEnd, 10);
                                
                                if (*pCur && !*pEnd)
                                {
                                    // c=IN IP4 xxx.xxx.xxx.xxx/xxx/xx
                                    AddULONG32(pHdr, "MulticastRange", ulRange);
                                    res = HXR_OK;
                                }
                            }
                        }
                    }
                }
                else
                {
                    // c=IN IP4 xxx.xxx.xxx.xxx
                    res = HXR_OK;
                }

                if (HXR_OK == res)
                {
                    AddString(pHdr, "MulticastAddress", pAddr);
                }
            }
        }
    }

    return res;
}

HX_RESULT SDPMediaDescParser::HandleBLine(char* pLine, IHXValues* pHdr)
{
    HX_RESULT res = HXR_FAILED;

    char* pCur = pLine;

    if (ScanForDelim(pCur, ':'))
    {
        char* pColon = pCur;
        *pCur++ = '\0';
        
        char* pEnd = 0;
        ULONG32 ulBwValue = strtoul(pCur, &pEnd, 10);

        if (*pCur && !*pEnd)
        {
            const char* pBwKey = 0;
            res = HXR_OK;

            if (!strcasecmp(pLine, "AS"))
            {
                // a=AvgBitRate has the higher precedence...
                ULONG32 ulTmp;
                if (!SUCCEEDED(pHdr->GetPropertyULONG32("AvgBitRate",
                                                        ulTmp)))
                {
                                // use this as a default.       
                                // it's kilobits per sec....
                    pBwKey = "AvgBitRate";
                    ulBwValue *= 1000;
                }
            }
            else if (!strcasecmp(pLine, "RR"))
            {
                pBwKey = "RtcpRRRate";
            }
            else if (!strcasecmp(pLine, "RS"))
            {
                pBwKey = "RtcpRSRate";
            }
            else
            {
                res = HXR_NOT_SUPPORTED;
            }

            if (pBwKey)
            {
                AddULONG32(pHdr, pBwKey, ulBwValue);
            }
        }
        
        *pColon = ':';
    }

    return res;
}

HX_RESULT SDPMediaDescParser::ParseFieldValue(char*& pValue, 
                                              FieldType& fieldType) const
{
    HX_RESULT res = HXR_OK;

    char* pCur = pValue;

    // Look for the following forms
    // a=anInt:integer;43
    // a=aString:string;"this is a string"
    // a=aBuffer:buffer;"TWFjIFRWAA=="

    // Assume we don't know the type.
    fieldType = ftUnknown;

    if (ScanForDelim(pCur, ';'))
    {
        // Replace the ';' with a '\0' so we
        // can check to see if it matches the known
        // types
        char* pSemiColon = pCur;

        *pCur++ = '\0';

        if (!strcmp(pValue, "integer"))
        {
            // Update the value pointer to the
            // start of the integer value
            pValue = pCur;
            fieldType = ftULONG32;
        }
        else if (!strcmp(pValue, "string"))
        {
            fieldType = ftString;
        }
        else if (!strcmp(pValue, "buffer"))
        {
            fieldType = ftBuffer;
        }

        if ((fieldType == ftString) || (fieldType == ftBuffer))
        {
            BOOL bFailed = TRUE;

            // Look for starting '"'
            if (*pCur == '"')
            {
                pCur++; // skip '"'

                if (*pCur)
                {
                    // Store start of the string
                    char* pStrStart = pCur;

                    // Create temporary buffer for unescaping
                    char* pTmpBuf = new char[strlen(pStrStart) + 1];

                    if (pTmpBuf)
                    {
                        char* pDest = pTmpBuf;

                        // Copy string into pTmpBuf and
                        // unescape any escape sequences
                        while (*pCur && *pCur != '"')
                        {
                            if (*pCur == '\\')
                            {
                                *pCur++; // skip '\\'
                            }
                            
                            *pDest++ = *pCur++;
                        }
                        
                        // Make sure the last character is a '"'
                        if (*pCur == '"')
                        {
                            // Replace ending '"' with '\0'
                            *pDest = '\0';

                            // Replace escaped string with
                            // unescaped string.
                            strcpy(pStrStart, pTmpBuf);

                            // Update the value pointer to the 
                            // start of the string value
                            pValue = pStrStart;
                            bFailed = FALSE;
                        }

                        delete [] pTmpBuf;
                    }
                }
            }
            
            if (bFailed)
            {
                fieldType = ftUnknown;
            }
        }
        

        if (fieldType == ftUnknown)
        {
            // This is not a type we recognize.
            // Replace the '\0' with a ';' so the
            // value is the way it was before.
            *pSemiColon = ';';
        }
    }
    
    return res;
}

HX_RESULT SDPMediaDescParser::HandleSpecialFields(const char* pFieldName, 
                                                  char* pFieldValue, 
                                                  IHXValues* pHdr)
{
    HX_RESULT res = HXR_FAILED;

    if (!strcasecmp("Range", pFieldName))
    {
        res = HandleRangeField(pFieldValue, pHdr);
    }
    else if (!strcasecmp("length", pFieldName))
    {
        res = HandleLengthField(pFieldValue, pHdr);
    }
    else if (!strcasecmp("rtpmap", pFieldName))
    {
        res = HandleRTPMapField(pFieldValue, pHdr);
    }
    else if (!strcasecmp("fmtp", pFieldName))
    {
        res = HandleFMTPField(pFieldValue, pHdr);
    }
    else if (!strcasecmp("ptime", pFieldName))
    {
        // a=ptime:43
        AddULONG32(pHdr, "Ptime", strtol(pFieldValue, 0, 10)); 
        res = HXR_OK;
    }
    else if (!strcasecmp("x-bufferdelay", pFieldName))
    {
        // a=x-bufferdelay:234
        // x-bufferdelay units are 1/1000 of a sec
        res = HandlePrerollField(pFieldValue, 1000, pHdr);
    }
    else if (!strcasecmp("x-initpredecbufperiod", pFieldName))
    {
        // 3GPP 26.234 Annex G field
        // a=x-initpredecbufperiod:45000
        // x-initpredecbufperiod units are 1/90000 of a sec
        res = HandlePrerollField(pFieldValue, 90000, pHdr);
    }
    else if (!strcasecmp("x-predecbufsize", pFieldName))
    {
        // 3GPP 26.234 Annex G field
        // a=x-predecbufsize:45000
        // x-predecbufsize units are bytes
        AddULONG32(pHdr, "x-predecbufsize", strtoul(pFieldValue, 0, 10)); 
        res = HXR_OK;
    }
    else if (!strcasecmp("SdpplinVersion", pFieldName))
    {
        res = checkVersion(strtol(pFieldValue, 0, 10));
        
        if (HXR_FAIL == res)
        {
            // need to update...
            // this flag causes to exit in "m=" case
            res = HXR_REQUEST_UPGRADE;
        }
    }
    else if (!strcasecmp("control", pFieldName))
    {
        AddString(pHdr, "Control", pFieldValue);
        res = HXR_OK;
    }
    else
    {
        res = HXR_NOT_SUPPORTED;
    }

    return res;
}

HX_RESULT SDPMediaDescParser::HandleRangeField(char* pFieldValue, 
                                               IHXValues* pHdr)
{
    char* pCur = pFieldValue;

    ULONG32 duration = 0;
    BOOL bIsLegal = TRUE;
    
    if (ScanForDelim(pCur, '='))
    {
        // replace '=' with '\0'
        *pCur++ = '\0';
        
        if (!strcasecmp(pFieldValue, "npt"))
        {
            // Look for the following npt forms
            // a=range:npt=-xxx
            // a=range:npt=xxx-
            // a=range:npt=xxx-xxx
            char* pLeftVal = pCur;
            
            if (ScanForDelim(pCur, '-'))
            {
                // replace '-' with '\0'
                
                *pCur++ = '\0';
                
                NPTime left(pLeftVal);
                NPTime right(pCur);
                
                if (*pCur)
                {
                    // a=range:npt=xxx-xxx
                    duration = (UINT32)(right - left);
                    m_bDefiniteDuration = TRUE;
                }
                else
                {
                    // a=range:npt=xxx-
                    // Treat open-ended play ranges as live streams 
                    // unless it is overridden by a media range.
                }
            }
            else
            {
                // This must be the following illegal form
                // a=range:npt=xxx 
                bIsLegal = FALSE;
            }
        }
    }
    else
    {
        duration = strtol(pFieldValue, 0, 10);
    }
    
    if (bIsLegal)
    {
        if (0 == m_ulDefaultDuration)
        {
            m_ulDefaultDuration = duration;
        }
        
        AddULONG32(pHdr, "Duration", duration);
    }

    return HXR_OK;
}

HX_RESULT SDPMediaDescParser::HandleLengthField(char* pFieldValue, 
                                                IHXValues* pHdr)
{
    HX_RESULT res = HXR_FAILED;

    char* pCur = pFieldValue;

    ULONG32 duration = 0;
    BOOL bIsLegal = TRUE;

    // Look for the following npt form
    // a=length:npt=xxx
    if (ScanForDelim(pCur, '='))
    {
        char* pEqual = pCur;

        // replace '=' with '\0'
        *pCur++ = '\0';
        
        if (!strcasecmp(pFieldValue, "npt") && *pCur)
        {
            NPTime dTime(pCur);
            
            duration = (UINT32)dTime;
            res = HXR_OK;
        }
        else
        {
            // Put back '=' character
            *pEqual = '=';
        }
    }
    else
    {
        duration = strtol(pFieldValue, 0, 10);
        res = HXR_OK;
    }
    
    if (duration)
    {
        m_bDefiniteDuration = TRUE;
    }

    if (0 == m_ulDefaultDuration)
    {
        m_ulDefaultDuration = duration;
    }
        
    AddULONG32(pHdr, "Duration", duration);

    return res;
}

HX_RESULT SDPMediaDescParser::HandleRTPMapField(char* pFieldValue, 
                                                IHXValues* pHdr)
{
    HX_RESULT res = HXR_FAILED;

    // e.g. a=rtpmap:101 xxx/90000/2

    char* pCur = 0;
    UINT32 payload = strtol(pFieldValue, &pCur, 10);
    ULONG32 rtpPayloadType = 0;

    res = pHdr->GetPropertyULONG32("RTPPayloadType", rtpPayloadType);
    if (*pFieldValue && (*pCur == ' '))
    {
        SkipSpaces(pCur);

        // there could be multiple of these...
        if (payload == rtpPayloadType)
        {    
            CHXString mimeType(m_mediaType);

            res = getRTPMapInfo(pCur, mimeType, pHdr);
            /* make sure there is no mime type set!
             *  MimeType from m= && a=rtpmap has the lowest precedence.
             *  a=mimetype -> mimetype table -> this mimetype
             */
            IHXBuffer* pMimeType = 0;
            if (!SUCCEEDED(pHdr->GetPropertyCString(
                "MimeType", pMimeType)))
            {
                AddString(pHdr, "MimeType", mimeType);
            }
            HX_RELEASE(pMimeType);
        }                
    }
                
    return res;
}

HX_RESULT SDPMediaDescParser::HandleFMTPField(char* pFieldValue, 
                                              IHXValues* pHdr)
{
    // e.g. a=fmtp:101 emphasis=50/15;foo=bar

    char* pCur = 0;
    UINT32 payload = strtol(pFieldValue, &pCur, 10);

    ULONG32 rtpPayloadType = 0;
    HX_RESULT res = pHdr->GetPropertyULONG32("RTPPayloadType", rtpPayloadType);

    if (*pFieldValue && *pCur == ' ')
    {
        SkipSpaces(pCur);

        // If the RTPPayloadType field is present, compare it
        // to the value in the fmtp field.
        // There could be multiple of these...
        if ((HXR_OK != res) || (payload == rtpPayloadType))
        {    
            AddString(pHdr, "PayloadParameters", pCur);
        
            CHXFMTPParser fmtp(m_pCCF);
            res = fmtp.Parse(pCur, pHdr);
        }
    }

    return res;
}

HX_RESULT SDPMediaDescParser::HandlePrerollField(char* pFieldValue, 
                                                 ULONG32 ulPrerollUnits,
                                                 IHXValues* pHdr)
{
    ULONG32 ulPreroll = 0;
    
    if (HXR_OK != pHdr->GetPropertyULONG32("Preroll", ulPreroll))
    {
        ULONG32 ulValue = strtoul(pFieldValue, 0, 10);

        // Convert Preroll value to milliseconds
        ulPreroll = (ulValue / ulPrerollUnits) * 1000;
        ulPreroll += ((ulValue % ulPrerollUnits) * 1000) / ulPrerollUnits;
        
        AddULONG32(pHdr, "Preroll", ulPreroll); 
    }
    
    return HXR_OK;
}
