/*

Copyright (c) 1997-1999  Microsoft Corporation

Module Name:
    sdp.cpp

Abstract:


Author:

*/
#include "sdppch.h"
#include <strstrea.h>

#include "sdp.h"
#include "sdpstran.h"



// state transitions for each of the states

const STATE_TRANSITION  g_StateStartTransitions[]       =   {   
    {CHAR_VERSION,       STATE_VERSION} 
};

const STATE_TRANSITION  g_StateVersionTransitions[]     =   {   
    {CHAR_ORIGIN,        STATE_ORIGIN}
};

const STATE_TRANSITION  g_StateOriginTransitions[]      =   {   
    {CHAR_SESSION_NAME,  STATE_SESSION_NAME}
};

const STATE_TRANSITION  g_StateSessionNameTransitions[] =   {   
    {CHAR_TITLE,        STATE_TITLE},
    {CHAR_URI,          STATE_URI},
    {CHAR_EMAIL,        STATE_EMAIL},
    {CHAR_PHONE,        STATE_PHONE},
    {CHAR_CONNECTION,   STATE_CONNECTION} 
};

const STATE_TRANSITION  g_StateTitleTransitions[]       =   {   
    {CHAR_URI,          STATE_URI},
    {CHAR_EMAIL,        STATE_EMAIL},
    {CHAR_PHONE,        STATE_PHONE},
    {CHAR_CONNECTION,   STATE_CONNECTION}   
};

const STATE_TRANSITION  g_StateUriTransitions[]         =   {   
    {CHAR_EMAIL,        STATE_EMAIL},
    {CHAR_PHONE,        STATE_PHONE},
    {CHAR_CONNECTION,   STATE_CONNECTION}   
};

const STATE_TRANSITION  g_StateEmailTransitions[]       =   {   
    {CHAR_EMAIL,        STATE_EMAIL},
    {CHAR_PHONE,        STATE_PHONE},
    {CHAR_CONNECTION,   STATE_CONNECTION}   
};

const STATE_TRANSITION  g_StatePhoneTransitions[]       =   {   
    {CHAR_PHONE,        STATE_PHONE},
    {CHAR_CONNECTION,   STATE_CONNECTION}   
};

const STATE_TRANSITION  g_StateConnectionTransitions[]  =   {  
    {CHAR_BANDWIDTH,    STATE_BANDWIDTH},
    {CHAR_TIME,         STATE_TIME},
    {CHAR_KEY,          STATE_KEY},
    {CHAR_ATTRIBUTE,    STATE_ATTRIBUTE},
    {CHAR_MEDIA,        STATE_MEDIA}        
};

const STATE_TRANSITION  g_StateBandwidthTransitions[]   =   {   
    {CHAR_TIME,         STATE_TIME},
    {CHAR_KEY,          STATE_KEY},
    {CHAR_ATTRIBUTE,    STATE_ATTRIBUTE},
    {CHAR_MEDIA,        STATE_MEDIA}        
};

const STATE_TRANSITION  g_StateTimeTransitions[]        =   {  
    {CHAR_TIME,         STATE_TIME},
    {CHAR_REPEAT,       STATE_REPEAT},
    {CHAR_ADJUSTMENT,   STATE_ADJUSTMENT},
    {CHAR_KEY,          STATE_KEY},
    {CHAR_ATTRIBUTE,    STATE_ATTRIBUTE},
    {CHAR_MEDIA,        STATE_MEDIA}        
};

const STATE_TRANSITION  g_StateRepeatTransitions[]      =   {  
    {CHAR_TIME,         STATE_TIME},
    {CHAR_REPEAT,       STATE_REPEAT},
    {CHAR_ADJUSTMENT,   STATE_ADJUSTMENT},
    {CHAR_KEY,          STATE_KEY},
    {CHAR_ATTRIBUTE,    STATE_ATTRIBUTE},
    {CHAR_MEDIA,        STATE_MEDIA}        
};

