Merge remote-tracking branch 'frafos/sayer/1.4-spce3.0'

1.4.3+spce3.0
Andrew Pogrebennyk 13 years ago
commit df5fb03f23

@ -0,0 +1,171 @@
/*
* Copyright (C) 2013 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 "ReplacesMapper.h"
#include "AmUtils.h"
#include "AmUriParser.h"
#include "AmSipHeaders.h"
bool findTag(const string replaces, const string& tag, size_t& p1, size_t& len);
void fixReplaces(AmSipRequest& req, bool is_invite) {
string replaces;
string refer_to;
AmUriParser refer_target;
vector<string> hdrs; // headers from Refer-To URI
vector<string>::iterator replaces_hdr_it; // Replaces header from Refer-To URI
DBG("Replaces handler: fixing %s request\n", is_invite?"INVITE":"REFER");
if (is_invite) {
replaces = getHeader(req.hdrs, SIP_HDR_REPLACES, true);
if (replaces.empty()) {
DBG("Replaces handler: no Replaces in INVITE, ignoring\n");
return;
}
} else {
refer_to = getHeader(req.hdrs, SIP_HDR_REFER_TO, SIP_HDR_REFER_TO_COMPACT, true);
if (refer_to.empty()) {
DBG("Replaces handler: empty Refer-To header, ignoring\n");
return;
}
size_t pos=0; size_t end=0;
if (!refer_target.parse_contact(refer_to, pos, end)) {
DBG("Replaces handler: unable to parse Refer-To name-addr, ignoring\n");
return;
}
if (refer_target.uri_headers.empty()) {
DBG("Replaces handler: no headers in Refer-To target, ignoring\n");
return;
}
hdrs = explode(refer_target.uri_headers, ";");
for (replaces_hdr_it=hdrs.begin(); replaces_hdr_it != hdrs.end(); replaces_hdr_it++) {
string s = URL_decode(*replaces_hdr_it);
const char* Replaces_str = "Replaces";
if ((s.length() >= 8) &&
!strncmp(Replaces_str, s.c_str(), 8)) {
size_t pos = 8;
while (s.length()>pos && (s[pos] == ' ' || s[pos] == '\t')) pos++;
if (s[pos] != '=')
continue;
pos++;
while (s.length()>pos && (s[pos] == ' ' || s[pos] == '\t')) pos++;
replaces = s.substr(pos);
break;
}
}
if (replaces_hdr_it == hdrs.end()) {
DBG("Replaces handler: no Replaces headers in Refer-To target, ignoring\n");
return;
}
}
DBG("Replaces found: '%s'\n", replaces.c_str());
size_t ftag_begin; size_t ftag_len;
size_t ttag_begin; size_t ttag_len;
size_t cid_len=0;
// todo: parse full replaces header and reconstruct including unknown params
if (!findTag(replaces, "from-tag=", ftag_begin, ftag_len)) {
WARN("Replaces missing 'from-tag', ignoring\n");
return;
}
if (!findTag(replaces, "to-tag=", ttag_begin, ttag_len)) {
WARN("Replaces missing 'to-tag', ignoring\n");
return;
}
while (cid_len < replaces.size() && replaces[cid_len] != ';')
cid_len++;
string ftag = replaces.substr(ftag_begin, ftag_len);
string ttag = replaces.substr(ttag_begin, ttag_len);
string callid = replaces.substr(0, cid_len);
bool early_only = replaces.find("early-only") != string::npos;
DBG("Replaces handler: found callid='%s', ftag='%s', ttag='%s'\n",
callid.c_str(), ftag.c_str(), ttag.c_str());
SBCCallRegistryEntry other_dlg;
if (SBCCallRegistry::lookupCall(ttag, other_dlg)) {
replaces = other_dlg.callid+
";from-tag="+other_dlg.ltag+";to-tag="+other_dlg.rtag;
if (early_only)
replaces += ";early_only";
DBG("Replaces handler: mapped Replaces to: '%s'\n", replaces.c_str());
if (is_invite) {
removeHeader(req.hdrs, SIP_HDR_REPLACES);
req.hdrs+=SIP_HDR_COLSP(SIP_HDR_REPLACES)+replaces+CRLF;
} else {
string replaces_enc = SIP_HDR_REPLACES "="+URL_encode(replaces);
string new_hdrs;
for (vector<string>::iterator it = hdrs.begin(); it != hdrs.end(); it++) {
if (it != hdrs.begin())
new_hdrs+=";";
if (it != replaces_hdr_it) {
// different hdr, just add it
new_hdrs+=*it;
} else {
//reconstructed replaces hdr
new_hdrs+=replaces_enc;
}
}
refer_target.uri_headers=new_hdrs;
removeHeader(req.hdrs, SIP_HDR_REFER_TO);
removeHeader(req.hdrs, SIP_HDR_REFER_TO_COMPACT);
req.hdrs+=SIP_HDR_COLSP(SIP_HDR_REFER_TO)+refer_target.nameaddr_str()+CRLF;
}
} else {
DBG("Replaces handler: call with tag '%s' not found\n", ttag.c_str());
}
}
bool findTag(const string replaces, const string& tag, size_t& p1, size_t& len)
{
size_t i = replaces.find(tag);
if (i == string::npos) return false;
p1 = i+tag.length();
size_t j = replaces.find(';', p1);
if (j != string::npos) {
len = j - p1;
} else {
len = replaces.size() - i;
}
return true;
}

@ -0,0 +1,34 @@
/*
* Copyright (C) 2013 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 _ReplacesMapper_H
#define _ReplacesMapper_H
#include "SBCCallRegistry.h"
#include "AmSipMsg.h"
void fixReplaces(AmSipRequest& req, bool is_invite);
#endif

@ -47,6 +47,8 @@ SBC - feature-wishlist
#include "HeaderFilter.h"
#include "ParamReplacer.h"
#include "SDPFilter.h"
#include "SBCCallRegistry.h"
#include "ReplacesMapper.h"
using std::map;
@ -513,6 +515,7 @@ SBCDialog::SBCDialog(const SBCCallProfile& call_profile)
SBCDialog::~SBCDialog()
{
SBCCallRegistry::removeCall(dlg.local_tag);
}
void SBCDialog::onInvite(const AmSipRequest& req)
@ -727,6 +730,15 @@ void SBCDialog::onInvite(const AmSipRequest& req)
}
}
call_profile.fix_replaces_inv =
replaceParameters(call_profile.fix_replaces_inv, "fix_replaces_inv", REPLACE_VALS);
call_profile.fix_replaces_ref =
replaceParameters(call_profile.fix_replaces_ref, "fix_replaces_ref", REPLACE_VALS);
if (call_profile.fix_replaces_inv == "yes") {
fixReplaces(invite_req, true);
}
#undef REPLACE_VALS
DBG("SBC: connecting to '%s'\n",ruri.c_str());
@ -795,13 +807,21 @@ void SBCDialog::onControlCmd(string& cmd, AmArg& params) {
}
int SBCDialog::relayEvent(AmEvent* ev) {
if ((call_profile.headerfilter != Transparent) &&
(ev->event_id == B2BSipRequest)) {
// header filter
if (ev->event_id == B2BSipRequest) {
B2BSipRequestEvent* req_ev = dynamic_cast<B2BSipRequestEvent*>(ev);
assert(req_ev);
inplaceHeaderFilter(req_ev->req.hdrs,
call_profile.headerfilter_list, call_profile.headerfilter);
assert(req_ev);
if (call_profile.headerfilter != Transparent) {
inplaceHeaderFilter(req_ev->req.hdrs,
call_profile.headerfilter_list, call_profile.headerfilter);
}
if (req_ev->req.method == SIP_METH_REFER &&
call_profile.fix_replaces_ref == "yes") {
fixReplaces(req_ev->req, false);
}
} else {
if (ev->event_id == B2BSipReply) {
if ((call_profile.headerfilter != Transparent) ||
@ -1146,6 +1166,11 @@ void SBCDialog::createCalleeSession()
throw;
}
// A->B
SBCCallRegistry::addCall(dlg.local_tag, SBCCallRegistryEntry(callee_dlg.callid, callee_dlg.local_tag, ""));
// B->A
SBCCallRegistry::addCall(callee_dlg.local_tag, SBCCallRegistryEntry(dlg.callid, dlg.local_tag, dlg.remote_tag));
callee_session->start();
AmSessionContainer* sess_cont = AmSessionContainer::instance();
@ -1169,6 +1194,8 @@ SBCCalleeSession::SBCCalleeSession(const AmB2BCallerSession* caller,
SBCCalleeSession::~SBCCalleeSession() {
if (auth)
delete auth;
SBCCallRegistry::removeCall(dlg.local_tag);
}
inline UACAuthCred* SBCCalleeSession::getCredentials() {
@ -1176,13 +1203,21 @@ inline UACAuthCred* SBCCalleeSession::getCredentials() {
}
int SBCCalleeSession::relayEvent(AmEvent* ev) {
if ((call_profile.headerfilter != Transparent) &&
(ev->event_id == B2BSipRequest)) {
// header filter
if (ev->event_id == B2BSipRequest) {
B2BSipRequestEvent* req_ev = dynamic_cast<B2BSipRequestEvent*>(ev);
assert(req_ev);
inplaceHeaderFilter(req_ev->req.hdrs,
call_profile.headerfilter_list, call_profile.headerfilter);
assert(req_ev);
if (call_profile.headerfilter != Transparent) {
inplaceHeaderFilter(req_ev->req.hdrs,
call_profile.headerfilter_list, call_profile.headerfilter);
}
if (req_ev->req.method == SIP_METH_REFER &&
call_profile.fix_replaces_ref == "yes") {
fixReplaces(req_ev->req, false);
}
} else {
if (ev->event_id == B2BSipReply) {
if ((call_profile.headerfilter != Transparent) ||
@ -1297,18 +1332,25 @@ void SBCCalleeSession::onSipReply(const AmSipReply& reply, int old_dlg_status,
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, old_dlg_status, trans_method);
}
if (NULL == auth) {
// update call registry (unfortunately has to be done always -
// not possible to determine if learned in this reply)
if (!dlg.remote_tag.empty()) {
SBCCallRegistry::updateCall(other_id, dlg.remote_tag);
}
if (NULL == auth) {
AmB2BCalleeSession::onSipReply(reply,old_dlg_status, trans_method);
return;
}
unsigned int cseq_before = dlg.cseq;
if (!auth->onSipReply(reply, old_dlg_status, trans_method)) {
AmB2BCalleeSession::onSipReply(reply, old_dlg_status, trans_method);
AmB2BCalleeSession::onSipReply(reply, old_dlg_status, trans_method);
} else {
if (cseq_before != dlg.cseq) {
DBG("uac_auth consumed reply with cseq %d and resent with cseq %d; "

@ -108,6 +108,9 @@ bool SBCCallProfile::readFromConfiguration(const string& name,
}
}
fix_replaces_inv = cfg.getParameter("fix_replaces_inv");
fix_replaces_ref = cfg.getParameter("fix_replaces_ref");;
sst_enabled = cfg.getParameter("enable_session_timer", "no") == "yes";
use_global_sst_config = !cfg.hasParameter("session_expires");
@ -220,6 +223,9 @@ bool SBCCallProfile::readFromConfiguration(const string& name,
sdpfilter_enabled?"en":"dis", FilterType2String(sdpfilter),
sdpfilter_list.size());
INFO("SBC: fixing Replaces in INVITE: '%s'\n", fix_replaces_inv.c_str());
INFO("SBC: fixing Replaces in REFER: '%s'\n", fix_replaces_ref.c_str());
INFO("SBC: RTP relay %sabled\n", rtprelay_enabled?"en":"dis");
if (rtprelay_enabled) {
if (!force_symmetric_rtp.empty()) {

@ -68,6 +68,9 @@ struct SBCCallProfile {
FilterType sdpfilter;
set<string> sdpfilter_list;
string fix_replaces_inv;
string fix_replaces_ref;
bool sst_enabled;
bool use_global_sst_config;

@ -0,0 +1,79 @@
/*
* Copyright (C) 2013 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 "SBCCallRegistry.h"
#include "log.h"
AmMutex SBCCallRegistry::registry_mutex;
std::map<string, SBCCallRegistryEntry> SBCCallRegistry::registry;
void SBCCallRegistry::addCall(const string& ltag, const SBCCallRegistryEntry& other_dlg) {
registry_mutex.lock();
registry[ltag] = other_dlg;
registry_mutex.unlock();
DBG("SBCCallRegistry: Added call '%s' - mapped to: '%s'\n", ltag.c_str(), other_dlg.ltag.c_str());
}
void SBCCallRegistry::updateCall(const string& ltag, const string& other_rtag) {
registry_mutex.lock();
std::map<string, SBCCallRegistryEntry>::iterator it = registry.find(ltag);
if (it != registry.end()) {
it->second.rtag = other_rtag;
}
registry_mutex.unlock();
DBG("SBCCallRegistry: Updated call '%s' - rtag to: '%s'\n", ltag.c_str(), other_rtag.c_str());
}
bool SBCCallRegistry::lookupCall(const string& ltag, SBCCallRegistryEntry& other_dlg) {
bool res = false;
registry_mutex.lock();
std::map<string, SBCCallRegistryEntry>::iterator it = registry.find(ltag);
if (it != registry.end()) {
res = true;
other_dlg = it->second;
}
registry_mutex.unlock();
if (res) {
DBG("SBCCallRegistry: found call mapping '%s' -> '%s'/'%s'/'%s'\n",
ltag.c_str(), other_dlg.ltag.c_str(), other_dlg.rtag.c_str(), other_dlg.callid.c_str());
} else {
DBG("SBCCallRegistry: no call mapping found for '%s'\n", ltag.c_str());
}
return res;
}
void SBCCallRegistry::removeCall(const string& ltag) {
registry_mutex.lock();
registry.erase(ltag);
registry_mutex.unlock();
DBG("SBCCallRegistry: removed entry for call '%s'\n", ltag.c_str());
}

@ -0,0 +1,61 @@
/*
* Copyright (C) 2013 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 _SBCCallRegistry_H
#define _SBCCallRegistry_H
#include "AmThread.h"
#include <string>
using std::string;
#include <map>
struct SBCCallRegistryEntry
{
string ltag;
string rtag;
string callid;
SBCCallRegistryEntry() { }
SBCCallRegistryEntry(const string& callid, const string& ltag, const string& rtag)
: ltag(ltag), rtag(rtag), callid(callid) { }
};
class SBCCallRegistry
{
static AmMutex registry_mutex;
static std::map<string, SBCCallRegistryEntry> registry;
public:
SBCCallRegistry() { }
~SBCCallRegistry() { }
static void addCall(const string& ltag, const SBCCallRegistryEntry& other_dlg);
static void updateCall(const string& ltag, const string& other_rtag);
static bool lookupCall(const string& ltag, SBCCallRegistryEntry& other_dlg);
static void removeCall(const string& ltag);
};
#endif

@ -46,6 +46,10 @@
# translate some 6xx class replies to 4xx class:
#reply_translations="603=>488 Not acceptable here|600=>406 Not Acceptable"
## fix replaces for call transfers
# fix_replaces_inv=yes
# fix_replaces_ref=yes
## authentication:
#enable_auth=yes
#auth_user=$P(u)

@ -12,6 +12,7 @@
#define SIP_METH_SUBSCRIBE "SUBSCRIBE"
#define SIP_METH_NOTIFY "NOTIFY"
#define SIP_METH_CANCEL "CANCEL"
#define SIP_METH_REFER "REFER"
#define SIP_HDR_FROM "From"
#define SIP_HDR_TO "To"
@ -27,6 +28,7 @@
#define SIP_HDR_P_ASSERTED_IDENTITY "P-Asserted-Identity"
#define SIP_HDR_P_PREFERRED_IDENTITY "P-Preferred-Identity"
#define SIP_HDR_REFER_TO "Refer-To"
#define SIP_HDR_REFER_TO_COMPACT "r"
#define SIP_HDR_EXPIRES "Expires"
#define SIP_HDR_SESSION_EXPIRES "Session-Expires"
#define SIP_HDR_MIN_SE "Min-SE"
@ -41,6 +43,7 @@
#define SIP_HDR_ACCEPT "Accept"
#define SIP_HDR_EVENT "Event"
#define SIP_HDR_SUBSCRIPTION_STATE "Subscription-State"
#define SIP_HDR_REPLACES "Replaces"
#define SIP_HDR_COL(_hdr) _hdr ":"
#define SIP_HDR_COLSP(_hdr) SIP_HDR_COL(_hdr) " "

@ -163,14 +163,14 @@ static inline int skip_uri(const string& s, unsigned int pos)
enum {
uS0= 0, // start
uSPROT, // protocol
uSUHOST, // user / host
uSUSER, // user
uSHOST, // host
uSHOSTWSP, // wsp after host
uSPORT, // port
uSPORTWSP, // wsp after port
uSHDR, // header
uSHDRWSP, // wsp after header
uSPARAM, // params
uSPARAM, // params
uSPARAMWSP, // wsp after params
uS6 // end
};
@ -183,7 +183,7 @@ bool AmUriParser::parse_uri() {
size_t pos = 0; int st = uS0;
size_t p1 = 0;
int eq = 0; const char* sip_prot = "SIP:";
uri_user = ""; uri_host = ""; uri_port = ""; uri_param = "";
uri_user = ""; uri_host = ""; uri_port = ""; uri_param = "";
if (uri.empty())
return false;
@ -199,24 +199,18 @@ bool AmUriParser::parse_uri() {
if ((eq<=4)&&(toupper(c) ==sip_prot[eq]))
eq++;
if (eq==4) { // found sip:
st = uSUHOST; p1 = pos;
uri.find('@', pos+1) == string::npos? st = uSHOST : st = uSUSER; p1 = pos;
};
} break;
};
} break;
case uSPROT: {
if (c == ':') { st = uSUHOST; p1 = pos;}
if (c == ':') { uri.find('@', pos+1) == string::npos? st = uSHOST : st = uSUSER; p1 = pos;}
} break;
case uSUHOST: {
case uSUSER: {
switch(c) {
case '@': { uri_user = uri.substr(p1+1, pos-p1-1);
st = uSHOST; p1 = pos; }; break;
case ':': { uri_host = uri.substr(p1+1, pos-p1-1);
st = uSPORT; p1 = pos; }; break;
case '?': { uri_host = uri.substr(p1+1, pos-p1-1);
st = uSHDR; p1 = pos; }; break;
case ';': { uri_host = uri.substr(p1+1, pos-p1-1);
st = uSPARAM;p1 = pos; }; break;
case '>': { uri_host = uri.substr(p1+1, pos-p1-1);
st = uS6; p1 = pos; }; break;
};
@ -269,8 +263,6 @@ bool AmUriParser::parse_uri() {
case uSHDR: {
switch (c) {
case ';': { uri_headers = uri.substr(p1+1, pos-p1-1);
st = uSPARAM; p1 = pos; }; break;
case '>': { uri_headers = uri.substr(p1+1, pos-p1-1);
st = uS6; p1 = pos; } break;
case '\t':
@ -298,13 +290,14 @@ bool AmUriParser::parse_uri() {
switch (c) {
case '>': { st = uS6; p1 = pos; } break;
};
} break;
} break;
};
// DBG("(2) c = %c, st = %d\n", c, st);
pos++;
}
switch(st) {
case uSUHOST:
case uSUSER:
case uSHOST: uri_host = uri.substr(p1+1, pos-p1-1); break;
case uSPORT: uri_port = uri.substr(p1+1, pos-p1-1); break;
case uSHDR: uri_headers = uri.substr(p1+1, pos-p1-1); break;
@ -373,10 +366,26 @@ bool AmUriParser::parse_params(const string& line, int& pos) {
return true;
}
bool AmUriParser::parse_nameaddr(const string& line) {
size_t pos=0; size_t end=0;
return parse_contact(line, pos, end);
}
bool AmUriParser::parse_contact(const string& line, size_t pos, size_t& end) {
int p0 = skip_name(line, pos);
if (p0 < 0) { return false; }
if ((size_t)p0 > pos) {
// save display name
size_t dn_b = pos; size_t dn_e = p0;
while (dn_b < line.size() && line[dn_b] == ' ') { dn_b++; } // skip leading WS
while (dn_e > 0 && line[dn_e-1] == ' ') dn_e--; // skip trailing WS
if (dn_e > dn_b) {
if (line[dn_e-1] == '"' && line[dn_b] == '"') {
dn_b++; dn_e--; // skip quotes
}
display_name = line.substr(dn_b, dn_e - dn_b);
}
}
int p1 = skip_uri(line, p0);
if (p1 < 0) { return false; }
// if (p1 < 0) return false;
@ -387,16 +396,87 @@ bool AmUriParser::parse_contact(const string& line, size_t pos, size_t& end) {
return true;
}
void AmUriParser::dump() {
string AmUriParser::add_param_to_param_list(const string& param_name,
const string& param_value, const string& param_list)
{
string list_of_params(param_list);
string param = param_name;
if (!param_value.empty())
param += "=" + param_value;
// if param_string empty - set it
if (list_of_params.empty()) {
list_of_params = param;
}
else {
// check if parameter already exists; if yes - replace it
size_t start = 0, end = 0, eq = 0, length = 0;
bool replaced = false;
do {
// get next param
end = list_of_params.find_first_of(';', start);
length = (end == string::npos) ? list_of_params.size() - start : end - start;
// it the param empty?
eq = list_of_params.substr(start, length).find('=');
if (eq != string::npos) { // non-empty param found
if (list_of_params.substr(start, eq) == param_name) {
list_of_params.replace(start, length, param);
replaced = true;
break;
}
}
else { // empty param found
if (list_of_params.substr(start, length) == param_name) {
list_of_params.replace(start, length, param);
replaced = true;
break;
}
}
start = end + 1;
}
while (end != string::npos && start != string::npos);
// if parameter doesn't exist - append it
if (!replaced)
list_of_params.append(";" + param);
}
return list_of_params;
}
void AmUriParser::add_user_param(const string& param_name, const string& param_value)
{
size_t begin = uri_user.find_first_of(';');
if (begin == string::npos) {
uri_user += ";" + param_name;
if (!param_value.empty()) uri_user += "=" + param_value;
}
else {
uri_user = uri_user.substr(0, begin) + ";" +
add_param_to_param_list(param_name, param_value, uri_user.substr(begin + 1));
}
}
void AmUriParser::dump() const {
DBG("--- Uri Info --- \n");
DBG(" uri '%s'\n", uri.c_str());
DBG(" uri_user '%s'\n", uri_user.c_str());
DBG(" uri_host '%s'\n", uri_host.c_str());
DBG(" uri_port '%s'\n", uri_port.c_str());
DBG(" uri_hdr '%s'\n", uri_headers.c_str());
DBG(" uri_param '%s'\n", uri_param.c_str());
for (map<string, string>::iterator it = params.begin();
DBG(" uri '%s'\n", uri.c_str());
DBG(" display_name '%s'\n", display_name.c_str());
DBG(" uri_user '%s'\n", uri_user.c_str());
DBG(" uri_host '%s'\n", uri_host.c_str());
DBG(" uri_port '%s'\n", uri_port.c_str());
DBG(" uri_hdr '%s'\n", uri_headers.c_str());
DBG(" uri_param '%s'\n", uri_param.c_str());
for (map<string, string>::const_iterator it = params.begin();
it != params.end(); it++) {
if (it->second.empty())
DBG(" param '%s'\n", it->first.c_str());
else
@ -404,3 +484,55 @@ void AmUriParser::dump() {
}
DBG("-------------------- \n");
}
string AmUriParser::uri_str() const
{
string res = canon_uri_str();
if(!uri_param.empty()) {
res += ";" + uri_param;
}
if (!uri_headers.empty()) {
res+="?" + uri_headers;
}
return res;
}
string AmUriParser::canon_uri_str() const
{
string res = "sip:"; // fixme: always SIP...
if(!uri_user.empty()) {
res += uri_user + "@";
}
res += uri_host;
if(!uri_port.empty()) {
res += ":" + uri_port;
}
return res;
}
string AmUriParser::nameaddr_str() const
{
string res = "<" + uri_str() + ">";
if(!display_name.empty())
res = "\"" + display_name + "\" " + res;
for (map<string, string>::const_iterator it = params.begin();
it != params.end(); it++) {
res += ";"+it->first;
if (!it->second.empty())
res += "="+it->second;
}
return res;
}
string AmUriParser::print() {
return nameaddr_str();
}

@ -40,18 +40,36 @@ struct AmUriParser {
string uri_host;
string uri_port;
string uri_headers;
string uri_param;
string uri_param; // <sip:user@host;uri_param>
// <sip:user;user_param@host>
map<string, string> params;
map<string, string> params; // <sip:user;@host>;params
bool isEqual(const AmUriParser& c) const;
/** @return true on success */
/** parse a name-addr @return true on success */
bool parse_contact(const string& line, size_t pos, size_t& end);
/** parse a name-addr @return true on success */
bool parse_nameaddr(const string& line);
/** @return true on success */
bool parse_uri();
bool parse_params(const string& line, int& pos);
void dump();
/** param_string is semicolon separated list of parameters with or without value.
* method can be used to add/replace param for uri and user parameters */
static string add_param_to_param_list(const string& param_name,
const string& param_value, const string& param_list);
void add_user_param(const string& param_name, const string& param_value);
void dump() const;
string uri_str() const;
string canon_uri_str() const;
string nameaddr_str() const;
AmUriParser() { }
string print();
};
#endif

