Added methods making possible to handle forked INVITEs.

git-svn-id: http://yate.null.ro/svn/yate/trunk@610 acf43c95-373e-0410-b603-e72c3f656dc1
This commit is contained in:
paulc 2005-12-15 20:52:36 +00:00
parent 485642968d
commit 0cd3df50ea
4 changed files with 146 additions and 79 deletions

View File

@ -237,7 +237,8 @@ SIPEngine::SIPEngine(const char* userAgent)
m_t1(500000), m_t4(5000000), m_maxForwards(70),
m_cseq(0), m_userAgent(userAgent), m_nonce_time(0)
{
DDebug(DebugInfo,"SIPEngine::SIPEngine() [%p]",this);
debugName("sipengine");
DDebug(this,DebugInfo,"SIPEngine::SIPEngine() [%p]",this);
if (m_userAgent.null())
m_userAgent << "YATE/" << YATE_VERSION;
m_allowed = "ACK";
@ -248,12 +249,12 @@ SIPEngine::SIPEngine(const char* userAgent)
SIPEngine::~SIPEngine()
{
DDebug(DebugInfo,"SIPEngine::~SIPEngine() [%p]",this);
DDebug(this,DebugInfo,"SIPEngine::~SIPEngine() [%p]",this);
}
SIPTransaction* SIPEngine::addMessage(SIPParty* ep, const char* buf, int len)
{
DDebug("SIPEngine",DebugInfo,"addMessage(%p,%d) [%p]",buf,len,this);
DDebug(this,DebugInfo,"addMessage(%p,%d) [%p]",buf,len,this);
SIPMessage* msg = SIPMessage::fromParsing(ep,buf,len);
if (ep)
ep->deref();
@ -267,7 +268,7 @@ SIPTransaction* SIPEngine::addMessage(SIPParty* ep, const char* buf, int len)
SIPTransaction* SIPEngine::addMessage(SIPMessage* message)
{
DDebug("SIPEngine",DebugInfo,"addMessage(%p) [%p]",message,this);
DDebug(this,DebugInfo,"addMessage(%p) [%p]",message,this);
if (!message)
return 0;
// make sure outgoing messages are well formed
@ -280,30 +281,51 @@ SIPTransaction* SIPEngine::addMessage(SIPMessage* message)
if (!branch.startsWith("z9hG4bK"))
branch.clear();
Lock lock(m_mutex);
SIPTransaction* forked = 0;
ObjList* l = &TransList;
for (; l; l = l->next()) {
SIPTransaction* t = static_cast<SIPTransaction*>(l->get());
if (t && t->processMessage(message,branch))
return t;
if (!t)
continue;
switch (t->processMessage(message,branch)) {
case SIPTransaction::Matched:
return t;
case SIPTransaction::NoDialog:
forked = t;
break;
case SIPTransaction::NoMatch:
default:
break;
}
}
if (forked)
return forkInvite(message,forked);
if (message->isAnswer()) {
Debug("SIPEngine",DebugInfo,"Message %p was an unhandled answer [%p]",message,this);
Debug(this,DebugInfo,"Message %p was an unhandled answer [%p]",message,this);
return 0;
}
if (message->isACK()) {
DDebug("SIPEngine",DebugAll,"Message %p was an unhandled ACK [%p]",message,this);
DDebug(this,DebugAll,"Message %p was an unhandled ACK [%p]",message,this);
return 0;
}
message->complete(this);
return new SIPTransaction(message,this,message->isOutgoing());
}
SIPTransaction* SIPEngine::forkInvite(SIPMessage* answer, const SIPTransaction* trans)
{
// TODO: build new transaction or CANCEL
Debug(this,DebugInfo,"Message %p was a forked INVITE answer [%p]",answer,this);
return 0;
}
bool SIPEngine::process()
{
SIPEvent* e = getEvent();
if (!e)
return false;
DDebug("SIPEngine",DebugInfo,"process() got event %p",e);
DDebug(this,DebugInfo,"process() got event %p",e);
processEvent(e);
return true;
}
@ -317,7 +339,7 @@ SIPEvent* SIPEngine::getEvent()
if (t) {
SIPEvent* e = t->getEvent();
if (e) {
DDebug("SIPEngine",DebugInfo,"Got event %p (state %s) from transaction %p [%p]",
DDebug(this,DebugInfo,"Got event %p (state %s) from transaction %p [%p]",
e,SIPTransaction::stateName(e->getState()),t,this);
return e;
}
@ -336,7 +358,7 @@ void SIPEngine::processEvent(SIPEvent *event)
type = "outgoing";
if (event->isIncoming())
type = "incoming";
DDebug("SIPEngine",DebugAll,"Processing %s event %p message %p [%p]",
DDebug(this,DebugAll,"Processing %s event %p message %p [%p]",
type,event,event->getMessage(),this);
if (event->getMessage()) {
if (event->isOutgoing()) {
@ -354,7 +376,7 @@ void SIPEngine::processEvent(SIPEvent *event)
if (event->isIncoming()) {
if ((event->getState() == SIPTransaction::Trying) &&
!event->getMessage()->isAnswer()) {
Debug("SIPEngine",DebugInfo,"Rejecting unhandled request '%s' in event %p [%p]",
Debug(this,DebugInfo,"Rejecting unhandled request '%s' in event %p [%p]",
event->getMessage()->method.c_str(),event,this);
event->getTransaction()->setResponse(405);
}
@ -416,7 +438,7 @@ u_int64_t SIPEngine::getTimer(char which, bool reliable) const
// K: Wait time for response retransmits
return reliable ? 0 : m_t4;
}
Debug("SIPEngine",DebugMild,"Requested invalid timer '%c' [%p]",which,this);
Debug(this,DebugMild,"Requested invalid timer '%c' [%p]",which,this);
return 0;
}
@ -431,7 +453,7 @@ void SIPEngine::nonceGet(String& nonce)
MD5 md5(tmp);
m_nonce = md5.hexDigest();
m_nonce << "." << t;
XDebug("SIPEngine",DebugAll,"Generated new nonce '%s' [%p]",
XDebug(this,DebugAll,"Generated new nonce '%s' [%p]",
m_nonce.c_str(),this);
}
nonce = m_nonce;
@ -473,7 +495,7 @@ bool SIPEngine::checkUser(const String& username, const String& realm, const Str
void SIPEngine::buildAuth(const String& username, const String& realm, const String& passwd,
const String& nonce, const String& method, const String& uri, String& response)
{
XDebug("SIPEngine",DebugAll,"Building auth: '%s:%s:%s' '%s' '%s:%s'",
XDebug(this,DebugAll,"Building auth: '%s:%s:%s' '%s' '%s:%s'",
username.c_str(),realm.c_str(),passwd.c_str(),nonce.c_str(),method.c_str(),uri.c_str());
MD5 m1,m2;
m1 << username << ":" << realm << ":" << passwd;
@ -508,7 +530,7 @@ int SIPEngine::authUser(const SIPMessage* message, String& user, bool proxy)
delQuotes(usr);
if (usr.null())
continue;
XDebug("SIPEngine",DebugAll,"authUser found user '%s'",usr.c_str());
XDebug(this,DebugAll,"authUser found user '%s'",usr.c_str());
// if we know the username check if it matches
if (user && (usr != user))
continue;
@ -521,7 +543,7 @@ int SIPEngine::authUser(const SIPMessage* message, String& user, bool proxy)
long age = nonceAge(nonce);
if (age < 0)
continue;
XDebug("SIPEngine",DebugAll,"authUser nonce age is %ld",age);
XDebug(this,DebugAll,"authUser nonce age is %ld",age);
String res(t->getParam("response"));
delQuotes(res);
if (res.null())

View File

@ -375,7 +375,7 @@ SIPMessage::~SIPMessage()
void SIPMessage::complete(SIPEngine* engine, const char* user, const char* domain, const char* dlgTag)
{
DDebug("SIPMessage",DebugAll,"complete(%p,'%s','%s','%s')%s%s%s [%p]",
DDebug(engine,DebugAll,"SIPMessage::complete(%p,'%s','%s','%s')%s%s%s [%p]",
engine,user,domain,dlgTag,
isACK() ? " ACK" : "",
isOutgoing() ? " OUT" : "",
@ -391,7 +391,7 @@ void SIPMessage::complete(SIPEngine* engine, const char* user, const char* domai
if (!getParty()) {
engine->buildParty(this);
if (!getParty()) {
Debug(DebugGoOn,"Could not complete party-less SIP message [%p]",this);
Debug(engine,DebugGoOn,"Could not complete party-less SIP message [%p]",this);
return;
}
}

View File

@ -33,7 +33,7 @@ SIPTransaction::SIPTransaction(SIPMessage* message, SIPEngine* engine, bool outg
: m_outgoing(outgoing), m_invite(false), m_transmit(false), m_state(Invalid), m_response(0), m_timeout(0),
m_firstMessage(message), m_lastMessage(0), m_pending(0), m_engine(engine), m_private(0)
{
DDebug(DebugAll,"SIPTransaction::SIPTransaction(%p,%p,%d) [%p]",
DDebug(getEngine(),DebugAll,"SIPTransaction::SIPTransaction(%p,%p,%d) [%p]",
message,engine,outgoing,this);
if (m_firstMessage) {
m_firstMessage->ref();
@ -77,7 +77,7 @@ SIPTransaction::SIPTransaction(SIPTransaction& original, SIPMessage* answer)
m_branch(original.m_branch), m_callid(original.m_callid), m_tag(original.m_tag),
m_private(0)
{
DDebug(DebugAll,"SIPTransaction::SIPTransaction(&%p,%p) [%p]",
DDebug(getEngine(),DebugAll,"SIPTransaction::SIPTransaction(&%p,%p) [%p]",
&original,answer,this);
SIPMessage* msg = new SIPMessage(*original.m_firstMessage);
@ -101,6 +101,20 @@ SIPTransaction::SIPTransaction(SIPTransaction& original, SIPMessage* answer)
m_engine->TransList.append(this);
}
SIPTransaction::SIPTransaction(const SIPTransaction& original, const String& tag)
: m_outgoing(true), m_invite(original.m_invite), m_transmit(false),
m_state(Process), m_response(original.m_response), m_timeout(0),
m_firstMessage(original.m_firstMessage), m_lastMessage(0),
m_pending(0), m_engine(original.m_engine),
m_branch(original.m_branch), m_callid(original.m_callid), m_tag(tag),
m_private(0)
{
if (m_firstMessage)
m_firstMessage->ref();
m_engine->TransList.append(this);
}
SIPTransaction::~SIPTransaction()
{
#ifdef DEBUG
@ -149,10 +163,10 @@ bool SIPTransaction::changeState(int newstate)
if ((newstate < 0) || (newstate == m_state))
return false;
if (m_state == Invalid) {
Debug(DebugGoOn,"SIPTransaction is already invalid [%p]",this);
Debug(getEngine(),DebugGoOn,"SIPTransaction is already invalid [%p]",this);
return false;
}
DDebug(DebugAll,"SIPTransaction state changed from %s to %s [%p]",
DDebug(getEngine(),DebugAll,"SIPTransaction state changed from %s to %s [%p]",
stateName(m_state),stateName(newstate),this);
m_state = newstate;
return true;
@ -172,7 +186,7 @@ void SIPTransaction::setLatestMessage(SIPMessage* message)
{
if (m_lastMessage == message)
return;
DDebug(DebugAll,"SIPTransaction latest message changing from %p %d to %p %d [%p]",
DDebug(getEngine(),DebugAll,"SIPTransaction latest message changing from %p %d to %p %d [%p]",
m_lastMessage, m_lastMessage ? m_lastMessage->code : 0,
message, message ? message->code : 0, this);
if (m_lastMessage)
@ -209,7 +223,7 @@ void SIPTransaction::setTimeout(u_int64_t delay, unsigned int count)
m_timeout = (count && delay) ? Time::now() + delay : 0;
#ifdef DEBUG
if (m_timeout)
Debug(DebugAll,"SIPTransaction new %d timeouts initially " FMT64U " usec apart [%p]",
Debug(getEngine(),DebugAll,"SIPTransaction new %d timeouts initially " FMT64U " usec apart [%p]",
m_timeouts,m_delay,this);
#endif
}
@ -234,7 +248,7 @@ SIPEvent* SIPTransaction::getEvent()
timeout = --m_timeouts;
m_timeout = (m_timeouts) ? Time::now() + m_delay : 0;
m_delay *= 2; // exponential back-off
DDebug(DebugAll,"SIPTransaction fired timer #%d [%p]",timeout,this);
DDebug(getEngine(),DebugAll,"SIPTransaction fired timer #%d [%p]",timeout,this);
}
e = isOutgoing() ? getClientEvent(m_state,timeout) : getServerEvent(m_state,timeout);
@ -261,7 +275,7 @@ SIPEvent* SIPTransaction::getEvent()
m_engine->TransList.remove(this);
return e;
case Invalid:
Debug(DebugFail,"SIPTransaction::getEvent in invalid state [%p]",this);
Debug(getEngine(),DebugFail,"SIPTransaction::getEvent in invalid state [%p]",this);
break;
}
return e;
@ -270,7 +284,7 @@ SIPEvent* SIPTransaction::getEvent()
void SIPTransaction::setResponse(SIPMessage* message)
{
if (m_outgoing) {
Debug(DebugWarn,"SIPTransaction::setResponse(%p) in client mode [%p]",message,this);
Debug(getEngine(),DebugWarn,"SIPTransaction::setResponse(%p) in client mode [%p]",message,this);
return;
}
Lock lock(mutex());
@ -294,7 +308,7 @@ void SIPTransaction::setResponse(SIPMessage* message)
bool SIPTransaction::setResponse(int code, const char* reason)
{
if (m_outgoing) {
Debug(DebugWarn,"SIPTransaction::setResponse(%d,'%s') in client mode [%p]",code,reason,this);
Debug(getEngine(),DebugWarn,"SIPTransaction::setResponse(%d,'%s') in client mode [%p]",code,reason,this);
return false;
}
switch (m_state) {
@ -302,7 +316,7 @@ bool SIPTransaction::setResponse(int code, const char* reason)
case Retrans:
case Finish:
case Cleared:
DDebug(DebugInfo,"SIPTransaction ignoring setResponse(%d) in state %s [%p]",
DDebug(getEngine(),DebugInfo,"SIPTransaction ignoring setResponse(%d) in state %s [%p]",
code,stateName(m_state),this);
return false;
}
@ -317,7 +331,7 @@ bool SIPTransaction::setResponse(int code, const char* reason)
void SIPTransaction::requestAuth(const String& realm, const String& domain, bool stale, bool proxy)
{
if (m_outgoing) {
Debug(DebugWarn,"SIPTransaction::requestAuth() in client mode [%p]",this);
Debug(getEngine(),DebugWarn,"SIPTransaction::requestAuth() in client mode [%p]",this);
return;
}
switch (m_state) {
@ -325,7 +339,7 @@ void SIPTransaction::requestAuth(const String& realm, const String& domain, bool
case Retrans:
case Finish:
case Cleared:
DDebug(DebugInfo,"SIPTransaction ignoring requestAuth() in state %s [%p]",
DDebug(getEngine(),DebugInfo,"SIPTransaction ignoring requestAuth() in state %s [%p]",
stateName(m_state),this);
return;
}
@ -355,46 +369,46 @@ int SIPTransaction::authUser(String& user, bool proxy)
return m_engine->authUser(m_firstMessage, user, proxy);
}
bool SIPTransaction::processMessage(SIPMessage* message, const String& branch)
SIPTransaction::Processed SIPTransaction::processMessage(SIPMessage* message, const String& branch)
{
if (!(message && m_firstMessage))
return false;
DDebug("SIPTransaction",DebugAll,"processMessage(%p,'%s') [%p]",
return NoMatch;
DDebug(getEngine(),DebugAll,"SIPTransaction::processMessage(%p,'%s') [%p]",
message,branch.c_str(),this);
if (branch) {
if (branch != m_branch) {
// different branch is allowed only for ACK in incoming INVITE...
if (!(isInvite() && isIncoming() && message->isACK()))
return false;
return NoMatch;
// ...and only if we sent a 200 response...
if (!m_lastMessage || ((m_lastMessage->code / 100) != 2))
return false;
return NoMatch;
// ...and if also matches the CSeq, Call-ID and To: tag
if ((m_firstMessage->getCSeq() != message->getCSeq()) ||
(getCallID() != message->getHeaderValue("Call-ID")) ||
(getDialogTag() != message->getParamValue("To","tag")))
return false;
DDebug(DebugAll,"SIPTransaction found non-branch ACK response to our 2xx");
return NoMatch;
DDebug(getEngine(),DebugAll,"SIPTransaction found non-branch ACK response to our 2xx");
}
else if (getMethod() != message->method) {
if (!(isIncoming() && isInvite() && message->isACK()))
return false;
return NoMatch;
}
}
else {
if (getMethod() != message->method) {
if (!(isIncoming() && isInvite() && message->isACK()))
return false;
return NoMatch;
}
if ((m_firstMessage->getCSeq() != message->getCSeq()) ||
(getCallID() != message->getHeaderValue("Call-ID")) ||
(m_firstMessage->getHeaderValue("From") != message->getHeaderValue("From")) ||
(m_firstMessage->getHeaderValue("To") != message->getHeaderValue("To")))
return false;
return NoMatch;
// allow braindamaged UAs that send answers with no Via line
if (m_firstMessage->getHeader("Via") && message->getHeader("Via") &&
(m_firstMessage->getHeaderValue("Via") != message->getHeaderValue("Via")))
return false;
return NoMatch;
// extra checks are to be made for ACK only
if (message->isACK()) {
// Hack to match URIs with lost tags. Cisco sucks. Period.
@ -403,29 +417,42 @@ bool SIPTransaction::processMessage(SIPMessage* message, const String& branch)
if (sc > 0)
tmp.assign(tmp,sc);
if ((getURI() != message->uri) && (tmp != message->uri))
return false;
return NoMatch;
if (getDialogTag() != message->getParamValue("To","tag"))
return false;
return NoMatch;
}
}
if (!message->getParty())
message->setParty(m_firstMessage->getParty());
if (isOutgoing() != message->isAnswer()) {
DDebug(DebugAll,"SIPTransaction ignoring retransmitted %s %p '%s' in [%p]",
DDebug(getEngine(),DebugAll,"SIPTransaction ignoring retransmitted %s %p '%s' in [%p]",
message->isAnswer() ? "answer" : "request",
message,message->method.c_str(),this);
return false;
return NoMatch;
}
DDebug(DebugAll,"SIPTransaction processing %s %p '%s' in [%p]",
DDebug(getEngine(),DebugAll,"SIPTransaction processing %s %p '%s' in [%p]",
message->isAnswer() ? "answer" : "request",
message,message->method.c_str(),this);
if (m_tag.null() && message->isAnswer()) {
if (message->isAnswer()) {
const NamedString* ns = message->getParam("To","tag");
if (ns) {
m_tag = *ns;
DDebug(DebugInfo,"SIPTransaction found dialog tag '%s' [%p]",
m_tag.c_str(),this);
if (m_tag.null()) {
if (ns) {
// establish the dialog
m_tag = *ns;
DDebug(getEngine(),DebugInfo,"SIPTransaction found dialog tag '%s' [%p]",
m_tag.c_str(),this);
}
}
else if (!ns) {
// we have a dialog and the message has not - ignore it
// as we would be unable to CANCEL it anyway
return NoMatch;
}
else if (m_tag != *ns) {
// we have a dialog established and this message is out of it
// discriminate forked answers to INVITEs for later processing
return isInvite() ? NoDialog : NoMatch;
}
}
@ -433,7 +460,7 @@ bool SIPTransaction::processMessage(SIPMessage* message, const String& branch)
processClientMessage(message,m_state);
else
processServerMessage(message,m_state);
return true;
return Matched;
}
void SIPTransaction::processClientMessage(SIPMessage* message, int state)

View File

@ -549,41 +549,44 @@ public:
class YSIP_API SIPTransaction : public RefObject
{
public:
/**
* Current state of the transaction
*/
enum State {
/**
* Invalid state - before constructor or after destructor
*/
// Invalid state - before constructor or after destructor
Invalid,
/**
* Initial state - after the initial message was inserted
*/
// Initial state - after the initial message was inserted
Initial,
/**
* Trying state - got the message but no decision made yet
*/
// Trying state - got the message but no decision made yet
Trying,
/**
* Process state - while locally processing the event
*/
// Process state - while locally processing the event
Process,
/**
* Retrans state - waiting for cleanup, retransmits latest message
*/
// Retrans state - waiting for cleanup, retransmits latest message
Retrans,
/**
* Finish state - transmits the last message and goes to Retrans
*/
// Finish state - transmits the last message and goes to Retrans
Finish,
/**
* Cleared state - removed from engine, awaiting destruction
*/
Cleared,
// Cleared state - removed from engine, awaiting destruction
Cleared
};
/**
* Possible return values from @ref processMessage()
*/
enum Processed {
// Not matched at all
NoMatch,
// Belongs to another dialog - probably result of a fork
NoDialog,
// Matched to transaction/dialog and processed
Matched
};
/**
@ -595,6 +598,13 @@ public:
*/
SIPTransaction(SIPMessage* message, SIPEngine* engine, bool outgoing = true);
/**
* Copy constructor to be used with forked INVITEs
* @param original Original transaction that is to be copied
* @param tag Dialog tag for the new transaction
*/
SIPTransaction(const SIPTransaction& original, const String& tag);
/**
* Destructor - clears all held objects
*/
@ -739,10 +749,10 @@ public:
* @param message A pointer to the message to check, should not be used
* afterwards if this method returned True
* @param branch The branch parameter extracted from first Via header
* @return True if the message was handled by this transaction, in
* @return Matched if the message was handled by this transaction, in
* which case it takes ownership over the message
*/
virtual bool processMessage(SIPMessage* message, const String& branch);
virtual Processed processMessage(SIPMessage* message, const String& branch);
/**
* Get an event for this transaction if any is available.
@ -960,7 +970,7 @@ protected:
/**
* This object can be one for each SIPListener.
*/
class YSIP_API SIPEngine
class YSIP_API SIPEngine : public DebugEnabler
{
public:
/**
@ -1044,6 +1054,14 @@ public:
*/
virtual void processEvent(SIPEvent *event);
/**
* Handle answers that create new dialogs for an outgoing INVITE
* @param answer The message that creates the INVITE fork
* @param trans One of the transactions part of the same INVITE
* @return Pointer to new transaction or NULL if message is ignored
*/
virtual SIPTransaction* forkInvite(SIPMessage* answer, const SIPTransaction* trans);
/**
* Get the timeout to be used for transactions involving human interaction.
* The default implementation returns 120000000 (2 minutes)