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.
1663 lines
48 KiB
1663 lines
48 KiB
/*
|
|
* Copyright (C) 2002-2003 Fhg Fokus
|
|
*
|
|
* 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 "AmB2BSession.h"
|
|
#include "AmSessionContainer.h"
|
|
#include "AmConfig.h"
|
|
#include "ampi/MonitoringAPI.h"
|
|
#include "AmSipHeaders.h"
|
|
#include "AmUtils.h"
|
|
#include "AmRtpReceiver.h"
|
|
|
|
#include "global_defs.h"
|
|
|
|
#include <assert.h>
|
|
|
|
// helpers
|
|
static const string sdp_content_type(SIP_APPLICATION_SDP);
|
|
static const string empty;
|
|
|
|
//
|
|
// helper functions
|
|
//
|
|
|
|
static void errCode2RelayedReply(AmSipReply &reply, int err_code, unsigned default_code = 500)
|
|
{
|
|
// FIXME: use cleaner method to propagate error codes/reasons,
|
|
// do it everywhere in the code
|
|
if ((err_code < -399) && (err_code > -700)) {
|
|
reply.code = -err_code;
|
|
}
|
|
else reply.code = default_code;
|
|
|
|
// TODO: optimize with a table
|
|
switch (reply.code) {
|
|
case 400: reply.reason = "Bad Request"; break;
|
|
case 478: reply.reason = "Unresolvable destination"; break;
|
|
case 488: reply.reason = SIP_REPLY_NOT_ACCEPTABLE_HERE; break;
|
|
default: reply.reason = SIP_REPLY_SERVER_INTERNAL_ERROR;
|
|
}
|
|
}
|
|
|
|
static bool isDSMEarlyAnnounceForced(const std::string &hdrs)
|
|
{
|
|
string p_dsm_app = getHeader(hdrs, SIP_HDR_P_DSM_APP, true);
|
|
return get_header_param(p_dsm_app, DSM_PARAM_EARLY_AN) == DSM_VALUE_FORCE;
|
|
}
|
|
|
|
static bool isDSMPlaybackFinished(const std::string &hdrs)
|
|
{
|
|
/** TODO: for the future, we might also want to check
|
|
* particular DSM applications, for now plays no role.
|
|
*/
|
|
string p_dsm_app = getHeader(hdrs, SIP_HDR_P_DSM_APP, true);
|
|
return get_header_param(p_dsm_app, DSM_PARAM_PLAYBACK) == DSM_VALUE_FINISHED;
|
|
}
|
|
|
|
static bool isDSMToTagReset(const std::string &hdrs)
|
|
{
|
|
string p_dsm_app = getHeader(hdrs, SIP_HDR_P_DSM_APP, true);
|
|
return get_header_param(p_dsm_app, DSM_PARAM_RESET_TOTAG) == DSM_VALUE_IS_SET;
|
|
}
|
|
|
|
//
|
|
// AmB2BSession methods
|
|
//
|
|
|
|
AmB2BSession::AmB2BSession(const string& other_local_tag, AmSipDialog* p_dlg,
|
|
AmSipSubscription* p_subs)
|
|
: AmSession(p_dlg),
|
|
other_id(other_local_tag),
|
|
sip_relay_only(true),
|
|
subs(p_subs),
|
|
rtp_relay_mode(RTP_Direct),
|
|
rtp_relay_force_symmetric_rtp(false),
|
|
enable_dtmf_transcoding(false),
|
|
enable_dtmf_rtp_filtering(false),
|
|
enable_dtmf_rtp_detection(false),
|
|
rtp_relay_transparent_seqno(true), rtp_relay_transparent_ssrc(true),
|
|
est_invite_cseq(0),est_invite_other_cseq(0),
|
|
media_session(NULL),
|
|
previous_origin_sessId(0),
|
|
previous_origin_sessV(0)
|
|
{
|
|
if(!subs) subs = new AmSipSubscription(dlg,this);
|
|
}
|
|
|
|
AmB2BSession::~AmB2BSession()
|
|
{
|
|
clearRtpReceiverRelay();
|
|
|
|
DBG("relayed_req.size() = %zu\n",relayed_req.size());
|
|
|
|
map<int,AmSipRequest>::iterator it = recvd_req.begin();
|
|
DBG("recvd_req.size() = %zu\n",recvd_req.size());
|
|
for(;it != recvd_req.end(); ++it){
|
|
DBG(" <%i,%s>\n",it->first,it->second.method.c_str());
|
|
}
|
|
|
|
if(subs)
|
|
delete subs;
|
|
}
|
|
|
|
void AmB2BSession::set_sip_relay_only(bool r) {
|
|
sip_relay_only = r;
|
|
|
|
// disable offer/answer if we just relay requests
|
|
//dlg.setOAEnabled(!sip_relay_only); ???
|
|
}
|
|
|
|
void AmB2BSession::clear_other()
|
|
{
|
|
setOtherId("");
|
|
}
|
|
|
|
void AmB2BSession::process(AmEvent* event)
|
|
{
|
|
B2BEvent* b2b_e = dynamic_cast<B2BEvent*>(event);
|
|
if(b2b_e){
|
|
|
|
onB2BEvent(b2b_e);
|
|
return;
|
|
}
|
|
|
|
SingleSubTimeoutEvent* to_ev = dynamic_cast<SingleSubTimeoutEvent*>(event);
|
|
if(to_ev) {
|
|
subs->onTimeout(to_ev->timer_id,to_ev->sub);
|
|
return;
|
|
}
|
|
|
|
AmSession::process(event);
|
|
}
|
|
|
|
void AmB2BSession::finalize()
|
|
{
|
|
// clean up relayed_req
|
|
if(!other_id.empty()) {
|
|
while(!relayed_req.empty()) {
|
|
TransMap::iterator it = relayed_req.begin();
|
|
const AmSipRequest& req = it->second;
|
|
relayError(req.method,req.cseq,true,481,SIP_REPLY_NOT_EXIST);
|
|
relayed_req.erase(it);
|
|
}
|
|
}
|
|
|
|
AmSession::finalize();
|
|
}
|
|
|
|
void AmB2BSession::sl_reply(const string &method, unsigned cseq, bool forward, int sip_code, const char *reason)
|
|
{
|
|
if (method != SIP_METH_ACK) {
|
|
AmSipReply n_reply;
|
|
n_reply.code = sip_code;
|
|
n_reply.reason = reason;
|
|
n_reply.cseq = cseq;
|
|
n_reply.cseq_method = method;
|
|
n_reply.from_tag = dlg->getLocalTag();
|
|
DBG("relaying stateless B2B SIP reply %d %s\n", sip_code, reason);
|
|
relayEvent(new B2BSipReplyEvent(n_reply, forward, method, getLocalTag()));
|
|
}
|
|
}
|
|
|
|
void AmB2BSession::relayError(const string &method, unsigned cseq,
|
|
bool forward, int err_code)
|
|
{
|
|
if (method != "ACK") {
|
|
AmSipReply n_reply;
|
|
errCode2RelayedReply(n_reply, err_code, 500);
|
|
n_reply.cseq = cseq;
|
|
n_reply.cseq_method = method;
|
|
n_reply.from_tag = dlg->getLocalTag();
|
|
DBG("relaying B2B SIP error reply %u %s\n", n_reply.code, n_reply.reason.c_str());
|
|
relayEvent(new B2BSipReplyEvent(n_reply, forward, method, getLocalTag()));
|
|
}
|
|
}
|
|
|
|
void AmB2BSession::relayError(const string &method, unsigned cseq, bool forward, int sip_code, const char *reason)
|
|
{
|
|
if (method != "ACK") {
|
|
AmSipReply n_reply;
|
|
n_reply.code = sip_code;
|
|
n_reply.reason = reason;
|
|
n_reply.cseq = cseq;
|
|
n_reply.cseq_method = method;
|
|
n_reply.from_tag = dlg->getLocalTag();
|
|
DBG("relaying B2B SIP reply %d %s\n", sip_code, reason);
|
|
relayEvent(new B2BSipReplyEvent(n_reply, forward, method, getLocalTag()));
|
|
}
|
|
}
|
|
|
|
void AmB2BSession::createFakeReply(AmMimeBody *sdp, AmMimeBody& reply_body) {
|
|
|
|
int rtp_int_tmp = getRtpInterface();
|
|
string rtp_local_ip = AmConfig::RTP_Ifs[rtp_int_tmp].LocalIP;
|
|
|
|
AmSdp s;
|
|
|
|
if (!sdp || s.parse((const char*)sdp->getPayload())) {
|
|
/* no offer in the INVITE (or can't be parsed), we have to append fake offer
|
|
into the reply */
|
|
s.version = 0;
|
|
s.origin.user = "sems";
|
|
s.sessionName = "sems";
|
|
s.conn.network = NT_IN;
|
|
s.conn.addrType = AT_V4;
|
|
|
|
/* MT#55582, removed.
|
|
s.conn.address = "0.0.0.0";
|
|
*/
|
|
|
|
/* re-fill the empty connection address with the media_ip */
|
|
if (s.conn.address.empty()) s.conn.address = rtp_local_ip;
|
|
|
|
s.media.push_back(SdpMedia());
|
|
SdpMedia &m = s.media.back();
|
|
m.type = MT_AUDIO;
|
|
m.transport = TP_RTPAVP;
|
|
m.send = false;
|
|
m.recv = false;
|
|
m.payloads.push_back(SdpPayload(0));
|
|
}
|
|
|
|
/* MT#55582, removed, because in case of generating a faked reply
|
|
during the media session refreshment (e.g. call pickup after the AA),
|
|
it leads to a held media session
|
|
|
|
if (!s.conn.address.empty()) s.conn.address = "0.0.0.0";
|
|
for (vector<SdpMedia>::iterator i = s.media.begin(); i != s.media.end(); ++i) {
|
|
//i->port = 0;
|
|
if (!i->conn.address.empty()) i->conn.address = "0.0.0.0";
|
|
}*/
|
|
|
|
/* re-fill the empty connection address with the media_ip */
|
|
if (s.conn.address.empty()) {
|
|
s.conn.address = rtp_local_ip;
|
|
DBG("RTP Connection address was empty, and has been rested to: <%s>\n", s.conn.address.c_str());
|
|
}
|
|
|
|
/* same here, but for the rest media sessions in SDP */
|
|
for (vector<SdpMedia>::iterator i = s.media.begin(); i != s.media.end(); ++i) {
|
|
if (i->conn.address.empty()) i->conn.address = rtp_local_ip;
|
|
}
|
|
|
|
string body_str;
|
|
s.print(body_str);
|
|
reply_body.parse(SIP_APPLICATION_SDP, (const unsigned char*)body_str.c_str(), body_str.length());
|
|
try {
|
|
updateLocalBody(reply_body);
|
|
} catch (...) { /* throw ? */ }
|
|
|
|
DBG("created pending INVITE reply body: %s\n", body_str.c_str());
|
|
}
|
|
|
|
void AmB2BSession::acceptPendingInvite(AmSipRequest *invite)
|
|
{
|
|
// reply the INVITE with fake 200 reply
|
|
|
|
AmMimeBody *sdp = invite->body.hasContentType(SIP_APPLICATION_SDP);
|
|
AmMimeBody body;
|
|
|
|
createFakeReply(sdp, body);
|
|
|
|
DBG("Replying to pending invite with 200 OK\n");
|
|
dlg->reply(*invite, 200, "OK", &body);
|
|
}
|
|
|
|
void AmB2BSession::acceptPendingInviteB2B(AmSipRequest& invite)
|
|
{
|
|
AmMimeBody *sdp = invite.body.hasContentType(SIP_APPLICATION_SDP);
|
|
AmSipReply n_reply;
|
|
createFakeReply(sdp, n_reply.body);
|
|
n_reply.code = 200;
|
|
n_reply.reason = "OK";
|
|
n_reply.cseq = invite.cseq;
|
|
n_reply.cseq_method = SIP_METH_INVITE;
|
|
n_reply.from_tag = dlg->getLocalTag();
|
|
DBG("Relaying B2B-event (fake 200 OK)\n");
|
|
relayEvent(new B2BSipReplyEvent(n_reply, true, SIP_METH_INVITE, getLocalTag()));
|
|
}
|
|
|
|
void AmB2BSession::onB2BEvent(B2BEvent* ev)
|
|
{
|
|
DBG("AmB2BSession::onB2BEvent\n");
|
|
|
|
switch (ev->event_id) {
|
|
|
|
case B2BSipRequest:
|
|
{
|
|
B2BSipRequestEvent* req_ev = dynamic_cast<B2BSipRequestEvent*>(ev);
|
|
assert(req_ev);
|
|
|
|
DBG("B2BSipRequest: %s (fwd=%s)\n", req_ev->req.method.c_str(), req_ev->forward? "true" : "false");
|
|
|
|
if (req_ev->forward) {
|
|
|
|
/* Check Max-Forwards first */
|
|
if (req_ev->req.max_forwards == 0) {
|
|
relayError(req_ev->req.method,req_ev->req.cseq, true,483,SIP_REPLY_TOO_MANY_HOPS);
|
|
return;
|
|
}
|
|
|
|
if (req_ev->req.method == SIP_METH_INVITE && dlg->getUACInvTransPending()) {
|
|
/* don't relay INVITE if INV trans pending */
|
|
DBG("Not sip-relaying INVITE with pending INV transaction, b2b-relaying 491 pending\n");
|
|
relayError(req_ev->req.method, req_ev->req.cseq, true, 491, SIP_REPLY_PENDING);
|
|
return;
|
|
}
|
|
|
|
if (req_ev->req.method == SIP_METH_BYE && dlg->getStatus() != AmBasicSipDialog::Connected) {
|
|
DBG("not sip-relaying BYE in not connected dlg, b2b-relaying 200 OK\n");
|
|
relayError(req_ev->req.method, req_ev->req.cseq, true, 200, "OK");
|
|
return;
|
|
}
|
|
|
|
/* relay, unless it's a BYE dedicated for other leg with a faked 183 */
|
|
int res = 0;
|
|
|
|
if (req_ev->req.method == SIP_METH_BYE && dlg->getFaked183As200()) {
|
|
DBG("This BYE will not forwarded, because other leg is a faked 183 to 200OK. CANCEL required.\n");
|
|
/* for now just answer with 200 OK, later on we must send CANCEL to the Early stage leg */
|
|
sl_reply(req_ev->req.method, req_ev->req.cseq, true, 200, "OK");
|
|
} else {
|
|
res = relaySip(req_ev->req); /* most requests get here */
|
|
}
|
|
|
|
if (res < 0) {
|
|
/* reply relayed request internally */
|
|
relayError(req_ev->req.method, req_ev->req.cseq, true, res);
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (req_ev->req.method == SIP_METH_BYE) {
|
|
/* CANCEL is handled differently: other side has already
|
|
sent a terminate event.
|
|
|| (req_ev->req.method == SIP_METH_CANCEL) */
|
|
|
|
if (dlg->getFaked183As200()) onOtherCancel();
|
|
else onOtherBye(req_ev->req);
|
|
}
|
|
}
|
|
return;
|
|
|
|
case B2BSipReply:
|
|
{
|
|
B2BSipReplyEvent* reply_ev = dynamic_cast<B2BSipReplyEvent*>(ev);
|
|
assert(reply_ev);
|
|
|
|
DBG("B2BSipReply: %i %s (fwd=%s)\n", reply_ev->reply.code,
|
|
reply_ev->reply.reason.c_str(),
|
|
reply_ev->forward? "true" : "false");
|
|
|
|
DBG("B2BSipReply: content-type = %s\n", reply_ev->reply.body.getCTStr().c_str());
|
|
|
|
if (reply_ev->forward) {
|
|
std::map<int,AmSipRequest>::iterator t_req = recvd_req.find(reply_ev->reply.cseq);
|
|
|
|
if (t_req != recvd_req.end()) {
|
|
|
|
if ((reply_ev->reply.code >= 300) && (reply_ev->reply.code <= 305) &&
|
|
!reply_ev->reply.contact.empty()) {
|
|
/* relay with Contact in 300 - 305 redirect messages */
|
|
AmSipReply n_reply(reply_ev->reply);
|
|
n_reply.hdrs += SIP_HDR_COLSP(SIP_HDR_CONTACT) + reply_ev->reply.contact + CRLF;
|
|
|
|
if(relaySip(t_req->second,n_reply) < 0) {
|
|
terminateOtherLeg();
|
|
terminateLeg();
|
|
}
|
|
|
|
} else {
|
|
/* relay response */
|
|
if (relaySip(t_req->second,reply_ev->reply) < 0) {
|
|
terminateOtherLeg();
|
|
terminateLeg();
|
|
}
|
|
}
|
|
|
|
} else {
|
|
DBG("Cannot relay reply: request already replied (code=%u;cseq=%u;call-id=%s)",
|
|
reply_ev->reply.code,
|
|
reply_ev->reply.cseq,
|
|
reply_ev->reply.callid.c_str());
|
|
}
|
|
|
|
} else {
|
|
|
|
/* ensure that 'P-DSM-App: <app-name>;early-announce=force' is not present */
|
|
if (reply_ev->reply.code == 183 && !dlg->getForcedEarlyAnnounce()) {
|
|
dlg->setForcedEarlyAnnounce(isDSMEarlyAnnounceForced(reply_ev->reply.hdrs));
|
|
}
|
|
|
|
/* don't forget to reset the force_early_announce, if 200 OK in the same leg received */
|
|
if (SIP_IS_200_CLASS(reply_ev->reply.code) && dlg->getForcedEarlyAnnounce()) {
|
|
dlg->setForcedEarlyAnnounce(false);
|
|
}
|
|
|
|
/* check whether not-forwarded (locally initiated)
|
|
* INV/UPD transaction changed session in other leg */
|
|
if (SIP_IS_200_CLASS(reply_ev->reply.code) &&
|
|
(!reply_ev->reply.body.empty()) &&
|
|
(reply_ev->reply.cseq_method == SIP_METH_INVITE ||
|
|
reply_ev->reply.cseq_method == SIP_METH_UPDATE))
|
|
{
|
|
if (updateSessionDescription(reply_ev->reply.body)) {
|
|
|
|
if (reply_ev->reply.cseq != est_invite_cseq) {
|
|
if (dlg->getUACInvTransPending()) {
|
|
DBG("changed session, but UAC INVITE trans pending\n");
|
|
/* todo(?): save until trans is finished? */
|
|
return;
|
|
}
|
|
DBG("session description changed - refreshing\n");
|
|
sendEstablishedReInvite();
|
|
} else {
|
|
DBG("reply to establishing INVITE request - not refreshing\n");
|
|
}
|
|
}
|
|
|
|
/* 183 - coming from DSM application */
|
|
} else if (reply_ev->reply.code == 183 &&
|
|
dlg->getForcedEarlyAnnounce() &&
|
|
!reply_ev->reply.body.empty()) {
|
|
|
|
if (updateSessionDescription(reply_ev->reply.body)) {
|
|
if (dlg->getUACInvTransPending()) {
|
|
DBG("changed session, but UAC INVITE trans pending\n");
|
|
} else {
|
|
DBG("Received 183 with <;%s=%s>, refreshing media session.\n", DSM_PARAM_EARLY_AN, DSM_VALUE_FORCE);
|
|
|
|
setMute(true);
|
|
AmMediaProcessor::instance()->removeSession(this);
|
|
|
|
if (sendEstablishedReInvite() < 0) {
|
|
ERROR("could not re-Invite after locally initiated request"
|
|
"in B2B leg changed session (this='%s', other='%s')\n",
|
|
getLocalTag().c_str(), other_id.c_str());
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Processing of the playback completion from DSM applications
|
|
* For now we only support: 480 Unavailable and 486 Busy/603 Decline.
|
|
* Exceptionally 487 is added, for cases like:
|
|
* a call to HG, where nobody answers and the timeout triggers a cancelation of all the legs.
|
|
*/
|
|
} else if ((reply_ev->reply.code == 480 ||
|
|
reply_ev->reply.code == 486 ||
|
|
reply_ev->reply.code == 487 ||
|
|
reply_ev->reply.code == 603)
|
|
&& dlg->getStatus() == AmSipDialog::Connected) {
|
|
|
|
/* TT#188800, if this is a completion of the playback of one of the DSM applications,
|
|
(office hours, play last caller, pre announce, early dbprompt)
|
|
in the session, which has had AA or a transfer before going to this DSM application,
|
|
then a caller is now likely in the connected state, and requires BYE, not 480 */
|
|
if (isDSMPlaybackFinished(reply_ev->reply.hdrs)) {
|
|
DBG("This is the end of DSM playback, the caller is in the connected state.\n");
|
|
DBG("Terminating the original leg with BYE, instead of 480.\n");
|
|
terminateLeg();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return;
|
|
|
|
case B2BTerminateLeg:
|
|
DBG("terminateLeg()\n");
|
|
terminateLeg();
|
|
break;
|
|
}
|
|
|
|
/* ERROR("unknown event caught\n"); */
|
|
}
|
|
|
|
bool AmB2BSession::getMappedReferID(unsigned int refer_id,
|
|
unsigned int& mapped_id) const
|
|
{
|
|
map<unsigned int, unsigned int>::const_iterator id_it =
|
|
refer_id_map.find(refer_id);
|
|
if(id_it != refer_id_map.end()) {
|
|
mapped_id = id_it->second;
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
void AmB2BSession::insertMappedReferID(unsigned int refer_id,
|
|
unsigned int mapped_id)
|
|
{
|
|
refer_id_map[refer_id] = mapped_id;
|
|
}
|
|
|
|
void AmB2BSession::onSipRequest(const AmSipRequest& req)
|
|
{
|
|
bool fwd = sip_relay_only &&
|
|
(req.method != SIP_METH_CANCEL);
|
|
|
|
if( ((req.method == SIP_METH_SUBSCRIBE) ||
|
|
(req.method == SIP_METH_NOTIFY) ||
|
|
(req.method == SIP_METH_REFER))
|
|
&& !subs->onRequestIn(req) ) {
|
|
return;
|
|
}
|
|
|
|
if(!fwd)
|
|
AmSession::onSipRequest(req);
|
|
else {
|
|
updateRefreshMethod(req.hdrs);
|
|
|
|
if(req.method == SIP_METH_BYE)
|
|
onBye(req);
|
|
}
|
|
|
|
B2BSipRequestEvent* r_ev = new B2BSipRequestEvent(req,fwd);
|
|
|
|
if (fwd) {
|
|
DBG("relaying B2B SIP request (fwd) %s %s\n", r_ev->req.method.c_str(), r_ev->req.r_uri.c_str());
|
|
|
|
if(r_ev->req.method == SIP_METH_NOTIFY) {
|
|
|
|
string event = getHeader(r_ev->req.hdrs,SIP_HDR_EVENT,true);
|
|
string id = get_header_param(event,"id");
|
|
event = strip_header_params(event);
|
|
|
|
if(event == "refer" && !id.empty()) {
|
|
|
|
int id_int=0;
|
|
if(str2int(id,id_int)) {
|
|
|
|
unsigned int mapped_id=0;
|
|
if(getMappedReferID(id_int,mapped_id)) {
|
|
|
|
removeHeader(r_ev->req.hdrs,SIP_HDR_EVENT);
|
|
r_ev->req.hdrs += SIP_HDR_COLSP(SIP_HDR_EVENT) "refer;id="
|
|
+ int2str(mapped_id) + CRLF;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
int res = relayEvent(r_ev);
|
|
if (res == 0) {
|
|
// successfuly relayed, store the request
|
|
if(req.method != SIP_METH_ACK)
|
|
recvd_req.insert(std::make_pair(req.cseq,req));
|
|
}
|
|
else {
|
|
// relay failed, generate error reply
|
|
DBG("relay failed, replying error\n");
|
|
AmSipReply n_reply;
|
|
errCode2RelayedReply(n_reply, res, 500);
|
|
dlg->reply(req, n_reply.code, n_reply.reason);
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
DBG("relaying B2B SIP request %s %s\n", r_ev->req.method.c_str(), r_ev->req.r_uri.c_str());
|
|
relayEvent(r_ev);
|
|
}
|
|
|
|
void AmB2BSession::onRequestSent(const AmSipRequest& req)
|
|
{
|
|
if( ((req.method == SIP_METH_SUBSCRIBE) ||
|
|
(req.method == SIP_METH_NOTIFY) ||
|
|
(req.method == SIP_METH_REFER)) ) {
|
|
subs->onRequestSent(req);
|
|
}
|
|
|
|
AmSession::onRequestSent(req);
|
|
}
|
|
|
|
void AmB2BSession::updateLocalSdp(AmSdp &sdp)
|
|
{
|
|
if (rtp_relay_mode == RTP_Direct) return; // nothing to do
|
|
|
|
if (!media_session) {
|
|
// report missing media session (here we get for rtp_relay_mode == RTP_Relay)
|
|
ERROR("BUG: media session is missing, can't update local SDP\n");
|
|
return; // FIXME: throw an exception here?
|
|
}
|
|
|
|
media_session->replaceConnectionAddress(sdp, a_leg, localMediaIP(), advertisedIP());
|
|
}
|
|
|
|
void AmB2BSession::saveLocalSdpOrigin(const AmSdp& sdp)
|
|
{
|
|
if (sdp_origin.conn.address.empty()) {
|
|
// remember this origin for whole dialog lifetime
|
|
sdp_origin = sdp.origin;
|
|
previous_sdp = sdp;
|
|
previous_origin_sessId = sdp.origin.sessId;
|
|
previous_origin_sessV = sdp.origin.sessV;
|
|
DBG("Remembering initial SDP Origin (Id %s V %s)\n",
|
|
uint128ToStr(sdp.origin.sessId).c_str(), uint128ToStr(sdp.origin.sessV).c_str());
|
|
}
|
|
}
|
|
|
|
void AmB2BSession::updateLocalSdpOrigin(AmSdp& sdp) {
|
|
// fix SDP origin
|
|
if (sdp_origin.conn.address.empty()) {
|
|
saveLocalSdpOrigin(sdp);
|
|
}
|
|
else {
|
|
bool sdp_changed = false;
|
|
// check if Origin Id/Version has changed
|
|
if ((sdp.origin.sessV != previous_origin_sessV) ||
|
|
(sdp.origin.sessId != previous_origin_sessId)){
|
|
sdp_changed = true;
|
|
// remember for next time
|
|
previous_origin_sessId = sdp.origin.sessId;
|
|
previous_origin_sessV = sdp.origin.sessV;
|
|
}
|
|
// use remembered SDP origin
|
|
sdp.origin = sdp_origin;
|
|
// check if SDP has changed (apart from origin)
|
|
if (!sdp_changed) {
|
|
// comparing the AmSdp objects may be unsafe (intialized members...),
|
|
// so comparing resulting SDP string
|
|
string s_sdp; string s_previous_sdp;
|
|
sdp.print(s_sdp); previous_sdp.print(s_previous_sdp);
|
|
if (!(s_sdp == s_previous_sdp)) {
|
|
sdp_changed = true;
|
|
}
|
|
}
|
|
// ...and increase version if changed
|
|
if (sdp_changed) {
|
|
// increase version
|
|
sdp_origin.sessV++;
|
|
// update origin
|
|
sdp.origin = sdp_origin;
|
|
// remember the current SDP for the next time
|
|
previous_sdp = sdp;
|
|
DBG("SDP changed; updating Origin (Id %s V %s)\n",
|
|
uint128ToStr(sdp.origin.sessId).c_str(), uint128ToStr(sdp.origin.sessV).c_str());
|
|
} else {
|
|
DBG("SDP unchanged; keeping Origin (Id %s V %s)\n",
|
|
uint128ToStr(sdp.origin.sessId).c_str(), uint128ToStr(sdp.origin.sessV).c_str());
|
|
}
|
|
}
|
|
}
|
|
|
|
void AmB2BSession::updateLocalBody(AmMimeBody& body)
|
|
{
|
|
AmMimeBody *sdp = body.hasContentType(SIP_APPLICATION_SDP);
|
|
if (!sdp) return;
|
|
|
|
AmSdp parser_sdp;
|
|
if (parser_sdp.parse((const char*)sdp->getPayload())) {
|
|
DBG("SDP parsing failed!\n");
|
|
return; // FIXME: throw an exception here?
|
|
}
|
|
|
|
updateLocalSdp(parser_sdp);
|
|
updateLocalSdpOrigin(parser_sdp);
|
|
|
|
// regenerate SDP
|
|
string n_body;
|
|
parser_sdp.print(n_body);
|
|
sdp->parse(sdp->getCTStr(), (const unsigned char*)n_body.c_str(), n_body.length());
|
|
}
|
|
|
|
void AmB2BSession::updateUACTransCSeq(unsigned int old_cseq, unsigned int new_cseq) {
|
|
if (old_cseq == new_cseq)
|
|
return;
|
|
|
|
TransMap::iterator t = relayed_req.find(old_cseq);
|
|
if (t != relayed_req.end()) {
|
|
relayed_req[new_cseq] = t->second;
|
|
relayed_req.erase(t);
|
|
DBG("updated relayed_req (UAC trans): CSeq %u -> %u\n", old_cseq, new_cseq);
|
|
}
|
|
|
|
if (est_invite_cseq == old_cseq) {
|
|
est_invite_cseq = new_cseq;
|
|
DBG("updated est_invite_cseq: CSeq %u -> %u\n", old_cseq, new_cseq);
|
|
}
|
|
}
|
|
|
|
|
|
void AmB2BSession::onSipReply(const AmSipRequest& req, const AmSipReply& reply,
|
|
AmBasicSipDialog::Status old_dlg_status)
|
|
{
|
|
TransMap::iterator t = relayed_req.find(reply.cseq);
|
|
bool fwd = (t != relayed_req.end()) && (reply.code != 100);
|
|
bool to_tag_reset = isDSMToTagReset(reply.hdrs); /* check P-DSM-App: <app-name>;reset-to-tag=1 */
|
|
|
|
DBG("onSipReply: %s -> %i %s (fwd=%s), c-t=%s\n",
|
|
reply.cseq_method.c_str(), reply.code,reply.reason.c_str(),
|
|
fwd? "true" : "false", reply.body.getCTStr().c_str());
|
|
|
|
/* update last reply for further usage with header getters */
|
|
last_200_reply = reply;
|
|
|
|
if (to_tag_reset && !dlg->getRemoteTag().empty() && reply.code >= 180 && reply.code <= 183 ) {
|
|
DBG("onSipReply: sess %p received %i reply with remote-tag %s", this, reply.code, reply.to_tag.c_str());
|
|
DBG("dlg->getRemoteTag(%s)\n", dlg->getRemoteTag().c_str());
|
|
DBG("dlg->setRemoteTag(%s)\n", reply.to_tag.c_str());
|
|
|
|
/* Overwrite the existing to RemoteTag with the received one in order to store always the last */
|
|
dlg->setRemoteTag(reply.to_tag.c_str());
|
|
} else if(!dlg->getRemoteTag().empty() && dlg->getRemoteTag() != reply.to_tag) {
|
|
DBG("sess %p received %i reply with != to-tag: %s (remote-tag:%s)",
|
|
this, reply.code, reply.to_tag.c_str(),dlg->getRemoteTag().c_str());
|
|
return; /* drop packet */
|
|
}
|
|
|
|
if( ((reply.cseq_method == SIP_METH_SUBSCRIBE) ||
|
|
(reply.cseq_method == SIP_METH_NOTIFY) ||
|
|
(reply.cseq_method == SIP_METH_REFER)) &&
|
|
!subs->onReplyIn(req,reply) )
|
|
{
|
|
DBG("subs.onReplyIn returned false\n");
|
|
return;
|
|
}
|
|
|
|
if (fwd) {
|
|
updateRefreshMethod(reply.hdrs);
|
|
AmSipReply n_reply = reply;
|
|
n_reply.cseq = t->second.cseq;
|
|
|
|
DBG("relaying B2B SIP reply %u %s\n", n_reply.code, n_reply.reason.c_str());
|
|
relayEvent(new B2BSipReplyEvent(n_reply, true, t->second.method, getLocalTag()));
|
|
|
|
if (reply.code >= 200) {
|
|
if ((reply.code < 300) && (t->second.method == SIP_METH_INVITE)) {
|
|
DBG("not removing relayed INVITE transaction yet...\n");
|
|
} else {
|
|
/* grab cseq-mqpping in case of REFER */
|
|
if ((reply.code < 300) && (reply.cseq_method == SIP_METH_REFER)) {
|
|
if (subs->subscriptionExists(SingleSubscription::Subscriber, "refer",int2str(reply.cseq))) {
|
|
/* remember mapping for refer event package event-id */
|
|
insertMappedReferID(reply.cseq,t->second.cseq);
|
|
}
|
|
}
|
|
relayed_req.erase(t);
|
|
}
|
|
}
|
|
|
|
} else {
|
|
AmSession::onSipReply(req, reply, old_dlg_status);
|
|
AmSipReply n_reply = reply;
|
|
|
|
if (est_invite_cseq == reply.cseq) {
|
|
n_reply.cseq = est_invite_other_cseq;
|
|
} else {
|
|
/* correct CSeq for 100 on relayed request (FIXME: why not relayed above?) */
|
|
if (t != relayed_req.end()) {
|
|
n_reply.cseq = t->second.cseq;
|
|
} else {
|
|
/* the reply here will not have the proper cseq for the other side.
|
|
* We should avoid collisions of CSeqs - painful in comparsions with
|
|
* est_invite_cseq where are compared CSeq numbers in different
|
|
* directions. Under presumption that 0 is not used we can use it
|
|
* as 'unspecified cseq' (according to RFC 3261 this seems to be valid
|
|
* value so it need not to work always) */
|
|
n_reply.cseq = 0;
|
|
}
|
|
}
|
|
|
|
DBG("relaying B2B SIP reply %u %s\n", n_reply.code, n_reply.reason.c_str());
|
|
relayEvent(new B2BSipReplyEvent(n_reply, false, reply.cseq_method, getLocalTag()));
|
|
}
|
|
}
|
|
|
|
void AmB2BSession::onReplySent(const AmSipRequest& req, const AmSipReply& reply)
|
|
{
|
|
if( ((reply.cseq_method == SIP_METH_SUBSCRIBE) ||
|
|
(reply.cseq_method == SIP_METH_NOTIFY) ||
|
|
(reply.cseq_method == SIP_METH_REFER)) ) {
|
|
subs->onReplySent(req,reply);
|
|
}
|
|
|
|
if(reply.code >= 200 && reply.cseq_method != SIP_METH_CANCEL){
|
|
if((req.method == SIP_METH_INVITE) && (reply.code >= 300)) {
|
|
DBG("relayed INVITE failed with %u %s\n", reply.code, reply.reason.c_str());
|
|
}
|
|
DBG("recvd_req.erase(<%u,%s>)\n", req.cseq, req.method.c_str());
|
|
recvd_req.erase(reply.cseq);
|
|
}
|
|
|
|
AmSession::onReplySent(req,reply);
|
|
}
|
|
|
|
void AmB2BSession::onInvite2xx(const AmSipReply& reply)
|
|
{
|
|
TransMap::iterator it = relayed_req.find(reply.cseq);
|
|
bool req_fwded = it != relayed_req.end();
|
|
|
|
/* update last reply for further usage with header getters */
|
|
last_200_reply = reply;
|
|
|
|
if(!req_fwded) {
|
|
DBG("req not fwded\n");
|
|
AmSession::onInvite2xx(reply);
|
|
} else {
|
|
DBG("no 200 ACK now: waiting for the 200 ACK from the other side...\n");
|
|
}
|
|
}
|
|
|
|
int AmB2BSession::onSdpCompleted(const AmSdp& local_sdp, const AmSdp& remote_sdp)
|
|
{
|
|
if (rtp_relay_mode != RTP_Direct) {
|
|
if (!media_session) {
|
|
// report missing media session (here we get for rtp_relay_mode == RTP_Relay)
|
|
ERROR("BUG: media session is missing, can't update SDP\n");
|
|
}
|
|
else {
|
|
media_session->updateStreams(a_leg, local_sdp, remote_sdp, this);
|
|
}
|
|
}
|
|
|
|
if(hasRtpStream() && RTPStream()->getSdpMediaIndex() >= 0) {
|
|
if(!sip_relay_only){
|
|
return AmSession::onSdpCompleted(local_sdp,remote_sdp);
|
|
}
|
|
DBG("sip_relay_only = true: doing nothing!\n");
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int AmB2BSession::relayEvent(AmEvent* ev)
|
|
{
|
|
DBG("AmB2BSession::relayEvent: to other_id='%s'\n",
|
|
other_id.c_str());
|
|
|
|
if(!other_id.empty()) {
|
|
if (!AmSessionContainer::instance()->postEvent(other_id,ev))
|
|
return -1;
|
|
} else {
|
|
delete ev;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
void AmB2BSession::onOtherBye(const AmSipRequest& req)
|
|
{
|
|
DBG("onOtherBye()\n");
|
|
terminateLeg();
|
|
}
|
|
|
|
void AmB2BSession::onOtherCancel()
|
|
{
|
|
DBG("The other leg will be canceled, because still in the Early stage.\n");
|
|
|
|
setStopped();
|
|
clearRtpReceiverRelay();
|
|
dlg->cancel();
|
|
}
|
|
|
|
bool AmB2BSession::onOtherReply(const AmSipReply& reply)
|
|
{
|
|
if(reply.code >= 300)
|
|
setStopped();
|
|
|
|
return false;
|
|
}
|
|
|
|
void AmB2BSession::terminateLeg()
|
|
{
|
|
setStopped();
|
|
|
|
clearRtpReceiverRelay();
|
|
|
|
dlg->bye("", SIP_FLAGS_VERBATIM);
|
|
}
|
|
|
|
void AmB2BSession::terminateOtherLeg()
|
|
{
|
|
if (!other_id.empty())
|
|
relayEvent(new B2BEvent(B2BTerminateLeg));
|
|
}
|
|
|
|
void AmB2BSession::onRtpTimeout() {
|
|
DBG("RTP Timeout, ending other leg\n");
|
|
terminateOtherLeg();
|
|
AmSession::onRtpTimeout();
|
|
}
|
|
|
|
void AmB2BSession::onSessionTimeout() {
|
|
DBG("Session Timer: Timeout, ending other leg\n");
|
|
terminateOtherLeg();
|
|
AmSession::onSessionTimeout();
|
|
}
|
|
|
|
void AmB2BSession::onRemoteDisappeared(const AmSipReply& reply) {
|
|
if (dlg && dlg->getStatus() == AmBasicSipDialog::Connected) {
|
|
DBG("%c leg: remote unreachable, ending other leg\n", a_leg?'A':'B');
|
|
terminateOtherLeg();
|
|
AmSession::onRemoteDisappeared(reply);
|
|
}
|
|
}
|
|
|
|
void AmB2BSession::onNoAck(unsigned int cseq)
|
|
{
|
|
DBG("OnNoAck(%u): terminate other leg.\n",cseq);
|
|
terminateOtherLeg();
|
|
AmSession::onNoAck(cseq);
|
|
}
|
|
|
|
bool AmB2BSession::saveSessionDescription(const AmMimeBody& body) {
|
|
|
|
const AmMimeBody* sdp_body = body.hasContentType(SIP_APPLICATION_SDP);
|
|
if(!sdp_body)
|
|
return false;
|
|
|
|
DBG("saving session description (%s, %.*s...)\n",
|
|
sdp_body->getCTStr().c_str(), 50, sdp_body->getPayload());
|
|
|
|
dlg->established_body = *sdp_body;
|
|
|
|
const char* cmp_body_begin = (const char*)sdp_body->getPayload();
|
|
size_t cmp_body_length = sdp_body->getLen();
|
|
|
|
#define skip_line \
|
|
while (cmp_body_length && *cmp_body_begin != '\n') { \
|
|
cmp_body_begin++; \
|
|
cmp_body_length--; \
|
|
} \
|
|
if (cmp_body_length) { \
|
|
cmp_body_begin++; \
|
|
cmp_body_length--; \
|
|
}
|
|
|
|
if (cmp_body_length) {
|
|
// for SDP, skip v and o line
|
|
// (o might change even if SDP unchanged)
|
|
skip_line;
|
|
skip_line;
|
|
}
|
|
|
|
body_hash = hashlittle(cmp_body_begin, cmp_body_length, 0);
|
|
return true;
|
|
}
|
|
|
|
bool AmB2BSession::updateSessionDescription(const AmMimeBody& body) {
|
|
|
|
const AmMimeBody* sdp_body = body.hasContentType(SIP_APPLICATION_SDP);
|
|
if(!sdp_body)
|
|
return false;
|
|
|
|
const char* cmp_body_begin = (const char*)sdp_body->getPayload();
|
|
size_t cmp_body_length = sdp_body->getLen();
|
|
if (cmp_body_length) {
|
|
// for SDP, skip v and o line
|
|
// (o might change even if SDP unchanged)
|
|
skip_line;
|
|
skip_line;
|
|
}
|
|
|
|
#undef skip_line
|
|
|
|
uint32_t new_body_hash = hashlittle(cmp_body_begin, cmp_body_length, 0);
|
|
|
|
if (body_hash != new_body_hash) {
|
|
DBG("session description changed - saving (%s, %.*s...)\n",
|
|
sdp_body->getCTStr().c_str(), 50, sdp_body->getPayload());
|
|
body_hash = new_body_hash;
|
|
dlg->established_body = body;
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
int AmB2BSession::sendEstablishedReInvite(const std::string &hdrs) {
|
|
if (dlg->established_body.empty()) {
|
|
ERROR("trying to re-INVITE with saved description, but none saved\n");
|
|
return -1;
|
|
}
|
|
|
|
DBG("sending re-INVITE with saved session description\n");
|
|
|
|
try {
|
|
AmMimeBody body(dlg->established_body); // contains only SDP
|
|
updateLocalBody(body);
|
|
return dlg->reinvite(hdrs, &body, SIP_FLAGS_VERBATIM);
|
|
} catch (const string& s) {
|
|
ERROR("sending established SDP reinvite: %s\n", s.c_str());
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
bool AmB2BSession::refresh(int flags) {
|
|
// no session refresh if not connected
|
|
if (dlg->getStatus() != AmSipDialog::Connected)
|
|
return false;
|
|
|
|
DBG(" AmB2BSession::refresh: refreshing session\n");
|
|
// not in B2B mode
|
|
if (other_id.empty() ||
|
|
// UPDATE as refresh handled like normal session
|
|
refresh_method == REFRESH_UPDATE) {
|
|
return AmSession::refresh(SIP_FLAGS_VERBATIM);
|
|
}
|
|
|
|
// refresh with re-INVITE
|
|
if (dlg->getUACInvTransPending()) {
|
|
DBG("INVITE transaction pending - not refreshing now\n");
|
|
return false;
|
|
}
|
|
return sendEstablishedReInvite() == 0;
|
|
}
|
|
|
|
int AmB2BSession::relaySip(const AmSipRequest& req)
|
|
{
|
|
AmMimeBody body(req.body);
|
|
|
|
if ((req.method == SIP_METH_INVITE ||
|
|
req.method == SIP_METH_UPDATE ||
|
|
req.method == SIP_METH_ACK ||
|
|
req.method == SIP_METH_PRACK))
|
|
{
|
|
updateLocalBody(body);
|
|
}
|
|
|
|
/* all methods apart ACK */
|
|
if (req.method != "ACK") {
|
|
relayed_req[dlg->cseq] = req;
|
|
|
|
const string* hdrs = &req.hdrs;
|
|
string m_hdrs;
|
|
|
|
/* translate RAck for PRACK */
|
|
if (req.method == SIP_METH_PRACK && req.rseq) {
|
|
TransMap::iterator t;
|
|
|
|
for (t=relayed_req.begin(); t != relayed_req.end(); t++)
|
|
{
|
|
if (t->second.cseq == req.rack_cseq) {
|
|
m_hdrs = req.hdrs +
|
|
SIP_HDR_COLSP(SIP_HDR_RACK) +
|
|
int2str(req.rseq) +
|
|
" " +
|
|
int2str(t->first) +
|
|
" " +
|
|
req.rack_method +
|
|
CRLF;
|
|
hdrs = &m_hdrs;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (t==relayed_req.end()) {
|
|
WARN("Transaction with CSeq %d not found for translating RAck cseq\n", req.rack_cseq);
|
|
}
|
|
}
|
|
|
|
DBG("relaying SIP request %s %s\n", req.method.c_str(), req.r_uri.c_str());
|
|
|
|
int err = dlg->sendRequest(req.method, &body, *hdrs, SIP_FLAGS_VERBATIM);
|
|
|
|
if(err < 0){
|
|
ERROR("dlg->sendRequest() failed\n");
|
|
return err;
|
|
}
|
|
|
|
if ((req.method == SIP_METH_INVITE ||
|
|
req.method == SIP_METH_UPDATE) &&
|
|
!req.body.empty())
|
|
{
|
|
saveSessionDescription(req.body);
|
|
}
|
|
|
|
} else {
|
|
/* all other methods (most probably 200OK for ACK) */
|
|
TransMap::iterator t = relayed_req.begin();
|
|
|
|
while (t != relayed_req.end())
|
|
{
|
|
if (t->second.cseq == req.cseq)
|
|
break;
|
|
t++;
|
|
}
|
|
|
|
if (t == relayed_req.end()) {
|
|
ERROR("transaction for ACK not found in relayed requests\n");
|
|
/* FIXME: local body (if updated) should be discarded here */
|
|
return -1;
|
|
}
|
|
|
|
DBG("sending relayed 200 ACK\n");
|
|
|
|
int err = dlg->send_200_ack(t->first, &body, req.hdrs, SIP_FLAGS_VERBATIM);
|
|
if(err < 0) {
|
|
ERROR("dlg->send_200_ack() failed\n");
|
|
return err;
|
|
}
|
|
|
|
if (!req.body.empty() &&
|
|
(t->second.method == SIP_METH_INVITE))
|
|
{
|
|
/* delayed SDP negotiation - save SDP */
|
|
saveSessionDescription(req.body);
|
|
}
|
|
|
|
relayed_req.erase(t);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int AmB2BSession::relaySip(const AmSipRequest& orig, const AmSipReply& reply)
|
|
{
|
|
const string* hdrs = &reply.hdrs;
|
|
string m_hdrs;
|
|
const string method(orig.method);
|
|
|
|
if (reply.rseq != 0) {
|
|
m_hdrs = reply.hdrs +
|
|
SIP_HDR_COLSP(SIP_HDR_RSEQ) + int2str(reply.rseq) + CRLF;
|
|
hdrs = &m_hdrs;
|
|
}
|
|
|
|
AmMimeBody body(reply.body);
|
|
if ((orig.method == SIP_METH_INVITE ||
|
|
orig.method == SIP_METH_UPDATE ||
|
|
orig.method == SIP_METH_ACK ||
|
|
orig.method == SIP_METH_PRACK))
|
|
{
|
|
updateLocalBody(body);
|
|
}
|
|
|
|
DBG("relaying SIP reply %u %s\n", reply.code, reply.reason.c_str());
|
|
|
|
int flags = SIP_FLAGS_VERBATIM;
|
|
if(reply.to_tag.empty())
|
|
flags |= SIP_FLAGS_NOTAG;
|
|
|
|
int err = dlg->reply(orig,reply.code,reply.reason,
|
|
&body, *hdrs, flags);
|
|
|
|
if(err < 0){
|
|
ERROR("dlg->reply() failed\n");
|
|
return err;
|
|
}
|
|
|
|
if ((method == SIP_METH_INVITE ||
|
|
method == SIP_METH_UPDATE) &&
|
|
!reply.body.empty()) {
|
|
saveSessionDescription(reply.body);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
void AmB2BSession::setRtpRelayMode(RTPRelayMode mode)
|
|
{
|
|
DBG("enabled RTP relay mode for B2B call '%s'\n",
|
|
getLocalTag().c_str());
|
|
|
|
rtp_relay_mode = mode;
|
|
}
|
|
|
|
void AmB2BSession::setRtpInterface(int relay_interface) {
|
|
DBG("setting RTP interface for session '%s' to %i\n",
|
|
getLocalTag().c_str(), relay_interface);
|
|
rtp_interface = relay_interface;
|
|
}
|
|
|
|
void AmB2BSession::setRtpRelayForceSymmetricRtp(bool force_symmetric) {
|
|
rtp_relay_force_symmetric_rtp = force_symmetric;
|
|
}
|
|
|
|
void AmB2BSession::setRtpRelayTransparentSeqno(bool transparent) {
|
|
rtp_relay_transparent_seqno = transparent;
|
|
}
|
|
|
|
void AmB2BSession::setRtpRelayTransparentSSRC(bool transparent) {
|
|
rtp_relay_transparent_ssrc = transparent;
|
|
}
|
|
|
|
void AmB2BSession::setEnableDtmfTranscoding(bool enable) {
|
|
enable_dtmf_transcoding = enable;
|
|
}
|
|
|
|
void AmB2BSession::setEnableDtmfRtpFiltering(bool enable) {
|
|
enable_dtmf_rtp_filtering = enable;
|
|
}
|
|
|
|
void AmB2BSession::setEnableDtmfRtpDetection(bool enable) {
|
|
enable_dtmf_rtp_detection = enable;
|
|
}
|
|
|
|
void AmB2BSession::getLowFiPLs(vector<SdpPayload>& lowfi_payloads) const {
|
|
lowfi_payloads = this->lowfi_payloads;
|
|
}
|
|
|
|
void AmB2BSession::setLowFiPLs(const vector<SdpPayload>& lowfi_payloads) {
|
|
this->lowfi_payloads = lowfi_payloads;
|
|
}
|
|
|
|
void AmB2BSession::clearRtpReceiverRelay() {
|
|
switch (rtp_relay_mode) {
|
|
|
|
case RTP_Relay:
|
|
case RTP_Transcoding:
|
|
if (media_session) {
|
|
media_session->stop(a_leg);
|
|
media_session->releaseReference();
|
|
media_session = NULL;
|
|
}
|
|
break;
|
|
|
|
case RTP_Direct:
|
|
// nothing to do
|
|
break;
|
|
}
|
|
}
|
|
|
|
void AmB2BSession::computeRelayMask(const SdpMedia &m, bool &enable, PayloadMask &mask)
|
|
{
|
|
int te_pl = -1;
|
|
enable = false;
|
|
|
|
mask.clear();
|
|
|
|
// walk through the media lines and find the telephone-event payload
|
|
for (std::vector<SdpPayload>::const_iterator i = m.payloads.begin();
|
|
i != m.payloads.end(); ++i)
|
|
{
|
|
// do not mark telephone-event payload for relay
|
|
if(!strcasecmp("telephone-event",i->encoding_name.c_str())){
|
|
te_pl = i->payload_type;
|
|
}
|
|
else {
|
|
enable = true;
|
|
}
|
|
}
|
|
|
|
if(!enable)
|
|
return;
|
|
|
|
if(te_pl > 0) {
|
|
DBG("unmarking telephone-event payload %d for relay\n", te_pl);
|
|
mask.set(te_pl);
|
|
}
|
|
|
|
DBG("marking all other payloads for relay\n");
|
|
mask.invert();
|
|
}
|
|
|
|
|
|
//
|
|
// AmB2BCallerSession methods
|
|
//
|
|
|
|
AmB2BCallerSession::AmB2BCallerSession()
|
|
: AmB2BSession(),
|
|
callee_status(None), sip_relay_early_media_sdp(false)
|
|
{
|
|
a_leg = true;
|
|
}
|
|
|
|
AmB2BCallerSession::~AmB2BCallerSession()
|
|
{
|
|
}
|
|
|
|
void AmB2BCallerSession::set_sip_relay_early_media_sdp(bool r)
|
|
{
|
|
sip_relay_early_media_sdp = r;
|
|
}
|
|
|
|
void AmB2BCallerSession::terminateLeg()
|
|
{
|
|
AmB2BSession::terminateLeg();
|
|
}
|
|
|
|
void AmB2BCallerSession::terminateOtherLeg()
|
|
{
|
|
AmB2BSession::terminateOtherLeg();
|
|
callee_status = None;
|
|
}
|
|
|
|
void AmB2BCallerSession::onB2BEvent(B2BEvent* ev)
|
|
{
|
|
bool processed = false;
|
|
|
|
if (ev->event_id == B2BSipReply) {
|
|
|
|
AmSipReply& reply = ((B2BSipReplyEvent*)ev)->reply;
|
|
|
|
/* update last reply for further usage with header getters */
|
|
last_200_reply = reply;
|
|
|
|
if (getOtherId().empty()) {
|
|
DBG("B2BSipReply: other_id empty (reply code=%i; method=%s; callid=%s; from_tag=%s; to_tag=%s; cseq=%i)\n",
|
|
reply.code,reply.cseq_method.c_str(),reply.callid.c_str(),reply.from_tag.c_str(),
|
|
reply.to_tag.c_str(),reply.cseq);
|
|
|
|
} else if (getOtherId() != reply.from_tag) { /* was: local_tag */
|
|
DBG("Dialog mismatch! (oi=%s;ft=%s)\n", getOtherId().c_str(), reply.from_tag.c_str());
|
|
return;
|
|
}
|
|
|
|
DBG("%u %s reply received from other leg\n", reply.code, reply.reason.c_str());
|
|
|
|
switch (callee_status) {
|
|
case NoReply:
|
|
case Ringing:
|
|
if (reply.cseq == invite_req.cseq) {
|
|
|
|
/* get possibly passed headers for updates towards caller */
|
|
string hdrs;
|
|
map<string,string>::const_iterator hdrs_it;
|
|
hdrs_it = ((B2BSipReplyEvent*)ev)->params.find("hdrs");
|
|
if (hdrs_it != ((B2BSipReplyEvent*)ev)->params.end()) {
|
|
hdrs = hdrs_it->second;
|
|
DBG("Got some headers, which can later be used for re-inviting the caller: '%s'\n", hdrs.c_str());
|
|
}
|
|
|
|
if (reply.code < 200) {
|
|
if ((!sip_relay_only) &&
|
|
(reply.code>=180 && reply.code<=183 && (!reply.body.empty()))) {
|
|
|
|
/* save early media SDP */
|
|
updateSessionDescription(reply.body);
|
|
|
|
if (sip_relay_early_media_sdp) {
|
|
if (reinviteCaller(reply, hdrs)) {
|
|
ERROR("re-INVITEing caller for early session failed - stopping this and other leg\n");
|
|
terminateOtherLeg();
|
|
terminateLeg();
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
callee_status = Ringing;
|
|
|
|
} else if(reply.code < 300) {
|
|
|
|
callee_status = Connected;
|
|
DBG("setting callee status to connected\n");
|
|
|
|
if (!sip_relay_only) {
|
|
DBG("received 200 class reply to establishing INVITE: "
|
|
"switching to SIP relay only mode, sending re-INVITE to caller\n");
|
|
|
|
sip_relay_only = true;
|
|
|
|
if (reinviteCaller(reply, hdrs)) {
|
|
ERROR("re-INVITEing caller failed - stopping this and other leg\n");
|
|
terminateOtherLeg();
|
|
terminateLeg();
|
|
}
|
|
}
|
|
|
|
} else {
|
|
/* TODO: terminated my own leg instead? (+ clear_other()) */
|
|
terminateOtherLeg();
|
|
}
|
|
|
|
processed = onOtherReply(reply);
|
|
}
|
|
break;
|
|
|
|
default:
|
|
DBG("reply from callee: %u %s\n",reply.code,reply.reason.c_str());
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!processed)
|
|
AmB2BSession::onB2BEvent(ev);
|
|
}
|
|
|
|
int AmB2BCallerSession::relayEvent(AmEvent* ev)
|
|
{
|
|
if(getOtherId().empty() && !getStopped()){
|
|
|
|
bool create_callee = false;
|
|
B2BSipEvent* sip_ev = dynamic_cast<B2BSipEvent*>(ev);
|
|
if (sip_ev && sip_ev->forward)
|
|
create_callee = true;
|
|
else
|
|
create_callee = dynamic_cast<B2BConnectEvent*>(ev) != NULL;
|
|
|
|
if (create_callee) {
|
|
createCalleeSession();
|
|
if (getOtherId().length()) {
|
|
MONITORING_LOG(getLocalTag().c_str(), "b2b_leg", getOtherId().c_str());
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
return AmB2BSession::relayEvent(ev);
|
|
}
|
|
|
|
void AmB2BCallerSession::onInvite(const AmSipRequest& req)
|
|
{
|
|
invite_req = req;
|
|
est_invite_cseq = req.cseq;
|
|
|
|
AmB2BSession::onInvite(req);
|
|
}
|
|
|
|
void AmB2BCallerSession::onInviteKeepSDP(const AmSipRequest& req)
|
|
{
|
|
/* save SDP body to re-use if newer request has no SDP */
|
|
AmMimeBody previous_body(invite_req.body);
|
|
invite_req = req;
|
|
|
|
if (invite_req.body.empty() && !previous_body.empty()) {
|
|
invite_req.body = previous_body;
|
|
DBG("Currently processed INVITE has no SDP body, use the one from previous offer.\n");
|
|
}
|
|
|
|
est_invite_cseq = req.cseq;
|
|
|
|
AmB2BSession::onInvite(req);
|
|
}
|
|
|
|
void AmB2BCallerSession::onInvite2xx(const AmSipReply& reply)
|
|
{
|
|
invite_req.cseq = reply.cseq;
|
|
est_invite_cseq = reply.cseq;
|
|
|
|
AmB2BSession::onInvite2xx(reply);
|
|
}
|
|
|
|
void AmB2BCallerSession::onCancel(const AmSipRequest& req)
|
|
{
|
|
terminateOtherLeg();
|
|
terminateLeg();
|
|
}
|
|
|
|
void AmB2BCallerSession::onSystemEvent(AmSystemEvent* ev) {
|
|
if (ev->sys_event == AmSystemEvent::ServerShutdown) {
|
|
terminateOtherLeg();
|
|
}
|
|
|
|
AmB2BSession::onSystemEvent(ev);
|
|
}
|
|
|
|
void AmB2BCallerSession::onRemoteDisappeared(const AmSipReply& reply) {
|
|
DBG("remote unreachable, ending B2BUA call\n");
|
|
clearRtpReceiverRelay();
|
|
|
|
AmB2BSession::onRemoteDisappeared(reply);
|
|
}
|
|
|
|
void AmB2BCallerSession::onBye(const AmSipRequest& req)
|
|
{
|
|
clearRtpReceiverRelay();
|
|
AmB2BSession::onBye(req);
|
|
}
|
|
|
|
void AmB2BCallerSession::connectCallee(const string& remote_party,
|
|
const string& remote_uri,
|
|
bool relayed_invite)
|
|
{
|
|
if(callee_status != None)
|
|
terminateOtherLeg();
|
|
|
|
clear_other();
|
|
|
|
if (relayed_invite) {
|
|
// relayed INVITE - we need to add the original INVITE to
|
|
// list of received (relayed) requests
|
|
recvd_req.insert(std::make_pair(invite_req.cseq,invite_req));
|
|
|
|
// in SIP relay mode from the beginning
|
|
sip_relay_only = true;
|
|
}
|
|
|
|
B2BConnectEvent* ev = new B2BConnectEvent(remote_party,remote_uri);
|
|
|
|
ev->body = invite_req.body;
|
|
ev->hdrs = invite_req.hdrs;
|
|
ev->relayed_invite = relayed_invite;
|
|
ev->r_cseq = invite_req.cseq;
|
|
|
|
DBG("relaying B2B connect event to %s\n", remote_uri.c_str());
|
|
relayEvent(ev);
|
|
callee_status = NoReply;
|
|
}
|
|
|
|
int AmB2BCallerSession::reinviteCaller(const AmSipReply& callee_reply, const string& hdrs)
|
|
{
|
|
return dlg->sendRequest(SIP_METH_INVITE,
|
|
&callee_reply.body,
|
|
hdrs, SIP_FLAGS_VERBATIM);
|
|
}
|
|
|
|
void AmB2BCallerSession::createCalleeSession() {
|
|
AmB2BCalleeSession* callee_session = newCalleeSession();
|
|
if (NULL == callee_session)
|
|
return;
|
|
|
|
AmSipDialog* callee_dlg = callee_session->dlg;
|
|
|
|
setOtherId(AmSession::getNewId());
|
|
|
|
callee_dlg->setLocalTag(getOtherId());
|
|
if (callee_dlg->getCallid().empty())
|
|
callee_dlg->setCallid(AmSession::getNewId());
|
|
|
|
callee_dlg->setLocalParty(dlg->getRemoteParty());
|
|
callee_dlg->setRemoteParty(dlg->getLocalParty());
|
|
callee_dlg->setRemoteUri(dlg->getLocalUri());
|
|
|
|
if (AmConfig::LogSessions) {
|
|
INFO("Starting B2B callee session %s\n",
|
|
callee_session->getLocalTag().c_str());
|
|
}
|
|
|
|
MONITORING_LOG4(getOtherId().c_str(),
|
|
"dir", "out",
|
|
"from", callee_dlg->getLocalParty().c_str(),
|
|
"to", callee_dlg->getRemoteParty().c_str(),
|
|
"ruri", callee_dlg->getRemoteUri().c_str());
|
|
|
|
try {
|
|
initializeRTPRelay(callee_session);
|
|
} catch (...) {
|
|
delete callee_session;
|
|
throw;
|
|
}
|
|
|
|
AmSessionContainer* sess_cont = AmSessionContainer::instance();
|
|
sess_cont->addSession(getOtherId(),callee_session);
|
|
|
|
callee_session->start();
|
|
}
|
|
|
|
AmB2BCalleeSession* AmB2BCallerSession::newCalleeSession()
|
|
{
|
|
return new AmB2BCalleeSession(this);
|
|
}
|
|
|
|
void AmB2BSession::setMediaSession(AmB2BMedia *new_session)
|
|
{
|
|
// FIXME: ignore old media_session? can it be already set here?
|
|
if (media_session) ERROR("BUG: non-empty media session overwritten\n");
|
|
media_session = new_session;
|
|
if (media_session)
|
|
media_session->addReference(); // new reference for me
|
|
}
|
|
|
|
void AmB2BCallerSession::initializeRTPRelay(AmB2BCalleeSession* callee_session) {
|
|
if (!callee_session) return;
|
|
|
|
callee_session->setRtpRelayMode(rtp_relay_mode);
|
|
callee_session->setEnableDtmfTranscoding(enable_dtmf_transcoding);
|
|
callee_session->setEnableDtmfRtpFiltering(enable_dtmf_rtp_filtering);
|
|
callee_session->setEnableDtmfRtpDetection(enable_dtmf_rtp_detection);
|
|
callee_session->setLowFiPLs(lowfi_payloads);
|
|
|
|
if ((rtp_relay_mode == RTP_Relay) || (rtp_relay_mode == RTP_Transcoding)) {
|
|
setMediaSession(new AmB2BMedia(this, callee_session)); // we need to add our reference
|
|
callee_session->setMediaSession(getMediaSession());
|
|
}
|
|
}
|
|
|
|
AmB2BCalleeSession::AmB2BCalleeSession(const string& other_local_tag)
|
|
: AmB2BSession(other_local_tag)
|
|
{
|
|
a_leg = false;
|
|
}
|
|
|
|
AmB2BCalleeSession::AmB2BCalleeSession(const AmB2BCallerSession* caller)
|
|
: AmB2BSession(caller->getLocalTag())
|
|
{
|
|
a_leg = false;
|
|
rtp_relay_mode = caller->getRtpRelayMode();
|
|
rtp_relay_force_symmetric_rtp = caller->getRtpRelayForceSymmetricRtp();
|
|
}
|
|
|
|
AmB2BCalleeSession::~AmB2BCalleeSession() {
|
|
}
|
|
|
|
void AmB2BCalleeSession::onB2BEvent(B2BEvent* ev)
|
|
{
|
|
if(ev->event_id == B2BConnectLeg){
|
|
B2BConnectEvent* co_ev = dynamic_cast<B2BConnectEvent*>(ev);
|
|
if (!co_ev)
|
|
return;
|
|
|
|
MONITORING_LOG3(getLocalTag().c_str(),
|
|
"b2b_leg", getOtherId().c_str(),
|
|
"to", co_ev->remote_party.c_str(),
|
|
"ruri", co_ev->remote_uri.c_str());
|
|
|
|
|
|
dlg->setRemoteParty(co_ev->remote_party);
|
|
dlg->setRemoteUri(co_ev->remote_uri);
|
|
|
|
if (co_ev->relayed_invite) {
|
|
AmSipRequest fake_req;
|
|
fake_req.method = SIP_METH_INVITE;
|
|
fake_req.cseq = co_ev->r_cseq;
|
|
relayed_req[dlg->cseq] = fake_req;
|
|
}
|
|
|
|
AmMimeBody body(co_ev->body);
|
|
try {
|
|
updateLocalBody(body);
|
|
} catch (const string& s) {
|
|
relayError(SIP_METH_INVITE, co_ev->r_cseq, co_ev->relayed_invite, 500,
|
|
SIP_REPLY_SERVER_INTERNAL_ERROR);
|
|
throw;
|
|
}
|
|
|
|
int res = dlg->sendRequest(SIP_METH_INVITE, &body,
|
|
co_ev->hdrs, SIP_FLAGS_VERBATIM);
|
|
if (res < 0) {
|
|
DBG("sending INVITE failed, relaying back error reply\n");
|
|
relayError(SIP_METH_INVITE, co_ev->r_cseq, co_ev->relayed_invite, res);
|
|
|
|
if (co_ev->relayed_invite)
|
|
relayed_req.erase(dlg->cseq);
|
|
|
|
setStopped();
|
|
return;
|
|
}
|
|
|
|
saveSessionDescription(co_ev->body);
|
|
|
|
// save CSeq of establising INVITE
|
|
est_invite_cseq = dlg->cseq - 1;
|
|
est_invite_other_cseq = co_ev->r_cseq;
|
|
|
|
return;
|
|
}
|
|
|
|
AmB2BSession::onB2BEvent(ev);
|
|
}
|
|
|
|
/** EMACS **
|
|
* Local variables:
|
|
* mode: c++
|
|
* c-basic-offset: 2
|
|
* End:
|
|
*/
|