@ -382,6 +382,97 @@ bool str2long(char*& str, long& result, char sep)
return false;
}
std::string URL_decode(const std::string& s) {
enum {
uSNormal= 0, // start
uSH1,
uSH2
};
int st = uSNormal;
string res;
for (size_t pos = 0; pos < s.length(); pos++) {
switch (st) {
case uSNormal: {
if (s[pos] == '%')
st = uSH1;
else
res+=s[pos];
}; break;
case uSH1: {
if (s[pos] == '%') {
res+='%';
st = uSNormal;
} else {
st = uSH2;
}
}; break;
case uSH2: {
char c = 0;
if ( s[pos] >='0' && s[pos] <='9')
c += s[pos] -'0';
else if (s[pos] >='a' && s[pos] <='f')
c += s[pos] -'a'+10;
else if (s[pos] >='A' && s[pos] <='F')
c += s[pos] -'A'+10;
else {
st = uSNormal;
DBG("error in escaped string: %%%c%c\n", s[pos-1], s[pos]);
continue;
}
if ( s[pos-1] >='0' && s[pos-1] <='9')
c += (s[pos-1] -'0') << 4;
else if (s[pos-1] >='a' && s[pos-1] <='f')
c += (s[pos-1] -'a'+10) << 4;
else if (s[pos-1] >='A' && s[pos-1] <='F')
c += (s[pos-1] -'A'+10 ) << 4;
else {
st = uSNormal;
DBG("error in escaped string: %%%c%c\n", s[pos-1], s[pos]);
continue;
}
res +=c;
st = uSNormal;
} break;
}
}
return res;
}
std::string URL_encode(const std::string &s)
{
const std::string unreserved = "-_.~";
std::string escaped="";
for(size_t i=0; i<s.length(); i++)
{
//RFC 3986 section 2.3 Unreserved Characters (January 2005)
if ((s[i] >= 'A' && s[i] <= 'Z')
|| (s[i] >= 'a' && s[i] <= 'z')
|| (s[i] >= '0' && s[i] <= '9')
|| (s[i] == '-') || (s[i] == '_') || (s[i] == '.') || (s[i] == '~') )
{
escaped.push_back(s[i]);
}
else
{
escaped.append("%");
char buf[3];
sprintf(buf, "%.2X", s[i]);
escaped.append(buf);
}
}
return escaped;
}
int parse_return_code(const char* lbuf, unsigned int& res_code, string& res_msg )
{
char res_code_str[4] = {'\0'};

@ -144,6 +144,9 @@ bool str2long(const string& str, long& result);
*/
bool str2long(char*& str, long& result, char sep = ' ');
std::string URL_decode(const std::string& s);
std::string URL_encode(const std::string& s);
/**
* Parse code/reason line.
* Syntax: "code reason"

@ -2,11 +2,17 @@ NAME=sems_tests
SIP_STACK_DIR=../sip
SIP_STACK=$(SIP_STACK_DIR)/sip_stack.a
CORE_SRCS=$(filter-out ../sems.cpp , $(wildcard ../*.cpp))
CORE_SRCS=$(filter-out ../sems.cpp , $(wildcard ../*.cpp))
CORE_HDRS=$(CORE_SRCS:.cpp=.h)
CORE_OBJS=$(CORE_SRCS:.cpp=.o)
CORE_DEPS=$(subst ../,,$(CORE_SRCS:.cpp=.d))
SBC_DIR=../../apps/sbc/
SBC_SRCS=$(wildcard $(SBC_DIR)*.cpp)
SBC_HDRS=$(SBC_SRCS:.cpp=.h)
SBC_OBJS=$(SBC_SRCS:.cpp=.o)
SBC_DEPS=$(subst $(SBC_DIR),,$(SBC_SRCS:.cpp=.d))
AUTH_DIR=../plug-in/uac_auth
AUTH_OBJS=$(AUTH_DIR)/UACAuth.o
@ -21,7 +27,7 @@ EXTRA_LDFLAGS += -lresolv
.PHONY: all
all: ../../Makefile.defs
-@$(MAKE) core_deps && $(MAKE) deps && \
-@$(MAKE) core_deps && $(MAKE) sbc_deps && $(MAKE) deps && \
$(MAKE) $(NAME) && \
./$(NAME)
@ -43,6 +49,9 @@ deps: $(DEPS)
.PHONY: core_deps
core_deps: $(CORE_DEPS)
.PHONY: sbc_deps
sbc_deps: $(SBC_DEPS)
AUTH_OBJS: $(AUTH_DIR)/UACAuth.cpp $(AUTH_DIR)/UACAuth.h
cd $(AUTH_DIR) ; $(MAKE) AUTH_OBJS
@ -56,16 +65,19 @@ include ../../Makefile.defs
%.d : %.cpp %.h ../../Makefile.defs
$(CXX) -MM $< $(CPPFLAGS) $(CXXFLAGS) > $@
%.d : $(SBC_DIR)%.cpp $(SBC_DIR)%.h ../../Makefile.defs
$(CXX) -MM $< $(CPPFLAGS) $(CXXFLAGS) > $@
%.d : ../%.cpp ../%.h ../../Makefile.defs
$(CXX) -MM $< $(CPPFLAGS) $(CXXFLAGS) > $@
$(NAME): $(OBJS) $(CORE_OBJS) $(SIP_STACK) ../../Makefile.defs
$(NAME): $(OBJS) $(CORE_OBJS) $(SBC_OBJS) $(SIP_STACK) ../../Makefile.defs
-@echo ""
-@echo "making $(NAME)"
$(LD) -o $(NAME) $(OBJS) $(CORE_OBJS) $(SIP_STACK) $(LDFLAGS) $(EXTRA_LDFLAGS) $(AUTH_OBJS)
$(LD) -o $(NAME) $(OBJS) $(CORE_OBJS) $(SBC_OBJS) $(SIP_STACK) $(LDFLAGS) $(EXTRA_LDFLAGS) $(AUTH_OBJS)
ifeq '$(NAME)' '$(MAKECMDGOALS)'
include $(DEPS) $(CORE_DEPS)
include $(DEPS) $(CORE_DEPS) $(SBC_DEPS)
endif

@ -25,6 +25,8 @@ FCT_BGN() {
FCTMF_SUITE_CALL(test_auth);
FCTMF_SUITE_CALL(test_headers);
FCTMF_SUITE_CALL(test_jsonarg);
FCTMF_SUITE_CALL(test_uriparser);
FCTMF_SUITE_CALL(test_replaces);
} FCT_END();

@ -0,0 +1,103 @@
#include "fct.h"
#include "log.h"
#include "AmSipHeaders.h"
#include "AmSipMsg.h"
#include "AmUtils.h"
#include "AmUriParser.h"
#include "../../apps/sbc/ReplacesMapper.h"
#include "../../apps/sbc/SBCCallRegistry.h"
FCTMF_SUITE_BGN(test_replaces) {
FCT_TEST_BGN(registry_simple) {
SBCCallRegistryEntry e = SBCCallRegistryEntry("callid2", "ltag2", "rtag2");
SBCCallRegistry::addCall("ltag", e);
fct_chk(SBCCallRegistry::lookupCall("ltag",e));
fct_chk(!SBCCallRegistry::lookupCall("ltag3",e));
SBCCallRegistry::removeCall("ltag");
} FCT_TEST_END();
FCT_TEST_BGN(replaces_fixup_invite) {
SBCCallRegistryEntry e = SBCCallRegistryEntry("C2", "C2f", "C2t");
SBCCallRegistry::addCall("Ct", e);
SBCCallRegistryEntry e2 = SBCCallRegistryEntry("C", "Ct", "Cf");
SBCCallRegistry::addCall("C2f", e2);
AmSipRequest r;
r.hdrs="Replaces: C;from-tag=Cf;to-tag=Ct\r\n";
fixReplaces(r, true);
DBG("r.hdrs='%s'\n", r.hdrs.c_str());
fct_chk(r.hdrs=="Replaces: C2;from-tag=C2f;to-tag=C2t\r\n");
SBCCallRegistry::removeCall("Ct");
SBCCallRegistry::removeCall("C2f");
} FCT_TEST_END();
FCT_TEST_BGN(replaces_fixup_refer) {
SBCCallRegistryEntry e = SBCCallRegistryEntry("C2", "C2f", "C2t");
SBCCallRegistry::addCall("Ct", e);
SBCCallRegistryEntry e2 = SBCCallRegistryEntry("C", "Ct", "Cf");
SBCCallRegistry::addCall("C2f", e2);
AmSipRequest r;
string orig_str = "Refer-To: \"Mr. Watson\" <sip:watson@bell-telephone.com?Replaces=C%3Bto-tag%3DCt%3Bfrom-tag%3DCf>;q=0.1";
string new_str = "Refer-To: \"Mr. Watson\" <sip:watson@bell-telephone.com?Replaces=C2%3Bfrom-tag%3DC2f%3Bto-tag%3DC2t>;q=0.1\r\n";
r.hdrs=orig_str+"\r\n";
fixReplaces(r, false);
DBG("r.hdrs='%s'\n", r.hdrs.c_str());
DBG("new s='%s'\n", new_str.c_str());
fct_chk(r.hdrs==new_str);
SBCCallRegistry::removeCall("Ct");
SBCCallRegistry::removeCall("C2f");
} FCT_TEST_END();
FCT_TEST_BGN(replaces_fixup_refer2) {
SBCCallRegistryEntry e = SBCCallRegistryEntry("C2", "C2f", "C2t");
SBCCallRegistry::addCall("Ct", e);
SBCCallRegistryEntry e2 = SBCCallRegistryEntry("C", "Ct", "Cf");
SBCCallRegistry::addCall("C2f", e2);
AmSipRequest r;
string orig_str = "Refer-To: \"Mr. Watson\" <sip:watson@bell-telephone.com?Require=replaces;Replaces=C%3Bto-tag%3DCt%3Bfrom-tag%3DCf>;q=0.1\r\n";
string new_str = "Refer-To: \"Mr. Watson\" <sip:watson@bell-telephone.com?Require=replaces;Replaces=C2%3Bfrom-tag%3DC2f%3Bto-tag%3DC2t>;q=0.1\r\n";
r.hdrs=orig_str;
fixReplaces(r, false);
DBG("r.hdrs='%s'\n", r.hdrs.c_str());
DBG("new s='%s'\n", new_str.c_str());
fct_chk(r.hdrs==new_str);
SBCCallRegistry::removeCall("Ct");
SBCCallRegistry::removeCall("C2f");
} FCT_TEST_END();
FCT_TEST_BGN(replaces_fixup_refer3) {
SBCCallRegistryEntry e = SBCCallRegistryEntry("C2", "C2f", "C2t");
SBCCallRegistry::addCall("Ct", e);
SBCCallRegistryEntry e2 = SBCCallRegistryEntry("C", "Ct", "Cf");
SBCCallRegistry::addCall("C2f", e2);
AmSipRequest r;
string orig_str = "Refer-To: \"Mr. Watson\" <sip:watson@bell-telephone.com?Require=replaces;Replaces=C%3Bto-tag%3DCt%3Bfrom-tag%3DCf;Bla=Blub>;q=0.1\r\n";
string new_str = "Refer-To: \"Mr. Watson\" <sip:watson@bell-telephone.com?Require=replaces;Replaces=C2%3Bfrom-tag%3DC2f%3Bto-tag%3DC2t;Bla=Blub>;q=0.1\r\n";
r.hdrs=orig_str;
fixReplaces(r, false);
DBG("r.hdrs='%s'\n", r.hdrs.c_str());
DBG("new s='%s'\n", new_str.c_str());
fct_chk(r.hdrs==new_str);
SBCCallRegistry::removeCall("Ct");
SBCCallRegistry::removeCall("C2f");
} FCT_TEST_END();
} FCTMF_SUITE_END();

@ -0,0 +1,135 @@
#include "fct.h"
#include "log.h"
#include "AmSipHeaders.h"
#include "AmSipMsg.h"
#include "AmUtils.h"
#include "AmUriParser.h"
FCTMF_SUITE_BGN(test_uriparser) {
FCT_TEST_BGN(uriparser_simple) {
AmUriParser p;
size_t end;
fct_chk( p.parse_contact("sip:u@d", 0, end) );
fct_chk( p.uri_user=="u");
fct_chk( p.uri_host=="d");
} FCT_TEST_END();
FCT_TEST_BGN(uriparser_angle) {
AmUriParser p;
size_t end;
fct_chk( p.parse_contact("<sip:u@d>", 0, end) );
fct_chk( p.uri_user=="u");
fct_chk( p.uri_host=="d");
} FCT_TEST_END();
FCT_TEST_BGN(uriparser_angle_param) {
AmUriParser p;
size_t end;
fct_chk( p.parse_contact("<sip:u@d>;tag=123", 0, end) );
fct_chk( p.uri_user=="u");
fct_chk( p.uri_host=="d");
fct_chk( p.params["tag"]=="123");
} FCT_TEST_END();
FCT_TEST_BGN(uriparser_uri_param) {
AmUriParser p;
size_t end;
fct_chk( p.parse_contact("<sip:u@d;tag=123>", 0, end) );
fct_chk( p.uri_user=="u");
fct_chk( p.uri_host=="d");
fct_chk( p.uri_param=="tag=123");
} FCT_TEST_END();
FCT_TEST_BGN(uriparser_params_nobracket) {
AmUriParser p;
size_t end;
fct_chk( p.parse_contact("sip:u@d;tag=123", 0, end) );
fct_chk( p.uri_user=="u");
fct_chk( p.uri_host=="d");
fct_chk( p.params["tag"]=="123");
} FCT_TEST_END();
FCT_TEST_BGN(uriparser_params_dname) {
AmUriParser p;
size_t end;
fct_chk( p.parse_contact("hu <sip:u@d;tag=123>", 0, end) );
// DBG("DN:: '%s'\n", p.display_name.c_str());
fct_chk( p.display_name=="hu");
fct_chk( p.uri_user=="u");
fct_chk( p.uri_host=="d");
} FCT_TEST_END();
FCT_TEST_BGN(uriparser_params_dname2) {
AmUriParser p;
size_t end;
fct_chk( p.parse_contact(" hu bar <sip:u@d;tag=123>", 0, end) );
// DBG("DN:: '%s'\n", p.display_name.c_str());
fct_chk( p.display_name=="hu bar");
fct_chk( p.uri_user=="u");
fct_chk( p.uri_host=="d");
} FCT_TEST_END();
FCT_TEST_BGN(uriparser_params_dname3) {
AmUriParser p;
size_t end;
fct_chk( p.parse_contact(" \"hu bar\" <sip:u@d;tag=123>", 0, end) );
fct_chk( p.display_name=="hu bar");
fct_chk( p.uri_user=="u");
fct_chk( p.uri_host=="d");
} FCT_TEST_END();
FCT_TEST_BGN(uriparser_params_dname4) {
AmUriParser p;
size_t end;
fct_chk( p.parse_contact(" \"hu bar\\\\ \" <sip:u@d;tag=123>", 0, end) );
fct_chk( p.display_name=="hu bar\\\\ ");
fct_chk( p.uri_user=="u");
fct_chk( p.uri_host=="d");
} FCT_TEST_END();
FCT_TEST_BGN(uriparser_params_dname4) {
AmUriParser p;
size_t end;
fct_chk(p.parse_contact("\"Mr. Watson\" <mailto:watson@bell-telephone.com> ;q=0.1", 0, end));
fct_chk( p.display_name=="Mr. Watson");
fct_chk( p.uri_user=="watson");
fct_chk( p.uri_host=="bell-telephone.com");
} FCT_TEST_END();
FCT_TEST_BGN(uriparser_headers) {
AmUriParser p;
size_t end;
fct_chk(p.parse_contact("\"Mr. Watson\" <mailto:watson@bell-telephone.com?Replaces:%20lkancskjd%3Bto-tag=3123141ab%3Bfrom-tag=kjhkjcsd> ;q=0.1", 0, end));
fct_chk( p.display_name=="Mr. Watson");
fct_chk( p.uri_user=="watson");
fct_chk( p.uri_host=="bell-telephone.com");
fct_chk( p.uri_headers=="Replaces:\%20lkancskjd%3Bto-tag=3123141ab%3Bfrom-tag=kjhkjcsd");
} FCT_TEST_END();
FCT_TEST_BGN(uriparser_headers_str) {
AmUriParser p;
string orig_str = "\"Mr. Watson\" <sip:watson@bell-telephone.com?Replaces:%20lkancskjd%3Bto-tag=3123141ab%3Bfrom-tag=kjhkjcsd>;q=0.1";
fct_chk(p.parse_nameaddr(orig_str));
fct_chk( p.display_name=="Mr. Watson");
fct_chk( p.uri_user=="watson");
fct_chk( p.uri_host=="bell-telephone.com");
fct_chk( p.uri_headers=="Replaces:\%20lkancskjd%3Bto-tag=3123141ab%3Bfrom-tag=kjhkjcsd");
string a_str = p.nameaddr_str();
// DBG(" >>%s<< => >>%s<<\n", orig_str.c_str(), a_str.c_str());
fct_chk(orig_str == a_str);
} FCT_TEST_END();
FCT_TEST_BGN(uriparser_url_escape) {
string src = "Replaces: CSADFSD;from-tag=31241231abc;to-tag=235123";
string dst = "Replaces%3A%20CSADFSD%3Bfrom-tag%3D31241231abc%3Bto-tag%3D235123";
fct_chk ( URL_decode(dst)==src );
fct_chk ( URL_encode(src)==dst );
fct_chk ( URL_decode(URL_encode(src))==src );
} FCT_TEST_END();
} FCTMF_SUITE_END();

@ -22,6 +22,7 @@ Features
o reply code translation
o SIP authentication
o SIP Session Timers
o Fixing Call Transfers (Replaces in REFER target and INVITE)
o call timer
o prepaid accounting
o CDR generation
@ -343,6 +344,20 @@ Warning: Changing response codes, especially between different response
code classes, can seriously mess up everything. Use with caution
and only if you know what you are doing!
Fixing Call Transfers (Replaces in REFER target and INVITE)
-----------------------------------------------------------
Using the profile options fix_replaces_inv and fix_replaces_ref Replaces
can be fixed for call transfers going through the SBC.
fix_replaces_inv=yes
fix_replaces_ref=yes
For situations where the call transfer is handled by the UAs (phone handles
the REFER), the Replaces should be fixed in the INVITE message (fix_replaces_inv=yes).
For situations where a PBX handles the call transfer (handles the REFER),
the Replaces should be fixed in the REFER message (fix_replaces_ref=yes).
Reliable 1xx (PRACK)
--------------------

Loading…
Cancel
Save