Index: pjproject/trunk/pjsip-apps/src/pjsua/pjsua_app.c
===================================================================
--- pjproject/trunk/pjsip-apps/src/pjsua/pjsua_app.c	(revision 2369)
+++ pjproject/trunk/pjsip-apps/src/pjsua/pjsua_app.c	(revision 2370)
@@ -63,4 +63,5 @@
     pjsua_transport_config  udp_cfg;
     pjsua_transport_config  rtp_cfg;
+    pjsip_redirect_op	    redir_op;
 
     unsigned		    acc_cnt;
@@ -279,4 +280,6 @@
     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  ("");
@@ -304,4 +307,5 @@
     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;
@@ -473,5 +477,5 @@
 	   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,
@@ -516,4 +520,5 @@
 	{ "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},
@@ -841,4 +846,13 @@
 	    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;
@@ -1786,4 +1800,11 @@
     }
 
+    /* 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",
@@ -1841,5 +1862,6 @@
  * 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;
@@ -2367,4 +2389,31 @@
 {
     PJ_LOG(3,(THIS_FILE, "Incoming DTMF on call %d: %c", call_id, 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));
+    }
 }
 
@@ -3916,4 +3965,19 @@
 
 
+	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') {
@@ -3963,4 +4027,5 @@
     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;
Index: pjproject/trunk/pjsip-apps/src/test-pjsua/mod_recvfrom.py
===================================================================
--- pjproject/trunk/pjsip-apps/src/test-pjsua/mod_recvfrom.py	(revision 2369)
+++ pjproject/trunk/pjsip-apps/src/test-pjsua/mod_recvfrom.py	(revision 2370)
@@ -1,3 +1,3 @@
-# $Id:$
+# $Id$
 import imp
 import sys
@@ -68,13 +68,15 @@
 				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 != "":
Index: pjproject/trunk/pjsip-apps/src/test-pjsua/scripts-sipp/inv_401_retry_after_100.xml
===================================================================
--- pjproject/trunk/pjsip-apps/src/test-pjsua/scripts-sipp/inv_401_retry_after_100.xml	(revision 2370)
+++ pjproject/trunk/pjsip-apps/src/test-pjsua/scripts-sipp/inv_401_retry_after_100.xml	(revision 2370)
@@ -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>
+
Index: pjproject/trunk/pjsip-apps/src/test-pjsua/scripts-sipp/uas-template.xml
===================================================================
--- pjproject/trunk/pjsip-apps/src/test-pjsua/scripts-sipp/uas-template.xml	(revision 2370)
+++ pjproject/trunk/pjsip-apps/src/test-pjsua/scripts-sipp/uas-template.xml	(revision 2370)
@@ -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>
+
Index: pjproject/trunk/pjsip/include/pjsip-ua/sip_inv.h
===================================================================
--- pjproject/trunk/pjsip/include/pjsip-ua/sip_inv.h	(revision 2369)
+++ pjproject/trunk/pjsip/include/pjsip-ua/sip_inv.h	(revision 2370)
@@ -116,5 +116,4 @@
     void (*on_state_changed)(pjsip_inv_session *inv, pjsip_event *e);
 
-
     /**
      * This callback is called when the invite usage module has created 
@@ -214,4 +213,52 @@
      */
     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;
@@ -277,4 +324,5 @@
     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   */
@@ -473,4 +521,51 @@
 				          int st_code,
 					  pj_bool_t notify );
+
+
+/**
+ * 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);
 
 
Index: pjproject/trunk/pjsip/include/pjsip/sip_dialog.h
===================================================================
--- pjproject/trunk/pjsip/include/pjsip/sip_dialog.h	(revision 2369)
+++ pjproject/trunk/pjsip/include/pjsip/sip_dialog.h	(revision 2370)
@@ -30,4 +30,5 @@
 #include <pjsip/sip_errno.h>
 #include <pjsip/sip_transport.h>
+#include <pjsip/sip_util.h>
 #include <pj/sock.h>
 #include <pj/assert.h>
@@ -122,4 +123,5 @@
     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.		    */
Index: pjproject/trunk/pjsip/include/pjsip/sip_util.h
===================================================================
--- pjproject/trunk/pjsip/include/pjsip/sip_util.h	(revision 2369)
+++ pjproject/trunk/pjsip/include/pjsip/sip_util.h	(revision 2370)
@@ -25,4 +25,193 @@
 
 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);
+
+/**
+ * @}
+ */
 
 /**
Index: pjproject/trunk/pjsip/include/pjsua-lib/pjsua.h
===================================================================
--- pjproject/trunk/pjsip/include/pjsua-lib/pjsua.h	(revision 2369)
+++ pjproject/trunk/pjsip/include/pjsua-lib/pjsua.h	(revision 2370)
@@ -1063,4 +1063,52 @@
     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;
 
@@ -3133,4 +3181,30 @@
 				       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);
 
 /**
Index: pjproject/trunk/pjsip/src/pjsip-ua/sip_inv.c
===================================================================
--- pjproject/trunk/pjsip/src/pjsip-ua/sip_inv.c	(revision 2369)
+++ pjproject/trunk/pjsip/src/pjsip-ua/sip_inv.c	(revision 2370)
@@ -226,4 +226,8 @@
 	    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);
@@ -1220,4 +1224,33 @@
 
 
+/*
+ * 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)
 {
@@ -1890,4 +1923,201 @@
 
     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;
 }
 
@@ -2553,4 +2783,17 @@
 	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:
@@ -2674,4 +2917,103 @@
 
 
+/* 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
@@ -2736,45 +3078,6 @@
 					      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,4 +3245,8 @@
 						  e->body.tsx_state.src.rdata);
 		}
+
+	    } else if (tsx->role == PJSIP_ROLE_UAC) {
+
+		handle_uac_call_rejection(inv, e);
 
 	    } else {
Index: pjproject/trunk/pjsip/src/pjsip/sip_dialog.c
===================================================================
--- pjproject/trunk/pjsip/src/pjsip/sip_dialog.c	(revision 2369)
+++ pjproject/trunk/pjsip/src/pjsip/sip_dialog.c	(revision 2370)
@@ -92,4 +92,5 @@
 	goto on_error;
 
+    pjsip_target_set_init(&dlg->target_set);
 
     *p_dlg = dlg;
@@ -182,4 +183,7 @@
 	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. */
Index: pjproject/trunk/pjsip/src/pjsip/sip_util.c
===================================================================
--- pjproject/trunk/pjsip/src/pjsip/sip_util.c	(revision 2369)
+++ pjproject/trunk/pjsip/src/pjsip/sip_util.c	(revision 2370)
@@ -49,4 +49,137 @@
 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;
+}
+
+
 
 /*
Index: pjproject/trunk/pjsip/src/pjsua-lib/pjsua_call.c
===================================================================
--- pjproject/trunk/pjsip/src/pjsua-lib/pjsua_call.c	(revision 2369)
+++ pjproject/trunk/pjsip/src/pjsua-lib/pjsua_call.c	(revision 2370)
@@ -65,4 +65,12 @@
 					    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);
 
 
@@ -157,5 +165,5 @@
     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: */
@@ -1431,4 +1439,30 @@
 
     return PJ_SUCCESS;
+}
+
+
+/*
+ * 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;
 }
 
@@ -2828,9 +2862,16 @@
 	    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_));
 	    }
@@ -3771,2 +3812,27 @@
     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();
+}
+
