mirror of https://github.com/sipwise/sems.git
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
231 lines
7.3 KiB
231 lines
7.3 KiB
#include "Am100rel.h"
|
|
#include "AmConfig.h"
|
|
|
|
#include "AmUtils.h"
|
|
#include "AmSipHeaders.h"
|
|
#include "AmSession.h"
|
|
|
|
#include "log.h"
|
|
|
|
Am100rel::Am100rel(AmSipDialog* dlg, AmSipDialogEventHandler* hdl)
|
|
: dlg(dlg), hdl(hdl), reliable_1xx(AmConfig::rel100),
|
|
rseq(0), rseq_1st(0), rseq_confirmed(false)
|
|
{
|
|
// if (reliable_1xx)
|
|
// rseq = 0;
|
|
}
|
|
|
|
int Am100rel::onRequestIn(const AmSipRequest& req)
|
|
{
|
|
if (reliable_1xx == REL100_IGNORED)
|
|
return 1;
|
|
|
|
/* activate the 100rel, if needed */
|
|
if (req.method == SIP_METH_INVITE) {
|
|
switch(reliable_1xx) {
|
|
case REL100_SUPPORTED: /* if support is on, enforce if asked by UAC */
|
|
if (key_in_list(getHeader(req.hdrs, SIP_HDR_SUPPORTED, SIP_HDR_SUPPORTED_COMPACT),
|
|
SIP_EXT_100REL) ||
|
|
key_in_list(getHeader(req.hdrs, SIP_HDR_REQUIRE),
|
|
SIP_EXT_100REL)) {
|
|
reliable_1xx = REL100_REQUIRE;
|
|
DBG(SIP_EXT_100REL " now active.\n");
|
|
}
|
|
break;
|
|
|
|
case REL100_REQUIRE: /* if support is required, reject if UAC doesn't */
|
|
if (! (key_in_list(getHeader(req.hdrs,SIP_HDR_SUPPORTED, SIP_HDR_SUPPORTED_COMPACT),
|
|
SIP_EXT_100REL) ||
|
|
key_in_list(getHeader(req.hdrs, SIP_HDR_REQUIRE),
|
|
SIP_EXT_100REL))) {
|
|
ERROR("'" SIP_EXT_100REL "' extension required, but not advertised"
|
|
" by peer.\n");
|
|
AmBasicSipDialog::reply_error(req, 421, SIP_REPLY_EXTENSION_REQUIRED,
|
|
SIP_HDR_COLSP(SIP_HDR_REQUIRE)
|
|
SIP_EXT_100REL CRLF);
|
|
if (hdl) hdl->onFailure();
|
|
return 0; // has been replied
|
|
}
|
|
break; // 100rel required
|
|
|
|
case REL100_DISABLED:
|
|
// TODO: shouldn't this be part of a more general check in SEMS?
|
|
if (key_in_list(getHeader(req.hdrs,SIP_HDR_REQUIRE),SIP_EXT_100REL)) {
|
|
AmBasicSipDialog::reply_error(req, 420, SIP_REPLY_BAD_EXTENSION,
|
|
SIP_HDR_COLSP(SIP_HDR_UNSUPPORTED)
|
|
SIP_EXT_100REL CRLF);
|
|
if (hdl) hdl->onFailure();
|
|
return 0; // has been replied
|
|
}
|
|
break;
|
|
|
|
default:
|
|
ERROR("BUG: unexpected value `%d' for '" SIP_EXT_100REL "' switch.",
|
|
reliable_1xx);
|
|
#ifndef NDEBUG
|
|
abort();
|
|
#endif
|
|
} // switch reliable_1xx
|
|
} else if (req.method == SIP_METH_PRACK) {
|
|
if (reliable_1xx != REL100_REQUIRE) {
|
|
WARN("unexpected PRACK received while " SIP_EXT_100REL " not active.\n");
|
|
// let if float up
|
|
} else if (rseq_1st<=req.rseq && req.rseq<=rseq) {
|
|
if (req.rseq == rseq) {
|
|
rseq_confirmed = true; // confirmed
|
|
}
|
|
// else: confirmation for one of the pending 1xx
|
|
DBG("%sRSeq (%u) confirmed.\n", (req.rseq==rseq) ? "latest " : "", rseq);
|
|
}
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
int Am100rel::onReplyIn(const AmSipReply& reply)
|
|
{
|
|
if (reliable_1xx == REL100_IGNORED)
|
|
return 1;
|
|
|
|
if (dlg->getStatus() != AmSipDialog::Trying &&
|
|
dlg->getStatus() != AmSipDialog::Proceeding &&
|
|
dlg->getStatus() != AmSipDialog::Early &&
|
|
dlg->getStatus() != AmSipDialog::Connected)
|
|
return 1;
|
|
|
|
if (100<reply.code && reply.code<200 && reply.cseq_method==SIP_METH_INVITE) {
|
|
switch (reliable_1xx) {
|
|
case REL100_SUPPORTED:
|
|
if (key_in_list(getHeader(reply.hdrs, SIP_HDR_REQUIRE),
|
|
SIP_EXT_100REL))
|
|
reliable_1xx = REL100_REQUIRE;
|
|
// no break!
|
|
else
|
|
break;
|
|
|
|
case REL100_REQUIRE:
|
|
if (!key_in_list(getHeader(reply.hdrs,SIP_HDR_REQUIRE),SIP_EXT_100REL) ||
|
|
!reply.rseq) {
|
|
ERROR(SIP_EXT_100REL " not supported or no positive RSeq value in "
|
|
"(reliable) 1xx.\n");
|
|
dlg->bye();
|
|
if (hdl) hdl->onFailure();
|
|
} else {
|
|
DBG(SIP_EXT_100REL " now active.\n");
|
|
if (hdl) ((AmSipDialogEventHandler*)hdl)->onInvite1xxRel(reply);
|
|
}
|
|
break;
|
|
|
|
case REL100_DISABLED:
|
|
// 100rel support disabled
|
|
break;
|
|
default:
|
|
ERROR("BUG: unexpected value `%d' for " SIP_EXT_100REL " switch.",
|
|
reliable_1xx);
|
|
#ifndef NDEBUG
|
|
abort();
|
|
#endif
|
|
} // switch reliable 1xx
|
|
} else if (reliable_1xx && reply.cseq_method==SIP_METH_PRACK) {
|
|
if (300 <= reply.code) {
|
|
// if PRACK fails, tear down session
|
|
dlg->bye();
|
|
if (hdl) hdl->onFailure();
|
|
} else if (200 <= reply.code) {
|
|
if (hdl)
|
|
((AmSipDialogEventHandler*)hdl)->onPrack2xx(reply);
|
|
} else {
|
|
WARN("received '%d' for " SIP_METH_PRACK " method.\n", reply.code);
|
|
}
|
|
// absorbe the replys for the prack (they've been dispatched through
|
|
// onPrack2xx, if necessary)
|
|
return 0;
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
void Am100rel::onRequestOut(AmSipRequest& req)
|
|
{
|
|
if (reliable_1xx == REL100_IGNORED || req.method!=SIP_METH_INVITE)
|
|
return;
|
|
|
|
switch(reliable_1xx) {
|
|
case REL100_SUPPORTED:
|
|
if (! key_in_list(getHeader(req.hdrs, SIP_HDR_REQUIRE), SIP_EXT_100REL))
|
|
req.hdrs += SIP_HDR_COLSP(SIP_HDR_SUPPORTED) SIP_EXT_100REL CRLF;
|
|
break;
|
|
case REL100_REQUIRE:
|
|
if (! key_in_list(getHeader(req.hdrs, SIP_HDR_REQUIRE), SIP_EXT_100REL))
|
|
req.hdrs += SIP_HDR_COLSP(SIP_HDR_REQUIRE) SIP_EXT_100REL CRLF;
|
|
break;
|
|
default:
|
|
ERROR("BUG: unexpected reliability switch value of '%d'.\n",
|
|
reliable_1xx);
|
|
case 0:
|
|
break;
|
|
}
|
|
}
|
|
|
|
void Am100rel::onReplyOut(AmSipReply& reply)
|
|
{
|
|
if (reliable_1xx == REL100_IGNORED)
|
|
return;
|
|
|
|
if (reply.cseq_method == SIP_METH_INVITE) {
|
|
if (100 < reply.code && reply.code < 200) {
|
|
switch (reliable_1xx) {
|
|
case REL100_SUPPORTED:
|
|
if (! key_in_list(getHeader(reply.hdrs, SIP_HDR_REQUIRE),
|
|
SIP_EXT_100REL))
|
|
reply.hdrs += SIP_HDR_COLSP(SIP_HDR_SUPPORTED) SIP_EXT_100REL CRLF;
|
|
break;
|
|
case REL100_REQUIRE:
|
|
// add Require HF
|
|
if (! key_in_list(getHeader(reply.hdrs, SIP_HDR_REQUIRE),
|
|
SIP_EXT_100REL))
|
|
reply.hdrs += SIP_HDR_COLSP(SIP_HDR_REQUIRE) SIP_EXT_100REL CRLF;
|
|
// add RSeq HF
|
|
if (getHeader(reply.hdrs, SIP_HDR_RSEQ).length())
|
|
// already added (by app?)
|
|
break;
|
|
if (! rseq) { // only init rseq if 1xx is used
|
|
rseq = (get_random() & 0x3ff) + 1; // start small (<1024) and non-0
|
|
rseq_confirmed = false;
|
|
rseq_1st = rseq;
|
|
} else {
|
|
if ((! rseq_confirmed) && (rseq_1st == rseq))
|
|
// refuse subsequent 1xx if first isn't yet PRACKed
|
|
throw AmSession::Exception(491, "first reliable 1xx not yet "
|
|
"PRACKed");
|
|
rseq ++;
|
|
}
|
|
reply.hdrs += SIP_HDR_COLSP(SIP_HDR_RSEQ) + int2str(rseq) + CRLF;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
} else if (reply.code < 300 && reliable_1xx == REL100_REQUIRE) { //code = 2xx
|
|
if (rseq && !rseq_confirmed)
|
|
// reliable 1xx is pending, 2xx'ing not allowed yet
|
|
throw AmSession::Exception(491, "last reliable 1xx not yet PRACKed");
|
|
}
|
|
}
|
|
}
|
|
|
|
void Am100rel::onTimeout(const AmSipRequest& req, const AmSipReply& rpl)
|
|
{
|
|
if (reliable_1xx == REL100_IGNORED)
|
|
return;
|
|
|
|
INFO("reply <%s> timed out (not PRACKed).\n", rpl.print().c_str());
|
|
if (100 < rpl.code && rpl.code < 200 && reliable_1xx == REL100_REQUIRE &&
|
|
rseq == rpl.rseq && rpl.cseq_method == SIP_METH_INVITE) {
|
|
INFO("reliable %d reply timed out; rejecting request.\n", rpl.code);
|
|
if(hdl) hdl->onNoPrack(req, rpl);
|
|
} else {
|
|
WARN("reply timed-out, but not reliable.\n"); // debugging
|
|
}
|
|
}
|
|
|
|
|