rtphone/src/libs/resiprocate/resip/dum/InviteSession.cxx

3307 lines
97 KiB
C++

#include "resip/stack/MultipartMixedContents.hxx"
#include "resip/stack/MultipartAlternativeContents.hxx"
#include "resip/stack/SdpContents.hxx"
#include "resip/stack/SipMessage.hxx"
#include "resip/stack/Helper.hxx"
#include "resip/dum/BaseCreator.hxx"
#include "resip/dum/Dialog.hxx"
#include "resip/dum/DialogEventStateManager.hxx"
#include "resip/dum/DialogUsageManager.hxx"
#include "resip/dum/InviteSession.hxx"
#include "resip/dum/ServerInviteSession.hxx"
#include "resip/dum/ClientSubscription.hxx"
#include "resip/dum/ServerSubscription.hxx"
#include "resip/dum/ClientInviteSession.hxx"
#include "resip/dum/InviteSessionHandler.hxx"
#include "resip/dum/MasterProfile.hxx"
#include "resip/dum/UsageUseException.hxx"
#include "resip/dum/DumHelper.hxx"
#include "rutil/Inserter.hxx"
#include "rutil/Logger.hxx"
#include "rutil/Timer.hxx"
#include "rutil/Random.hxx"
#include "rutil/compat.hxx"
#include "rutil/WinLeakCheck.hxx"
// Remove warning about 'this' use in initiator list - pointer is only stored
#if defined(WIN32) && !defined(__GNUC__)
#pragma warning( disable : 4355 ) // using this in base member initializer list
#pragma warning( disable : 4800 ) // forcing value to bool (performance warning)
#endif
#define RESIPROCATE_SUBSYSTEM Subsystem::DUM
#define THROW(msg) throw DialogUsage::Exception(msg, __FILE__,__LINE__);
using namespace resip;
using namespace std;
Data EndReasons[] =
{
"Not Specified",
"User Hung Up",
"Application Rejected Sdp(usually no common codec)",
"Illegal Sdp Negotiation",
"ACK not received",
"Session Timer Expired",
"Stale re-Invite"
};
const Data& InviteSession::getEndReasonString(InviteSession::EndReason reason)
{
if(reason != InviteSession::UserSpecified)
{
resip_assert(reason >= InviteSession::NotSpecified && reason < InviteSession::ENDREASON_MAX); //!dcm! -- necessary?
return EndReasons[reason];
}
else
{
return mUserEndReason;
}
}
InviteSession::InviteSession(DialogUsageManager& dum, Dialog& dialog)
: DialogUsage(dum, dialog),
mState(Undefined),
mNitState(NitComplete),
mServerNitState(NitComplete),
mLastLocalSessionModification(new SipMessage),
mLastRemoteSessionModification(new SipMessage),
mInvite200(new SipMessage),
mLastNitResponse(new SipMessage),
mCurrentRetransmit200(0),
mStaleReInviteTimerSeq(1),
mSessionInterval(0),
mMinSE(90),
mSessionRefresher(false),
mSessionTimerSeq(0),
mSessionRefreshReInvite(false),
mReferSub(true),
mCurrentEncryptionLevel(DialogUsageManager::None),
mProposedEncryptionLevel(DialogUsageManager::None),
mEndReason(NotSpecified)
{
DebugLog ( << "^^^ InviteSession::InviteSession " << this);
resip_assert(mDum.mInviteSessionHandler);
}
InviteSession::~InviteSession()
{
DebugLog ( << "^^^ InviteSession::~InviteSession " << this);
mDialog.mInviteSession = 0;
while(!mNITQueue.empty())
{
delete mNITQueue.front();
mNITQueue.pop();
}
}
bool
InviteSession::hasLocalOfferAnswer() const
{
return (mCurrentLocalOfferAnswer.get());
}
const Contents&
InviteSession::getLocalOfferAnswer() const
{
if(mCurrentLocalOfferAnswer.get())
{
return *mCurrentLocalOfferAnswer;
}
else
{
return SdpContents::Empty;
}
}
bool
InviteSession::hasRemoteOfferAnswer() const
{
return (mCurrentRemoteOfferAnswer.get());
}
const Contents&
InviteSession::getRemoteOfferAnswer() const
{
if(mCurrentRemoteOfferAnswer.get())
{
return *mCurrentRemoteOfferAnswer;
}
else
{
return SdpContents::Empty;
}
}
bool
InviteSession::hasProposedRemoteOfferAnswer() const
{
return (mProposedRemoteOfferAnswer.get());
}
const Contents&
InviteSession::getProposedRemoteOfferAnswer() const
{
if(mProposedRemoteOfferAnswer.get())
{
return *mProposedRemoteOfferAnswer;
}
else
{
return SdpContents::Empty;
}
}
bool
InviteSession::hasLocalSdp() const
{
resip_assert(!mDum.mInviteSessionHandler->isGenericOfferAnswer());
return (mCurrentLocalOfferAnswer.get());
}
const SdpContents&
InviteSession::getLocalSdp() const
{
resip_assert(!mDum.mInviteSessionHandler->isGenericOfferAnswer());
if(mCurrentLocalOfferAnswer.get())
{
const SdpContents* sdp = dynamic_cast<const SdpContents*>(mCurrentLocalOfferAnswer.get());
resip_assert(sdp);
return *sdp;
}
else
{
return SdpContents::Empty;
}
}
bool
InviteSession::hasRemoteSdp() const
{
resip_assert(!mDum.mInviteSessionHandler->isGenericOfferAnswer());
return (mCurrentRemoteOfferAnswer.get());
}
const SdpContents&
InviteSession::getRemoteSdp() const
{
resip_assert(!mDum.mInviteSessionHandler->isGenericOfferAnswer());
if(mCurrentRemoteOfferAnswer.get())
{
const SdpContents* sdp = dynamic_cast<const SdpContents*>(mCurrentRemoteOfferAnswer.get());
resip_assert(sdp);
return *sdp;
}
else
{
return SdpContents::Empty;
}
}
bool
InviteSession::hasProposedRemoteSdp() const
{
resip_assert(!mDum.mInviteSessionHandler->isGenericOfferAnswer());
return (mProposedRemoteOfferAnswer.get());
}
const SdpContents&
InviteSession::getProposedRemoteSdp() const
{
resip_assert(!mDum.mInviteSessionHandler->isGenericOfferAnswer());
if(mProposedRemoteOfferAnswer.get())
{
const SdpContents* sdp = dynamic_cast<const SdpContents*>(mProposedRemoteOfferAnswer.get());
resip_assert(sdp);
return *sdp;
}
else
{
return SdpContents::Empty;
}
}
InviteSessionHandle
InviteSession::getSessionHandle()
{
return InviteSessionHandle(mDum, getBaseHandle().getId());
}
void InviteSession::storePeerCapabilities(const SipMessage& msg)
{
if (msg.exists(h_Allows))
{
mPeerSupportedMethods = msg.header(h_Allows);
}
if (msg.exists(h_Supporteds))
{
mPeerSupportedOptionTags = msg.header(h_Supporteds);
}
if (msg.exists(h_AcceptEncodings))
{
mPeerSupportedEncodings = msg.header(h_AcceptEncodings);
}
if (msg.exists(h_AcceptLanguages))
{
mPeerSupportedLanguages = msg.header(h_AcceptLanguages);
}
if (msg.exists(h_AllowEvents))
{
mPeerAllowedEvents = msg.header(h_AllowEvents);
}
if (msg.exists(h_Accepts))
{
mPeerSupportedMimeTypes = msg.header(h_Accepts);
}
if (msg.exists(h_UserAgent))
{
mPeerUserAgent = msg.header(h_UserAgent).value();
}
}
bool
InviteSession::updateMethodSupported() const
{
// Check if Update is supported locally
if(mDum.getMasterProfile()->isMethodSupported(UPDATE))
{
// Check if peer supports UPDATE
return mPeerSupportedMethods.find(Token("UPDATE"));
}
return false;
}
bool
InviteSession::isConnected() const
{
switch (mState)
{
case Connected:
case SentUpdate:
case SentUpdateGlare:
case SentReinvite:
case SentReinviteGlare:
case SentReinviteNoOffer:
case SentReinviteAnswered:
case SentReinviteNoOfferGlare:
case ReceivedUpdate:
case ReceivedReinvite:
case ReceivedReinviteNoOffer:
case ReceivedReinviteSentOffer:
case Answered:
case WaitingToOffer:
case WaitingToRequestOffer:
return true;
default:
return false;
}
}
bool
InviteSession::isEarly() const
{
switch (mState)
{
case UAC_Early:
case UAC_EarlyWithOffer:
case UAC_EarlyWithAnswer:
case UAC_SentUpdateEarly:
case UAC_SentUpdateEarlyGlare:
case UAC_ReceivedUpdateEarly:
case UAC_SentAnswer:
case UAC_QueuedUpdate:
return true;
default:
return false;
}
}
bool
InviteSession::isAccepted() const
{
switch (mState)
{
case UAS_Start:
case UAS_Offer:
case UAS_OfferProvidedAnswer:
case UAS_EarlyOffer:
case UAS_EarlyProvidedAnswer:
case UAS_NoOffer:
case UAS_ProvidedOffer:
case UAS_EarlyNoOffer:
case UAS_EarlyProvidedOffer:
//case UAS_Accepted: // Obvious
//case UAS_WaitingToOffer: // We have accepted here and are waiting for ACK to Offer
//case UAS_WaitingToRequestOffer: // We have accepted here and are waiting for ACK to request an offer
//case UAS_AcceptedWaitingAnswer: // Obvious
case UAS_OfferReliable:
case UAS_OfferReliableProvidedAnswer:
case UAS_NoOfferReliable:
case UAS_ProvidedOfferReliable:
case UAS_FirstSentOfferReliable:
case UAS_FirstSentAnswerReliable:
case UAS_NoAnswerReliableWaitingPrack:
case UAS_NegotiatedReliable:
case UAS_NoAnswerReliable:
case UAS_SentUpdate:
//case UAS_SentUpdateAccepted: // we have accepted here
case UAS_SentUpdateGlare:
case UAS_ReceivedUpdate:
//case UAS_ReceivedUpdateWaitingAnswer: // happens only after accept is called
//case UAS_WaitingToHangup: // always from an accepted state
return false;
default:
return true;
}
}
bool
InviteSession::isTerminated() const
{
switch (mState)
{
case Terminated:
case WaitingToTerminate:
case WaitingToHangup:
case UAC_Cancelled:
case UAS_WaitingToHangup:
return true;
default:
return false;
}
}
EncodeStream&
InviteSession::dump(EncodeStream& strm) const
{
strm << "INVITE: " << mId
<< " " << toData(mState)
<< " ADDR=" << myAddr()
<< " PEER=" << peerAddr();
return strm;
}
void
InviteSession::requestOffer()
{
switch (mState)
{
case Connected:
case WaitingToRequestOffer:
case UAS_WaitingToRequestOffer:
transition(SentReinviteNoOffer);
mDialog.makeRequest(*mLastLocalSessionModification, INVITE);
startStaleReInviteTimer();
mLastLocalSessionModification->setContents(0); // Clear the contents from the INVITE
setSessionTimerHeaders(*mLastLocalSessionModification);
InfoLog (<< "Sending " << mLastLocalSessionModification->brief());
// call send to give app an chance to adorn the message.
send(mLastLocalSessionModification);
break;
case Answered:
// queue the offer to be sent after the ACK is received
transition(WaitingToRequestOffer);
break;
// ?slg? Can we handle all of the states listed in isConnected() ???
default:
WarningLog (<< "Can't requestOffer when not in Connected state");
throw DialogUsage::Exception("Can't request an offer", __FILE__,__LINE__);
}
}
void
InviteSession::provideOffer(const Contents& offer,
DialogUsageManager::EncryptionLevel level,
const Contents* alternative)
{
switch (mState)
{
case Connected:
case WaitingToOffer:
case UAS_WaitingToOffer:
transition(SentReinvite);
mDialog.makeRequest(*mLastLocalSessionModification, INVITE);
startStaleReInviteTimer();
setSessionTimerHeaders(*mLastLocalSessionModification);
InfoLog (<< "Sending " << mLastLocalSessionModification->brief());
InviteSession::setOfferAnswer(*mLastLocalSessionModification, offer, alternative);
mProposedLocalOfferAnswer = InviteSession::makeOfferAnswer(offer, alternative);
mProposedEncryptionLevel = level;
DumHelper::setOutgoingEncryptionLevel(*mLastLocalSessionModification, mProposedEncryptionLevel);
// call send to give app an chance to adorn the message.
send(mLastLocalSessionModification);
break;
case Answered:
// queue the offer to be sent after the ACK is received
transition(WaitingToOffer);
mProposedEncryptionLevel = level;
mProposedLocalOfferAnswer = InviteSession::makeOfferAnswer(offer, alternative);
break;
case ReceivedReinviteNoOffer:
resip_assert(!mProposedRemoteOfferAnswer.get());
transition(ReceivedReinviteSentOffer);
mDialog.makeResponse(*mInvite200, *mLastRemoteSessionModification, 200);
handleSessionTimerRequest(*mInvite200, *mLastRemoteSessionModification);
InviteSession::setOfferAnswer(*mInvite200, offer, 0);
mProposedLocalOfferAnswer = InviteSession::makeOfferAnswer(offer);
InfoLog (<< "Sending " << mInvite200->brief());
DumHelper::setOutgoingEncryptionLevel(*mInvite200, mCurrentEncryptionLevel);
send(mInvite200);
startRetransmit200Timer();
break;
default:
WarningLog (<< "Incorrect state to provideOffer: " << toData(mState));
throw DialogUsage::Exception("Can't provide an offer", __FILE__,__LINE__);
}
}
class InviteSessionProvideOfferExCommand : public DumCommandAdapter
{
public:
InviteSessionProvideOfferExCommand(const InviteSessionHandle& inviteSessionHandle,
const Contents& offer,
DialogUsageManager::EncryptionLevel level,
const Contents* alternative)
: mInviteSessionHandle(inviteSessionHandle),
mOffer(offer.clone()),
mLevel(level),
mAlternative(alternative ? alternative->clone() : 0)
{
}
virtual void executeCommand()
{
if(mInviteSessionHandle.isValid())
{
mInviteSessionHandle->provideOffer(*mOffer, mLevel, mAlternative.get());
}
}
virtual EncodeStream& encodeBrief(EncodeStream& strm) const
{
return strm << "InviteSessionProvideOfferExCommand";
}
private:
InviteSessionHandle mInviteSessionHandle;
std::auto_ptr<const Contents> mOffer;
DialogUsageManager::EncryptionLevel mLevel;
std::auto_ptr<const Contents> mAlternative;
};
void
InviteSession::provideOfferCommand(const Contents& offer, DialogUsageManager::EncryptionLevel level, const Contents* alternative)
{
mDum.post(new InviteSessionProvideOfferExCommand(getSessionHandle(), offer, level, alternative));
}
void
InviteSession::provideOffer(const Contents& offer)
{
return provideOffer(offer, mCurrentEncryptionLevel, 0);
}
class InviteSessionProvideOfferCommand : public DumCommandAdapter
{
public:
InviteSessionProvideOfferCommand(const InviteSessionHandle& inviteSessionHandle, const Contents& offer)
: mInviteSessionHandle(inviteSessionHandle),
mOffer(offer.clone())
{
}
virtual void executeCommand()
{
if(mInviteSessionHandle.isValid())
{
mInviteSessionHandle->provideOffer(*mOffer);
}
}
virtual EncodeStream& encodeBrief(EncodeStream& strm) const
{
return strm << "InviteSessionProvideOfferCommand";
}
private:
InviteSessionHandle mInviteSessionHandle;
std::auto_ptr<const Contents> mOffer;
};
void
InviteSession::provideOfferCommand(const Contents& offer)
{
mDum.post(new InviteSessionProvideOfferCommand(getSessionHandle(), offer));
}
void
InviteSession::provideAnswer(const Contents& answer)
{
switch (mState)
{
case ReceivedReinvite:
transition(Connected);
mDialog.makeResponse(*mInvite200, *mLastRemoteSessionModification, 200);
handleSessionTimerRequest(*mInvite200, *mLastRemoteSessionModification);
InviteSession::setOfferAnswer(*mInvite200, answer, 0);
mCurrentLocalOfferAnswer = InviteSession::makeOfferAnswer(answer);
mCurrentRemoteOfferAnswer = mProposedRemoteOfferAnswer;
InfoLog (<< "Sending " << mInvite200->brief());
DumHelper::setOutgoingEncryptionLevel(*mInvite200, mCurrentEncryptionLevel);
send(mInvite200);
startRetransmit200Timer();
if (mDum.mDialogEventStateManager)
{
// New Offer/Answer - generate a new confirmed callback with updated SDP
mDum.mDialogEventStateManager->onConfirmed(mDialog, getSessionHandle());
}
break;
case ReceivedUpdate: // same as ReceivedReinvite case.
{
transition(Connected);
SharedPtr<SipMessage> response(new SipMessage);
mDialog.makeResponse(*response, *mLastRemoteSessionModification, 200);
handleSessionTimerRequest(*response, *mLastRemoteSessionModification);
InviteSession::setOfferAnswer(*response, answer, 0);
mCurrentLocalOfferAnswer = InviteSession::makeOfferAnswer(answer);
mCurrentRemoteOfferAnswer = mProposedRemoteOfferAnswer;
InfoLog (<< "Sending " << response->brief());
DumHelper::setOutgoingEncryptionLevel(*response, mCurrentEncryptionLevel);
send(response);
if (mDum.mDialogEventStateManager)
{
// New Offer/Answer - generate a new confirmed callback with updated SDP
mDum.mDialogEventStateManager->onConfirmed(mDialog, getSessionHandle());
}
break;
}
case SentReinviteAnswered:
transition(Connected);
sendAck(&answer);
mCurrentRemoteOfferAnswer = mProposedRemoteOfferAnswer;
mCurrentLocalOfferAnswer = InviteSession::makeOfferAnswer(answer);
if (mDum.mDialogEventStateManager)
{
// New Offer/Answer - generate a new confirmed callback with updated SDP
mDum.mDialogEventStateManager->onConfirmed(mDialog, getSessionHandle());
}
break;
default:
WarningLog (<< "Incorrect state to provideAnswer: " << toData(mState));
throw DialogUsage::Exception("Can't provide an answer", __FILE__,__LINE__);
}
}
class InviteSessionProvideAnswerCommand : public DumCommandAdapter
{
public:
InviteSessionProvideAnswerCommand(const InviteSessionHandle& inviteSessionHandle, const Contents& answer)
: mInviteSessionHandle(inviteSessionHandle),
mAnswer(answer.clone())
{
}
virtual void executeCommand()
{
if(mInviteSessionHandle.isValid())
{
mInviteSessionHandle->provideAnswer(*mAnswer);
}
}
virtual EncodeStream& encodeBrief(EncodeStream& strm) const
{
return strm << "InviteSessionProvideAnswerCommand";
}
private:
InviteSessionHandle mInviteSessionHandle;
std::auto_ptr<const Contents> mAnswer;
};
void
InviteSession::provideAnswerCommand(const Contents& answer)
{
mDum.post(new InviteSessionProvideAnswerCommand(getSessionHandle(), answer));
}
void
InviteSession::end()
{
end(NotSpecified);
}
void
InviteSession::end(const Data& userReason)
{
mUserEndReason = userReason;
end(UserSpecified);
}
void
InviteSession::end(EndReason reason)
{
if (mEndReason == NotSpecified)
{
mEndReason = reason;
}
InviteSessionHandler* handler = mDum.mInviteSessionHandler;
switch (mState)
{
case Connected:
case SentUpdate:
case SentUpdateGlare:
case SentReinviteGlare:
case SentReinviteNoOfferGlare:
case SentReinviteAnswered:
{
// !jf! do we need to store the BYE somewhere?
// .dw. BYE message handled
SharedPtr<SipMessage> msg = sendBye();
transition(Terminated);
handler->onTerminated(getSessionHandle(), InviteSessionHandler::LocalBye, msg.get());
break;
}
case SentReinvite:
case SentReinviteNoOffer:
transition(WaitingToTerminate);
break;
case Answered:
case WaitingToOffer:
case WaitingToRequestOffer:
case ReceivedReinviteSentOffer:
if(mCurrentRetransmit200) // If retransmit200 timer is active then ACK is not received yet - wait for it
{
transition(WaitingToHangup);
}
else
{
// ACK has likely timedout - hangup immediately
SharedPtr<SipMessage> msg = sendBye();
transition(Terminated);
mDum.mInviteSessionHandler->onTerminated(getSessionHandle(), InviteSessionHandler::LocalBye, msg.get());
}
break;
case ReceivedUpdate:
case ReceivedReinvite:
case ReceivedReinviteNoOffer:
{
SharedPtr<SipMessage> response(new SipMessage);
mDialog.makeResponse(*response, *mLastRemoteSessionModification, 488);
InfoLog (<< "Sending " << response->brief());
send(response);
SharedPtr<SipMessage> msg = sendBye();
transition(Terminated);
handler->onTerminated(getSessionHandle(), InviteSessionHandler::LocalBye, msg.get());
break;
}
case WaitingToTerminate: // ?slg? Why is this here?
{
SharedPtr<SipMessage> msg = sendBye();
transition(Terminated);
handler->onTerminated(getSessionHandle(), InviteSessionHandler::LocalBye, msg.get());
break;
}
case Terminated:
// no-op.
break;
default:
resip_assert(0);
break;
}
}
class InviteSessionEndCommand : public DumCommandAdapter
{
public:
InviteSessionEndCommand(const InviteSessionHandle& inviteSessionHandle, InviteSession::EndReason reason)
: mInviteSessionHandle(inviteSessionHandle),
mReason(reason)
{
}
virtual void executeCommand()
{
if(mInviteSessionHandle.isValid())
{
mInviteSessionHandle->end(mReason);
}
}
virtual EncodeStream& encodeBrief(EncodeStream& strm) const
{
return strm << "InviteSessionEndCommand";
}
private:
InviteSessionHandle mInviteSessionHandle;
InviteSession::EndReason mReason;
};
void
InviteSession::endCommand(EndReason reason)
{
mDum.post(new InviteSessionEndCommand(getSessionHandle(), reason));
}
void
InviteSession::reject(int statusCode, WarningCategory *warning)
{
switch (mState)
{
case ReceivedUpdate:
case ReceivedReinvite:
case ReceivedReinviteNoOffer:
{
mProposedRemoteOfferAnswer.reset(); // Clear out any potential ProposedRemoteOfferAnswer since we are rejecting
transition(Connected);
SharedPtr<SipMessage> response(new SipMessage);
mDialog.makeResponse(*response, *mLastRemoteSessionModification, statusCode);
if(warning)
{
response->header(h_Warnings).push_back(*warning);
}
InfoLog (<< "Sending " << response->brief());
send(response);
break;
}
// Sent a reINVITE no offer and received a 200-offer.
// Simply send an ACK without an answer and stay in Connected.
case SentReinviteAnswered:
{
InfoLog (<< "Not sending " << statusCode << " error since transaction"
"already completed, sending answer-less ACK");
transition(Connected);
sendAck();
break;
}
default:
resip_assert(0);
break;
}
}
class InviteSessionRejectCommand : public DumCommandAdapter
{
public:
InviteSessionRejectCommand(const InviteSessionHandle& inviteSessionHandle, int code, WarningCategory* warning)
: mInviteSessionHandle(inviteSessionHandle),
mCode(code),
mWarning(warning?new WarningCategory(*warning):0)
{
}
virtual void executeCommand()
{
if(mInviteSessionHandle.isValid())
{
mInviteSessionHandle->reject(mCode, mWarning.get());
}
}
virtual EncodeStream& encodeBrief(EncodeStream& strm) const
{
return strm << "InviteSessionRejectCommand";
}
private:
InviteSessionHandle mInviteSessionHandle;
int mCode;
std::auto_ptr<WarningCategory> mWarning;
};
void
InviteSession::rejectCommand(int code, WarningCategory *warning)
{
mDum.post(new InviteSessionRejectCommand(getSessionHandle(), code, warning));
}
void
InviteSession::targetRefresh(const NameAddr& localUri)
{
if (isConnected()) // ?slg? likely not safe in any state except Connected - what should behaviour be if state is ReceivedReinvite?
{
mDialog.mLocalContact = localUri;
sessionRefresh();
}
else
{
WarningLog (<< "Can't targetRefresh before Connected");
throw UsageUseException("targetRefresh not allowed in this context", __FILE__, __LINE__);
}
}
void
InviteSession::refer(const NameAddr& referTo, bool referSub)
{
refer(referTo,myAddr(),std::auto_ptr<resip::Contents>(0),referSub);
}
void
InviteSession::refer(const NameAddr& referTo, const NameAddr& referredBy, bool referSub)
{
refer(referTo,referredBy,std::auto_ptr<resip::Contents>(0),referSub);
}
void
InviteSession::refer(const NameAddr& referTo, std::auto_ptr<resip::Contents> contents,bool referSub)
{
refer(referTo,myAddr(),contents,referSub);
}
void
InviteSession::refer(const NameAddr& referTo, const NameAddr& referredBy, std::auto_ptr<resip::Contents> contents, bool referSub)
{
if (isConnected()) // ?slg? likely not safe in any state except Connected - what should behaviour be if state is ReceivedReinvite?
{
SharedPtr<SipMessage> refer(new SipMessage());
mDialog.makeRequest(*refer, REFER, mNitState == NitComplete); // only increment CSeq if not going to queue NIT
refer->header(h_ReferTo) = referTo;
refer->header(h_ReferredBy) = referredBy;
refer->header(h_ReferredBy).remove(p_tag); // tag-param not permitted in rfc3892; not the same as generic-param
refer->setContents(contents);
if (!referSub)
{
refer->header(h_ReferSub).value() = "false";
refer->header(h_Supporteds).push_back(Token(Symbols::NoReferSub));
}
if(mNitState == NitComplete)
{
mNitState = NitProceeding;
mReferSub = referSub;
mLastSentNITRequest = refer;
send(refer);
return;
}
mNITQueue.push(new QueuedNIT(refer,referSub));
InfoLog(<< "refer - queuing NIT:" << refer->brief());
return;
}
else
{
WarningLog (<< "Can't refer before Connected");
throw UsageUseException("REFER not allowed in this context", __FILE__, __LINE__);
}
}
const SharedPtr<SipMessage>
InviteSession::getLastSentNITRequest() const
{
return mLastSentNITRequest;
}
void
InviteSession::nitComplete()
{
mNitState = NitComplete;
if (mNITQueue.size())
{
QueuedNIT *qn=mNITQueue.front();
mNITQueue.pop();
mNitState = NitProceeding;
mReferSub = qn->referSubscription();
mLastSentNITRequest = qn->getNIT();
mDialog.setRequestNextCSeq(*mLastSentNITRequest.get());
InfoLog(<< "checkNITQueue - sending queued NIT:" << mLastSentNITRequest->brief());
send(mLastSentNITRequest);
delete qn;
}
}
class InviteSessionReferCommand : public DumCommandAdapter
{
public:
InviteSessionReferCommand(const InviteSessionHandle& inviteSessionHandle, const NameAddr& referTo, bool referSub)
: mInviteSessionHandle(inviteSessionHandle),
mReferTo(referTo),
mReferSub(referSub)
{
}
virtual void executeCommand()
{
if(mInviteSessionHandle.isValid())
{
mInviteSessionHandle->refer(mReferTo, mReferSub);
}
}
virtual EncodeStream& encodeBrief(EncodeStream& strm) const
{
return strm << "InviteSessionReferCommand";
}
private:
InviteSessionHandle mInviteSessionHandle;
NameAddr mReferTo;
bool mReferSub;
};
void
InviteSession::referCommand(const NameAddr& referTo, bool referSub)
{
mDum.post(new InviteSessionReferCommand(getSessionHandle(), referTo, referSub));
}
void
InviteSession::refer(const NameAddr& referTo, InviteSessionHandle sessionToReplace, bool referSub)
{
refer(referTo,sessionToReplace,std::auto_ptr<resip::Contents>(0),referSub);
}
void
InviteSession::refer(const NameAddr& referTo, InviteSessionHandle sessionToReplace, std::auto_ptr<resip::Contents> contents, bool referSub)
{
if (!sessionToReplace.isValid())
{
throw UsageUseException("Attempted to make a refer w/ and invalid replacement target", __FILE__, __LINE__);
}
CallId replaces;
DialogId id = sessionToReplace->mDialog.getId();
replaces.value() = id.getCallId();
replaces.param(p_toTag) = id.getRemoteTag();
replaces.param(p_fromTag) = id.getLocalTag();
refer(referTo, replaces, contents, referSub);
}
void
InviteSession::refer(const NameAddr& referTo, const CallId& replaces, bool referSub)
{
refer(referTo,replaces,std::auto_ptr<resip::Contents>(0),referSub);
}
void
InviteSession::refer(const NameAddr& referTo, const CallId& replaces, std::auto_ptr<resip::Contents> contents, bool referSub)
{
if (isConnected()) // ?slg? likely not safe in any state except Connected - what should behaviour be if state is ReceivedReinvite?
{
SharedPtr<SipMessage> refer(new SipMessage());
mDialog.makeRequest(*refer, REFER, mNitState == NitComplete); // only increment CSeq if not going to queue NIT
refer->setContents(contents);
refer->header(h_ReferTo) = referTo;
refer->header(h_ReferredBy) = myAddr();
refer->header(h_ReferredBy).remove(p_tag);
refer->header(h_ReferTo).uri().embedded().header(h_Replaces) = replaces;
if (!referSub)
{
refer->header(h_ReferSub).value() = "false";
refer->header(h_Supporteds).push_back(Token(Symbols::NoReferSub));
}
if(mNitState == NitComplete)
{
mNitState = NitProceeding;
mReferSub = referSub;
mLastSentNITRequest = refer;
send(refer);
return;
}
mNITQueue.push(new QueuedNIT(refer,referSub));
InfoLog(<< "refer/replace - queuing NIT:" << refer->brief());
return;
}
else
{
WarningLog (<< "Can't refer before Connected");
resip_assert(0);
throw UsageUseException("REFER not allowed in this context", __FILE__, __LINE__);
}
}
class InviteSessionReferExCommand : public DumCommandAdapter
{
public:
InviteSessionReferExCommand(const InviteSessionHandle& inviteSessionHandle, const NameAddr& referTo, InviteSessionHandle sessionToReplace, bool referSub)
: mInviteSessionHandle(inviteSessionHandle),
mSessionToReplace(sessionToReplace),
mReferTo(referTo),
mReferSub(referSub)
{
}
virtual void executeCommand()
{
if(mInviteSessionHandle.isValid())
{
mInviteSessionHandle->refer(mReferTo, mSessionToReplace, mReferSub);
}
}
virtual EncodeStream& encodeBrief(EncodeStream& strm) const
{
return strm << "InviteSessionReferExCommand";
}
private:
InviteSessionHandle mInviteSessionHandle;
InviteSessionHandle mSessionToReplace;
NameAddr mReferTo;
bool mReferSub;
};
void
InviteSession::referCommand(const NameAddr& referTo, InviteSessionHandle sessionToReplace, bool referSub)
{
mDum.post(new InviteSessionReferExCommand(getSessionHandle(), referTo, sessionToReplace, referSub));
}
void
InviteSession::info(const Contents& contents)
{
SharedPtr<SipMessage> info(new SipMessage());
mDialog.makeRequest(*info, INFO, mNitState == NitComplete); // only increment CSeq if not going to queue NIT
// !jf! handle multipart here
info->setContents(&contents);
DumHelper::setOutgoingEncryptionLevel(*info, mCurrentEncryptionLevel);
if (mNitState == NitComplete)
{
mNitState = NitProceeding;
mLastSentNITRequest = info;
send(info);
return;
}
mNITQueue.push(new QueuedNIT(info));
InfoLog(<< "info - queuing NIT:" << info->brief());
return;
}
class InviteSessionInfoCommand : public DumCommandAdapter
{
public:
InviteSessionInfoCommand(const InviteSessionHandle& inviteSessionHandle, const Contents& contents)
: mInviteSessionHandle(inviteSessionHandle),
mContents(contents.clone())
{
}
virtual void executeCommand()
{
if(mInviteSessionHandle.isValid())
{
mInviteSessionHandle->info(*mContents);
}
}
virtual EncodeStream& encodeBrief(EncodeStream& strm) const
{
return strm << "InviteSessionInfoCommand";
}
private:
InviteSessionHandle mInviteSessionHandle;
std::auto_ptr<Contents> mContents;
};
void
InviteSession::infoCommand(const Contents& contents)
{
mDum.post(new InviteSessionInfoCommand(getSessionHandle(), contents));
}
void
InviteSession::message(const Contents& contents)
{
SharedPtr<SipMessage> message(new SipMessage());
mDialog.makeRequest(*message, MESSAGE, mNitState == NitComplete); // only increment CSeq if not going to queue NIT
// !jf! handle multipart here
message->setContents(&contents);
DumHelper::setOutgoingEncryptionLevel(*message, mCurrentEncryptionLevel);
InfoLog (<< "Trying to send MESSAGE: " << message);
if (mNitState == NitComplete)
{
mNitState = NitProceeding;
mLastSentNITRequest = message;
send(message);
return;
}
mNITQueue.push(new QueuedNIT(message));
InfoLog(<< "message - queuing NIT:" << message->brief());
return;
}
class InviteSessionMessageCommand : public DumCommandAdapter
{
public:
InviteSessionMessageCommand(const InviteSessionHandle& inviteSessionHandle, const Contents& contents)
: mInviteSessionHandle(inviteSessionHandle),
mContents(contents.clone())
{
}
virtual void executeCommand()
{
if(mInviteSessionHandle.isValid())
{
mInviteSessionHandle->message(*mContents);
}
}
virtual EncodeStream& encodeBrief(EncodeStream& strm) const
{
return strm << "InviteSessionMessageCommand";
}
private:
InviteSessionHandle mInviteSessionHandle;
std::auto_ptr<Contents> mContents;
};
void
InviteSession::messageCommand(const Contents& contents)
{
mDum.post(new InviteSessionMessageCommand(getSessionHandle(), contents));
}
void
InviteSession::dispatch(const SipMessage& msg)
{
// Look for 2xx retransmissions - resend ACK and filter out of state machine
if(msg.header(h_CSeq).method() == INVITE && msg.isResponse() && msg.header(h_StatusLine).statusCode() / 100 == 2)
{
AckMap::iterator i = mAcks.find(msg.getTransactionId());
if (i != mAcks.end())
{
send(i->second); // resend ACK
return;
}
}
// !jf! do we need to handle 3xx here or is it handled elsewhere?
switch (mState)
{
case Connected:
dispatchConnected(msg);
break;
case SentUpdate:
dispatchSentUpdate(msg);
break;
case SentReinvite:
dispatchSentReinvite(msg);
break;
case SentReinviteNoOffer:
dispatchSentReinviteNoOffer(msg);
break;
case SentReinviteAnswered:
dispatchSentReinviteAnswered(msg);
break;
case SentUpdateGlare:
case SentReinviteGlare:
// The behavior is the same except for timer which is handled in dispatch(Timer)
dispatchGlare(msg);
break;
case SentReinviteNoOfferGlare:
dispatchReinviteNoOfferGlare(msg);
break;
case ReceivedUpdate:
case ReceivedReinvite:
case ReceivedReinviteNoOffer:
dispatchReceivedUpdateOrReinvite(msg);
break;
case ReceivedReinviteSentOffer:
dispatchReceivedReinviteSentOffer(msg);
break;
case Answered:
dispatchAnswered(msg);
break;
case WaitingToOffer:
dispatchWaitingToOffer(msg);
break;
case WaitingToRequestOffer:
dispatchWaitingToRequestOffer(msg);
break;
case WaitingToTerminate:
dispatchWaitingToTerminate(msg);
break;
case WaitingToHangup:
dispatchWaitingToHangup(msg);
break;
case Terminated:
dispatchTerminated(msg);
break;
case Undefined:
default:
resip_assert(0);
break;
}
}
void
InviteSession::dispatch(const DumTimeout& timeout)
{
if (timeout.type() == DumTimeout::Retransmit200)
{
if (mCurrentRetransmit200)
{
InfoLog (<< "Retransmitting: " << endl << mInvite200->brief());
//DumHelper::setOutgoingEncryptionLevel(*mInvite200, mCurrentEncryptionLevel);
send(mInvite200);
mCurrentRetransmit200 *= 2;
mDum.addTimerMs(DumTimeout::Retransmit200, resipMin(Timer::T2, mCurrentRetransmit200), getBaseHandle(), timeout.seq());
}
}
else if (timeout.type() == DumTimeout::WaitForAck)
{
if(mCurrentRetransmit200) // If retransmit200 timer is active then ACK is not received yet
{
if (timeout.seq() == mLastRemoteSessionModification->header(h_CSeq).sequence())
{
mCurrentRetransmit200 = 0; // stop the 200 retransmit timer
// If we are waiting for an Ack and it times out, then end with a BYE
if(mState == UAS_WaitingToHangup ||
mState == WaitingToHangup)
{
SharedPtr<SipMessage> msg = sendBye();
transition(Terminated);
mDum.mInviteSessionHandler->onTerminated(getSessionHandle(), InviteSessionHandler::LocalBye, msg.get());
}
else if(mState == ReceivedReinviteSentOffer)
{
transition(Connected);
mProposedLocalOfferAnswer.reset();
mProposedEncryptionLevel = DialogUsageManager::None;
//!dcm! -- should this be onIllegalNegotiation?
mDum.mInviteSessionHandler->onOfferRejected(getSessionHandle(), 0);
}
else if(mState == WaitingToOffer ||
mState == UAS_WaitingToOffer)
{
resip_assert(mProposedLocalOfferAnswer.get());
mDum.mInviteSessionHandler->onAckNotReceived(getSessionHandle());
if(!isTerminated())
{
provideProposedOffer();
}
}
else if(mState == WaitingToRequestOffer ||
mState == UAS_WaitingToRequestOffer)
{
mDum.mInviteSessionHandler->onAckNotReceived(getSessionHandle());
if(!isTerminated())
{
requestOffer();
}
}
else
{
// this is so the app can decided to ignore this. default implementation
// will call end next
mDum.mInviteSessionHandler->onAckNotReceived(getSessionHandle());
}
}
}
}
else if (timeout.type() == DumTimeout::CanDiscardAck)
{
AckMap::iterator i = mAcks.find(timeout.transactionId());
if (i != mAcks.end())
{
mAcks.erase(i);
}
}
else if (timeout.type() == DumTimeout::Glare)
{
if (mState == SentUpdateGlare)
{
transition(SentUpdate);
InfoLog (<< "Retransmitting the UPDATE (glare condition timer)");
mDialog.makeRequest(*mLastLocalSessionModification, UPDATE); // increments CSeq
send(mLastLocalSessionModification);
}
else if (mState == SentReinviteGlare)
{
transition(SentReinvite);
InfoLog (<< "Retransmitting the reINVITE (glare condition timer)");
mDialog.makeRequest(*mLastLocalSessionModification, INVITE); // increments CSeq
startStaleReInviteTimer();
send(mLastLocalSessionModification);
}
else if (mState == SentReinviteNoOfferGlare)
{
transition(SentReinviteNoOffer);
InfoLog (<< "Retransmitting the reINVITE-nooffer (glare condition timer)");
mDialog.makeRequest(*mLastLocalSessionModification, INVITE); // increments CSeq
startStaleReInviteTimer();
send(mLastLocalSessionModification);
}
}
else if (timeout.type() == DumTimeout::StaleReInvite)
{
if(timeout.seq() == mStaleReInviteTimerSeq)
{
if(mState == WaitingToTerminate)
{
SharedPtr<SipMessage> msg = sendBye();
transition(Terminated);
mDum.mInviteSessionHandler->onTerminated(getSessionHandle(), InviteSessionHandler::LocalBye, msg.get());
}
else if(mState == SentReinvite ||
mState == SentReinviteNoOffer)
{
transition(Connected);
mProposedLocalOfferAnswer.reset();
mProposedEncryptionLevel = DialogUsageManager::None;
// this is so the app can decide to ignore this. default implementation
// will call end next - which will send a BYE
mDum.mInviteSessionHandler->onStaleReInviteTimeout(getSessionHandle());
}
}
}
else if (timeout.type() == DumTimeout::SessionExpiration)
{
if(timeout.seq() == mSessionTimerSeq)
{
// this is so the app can decide to ignore this. default implementation
// will call end next - which will send a BYE
mDum.mInviteSessionHandler->onSessionExpired(getSessionHandle());
}
}
else if (timeout.type() == DumTimeout::SessionRefresh)
{
if(timeout.seq() == mSessionTimerSeq)
{
// Note: If not connected then we must be issueing a reinvite/update or
// receiving one - in either case the session timer stuff will get
// reset/renegotiated - thus just ignore this referesh
if(mState == Connected)
{
sessionRefresh();
}
}
}
}
void
InviteSession::dispatchConnected(const SipMessage& msg)
{
InviteSessionHandler* handler = mDum.mInviteSessionHandler;
std::auto_ptr<Contents> offerAnswer = InviteSession::getOfferAnswer(msg);
switch (toEvent(msg, offerAnswer.get()))
{
case OnInvite:
case OnInviteReliable:
*mLastRemoteSessionModification = msg;
transition(ReceivedReinviteNoOffer);
handler->onOfferRequired(getSessionHandle(), msg);
break;
case OnInviteOffer:
case OnInviteReliableOffer:
*mLastRemoteSessionModification = msg;
transition(ReceivedReinvite);
mCurrentEncryptionLevel = getEncryptionLevel(msg);
mProposedRemoteOfferAnswer = offerAnswer;
handler->onOffer(getSessionHandle(), msg, *mProposedRemoteOfferAnswer);
break;
case On2xx:
case On2xxOffer:
case On2xxAnswer:
// retransmission of 200I
// !jf! Need to include the answer here.
sendAck();
break;
case OnUpdateOffer:
transition(ReceivedUpdate);
// !kh!
// Find out if it's an UPDATE requiring state change.
// See rfc3311 5.2, 4th paragraph.
*mLastRemoteSessionModification = msg;
mCurrentEncryptionLevel = getEncryptionLevel(msg);
mProposedRemoteOfferAnswer = offerAnswer;
handler->onOffer(getSessionHandle(), msg, *mProposedRemoteOfferAnswer);
break;
case OnUpdate:
{
// ?slg? no offerAnswer in update - just respond immediately (likely session timer) - do we need a callback?
SharedPtr<SipMessage> response(new SipMessage);
mDialog.makeResponse(*response, msg, 200);
handleSessionTimerRequest(*response, msg);
send(response);
break;
}
case OnUpdateRejected:
case On200Update:
WarningLog (<< "DUM delivered an UPDATE response in an incorrect state " << endl << msg);
resip_assert(0);
break;
case OnAck:
case OnAckAnswer: // .bwc. Don't drop ACK with SDP!
mCurrentRetransmit200 = 0; // stop the 200 retransmit timer
handler->onAckReceived(getSessionHandle(), msg);
break;
default:
dispatchOthers(msg);
break;
}
}
void
InviteSession::dispatchSentUpdate(const SipMessage& msg)
{
InviteSessionHandler* handler = mDum.mInviteSessionHandler;
std::auto_ptr<Contents> offerAnswer = InviteSession::getOfferAnswer(msg);
switch (toEvent(msg, offerAnswer.get()))
{
case OnInvite:
case OnInviteReliable:
case OnInviteOffer:
case OnInviteReliableOffer:
case OnUpdate:
case OnUpdateOffer:
{
// glare
SharedPtr<SipMessage> response(new SipMessage);
mDialog.makeResponse(*response, msg, 491);
send(response);
break;
}
case On200Update:
transition(Connected);
handleSessionTimerResponse(msg);
if (offerAnswer.get() && mProposedLocalOfferAnswer.get())
{
mCurrentEncryptionLevel = getEncryptionLevel(msg);
setCurrentLocalOfferAnswer(msg);
mCurrentRemoteOfferAnswer = offerAnswer;
if (mDum.mDialogEventStateManager)
{
// New Offer/Answer - generate a new confirmed callback with updated SDP
mDum.mDialogEventStateManager->onConfirmed(mDialog, getSessionHandle());
}
handler->onAnswer(getSessionHandle(), msg, *mCurrentRemoteOfferAnswer);
}
else if(mProposedLocalOfferAnswer.get())
{
// If we sent an offer in the Update Request and no answer is received
handler->onIllegalNegotiation(getSessionHandle(), msg);
mProposedLocalOfferAnswer.reset();
mProposedEncryptionLevel = DialogUsageManager::None;
}
break;
case On491Update:
transition(SentUpdateGlare);
start491Timer();
break;
case On422Update: // session timer
if(msg.exists(h_MinSE))
{
// Change interval to min from 422 response
mSessionInterval = msg.header(h_MinSE).value();
mMinSE = mSessionInterval;
sessionRefresh();
}
else
{
// Response must contain Min_SE - if not - just ignore
// ?slg? callback?
transition(Connected);
mProposedLocalOfferAnswer.reset();
mProposedEncryptionLevel = DialogUsageManager::None;
}
break;
case OnUpdateRejected:
transition(Connected);
mProposedLocalOfferAnswer.reset();
handler->onOfferRejected(getSessionHandle(), &msg);
break;
case OnGeneralFailure:
sendBye();
transition(Terminated);
handler->onTerminated(getSessionHandle(), InviteSessionHandler::Error, &msg);
break;
default:
dispatchOthers(msg);
break;
}
}
void
InviteSession::dispatchSentReinvite(const SipMessage& msg)
{
InviteSessionHandler* handler = mDum.mInviteSessionHandler;
std::auto_ptr<Contents> offerAnswer = InviteSession::getOfferAnswer(msg);
switch (toEvent(msg, offerAnswer.get()))
{
case OnInvite:
case OnInviteReliable:
case OnInviteOffer:
case OnInviteReliableOffer:
case OnUpdate:
case OnUpdateOffer:
{
SharedPtr<SipMessage> response(new SipMessage);
mDialog.makeResponse(*response, msg, 491);
send(response);
break;
}
case On1xx:
case On1xxEarly:
// Some UA's send a 100 response to a ReInvite - just ignore it
break;
case On2xxAnswer:
case On2xxOffer: // .slg. doesn't really make sense - should be in SentReinviteNoOffer to get this
{
mStaleReInviteTimerSeq++;
transition(Connected);
handleSessionTimerResponse(msg);
setCurrentLocalOfferAnswer(msg);
// !jf! I need to potentially include an answer in the ACK here
sendAck();
mCurrentEncryptionLevel = getEncryptionLevel(msg);
if (mSessionRefreshReInvite)
{
mSessionRefreshReInvite = false;
if (*mCurrentRemoteOfferAnswer != *offerAnswer)
{
mCurrentRemoteOfferAnswer = offerAnswer;
if (mDum.mDialogEventStateManager)
{
// New Offer/Answer - generate a new confirmed callback with updated SDP
mDum.mDialogEventStateManager->onConfirmed(mDialog, getSessionHandle());
}
handler->onRemoteAnswerChanged(getSessionHandle(), msg, *mCurrentRemoteOfferAnswer);
}
}
else
{
mCurrentRemoteOfferAnswer = offerAnswer;
if (mDum.mDialogEventStateManager)
{
// New Offer/Answer - generate a new confirmed callback with updated SDP
mDum.mDialogEventStateManager->onConfirmed(mDialog, getSessionHandle());
}
handler->onAnswer(getSessionHandle(), msg, *mCurrentRemoteOfferAnswer);
}
// !jf! do I need to allow a reINVITE overlapping the retransmission of
// the ACK when a 200I is received? If yes, then I need to store all
// ACK messages for 64*T1
break;
}
case On2xx:
mStaleReInviteTimerSeq++;
sendAck();
transition(Connected);
handleSessionTimerResponse(msg);
handler->onIllegalNegotiation(getSessionHandle(), msg);
mProposedLocalOfferAnswer.reset();
mProposedEncryptionLevel = DialogUsageManager::None;
break;
case On422Invite:
mStaleReInviteTimerSeq++;
if(msg.exists(h_MinSE))
{
// Change interval to min from 422 response
mSessionInterval = msg.header(h_MinSE).value();
mMinSE = mSessionInterval;
sessionRefresh();
}
else
{
// Response must contact Min_SE - if not - just ignore
// ?slg? callback?
transition(Connected);
mProposedLocalOfferAnswer.reset();
mProposedEncryptionLevel = DialogUsageManager::None;
}
break;
case On491Invite:
mStaleReInviteTimerSeq++;
transition(SentReinviteGlare);
start491Timer();
break;
case OnGeneralFailure:
mStaleReInviteTimerSeq++;
sendBye();
transition(Terminated);
handler->onTerminated(getSessionHandle(), InviteSessionHandler::Error, &msg);
break;
case OnInviteFailure:
case On487Invite:
mStaleReInviteTimerSeq++;
transition(Connected);
mProposedLocalOfferAnswer.reset();
handler->onOfferRejected(getSessionHandle(), &msg);
break;
default:
dispatchOthers(msg);
break;
}
}
void
InviteSession::dispatchSentReinviteNoOffer(const SipMessage& msg)
{
InviteSessionHandler* handler = mDum.mInviteSessionHandler;
std::auto_ptr<Contents> offerAnswer = InviteSession::getOfferAnswer(msg);
switch (toEvent(msg, offerAnswer.get()))
{
case OnInvite:
case OnInviteReliable:
case OnInviteOffer:
case OnInviteReliableOffer:
case OnUpdate:
case OnUpdateOffer:
{
SharedPtr<SipMessage> response(new SipMessage);
mDialog.makeResponse(*response, msg, 491);
send(response);
break;
}
case On1xx:
case On1xxEarly:
// Some UA's send a 100 response to a ReInvite - just ignore it
break;
case On2xxAnswer: // .slg. doesn't really make sense - should be in SentReinvite to get this
case On2xxOffer:
{
mStaleReInviteTimerSeq++;
transition(SentReinviteAnswered);
handleSessionTimerResponse(msg);
mCurrentEncryptionLevel = getEncryptionLevel(msg);
mProposedRemoteOfferAnswer = offerAnswer;
handler->onOffer(getSessionHandle(), msg, *mProposedRemoteOfferAnswer);
break;
}
case On2xx:
mStaleReInviteTimerSeq++;
sendAck();
transition(Connected);
handleSessionTimerResponse(msg);
handler->onIllegalNegotiation(getSessionHandle(), msg);
mProposedLocalOfferAnswer.reset();
mProposedEncryptionLevel = DialogUsageManager::None;
break;
case On422Invite:
mStaleReInviteTimerSeq++;
if(msg.exists(h_MinSE))
{
// Change interval to min from 422 response
mSessionInterval = msg.header(h_MinSE).value();
mMinSE = mSessionInterval;
sessionRefresh();
}
else
{
// Response must contact Min_SE - if not - just ignore
// ?slg? callback?
transition(Connected);
mProposedLocalOfferAnswer.reset();
mProposedEncryptionLevel = DialogUsageManager::None;
}
break;
case On491Invite:
mStaleReInviteTimerSeq++;
transition(SentReinviteNoOfferGlare);
start491Timer();
break;
case OnGeneralFailure:
mStaleReInviteTimerSeq++;
sendBye();
transition(Terminated);
handler->onTerminated(getSessionHandle(), InviteSessionHandler::Error, &msg);
break;
case OnInviteFailure:
case On487Invite:
mStaleReInviteTimerSeq++;
transition(Connected);
mProposedLocalOfferAnswer.reset();
handler->onOfferRejected(getSessionHandle(), &msg);
break;
default:
dispatchOthers(msg);
break;
}
}
void
InviteSession::dispatchReceivedReinviteSentOffer(const SipMessage& msg)
{
InviteSessionHandler* handler = mDum.mInviteSessionHandler;
std::auto_ptr<Contents> offerAnswer = InviteSession::getOfferAnswer(msg);
switch (toEvent(msg, offerAnswer.get()))
{
case OnInvite:
case OnInviteReliable:
case OnInviteOffer:
case OnInviteReliableOffer:
case OnUpdate:
case OnUpdateOffer:
{
SharedPtr<SipMessage> response(new SipMessage);
mDialog.makeResponse(*response, msg, 491);
send(response);
break;
}
case OnAckAnswer:
transition(Connected);
setCurrentLocalOfferAnswer(msg);
mCurrentRemoteOfferAnswer = offerAnswer;
mCurrentEncryptionLevel = getEncryptionLevel(msg);
mCurrentRetransmit200 = 0; // stop the 200 retransmit timer
if (mDum.mDialogEventStateManager)
{
// New Offer/Answer - generate a new confirmed callback with updated SDP
mDum.mDialogEventStateManager->onConfirmed(mDialog, getSessionHandle());
}
handler->onAnswer(getSessionHandle(), msg, *mCurrentRemoteOfferAnswer);
break;
case OnAck:
if (mLastRemoteSessionModification->header(h_CSeq).sequence() > msg.header(h_CSeq).sequence())
{
InfoLog(<< "dropped stale ACK");
}
else
{
InfoLog(<< "Got Ack with no answer");
transition(Connected);
mProposedLocalOfferAnswer.reset();
mProposedEncryptionLevel = DialogUsageManager::None;
mCurrentRetransmit200 = 0; // stop the 200 retransmit timer
//!dcm! -- should this be onIllegalNegotiation?
handler->onOfferRejected(getSessionHandle(), &msg);
}
break;
default:
dispatchOthers(msg);
break;
}
}
void
InviteSession::dispatchGlare(const SipMessage& msg)
{
InviteSessionHandler* handler = mDum.mInviteSessionHandler;
MethodTypes method = msg.header(h_CSeq).method();
if (msg.isRequest() && (method == INVITE || method == UPDATE))
{
DebugLog(<< "Re-INVITE or UPDATE received when in SentReinviteGlare or SentUpdateGlare" << endl);
// Received inbound reinvite or update, when waiting to resend outbound reinvite or update
handler->onOfferRejected(getSessionHandle(), &msg);
if(!isTerminated()) // make sure application didn't call end()
{
dispatchConnected(msg); // act as if we received message in Connected state
}
else
{
dispatchTerminated(msg);
}
}
else
{
dispatchOthers(msg);
}
}
void
InviteSession::dispatchReinviteNoOfferGlare(const SipMessage& msg)
{
InviteSessionHandler* handler = mDum.mInviteSessionHandler;
MethodTypes method = msg.header(h_CSeq).method();
if (msg.isRequest() && (method == INVITE || method == UPDATE))
{
// Received inbound reinvite or update, when waiting to resend outbound reinvite or update
handler->onOfferRequestRejected(getSessionHandle(), msg);
if(!isTerminated()) // make sure application didn't call end()
{
dispatchConnected(msg); // act as if we received message in Connected state
}
else
{
dispatchTerminated(msg);
}
}
else
{
dispatchOthers(msg);
}
}
void
InviteSession::dispatchReceivedUpdateOrReinvite(const SipMessage& msg)
{
// InviteSessionHandler* handler = mDum.mInviteSessionHandler; // unused
std::auto_ptr<Contents> offerAnswer = InviteSession::getOfferAnswer(msg);
switch (toEvent(msg, offerAnswer.get()))
{
case OnInvite:
case OnInviteReliable:
case OnInviteOffer:
case OnInviteReliableOffer:
case OnUpdate:
case OnUpdateOffer:
{
// Means that the UAC has sent us a second reINVITE or UPDATE before we
// responded to the first one. Bastard!
SharedPtr<SipMessage> response(new SipMessage);
mDialog.makeResponse(*response, msg, 500);
response->header(h_RetryAfter).value() = Random::getRandom() % 10;
send(response);
break;
}
case OnBye:
{
// BYE received after a reINVITE, terminate the reINVITE transaction.
SharedPtr<SipMessage> response(new SipMessage);
mDialog.makeResponse(*response, *mLastRemoteSessionModification, 487); // Request Terminated
handleSessionTimerRequest(*response, *mLastRemoteSessionModification);
send(response);
dispatchBye(msg);
break;
}
default:
dispatchOthers(msg);
break;
}
}
void
InviteSession::dispatchAnswered(const SipMessage& msg)
{
if (msg.isRequest() && msg.header(h_RequestLine).method() == ACK)
{
mCurrentRetransmit200 = 0; // stop the 200 retransmit timer
transition(Connected);
}
else
{
dispatchOthers(msg);
}
}
void
InviteSession::dispatchSentReinviteAnswered(const SipMessage& msg)
{
if (msg.isResponse() &&
msg.header(h_CSeq).method() == INVITE &&
msg.header(h_StatusLine).statusCode() / 200 == 1)
{
// Receving a 200 retransmission is possible - but we don't have an ACK response yet - we are still waiting for provideAnswer to be
// called by the app - so just drop the retransmission
return;
}
dispatchOthers(msg);
}
void
InviteSession::dispatchWaitingToOffer(const SipMessage& msg)
{
if (msg.isRequest() && msg.header(h_RequestLine).method() == ACK)
{
resip_assert(mProposedLocalOfferAnswer.get());
mCurrentRetransmit200 = 0; // stop the 200 retransmit timer
provideProposedOffer();
}
else
{
dispatchOthers(msg);
}
}
void
InviteSession::dispatchWaitingToRequestOffer(const SipMessage& msg)
{
if (msg.isRequest() && msg.header(h_RequestLine).method() == ACK)
{
mCurrentRetransmit200 = 0; // stop the 200 retransmit timer
requestOffer();
}
else
{
dispatchOthers(msg);
}
}
void
InviteSession::dispatchWaitingToTerminate(const SipMessage& msg)
{
if (msg.isResponse() &&
msg.header(h_CSeq).method() == INVITE)
{
if(msg.header(h_StatusLine).statusCode() / 200 == 1) // Note: stack ACK's non-2xx final responses only
{
// !jf! Need to include the answer here.
sendAck();
}
SharedPtr<SipMessage> msg = sendBye();
transition(Terminated);
mDum.mInviteSessionHandler->onTerminated(getSessionHandle(), InviteSessionHandler::LocalBye, msg.get());
}
else if(msg.isRequest())
{
if(msg.method() == BYE)
{
dispatchBye(msg);
}
else
{
SharedPtr<SipMessage> response(new SipMessage);
mDialog.makeResponse(*response, msg, 400 /* Bad Request */);
send(response);
}
}
}
void
InviteSession::dispatchWaitingToHangup(const SipMessage& msg)
{
std::auto_ptr<Contents> offerAnswer = InviteSession::getOfferAnswer(msg);
switch (toEvent(msg, offerAnswer.get()))
{
case OnAck:
case OnAckAnswer:
{
mCurrentRetransmit200 = 0; // stop the 200 retransmit timer
SharedPtr<SipMessage> msg = sendBye();
transition(Terminated);
mDum.mInviteSessionHandler->onTerminated(getSessionHandle(), InviteSessionHandler::LocalBye, msg.get());
break;
}
default:
break;
}
}
void
InviteSession::dispatchTerminated(const SipMessage& msg)
{
InfoLog (<< "InviteSession::dispatchTerminated " << msg.brief());
if (msg.isRequest())
{
if (BYE == msg.header(h_CSeq).method())
{
SharedPtr<SipMessage> response(new SipMessage);
mDialog.makeResponse(*response, msg, 200);
send(response);
}
else
{
SharedPtr<SipMessage> response(new SipMessage);
mDialog.makeResponse(*response, msg, 481);
send(response);
}
// !jf! means the peer sent BYE while we are waiting for response to BYE
//mDum.destroy(this);
}
else
{
mDum.destroy(this);
}
}
void
InviteSession::dispatchOthers(const SipMessage& msg)
{
// handle OnGeneralFailure
// handle OnRedirect
switch (msg.header(h_CSeq).method())
{
case PRACK:
dispatchPrack(msg);
break;
case CANCEL:
dispatchCancel(msg);
break;
case BYE:
dispatchBye(msg);
break;
case INFO:
dispatchInfo(msg);
break;
case MESSAGE:
dispatchMessage(msg);
break;
case ACK:
// Ignore duplicate ACKs from 2xx reTransmissions
break;
default:
// handled in Dialog
WarningLog (<< "DUM delivered a "
<< msg.header(h_CSeq).unknownMethodName()
<< " to the InviteSession in state: " << toData(mState)
<< endl
<< msg);
resip_assert(0);
break;
}
}
void
InviteSession::dispatchUnhandledInvite(const SipMessage& msg)
{
resip_assert(msg.isRequest());
resip_assert(msg.header(h_CSeq).method() == INVITE);
// If we get an INVITE request from the wire and we are not in
// Connected state, reject the request and send a BYE
SharedPtr<SipMessage> response(new SipMessage);
mDialog.makeResponse(*response, msg, 400); // !jf! what code to use?
InfoLog (<< "Sending " << response->brief());
send(response);
sendBye();
transition(Terminated);
mDum.mInviteSessionHandler->onTerminated(getSessionHandle(), InviteSessionHandler::Error, &msg);
}
void
InviteSession::dispatchPrack(const SipMessage& msg)
{
resip_assert(msg.header(h_CSeq).method() == PRACK);
if(msg.isRequest())
{
SharedPtr<SipMessage> rsp(new SipMessage);
mDialog.makeResponse(*rsp, msg, 481);
send(rsp);
sendBye();
// !jf! should we make some other callback here
transition(Terminated);
mDum.mInviteSessionHandler->onTerminated(getSessionHandle(), InviteSessionHandler::Error, &msg);
}
else
{
// ignore. could be PRACK/200
}
}
void
InviteSession::dispatchCancel(const SipMessage& msg)
{
InviteSessionHandler* handler = mDum.mInviteSessionHandler;
resip_assert(msg.header(h_CSeq).method() == CANCEL);
if(msg.isRequest())
{
SharedPtr<SipMessage> rsp(new SipMessage);
mDialog.makeResponse(*rsp, msg, 200);
send(rsp);
sendBye();
// !jf! should we make some other callback here
transition(Terminated);
handler->onTerminated(getSessionHandle(), InviteSessionHandler::RemoteCancel, &msg);
}
else
{
WarningLog (<< "DUM let me send a CANCEL at an incorrect state " << endl << msg);
resip_assert(0);
}
}
void
InviteSession::dispatchBye(const SipMessage& msg)
{
InviteSessionHandler* handler = mDum.mInviteSessionHandler;
if (msg.isRequest())
{
// Check for any non-invite server transactions (e.g. INFO)
// that have not been responded yet and terminate them.
if (mServerNitState == NitProceeding)
{
mLastNitResponse->header(h_StatusLine).statusCode() = 487;
mLastNitResponse->setContents(0);
Helper::getResponseCodeReason(487, mLastNitResponse->header(h_StatusLine).reason());
send(mLastNitResponse);
mServerNitState = NitComplete;
}
SharedPtr<SipMessage> rsp(new SipMessage);
InfoLog (<< "Received " << msg.brief());
mDialog.makeResponse(*rsp, msg, 200);
send(rsp);
// !jf! should we make some other callback here
transition(Terminated);
if (mDum.mDialogEventStateManager)
{
mDum.mDialogEventStateManager->onTerminated(mDialog, msg, InviteSessionHandler::RemoteBye);
}
handler->onTerminated(getSessionHandle(), InviteSessionHandler::RemoteBye, &msg);
mDum.destroy(this);
}
else
{
WarningLog (<< "DUM let me send a BYE at an incorrect state " << endl << msg);
resip_assert(0);
}
}
void
InviteSession::dispatchInfo(const SipMessage& msg)
{
InviteSessionHandler* handler = mDum.mInviteSessionHandler;
if (msg.isRequest())
{
if (mServerNitState == NitProceeding)
{
// Means that the UAC has sent us a second INFO before we
// responded to the first one.
SharedPtr<SipMessage> response(new SipMessage);
mDialog.makeResponse(*response, msg, 500);
response->header(h_RetryAfter).value() = Random::getRandom() % 10;
send(response);
WarningLog(<<"an INFO message was received before the application called acceptNIT() for the previous INFO message");
}
else
{
InfoLog (<< "Received " << msg.brief());
mServerNitState = NitProceeding;
mDialog.makeResponse(*mLastNitResponse, msg, 200);
handler->onInfo(getSessionHandle(), msg);
}
}
else
{
resip_assert(mNitState == NitProceeding);
//!dcm! -- toss away 1xx to an info?
if (msg.header(h_StatusLine).statusCode() >= 300)
{
handler->onInfoFailure(getSessionHandle(), msg);
}
else if (msg.header(h_StatusLine).statusCode() >= 200)
{
handler->onInfoSuccess(getSessionHandle(), msg);
}
nitComplete();
}
}
void
InviteSession::acceptNIT(int statusCode, const Contents * contents)
{
if (statusCode / 100 != 2)
{
throw UsageUseException("Must accept with a 2xx", __FILE__, __LINE__);
}
if (mServerNitState != NitProceeding )
{
throw UsageUseException("No transaction to accept", __FILE__, __LINE__);
}
mLastNitResponse->header(h_StatusLine).statusCode() = statusCode;
mLastNitResponse->setContents(contents);
Helper::getResponseCodeReason(statusCode, mLastNitResponse->header(h_StatusLine).reason());
send(mLastNitResponse);
mServerNitState = NitComplete;
}
class InviteSessionAcceptNITCommand : public DumCommandAdapter
{
public:
InviteSessionAcceptNITCommand(const InviteSessionHandle& inviteSessionHandle, int statusCode, const Contents* contents)
: mInviteSessionHandle(inviteSessionHandle),
mStatusCode(statusCode),
mContents(contents?contents->clone():0)
{
}
virtual void executeCommand()
{
if(mInviteSessionHandle.isValid())
{
mInviteSessionHandle->acceptNIT(mStatusCode, mContents.get());
}
}
virtual EncodeStream& encodeBrief(EncodeStream& strm) const
{
return strm << "InviteSessionAcceptNITCommand";
}
private:
InviteSessionHandle mInviteSessionHandle;
int mStatusCode;
std::auto_ptr<Contents> mContents;
};
void
InviteSession::acceptNITCommand(int statusCode, const Contents* contents)
{
mDum.post(new InviteSessionAcceptNITCommand(getSessionHandle(), statusCode, contents));
}
void
InviteSession::rejectNIT(int statusCode)
{
if (statusCode < 400)
{
throw UsageUseException("Must reject with a >= 4xx", __FILE__, __LINE__);
}
if (mServerNitState != NitProceeding )
{
throw UsageUseException("No transaction to reject", __FILE__, __LINE__);
}
mLastNitResponse->header(h_StatusLine).statusCode() = statusCode;
mLastNitResponse->setContents(0);
Helper::getResponseCodeReason(statusCode, mLastNitResponse->header(h_StatusLine).reason());
send(mLastNitResponse);
mServerNitState = NitComplete;
}
class InviteSessionRejectNITCommand : public DumCommandAdapter
{
public:
InviteSessionRejectNITCommand(const InviteSessionHandle& inviteSessionHandle, int statusCode)
: mInviteSessionHandle(inviteSessionHandle),
mStatusCode(statusCode)
{
}
virtual void executeCommand()
{
if(mInviteSessionHandle.isValid())
{
mInviteSessionHandle->rejectNIT(mStatusCode);
}
}
virtual EncodeStream& encodeBrief(EncodeStream& strm) const
{
return strm << "InviteSessionRejectNITCommand";
}
private:
InviteSessionHandle mInviteSessionHandle;
int mStatusCode;
};
void
InviteSession::rejectNITCommand(int statusCode)
{
mDum.post(new InviteSessionRejectNITCommand(getSessionHandle(), statusCode));
}
void
InviteSession::dispatchMessage(const SipMessage& msg)
{
InviteSessionHandler* handler = mDum.mInviteSessionHandler;
if (msg.isRequest())
{
if (mServerNitState == NitProceeding)
{
// Means that the UAC has sent us a second NIT message before we
// responded to the first one.
SharedPtr<SipMessage> response(new SipMessage);
mDialog.makeResponse(*response, msg, 500);
response->header(h_RetryAfter).value() = Random::getRandom() % 10;
send(response);
}
else
{
InfoLog (<< "Received " << msg.brief());
mServerNitState = NitProceeding;
mDialog.makeResponse(*mLastNitResponse, msg, 200);
mLastNitResponse->header(h_Contacts).clear();
handler->onMessage(getSessionHandle(), msg);
}
}
else
{
resip_assert(mNitState == NitProceeding);
//!dcm! -- toss away 1xx to an message?
if (msg.header(h_StatusLine).statusCode() >= 300)
{
handler->onMessageFailure(getSessionHandle(), msg);
}
else if (msg.header(h_StatusLine).statusCode() >= 200)
{
handler->onMessageSuccess(getSessionHandle(), msg);
}
nitComplete();
}
}
void
InviteSession::startRetransmit200Timer()
{
mCurrentRetransmit200 = Timer::T1;
unsigned int seq = mLastRemoteSessionModification->header(h_CSeq).sequence();
mDum.addTimerMs(DumTimeout::Retransmit200, mCurrentRetransmit200, getBaseHandle(), seq);
mDum.addTimerMs(DumTimeout::WaitForAck, Timer::TH, getBaseHandle(), seq);
}
// RFC3261 section 14.1
// If a UAC receives a 491 response to a re-INVITE, it SHOULD start a timer with
// a value T chosen as follows:
// 1. If the UAC is the owner of the Call-ID of the dialog ID, T has a randomly
// chosen value between 2.1 and 4 seconds in units of 10 ms.
// 2. If the UAC is not the owner of the Call-ID of the dialog ID, T has a
// randomly chosen value of between 0 and 2 seconds in units of 10 ms.
void
InviteSession::start491Timer()
{
unsigned int seq = mLastLocalSessionModification->header(h_CSeq).sequence();
if (dynamic_cast<ClientInviteSession*>(this))
{
int timer = Random::getRandom() % (4000 - 2100);
timer += 2100;
timer -= timer % 10;
DebugLog(<< "491 timer value: " << timer << "ms" << endl);
mDum.addTimerMs(DumTimeout::Glare, timer, getBaseHandle(), seq);
}
else
{
int timer = Random::getRandom() % 2000;
timer -= timer % 10;
DebugLog(<< "491 timer value: " << timer << "ms" << endl);
mDum.addTimerMs(DumTimeout::Glare, timer, getBaseHandle(), seq);
}
}
void
InviteSession::startStaleReInviteTimer()
{
InfoLog (<< toData(mState) << ": startStaleReInviteTimer");
unsigned long when = mDialog.mDialogSet.getUserProfile()->getDefaultStaleReInviteTime();
mDum.addTimer(DumTimeout::StaleReInvite,
when,
getBaseHandle(),
++mStaleReInviteTimerSeq);
}
void
InviteSession::setSessionTimerHeaders(SipMessage &msg)
{
if(mSessionInterval >= 90) // If mSessionInterval is 0 then SessionTimers are considered disabled
{
msg.header(h_SessionExpires).value() = mSessionInterval;
if(msg.isRequest())
{
msg.header(h_SessionExpires).param(p_refresher) = Data(mSessionRefresher ? "uac" : "uas");
}
else
{
msg.header(h_SessionExpires).param(p_refresher) = Data(mSessionRefresher ? "uas" : "uac");
}
if(msg.isRequest() ||
(msg.isResponse() && msg.header(h_StatusLine).responseCode() == 422))
{
msg.header(h_MinSE).value() = mMinSE;
}
}
else
{
msg.remove(h_SessionExpires);
msg.remove(h_MinSE);
}
}
void
InviteSession::sessionRefresh()
{
if (updateMethodSupported())
{
transition(SentUpdate);
mDialog.makeRequest(*mLastLocalSessionModification, UPDATE);
mLastLocalSessionModification->setContents(0); // Don't send SDP
}
else
{
transition(SentReinvite);
mDialog.makeRequest(*mLastLocalSessionModification, INVITE);
startStaleReInviteTimer();
InviteSession::setOfferAnswer(*mLastLocalSessionModification, mCurrentLocalOfferAnswer.get());
mProposedLocalOfferAnswer = InviteSession::makeOfferAnswer(*mCurrentLocalOfferAnswer.get(), 0);
mSessionRefreshReInvite = true;
}
setSessionTimerHeaders(*mLastLocalSessionModification);
InfoLog (<< "sessionRefresh: Sending " << mLastLocalSessionModification->brief());
DumHelper::setOutgoingEncryptionLevel(*mLastLocalSessionModification, mCurrentEncryptionLevel);
send(mLastLocalSessionModification);
}
void
InviteSession::setSessionTimerPreferences()
{
mSessionInterval = mDialog.mDialogSet.getUserProfile()->getDefaultSessionTime(); // Used only if remote doesn't request a time
if(mSessionInterval != 0)
{
// If session timers are not disabled then ensure interval is greater than or equal to MinSE
mSessionInterval = resipMax(mMinSE, mSessionInterval);
}
switch(mDialog.mDialogSet.getUserProfile()->getDefaultSessionTimerMode())
{
case Profile::PreferLocalRefreshes:
mSessionRefresher = true; // Default refresher is Local
break;
case Profile::PreferRemoteRefreshes:
mSessionRefresher = false; // Default refresher is Remote
break;
case Profile::PreferCalleeRefreshes:
mSessionRefresher = dynamic_cast<ServerInviteSession*>(this) != NULL; // Default refresher is callee
break;
case Profile::PreferCallerRefreshes:
mSessionRefresher = dynamic_cast<ClientInviteSession*>(this) != NULL; // Default refresher is caller
break;
}
}
void
InviteSession::startSessionTimer()
{
if(mSessionInterval >= 90) // 90 is the absolute minimum - RFC4028
{
// Check if we are the refresher
if(mSessionRefresher)
{
// Start Session-Refresh Timer to mSessionInterval / 2 (recommended by RFC4028)
mDum.addTimer(DumTimeout::SessionRefresh, mSessionInterval / 2, getBaseHandle(), ++mSessionTimerSeq);
}
else
{
// Start Session-Expiration Timer to mSessionInterval - BYE should be sent a minimum of 32 and one third of the SessionInterval, seconds before the session expires (recommended by RFC4028)
mDum.addTimer(DumTimeout::SessionExpiration, mSessionInterval - resipMin((UInt32)32,mSessionInterval/3), getBaseHandle(), ++mSessionTimerSeq);
}
}
else // Session Interval less than 90 - consider timers disabled
{
++mSessionTimerSeq; // increment seq, incase old timers are running and now session timers are disabled
}
}
void
InviteSession::handleSessionTimerResponse(const SipMessage& msg)
{
resip_assert(msg.header(h_CSeq).method() == INVITE || msg.header(h_CSeq).method() == UPDATE);
// Allow Re-Invites and Updates to update the Peer P-Asserted-Identity
if (msg.exists(h_PAssertedIdentities))
{
mPeerPAssertedIdentities = msg.header(h_PAssertedIdentities);
}
// If session timers are locally supported then handle response
if(mDum.getMasterProfile()->getSupportedOptionTags().find(Token(Symbols::Timer)))
{
setSessionTimerPreferences();
if(msg.exists(h_Requires) && msg.header(h_Requires).find(Token(Symbols::Timer))
&& !msg.exists(h_SessionExpires))
{
// If no Session Expires in response and Requires header is present then session timer is to be 'turned off'
mSessionInterval = 0;
}
// Process Session Timer headers
else if(msg.exists(h_SessionExpires))
{
mSessionInterval = msg.header(h_SessionExpires).value();
if(msg.header(h_SessionExpires).exists(p_refresher))
{
// Remote end specified refresher preference
mSessionRefresher = (msg.header(h_SessionExpires).param(p_refresher) == Data("uac"));
}
}
else
{
// Note: If no Requires or Session-Expires, then UAS does not support Session Timers
// - we are free to use our SessionInterval settings (set above as a default)
// If far end doesn't support then refresher must be local
mSessionRefresher = true;
}
// Update MinSE if specified and longer than current value
if(msg.exists(h_MinSE))
{
mMinSE = resipMax(mMinSE, msg.header(h_MinSE).value());
}
startSessionTimer();
}
}
void
InviteSession::handleSessionTimerRequest(SipMessage &response, const SipMessage& request)
{
resip_assert(request.header(h_CSeq).method() == INVITE || request.header(h_CSeq).method() == UPDATE);
// Allow Re-Invites and Updates to update the Peer P-Asserted-Identity
if (request.exists(h_PAssertedIdentities))
{
mPeerPAssertedIdentities = request.header(h_PAssertedIdentities);
}
// If session timers are locally supported then add necessary headers to response
if(mDum.getMasterProfile()->getSupportedOptionTags().find(Token(Symbols::Timer)))
{
// Update MinSE if specified and longer than current value
if(request.exists(h_MinSE))
{
mMinSE = resipMax(mMinSE, request.header(h_MinSE).value());
}
setSessionTimerPreferences();
// Check if far-end supports
bool farEndSupportsTimer = false;
if(request.exists(h_Supporteds) && request.header(h_Supporteds).find(Token(Symbols::Timer)))
{
farEndSupportsTimer = true;
if(request.exists(h_SessionExpires))
{
// Use Session Interval requested by remote - if none then use local settings
mSessionInterval = request.header(h_SessionExpires).value();
if(request.header(h_SessionExpires).exists(p_refresher))
{
mSessionRefresher = (request.header(h_SessionExpires).param(p_refresher) == Data("uas"));
}
}
}
else
{
// If far end doesn't support then refresher must be local
mSessionRefresher = true;
}
// Add Session-Expires to response if required
if(mSessionInterval >= 90)
{
if(farEndSupportsTimer)
{
// If far end supports session-timer then require it, if not already present
if(!response.header(h_Requires).find(Token(Symbols::Timer)))
{
response.header(h_Requires).push_back(Token(Symbols::Timer));
}
}
setSessionTimerHeaders(response);
}
startSessionTimer();
}
}
Data
InviteSession::toData(State state)
{
switch (state)
{
case Undefined:
return "InviteSession::Undefined";
case Connected:
return "InviteSession::Connected";
case SentUpdate:
return "InviteSession::SentUpdate";
case SentUpdateGlare:
return "InviteSession::SentUpdateGlare";
case SentReinvite:
return "InviteSession::SentReinvite";
case SentReinviteGlare:
return "InviteSession::SentReinviteGlare";
case SentReinviteNoOffer:
return "InviteSession::SentReinviteNoOffer";
case SentReinviteAnswered:
return "InviteSession::SentReinviteAnswered";
case SentReinviteNoOfferGlare:
return "InviteSession::SentReinviteNoOfferGlare";
case ReceivedUpdate:
return "InviteSession::ReceivedUpdate";
case ReceivedReinvite:
return "InviteSession::ReceivedReinvite";
case ReceivedReinviteNoOffer:
return "InviteSession::ReceivedReinviteNoOffer";
case ReceivedReinviteSentOffer:
return "InviteSession::ReceivedReinviteSentOffer";
case Answered:
return "InviteSession::Answered";
case WaitingToOffer:
return "InviteSession::WaitingToOffer";
case WaitingToRequestOffer:
return "InviteSession::WaitingToRequestOffer";
case WaitingToTerminate:
return "InviteSession::WaitingToTerminate";
case WaitingToHangup:
return "InviteSession::WaitingToHangup";
case Terminated:
return "InviteSession::Terminated";
case UAC_Start:
return "UAC_Start";
case UAS_Offer:
return "UAS_Offer";
case UAS_OfferProvidedAnswer:
return "UAS_OfferProvidedAnswer";
case UAS_EarlyOffer:
return "UAS_EarlyOffer";
case UAS_EarlyProvidedAnswer:
return "UAS_EarlyProvidedAnswer";
case UAS_NoOffer:
return "UAS_NoOffer";
case UAS_ProvidedOffer:
return "UAS_ProvidedOffer";
case UAS_EarlyNoOffer:
return "UAS_EarlyNoOffer";
case UAS_EarlyProvidedOffer:
return "UAS_EarlyProvidedOffer";
case UAS_Accepted:
return "UAS_Accepted";
case UAS_WaitingToOffer:
return "UAS_WaitingToOffer";
case UAS_AcceptedWaitingAnswer:
return "UAS_AcceptedWaitingAnswer";
case UAC_Early:
return "UAC_Early";
case UAC_EarlyWithOffer:
return "UAC_EarlyWithOffer";
case UAC_EarlyWithAnswer:
return "UAC_EarlyWithAnswer";
case UAC_Answered:
return "UAC_Answered";
case UAC_SentUpdateEarly:
return "UAC_SentUpdateEarly";
case UAC_SentUpdateEarlyGlare:
return "UAC_SentUpdateEarlyGlare";
case UAC_ReceivedUpdateEarly:
return "UAC_ReceivedUpdateEarly";
case UAC_SentAnswer:
return "UAC_SentAnswer";
case UAC_QueuedUpdate:
return "UAC_QueuedUpdate";
case UAC_Cancelled:
return "UAC_Cancelled";
case UAS_Start:
return "UAS_Start";
case UAS_OfferReliable:
return "UAS_OfferReliable";
case UAS_OfferReliableProvidedAnswer:
return "UAS_OfferReliableProvidedAnswer";
case UAS_NoOfferReliable:
return "UAS_NoOfferReliable";
case UAS_ProvidedOfferReliable:
return "UAS_ProvidedOfferReliable";
case UAS_FirstSentOfferReliable:
return "UAS_FirstSentOfferReliable";
case UAS_FirstSentAnswerReliable:
return "UAS_FirstSentAnswerReliable";
case UAS_NoAnswerReliableWaitingPrack:
return "UAS_NoAnswerReliableWaitingPrack";
case UAS_NoAnswerReliable:
return "UAS_NoAnswerReliable";
case UAS_NegotiatedReliable:
return "UAS_NegotiatedReliable";
case UAS_SentUpdate:
return "UAS_SentUpdate";
case UAS_SentUpdateAccepted:
return "UAS_SentUpdateAccepted";
case UAS_SentUpdateGlare:
return "UAS_SentUpdateGlare";
case UAS_ReceivedUpdate:
return "UAS_ReceivedUpdate";
case UAS_ReceivedUpdateWaitingAnswer:
return "UAS_ReceivedUpdateWaitingAnswer";
case UAS_WaitingToHangup:
return "UAS_WaitingToHangup";
case UAS_WaitingToRequestOffer:
return "UAS_WaitingToRequestOffer";
}
resip_assert(0);
return "Undefined";
}
void
InviteSession::transition(State target)
{
InfoLog (<< "Transition " << toData(mState) << " -> " << toData(target));
mState = target;
}
bool
InviteSession::isReliable(const SipMessage& msg) const
{
if(msg.method() != INVITE)
{
return false;
}
if(msg.isRequest())
{
return mDum.getMasterProfile()->getUasReliableProvisionalMode() > MasterProfile::Never &&
((msg.exists(h_Supporteds) && msg.header(h_Supporteds).find(Token(Symbols::C100rel))) ||
(msg.exists(h_Requires) && msg.header(h_Requires).find(Token(Symbols::C100rel))));
}
else
{
// RFC3262 says reliable provisionals MUST have a Require: 100rel and an RSeq
return mDum.getMasterProfile()->getUacReliableProvisionalMode() > MasterProfile::Never &&
msg.exists(h_Requires) && msg.header(h_Requires).find(Token(Symbols::C100rel)) &&
msg.exists(h_RSeq);
}
}
//static std::auto_ptr<SdpContents> emptySdp;
std::auto_ptr<Contents>
InviteSession::getOfferAnswer(const SipMessage& msg)
{
if(mDum.mInviteSessionHandler->isGenericOfferAnswer())
{
if(msg.getContents())
{
return std::auto_ptr<Contents>(msg.getContents()->clone());
}
else
{
return std::auto_ptr<Contents>();
}
}
else
{
return std::auto_ptr<Contents>(Helper::getSdp(msg.getContents()));
}
}
std::auto_ptr<Contents>
InviteSession::makeOfferAnswer(const Contents& offerAnswer)
{
return std::auto_ptr<Contents>(static_cast<Contents*>(offerAnswer.clone()));
}
auto_ptr<Contents>
InviteSession::makeOfferAnswer(const Contents& offerAnswer,
const Contents* alternative)
{
if (alternative)
{
MultipartAlternativeContents* mac = new MultipartAlternativeContents;
mac->parts().push_back(alternative->clone());
mac->parts().push_back(offerAnswer.clone());
return auto_ptr<Contents>(mac);
}
else
{
return auto_ptr<Contents>(offerAnswer.clone());
}
}
void
InviteSession::setOfferAnswer(SipMessage& msg, const Contents& offerAnswer, const Contents* alternative)
{
// !jf! should deal with multipart here
// This will clone the offerAnswer since the InviteSession also wants to keep its own
// copy of the offerAnswer around for the application to access
if (alternative)
{
MultipartAlternativeContents* mac = new MultipartAlternativeContents;
mac->parts().push_back(alternative->clone());
mac->parts().push_back(offerAnswer.clone());
msg.setContents(auto_ptr<Contents>(mac));
}
else
{
msg.setContents(&offerAnswer);
}
}
void
InviteSession::setOfferAnswer(SipMessage& msg, const Contents* offerAnswer)
{
resip_assert(offerAnswer);
msg.setContents(offerAnswer);
}
void
InviteSession::provideProposedOffer()
{
MultipartAlternativeContents* mp_ans =
dynamic_cast<MultipartAlternativeContents*>(mProposedLocalOfferAnswer.get());
if (mp_ans)
{
// .kw. can cast below ever be NULL? Need assert/throw?
provideOffer( *(dynamic_cast<Contents*>((mp_ans)->parts().back())),
mProposedEncryptionLevel,
dynamic_cast<Contents*>((mp_ans)->parts().front()));
}
else
{
// .kw. can cast below ever be NULL? Need assert/throw?
provideOffer(*(dynamic_cast<Contents*>(mProposedLocalOfferAnswer.get())), mProposedEncryptionLevel, 0);
}
}
InviteSession::Event
InviteSession::toEvent(const SipMessage& msg, const Contents* offerAnswer)
{
MethodTypes method = msg.header(h_CSeq).method();
int code = msg.isResponse() ? msg.header(h_StatusLine).statusCode() : 0;
//.dcm. Treat an invite as reliable if UAS 100rel support is enabled. For
//responses, reiable provisionals should only be received if the invite was
//sent reliably. Spurious reliable provisional respnoses are dropped outside
//the state machine.
bool reliable = isReliable(msg);
bool sentOffer = mProposedLocalOfferAnswer.get();
if (code == 481 || code == 408)
{
return OnGeneralFailure;
}
else if (code >= 300 && code <= 399)
{
return OnRedirect;
}
else if (method == INVITE && code == 0)
{
if (offerAnswer)
{
if (reliable)
{
return OnInviteReliableOffer;
}
else
{
return OnInviteOffer;
}
}
else
{
if (reliable)
{
return OnInviteReliable;
}
else
{
return OnInvite;
}
}
}
else if (method == INVITE && code > 100 && code < 200)
{
if (reliable)
{
if (offerAnswer)
{
if (sentOffer)
{
return On1xxAnswer;
}
else
{
return On1xxOffer;
}
}
else
{
return On1xx;
}
}
else
{
if (offerAnswer)
{
return On1xxEarly;
}
else
{
return On1xx;
}
}
}
else if (method == INVITE && code >= 200 && code < 300)
{
if (offerAnswer)
{
if (sentOffer)
{
return On2xxAnswer;
}
else
{
return On2xxOffer;
}
}
else
{
return On2xx;
}
}
else if (method == INVITE && code == 422)
{
return On422Invite;
}
else if (method == INVITE && code == 487)
{
return On487Invite;
}
else if (method == INVITE && code == 491)
{
return On491Invite;
}
else if (method == INVITE && code >= 400)
{
return OnInviteFailure;
}
else if (method == ACK)
{
if (offerAnswer)
{
return OnAckAnswer;
}
else
{
return OnAck;
}
}
else if (method == CANCEL && code == 0)
{
return OnCancel;
}
else if (method == CANCEL && code / 200 == 1)
{
return On200Cancel;
}
else if (method == CANCEL && code >= 400)
{
return OnCancelFailure;
}
else if (method == BYE && code == 0)
{
return OnBye;
}
else if (method == BYE && code / 200 == 1)
{
return On200Bye;
}
else if (method == PRACK && code == 0)
{
return OnPrack;
}
else if (method == PRACK && code / 200 == 1)
{
return On200Prack;
}
else if (method == UPDATE && code == 0)
{
if (offerAnswer)
{
return OnUpdateOffer;
}
else
{
return OnUpdate;
}
}
else if (method == UPDATE && code / 200 == 1)
{
return On200Update;
}
else if (method == UPDATE && code == 422)
{
return On422Update;
}
else if (method == UPDATE && code == 491)
{
return On491Update;
}
else if (method == UPDATE && code >= 400)
{
return OnUpdateRejected;
}
else
{
//assert(0); // dispatchOthers will throw if the message type is really unknown
return Unknown;
}
}
void
InviteSession::sendAck(const Contents *answer)
{
SharedPtr<SipMessage> ack(new SipMessage);
SharedPtr<SipMessage> source;
if (mLastLocalSessionModification->method() == UPDATE)
{
//.dcm. scary--we could make a special ClientInviteSession variable/sendAck
source = mDialog.mDialogSet.getCreator()->getLastRequest();
}
else
{
source = mLastLocalSessionModification;
}
resip_assert(mAcks.count(source->getTransactionId()) == 0);
mDialog.makeRequest(*ack, ACK);
// Copy Authorization and Proxy Authorization headers from
// mLastLocalSessionModification; regardless of whether this was the original
// INVITE or not, this is the correct place to go for auth headers.
if(mLastLocalSessionModification->exists(h_Authorizations))
{
ack->header(h_Authorizations) = mLastLocalSessionModification->header(h_Authorizations);
}
if(mLastLocalSessionModification->exists(h_ProxyAuthorizations))
{
ack->header(h_ProxyAuthorizations) = mLastLocalSessionModification->header(h_ProxyAuthorizations);
}
// Copy CSeq from original INVITE
ack->header(h_CSeq).sequence() = source->header(h_CSeq).sequence();
if(answer != 0)
{
setOfferAnswer(*ack, *answer);
}
mAcks[source->getTransactionId()] = ack;
mDum.addTimerMs(DumTimeout::CanDiscardAck, Timer::TH, getBaseHandle(), ack->header(h_CSeq).sequence(), 0, source->getTransactionId());
InfoLog (<< "Sending " << ack->brief());
send(ack);
}
SharedPtr<SipMessage>
InviteSession::sendBye()
{
SharedPtr<SipMessage> bye(new SipMessage());
mDialog.makeRequest(*bye, BYE);
Data txt;
if (mEndReason != NotSpecified)
{
Token reason("SIP");
txt = getEndReasonString(mEndReason);
reason.param(p_text) = txt;
bye->header(h_Reasons).push_back(reason);
}
if (mDum.mDialogEventStateManager)
{
mDum.mDialogEventStateManager->onTerminated(mDialog, *bye, InviteSessionHandler::LocalBye);
}
InfoLog (<< myAddr() << " Sending BYE " << txt);
send(bye);
return bye;
}
DialogUsageManager::EncryptionLevel
InviteSession::getEncryptionLevel(const SipMessage& msg)
{
DialogUsageManager::EncryptionLevel level = DialogUsageManager::None;
const SecurityAttributes* secAttr = msg.getSecurityAttributes();
if (secAttr)
{
SignatureStatus sig = secAttr->getSignatureStatus();
bool sign = (SignatureTrusted == sig || SignatureCATrusted == sig || SignatureSelfSigned == sig);
bool encrypted = secAttr->isEncrypted();
if (encrypted && sign ) level = DialogUsageManager::SignAndEncrypt;
else if (encrypted) level = DialogUsageManager::Encrypt;
else if (sign) level = DialogUsageManager::Sign;
}
return level;
}
void
InviteSession::setCurrentLocalOfferAnswer(const SipMessage& msg)
{
resip_assert(mProposedLocalOfferAnswer.get());
if (dynamic_cast<MultipartAlternativeContents*>(mProposedLocalOfferAnswer.get()))
{
if (DialogUsageManager::Encrypt == getEncryptionLevel(msg) || DialogUsageManager::SignAndEncrypt == getEncryptionLevel(msg))
{
mCurrentLocalOfferAnswer = auto_ptr<Contents>(static_cast<Contents*>((dynamic_cast<MultipartAlternativeContents*>(mProposedLocalOfferAnswer.get()))->parts().back()->clone()));
}
else
{
mCurrentLocalOfferAnswer = auto_ptr<Contents>(static_cast<Contents*>((dynamic_cast<MultipartAlternativeContents*>(mProposedLocalOfferAnswer.get()))->parts().front()->clone()));
}
}
else
{
mCurrentLocalOfferAnswer = auto_ptr<Contents>(static_cast<Contents*>(mProposedLocalOfferAnswer.get()->clone()));
}
mProposedLocalOfferAnswer.reset();
}
void
InviteSession::onReadyToSend(SipMessage& msg)
{
mDum.mInviteSessionHandler->onReadyToSend(getSessionHandle(), msg);
}
void
InviteSession::flowTerminated()
{
// notify handler
mDum.mInviteSessionHandler->onFlowTerminated(getSessionHandle());
}
void
InviteSession::referNoSub(const SipMessage& msg)
{
resip_assert(msg.isRequest() && msg.header(h_CSeq).method()==REFER);
mLastReferNoSubRequest = msg;
mDum.mInviteSessionHandler->onReferNoSub(getSessionHandle(), mLastReferNoSubRequest);
}
void
InviteSession::acceptReferNoSub(int statusCode)
{
if (statusCode / 100 != 2)
{
throw UsageUseException("Must accept with a 2xx", __FILE__, __LINE__);
}
SharedPtr<SipMessage> response(new SipMessage);
mDialog.makeResponse(*response, mLastReferNoSubRequest, statusCode);
response->header(h_ReferSub).value() = "false";
//response->header(h_Supporteds).push_back(Token(Symbols::NoReferSub));
send(response);
}
void
InviteSession::rejectReferNoSub(int responseCode)
{
if (responseCode < 400)
{
throw UsageUseException("Must reject with a >= 4xx", __FILE__, __LINE__);
}
SharedPtr<SipMessage> response(new SipMessage);
mDialog.makeResponse(*response, mLastReferNoSubRequest, responseCode);
send(response);
}
/* ====================================================================
* The Vovida Software License, Version 1.0
*
* Copyright (c) 2000 Vovida Networks, Inc. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in
* the documentation and/or other materials provided with the
* distribution.
*
* 3. The names "VOCAL", "Vovida Open Communication Application Library",
* and "Vovida Open Communication Application Library (VOCAL)" must
* not be used to endorse or promote products derived from this
* software without prior written permission. For written
* permission, please contact vocal@vovida.org.
*
* 4. Products derived from this software may not be called "VOCAL", nor
* may "VOCAL" appear in their name, without prior written
* permission of Vovida Networks, Inc.
*
* THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESSED OR IMPLIED
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, TITLE AND
* NON-INFRINGEMENT ARE DISCLAIMED. IN NO EVENT SHALL VOVIDA
* NETWORKS, INC. OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT DAMAGES
* IN EXCESS OF $1,000, NOR FOR ANY INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
* OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
* USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
* DAMAGE.
*
* ====================================================================
*
* This software consists of voluntary contributions made by Vovida
* Networks, Inc. and many individuals on behalf of Vovida Networks,
* Inc. For more information on Vovida Networks, Inc., please see
* <http://www.vovida.org/>.
*
*/