const STATE_TRANSITION  g_StateAdjustmentTransitions[]  =   {  
    {CHAR_KEY,          STATE_KEY},
    {CHAR_ATTRIBUTE,    STATE_ATTRIBUTE},
    {CHAR_MEDIA,        STATE_MEDIA}        
};

const STATE_TRANSITION  g_StateKeyTransitions[]         =   {  
    {CHAR_ATTRIBUTE,    STATE_ATTRIBUTE},
    {CHAR_MEDIA,        STATE_MEDIA}        
};

const STATE_TRANSITION  g_StateAttributeTransitions[]   =   {  
    {CHAR_ATTRIBUTE,    STATE_ATTRIBUTE},
    {CHAR_MEDIA,        STATE_MEDIA}        
};

const STATE_TRANSITION  g_StateMediaTransitions[]       =   {  
    {CHAR_MEDIA,        STATE_MEDIA},
    {CHAR_MEDIA_TITLE,  STATE_MEDIA_TITLE},
    {CHAR_MEDIA_CONNECTION, STATE_MEDIA_CONNECTION},
    {CHAR_MEDIA_BANDWIDTH,  STATE_MEDIA_BANDWIDTH},
    {CHAR_MEDIA_KEY,    STATE_MEDIA_KEY},
    {CHAR_MEDIA_ATTRIBUTE,  STATE_MEDIA_ATTRIBUTE} 
};

const STATE_TRANSITION  g_StateMediaTitleTransitions[]  =   {  
    {CHAR_MEDIA,        STATE_MEDIA},
    {CHAR_MEDIA_CONNECTION, STATE_MEDIA_CONNECTION},
    {CHAR_MEDIA_BANDWIDTH,  STATE_MEDIA_BANDWIDTH},
    {CHAR_MEDIA_KEY,    STATE_MEDIA_KEY},
    {CHAR_MEDIA_ATTRIBUTE,  STATE_MEDIA_ATTRIBUTE} 
};

const STATE_TRANSITION  g_StateMediaConnectionTransitions[]= {
    {CHAR_MEDIA,        STATE_MEDIA},
    {CHAR_MEDIA_BANDWIDTH,  STATE_MEDIA_BANDWIDTH},
    {CHAR_MEDIA_KEY,    STATE_MEDIA_KEY},
    {CHAR_MEDIA_ATTRIBUTE,  STATE_MEDIA_ATTRIBUTE} 
};

const STATE_TRANSITION  g_StateMediaBandwidthTransitions[]=  {
    {CHAR_MEDIA,        STATE_MEDIA},
    {CHAR_MEDIA_KEY,    STATE_MEDIA_KEY},
    {CHAR_MEDIA_ATTRIBUTE,  STATE_MEDIA_ATTRIBUTE} 
};


const STATE_TRANSITION  g_StateMediaKeyTransitions[]    =   {  
    {CHAR_MEDIA,        STATE_MEDIA},
    {CHAR_MEDIA_ATTRIBUTE,  STATE_MEDIA_ATTRIBUTE} 
};


const STATE_TRANSITION  g_StateMediaAttributeTransitions[]={ 
    {CHAR_MEDIA,        STATE_MEDIA},
    {CHAR_MEDIA_ATTRIBUTE,  STATE_MEDIA_ATTRIBUTE},    
    {CHAR_MEDIA,        STATE_MEDIA}
};


