/* * Copyright (C) 2012 Frafos GmbH * * This file is part of SEMS, a free SIP media server. * * SEMS is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. This program is released under * the GPL with the additional exemption that compiling, linking, * and/or using OpenSSL is allowed. * * For a license to use the SEMS software under conditions * other than those described here, or to purchase support for this * software, please contact iptel.org by e-mail at the following addresses: * info@iptel.org * * SEMS is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "AmSipSubscription.h" #include "AmEventQueue.h" #include "AmSipHeaders.h" #include "AmAppTimer.h" #include "AmUtils.h" #include "jsonArg.h" #include "AmSession.h" // getNewId() #include "AmSessionContainer.h" #include "sip/sip_timers.h" #include "log.h" #include #define DEFAULT_SUB_EXPIRES 600 // TIMER N should first expire once transaction timer has hit // in case we receive no reply to SUBSCRIBE. #define RFC6665_TIMER_N_DURATION ((64 + 4)*T1_TIMER)/1000.0 #define SIP_HDR_SUBSCRIPTION_STATE "Subscription-State" #define SIP_HDR_EVENT "Event" const char* __timer_id_str[2] = { "RFC6665_TIMER_N", "SUBSCRIPTION_EXPIRE" }; const char* __sub_state_str[] = { "init", "notify_wait", "pending", "active", "terminated" }; SingleSubscription::SingleSubscription(AmSipSubscription* subs, Role role, const string& event, const string& id) : subs(subs), role(role), event(event), id(id), sub_state(SubState_init), pending_subscribe(0), expires(0), timer_n(this,RFC6665_TIMER_N),timer_expires(this,SUBSCRIPTION_EXPIRE) { assert(subs); } AmBasicSipDialog* SingleSubscription::dlg() { return subs->dlg; } void SingleSubscription::onTimer(int timer_id) { DBG("[%p] tag=%s;role=%s timer_id = %s\n",this, dlg()->getLocalTag().c_str(), role ? "Notifier" : "Subscriber", __timer_id_str[timer_id]); switch(timer_id){ case RFC6665_TIMER_N: case SUBSCRIPTION_EXPIRE: if(subs->ev_q) { AmEvent* ev = new SingleSubTimeoutEvent(subs->dlg->getLocalTag(), timer_id,this); subs->ev_q->postEvent(ev); } return; } } void SingleSubscription::terminate() { setState(SubState_terminated); } bool SingleSubscription::terminated() { return getState() == SubState_terminated; } SingleSubscription* AmSipSubscription::newSingleSubscription(SingleSubscription::Role role, const string& event, const string& id) { return new SingleSubscription(this,role,event,id); } SingleSubscription* AmSipSubscription::makeSubscription(const AmSipRequest& req, bool uac) { string event; string id; SingleSubscription::Role role = uac ? SingleSubscription::Subscriber : SingleSubscription::Notifier; /* SUBSCRIBE */ if (req.method == SIP_METH_SUBSCRIBE) { // fetch Event-HF event = getHeader(req.hdrs,SIP_HDR_EVENT,true); id = get_header_param(event,"id"); event = strip_header_params(event); /* REFER */ } else if(req.method == SIP_METH_REFER) { //TODO: fetch Refer-Sub-HF (RFC 4488) event = "refer"; id = int2str(req.cseq); /* other */ } else { DBG("subscription are only created by SUBSCRIBE or REFER requests. This one: '%s'\n", req.method.c_str()); // subscription are only created by SUBSCRIBE or REFER requests // and we do not support unsolicited NOTIFYs return NULL; } return newSingleSubscription(role,event,id); } SingleSubscription::~SingleSubscription() { // just to be sure... AmAppTimer::instance()->removeTimer(&timer_n); // this one should still be active AmAppTimer::instance()->removeTimer(&timer_expires); } void SingleSubscription::requestFSM(const AmSipRequest& req) { if((req.method == SIP_METH_SUBSCRIBE) || (req.method == SIP_METH_REFER)) { if(getState() == SubState_init) { setState(SubState_notify_wait); } // start Timer N (RFC6665/4.1.2) DBG("setTimer(%s,RFC6665_TIMER_N)\n",dlg()->getLocalTag().c_str()); AmAppTimer::instance()->setTimer(&timer_n,RFC6665_TIMER_N_DURATION); } else if(req.method == SIP_METH_NOTIFY) { subs->onNotify(req,this); } } bool SingleSubscription::onRequestIn(const AmSipRequest& req) { if((req.method == SIP_METH_SUBSCRIBE) || (req.method == SIP_METH_REFER)) { if(pending_subscribe) { dlg()->reply(req,500, SIP_REPLY_SERVER_INTERNAL_ERROR, NULL, SIP_HDR_COLSP(SIP_HDR_RETRY_AFTER) + int2str(get_random() % 10) + CRLF); return false; } pending_subscribe++; } requestFSM(req); return true; } void SingleSubscription::onRequestSent(const AmSipRequest& req) { //TODO: check pending_subscribe in onSendRequest if((req.method == SIP_METH_SUBSCRIBE) || (req.method == SIP_METH_REFER)) { pending_subscribe++; } requestFSM(req); } void SingleSubscription::replyFSM(const AmSipRequest& req, const AmSipReply& reply) { if(reply.code < 200) return; if((req.method == SIP_METH_SUBSCRIBE) || (req.method == SIP_METH_REFER)) { // final reply if(reply.code >= 300) { if(getState() == SubState_notify_wait) { // initial SUBSCRIBE failed terminate(); subs->onFailureReply(reply,this); } else { // subscription refresh failed // from RFC 5057: terminate usage switch(reply.code){ case 405: case 489: case 481: case 501: terminate(); subs->onFailureReply(reply,this); break; } } } else { // success // set dialog identifier if not yet set if(dlg()->getRemoteTag().empty()) { dlg()->setRemoteTag(reply.to_tag); dlg()->setRouteSet(reply.route); } // check Expires-HF string expires_txt = getHeader(reply.hdrs,SIP_HDR_EXPIRES,true); expires_txt = strip_header_params(expires_txt); int sub_expires=0; if(!expires_txt.empty() && str2int(expires_txt,sub_expires)){ if(sub_expires){ DBG("setTimer(%s,SUBSCRIPTION_EXPIRE)\n",dlg()->getLocalTag().c_str()); AmAppTimer::instance()->setTimer(&timer_expires,(double)sub_expires); expires = sub_expires + AmAppTimer::instance()->unix_clock.get(); DBG("removeTimer(%s,RFC6665_TIMER_N)\n",dlg()->getLocalTag().c_str()); AmAppTimer::instance()->removeTimer(&timer_n); } else { // we do not care too much, as timer N is set // for each SUBSCRIBE request DBG("Expires-HF equals 0\n"); } } else if(reply.cseq_method == SIP_METH_SUBSCRIBE){ // Should we really enforce that? // -> we still have timer N... // replies to SUBSCRIBE MUST contain a Expires-HF // if not, or if not readable, we should probably // quit the subscription DBG("replies to SUBSCRIBE MUST contain a Expires-HF\n"); terminate(); subs->onFailureReply(reply,this); } } pending_subscribe--; } else if(reply.cseq_method == SIP_METH_NOTIFY) { if(reply.code >= 300) { // final error reply // from RFC 5057: terminate usage switch(reply.code){ case 405: case 481: case 489: case 501: terminate(); subs->onFailureReply(reply,this); break; default: // all other response codes: // only the transaction fails break; } return; } // check Subscription-State-HF string sub_state_txt = getHeader(req.hdrs,SIP_HDR_SUBSCRIPTION_STATE,true); string expires_txt = get_header_param(sub_state_txt,"expires"); int notify_expire=0; if(!expires_txt.empty()) str2int(expires_txt,notify_expire); // Kill timer N DBG("removeTimer(%s,RFC6665_TIMER_N)\n",dlg()->getLocalTag().c_str()); AmAppTimer::instance()->removeTimer(&timer_n); sub_state_txt = strip_header_params(sub_state_txt); if(notify_expire && (sub_state_txt == "active")) { setState(SubState_active); } else if(notify_expire && (sub_state_txt == "pending")){ setState(SubState_pending); } else { terminate(); //subs->onFailureReply(reply,this); return; } // reset expire timer DBG("setTimer(%s,SUBSCRIPTION_EXPIRE)\n",dlg()->getLocalTag().c_str()); AmAppTimer::instance()->setTimer(&timer_expires,(double)notify_expire); expires = notify_expire + AmAppTimer::instance()->unix_clock.get(); } return; } void SingleSubscription::setExpires(unsigned long exp) { double notify_expire = exp - AmAppTimer::instance()->unix_clock.get(); if(notify_expire > 0.0) { AmAppTimer::instance()->setTimer(&timer_expires,notify_expire); expires = exp; } else { DBG("new 'expires' is already expired: sending event"); onTimer(SUBSCRIPTION_EXPIRE); } } void SingleSubscription::setState(unsigned int st) { DBG("st = %s\n",__sub_state_str[st]); if(sub_state == SubState_terminated) return; if(st == SubState_terminated) { sub_state = SubState_terminated; dlg()->decUsages(); } else { sub_state = st; } } string SingleSubscription::to_str() { return "[" + str2json(event) + "," + str2json(id) + "," + (role == Subscriber ? str2json("SUB") : str2json("NOT")) + "," + str2json(__sub_state_str[sub_state]) + "]"; } /** * AmSipSubscription */ AmSipSubscription::AmSipSubscription(AmBasicSipDialog* dlg, AmEventQueue* ev_q) : dlg(dlg), ev_q(ev_q) { assert(dlg); } AmSipSubscription::~AmSipSubscription() { while(!subs.empty()) { DBG("removing single subscription"); removeSubscription(subs.begin()); } } bool AmSipSubscription::isActive() { for(Subscriptions::iterator it=subs.begin(); it != subs.end(); it++) { if((*it)->getState() == SingleSubscription::SubState_active) return true; } return false; } void AmSipSubscription::terminate() { for(Subscriptions::iterator it=subs.begin(); it != subs.end(); it++) { (*it)->terminate(); } } bool AmSipSubscription::subscriptionExists(SingleSubscription::Role role, const string& event, const string& id) { return findSubscription(role,event,id) != subs.end(); } AmSipSubscription::Subscriptions::iterator AmSipSubscription::findSubscription(SingleSubscription::Role role, const string& event, const string& id) { Subscriptions::iterator match = subs.end(); bool no_id = id.empty() && (event == "refer"); DBG("searching for event='%s'; id='%s'; no_id=%i", event.c_str(),id.c_str(),no_id); for(Subscriptions::iterator it = subs.begin(); it != subs.end(); it++) { SingleSubscription* sub = *it; DBG("role='%s';event='%s';id='%s'", sub->role ? "Notifier" : "Subscriber", sub->event.c_str(), sub->id.c_str()); if( (sub->role == role) && (sub->event == event) && (no_id || (sub->id == id)) ){ match = it; DBG("\tmatched!"); break; } } if((match != subs.end()) && (*match)->terminated()) { DBG("matched terminated subscription: deleting it first\n"); removeSubscription(match); match = subs.end(); } return match; } AmSipSubscription::Subscriptions::iterator AmSipSubscription::createSubscription(const AmSipRequest& req, bool uac) { SingleSubscription* sub = makeSubscription(req,uac); if(!sub){ return subs.end(); } dlg->incUsages(); DBG("new subscription: %s",sub->to_str().c_str()); return subs.insert(subs.end(),sub); } void AmSipSubscription::removeSubFromUACCSeqMap(Subscriptions::iterator sub) { for (CSeqMap::iterator i = uac_cseq_map.begin(); i != uac_cseq_map.end();) { if (i->second == sub) { DBG("removing UAC subnot transaction with cseq=%i",i->first); CSeqMap::iterator del_i = i; ++i; uac_cseq_map.erase(del_i); continue; } ++i; } } void AmSipSubscription::removeSubFromUASCSeqMap(Subscriptions::iterator sub) { for (CSeqMap::iterator i = uas_cseq_map.begin(); i != uas_cseq_map.end();) { if (i->second == sub) { unsigned int cseq = i->first; CSeqMap::iterator del_i = i; ++i; uas_cseq_map.erase(del_i); DBG("removed UAS subnot transaction with cseq=%i",cseq); // reply pending UAS transaction AmSipRequest* req = dlg->getUASTrans(cseq); if(req) { DBG("found request(cseq=%i): replying 481 to pending UAS transaction", req->cseq); dlg->reply(*req,481,SIP_REPLY_NOT_EXIST); } else { DBG("request not found: could not reply 481 to pending UAS transaction"); } continue; } ++i; } } void AmSipSubscription::removeSubscription(Subscriptions::iterator sub) { removeSubFromUACCSeqMap(sub); removeSubFromUASCSeqMap(sub); delete *sub; subs.erase(sub); } /** * match single subscription * if none, create one */ AmSipSubscription::Subscriptions::iterator AmSipSubscription::matchSubscription(const AmSipRequest& req, bool uac) { if((!uac && req.to_tag.empty()) || (uac && dlg->getRemoteTag().empty()) || (req.method == SIP_METH_REFER) || subs.empty()) { DBG("no to-tag, REFER or subs empty: create new subscription\n"); return createSubscription(req,uac); } SingleSubscription::Role role; string event; string id; if(req.method == SIP_METH_SUBSCRIBE) { role = uac ? SingleSubscription::Subscriber : SingleSubscription::Notifier; } else if(req.method == SIP_METH_NOTIFY){ role = uac ? SingleSubscription::Notifier : SingleSubscription::Subscriber; } else { DBG("unsupported request\n"); return subs.end(); } // parse Event-HF event = getHeader(req.hdrs,SIP_HDR_EVENT,true); id = get_header_param(event,"id"); event = strip_header_params(event); Subscriptions::iterator match = findSubscription(role,event,id); if(match == subs.end()){ if(req.method == SIP_METH_SUBSCRIBE) { // no match... new subscription? DBG("no match found, SUBSCRIBE: create new subscription\n"); return createSubscription(req,uac); } } return match; } bool AmSipSubscription::onRequestIn(const AmSipRequest& req) { // UAS side Subscriptions::iterator sub_it = matchSubscription(req,false); if((sub_it == subs.end()) || (*sub_it)->terminated()) { if((sub_it == subs.end()) && (req.method == SIP_METH_NOTIFY) && allow_subless_notify) { return true; } dlg->reply(req, 481, SIP_REPLY_NOT_EXIST); return false; } // process request; uas_cseq_map[req.cseq] = sub_it; return (*sub_it)->onRequestIn(req); } void AmSipSubscription::onRequestSent(const AmSipRequest& req) { // UAC side Subscriptions::iterator sub_it = matchSubscription(req,true); if(sub_it == subs.end()){ if((req.method == SIP_METH_NOTIFY) && allow_subless_notify) { return; } // should we exclude this case in onSendRequest??? ERROR("we just sent a request for which we could obtain no subscription\n"); return; } // process request; uac_cseq_map[req.cseq] = sub_it; (*sub_it)->onRequestSent(req); } bool AmSipSubscription::onReplyIn(const AmSipRequest& req, const AmSipReply& reply) { // UAC side CSeqMap::iterator cseq_it = uac_cseq_map.find(req.cseq); if(cseq_it == uac_cseq_map.end()){ if((req.method == SIP_METH_NOTIFY) && allow_subless_notify) { return true; } DBG("could not find %i in our uac_cseq_map\n",req.cseq); return false; } Subscriptions::iterator sub_it = cseq_it->second; SingleSubscription* sub = *sub_it; uac_cseq_map.erase(cseq_it); sub->replyFSM(req,reply); if(sub->terminated()){ removeSubscription(sub_it); } return true; } void AmSipSubscription::onReplySent(const AmSipRequest& req, const AmSipReply& reply) { // UAS side CSeqMap::iterator cseq_it = uas_cseq_map.find(req.cseq); if(cseq_it == uas_cseq_map.end()) return; Subscriptions::iterator sub_it = cseq_it->second; SingleSubscription* sub = *sub_it; uas_cseq_map.erase(cseq_it); sub->replyFSM(req,reply); if(sub->terminated()){ removeSubscription(sub_it); } } void AmSipSubscription::onTimeout(int timer_id, SingleSubscription* sub) { Subscriptions::iterator it = subs.begin(); for(; it != subs.end(); it++) { if(*it == sub) break; } if(it == subs.end()) return; // no match... sub->terminate(); removeSubscription(it); } void AmSipSubscription::debug() { DBG("subscriptions with lt=%s:",dlg->getLocalTag().c_str()); for(Subscriptions::iterator it = subs.begin(); it != subs.end(); it++) { DBG("\t%s",(*it)->to_str().c_str()); } } SIPSubscriptionEvent::SIPSubscriptionEvent(SubscriptionStatus status, const string& handle, unsigned int expires, unsigned int code, const string& reason) : AmEvent(E_SIP_SUBSCRIPTION), status(status), handle(handle), expires(expires), code(code), reason(reason), notify_body(nullptr) {} const char* SIPSubscriptionEvent::getStatusText() { switch (status) { case SubscribeActive: return "active"; case SubscribeFailed: return "failed"; case SubscribeTerminated: return "terminated"; case SubscribePending: return "pending"; case SubscriptionTimeout: return "timeout"; } return "unknown"; } AmSipSubscriptionDialog::AmSipSubscriptionDialog(const AmSipSubscriptionInfo& info, const string& sess_link, AmEventQueue* ev_q) : AmBasicSipDialog(this), AmSipSubscription(this,ev_q), sess_link(sess_link) { user = info.user; domain = info.domain; local_uri = "sip:"+info.from_user+"@"+info.domain; local_party = "<"+local_uri+">"; remote_uri = "sip:"+info.user+"@"+info.domain; remote_party = "<"+remote_uri+">"; callid = AmSession::getNewId(); local_tag = AmSession::getNewId(); event = info.event; event_id = info.id; accept = info.accept; } int AmSipSubscriptionDialog::subscribe(int expires) { string hdrs; if(!event.empty()){ hdrs += SIP_HDR_COLSP(SIP_HDR_EVENT) + event; if(!event_id.empty()) hdrs += ";id=" + event_id; hdrs += CRLF; } if (!accept.empty()) { hdrs += SIP_HDR_COLSP(SIP_HDR_ACCEPT) + accept + CRLF; } if (expires >= 0) { hdrs += SIP_HDR_COLSP(SIP_HDR_EXPIRES) + int2str(expires) + CRLF; } return sendRequest(SIP_METH_SUBSCRIBE,NULL,hdrs); } string AmSipSubscriptionDialog::getDescription() { return "'"+user+"@"+domain+", Event: "+event+"/"+event_id+"'"; } void AmSipSubscriptionDialog::onNotify(const AmSipRequest& req, SingleSubscription* sub) { assert(sub); // subscription state is update after the reply has been sent reply(req, 200, "OK"); SIPSubscriptionEvent* sub_ev = new SIPSubscriptionEvent(SIPSubscriptionEvent::SubscribeFailed, local_tag); switch(sub->getState()){ case SingleSubscription::SubState_pending: sub_ev->status = SIPSubscriptionEvent::SubscribePending; sub_ev->expires = sub->getExpires(); break; case SingleSubscription::SubState_active: sub_ev->status = SIPSubscriptionEvent::SubscribeActive; sub_ev->expires = sub->getExpires(); break; case SingleSubscription::SubState_terminated: sub_ev->status = SIPSubscriptionEvent::SubscribeTerminated; break; default: break; } if(!req.body.empty()) sub_ev->notify_body.reset(new AmMimeBody(req.body)); DBG("posting event to '%s'\n", sess_link.c_str()); AmSessionContainer::instance()->postEvent(sess_link, sub_ev); } void AmSipSubscriptionDialog::onFailureReply(const AmSipReply& reply, SingleSubscription* sub) { assert(sub); SIPSubscriptionEvent* sub_ev = new SIPSubscriptionEvent(SIPSubscriptionEvent::SubscribeFailed, local_tag, 0, reply.code, reply.reason); DBG("posting event to '%s'\n", sess_link.c_str()); AmSessionContainer::instance()->postEvent(sess_link, sub_ev); } void AmSipSubscriptionDialog::onTimeout(int timer_id, SingleSubscription* sub) { AmSipSubscription::onTimeout(timer_id,sub); // possibly we've got a timeout for an already destroyed subscription. // however, it only happens if the subscription has been destroyed right // before this event (same processEvents() call). SIPSubscriptionEvent* sub_ev = new SIPSubscriptionEvent(SIPSubscriptionEvent::SubscriptionTimeout, local_tag); DBG("posting event to '%s'\n", sess_link.c_str()); AmSessionContainer::instance()->postEvent(sess_link, sub_ev); }