diff --git a/apps/sbc/ReplacesMapper.cpp b/apps/sbc/ReplacesMapper.cpp new file mode 100644 index 00000000..d00e265b --- /dev/null +++ b/apps/sbc/ReplacesMapper.cpp @@ -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 hdrs; // headers from Refer-To URI + vector::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::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; +} + diff --git a/apps/sbc/ReplacesMapper.h b/apps/sbc/ReplacesMapper.h new file mode 100644 index 00000000..7b1c46fe --- /dev/null +++ b/apps/sbc/ReplacesMapper.h @@ -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 diff --git a/apps/sbc/SBC.cpp b/apps/sbc/SBC.cpp index 6302cb3f..b589fb55 100644 --- a/apps/sbc/SBC.cpp +++ b/apps/sbc/SBC.cpp @@ -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(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(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; " diff --git a/apps/sbc/SBCCallProfile.cpp b/apps/sbc/SBCCallProfile.cpp index 8a2cddb7..1e35779c 100644 --- a/apps/sbc/SBCCallProfile.cpp +++ b/apps/sbc/SBCCallProfile.cpp @@ -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()) { diff --git a/apps/sbc/SBCCallProfile.h b/apps/sbc/SBCCallProfile.h index fe20dfcf..b34c3063 100644 --- a/apps/sbc/SBCCallProfile.h +++ b/apps/sbc/SBCCallProfile.h @@ -68,6 +68,9 @@ struct SBCCallProfile { FilterType sdpfilter; set sdpfilter_list; + string fix_replaces_inv; + string fix_replaces_ref; + bool sst_enabled; bool use_global_sst_config; diff --git a/apps/sbc/SBCCallRegistry.cpp b/apps/sbc/SBCCallRegistry.cpp new file mode 100644 index 00000000..c0677c43 --- /dev/null +++ b/apps/sbc/SBCCallRegistry.cpp @@ -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 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::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::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()); +} diff --git a/apps/sbc/SBCCallRegistry.h b/apps/sbc/SBCCallRegistry.h new file mode 100644 index 00000000..be8f1139 --- /dev/null +++ b/apps/sbc/SBCCallRegistry.h @@ -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 +using std::string; +#include + +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 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 diff --git a/apps/sbc/etc/transparent.sbcprofile.conf b/apps/sbc/etc/transparent.sbcprofile.conf index f34f5279..90ca619f 100644 --- a/apps/sbc/etc/transparent.sbcprofile.conf +++ b/apps/sbc/etc/transparent.sbcprofile.conf @@ -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) diff --git a/core/AmSipHeaders.h b/core/AmSipHeaders.h index 4abb428f..f23f81dc 100644 --- a/core/AmSipHeaders.h +++ b/core/AmSipHeaders.h @@ -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) " " diff --git a/core/AmUriParser.cpp b/core/AmUriParser.cpp index f67e324e..6b3a8317 100644 --- a/core/AmUriParser.cpp +++ b/core/AmUriParser.cpp @@ -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::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::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::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(); +} diff --git a/core/AmUriParser.h b/core/AmUriParser.h index 107e0a32..840cd0f7 100644 --- a/core/AmUriParser.h +++ b/core/AmUriParser.h @@ -40,18 +40,36 @@ struct AmUriParser { string uri_host; string uri_port; string uri_headers; - string uri_param; + string uri_param; // + // - map params; + map params; // ;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 diff --git a/core/AmUtils.cpp b/core/AmUtils.cpp index 52d75899..f1030b8e 100644 --- a/core/AmUtils.cpp +++ b/core/AmUtils.cpp @@ -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= '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'}; diff --git a/core/AmUtils.h b/core/AmUtils.h index 75be38ed..affb5d4d 100644 --- a/core/AmUtils.h +++ b/core/AmUtils.h @@ -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" diff --git a/core/tests/Makefile b/core/tests/Makefile index c614e808..d89844fe 100644 --- a/core/tests/Makefile +++ b/core/tests/Makefile @@ -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 diff --git a/core/tests/sems_tests.cpp b/core/tests/sems_tests.cpp index 1c3a1bcb..63328381 100644 --- a/core/tests/sems_tests.cpp +++ b/core/tests/sems_tests.cpp @@ -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(); diff --git a/core/tests/test_replaces.cpp b/core/tests/test_replaces.cpp new file mode 100644 index 00000000..cbe7ad25 --- /dev/null +++ b/core/tests/test_replaces.cpp @@ -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\" ;q=0.1"; + string new_str = "Refer-To: \"Mr. Watson\" ;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\" ;q=0.1\r\n"; + string new_str = "Refer-To: \"Mr. Watson\" ;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\" ;q=0.1\r\n"; + string new_str = "Refer-To: \"Mr. Watson\" ;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(); diff --git a/core/tests/test_replaces.h b/core/tests/test_replaces.h new file mode 100644 index 00000000..8b137891 --- /dev/null +++ b/core/tests/test_replaces.h @@ -0,0 +1 @@ + diff --git a/core/tests/test_uriparser.cpp b/core/tests/test_uriparser.cpp new file mode 100644 index 00000000..3bdc7275 --- /dev/null +++ b/core/tests/test_uriparser.cpp @@ -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("", 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(";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("", 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 ", 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 ", 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\" ", 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\\\\ \" ", 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\" ;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\" ;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\" ;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(); diff --git a/core/tests/test_uriparser.h b/core/tests/test_uriparser.h new file mode 100644 index 00000000..e69de29b diff --git a/doc/Readme.sbc.txt b/doc/Readme.sbc.txt index 49017291..994d6ae9 100644 --- a/doc/Readme.sbc.txt +++ b/doc/Readme.sbc.txt @@ -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) --------------------