// const state transition table definition
const TRANSITION_INFO g_TransitionTable[STATE_NUM_STATES] = {
    STATE_TRANSITION_ENTRY(STATE_START,         g_StateStartTransitions),

    STATE_TRANSITION_ENTRY(STATE_VERSION,       g_StateVersionTransitions),

    STATE_TRANSITION_ENTRY(STATE_ORIGIN,        g_StateOriginTransitions),

    STATE_TRANSITION_ENTRY(STATE_SESSION_NAME,  g_StateSessionNameTransitions),

    STATE_TRANSITION_ENTRY(STATE_TITLE,         g_StateTitleTransitions),

    STATE_TRANSITION_ENTRY(STATE_URI,           g_StateUriTransitions),

    STATE_TRANSITION_ENTRY(STATE_EMAIL,         g_StateEmailTransitions),

    STATE_TRANSITION_ENTRY(STATE_PHONE,         g_StatePhoneTransitions),

    STATE_TRANSITION_ENTRY(STATE_CONNECTION,    g_StateConnectionTransitions),

    STATE_TRANSITION_ENTRY(STATE_BANDWIDTH,     g_StateBandwidthTransitions),

    STATE_TRANSITION_ENTRY(STATE_TIME,          g_StateTimeTransitions),

    STATE_TRANSITION_ENTRY(STATE_REPEAT,        g_StateRepeatTransitions),

    STATE_TRANSITION_ENTRY(STATE_ADJUSTMENT,    g_StateAdjustmentTransitions),

    STATE_TRANSITION_ENTRY(STATE_KEY,           g_StateKeyTransitions),

    STATE_TRANSITION_ENTRY(STATE_ATTRIBUTE,     g_StateAttributeTransitions),

    STATE_TRANSITION_ENTRY(STATE_MEDIA,         g_StateMediaTransitions),

    STATE_TRANSITION_ENTRY(STATE_MEDIA_TITLE,   g_StateMediaTitleTransitions),

    STATE_TRANSITION_ENTRY(STATE_MEDIA_CONNECTION,  g_StateMediaConnectionTransitions),

    STATE_TRANSITION_ENTRY(STATE_MEDIA_BANDWIDTH,   g_StateMediaBandwidthTransitions),

    STATE_TRANSITION_ENTRY(STATE_MEDIA_KEY,         g_StateMediaKeyTransitions),

    STATE_TRANSITION_ENTRY(STATE_MEDIA_ATTRIBUTE,   g_StateMediaAttributeTransitions)
};



BOOL
SDP::Init(
    )
{
    // check if already initialized
    if ( NULL != m_MediaList )
    {
        SetLastError(ERROR_ALREADY_INITIALIZED);
        return FALSE;
    }

    // create media and time lists
    // set flags to destroy them when the sdp instance destructs

    try
    {
        m_MediaList = new SDP_MEDIA_LIST;
    }
    catch(...)
    {
        m_MediaList = NULL;
    }

    if ( NULL == m_MediaList )
    {
        SetLastError(ERROR_NOT_ENOUGH_MEMORY);
        return FALSE;
    }
    m_DestroyMediaList = TRUE;

    try
    {
        m_TimeList = new SDP_TIME_LIST;
    }
    catch(...)
    {
        m_TimeList = NULL;
    }

    if ( NULL == m_TimeList )
    {
        SetLastError(ERROR_NOT_ENOUGH_MEMORY);
        return FALSE;
    }
    m_DestroyTimeList = TRUE;

    return TRUE;
}




// determine the character set implicit from the packet
BOOL
SDP::DetermineCharacterSet(
    IN      CHAR                *SdpPacket,
        OUT SDP_CHARACTER_SET   &CharacterSet
    )
{
    ASSERT(NULL != SdpPacket);

    // search for charset string (attribute "\na=charset:")
    CHAR *AttributeString = strstr(SdpPacket, SDP_CHARACTER_SET_STRING);

    // check if the character set is supplied
    if ( NULL == AttributeString )
    {
        // ASCII is the default character set
        CharacterSet = CS_ASCII;
        return TRUE;
    }
    else
    {
        // the character set attribute string must be present before the first media field
        CHAR *FirstMediaField = strstr(SdpPacket, MEDIA_SEARCH_STRING);

        // there is a media field and it doesn't occur after the character set string, signal error
        if ( (NULL != FirstMediaField)              &&
             (FirstMediaField <= AttributeString)    )
        {
            SetLastError(SDP_INVALID_CHARACTER_SET_FORMAT);
            return FALSE;
        }

        // advance attribute string beyond the attribute specification
        AttributeString += SDP_CHARACTER_SET_STRLEN;

        // compare the character set string with each of the well known
        // character set strings
        for ( UINT i=0; i < NUM_SDP_CHARACTER_SET_ENTRIES; i++ )
        {
            // NOTE: no need to null terminate the character string as
            // strncmp will return on finding the first character that
            // doesn't match
            if ( !strncmp(
                    AttributeString, 
                    SDP_CHARACTER_SET_TABLE[i].m_CharSetString, 
                    SDP_CHARACTER_SET_TABLE[i].m_Length
                    ) )
            {
                CharacterSet = SDP_CHARACTER_SET_TABLE[i].m_CharSetCode;
				return TRUE;
            }
        }

        // unrecognized character set
        SetLastError(SDP_INVALID_CHARACTER_SET);
        return FALSE;
    }

    // the code should not reach here
    ASSERT(FALSE);
}



