Revision: 24371
http://opalvoip.svn.sourceforge.net/opalvoip/?rev=24371&view=rev
Author: rjongbloed
Date: 2010-05-24 00:56:41 +0000 (Mon, 24 May 2010)
Log Message:
-----------
Fixed consultation transfer scenario where INVITE with Replaces header is used but the "Party C" it is directed to has not yet answered the call. This fills in some missing compliance to RFC 3891 for early dialogs.
Added callback to OnTransferNotify() when get the INVITE with Replaces and have a Referred-By header to indicate to application that we received a completed consultation transfer, thanks Samuel Widmer.
Modified Paths:
--------------
opal/trunk/src/sip/sipcon.cxx
Modified: opal/trunk/src/sip/sipcon.cxx
===================================================================
--- opal/trunk/src/sip/sipcon.cxx 2010-05-22 03:30:43 UTC (rev 24370)
+++ opal/trunk/src/sip/sipcon.cxx 2010-05-24 00:56:41 UTC (rev 24371)
@@ -142,6 +142,7 @@
{ OpalConnection::EndedByNoEndPoint , SIP_PDU::Failure_NotFound }, // TODO - SGW - add for endpoints not running on a ip from H323 side.
{ OpalConnection::EndedByUnreachable , SIP_PDU::Failure_Forbidden }, // TODO - SGW - add for avoid sip calls to SGW IP.
{ OpalConnection::EndedByNoBandwidth , SIP_PDU::GlobalFailure_NotAcceptable }, // TODO - SGW - added to reject call when no bandwidth
+ { OpalConnection::EndedByInvalidConferenceID,SIP_PDU::Failure_TransactionDoesNotExist}
};
for (PINDEX i = 0; i < PARRAYSIZE(ReasonToSIPCode); i++) {
@@ -366,7 +367,10 @@
// EndedByCallForwarded is a special case because it needs extra paramater
SendInviteResponse(sipCode, NULL, callEndReason == EndedByCallForwarded ? (const char *)forwardParty : NULL);
- // Wait for ACK from remote before destroying object
+ /* Wait for ACK from remote before destroying object. Note that we either
+ get the ACK, or OnAckTimeout() fires and sets this flag anyway. We are
+ outside of a connection mutex lock at this point, so no deadlock
+ should occur. Really should be a PSyncPoint. */
while (!ackReceived)
PThread::Sleep(100);
@@ -2021,38 +2025,78 @@
SetRemoteMediaFormats(originalInvite->GetSDP());
- // Replaces header has already been validated in SIPEndPoint::OnReceivedINVITE
- PSafePtr<SIPConnection> replacedConnection = endpoint.GetSIPConnectionWithLock(mime("Replaces"), PSafeReadOnly);
- if (replacedConnection != NULL) {
- PTRACE(3, "SIP\tConnection " << *replacedConnection << " replaced by " << *this);
+ // See if we have a replaces header, if not is normal call
+ PString replaces = mime("Replaces");
+ if (replaces.IsEmpty()) {
+ // indicate the other is to start ringing (but look out for clear calls)
+ if (!OnIncomingConnection(0, NULL)) {
+ PTRACE(1, "SIP\tOnIncomingConnection failed for INVITE from " << request.GetURI() << " for " << *this);
+ Release();
+ return;
+ }
- // Do OnRelease for other connection synchronously or there is
- // confusion with media streams still open
- replacedConnection->synchronousOnRelease = true;
- replacedConnection->Release(OpalConnection::EndedByCallForwarded);
+ PTRACE(3, "SIP\tOnIncomingConnection succeeded for INVITE from " << request.GetURI() << " for " << *this);
- SetConnected();
+ OnApplyStringOptions();
+
+ if (ownerCall.OnSetUp(*this)) {
+ AnsweringCall(OnAnswerCall(GetRemotePartyURL()));
+ return;
+ }
+
+ PTRACE(1, "SIP\tOnSetUp failed for INVITE from " << request.GetURI() << " for " << *this);
+ Release();
return;
}
- // indicate the other is to start ringing (but look out for clear calls)
- if (!OnIncomingConnection(0, NULL)) {
- PTRACE(1, "SIP\tOnIncomingConnection failed for INVITE from " << request.GetURI() << " for " << *this);
- Release();
+ // Replaces header string has already been validated in SIPEndPoint::OnReceivedINVITE
+ PSafePtr<SIPConnection> replacedConnection = endpoint.GetSIPConnectionWithLock(replaces, PSafeReadOnly);
+ if (replacedConnection == NULL) {
+ /* Allow for a race condition where between when SIPEndPoint::OnReceivedINVITE()
+ is executed and here, the call to be replaced was released. */
+ Release(EndedByInvalidConferenceID);
return;
}
- PTRACE(3, "SIP\tOnIncomingConnection succeeded for INVITE from " << request.GetURI() << " for " << *this);
+ if (replacedConnection->GetPhase() < ConnectedPhase) {
+ if (!replacedConnection->IsOriginating()) {
+ PTRACE(3, "SIP\tEarly connection " << *replacedConnection << " cannot be replaced by " << *this);
+ Release(EndedByInvalidConferenceID);
+ return;
+ }
+ }
+ else {
+ if (replaces.Find(";early-only") != P_MAX_INDEX) {
+ PTRACE(3, "SIP\tReplaces has early-only on early connection " << *this);
+ Release(EndedByLocalBusy);
+ return;
+ }
+ }
- OnApplyStringOptions();
+ PTRACE(3, "SIP\tEstablished connection " << *replacedConnection << " replaced by " << *this);
- if (!ownerCall.OnSetUp(*this)) {
- PTRACE(1, "SIP\tOnSetUp failed for INVITE from " << request.GetURI() << " for " << *this);
- Release();
- return;
+ // Do OnRelease for other connection synchronously or there is
+ // confusion with media streams still open
+ replacedConnection->synchronousOnRelease = true;
+ replacedConnection->Release(OpalConnection::EndedByCallForwarded);
+
+ // Check if we are the target of an attended transfer, indicated by a referred-by header
+ PString referredBy = mime.GetReferredBy();
+ if (!referredBy.IsEmpty()) {
+ /* Indicate to application we are party C in a consultation transfer.
+ The calls are A->B (first call), B->C (consultation call) => A->C
+ (final call after the transfer) */
+ PStringToString info;
+ PCaselessString state = mime.GetSubscriptionState(info);
+ info.SetAt("party", "C");
+ info.SetAt("Referred-By", referredBy);
+ OnTransferNotify(info);
}
- AnsweringCall(OnAnswerCall(GetRemotePartyURL()));
+ /* According to RFC 3891 we now send a 200 OK in both the earl and confirmed
+ dialog cases. OnReleased() is responsible for if the replaced connection is
+ sent a BYE or a CANCEL. */
+ SetConnected();
}
@@ -2190,6 +2234,7 @@
PStringToString info;
PCaselessString state = mime.GetSubscriptionState(info);
+ info.SetAt("party", "B"); // We are B party in consultation transfer
info.SetAt("state", state);
info.SetAt("code", psprintf("%u", code));
info.SetAt("result", state != "terminated" || code < 200
This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site.
|