/* Copyright (c) 1997-1999 Microsoft Corporation Module Name: sdp.cpp Abstract: Author: */ #include "sdppch.h" #include #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; } }