/*
Assumption: We are at the start of a new line. There may or may not be a
new line character before current
*/

BOOL
SDP::GetType(
        OUT CHAR    &Type,
        OUT BOOL    &EndOfPacket
    )
{
    // ensure that we don't peek beyond the end of the string
    if ( EOS == m_Current[0] )
    {
        EndOfPacket = TRUE;
        return TRUE;
    }

    // check if the second char is EQUAL_CHAR
    if ( CHAR_EQUAL != m_Current[1] )
    {
        SetLastError(SDP_INVALID_FORMAT);
        return FALSE;
    }

    EndOfPacket = FALSE;
    Type = m_Current[0];
    return TRUE;
}


BOOL
SDP::CheckTransition(
    IN      CHAR        Type,
    IN      PARSE_STATE CurrentParseState,
        OUT PARSE_STATE &NewParseState
    )
{
    // validate the current state
    ASSERT(STATE_NUM_STATES > CurrentParseState);

    // validate transition table entry
    ASSERT(g_TransitionTable[CurrentParseState].m_ParseState == CurrentParseState);

    // see if such a trigger exists for the current state
    for( UINT i=0; i < g_TransitionTable[CurrentParseState].m_NumTransitions; i++ )
    {
        // check if trigger has been found
       if ( Type == g_TransitionTable[CurrentParseState].m_Transitions[i].m_Type )
        {
            NewParseState = g_TransitionTable[CurrentParseState].m_Transitions[i].m_NewParseState;
            break;
        }
    }
    
    // check if a trigger was found
    if ( g_TransitionTable[CurrentParseState].m_NumTransitions <= i )
    {
        SetLastError(SDP_INVALID_FORMAT);
        return FALSE;
    }

    return TRUE;
}


