summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBenny Prijono <bennylp@teluu.com>2008-11-27 00:06:46 +0000
committerBenny Prijono <bennylp@teluu.com>2008-11-27 00:06:46 +0000
commitc40accaad90c1ec16cf734e478d13247812c0e0c (patch)
tree49326bcc74cf57217fee661f888332279725d09e
parent9b44f51f9e162f47a951d3e933e0b0e462db576b (diff)
Ticket #10: handle redirection response in the invite session
git-svn-id: http://svn.pjsip.org/repos/pjproject/trunk@2370 74dad513-b988-da41-8d7b-12977e46ad98
-rw-r--r--pjsip-apps/src/pjsua/pjsua_app.c69
-rw-r--r--pjsip-apps/src/test-pjsua/mod_recvfrom.py22
-rw-r--r--pjsip-apps/src/test-pjsua/scripts-sipp/inv_401_retry_after_100.xml106
-rw-r--r--pjsip-apps/src/test-pjsua/scripts-sipp/uas-template.xml84
-rw-r--r--pjsip/include/pjsip-ua/sip_inv.h97
-rw-r--r--pjsip/include/pjsip/sip_dialog.h2
-rw-r--r--pjsip/include/pjsip/sip_util.h189
-rw-r--r--pjsip/include/pjsua-lib/pjsua.h74
-rw-r--r--pjsip/src/pjsip-ua/sip_inv.c387
-rw-r--r--pjsip/src/pjsip/sip_dialog.c4
-rw-r--r--pjsip/src/pjsip/sip_util.c133
-rw-r--r--pjsip/src/pjsua-lib/pjsua_call.c70
12 files changed, 1182 insertions, 55 deletions
diff --git a/pjsip-apps/src/pjsua/pjsua_app.c b/pjsip-apps/src/pjsua/pjsua_app.c
index 0bdc4c34..870d0129 100644
--- a/pjsip-apps/src/pjsua/pjsua_app.c
+++ b/pjsip-apps/src/pjsua/pjsua_app.c
@@ -62,6 +62,7 @@ static struct app_config
pj_bool_t use_tls;
pjsua_transport_config udp_cfg;
pjsua_transport_config rtp_cfg;
+ pjsip_redirect_op redir_op;
unsigned acc_cnt;
pjsua_acc_config acc_cfg[PJSUA_MAX_ACC];
@@ -278,6 +279,8 @@ static void usage(void)
puts (" --norefersub Suppress event subscription when transfering calls");
puts (" --use-compact-form Minimize SIP message size");
puts (" --no-force-lr Allow strict-route to be used (i.e. do not force lr)");
+ puts (" --accept-redirect=N Specify how to handle call redirect (3xx) response.");
+ puts (" 0: reject, 1: follow automatically (default), 2: ask");
puts ("");
puts ("When URL is specified, pjsua will immediately initiate call to that URL");
@@ -303,6 +306,7 @@ static void default_config(struct app_config *cfg)
cfg->udp_cfg.port = 5060;
pjsua_transport_config_default(&cfg->rtp_cfg);
cfg->rtp_cfg.port = 4000;
+ cfg->redir_op = PJSIP_REDIRECT_ACCEPT;
cfg->duration = NO_LIMIT;
cfg->wav_id = PJSUA_INVALID_ID;
cfg->rec_id = PJSUA_INVALID_ID;
@@ -472,7 +476,7 @@ static pj_status_t parse_args(int argc, char *argv[],
OPT_RX_DROP_PCT, OPT_TX_DROP_PCT, OPT_EC_TAIL, OPT_EC_OPT,
OPT_NEXT_ACCOUNT, OPT_NEXT_CRED, OPT_MAX_CALLS,
OPT_DURATION, OPT_NO_TCP, OPT_NO_UDP, OPT_THREAD_CNT,
- OPT_NOREFERSUB,
+ OPT_NOREFERSUB, OPT_ACCEPT_REDIRECT,
OPT_USE_TLS, OPT_TLS_CA_FILE, OPT_TLS_CERT_FILE, OPT_TLS_PRIV_FILE,
OPT_TLS_PASSWORD, OPT_TLS_VERIFY_SERVER, OPT_TLS_VERIFY_CLIENT,
OPT_TLS_NEG_TIMEOUT, OPT_TLS_SRV_NAME,
@@ -515,6 +519,7 @@ static pj_status_t parse_args(int argc, char *argv[],
{ "contact", 1, 0, OPT_CONTACT},
{ "auto-update-nat", 1, 0, OPT_AUTO_UPDATE_NAT},
{ "use-compact-form", 0, 0, OPT_USE_COMPACT_FORM},
+ { "accept-redirect", 1, 0, OPT_ACCEPT_REDIRECT},
{ "no-force-lr",0, 0, OPT_NO_FORCE_LR},
{ "realm", 1, 0, OPT_REALM},
{ "username", 1, 0, OPT_USERNAME},
@@ -840,6 +845,15 @@ static pj_status_t parse_args(int argc, char *argv[],
}
break;
+ case OPT_ACCEPT_REDIRECT:
+ cfg->redir_op = my_atoi(pj_optarg);
+ if (cfg->redir_op<0 || cfg->redir_op>PJSIP_REDIRECT_STOP) {
+ PJ_LOG(1,(THIS_FILE,
+ "Error: accept-redirect value '%s' ", pj_optarg));
+ return PJ_EINVAL;
+ }
+ break;
+
case OPT_NO_FORCE_LR:
cfg->cfg.force_lr = PJ_FALSE;
break;
@@ -1785,6 +1799,13 @@ static int write_settings(const struct app_config *config,
pj_strcat2(&cfg, line);
}
+ /* accept-redirect */
+ if (config->redir_op != PJSIP_REDIRECT_ACCEPT) {
+ pj_ansi_sprintf(line, "--accept-redirect %d\n",
+ config->redir_op);
+ pj_strcat2(&cfg, line);
+ }
+
/* Max calls. */
pj_ansi_sprintf(line, "--max-calls %d\n",
config->cfg.max_calls);
@@ -1840,7 +1861,8 @@ static void app_dump(pj_bool_t detail)
* printing it is a bit tricky, it should be printed part by part as long
* as the logger can accept.
*/
-static void log_call_dump(int call_id) {
+static void log_call_dump(int call_id)
+{
unsigned call_dump_len;
unsigned part_len;
unsigned part_idx;
@@ -2369,6 +2391,33 @@ static void call_on_dtmf_callback(pjsua_call_id call_id, int dtmf)
}
/*
+ * Redirection handler.
+ */
+static void call_on_redirected(pjsua_call_id call_id, const pjsip_uri *target,
+ pjsip_redirect_op *cmd, const pjsip_event *e)
+{
+ *cmd = app_config.redir_op;
+
+ PJ_UNUSED_ARG(e);
+
+ if (*cmd == PJSIP_REDIRECT_PENDING) {
+ char uristr[PJSIP_MAX_URL_SIZE];
+ int len;
+
+ len = pjsip_uri_print(PJSIP_URI_IN_FROMTO_HDR, target, uristr,
+ sizeof(uristr));
+ if (len < 1) {
+ pj_ansi_strcpy(uristr, "--URI too long--");
+ }
+
+ PJ_LOG(3,(THIS_FILE, "Call %d is being redirected to %.*s. "
+ "Press 'Ra' to accept, 'Rr' to reject, or 'Rd' to "
+ "disconnect.",
+ call_id, len, uristr));
+ }
+}
+
+/*
* Handler registration status has changed.
*/
static void on_reg_state(pjsua_acc_id acc_id)
@@ -3915,6 +3964,21 @@ void console_app_main(const pj_str_t *uri_to_call)
goto on_exit;
+ case 'R':
+ if (!pjsua_call_is_active(current_call)) {
+ PJ_LOG(1,(THIS_FILE, "Call %d has gone", current_call));
+ } else if (menuin[1] == 'a') {
+ pjsua_call_process_redirect(current_call,
+ PJSIP_REDIRECT_ACCEPT);
+ } else if (menuin[1] == 'r') {
+ pjsua_call_process_redirect(current_call,
+ PJSIP_REDIRECT_REJECT);
+ } else {
+ pjsua_call_process_redirect(current_call,
+ PJSIP_REDIRECT_STOP);
+ }
+ break;
+
default:
if (menuin[0] != '\n' && menuin[0] != '\r') {
printf("Invalid input %s", menuin);
@@ -3962,6 +4026,7 @@ pj_status_t app_init(int argc, char *argv[])
app_config.cfg.cb.on_incoming_call = &on_incoming_call;
app_config.cfg.cb.on_call_tsx_state = &on_call_tsx_state;
app_config.cfg.cb.on_dtmf_digit = &call_on_dtmf_callback;
+ app_config.cfg.cb.on_call_redirected = &call_on_redirected;
app_config.cfg.cb.on_reg_state = &on_reg_state;
app_config.cfg.cb.on_incoming_subscribe = &on_incoming_subscribe;
app_config.cfg.cb.on_buddy_state = &on_buddy_state;
diff --git a/pjsip-apps/src/test-pjsua/mod_recvfrom.py b/pjsip-apps/src/test-pjsua/mod_recvfrom.py
index 259083f5..1510aed1 100644
--- a/pjsip-apps/src/test-pjsua/mod_recvfrom.py
+++ b/pjsip-apps/src/test-pjsua/mod_recvfrom.py
@@ -1,4 +1,4 @@
-# $Id:$
+# $Id$
import imp
import sys
import inc_sip as sip
@@ -67,15 +67,17 @@ def test_func(test):
tname = ""
raise TestError("Excluded pattern " + pat + " found" + tname)
# Create response
- response = dlg.create_response(request, t.resp_code, "Status reason")
- # Add headers to response
- for h in t.resp_hdr:
- response = response + h + "\r\n"
- # Add message body if required
- if t.body:
- response = response + t.body
- # Send response
- dlg.send_msg(response, src_addr)
+ if t.resp_code!=0:
+ response = dlg.create_response(request, t.resp_code, "Status reason")
+ # Add headers to response
+ for h in t.resp_hdr:
+ response = response + h + "\r\n"
+ # Add message body if required
+ if t.body:
+ response = response + t.body
+ # Send response
+ dlg.send_msg(response, src_addr)
+
# Expect something to happen in pjsua
if t.expect != "":
pjsua.expect(t.expect)
diff --git a/pjsip-apps/src/test-pjsua/scripts-sipp/inv_401_retry_after_100.xml b/pjsip-apps/src/test-pjsua/scripts-sipp/inv_401_retry_after_100.xml
new file mode 100644
index 00000000..6debd134
--- /dev/null
+++ b/pjsip-apps/src/test-pjsua/scripts-sipp/inv_401_retry_after_100.xml
@@ -0,0 +1,106 @@
+<?xml version="1.0" encoding="ISO-8859-1" ?>
+<!DOCTYPE scenario SYSTEM "sipp.dtd">
+
+<!-- This program 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 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 -->
+<!-- -->
+<!-- -->
+
+<scenario name="Authorization retry after 1xx response test">
+ <!-- Wait for INVITE request -->
+ <recv request="INVITE" crlf="true">
+ </recv>
+
+ <!-- Send 100 Trying -->
+ <send>
+ <![CDATA[
+
+ SIP/2.0 100 Trying
+ [last_Via:]
+ [last_From:]
+ [last_To:]
+ [last_Call-ID:]
+ [last_CSeq:]
+ ]]>
+ </send>
+
+ <!-- Send 180 Ringing (with tag) -->
+ <send>
+ <![CDATA[
+
+ SIP/2.0 180 Ringing
+ [last_Via:]
+ [last_From:]
+ [last_To:];tag=[call_number]
+ [last_Call-ID:]
+ [last_CSeq:]
+ ]]>
+ </send>
+
+ <!-- Send 401 Unauthorized -->
+ <send retrans="500">
+ <![CDATA[
+
+ SIP/2.0 401 Unauthorized
+ [last_Via:]
+ [last_From:]
+ [last_To:];tag=[call_number]
+ [last_Call-ID:]
+ [last_CSeq:]
+ WWW-Authenticate: Digest realm="sipp", nonce="1234"
+ Content-Length: 0
+ ]]>
+ </send>
+
+ <!-- Wait for ACK -->
+ <recv request="ACK"
+ optional="false"
+ rtd="true"
+ crlf="true">
+ </recv>
+
+ <!-- Wait for INVITE retransmission -->
+ <recv request="INVITE" crlf="true">
+ </recv>
+
+ <!-- Send 500 Test Success to terminate the call -->
+ <send retrans="500">
+ <![CDATA[
+
+ SIP/2.0 500 Test Success
+ [last_Via:]
+ [last_From:]
+ [last_To:];tag=[call_number]
+ [last_Call-ID:]
+ [last_CSeq:]
+ Content-Length: 0
+ ]]>
+ </send>
+
+ <!-- Wait for ACK -->
+ <recv request="ACK"
+ optional="false"
+ rtd="true"
+ crlf="true">
+ </recv>
+
+ <!-- definition of the response time repartition table (unit is ms) -->
+ <ResponseTimeRepartition value="10, 20, 30, 40, 50, 100, 150, 200"/>
+
+ <!-- definition of the call length repartition table (unit is ms) -->
+ <CallLengthRepartition value="10, 50, 100, 500, 1000, 5000, 10000"/>
+
+</scenario>
+
diff --git a/pjsip-apps/src/test-pjsua/scripts-sipp/uas-template.xml b/pjsip-apps/src/test-pjsua/scripts-sipp/uas-template.xml
new file mode 100644
index 00000000..d51f89c8
--- /dev/null
+++ b/pjsip-apps/src/test-pjsua/scripts-sipp/uas-template.xml
@@ -0,0 +1,84 @@
+<?xml version="1.0" encoding="ISO-8859-1" ?>
+<!DOCTYPE scenario SYSTEM "sipp.dtd">
+
+<!-- This program 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 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 -->
+<!-- -->
+<!-- Sipp default 'uas' scenario. -->
+<!-- -->
+
+<scenario name="Basic UAS responder">
+ <!-- By adding rrs="true" (Record Route Sets), the route sets -->
+ <!-- are saved and used for following messages sent. Useful to test -->
+ <!-- against stateful SIP proxies/B2BUAs. -->
+ <recv request="INVITE" crlf="true">
+ </recv>
+
+ <!-- The '[last_*]' keyword is replaced automatically by the -->
+ <!-- specified header if it was present in the last message received -->
+ <!-- (except if it was a retransmission). If the header was not -->
+ <!-- present or if no message has been received, the '[last_*]' -->
+ <!-- keyword is discarded, and all bytes until the end of the line -->
+ <!-- are also discarded. -->
+ <!-- -->
+ <!-- If the specified header was present several times in the -->
+ <!-- message, all occurences are concatenated (CRLF seperated) -->
+ <!-- to be used in place of the '[last_*]' keyword. -->
+
+ <send>
+ <![CDATA[
+
+ SIP/2.0 100 Trying
+ [last_Via:]
+ [last_From:]
+ [last_To:];tag=[call_number]
+ [last_Call-ID:]
+ [last_CSeq:]
+ ]]>
+ </send>
+
+ <send retrans="500">
+ <![CDATA[
+
+ SIP/2.0 301 Redirection
+ [last_Via:]
+ [last_From:]
+ [last_To:];tag=[call_number]
+ [last_Call-ID:]
+ [last_CSeq:]
+ Contact: <sip:target@192.168.254.254>
+ Content-Length: 0
+ ]]>
+ </send>
+
+ <recv request="ACK"
+ optional="false"
+ rtd="true"
+ crlf="true">
+ </recv>
+
+ <!-- Keep the call open for a while in case the 200 is lost to be -->
+ <!-- able to retransmit it if we receive the BYE again. -->
+ <pause milliseconds="4000"/>
+
+
+ <!-- definition of the response time repartition table (unit is ms) -->
+ <ResponseTimeRepartition value="10, 20, 30, 40, 50, 100, 150, 200"/>
+
+ <!-- definition of the call length repartition table (unit is ms) -->
+ <CallLengthRepartition value="10, 50, 100, 500, 1000, 5000, 10000"/>
+
+</scenario>
+
diff --git a/pjsip/include/pjsip-ua/sip_inv.h b/pjsip/include/pjsip-ua/sip_inv.h
index ba9858a8..0d878573 100644
--- a/pjsip/include/pjsip-ua/sip_inv.h
+++ b/pjsip/include/pjsip-ua/sip_inv.h
@@ -115,7 +115,6 @@ typedef struct pjsip_inv_callback
*/
void (*on_state_changed)(pjsip_inv_session *inv, pjsip_event *e);
-
/**
* This callback is called when the invite usage module has created
* a new dialog and invite because of forked outgoing request.
@@ -214,6 +213,54 @@ typedef struct pjsip_inv_callback
*/
void (*on_send_ack)(pjsip_inv_session *inv, pjsip_rx_data *rdata);
+ /**
+ * This callback is called when the session is about to resend the
+ * INVITE request to the specified target, following the previously
+ * received redirection response.
+ *
+ * Application may accept the redirection to the specified target
+ * (the default behavior if this callback is implemented), reject
+ * this target only and make the session continue to try the next
+ * target in the list if such target exists, stop the whole
+ * redirection process altogether and cause the session to be
+ * disconnected, or defer the decision to ask for user confirmation.
+ *
+ * This callback is optional. If this callback is not implemented,
+ * the default behavior is to NOT follow the redirection response.
+ *
+ * @param inv The invite session.
+ * @param target The current target to be tried.
+ * @param cmd Action to be performed for the target. Set this
+ * parameter to one of the value below:
+ * - PJSIP_REDIRECT_ACCEPT: immediately accept the
+ * redirection (default value). When set, the
+ * session will immediately resend INVITE request
+ * to the target.
+ * - PJSIP_REDIRECT_REJECT: immediately reject this
+ * target. The session will continue retrying with
+ * next target if present, or disconnect the call
+ * if there is no more target to try.
+ * - PJSIP_REDIRECT_STOP: stop the whole redirection
+ * process and immediately disconnect the call. The
+ * on_state_changed() callback will be called with
+ * PJSIP_INV_STATE_DISCONNECTED state immediately
+ * after this callback returns.
+ * - PJSIP_REDIRECT_PENDING: set to this value if
+ * no decision can be made immediately (for example
+ * to request confirmation from user). Application
+ * then MUST call #pjsip_inv_process_redirect()
+ * to either accept or reject the redirection upon
+ * getting user decision.
+ * @param e The event that caused this callback to be called.
+ * This could be the receipt of 3xx response, or
+ * 4xx/5xx response received for the INVITE sent to
+ * subsequent targets, or NULL if this callback is
+ * called from within #pjsip_inv_process_redirect()
+ * context.
+ */
+ void (*on_redirected)(pjsip_inv_session *inv, const pjsip_uri *target,
+ pjsip_redirect_op *cmd, const pjsip_event *e);
+
} pjsip_inv_callback;
@@ -276,6 +323,7 @@ struct pjsip_inv_session
unsigned options; /**< Options in use. */
pjmedia_sdp_neg *neg; /**< Negotiator. */
pjsip_transaction *invite_tsx; /**< 1st invite tsx. */
+ pjsip_tx_data *invite_req; /**< Saved invite req */
pjsip_tx_data *last_answer; /**< Last INVITE resp. */
pjsip_tx_data *last_ack; /**< Last ACK request */
pj_int32_t last_ack_cseq; /**< CSeq of last ACK */
@@ -475,6 +523,53 @@ PJ_DECL(pj_status_t) pjsip_inv_terminate( pjsip_inv_session *inv,
/**
+ * Restart UAC session and prepare the session for a new initial INVITE.
+ * This function can be called for example when the application wants to
+ * follow redirection response with a new call reusing this session so
+ * that the new call will have the same Call-ID and From headers. After
+ * the session is restarted, application may create and send a new INVITE
+ * request.
+ *
+ * @param inv The invite session.
+ * @param new_offer Should be set to PJ_TRUE since the application will
+ * restart the session.
+ *
+ * @return PJ_SUCCESS on successful operation.
+ */
+PJ_DECL(pj_status_t) pjsip_inv_uac_restart(pjsip_inv_session *inv,
+ pj_bool_t new_offer);
+
+
+/**
+ * Accept or reject redirection response. Application MUST call this function
+ * after it signaled PJSIP_REDIRECT_PENDING in the \a on_redirected()
+ * callback, to notify the invite session whether to accept or reject the
+ * redirection to the current target. Application can use the combination of
+ * PJSIP_REDIRECT_PENDING command in \a on_redirected() callback and this
+ * function to ask for user permission before redirecting the call.
+ *
+ * Note that if the application chooses to reject or stop redirection (by
+ * using PJSIP_REDIRECT_REJECT or PJSIP_REDIRECT_STOP respectively), the
+ * session disconnection callback will be called before this function returns.
+ * And if the application rejects the target, the \a on_redirected() callback
+ * may also be called before this function returns if there is another target
+ * to try.
+ *
+ * @param inv The invite session.
+ * @param cmd Redirection operation. The semantic of this argument
+ * is similar to the description in the \a on_redirected()
+ * callback, except that the PJSIP_REDIRECT_PENDING is
+ * not accepted here.
+ * @param e Should be set to NULL.
+ *
+ * @return PJ_SUCCESS on successful operation.
+ */
+PJ_DECL(pj_status_t) pjsip_inv_process_redirect(pjsip_inv_session *inv,
+ pjsip_redirect_op cmd,
+ pjsip_event *e);
+
+
+/**
* Create the initial INVITE request for this session. This function can only
* be called for UAC session. If local media capability is specified when
* the invite session was created, then this function will put an SDP offer
diff --git a/pjsip/include/pjsip/sip_dialog.h b/pjsip/include/pjsip/sip_dialog.h
index 182c7a2e..e41f7b51 100644
--- a/pjsip/include/pjsip/sip_dialog.h
+++ b/pjsip/include/pjsip/sip_dialog.h
@@ -29,6 +29,7 @@
#include <pjsip/sip_auth.h>
#include <pjsip/sip_errno.h>
#include <pjsip/sip_transport.h>
+#include <pjsip/sip_util.h>
#include <pj/sock.h>
#include <pj/assert.h>
@@ -121,6 +122,7 @@ struct pjsip_dialog
/* Dialog's session properties. */
pjsip_dialog_state state; /**< Dialog state. */
pjsip_uri *target; /**< Current target. */
+ pjsip_target_set target_set; /**< Target set, for UAC only. */
pjsip_hdr inv_hdr; /**< Headers from hparam in dest URL */
pjsip_dlg_party local; /**< Local party info. */
pjsip_dlg_party remote; /**< Remote party info. */
diff --git a/pjsip/include/pjsip/sip_util.h b/pjsip/include/pjsip/sip_util.h
index 4a5a4716..1314e361 100644
--- a/pjsip/include/pjsip/sip_util.h
+++ b/pjsip/include/pjsip/sip_util.h
@@ -26,6 +26,195 @@
PJ_BEGIN_DECL
/**
+ * @defgroup PJSIP_ENDPT_TARGET_URI Target URI Management
+ * @ingroup PJSIP_CORE_CORE
+ * @brief Management of target URI's in case of redirection
+ * @{
+ * This module provides utility functions to manage target set for UAC.
+ * The target set is provided as pjsip_target_set structure. Initially,
+ * the target set for UAC contains only one target, that is the target of
+ * the initial request. When 3xx/redirection class response is received,
+ * the UAC can use the functionality of this module to add the URI's listed
+ * in the Contact header(s) in the response to the target set, and retry
+ * sending the request to the next destination/target. The UAC may retry
+ * this sequentially until one of the target answers with succesful/2xx
+ * response, or one target returns global error/6xx response, or all targets
+ * are exhausted.
+ *
+ * This module is currently used by the \ref PJSIP_INV.
+ */
+
+/**
+ * This structure describes a target, which can be chained together to form
+ * a target set. Each target contains an URI, priority (as q-value), and
+ * the last status code and reason phrase received from the target, if the
+ * target has been contacted. If the target has not been contacted, the
+ * status code field will be zero.
+ */
+typedef struct pjsip_target
+{
+ PJ_DECL_LIST_MEMBER(struct pjsip_target);/**< Standard list element */
+ pjsip_uri *uri; /**< The target URI */
+ int q1000; /**< q-value multiplied by 1000 */
+ pjsip_status_code code; /**< Last status code received */
+ pj_str_t reason; /**< Last reason phrase received */
+} pjsip_target;
+
+
+/**
+ * This describes a target set. A target set contains a linked-list of
+ * pjsip_target.
+ */
+typedef struct pjsip_target_set
+{
+ pjsip_target head; /**< Target linked-list head */
+ pjsip_target *current; /**< Current target. */
+} pjsip_target_set;
+
+
+/**
+ * These enumerations specify the action to be performed to a redirect
+ * response.
+ */
+typedef enum pjsip_redirect_op
+{
+ /**
+ * Reject the redirection to the current target. The UAC will
+ * select the next target from the target set if exists.
+ */
+ PJSIP_REDIRECT_REJECT,
+
+ /**
+ * Accept the redirection to the current target. The INVITE request
+ * will be resent to the current target.
+ */
+ PJSIP_REDIRECT_ACCEPT,
+
+ /**
+ * Defer the redirection decision, for example to request permission
+ * from the end user.
+ */
+ PJSIP_REDIRECT_PENDING,
+
+ /**
+ * Stop the whole redirection process altogether. This will cause
+ * the invite session to be disconnected.
+ */
+ PJSIP_REDIRECT_STOP
+
+} pjsip_redirect_op;
+
+
+/**
+ * Initialize target set. This will empty the list of targets in the
+ * target set.
+ *
+ * @param tset The target set.
+ */
+PJ_INLINE(void) pjsip_target_set_init(pjsip_target_set *tset)
+{
+ pj_list_init(&tset->head);
+ tset->current = NULL;
+}
+
+
+/**
+ * Add an URI to the target set, if the URI is not already in the target set.
+ * The URI comparison rule of pjsip_uri_cmp() will be used to determine the
+ * equality of this URI compared to existing URI's in the target set. The
+ * URI will be cloned using the specified memory pool before it is added to
+ * the list.
+ *
+ * The first URI added to the target set will also be made current target
+ * by this function.
+ *
+ * @param tset The target set.
+ * @param pool The memory pool to be used to duplicate the URI.
+ * @param uri The URI to be checked and added.
+ * @param q1000 The q-value multiplied by 1000.
+ *
+ * @return PJ_SUCCESS if the URI was added to the target set,
+ * or PJ_EEXISTS if the URI already exists in the target
+ * set, or other error codes.
+ */
+PJ_DECL(pj_status_t) pjsip_target_set_add_uri(pjsip_target_set *tset,
+ pj_pool_t *pool,
+ const pjsip_uri *uri,
+ int q1000);
+
+/**
+ * Extract URI's in the Contact headers of the specified (response) message
+ * and add them to the target set. This function will also check if the
+ * URI's already exist in the target set before adding them to the list.
+ *
+ * @param tset The target set.
+ * @param pool The memory pool to be used to duplicate the URI's.
+ * @param msg SIP message from which the Contact headers will be
+ * scanned and the URI's to be extracted, checked, and
+ * added to the target set.
+ *
+ * @return PJ_SUCCESS if at least one URI was added to the
+ * target set, or PJ_EEXISTS if all URI's in the message
+ * already exists in the target set or if the message
+ * doesn't contain usable Contact headers, or other error
+ * codes.
+ */
+PJ_DECL(pj_status_t) pjsip_target_set_add_from_msg(pjsip_target_set *tset,
+ pj_pool_t *pool,
+ const pjsip_msg *msg);
+
+/**
+ * Get the next target to be retried. This function will scan the target set
+ * for target which hasn't been tried, and return one target with the
+ * highest q-value, if such target exists. This function will return NULL
+ * if there is one target with 2xx or 6xx code or if all targets have been
+ * tried.
+ *
+ * @param tset The target set.
+ *
+ * @return The next target to be tried, or NULL if all targets have
+ * been tried or at least one target returns 2xx or 6xx
+ * response.
+ */
+PJ_DECL(pjsip_target*)
+pjsip_target_set_get_next(const pjsip_target_set *tset);
+
+
+/**
+ * Set the specified target as the current target in the target set. The
+ * current target may be used by application to keep track on which target
+ * is currently being operated on.
+ *
+ * @param tset The target set.
+ * @param target The target to be set as current target.
+ *
+ * @return PJ_SUCCESS or the appropriate error code.
+ */
+PJ_DECL(pj_status_t) pjsip_target_set_set_current(pjsip_target_set *tset,
+ pjsip_target *target);
+
+
+/**
+ * Set the status code and reason phrase of the specified target.
+ *
+ * @param target The target.
+ * @param pool The memory pool to be used to duplicate the reason phrase.
+ * @param code The SIP status code to be set to the target.
+ * @param reason The reason phrase to be set to the target.
+ *
+ * @return PJ_SUCCESS on successful operation or the appropriate
+ * error code.
+ */
+PJ_DECL(pj_status_t) pjsip_target_assign_status(pjsip_target *target,
+ pj_pool_t *pool,
+ int status_code,
+ const pj_str_t *reason);
+
+/**
+ * @}
+ */
+
+/**
* @defgroup PJSIP_ENDPT_STATELESS Message Creation and Stateless Operations
* @ingroup PJSIP_CORE_CORE
* @brief Utilities to create various messages and base function to send messages.
diff --git a/pjsip/include/pjsua-lib/pjsua.h b/pjsip/include/pjsua-lib/pjsua.h
index 1818f428..efc1c188 100644
--- a/pjsip/include/pjsua-lib/pjsua.h
+++ b/pjsip/include/pjsua-lib/pjsua.h
@@ -1062,6 +1062,54 @@ typedef struct pjsua_callback
*/
void (*on_nat_detect)(const pj_stun_nat_detect_result *res);
+ /**
+ * This callback is called when the call is about to resend the
+ * INVITE request to the specified target, following the previously
+ * received redirection response.
+ *
+ * Application may accept the redirection to the specified target
+ * (the default behavior if this callback is implemented), reject
+ * this target only and make the session continue to try the next
+ * target in the list if such target exists, stop the whole
+ * redirection process altogether and cause the session to be
+ * disconnected, or defer the decision to ask for user confirmation.
+ *
+ * This callback is optional. If this callback is not implemented,
+ * the default behavior is to NOT follow the redirection response.
+ *
+ * @param call_id The call ID.
+ * @param target The current target to be tried.
+ * @param cmd Action to be performed for the target. Set this
+ * parameter to one of the value below:
+ * - PJSIP_REDIRECT_ACCEPT: immediately accept the
+ * redirection (default value). When set, the
+ * call will immediately resend INVITE request
+ * to the target.
+ * - PJSIP_REDIRECT_REJECT: immediately reject this
+ * target. The call will continue retrying with
+ * next target if present, or disconnect the call
+ * if there is no more target to try.
+ * - PJSIP_REDIRECT_STOP: stop the whole redirection
+ * process and immediately disconnect the call. The
+ * on_call_state() callback will be called with
+ * PJSIP_INV_STATE_DISCONNECTED state immediately
+ * after this callback returns.
+ * - PJSIP_REDIRECT_PENDING: set to this value if
+ * no decision can be made immediately (for example
+ * to request confirmation from user). Application
+ * then MUST call #pjsua_call_process_redirect()
+ * to either accept or reject the redirection upon
+ * getting user decision.
+ * @param e The event that caused this callback to be called.
+ * This could be the receipt of 3xx response, or
+ * 4xx/5xx response received for the INVITE sent to
+ * subsequent targets, or NULL if this callback is
+ * called from within #pjsua_call_process_redirect()
+ * context.
+ */
+ void (*on_call_redirected)(pjsua_call_id call_id, const pjsip_uri *target,
+ pjsip_redirect_op *cmd, const pjsip_event *e);
+
} pjsua_callback;
@@ -3132,6 +3180,32 @@ PJ_DECL(pj_status_t) pjsua_call_hangup(pjsua_call_id call_id,
const pj_str_t *reason,
const pjsua_msg_data *msg_data);
+/**
+ * Accept or reject redirection response. Application MUST call this function
+ * after it signaled PJSIP_REDIRECT_PENDING in the \a on_call_redirected()
+ * callback, to notify the call whether to accept or reject the redirection
+ * to the current target. Application can use the combination of
+ * PJSIP_REDIRECT_PENDING command in \a on_call_redirected() callback and
+ * this function to ask for user permission before redirecting the call.
+ *
+ * Note that if the application chooses to reject or stop redirection (by
+ * using PJSIP_REDIRECT_REJECT or PJSIP_REDIRECT_STOP respectively), the
+ * call disconnection callback will be called before this function returns.
+ * And if the application rejects the target, the \a on_call_redirected()
+ * callback may also be called before this function returns if there is
+ * another target to try.
+ *
+ * @param call_id The call ID.
+ * @param cmd Redirection operation to be applied to the current
+ * target. The semantic of this argument is similar
+ * to the description in the \a on_call_redirected()
+ * callback, except that the PJSIP_REDIRECT_PENDING is
+ * not accepted here.
+ *
+ * @return PJ_SUCCESS on successful operation.
+ */
+PJ_DECL(pj_status_t) pjsua_call_process_redirect(pjsua_call_id call_id,
+ pjsip_redirect_op cmd);
/**
* Put the specified call on hold. This will send re-INVITE with the
diff --git a/pjsip/src/pjsip-ua/sip_inv.c b/pjsip/src/pjsip-ua/sip_inv.c
index 0ec82cef..46872f06 100644
--- a/pjsip/src/pjsip-ua/sip_inv.c
+++ b/pjsip/src/pjsip-ua/sip_inv.c
@@ -225,6 +225,10 @@ void inv_set_state(pjsip_inv_session *inv, pjsip_inv_state state,
pjsip_tx_data_dec_ref(inv->last_ack);
inv->last_ack = NULL;
}
+ if (inv->invite_req) {
+ pjsip_tx_data_dec_ref(inv->invite_req);
+ inv->invite_req = NULL;
+ }
pjsip_100rel_end_session(inv);
pjsip_dlg_dec_session(inv->dlg, &mod_inv.mod);
}
@@ -1219,6 +1223,35 @@ PJ_DEF(pj_status_t) pjsip_inv_terminate( pjsip_inv_session *inv,
}
+/*
+ * Restart UAC session, possibly because app or us wants to re-send the
+ * INVITE request due to 401/407 challenge or 3xx response.
+ */
+PJ_DEF(pj_status_t) pjsip_inv_uac_restart(pjsip_inv_session *inv,
+ pj_bool_t new_offer)
+{
+ PJ_ASSERT_RETURN(inv, PJ_EINVAL);
+
+ inv->state = PJSIP_INV_STATE_NULL;
+ inv->invite_tsx = NULL;
+ if (inv->last_answer) {
+ pjsip_tx_data_dec_ref(inv->last_answer);
+ inv->last_answer = NULL;
+ }
+
+ if (new_offer && inv->neg) {
+ pjmedia_sdp_neg_state neg_state;
+
+ neg_state = pjmedia_sdp_neg_get_state(inv->neg);
+ if (neg_state == PJMEDIA_SDP_NEG_STATE_LOCAL_OFFER) {
+ pjmedia_sdp_neg_cancel_offer(inv->neg);
+ }
+ }
+
+ return PJ_SUCCESS;
+}
+
+
static void *clone_sdp(pj_pool_t *pool, const void *data, unsigned len)
{
PJ_UNUSED_ARG(len);
@@ -1891,6 +1924,203 @@ PJ_DEF(pj_status_t) pjsip_inv_end_session( pjsip_inv_session *inv,
return PJ_SUCCESS;
}
+/* Following redirection recursion, get next target from the target set and
+ * notify user.
+ *
+ * Returns PJ_FALSE if recursion fails (either because there's no more target
+ * or user rejects the recursion). If we return PJ_FALSE, caller should
+ * disconnect the session.
+ *
+ * Note:
+ * the event 'e' argument may be NULL.
+ */
+static pj_bool_t inv_uac_recurse(pjsip_inv_session *inv, int code,
+ const pj_str_t *reason, pjsip_event *e)
+{
+ pjsip_redirect_op op = PJSIP_REDIRECT_ACCEPT;
+ pjsip_target *target;
+
+ /* Won't redirect if the callback is not implemented. */
+ if (mod_inv.cb.on_redirected == NULL)
+ return PJ_FALSE;
+
+ if (reason == NULL)
+ reason = pjsip_get_status_text(code);
+
+ /* Set status of current target */
+ pjsip_target_assign_status(inv->dlg->target_set.current, inv->dlg->pool,
+ code, reason);
+
+ /* Fetch next target from the target set. We only want to
+ * process SIP/SIPS URI for now.
+ */
+ for (;;) {
+ target = pjsip_target_set_get_next(&inv->dlg->target_set);
+ if (target == NULL) {
+ /* No more target. */
+ return PJ_FALSE;
+ }
+
+ if (!PJSIP_URI_SCHEME_IS_SIP(target->uri) &&
+ !PJSIP_URI_SCHEME_IS_SIPS(target->uri))
+ {
+ code = PJSIP_SC_UNSUPPORTED_URI_SCHEME;
+ reason = pjsip_get_status_text(code);
+
+ /* Mark this target as unusable and fetch next target. */
+ pjsip_target_assign_status(target, inv->dlg->pool, code, reason);
+ } else {
+ /* Found a target */
+ break;
+ }
+ }
+
+ /* We have target in 'target'. Set this target as current target
+ * and notify callback.
+ */
+ pjsip_target_set_set_current(&inv->dlg->target_set, target);
+
+ (*mod_inv.cb.on_redirected)(inv, target->uri, &op, e);
+
+
+ /* Check what the application wants to do now */
+ switch (op) {
+ case PJSIP_REDIRECT_ACCEPT:
+ case PJSIP_REDIRECT_STOP:
+ /* Must increment session counter, that's the convention of the
+ * pjsip_inv_process_redirect().
+ */
+ pjsip_dlg_inc_session(inv->dlg, &mod_inv.mod);
+
+ /* Act on the recursion */
+ pjsip_inv_process_redirect(inv, op, e);
+ return PJ_TRUE;
+
+ case PJSIP_REDIRECT_PENDING:
+ /* Increment session so that the dialog/session is not destroyed
+ * while we're waiting for user confirmation.
+ */
+ pjsip_dlg_inc_session(inv->dlg, &mod_inv.mod);
+
+ /* Also clear the invite_tsx variable, otherwise when this tsx is
+ * terminated, it will also terminate the session.
+ */
+ inv->invite_tsx = NULL;
+
+ /* Done. The processing will continue once the application calls
+ * pjsip_inv_process_redirect().
+ */
+ return PJ_TRUE;
+
+ case PJSIP_REDIRECT_REJECT:
+ /* Recursively call this function again to fetch next target, if any.
+ */
+ return inv_uac_recurse(inv, PJSIP_SC_REQUEST_TERMINATED, NULL, e);
+
+ }
+
+ pj_assert(!"Should not reach here");
+ return PJ_FALSE;
+}
+
+
+/* Process redirection/recursion */
+PJ_DEF(pj_status_t) pjsip_inv_process_redirect( pjsip_inv_session *inv,
+ pjsip_redirect_op op,
+ pjsip_event *e)
+{
+ const pjsip_status_code cancel_code = PJSIP_SC_REQUEST_TERMINATED;
+ pjsip_event usr_event;
+ pj_status_t status = PJ_SUCCESS;
+
+ PJ_ASSERT_RETURN(inv && op != PJSIP_REDIRECT_PENDING, PJ_EINVAL);
+
+ if (e == NULL) {
+ PJSIP_EVENT_INIT_USER(usr_event, NULL, NULL, NULL, NULL);
+ e = &usr_event;
+ }
+
+ pjsip_dlg_inc_lock(inv->dlg);
+
+ /* Decrement session. That's the convention here to prevent the dialog
+ * or session from being destroyed while we're waiting for user
+ * confirmation.
+ */
+ pjsip_dlg_dec_session(inv->dlg, &mod_inv.mod);
+
+ /* See what the application wants to do now */
+ switch (op) {
+ case PJSIP_REDIRECT_ACCEPT:
+ /* User accept the redirection. Reset the session and resend the
+ * INVITE request.
+ */
+ {
+ pjsip_tx_data *tdata;
+ pjsip_via_hdr *via;
+
+ /* Get the original INVITE request. */
+ tdata = inv->invite_req;
+ pjsip_tx_data_add_ref(tdata);
+
+ /* Restore strict route set.
+ * See http://trac.pjsip.org/repos/ticket/492
+ */
+ pjsip_restore_strict_route_set(tdata);
+
+ /* Set target */
+ tdata->msg->line.req.uri =
+ pjsip_uri_clone(tdata->pool, inv->dlg->target_set.current->uri);
+
+ /* Remove branch param in Via header. */
+ via = (pjsip_via_hdr*)
+ pjsip_msg_find_hdr(tdata->msg, PJSIP_H_VIA, NULL);
+ via->branch_param.slen = 0;
+
+ /* Must invalidate the message! */
+ pjsip_tx_data_invalidate_msg(tdata);
+
+ /* Reset the session */
+ pjsip_inv_uac_restart(inv, PJ_FALSE);
+
+ /* (re)Send the INVITE request */
+ status = pjsip_inv_send_msg(inv, tdata);
+ }
+ break;
+
+ case PJSIP_REDIRECT_STOP:
+ /* User doesn't want the redirection. Disconnect the session now. */
+ inv_set_cause(inv, cancel_code, pjsip_get_status_text(cancel_code));
+ inv_set_state(inv, PJSIP_INV_STATE_DISCONNECTED, e);
+
+ /* Caller should expect that the invite session is gone now, so
+ * we don't need to set status to PJSIP_ESESSIONTERMINATED here.
+ */
+ break;
+
+ case PJSIP_REDIRECT_REJECT:
+ /* Current target is rejected. Fetch next target if any. */
+ if (inv_uac_recurse(inv, cancel_code, NULL, NULL) == PJ_FALSE) {
+ inv_set_cause(inv, cancel_code,
+ pjsip_get_status_text(cancel_code));
+ inv_set_state(inv, PJSIP_INV_STATE_DISCONNECTED, e);
+
+ /* Tell caller that the invite session is gone now */
+ status = PJSIP_ESESSIONTERMINATED;
+ }
+ break;
+
+
+ case PJSIP_REDIRECT_PENDING:
+ pj_assert(!"Should not happen");
+ break;
+ }
+
+
+ pjsip_dlg_dec_lock(inv->dlg);
+
+ return status;
+}
+
/*
* Create re-INVITE.
@@ -2552,6 +2782,19 @@ static void inv_on_state_null( pjsip_inv_session *inv, pjsip_event *e)
if (dlg->role == PJSIP_ROLE_UAC) {
+ /* Save the original INVITE request, if on_redirected() callback
+ * is implemented. We may need to resend the INVITE if we receive
+ * redirection response.
+ */
+ if (mod_inv.cb.on_redirected) {
+ if (inv->invite_req) {
+ pjsip_tx_data_dec_ref(inv->invite_req);
+ inv->invite_req = NULL;
+ }
+ inv->invite_req = tsx->last_tx;
+ pjsip_tx_data_add_ref(inv->invite_req);
+ }
+
switch (tsx->state) {
case PJSIP_TSX_STATE_CALLING:
inv_set_state(inv, PJSIP_INV_STATE_CALLING, e);
@@ -2673,6 +2916,105 @@ static pj_bool_t handle_uac_tsx_response(pjsip_inv_session *inv,
}
+/* Handle call rejection, especially with regard to processing call
+ * redirection. We need to handle the following scenarios:
+ * - 3xx response is received -- see if on_redirected() callback is
+ * implemented. If so, add the Contact URIs in the response to the
+ * target set and notify user.
+ * - 4xx - 6xx resposne is received -- see if we're currently recursing,
+ * if so fetch the next target if any and notify the on_redirected()
+ * callback.
+ * - for other cases -- disconnect the session.
+ */
+static void handle_uac_call_rejection(pjsip_inv_session *inv, pjsip_event *e)
+{
+ pjsip_transaction *tsx = e->body.tsx_state.tsx;
+ pj_status_t status;
+
+ if (PJSIP_IS_STATUS_IN_CLASS(tsx->status_code, 300)) {
+
+ if (mod_inv.cb.on_redirected == NULL) {
+
+ /* Redirection callback is not implemented, disconnect the
+ * call.
+ */
+ goto terminate_session;
+
+ } else {
+ const pjsip_msg *res_msg;
+
+ res_msg = e->body.tsx_state.src.rdata->msg_info.msg;
+
+ /* Gather all Contact URI's in the response and add them
+ * to target set. The function will take care of removing
+ * duplicate URI's.
+ */
+ pjsip_target_set_add_from_msg(&inv->dlg->target_set,
+ inv->dlg->pool, res_msg);
+
+ /* Recurse to alternate targets if application allows us */
+ if (!inv_uac_recurse(inv, tsx->status_code, &tsx->status_text, e))
+ {
+ /* Recursion fails, terminate session now */
+ goto terminate_session;
+ }
+
+ /* Done */
+ }
+
+ } else if ((tsx->status_code==401 || tsx->status_code==407) &&
+ !inv->cancelling)
+ {
+
+ /* Handle authentication failure:
+ * Resend the request with Authorization header.
+ */
+ pjsip_tx_data *tdata;
+
+ status = pjsip_auth_clt_reinit_req(&inv->dlg->auth_sess,
+ e->body.tsx_state.src.rdata,
+ tsx->last_tx,
+ &tdata);
+
+ if (status != PJ_SUCCESS) {
+
+ /* Does not have proper credentials. If we are currently
+ * recursing, try the next target. Otherwise end the session.
+ */
+ if (!inv_uac_recurse(inv, tsx->status_code, &tsx->status_text, e))
+ {
+ /* Recursion fails, terminate session now */
+ goto terminate_session;
+ }
+
+ } else {
+
+ /* Restart session. */
+ pjsip_inv_uac_restart(inv, PJ_FALSE);
+
+ /* Send the request. */
+ status = pjsip_inv_send_msg(inv, tdata);
+ }
+
+ } else if (PJSIP_IS_STATUS_IN_CLASS(tsx->status_code, 600)) {
+ /* Global error */
+ goto terminate_session;
+
+ } else {
+ /* See if we have alternate target to try */
+ if (!inv_uac_recurse(inv, tsx->status_code, &tsx->status_text, e)) {
+ /* Recursion fails, terminate session now */
+ goto terminate_session;
+ }
+ }
+ return;
+
+terminate_session:
+ inv_set_cause(inv, tsx->status_code, &tsx->status_text);
+ inv_set_state(inv, PJSIP_INV_STATE_DISCONNECTED, e);
+}
+
+
/*
* State CALLING is after sending initial INVITE request but before
* any response (with tag) is received.
@@ -2735,47 +3077,8 @@ static void inv_on_state_calling( pjsip_inv_session *inv, pjsip_event *e)
inv_check_sdp_in_incoming_msg(inv, tsx,
e->body.tsx_state.src.rdata);
- } else if ((tsx->status_code==401 || tsx->status_code==407) &&
- !inv->cancelling)
- {
-
- /* Handle authentication failure:
- * Resend the request with Authorization header.
- */
- pjsip_tx_data *tdata;
-
- status = pjsip_auth_clt_reinit_req(&inv->dlg->auth_sess,
- e->body.tsx_state.src.rdata,
- tsx->last_tx,
- &tdata);
-
- if (status != PJ_SUCCESS) {
-
- /* Does not have proper credentials.
- * End the session.
- */
- inv_set_cause(inv, tsx->status_code, &tsx->status_text);
- inv_set_state(inv, PJSIP_INV_STATE_DISCONNECTED, e);
-
- } else {
-
- /* Restart session. */
- inv->state = PJSIP_INV_STATE_NULL;
- inv->invite_tsx = NULL;
- if (inv->last_answer) {
- pjsip_tx_data_dec_ref(inv->last_answer);
- inv->last_answer = NULL;
- }
-
- /* Send the request. */
- status = pjsip_inv_send_msg(inv, tdata);
- }
-
} else {
-
- inv_set_cause(inv, tsx->status_code, &tsx->status_text);
- inv_set_state(inv, PJSIP_INV_STATE_DISCONNECTED, e);
-
+ handle_uac_call_rejection(inv, e);
}
break;
@@ -2942,6 +3245,10 @@ static void inv_on_state_early( pjsip_inv_session *inv, pjsip_event *e)
e->body.tsx_state.src.rdata);
}
+ } else if (tsx->role == PJSIP_ROLE_UAC) {
+
+ handle_uac_call_rejection(inv, e);
+
} else {
inv_set_cause(inv, tsx->status_code, &tsx->status_text);
inv_set_state(inv, PJSIP_INV_STATE_DISCONNECTED, e);
diff --git a/pjsip/src/pjsip/sip_dialog.c b/pjsip/src/pjsip/sip_dialog.c
index 4489cb8e..6c6b2b3a 100644
--- a/pjsip/src/pjsip/sip_dialog.c
+++ b/pjsip/src/pjsip/sip_dialog.c
@@ -91,6 +91,7 @@ static pj_status_t create_dialog( pjsip_user_agent *ua,
if (status != PJ_SUCCESS)
goto on_error;
+ pjsip_target_set_init(&dlg->target_set);
*p_dlg = dlg;
return PJ_SUCCESS;
@@ -182,6 +183,9 @@ PJ_DEF(pj_status_t) pjsip_dlg_create_uac( pjsip_user_agent *ua,
pj_list_init(&uri->header_param);
}
+ /* Add target to the target set */
+ pjsip_target_set_add_uri(&dlg->target_set, dlg->pool, dlg->target, 0);
+
/* Init local info. */
dlg->local.info = pjsip_from_hdr_create(dlg->pool);
pj_strdup_with_null(dlg->pool, &dlg->local.info_str, local_uri);
diff --git a/pjsip/src/pjsip/sip_util.c b/pjsip/src/pjsip/sip_util.c
index f059d20e..3529313a 100644
--- a/pjsip/src/pjsip/sip_util.c
+++ b/pjsip/src/pjsip/sip_util.c
@@ -49,6 +49,139 @@ static const char *event_str[] =
static pj_str_t str_TEXT = { "text", 4},
str_PLAIN = { "plain", 5 };
+/* Add URI to target-set */
+PJ_DEF(pj_status_t) pjsip_target_set_add_uri( pjsip_target_set *tset,
+ pj_pool_t *pool,
+ const pjsip_uri *uri,
+ int q1000)
+{
+ pjsip_target *t, *pos = NULL;
+
+ PJ_ASSERT_RETURN(tset && pool && uri, PJ_EINVAL);
+
+ /* Set q-value to 1 if it is not set */
+ if (q1000 <= 0)
+ q1000 = 1000;
+
+ /* Scan all the elements to see for duplicates, and at the same time
+ * get the position where the new element should be inserted to
+ * based on the q-value.
+ */
+ t = tset->head.next;
+ while (t != &tset->head) {
+ if (pjsip_uri_cmp(PJSIP_URI_IN_REQ_URI, t->uri, uri)==PJ_SUCCESS)
+ return PJ_EEXISTS;
+ if (pos==NULL && t->q1000 < q1000)
+ pos = t;
+ t = t->next;
+ }
+
+ /* Create new element */
+ t = PJ_POOL_ZALLOC_T(pool, pjsip_target);
+ t->uri = pjsip_uri_clone(pool, uri);
+ t->q1000 = q1000;
+
+ /* Insert */
+ if (pos == NULL)
+ pj_list_push_back(&tset->head, t);
+ else
+ pj_list_insert_before(pos, t);
+
+ /* Set current target if this is the first URI */
+ if (tset->current == NULL)
+ tset->current = t;
+
+ return PJ_SUCCESS;
+}
+
+/* Add URI's in the Contact header in the message to target-set */
+PJ_DEF(pj_status_t) pjsip_target_set_add_from_msg( pjsip_target_set *tset,
+ pj_pool_t *pool,
+ const pjsip_msg *msg)
+{
+ const pjsip_hdr *hdr;
+ unsigned added = 0;
+
+ PJ_ASSERT_RETURN(tset && pool && msg, PJ_EINVAL);
+
+ /* Scan for Contact headers and add the URI */
+ hdr = msg->hdr.next;
+ while (hdr != &msg->hdr) {
+ if (hdr->type == PJSIP_H_CONTACT) {
+ const pjsip_contact_hdr *cn_hdr = (const pjsip_contact_hdr*)hdr;
+
+ if (!cn_hdr->star) {
+ pj_status_t rc;
+ rc = pjsip_target_set_add_uri(tset, pool, cn_hdr->uri,
+ cn_hdr->q1000);
+ if (rc == PJ_SUCCESS)
+ ++added;
+ }
+ }
+ hdr = hdr->next;
+ }
+
+ return added ? PJ_SUCCESS : PJ_EEXISTS;
+}
+
+
+/* Get next target, if any */
+PJ_DEF(pjsip_target*) pjsip_target_set_get_next(const pjsip_target_set *tset)
+{
+ const pjsip_target *t, *next = NULL;
+
+ t = tset->head.next;
+ while (t != &tset->head) {
+ if (PJSIP_IS_STATUS_IN_CLASS(t->code, 200)) {
+ /* No more target since one target has been successful */
+ return NULL;
+ }
+ if (PJSIP_IS_STATUS_IN_CLASS(t->code, 600)) {
+ /* No more target since one target returned global error */
+ return NULL;
+ }
+ if (t->code==0 && next==NULL) {
+ /* This would be the next target as long as we don't find
+ * targets with 2xx or 6xx status after this.
+ */
+ next = t;
+ }
+ t = t->next;
+ }
+
+ return (pjsip_target*)next;
+}
+
+
+/* Set current target */
+PJ_DEF(pj_status_t) pjsip_target_set_set_current( pjsip_target_set *tset,
+ pjsip_target *target)
+{
+ PJ_ASSERT_RETURN(tset && target, PJ_EINVAL);
+ PJ_ASSERT_RETURN(pj_list_find_node(tset, target) != NULL, PJ_ENOTFOUND);
+
+ tset->current = target;
+
+ return PJ_SUCCESS;
+}
+
+
+/* Assign status to a target */
+PJ_DEF(pj_status_t) pjsip_target_assign_status( pjsip_target *target,
+ pj_pool_t *pool,
+ int status_code,
+ const pj_str_t *reason)
+{
+ PJ_ASSERT_RETURN(target && pool && status_code && reason, PJ_EINVAL);
+
+ target->code = status_code;
+ pj_strdup(pool, &target->reason, reason);
+
+ return PJ_SUCCESS;
+}
+
+
+
/*
* Initialize transmit data (msg) with the headers and optional body.
* This will just put the headers in the message as it is. Be carefull
diff --git a/pjsip/src/pjsua-lib/pjsua_call.c b/pjsip/src/pjsua-lib/pjsua_call.c
index 546be666..6831487d 100644
--- a/pjsip/src/pjsua-lib/pjsua_call.c
+++ b/pjsip/src/pjsua-lib/pjsua_call.c
@@ -65,6 +65,14 @@ static void pjsua_call_on_tsx_state_changed(pjsip_inv_session *inv,
pjsip_transaction *tsx,
pjsip_event *e);
+/*
+ * Redirection handler.
+ */
+static void pjsua_call_on_redirected(pjsip_inv_session *inv,
+ const pjsip_uri *target,
+ pjsip_redirect_op *cmd,
+ const pjsip_event *e);
+
/* Create SDP for call hold. */
static pj_status_t create_sdp_of_call_hold(pjsua_call *call,
@@ -156,7 +164,7 @@ pj_status_t pjsua_call_subsys_init(const pjsua_config *cfg)
inv_cb.on_rx_offer = &pjsua_call_on_rx_offer;
inv_cb.on_create_offer = &pjsua_call_on_create_offer;
inv_cb.on_tsx_state_changed = &pjsua_call_on_tsx_state_changed;
-
+ inv_cb.on_redirected = &pjsua_call_on_redirected;
/* Initialize invite session module: */
status = pjsip_inv_usage_init(pjsua_var.endpt, &inv_cb);
@@ -1434,6 +1442,32 @@ PJ_DEF(pj_status_t) pjsua_call_hangup(pjsua_call_id call_id,
/*
+ * Accept or reject redirection.
+ */
+PJ_DEF(pj_status_t) pjsua_call_process_redirect( pjsua_call_id call_id,
+ pjsip_redirect_op cmd)
+{
+ pjsua_call *call;
+ pjsip_dialog *dlg;
+ pj_status_t status;
+
+ PJ_ASSERT_RETURN(call_id>=0 && call_id<(int)pjsua_var.ua_cfg.max_calls,
+ PJ_EINVAL);
+
+ status = acquire_call("pjsua_call_process_redirect()", call_id,
+ &call, &dlg);
+ if (status != PJ_SUCCESS)
+ return status;
+
+ status = pjsip_inv_process_redirect(call->inv, cmd, NULL);
+
+ pjsip_dlg_dec_lock(dlg);
+
+ return status;
+}
+
+
+/*
* Put the specified call on hold.
*/
PJ_DEF(pj_status_t) pjsua_call_set_hold(pjsua_call_id call_id,
@@ -2827,12 +2861,19 @@ static void pjsua_call_on_state_changed(pjsip_inv_session *inv,
pj_gettimeofday(&call->dis_time);
if (call->res_time.sec == 0)
pj_gettimeofday(&call->res_time);
- if (e->body.tsx_state.tsx->status_code > call->last_code) {
+ if (e->type == PJSIP_EVENT_TSX_STATE &&
+ e->body.tsx_state.tsx->status_code > call->last_code)
+ {
call->last_code = (pjsip_status_code)
e->body.tsx_state.tsx->status_code;
pj_strncpy(&call->last_text,
&e->body.tsx_state.tsx->status_text,
sizeof(call->last_text_buf_));
+ } else {
+ call->last_code = PJSIP_SC_REQUEST_TERMINATED;
+ pj_strncpy(&call->last_text,
+ pjsip_get_status_text(call->last_code),
+ sizeof(call->last_text_buf_));
}
break;
default:
@@ -3770,3 +3811,28 @@ static void pjsua_call_on_tsx_state_changed(pjsip_inv_session *inv,
PJSUA_UNLOCK();
}
+
+
+/* Redirection handler */
+static void pjsua_call_on_redirected(pjsip_inv_session *inv,
+ const pjsip_uri *target,
+ pjsip_redirect_op *cmd,
+ const pjsip_event *e)
+{
+ pjsua_call *call = (pjsua_call*) inv->dlg->mod_data[pjsua_var.mod.id];
+
+ PJSUA_LOCK();
+
+ if (pjsua_var.ua_cfg.cb.on_call_redirected) {
+ (*pjsua_var.ua_cfg.cb.on_call_redirected)(call->index, target, cmd, e);
+ } else {
+ PJ_LOG(4,(THIS_FILE, "Unhandled redirection for call %d "
+ "(callback not implemented by application). Disconnecting "
+ "call.",
+ call->index));
+ *cmd = PJSIP_REDIRECT_STOP;
+ }
+
+ PJSUA_UNLOCK();
+}
+