#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 (100bye(); 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 } }