BOOL
SDP::GetValue(
    IN      CHAR    Type
    )
{
    PARSE_STATE NewParseState;

    // check if such a transition (current parse state --Type--> new parse state) exists
    if ( !CheckTransition(Type, m_ParseState, NewParseState) )
    {
        return FALSE;
    }

    BOOL    LineParseResult = FALSE;

    // fire corresponding action
    switch(NewParseState)
    {
    case STATE_VERSION:
        {
            LineParseResult = m_ProtocolVersion.ParseLine(m_Current);
        }

        break;

    case STATE_ORIGIN:
        {
            LineParseResult = m_Origin.ParseLine(m_Current);
        }

        break;

    case STATE_SESSION_NAME:
        {
            LineParseResult = m_SessionName.ParseLine(m_Current);
        }

        break;

    case STATE_TITLE:
        {
            LineParseResult = m_SessionTitle.ParseLine(m_Current);
        }

        break;

    case STATE_URI:
        {
            LineParseResult = m_Uri.ParseLine(m_Current);
        }

        break;

    case STATE_EMAIL:
        {
            LineParseResult = m_EmailList.ParseLine(m_Current);
        }

        break;

    case STATE_PHONE:
        {
            LineParseResult = m_PhoneList.ParseLine(m_Current);
        }

        break;

    case STATE_CONNECTION:
        {
            LineParseResult = m_Connection.ParseLine(m_Current);
        }

        break;

    case STATE_BANDWIDTH:
        {
            LineParseResult = m_Bandwidth.ParseLine(m_Current);
        }

        break;

    case STATE_TIME:
        {
            LineParseResult = GetTimeList().ParseLine(m_Current);
        }

        break;

    case STATE_REPEAT:
        {
            ParseMember(SDP_TIME, GetTimeList(), SDP_REPEAT_LIST, GetRepeatList, m_Current, LineParseResult);
        }

        break;

    case STATE_ADJUSTMENT:
        {
            LineParseResult = GetTimeList().GetAdjustment().ParseLine(m_Current);
        }

        break;

    case STATE_KEY:
        {
            LineParseResult = m_EncryptionKey.ParseLine(m_Current);
        }

        break;

    case STATE_ATTRIBUTE:
        {
            LineParseResult = m_AttributeList.ParseLine(m_Current);
        }

        break;

    case STATE_MEDIA:
        {
            LineParseResult = GetMediaList().ParseLine(m_Current);
        }

        break;

    case STATE_MEDIA_TITLE:
        {
            ParseMember(SDP_MEDIA, GetMediaList(), SDP_REQD_BSTRING_LINE, GetTitle, m_Current, LineParseResult);
        }

        break;

    case STATE_MEDIA_CONNECTION:
        {
            ParseMember(SDP_MEDIA, GetMediaList(), SDP_CONNECTION, GetConnection, m_Current, LineParseResult);
        }

        break;

    case STATE_MEDIA_BANDWIDTH:
        {
            ParseMember(SDP_MEDIA, GetMediaList(), SDP_BANDWIDTH, GetBandwidth, m_Current, LineParseResult);
        }

        break;

    case STATE_MEDIA_KEY:
        {
            ParseMember(SDP_MEDIA, GetMediaList(), SDP_ENCRYPTION_KEY, GetEncryptionKey, m_Current, LineParseResult);
        }

        break;

    case STATE_MEDIA_ATTRIBUTE:
        {
            ParseMember(SDP_MEDIA, GetMediaList(), SDP_ATTRIBUTE_LIST, GetAttributeList, m_Current, LineParseResult);
        }

        break;

    default:
        {
            // should never reach here
            ASSERT(FALSE);

            SetLastError(SDP_INVALID_FORMAT);
            return FALSE;
        }

        break;
    };

    // check if parsing the line succeeded
    if ( !LineParseResult )
    {
        return FALSE;
    }
        
    // change to the new state
    m_ParseState    = NewParseState;
    return TRUE;
}



BOOL    
SDP::IsValidEndState(
    )   const
{
    if ( (STATE_CONNECTION  <=  m_ParseState)    &&
         (STATE_NUM_STATES  >   m_ParseState)     )
    {
        return TRUE;
    }

    SetLastError(SDP_INVALID_FORMAT);
    return FALSE;
}



void
SDP::Reset(
	)
{
	// perform the destructor actions (release any allocated resources)

	// free the sdp packet if one was created
    if ( NULL != m_SdpPacket )
    {
        delete m_SdpPacket;
 		m_SdpPacket = NULL;
   }

	// perform the constructor actions (initialize variables, resources)

    // initialize the parse state
    m_ParseState = STATE_START;

	m_LastGenFailed = FALSE;
	m_BytesAllocated = 0;
	m_SdpPacketLength = 0;

	m_Current = NULL;
	m_ParseState = STATE_START;

	// m_CharacterSet - nothing needs to be set
    m_CharacterSet = CS_UTF8;

	// reset the member instances
	m_ProtocolVersion.Reset();
	m_Origin.Reset();
	m_SessionName.Reset();
	m_SessionTitle.Reset();
	m_Uri.Reset();
	m_EmailList.Reset();
	m_PhoneList.Reset(); 
	m_Connection.Reset();
	m_Bandwidth.Reset();
	GetTimeList().Reset();
	m_EncryptionKey.Reset();
	m_AttributeList.Reset();
	GetMediaList().Reset();
}


