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.
sems/core/AmSipSubscription.cpp

803 lines
20 KiB

/*
* 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 <assert.h>
#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);
}