void WaitingFor200OkWithMediaOffer::ProvisionalResponse( DialogTracker& impl, SipMessage& response, const char* address, int port ) const { if( response.getResponseStatusCode() != SIP_TRYING_CODE ) { // RFC requires that all SDP previews be identical. In ensure that this // requirement is met, we apply the saved copy of the patched SDP preview // to the response. if( response.hasSdpBody() ) { impl.applyPatchedSdpPreview( response ); } // we are receiving a provisional response - check if it is sent reliably... if( response.getHeaderValue( 0, SIP_RSEQ_FIELD ) ) { // Presence of RSeq: header in the message indicates that it is sent reliably ChangeState( impl, impl.pWaitingForPrackWithMediaAnswer ); } else { // We have received an unreliable provisional response - although that does // not cause a state machine state change, we need to reset the tick counter // to show that there is still activity in this dialog. impl.resetTimerTickCounter(); } } }
void WaitingForMediaOffer::ProvisionalResponse( DialogTracker& impl, SipMessage& response, const char* address, int port ) const { if( response.getResponseStatusCode() != SIP_TRYING_CODE ) { // Both reliable and unreliable provisional responses can carry SDP bodies. According to // draft-ietf-sipping-sip-offeranswer-04.txt section 3.1, unreliable provisional responses // carrying an offer is a mere preview of what the 'real' SDP offfer will be and that it // must be identical to it. Since we may be changing the SDP of the 'real' offer to compensate // for NATs we need to also manipulate the 'preview' offer to make it meet the requirement that // the preview and 'real' offers be identical. impl.ProcessMediaOffer( response, INITIAL_OFFER_ANSWER ); // we are receiving a provisional response - check if it is sent reliably... if( response.getHeaderValue( 0, SIP_RSEQ_FIELD ) ) { // Presence of RSeq: header in the message indicates that it is sent reliably ChangeState( impl, impl.pWaitingForPrackWithMediaAnswer ); } else { // We have received an unreliable provisional response - take a copy of the // patched SDP so that it can be re-applied to subsequent responses carrying // the same SDP body. impl.savePatchedSdpPreview( response ); ChangeState( impl, impl.pWaitingFor200OkWithMediaOffer ); } } }
void WaitingFor200OkforInvite::SuccessfulResponse( DialogTracker& impl, SipMessage& response, const char* address, int port ) const { int seqNum; UtlString seqMethod; if( response.getCSeqField( &seqNum, &seqMethod ) ) { if( seqMethod.compareTo( SIP_INVITE_METHOD ) == 0 ) { // normally we would not be expecting this 200 OK response to carry // an SDP however we have encountered in the field some endpoints // that repeat the SDP answer they already sent in a previous // reliable provisional response (see XECS-2079 for the details). // Given that, if an SDP answer is found, we will reprocess it // to make sure it gets the same transformations that the initial // one got as per XECS-2089. if( response.hasSdpBody() ) { impl.ProcessMediaAnswer( response, INITIAL_OFFER_ANSWER ); } ChangeState( impl, impl.pWaitingForAckForInvite ); } else { OsSysLog::add(FAC_NAT,PRI_DEBUG,"'%s:%s' - Received unexpected successful response for %s request", impl.name(), impl.GetCurrentState()->name(), seqMethod.data() ); } } }
void WaitingForMediaAnswer::ProvisionalResponse( DialogTracker& impl, SipMessage& response, const char* address, int port ) const { if( response.getResponseStatusCode() != SIP_TRYING_CODE ) { if( response.hasSdpBody() ) { // Both reliable and unreliable provisional responses can carry SDP bodies. According to // draft-ietf-sipping-sip-offeranswer-04.txt section 3.1, unreliable provisional responses // carrying an answer is a mere previes of what the 'real' SDP answer will be and that it // must be identical to it. Since we may be changing the SDP of the 'real' answer to compensate // for NATs we need to also manipulate the 'preview' answer to make match the requirement that // the preview and 'real' answers be identical. // we are receiving a provisional response - check if it is sent reliably... impl.ProcessMediaAnswer( response, INITIAL_OFFER_ANSWER ); if( response.getHeaderValue( 0, SIP_RSEQ_FIELD ) ) { // Presence of RSeq: header in the message indicates that it is sent reliably ChangeState( impl, impl.pWaitingForPrack ); } else { // We have received an unreliable provisional response - although that does // not cause a state machine state change, we need to reset the tick counter // to show that there is still activity in this dialog. impl.resetTimerTickCounter(); } } } }
void ProcessingPrack::SuccessfulResponse( DialogTracker& impl, SipMessage& response, const char* address, int port ) const { int seqNum; UtlString seqMethod; if( response.getCSeqField( &seqNum, &seqMethod ) ) { if( seqMethod.compareTo( SIP_INVITE_METHOD ) == 0 ) { // normally we would not be expecting this 200 OK response to carry // an SDP in this state however we have encountered in the field some endpoints // that repeat the SDP answer they already sent in a previous // Successful response (see XECS-2079 for the details). // Given that, if an SDP answer is found, we will reprocess it // to make sure it gets the same transformations that the initial // one got as per XECS-2089. if( response.hasSdpBody() ) { impl.ProcessMediaAnswer( response, INITIAL_OFFER_ANSWER ); } } else { OsSysLog::add(FAC_NAT,PRI_DEBUG,"'%s:%s' - Received unexpected Successful Response for %s request", impl.name(), impl.GetCurrentState()->name(), seqMethod.data() ); } // We have received a response - although that does // not cause a state machine state change, we need to reset the tick counter // to show that there is still activity in this dialog. impl.resetTimerTickCounter(); } }
void Negotiating::FailureResponse( DialogTracker& impl, SipMessage& response, const char* address, int port ) const { int seqNum; UtlString seqMethod; if( response.getCSeqField( &seqNum, &seqMethod ) ) { if( seqMethod.compareTo( SIP_INVITE_METHOD ) == 0 ) { // session negotiation failed. Deallocate all the tentative // media relays tentatively allocated to handle the media // sessions that just failed. impl.deallocateAndClearAllMediaRelaySessions( true, true, false ); if( !impl.getDialogEstablishedFlag() ) { // this is a final failure response to a dialog-forming INVITE. That // event marks the end of the dialog hence, we do not need to continue // to track it. ChangeState( impl, impl.pMoribund ); } else { // the renegotiation failed but the dialog is still active. Go back to state where // we wait for an incoming INVITE. ChangeState( impl, impl.pWaitingForInvite ); } } else { OsSysLog::add(FAC_NAT,PRI_DEBUG,"'%s:%s' - Received unexpected successful response for %s request", impl.name(), impl.GetCurrentState()->name(), seqMethod.data() ); } } }
bool WaitingForAckForInvite::AckRequest( DialogTracker& impl, SipMessage& request, TransactionDirectionality direction, const char* address, int port ) const { impl.setDialogEstablishedFlag(); impl.promoteTentativeMediaRelaySessionsToCurrent(); ChangeState( impl, impl.pWaitingForInvite ); return false; }
void TimeBoundState::CleanUpTimerTick( DialogTracker& impl ) const { if( impl.incrementTimerTickCounter() >= MAX_TIMER_TICK_COUNTS_BEFORE_DIALOG_TRACKER_CLEAN_UP ) { OsSysLog::add(FAC_NAT,PRI_DEBUG,"'%s:%s' - cleaning up stale dialog tracker", impl.name(), impl.GetCurrentState()->name() ); ChangeState( impl, impl.pMoribund ); } }
void DialogTrackerState::SuccessfulResponse( DialogTracker& impl, SipMessage& response, const char* address, int port ) const { if( impl.isARetransmittedResponse( response ) ) { impl.restoreSdpBodyOfRetransmittedResponse( response ); } else { OsSysLog::add(FAC_NAT,PRI_WARNING,"'%s': Received unexpected event SuccessfulResponse while in state '%s'", impl.name(), impl.GetCurrentState()->name() ); } }
void WaitingForInvite::CleanUpTimerTick( DialogTracker& impl ) const { if( impl.getDialogEstablishedFlag() ) { // we have an established dialog - check it see if the media is still flowing if( !impl.wasMediaTrafficSeenInLastNSeconds( IDLE_MEDIA_MAXIMUM_IN_SECONDS ) ) { OsSysLog::add(FAC_NAT,PRI_WARNING,"'%s': Terminating dialog tracker due to excessive media inactivity period", impl.name() ); ChangeState( impl, impl.pMoribund ); } } }
bool DialogTrackerState::UpdateRequest( DialogTracker& impl, SipMessage& request, TransactionDirectionality direction, const char* address, int port ) const { if( impl.isARetransmittedRequest( request ) ) { impl.restoreSdpBodyOfRetransmittedRequest( request ); } else { OsSysLog::add(FAC_NAT,PRI_WARNING,"'%s': Received unexpected event UpdateRequest while in state '%s'", impl.name(), impl.GetCurrentState()->name() ); } return true; }
void WaitingFor200OkForPrack::FailureResponse( DialogTracker& impl, SipMessage& response, const char* address, int port ) const { int seqNum; UtlString seqMethod; if( response.getCSeqField( &seqNum, &seqMethod ) ) { if( seqMethod.compareTo( SIP_PRACK_METHOD ) == 0 ) { ChangeState( impl, impl.pWaitingForPrack ); } else { OsSysLog::add(FAC_NAT,PRI_DEBUG,"'%s:%s' - Received unexpected successful response for %s request", impl.name(), impl.GetCurrentState()->name(), seqMethod.data() ); } } }
void WaitingFor200OkWithAnswerForPrack::SuccessfulResponse( DialogTracker& impl, SipMessage& response, const char* address, int port ) const { int seqNum; UtlString seqMethod; if( response.getCSeqField( &seqNum, &seqMethod ) ) { if( seqMethod.compareTo( SIP_PRACK_METHOD ) == 0 ) { impl.ProcessMediaAnswer( response, NON_INITIAL_OFFER_ANSWER ); impl.modifyNonIntialOfferAnswerExchangeDoneFlag( true ); ChangeState( impl, impl.pWaitingFor200OkforInvite ); } else { OsSysLog::add(FAC_NAT,PRI_DEBUG,"'%s:%s' - Received unexpected successful response for %s request", impl.name(), impl.GetCurrentState()->name(), seqMethod.data() ); } } }
void WaitingFor200OkForSlowStartPrack::FailureResponse( DialogTracker& impl, SipMessage& response, const char* address, int port ) const { int seqNum; UtlString seqMethod; if( response.getCSeqField( &seqNum, &seqMethod ) ) { if( seqMethod.compareTo( SIP_PRACK_METHOD ) == 0 ) { // PRACK was rejected. Keep tentative INVITE media relays in case an // acceptable PRACK gets generated by the UAC. ChangeState( impl, impl.pWaitingForPrackWithMediaAnswer ); } else { OsSysLog::add(FAC_NAT,PRI_DEBUG,"'%s:%s' - Received unexpected failure response for %s request", impl.name(), impl.GetCurrentState()->name(), seqMethod.data() ); } } }
void WaitingFor200OkWithMediaOffer::SuccessfulResponse( DialogTracker& impl, SipMessage& response, const char* address, int port ) const { // RFC requires that all SDP previews be identical. In ensure that this // requirement is met, we apply the saved copy of the patched SDP preview // to the response. if( response.hasSdpBody() ) { impl.applyPatchedSdpPreview( response ); } ChangeState( impl, impl.pWaitingForAckWithAnswerForInvite ); }
void WaitingFor200OkWithAnswerForPrack::FailureResponse( DialogTracker& impl, SipMessage& response, const char* address, int port ) const { int seqNum; UtlString seqMethod; if( response.getCSeqField( &seqNum, &seqMethod ) ) { if( seqMethod.compareTo( SIP_PRACK_METHOD ) == 0 ) { // PRACK offer/answer failed. Deallocate any tentative media relays allocation // in preparation for that failed PRACK offer/answer negotiation. impl.deallocateAndClearAllMediaRelaySessions( false, true, false ); ChangeState( impl, impl.pWaitingForPrack ); } else { OsSysLog::add(FAC_NAT,PRI_DEBUG,"'%s:%s' - Received unexpected failure response for %s request", impl.name(), impl.GetCurrentState()->name(), seqMethod.data() ); } } }
void WaitingFor200OkWithAnswerForPrack::SuccessfulResponse( DialogTracker& impl, SipMessage& response, const char* address, int port ) const { int seqNum; UtlString seqMethod; if( response.getCSeqField( &seqNum, &seqMethod ) ) { if( seqMethod.compareTo( SIP_PRACK_METHOD ) == 0 ) { impl.ProcessMediaAnswer( response, NON_INITIAL_OFFER_ANSWER ); impl.modifyNonIntialOfferAnswerExchangeDoneFlag( true ); ChangeState( impl, impl.pProcessingPrackWaitingForAckforInvite ); } else { // Not interesting for us but our parent class provides some handling for // other successful responses. ProcessingPrack::SuccessfulResponse( impl, response, address, port ); } } }
bool WaitingForPrack::PrackRequest( DialogTracker& impl, SipMessage& request, TransactionDirectionality direction, const char* address, int port ) const { if( request.hasSdpBody() ) { impl.ProcessMediaOffer( request, NON_INITIAL_OFFER_ANSWER ); ChangeState( impl, impl.pWaitingFor200OkWithAnswerForPrack ); } else { ChangeState( impl, impl.pWaitingFor200OkForPrack ); } return true; }
DialogTracker* SessionContext::allocateNewDialogTrackerBasedOnReference( const UtlString& discriminatingTag ) { DialogTracker* pNewDialogTracker = 0; pNewDialogTracker = new DialogTracker( *mpReferenceDialogTracker, discriminatingTag ); if( pNewDialogTracker ) { addDialogTrackerToList( discriminatingTag, pNewDialogTracker ); OsSysLog::add(FAC_NAT, PRI_DEBUG, "SessionContext[%s]::allocateNewDialogTrackerBasedOnReference: allocated DialogTracker #%zd for tag %s", mHandle.data(), getNumberOfTrackedDialogs(), discriminatingTag.data() ); // We have a new tracker that is utilizing the same Media RelaySessions as the // reference. Increment their link count to track the number of DialogTrackers using // them and avoid premature de-allocations. size_t index; size_t numSavedMediaDescriptors = pNewDialogTracker->getNumberOfMediaDescriptors(); for( index = 0; index < numSavedMediaDescriptors; index++ ) { const MediaDescriptor* pMediaDescriptor; pMediaDescriptor = pNewDialogTracker->getReadOnlyMediaDescriptor( index ); tMediaRelayHandle tempMediaRelayHandle; if( ( tempMediaRelayHandle = pMediaDescriptor->getTentativeInitialMediaRelayHandle() ) != INVALID_MEDIA_RELAY_HANDLE ) { mpMediaRelay->incrementLinkCountOfMediaRelaySession( tempMediaRelayHandle ); } if( ( tempMediaRelayHandle = pMediaDescriptor->getTentativeNonInitialMediaRelayHandle() ) != INVALID_MEDIA_RELAY_HANDLE ) { mpMediaRelay->incrementLinkCountOfMediaRelaySession( tempMediaRelayHandle ); } if( ( tempMediaRelayHandle = pMediaDescriptor->getCurrentMediaRelayHandle() ) != INVALID_MEDIA_RELAY_HANDLE ) { mpMediaRelay->incrementLinkCountOfMediaRelaySession( tempMediaRelayHandle ); } } } return pNewDialogTracker; }
void SessionContext::handleCleanUpTimerTick( void ) { ssize_t numberOfDialogTrackersEnteringRoutine = getNumberOfTrackedDialogs(); UtlHashMapIterator dialogTrackerIterator( mDialogTrackersMap ); while( dialogTrackerIterator() ) { DialogTracker *pDialogTracker; pDialogTracker = dynamic_cast<DialogTracker*>( dialogTrackerIterator.value() ); pDialogTracker->handleCleanUpTimerTick(); } // Check if the processing of the request caused the last DialogTracker to be deleted. // If so, the SessionContext is not required anymore therefore tell the CallTracker that // we are ready for deletion if( numberOfDialogTrackersEnteringRoutine && deleteDialogTrackersReadyForDeletion() == numberOfDialogTrackersEnteringRoutine ) { mpOwningCallTracker->reportSessionContextReadyForDeletion( mHandle ); } }
void WaitingForMediaAnswer::SuccessfulResponse( DialogTracker& impl, SipMessage& response, const char* address, int port ) const { int seqNum; UtlString seqMethod; if( response.getCSeqField( &seqNum, &seqMethod ) ) { if( seqMethod.compareTo( SIP_INVITE_METHOD ) == 0 ) { impl.ProcessMediaAnswer( response, INITIAL_OFFER_ANSWER ); ChangeState( impl, impl.pWaitingForAckForInvite ); } } }
void SessionContext::handleResponse( SipMessage& message, const char* address, int port ) { ssize_t numberOfDialogTrackersEnteringRoutine = getNumberOfTrackedDialogs(); UtlString discriminatingTag = getDiscriminatingTagValue( message ); // Retrieve DialogTracker object that handles this dialog. DialogTracker* pDialogTracker = 0; if( !discriminatingTag.isNull() && ( pDialogTracker = getDialogTrackerForTag( discriminatingTag ) ) != 0 ) { // present the response to the DialogTracker pDialogTracker->handleResponse( message, address, port ); } else { if( message.getResponseStatusCode() < SIP_3XX_CLASS_CODE ) { // we do not have a DialogTracker for this response. If this is as // 1xx or 2xx response to the dialog-forming INVITE, this response is creating a // new dialog. Create a new DialogTracker based on the reference DialogTracker if( !discriminatingTag.isNull() && mDialogFormingInviteCseq == CseqData( message ) ) { DialogTracker* pNewDialogTracker; if( (pNewDialogTracker = allocateNewDialogTrackerBasedOnReference( discriminatingTag ) ) ) { pNewDialogTracker->handleResponse( message, address, port ); } } } else if( message.getResponseStatusCode() >= SIP_4XX_CLASS_CODE ) { // This session context has received a final failure response. The // INVITE has been rejected. Present that response to all the // DialogTrackers so that they can terminate. UtlHashMapIterator dialogTrackerIterator( mDialogTrackersMap ); while( dialogTrackerIterator() ) { DialogTracker *pDialogTracker; pDialogTracker = dynamic_cast<DialogTracker*>( dialogTrackerIterator.value() ); pDialogTracker->handleResponse( message, address, port ); } } } // Check if the processing of the request caused the last DialogTracker to be deleted. // If so, the SessionContext is not required anymore therefore tell the CallTracker that // we are ready for deletion if( numberOfDialogTrackersEnteringRoutine && deleteDialogTrackersReadyForDeletion() == numberOfDialogTrackersEnteringRoutine ) { mpOwningCallTracker->reportSessionContextReadyForDeletion( mHandle ); } }
bool DialogTrackerState::InviteRequest( DialogTracker& impl, SipMessage& request, TransactionDirectionality direction, const char* address, int port ) const { if( !impl.isRequestAlreadyHandledByOther( request ) ) { impl.markRequestAsHandledByUs( request ); } if( impl.isARetransmittedRequest( request ) ) { impl.restoreSdpBodyOfRetransmittedRequest( request ); } else { Os::Logger::instance().log(FAC_NAT,PRI_WARNING,"'%s': Received unexpected event InviteRequest while in state '%s'", impl.name(), impl.GetCurrentState()->name() ); } return true; }
bool SessionContext::handleRequest( SipMessage& message, const char* address, int port, bool bFromCallerToCallee ) { // This routine steers incoming requests to the DialogTracker instance that is // responsible for handling them based on the request's to- or from-tags depending // on the directionality of the request. ssize_t numberOfDialogTrackersEnteringRoutine = getNumberOfTrackedDialogs(); bool bTrackRequestResponse = false; UtlString discriminatingTag = getDiscriminatingTagValue( message, bFromCallerToCallee ); // if a discriminating tag was found, try to find a DialogTracker for it. if( !discriminatingTag.isNull() ) { DialogTracker* pDialogTracker = 0; if( ( pDialogTracker = getDialogTrackerForTag( discriminatingTag ) ) != 0 ) { bTrackRequestResponse = pDialogTracker->handleRequest( message, address, port, bFromCallerToCallee ); } else { OsSysLog::add(FAC_NAT, PRI_CRIT, "SessionContext[%s]::handleRequest: received in-dialog request with unknown discriminating tag: %s", mHandle.data(), discriminatingTag.data() ); } } else { // The request does not yet have a discriminating tag. This is likely indicating a // dialog-forming INVITE but to be sure, check that the request is indeed an // INVITE in the caller->callee direction. UtlString method; message.getRequestMethod(&method); if( bFromCallerToCallee && method.compareTo( SIP_INVITE_METHOD ) == 0 ) { // The INVITE is dialog-forming. Check whether or not already have // the reference dialog tracker for it. if( !mpReferenceDialogTracker ) { // This is the first time we see that dialog-forming request - create // a reference dialog tracker that will serve as a template to create // new DialogTracker objects for the dialogs that responses to the // request will establish. Url tempUrl; char tempBuffer[50]; sprintf( tempBuffer, "%s-%s", mHandle.data(), "ref" ); if( ( mpReferenceDialogTracker = new DialogTracker( tempBuffer, mSystemIdentificationString, this ) ) ) { mpReferenceDialogTracker->handleRequest( message, address, port, bFromCallerToCallee ); // save the From tag of the dialog-forming request. This will be used to identify // the discriminating tag when the directionality of a message is unknown. message.getFromUrl( tempUrl ); tempUrl.getFieldParameter( "tag", mDialogOriginalFromTag ); mDialogFormingInviteCseq.setValue( message ); bTrackRequestResponse = true; } } else { // This dialog-forming request has already been seen - this is likely a // retransmission. Present it to the reference dialog tracker so that // it can handle the retransmission properly. bTrackRequestResponse = mpReferenceDialogTracker->handleRequest( message, address, port, bFromCallerToCallee ); } } } // Check if the processing of the request caused the last DialogTracker to be deleted. // If so, the SessionContext is not required anymore therefore tell the CallTracker that // we are ready for deletion if( numberOfDialogTrackersEnteringRoutine && deleteDialogTrackersReadyForDeletion() == numberOfDialogTrackersEnteringRoutine ) { mpOwningCallTracker->reportSessionContextReadyForDeletion( mHandle ); } return bTrackRequestResponse; }
bool WaitingForInvite::InviteRequest( DialogTracker& impl, SipMessage& request, TransactionDirectionality direction, const char* address, int port ) const { bool bTrackRequestResponse = false; impl.setTransactionDirectionality( direction ); // check is another sipX is already taking care of NAT traversing this session. if( !impl.isRequestAlreadyHandledByOther( request ) ) { bTrackRequestResponse = true; impl.markRequestAsHandledByUs( request ); // check if the INVITE contains an SDP offer if( request.hasSdpBody() ) { // request contains an SDP offer. Check if the SDP or the or the actual // location of the two endpoints impose the use of a media relay. if( impl.doesEndpointsLocationImposeMediaRelay() ) { OsSysLog::add(FAC_NAT,PRI_DEBUG,"'%s:%s' - Media relay required", impl.name(), impl.GetCurrentState()->name() ); impl.setMediaRelayRequiredFlag(); } else { OsSysLog::add(FAC_NAT,PRI_DEBUG,"'%s:%s' - Media relay not required", impl.name(), impl.GetCurrentState()->name() ); impl.clearMediaRelayRequiredFlag(); } impl.ProcessMediaOffer( request, INITIAL_OFFER_ANSWER ); ChangeState( impl, impl.pWaitingForMediaAnswer ); } else { // request does contains an SDP offer. This INVITE may be used to set up a 3PCC // call. Play it safe and impose the use of a media relay to guarantee speechpath impl.setMediaRelayRequiredFlag(); ChangeState( impl, impl.pWaitingForMediaOffer ); } } else { // this particular session is already being handled by another sipX i nthe network. // That sipX is taking care of overcoming the NATs that separate the endpoints involved // in that session. There isn't much value we can add here so just bail on tracking that // session. ChangeState( impl, impl.pMoribund ); } return bTrackRequestResponse; }
bool DialogTrackerState::ByeRequest( DialogTracker& impl, SipMessage& request, TransactionDirectionality direction, const char* address, int port ) const { impl.deallocateAndClearAllMediaRelaySessions(); ChangeState( impl, impl.pMoribund ); return false; }
void TimeBoundState::DoEntryAction( DialogTracker& impl ) const { impl.resetTimerTickCounter(); }
void Moribund::DoEntryAction( DialogTracker& impl ) const { impl.reportDialogCompleted(); }
void WaitingForMediaOffer::SuccessfulResponse( DialogTracker& impl, SipMessage& response, const char* address, int port ) const { impl.ProcessMediaOffer( response, INITIAL_OFFER_ANSWER ); ChangeState( impl, impl.pWaitingForAckWithAnswerForInvite ); }
bool WaitingForPrackWithMediaAnswer::PrackRequest( DialogTracker& impl, SipMessage& request, TransactionDirectionality direction, const char* address, int port ) const { impl.ProcessMediaAnswer( request, INITIAL_OFFER_ANSWER ); ChangeState( impl, impl.pWaitingFor200OkForSlowStartPrack ); return true; }