BOOL
SDP::ParseSdpPacket(
    IN      CHAR                *SdpPacket,
    IN      SDP_CHARACTER_SET   CharacterSet
    )
{
    ASSERT(NULL != m_MediaList);
    ASSERT(NULL != m_TimeList);

    // check if the instance has already parsed an sdp packet
    if ( NULL != m_Current )
    {
        // reset the instance and try to parse the sdp packet
		Reset();
    }

    // check if the passed in parameters are valid
    if ( (NULL == SdpPacket) || (CS_INVALID == CharacterSet) )
    {
        SetLastError(SDP_INVALID_PARAMETER);
        return FALSE;
    }

    // point the current pointer to the start of sdp packet
	m_Current = SdpPacket;

    // if the character set has not yet been determined
    if ( CS_IMPLICIT == CharacterSet )
    {
        // determine the character set
        if ( !DetermineCharacterSet(SdpPacket, m_CharacterSet) )
        {
            return FALSE;
        }
    }
    else // it cannot be CS_UNRECOGNIZED (checked on entry)
    {
        ASSERT(CS_INVALID != CharacterSet);

        m_CharacterSet = CharacterSet;
    }

    // set the character sets for all the SDP_BSTRING instances that make up the SDP description
    m_Origin.GetUserName().SetCharacterSet(m_CharacterSet);
    m_SessionName.GetBstring().SetCharacterSet(m_CharacterSet);
    m_SessionTitle.GetBstring().SetCharacterSet(m_CharacterSet);

    m_EmailList.SetCharacterSet(m_CharacterSet);
    m_PhoneList.SetCharacterSet(m_CharacterSet);

    // set the character set for the SDP_MEDIA_LIST instance
    GetMediaList().SetCharacterSet(m_CharacterSet);

    // parse the type and its value for each line in the sdp packet
    do
    {
        BOOL    EndOfPacket;
        CHAR    Type;

        // get the next type
        if ( !GetType(Type, EndOfPacket) )
        {
            return FALSE;
        }

        if ( EndOfPacket )
        {
            break;
        }

        // advance the current pointer to beyond the Type= fields
        m_Current+=2;
          
        // get the value for the specified type
        if ( !GetValue(Type) )
        {
            return FALSE;
        }
    }
    while ( 1 );

    // validate if the parsing state is a valid end state
    if ( !IsValidEndState() )
    {
        return FALSE;
    }

    return TRUE;
}



// clears the modified state for each member field/value
// this is used in sdpblb.dll to clear the modified state (when an sdp 
// is parsed in, the state of all parsed in fields/values is modified) and 
// the m_WasModified dirty flag
void    
SDP::ClearModifiedState(
    )
{
    m_ProtocolVersion.GetCharacterStringSize();
    m_Origin.GetCharacterStringSize();
    m_SessionName.GetCharacterStringSize();
    m_SessionTitle.GetCharacterStringSize();
    m_Uri.GetCharacterStringSize();
    m_EmailList.GetCharacterStringSize();
    m_PhoneList.GetCharacterStringSize();
    m_Connection.GetCharacterStringSize();
    m_Bandwidth.GetCharacterStringSize();
    GetTimeList().GetCharacterStringSize();
    m_EncryptionKey.GetCharacterStringSize();
    m_AttributeList.GetCharacterStringSize();
    GetMediaList().GetCharacterStringSize();
}


BOOL   
SDP::IsValid(
    )
{
    // query only the mandatory values
    return 
        m_ProtocolVersion.IsValid()  &&
        m_Origin.IsValid()           &&
        m_SessionName.IsValid()      &&
        m_Connection.IsValid();
}


BOOL   
SDP::IsModified(
    )
{
    ASSERT(IsValid());

    return 
        m_ProtocolVersion.IsModified()  ||
        m_Origin.IsModified()           ||
        m_SessionName.IsModified()      ||
        m_SessionTitle.IsModified()     ||
        m_Uri.IsModified()              ||
        m_EmailList.IsModified()        ||
        m_PhoneList.IsModified()        ||
        m_Connection.IsModified()       ||
        m_Bandwidth.IsModified()        ||
        GetTimeList().IsModified()      ||
        m_EncryptionKey.IsModified()    ||
        m_AttributeList.IsModified()    ||
        GetMediaList().IsModified();
}


// an sdp packet is not generated the way a line is generated (using a SeparatorChar and
// an sdp field carray). this is mainly because these carrays will have to be modified on
// insertion of email and phone lists, media fields and attribute lists or specification of
// other optional sdp properties.

