From 0012b5fa61887fdbcf504ffbf386fc188c704ad1 Mon Sep 17 00:00:00 2001 From: Stefan Sayer Date: Fri, 23 Apr 2010 18:47:07 +0000 Subject: [PATCH] first version of a SST enabled B2B application. The incoming INVITE establishind a call is passed in signaling only B2B mode to the B leg, which tries to send it to the request URI. SIP Session Timers are enabled on both legs. When the timer expires, an empty INVITE is sent, and the resulting SDP offer from body of the 200 is relayed into the other leg, where it is sent out as INVITE with the offer. The answer from B leg is relayed into A leg and sent as body in ACK message. SST expiration is configurable in confi file. git-svn-id: http://svn.berlios.de/svnroot/repos/sems/trunk@1821 8eb893ce-cfd4-0310-b710-fb5ebe64c474 --- apps/sst_b2b/CMakeLists.txt | 7 + apps/sst_b2b/Makefile | 7 + apps/sst_b2b/SSTB2B.cpp | 392 ++++++++++++++++++++++++++++++++++ apps/sst_b2b/SSTB2B.h | 134 ++++++++++++ apps/sst_b2b/etc/sst_b2b.conf | 10 + core/AmB2BSession.cpp | 92 +++++++- core/AmB2BSession.h | 38 +++- 7 files changed, 673 insertions(+), 7 deletions(-) create mode 100644 apps/sst_b2b/CMakeLists.txt create mode 100644 apps/sst_b2b/Makefile create mode 100644 apps/sst_b2b/SSTB2B.cpp create mode 100644 apps/sst_b2b/SSTB2B.h create mode 100644 apps/sst_b2b/etc/sst_b2b.conf diff --git a/apps/sst_b2b/CMakeLists.txt b/apps/sst_b2b/CMakeLists.txt new file mode 100644 index 00000000..fbefd025 --- /dev/null +++ b/apps/sst_b2b/CMakeLists.txt @@ -0,0 +1,7 @@ +set (sst_b2b_SRCS +SSTB2B.cpp +) + +SET(sems_module_name sst_b2b) +INCLUDE(${CMAKE_SOURCE_DIR}/cmake/module.rules.txt) +INCLUDE(${CMAKE_SOURCE_DIR}/cmake/config.rules.txt) diff --git a/apps/sst_b2b/Makefile b/apps/sst_b2b/Makefile new file mode 100644 index 00000000..323753f2 --- /dev/null +++ b/apps/sst_b2b/Makefile @@ -0,0 +1,7 @@ +plug_in_name = sst_b2b + +module_ldflags = +module_cflags = -DMOD_NAME=\"$(plug_in_name)\" + +COREPATH ?= ../../core +include $(COREPATH)/plug-in/Makefile.app_module diff --git a/apps/sst_b2b/SSTB2B.cpp b/apps/sst_b2b/SSTB2B.cpp new file mode 100644 index 00000000..e483a887 --- /dev/null +++ b/apps/sst_b2b/SSTB2B.cpp @@ -0,0 +1,392 @@ +/* + * $Id: SSTB2B.cpp 1784 2010-04-15 13:01:00Z sayer $ + * + * Copyright (C) 2010 Stefan Sayer + * + * 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. + * + * 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 "SSTB2B.h" + +#include "log.h" +#include "AmUtils.h" +#include "AmAudio.h" +#include "AmPlugIn.h" +#include "AmMediaProcessor.h" +#include "AmConfigReader.h" +#include "AmSessionContainer.h" + +string SSTB2BFactory::user; +string SSTB2BFactory::domain; +string SSTB2BFactory::pwd; +AmConfigReader SSTB2BFactory::cfg; +AmSessionEventHandlerFactory* SSTB2BFactory::session_timer_fact = NULL; + +EXPORT_SESSION_FACTORY(SSTB2BFactory,MOD_NAME); + +SSTB2BFactory::SSTB2BFactory(const string& _app_name) +: AmSessionFactory(_app_name) +{ +} + + +int SSTB2BFactory::onLoad() +{ + if(cfg.loadFile(AmConfig::ModConfigPath + string(MOD_NAME ".conf"))) { + INFO("No configuration for sst_b2b present (%s)\n", + (AmConfig::ModConfigPath + string(MOD_NAME ".conf")).c_str() + ); + } + + session_timer_fact = AmPlugIn::instance()->getFactory4Seh("session_timer"); + if(!session_timer_fact) { + ERROR("could not load session_timer from session_timer plug-in\n"); + return -1; + } + + return 0; +} + + +AmSession* SSTB2BFactory::onInvite(const AmSipRequest& req) +{ + + SSTB2BDialog* b2b_dlg = new SSTB2BDialog(); + AmSessionEventHandler* h = session_timer_fact->getHandler(b2b_dlg); + if(!h) { + ERROR("could not get a session timer event handler\n"); + throw AmSession::Exception(500,"Server internal error"); + } + if(h->configure(cfg)){ + ERROR("Could not configure the session timer: disabling session timers.\n"); + delete h; + } else { + b2b_dlg->addHandler(h); + } + + return b2b_dlg; +} + + +SSTB2BDialog::SSTB2BDialog() // AmDynInvoke* user_timer) +: m_state(BB_Init), + AmB2BCallerSession() + +{ + set_sip_relay_only(false); +} + + +SSTB2BDialog::~SSTB2BDialog() +{ +} + + +void SSTB2BDialog::onInvite(const AmSipRequest& req) +{ + DBG("onINVITE -------------------------------\n"); + // this will prevent us from being added to media processor + setInOut(NULL,NULL); + + from = req.from; + to = req.to; + + m_state = BB_Dialing; + + if(dlg.reply(req, 100, "Connecting") != 0) { + throw AmSession::Exception(500,"Failed to reply 100"); + } + + invite_req = req; + + removeHeader(invite_req.hdrs,PARAM_HDR); + removeHeader(invite_req.hdrs,"P-App-Name"); + + dlg.updateStatus(req); + recvd_req.insert(std::make_pair(req.cseq,req)); + + set_sip_relay_only(true); + connectCallee("<" + req.r_uri + ">", req.r_uri, true); +} + +void SSTB2BDialog::sendReinvite(bool updateSDP, const string& headers) { + if (sip_relay_only) { + // we send empty reinvite + DBG("sending empty reinvite in callee session\n"); + dlg.reinvite(headers, "", ""); + } else { + AmB2BCallerSession::sendReinvite(updateSDP, headers); + } + + // // we send empty reinvite + // dlg.reinvite(headers, "", ""); + // we send reinvite with the last body we got from the other side + // last_otherleg_content_type, last_otherleg_body); +} + +void SSTB2BDialog::process(AmEvent* ev) +{ + AmB2BCallerSession::process(ev); +} + +void SSTB2BDialog::onSipRequest(const AmSipRequest& req) { + // AmB2BSession does not call AmSession::onSipRequest for + // forwarded requests - so lets call event handlers here + // todo: this is a hack, replace this by calling proper session + // event handler in AmB2BSession + bool fwd = sip_relay_only && + (req.method != "BYE") && + (req.method != "CANCEL"); + if (fwd) { + CALL_EVENT_H(onSipRequest,req); + } + + AmB2BCallerSession::onSipRequest(req); +} + +void SSTB2BDialog::onSipReply(const AmSipReply& reply) { + TransMap::iterator t = relayed_req.find(reply.cseq); + bool fwd = t != relayed_req.end(); + + DBG("onSipReply: %i %s (fwd=%i)\n",reply.code,reply.reason.c_str(),fwd); + DBG("onSipReply: content-type = %s\n",reply.content_type.c_str()); + if (fwd) { + CALL_EVENT_H(onSipReply,reply); + } + + AmB2BCallerSession::onSipReply(reply); +} + +bool SSTB2BDialog::onOtherReply(const AmSipReply& reply) +{ + bool ret = false; + + if ((m_state == BB_Dialing) && (reply.cseq == invite_req.cseq)) { + if (reply.code < 200) { + DBG("Callee is trying... code %d\n", reply.code); + } + else if(reply.code < 300) { + if(getCalleeStatus() == Connected) { + m_state = BB_Connected; + setInOut(NULL, NULL); + } + } + else if(reply.code == 487 && dlg.getStatus() == AmSipDialog::Pending) { + DBG("Stopping leg A on 487 from B with 487\n"); + dlg.reply(invite_req, 487, "Request terminated"); + setStopped(); + ret = true; + } + else if (reply.code >= 300 && dlg.getStatus() == AmSipDialog::Connected) { + DBG("Callee final error in connected state with code %d\n",reply.code); + terminateLeg(); + } + else { + DBG("Callee final error with code %d\n",reply.code); + AmB2BCallerSession::onOtherReply(reply); + } + } + return ret; +} + + +void SSTB2BDialog::onOtherBye(const AmSipRequest& req) +{ +// stopAccounting(); + AmB2BCallerSession::onOtherBye(req); +} + + +void SSTB2BDialog::onBye(const AmSipRequest& req) +{ + if (m_state == BB_Connected) { +// stopAccounting(); + } + terminateOtherLeg(); + setStopped(); +} + + +void SSTB2BDialog::onCancel() +{ + if(dlg.getStatus() == AmSipDialog::Pending) { + DBG("Wait for leg B to terminate"); + } else { + DBG("Canceling leg A on CANCEL since dialog is not pending"); + dlg.reply(invite_req, 487, "Request terminated"); + setStopped(); + } +} + +void SSTB2BDialog::createCalleeSession() +{ + SSTB2BCalleeSession* callee_session = new SSTB2BCalleeSession(this, user, password); + + // adding auth handler + AmSessionEventHandlerFactory* uac_auth_f = + AmPlugIn::instance()->getFactory4Seh("uac_auth"); + if (NULL == uac_auth_f) { + INFO("uac_auth module not loaded. uac auth NOT enabled for callee session.\n"); + } else { + AmSessionEventHandler* h = uac_auth_f->getHandler(callee_session); + + // we cannot use the generic AmSessionEventHandler hooks, + // because the hooks don't work in AmB2BSession + callee_session->setAuthHandler(h); + DBG("uac auth enabled for callee session.\n"); + } + + AmSessionEventHandler* h = SSTB2BFactory::session_timer_fact->getHandler(callee_session); + if(!h) { + ERROR("could not get a session timer event handler\n"); + delete callee_session; + throw AmSession::Exception(500,"Server internal error"); + } + if(h->configure(SSTB2BFactory::cfg)){ + ERROR("Could not configure the session timer: disabling session timers.\n"); + delete h; + } else { + callee_session->addHandler(h); + } + + AmSipDialog& callee_dlg = callee_session->dlg; + + other_id = AmSession::getNewId(); + + callee_dlg.local_tag = other_id; + callee_dlg.callid = AmSession::getNewId() + "@" + AmConfig::LocalIP; + + // this will be overwritten by ConnectLeg event + callee_dlg.remote_party = dlg.local_party; + callee_dlg.remote_uri = dlg.local_uri; + + // if given as parameters, use these + callee_dlg.local_party = from; + callee_dlg.local_uri = from; + + DBG("Created B2BUA callee leg, From: %s\n", + from.c_str()); + + if (AmConfig::LogSessions) { + INFO("Starting B2B callee session %s app %s\n", + callee_session->getLocalTag().c_str(), invite_req.cmd.c_str()); + } + + MONITORING_LOG5(other_id.c_str(), + "app", invite_req.cmd.c_str(), + "dir", "out", + "from", callee_dlg.local_party.c_str(), + "to", callee_dlg.remote_party.c_str(), + "ruri", callee_dlg.remote_uri.c_str()); + + callee_session->start(); + + AmSessionContainer* sess_cont = AmSessionContainer::instance(); + sess_cont->addSession(other_id,callee_session); +} + +SSTB2BCalleeSession::SSTB2BCalleeSession(const AmB2BCallerSession* caller, + const string& user, const string& pwd) + : auth(NULL), + credentials("", user, pwd), // domain (realm) is unused in credentials + AmB2BCalleeSession(caller) { +} + +SSTB2BCalleeSession::~SSTB2BCalleeSession() { + if (auth) + delete auth; +} + +inline UACAuthCred* SSTB2BCalleeSession::getCredentials() { + return &credentials; +} + +void SSTB2BCalleeSession::onSipRequest(const AmSipRequest& req) { + // AmB2BSession does not call AmSession::onSipRequest for + // forwarded requests - so lets call event handlers here + // todo: this is a hack, replace this by calling proper session + // event handler in AmB2BSession + bool fwd = sip_relay_only && + (req.method != "BYE") && + (req.method != "CANCEL"); + if (fwd) { + CALL_EVENT_H(onSipRequest,req); + } + + AmB2BCalleeSession::onSipRequest(req); +} + +void SSTB2BCalleeSession::onSipReply(const AmSipReply& reply) { + // call event handlers where it is not done + TransMap::iterator t = relayed_req.find(reply.cseq); + bool fwd = t != relayed_req.end(); + DBG("onSipReply: %i %s (fwd=%i)\n",reply.code,reply.reason.c_str(),fwd); + DBG("onSipReply: content-type = %s\n",reply.content_type.c_str()); + if(fwd) { + CALL_EVENT_H(onSipReply,reply); + } + + if (NULL == auth) { + AmB2BCalleeSession::onSipReply(reply); + return; + } + + int cseq_before = dlg.cseq; + if (!auth->onSipReply(reply)) { + AmB2BCalleeSession::onSipReply(reply); + } else { + if (cseq_before != dlg.cseq) { + DBG("uac_auth consumed reply with cseq %d and resent with cseq %d; " + "updating relayed_req map\n", + reply.cseq, cseq_before); + TransMap::iterator it=relayed_req.find(reply.cseq); + if (it != relayed_req.end()) { + relayed_req[cseq_before] = it->second; + relayed_req.erase(it); + } + } + } +} + +void SSTB2BCalleeSession::onSendRequest(const string& method, const string& content_type, + const string& body, string& hdrs, int flags, unsigned int cseq) +{ + if (NULL != auth) { + DBG("auth->onSendRequest cseq = %d\n", cseq); + auth->onSendRequest(method, content_type, + body, hdrs, flags, cseq); + } + + AmB2BCalleeSession::onSendRequest(method, content_type, + body, hdrs, flags, cseq); +} + +void SSTB2BCalleeSession::sendReinvite(bool updateSDP, const string& headers) { + if (sip_relay_only) { + // we send empty reinvite + DBG("sending empty reinvite in callee session\n"); + dlg.reinvite(headers, "", ""); + } else { + AmB2BCalleeSession::sendReinvite(updateSDP, headers); + } +} + diff --git a/apps/sst_b2b/SSTB2B.h b/apps/sst_b2b/SSTB2B.h new file mode 100644 index 00000000..439ee4f0 --- /dev/null +++ b/apps/sst_b2b/SSTB2B.h @@ -0,0 +1,134 @@ +/* + * $Id: AuthB2B.h 1252 2009-02-01 12:51:06Z sayer $ + * + * Copyright (C) 2010 Stefan Sayer + * + * 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. + * + * 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 + */ + +#ifndef _SST_B2B_H +#define _SST_B2B_H + +#include "AmB2BSession.h" +#include "ampi/UACAuthAPI.h" + +#include "AmConfigReader.h" + +using std::string; + +class SSTB2BFactory: public AmSessionFactory +{ +/* AmDynInvokeFactory* user_timer_fact; */ + + + public: + SSTB2BFactory(const string& _app_name); + + int onLoad(); + AmSession* onInvite(const AmSipRequest& req); + static string user; + static string domain; + static string pwd; + + static AmConfigReader cfg; + static AmSessionEventHandlerFactory* session_timer_fact; +}; + +class SSTB2BDialog : public AmB2BCallerSession +{ + enum { + BB_Init = 0, + BB_Dialing, + BB_Connected, + BB_Teardown + } CallerState; + + int m_state; + + string domain; + string user; + string password; + + string from; + string to; + + string last_otherleg_content_type; + string last_otherleg_body; + + string last_content_type; + string last_body; + +/* AmDynInvoke* m_user_timer; */ + /* set b2b_reinvite_cseqs; // cseqs of reinvite we sent from the middle */ + + public: + + SSTB2BDialog(); //AmDynInvoke* user_timer); + ~SSTB2BDialog(); + + void process(AmEvent* ev); + void onBye(const AmSipRequest& req); + void onInvite(const AmSipRequest& req); + void onCancel(); + + void sendReinvite(bool updateSDP, const string& headers); + + protected: + void onSipReply(const AmSipReply& reply); + void onSipRequest(const AmSipRequest& req); + + protected: + + bool onOtherReply(const AmSipReply& reply); + void onOtherBye(const AmSipRequest& req); + + void createCalleeSession(); +}; + +class SSTB2BCalleeSession +: public AmB2BCalleeSession, public CredentialHolder +{ + UACAuthCred credentials; + AmSessionEventHandler* auth; + + /* string last_otherleg_content_type; */ + /* string last_otherleg_body; */ + + protected: + void onSipRequest(const AmSipRequest& req); + void onSipReply(const AmSipReply& reply); + void onSendRequest(const string& method, const string& content_type, + const string& body, string& hdrs, int flags, unsigned int cseq); + + /* bool onOtherReply(const AmSipReply& reply); */ + + public: + SSTB2BCalleeSession(const AmB2BCallerSession* caller, const string& user, const string& pwd); + ~SSTB2BCalleeSession(); + + inline UACAuthCred* getCredentials(); + + void setAuthHandler(AmSessionEventHandler* h) { auth = h; } + + void sendReinvite(bool updateSDP, const string& headers); +}; +#endif diff --git a/apps/sst_b2b/etc/sst_b2b.conf b/apps/sst_b2b/etc/sst_b2b.conf new file mode 100644 index 00000000..7d8e66c6 --- /dev/null +++ b/apps/sst_b2b/etc/sst_b2b.conf @@ -0,0 +1,10 @@ +# session timer configuration: +enable_session_timer=yes +session_expires=90 +minimum_timer=10 + +#authentication (questionable whether that works) +# user=someuser +# domain=somedomain.net +# pwd=sompwd + diff --git a/core/AmB2BSession.cpp b/core/AmB2BSession.cpp index 333835ad..f6b0c341 100644 --- a/core/AmB2BSession.cpp +++ b/core/AmB2BSession.cpp @@ -135,6 +135,45 @@ void AmB2BSession::onB2BEvent(B2BEvent* ev) case B2BTerminateLeg: terminateLeg(); break; + + case B2BMsgBody: + { + if (!sip_relay_only) { + ERROR("relayed message body received but not in sip_relay_only mode\n"); + return; + } + + B2BMsgBodyEvent* body_ev = dynamic_cast(ev); + assert(body_ev); + + DBG("received B2B Msg body event; is_offer=%s, r_cseq=%d\n", + body_ev->is_offer?"true":"false", body_ev->r_cseq); + + if (body_ev->is_offer) { + // send INVITE with SDP + trans_ticket tt; // empty transaction ticket + relayed_body_req[dlg.cseq] = AmSipTransaction("INVITE", body_ev->r_cseq, tt); + if (dlg.reinvite("", body_ev->content_type, body_ev->body)) { + ERROR("sending reinvite with relayed body\n"); + relayed_body_req.erase(dlg.cseq); + // TODO?: relay error back instead? + // tear down: + DBG("error sending reinvite - terminating this and the other leg\n"); + terminateOtherLeg(); + terminateLeg(); + } + } else { + // is_answer - send 200 ACK + // todo: use that from uas_trans? + trans_ticket tt; // not used for ACK + AmSipTransaction trans("INVITE", body_ev->r_cseq, tt); + if (dlg.send_200_ack(trans, body_ev->content_type, body_ev->body, + "" /* hdrs - todo */, SIP_FLAGS_VERBATIM)) { + ERROR("sending ACK with SDP\n"); + } + } + return; + }; break; } //ERROR("unknown event caught\n"); @@ -177,7 +216,46 @@ void AmB2BSession::onSipReply(const AmSipReply& reply) relayed_req.erase(t); } } else { - AmSession::onSipReply(reply); + bool relay_body = + // is a reply to request we sent, + // even though we are in sip_relay_only mode + (sip_relay_only && + // positive reply + (200 <= reply.code) && (reply.code < 300) + // with body + && !reply.body.empty()); // todo: && method == INVITE??? + + if (relay_body) { + // is it an answer to a relayed body, or an answer to empty re-INVITE? + TransMap::iterator rel_body_it = relayed_body_req.find(reply.cseq); + bool is_offer = (rel_body_it == relayed_body_req.end()); + // answer to empty re-INVITE we have sent + relayEvent(new B2BMsgBodyEvent(reply.content_type, reply.body, + is_offer, is_offer ? reply.cseq : rel_body_it->second.cseq)); + + if (is_offer) { + // onSipReply from AmSession without do_200_ack in dlg.updateStatus(reply) + // todo (?): add do_200_ack flag to AmSession::onSipReply and call AmSession::onSipReply + CALL_EVENT_H(onSipReply,reply); + + int status = dlg.getStatus(); + dlg.updateStatus(reply, false); + + if (status != dlg.getStatus()) + DBG("Dialog status changed %s -> %s (stopped=%s) \n", + AmSipDialog::status2str[status], + AmSipDialog::status2str[dlg.getStatus()], + getStopped() ? "true" : "false"); + else + DBG("Dialog status stays %s (stopped=%s)\n", AmSipDialog::status2str[status], + getStopped() ? "true" : "false"); + } else { + relayed_body_req.erase(rel_body_it); + AmSession::onSipReply(reply); + } + } else { + AmSession::onSipReply(reply); + } relayEvent(new B2BSipReplyEvent(reply,false)); } } @@ -305,7 +383,11 @@ void AmB2BCallerSession::onB2BEvent(B2BEvent* ev) if(reply.code < 200){ if ((!sip_relay_only) && sip_relay_early_media_sdp && reply.code>=180 && reply.code<=183 && (!reply.body.empty())) { - reinviteCaller(reply); + if (reinviteCaller(reply)) { + ERROR("re-INVITEing caller for early session - stopping this and other leg\n"); + terminateOtherLeg(); + terminateLeg(); + } } callee_status = Ringing; @@ -316,7 +398,11 @@ void AmB2BCallerSession::onB2BEvent(B2BEvent* ev) if (!sip_relay_only) { sip_relay_only = true; - reinviteCaller(reply); + if (reinviteCaller(reply)) { + ERROR("re-INVITEing caller - stopping this and other leg\n"); + terminateOtherLeg(); + terminateLeg(); + } } } else { diff --git a/core/AmB2BSession.h b/core/AmB2BSession.h index 3375e7ea..97fe830e 100644 --- a/core/AmB2BSession.h +++ b/core/AmB2BSession.h @@ -35,7 +35,8 @@ enum { B2BTerminateLeg, B2BConnectLeg, B2BCallAccepted, B2BSipRequest, - B2BSipReply }; + B2BSipReply, + B2BMsgBody }; /** \brief base class for event in B2B session */ struct B2BEvent: public AmEvent @@ -78,6 +79,25 @@ struct B2BSipReplyEvent: public B2BSipEvent {} }; +/** \brief relay a message body to other leg in B2B session */ +struct B2BMsgBodyEvent : public B2BEvent { + string content_type; + string body; + + bool is_offer; + unsigned int r_cseq; + + B2BMsgBodyEvent(const string& content_type, + const string& body, + bool is_offer, + unsigned int r_cseq) + : B2BEvent(B2BMsgBody), + content_type(content_type), body(body), + is_offer(is_offer), r_cseq(r_cseq) { + } + ~B2BMsgBodyEvent() { } +}; + /** \brief trigger connecting the callee leg in B2B session */ struct B2BConnectEvent: public B2BEvent { @@ -94,8 +114,10 @@ struct B2BConnectEvent: public B2BEvent B2BConnectEvent(const string& remote_party, const string& remote_uri) : B2BEvent(B2BConnectLeg), - remote_party(remote_party), - remote_uri(remote_uri) + remote_party(remote_party), + remote_uri(remote_uri), + relayed_invite(false), + r_cseq(0) {} }; @@ -116,11 +138,19 @@ class AmB2BSession: public AmSession */ bool sip_relay_only; - /** Requests which + /** + * Requests which * have been relayed (sent) */ TransMap relayed_req; + /** + * Requests which have been originated + * from local dialog but with + * relayed body + */ + TransMap relayed_body_req; + /** Requests received for relaying */ std::map recvd_req;