CHAR    *    
SDP::GenerateSdpPacket(
    )
{
    // check if valid
    if ( !IsValid() )
    {
        return NULL;
    }

    // check if the sdp packet needs to be regenerated
    // (if the sdp packet exists and no modifications have taken place since
    // the last time)
    BOOL HasChangedSinceLast = IsModified();
    if ( (!m_LastGenFailed) && (NULL != m_SdpPacket) && !HasChangedSinceLast )
    {
        return m_SdpPacket;
    }

    // determine the length of character string
    m_SdpPacketLength  =    
        m_ProtocolVersion.GetCharacterStringSize()  +
        m_Origin.GetCharacterStringSize()           +
        m_SessionName.GetCharacterStringSize()      +
        m_SessionTitle.GetCharacterStringSize()     +
        m_Uri.GetCharacterStringSize()              +
        m_EmailList.GetCharacterStringSize()        +
        m_PhoneList.GetCharacterStringSize()        +
        m_Connection.GetCharacterStringSize()       +
        m_Bandwidth.GetCharacterStringSize()        +
        GetTimeList().GetCharacterStringSize()      +
        m_EncryptionKey.GetCharacterStringSize()    +
        m_AttributeList.GetCharacterStringSize()    +
        GetMediaList().GetCharacterStringSize();

    // check if a buffer needs to be allocated, allocate if required
    if ( m_BytesAllocated < (m_SdpPacketLength+1) )
    {
        CHAR * NewSdpPacket;         

        try
        {
            NewSdpPacket = new CHAR[m_SdpPacketLength+1];
        }
        catch(...)
        {
            NewSdpPacket = NULL;
        }

        if (NewSdpPacket == NULL)
        {
            m_LastGenFailed = TRUE;
            return NULL;
        }
       
        // if we have an old sdp packet, get rid of it now
        if ( NULL != m_SdpPacket )
        {
            delete m_SdpPacket;
        }

        m_SdpPacket = NewSdpPacket;
        m_BytesAllocated = m_SdpPacketLength+1;
    }

    // fill in the buffer
    ostrstream  OutputStream(m_SdpPacket, m_BytesAllocated);

    // if this method ever fails here for this instance, further calls to the
    // method without modification will return a ptr
    if ( !( m_ProtocolVersion.PrintValue(OutputStream) &&
            m_Origin.PrintValue(OutputStream)          &&
            m_SessionName.PrintValue(OutputStream)     &&
            m_SessionTitle.PrintValue(OutputStream)    &&
            m_Uri.PrintValue(OutputStream)             &&
            m_EmailList.PrintValue(OutputStream)       &&
            m_PhoneList.PrintValue(OutputStream)       &&
            m_Connection.PrintValue(OutputStream)      &&
            m_Bandwidth.PrintValue(OutputStream)       &&
            GetTimeList().PrintValue(OutputStream)        &&
            m_EncryptionKey.PrintValue(OutputStream)   &&
            m_AttributeList.PrintValue(OutputStream)   &&
            GetMediaList().PrintValue(OutputStream)       )   
       )
    {
        m_LastGenFailed = TRUE;
        return NULL;
    }

    OutputStream << EOS;
    m_LastGenFailed = FALSE;

    // dirty flag - is initially false and is set to TRUE when an sdp is generated because it had
    // been modified since the last time the sdp was generated.
    // NOTE: at this point IsModified() is false, so m_WasModified captures the fact that
    // the sdp was modified at some point
    if ( !m_WasModified && HasChangedSinceLast )
    {
        m_WasModified = TRUE;
    }
    return m_SdpPacket;
}



SDP::~SDP(
    )
{
    if ( NULL != m_SdpPacket )
    {
        delete m_SdpPacket;
    }

    if ( m_DestroyMediaList && (NULL != m_MediaList) )
    {
        delete m_MediaList;
    }

    if ( m_DestroyTimeList && (NULL != m_TimeList) )
    {
        delete m_TimeList;
    }
}