Complete tsx layer selftest, implemented authentication framework

git-svn-id: https://svn.pjsip.org/repos/pjproject/trunk@123 74dad513-b988-da41-8d7b-12977e46ad98
diff --git a/pjsip/build/pjsip_core.dsp b/pjsip/build/pjsip_core.dsp
index 2fddad0..9a28322 100644
--- a/pjsip/build/pjsip_core.dsp
+++ b/pjsip/build/pjsip_core.dsp
@@ -87,7 +87,7 @@
 # PROP Default_Filter "cpp;c;cxx;rc;def;r;odl;idl;hpj;bat"

 # Begin Source File

 

-SOURCE=..\src\pjsip\sip_auth.c

+SOURCE=..\src\pjsip\sip_auth_client.c

 # End Source File

 # Begin Source File

 

@@ -99,6 +99,10 @@
 # End Source File

 # Begin Source File

 

+SOURCE=..\src\pjsip\sip_auth_server.c

+# End Source File

+# Begin Source File

+

 SOURCE=..\src\pjsip\sip_endpoint.c

 # End Source File

 # Begin Source File

diff --git a/pjsip/build/pjsip_ua.dsp b/pjsip/build/pjsip_ua.dsp
index cd45b10..4d7ae9e 100644
--- a/pjsip/build/pjsip_ua.dsp
+++ b/pjsip/build/pjsip_ua.dsp
@@ -107,23 +107,15 @@
 # PROP Default_Filter "h;hpp;hxx;hm;inl"

 # Begin Source File

 

-SOURCE=..\src\pjsip_ua.h

+SOURCE="..\include\pjsip-ua\sip_dialog.h"

 # End Source File

 # Begin Source File

 

-SOURCE=..\src\pjsip_mod_ua\sip_dialog.h

+SOURCE="..\include\pjsip-ua\sip_regc.h"

 # End Source File

 # Begin Source File

 

-SOURCE=..\src\pjsip_mod_ua\sip_reg.h

-# End Source File

-# Begin Source File

-

-SOURCE=..\src\pjsip_mod_ua\sip_ua.h

-# End Source File

-# Begin Source File

-

-SOURCE=..\src\pjsip_mod_ua\sip_ua_private.h

+SOURCE="..\include\pjsip-ua\sip_ua.h"

 # End Source File

 # End Group

 # End Target

diff --git a/pjsip/include/pjsip-ua/sip_ua.h b/pjsip/include/pjsip-ua/sip_ua.h
index 92bf0db..a1d7c42 100644
--- a/pjsip/include/pjsip-ua/sip_ua.h
+++ b/pjsip/include/pjsip-ua/sip_ua.h
@@ -20,11 +20,10 @@
 #define __PJSIP_SIP_UA_H__
 
 /**
- * @file ua.h
- * @brief SIP User Agent Library
+ * @file sip_ua.h
+ * @brief SIP User Agent Module
  */
 
-#include <pjsip_mod_ua/sip_dialog.h>
 
 PJ_BEGIN_DECL
 
@@ -40,52 +39,34 @@
  *   User Agent manages the interactions between application and SIP dialogs.
  */
 
-typedef struct pjsip_dlg_callback pjsip_dlg_callback;
-
-/**
- * \brief This structure describes a User Agent instance.
- */
-struct pjsip_user_agent
-{
-    pjsip_endpoint     *endpt;
-    pj_pool_t	       *pool;
-    pj_mutex_t	       *mutex;
-    pj_uint32_t		mod_id;
-    pj_hash_table_t    *dlg_table;
-    pjsip_dlg_callback *dlg_cb;
-    pj_list		dlg_list;
-};
-
-/**
- * Create a new dialog.
- */
-PJ_DECL(pjsip_dlg*) pjsip_ua_create_dialog( pjsip_user_agent *ua,
-					       pjsip_role_e role );
+/** User agent type. */
+typedef pjsip_module pjsip_user_agent;
 
 
 /**
- * Destroy dialog.
+ * Initialize user agent layer and register it to the specified endpoint.
+ *
+ * @param endpt		The endpoint where the user agent will be
+ *			registered.
+ *
+ * @return		PJ_SUCCESS on success.
  */
-PJ_DECL(void) pjsip_ua_destroy_dialog( pjsip_dlg *dlg );
-
-
-/** 
- * Register callback to receive dialog notifications.
- */
-PJ_DECL(void) pjsip_ua_set_dialog_callback( pjsip_user_agent *ua, 
-					    pjsip_dlg_callback *cb );
-
+PJ_DECL(pj_status_t) pjsip_ua_init(pjsip_endpoint *endpt);
 
 /**
- * Get the module interface for the UA module.
+ * Get the instance of the user agent.
+ *
+ * @return		The user agent module instance.
  */
-PJ_DECL(pjsip_module*) pjsip_ua_get_module(void);
-
+PJ_DECL(pjsip_user_agent*) pjsip_ua_instance(void);
 
 /**
- * Dump user agent state to log file.
+ * Destroy the user agent layer.
+ *
+ * @return		PJ_SUCCESS on success.
  */
-PJ_DECL(void) pjsip_ua_dump( pjsip_user_agent *ua );
+PJ_DECL(pj_status_t) pjsip_ua_destroy(void);
+
 
 /**
  * @}
@@ -93,5 +74,6 @@
 
 PJ_END_DECL
 
+
 #endif	/* __PJSIP_UA_H__ */
 
diff --git a/pjsip/include/pjsip/sip_auth.h b/pjsip/include/pjsip/sip_auth.h
index 7d8b9c6..e5f8065 100644
--- a/pjsip/include/pjsip/sip_auth.h
+++ b/pjsip/include/pjsip/sip_auth.h
@@ -29,13 +29,18 @@
 
 PJ_BEGIN_DECL
 
+
 /**
  * @defgroup PJSIP_AUTH_API Authorization API's
  * @ingroup PJSIP_AUTH
  * @{
  */
 
- /** Type of data in the credential information. */
+/* Length of digest string. */
+#define PJSIP_MD5STRLEN 32
+
+
+/** Type of data in the credential information. */
 typedef enum pjsip_cred_data_type
 {
     PJSIP_CRED_DATA_PLAIN_PASSWD,   /**< Plain text password.	*/
@@ -77,8 +82,8 @@
 {
     PJ_DECL_LIST_MEMBER(struct pjsip_cached_auth_hdr);
 
-    pjsip_method	     method;
-    pjsip_authorization_hdr *hdr;
+    pjsip_method	     method;	/**< To quickly see the method. */
+    pjsip_authorization_hdr *hdr;	/**< The cached header.		*/
 
 } pjsip_cached_auth_hdr;
 
@@ -93,89 +98,109 @@
  * Other than that, this structure also keeps the last authorization headers
  * that have been sent in the cache list.
  */
-typedef struct pjsip_auth_session
+typedef struct pjsip_cached_auth
 {
-    PJ_DECL_LIST_MEMBER(struct pjsip_auth_session);
+    PJ_DECL_LIST_MEMBER(struct pjsip_cached_auth);
 
-    pj_str_t			 realm;
-    pj_bool_t			 is_proxy;
-    pjsip_auth_qop_type		 qop_value;
+    pj_str_t			 realm;	    /**< Realm.			    */
+    pj_bool_t			 is_proxy;  /**< Server type (401/407)	    */
+    pjsip_auth_qop_type		 qop_value; /**< qop required by server.    */
 #if PJSIP_AUTH_QOP_SUPPORT
-    pj_uint32_t			 nc;
-    pj_str_t			 cnonce;
+    pj_uint32_t			 nc;	    /**< Nonce count.		    */
+    pj_str_t			 cnonce;    /**< Cnonce value.		    */
 #endif
 #if PJSIP_AUTH_AUTO_SEND_NEXT
-    pjsip_www_authenticate_hdr	*last_chal;
+    pjsip_www_authenticate_hdr	*last_chal; /**< Last challenge seen.	    */
 #endif
 #if PJSIP_AUTH_HEADER_CACHING
-    pjsip_cached_auth_hdr	 cached_hdr;
+    pjsip_cached_auth_hdr	 cached_hdr;/**< List of cached header for
+						 each method.		    */
 #endif
 
-} pjsip_auth_session;
+} pjsip_cached_auth;
 
 
 /**
- * Create authorization header for the specified credential.
- * Application calls this function to create Authorization or Proxy-Authorization
- * header after receiving WWW-Authenticate or Proxy-Authenticate challenge
- * (normally in 401/407 response).
- * If authorization session argument is specified, this function will update
- * the session with the updated information if required (e.g. to update
- * nonce-count when qop is "auth" or "auth-int"). This function will also
- * save the authorization header in the session's cached header list.
- *
- * @param req_pool	Pool to allocate new header for the request.
- * @param hdr		The WWW-Authenticate or Proxy-Authenticate found in 
- *			the response.
- * @param uri		The URI for which authorization is targeted to.
- * @param cred_info	The credential to be used for authentication.
- * @param method	The method.
- * @param sess_pool	Session pool to update session or to allocate message
- *			in the cache. May be NULL if auth_sess is NULL.
- * @param auth_sess	If not NULL, this specifies the specific authentication
- *			session to be used or updated.
- *
- * @return		The Authorization header, which can be typecasted to 
- *			Proxy-Authorization.
+ * This structure describes client authentication sessions. It keeps
+ * all the information needed to authorize the client against all downstream 
+ * servers.
  */
-PJ_DECL(pjsip_authorization_hdr*) pjsip_auth_respond( 
-					 pj_pool_t *req_pool,
-					 const pjsip_www_authenticate_hdr *hdr,
-					 const pjsip_uri *uri,
-					 const pjsip_cred_info *cred_info,
-					 const pjsip_method *method,
-					 pj_pool_t *sess_pool,
-					 pjsip_auth_session *auth_sess);
+typedef struct pjsip_auth_clt_sess
+{
+    pj_pool_t		*pool;		/**< Pool to use.		    */
+    pjsip_endpoint	*endpt;		/**< Endpoint where this belongs.   */
+    unsigned		 cred_cnt;	/**< Number of credentials.	    */
+    pjsip_cred_info	*cred_info;	/**< Array of credential information*/
+    pjsip_cached_auth	 cached_auth;	/**< Cached authorization info.	    */
 
-/**
- * Verify digest in the authorization request.
- *
- * @param hdr		The incoming Authorization/Proxy-Authorization header.
- * @param method	The method.
- * @param password	The plaintext password to verify.
- *
- * @return		Non-zero if authorization succeed.
- */
-PJ_DECL(pj_bool_t) pjsip_auth_verify(	const pjsip_authorization_hdr *hdr,
-					const pj_str_t *method,
-					const pjsip_cred_info *cred_info );
+} pjsip_auth_clt_sess;
 
 
 /**
- * This function can be used to find credential information which matches
- * the specified realm.
+ * Type of function to lookup credential for the specified name.
  *
- * @param count		Number of credentials in the parameter.
- * @param cred		The array of credentials.
- * @param realm		Realm to search.
- * @param scheme	Authentication scheme.
+ * @param pool		Pool to initialize the credential info.
+ * @param realm		Realm to find the account.
+ * @param acc_name	Account name to look for.
+ * @param cred_info	The structure to put the credential when it's found.
  *
- * @return		The credential which matches the specified realm.
+ * @return		The function MUST return PJ_SUCCESS when it found
+ *			a correct credential for the specified account and
+ *			realm. Otherwise it may return PJSIP_EAUTHACCNOTFOUND
+ *			or PJSIP_EAUTHACCDISABLED.
  */
-PJ_DECL(const pjsip_cred_info*) pjsip_auth_find_cred( unsigned count,
-						      const pjsip_cred_info cred[],
-						      const pj_str_t *realm,
-						      const pj_str_t *scheme );
+typedef pj_status_t pjsip_auth_lookup_cred( pj_pool_t *pool,
+					    const pj_str_t *realm,
+					    const pj_str_t *acc_name,
+					    pjsip_cred_info *cred_info );
+
+/** Flag to specify that server is a proxy. */
+#define PJSIP_AUTH_SRV_IS_PROXY	    1
+
+/**
+ * This structure describes server authentication information.
+ */
+typedef struct pjsip_auth_srv
+{
+    pj_str_t		     realm;	/**< Realm to serve.		    */
+    pj_bool_t		     is_proxy;	/**< Will issue 407 instead of 401  */
+    pjsip_auth_lookup_cred  *lookup;	/**< Lookup function.		    */
+
+} pjsip_auth_srv;
+
+
+/**
+ * Initialize client authentication session data structure, and set the 
+ * session to use pool for its subsequent memory allocation. The argument 
+ * options should be set to zero for this PJSIP version.
+ *
+ * @param sess		The client authentication session.
+ * @param endpt		Endpoint where this session belongs.
+ * @param pool		Pool to use.
+ * @param options	Must be zero.
+ *
+ * @return		PJ_SUCCESS on success.
+ */
+PJ_DECL(pj_status_t) pjsip_auth_clt_init( pjsip_auth_clt_sess *sess,
+					  pjsip_endpoint *endpt,
+					  pj_pool_t *pool, 
+					  unsigned options);
+
+
+
+/**
+ * Set the credentials to be used during the session. This will duplicate 
+ * the specified credentials using client authentication's pool.
+ *
+ * @param sess		The client authentication session.
+ * @param cred_cnt	Number of credentials.
+ * @param c		Array of credentials.
+ *
+ * @return		PJ_SUCCESS on success.
+ */
+PJ_DECL(pj_status_t) pjsip_auth_clt_set_credentials( pjsip_auth_clt_sess *sess,
+						     int cred_cnt,
+						     const pjsip_cred_info *c);
 
 
 /**
@@ -194,20 +219,14 @@
  * are not set, this function will do nothing. The stack then will only send
  * Authorization/Proxy-Authorization to respond 401/407 response.
  *
- * @param sess_pool	Session level pool, where memory will be allocated from
- *			for data that persists across requests (e.g. caching).
+ * @param sess		The client authentication session.
  * @param tdata		The request message to be initialized.
- * @param sess_list	List of authorization sessions that have been recorded.
- * @param cred_count	Number of credentials.
- * @param cred_info	Array of credentials.
  *
- * @return		Zero if successfull.
+ * @return		PJ_SUCCESS if successfull.
  */
-PJ_DECL(pj_status_t) pjsip_auth_init_req( pj_pool_t *sess_pool,
-					  pjsip_tx_data *tdata,
-					  pjsip_auth_session *sess_list,
-					  int cred_count, 
-					  const pjsip_cred_info cred_info[]);
+PJ_DECL(pj_status_t) pjsip_auth_clt_init_req( pjsip_auth_clt_sess *sess,
+					      pjsip_tx_data *tdata );
+
 
 /**
  * Call this function when a transaction failed with 401 or 407 response.
@@ -215,33 +234,104 @@
  * authentication challenge found in the response message, and add the
  * new authorization header in the authorization cache.
  *
- * Note that upon return the reference counter of the transmit data
- * will be incremented.
+ * Note that upon return the reference counter of the new transmit data
+ * will be set to 1.
  *
- * @param endpt		Endpoint.
- * @param pool		The pool to allocate memory for new cred_info.
- * @param cached_list	Cached authorization headers.
- * @param cred_count	Number of credentials.
- * @param cred_info	Array of credentials to use.
- * @param tdata		The original request message, which normally can be
- *			retrieved from tsx->last_tx.
+ * @param sess		The client authentication session.
  * @param rdata		The response message containing 401/407 status.
+ * @param old_request	The original request message, which will be re-
+ *			created with authorization info.
+ * @param new_request	Pointer to receive new request message which
+ *			will contain all required authorization headers.
  *
- * @return		New transmit data buffer, or NULL if the dialog
- *			can not respond to the authorization challenge.
+ * @return		PJ_SUCCESS if new request can be successfully
+ *			created to respond all the authentication
+ *			challenges.
  */
-PJ_DECL(pjsip_tx_data*) 
-pjsip_auth_reinit_req( pjsip_endpoint *endpt,
-		       pj_pool_t *ses_pool,
-		       pjsip_auth_session *sess_list,
-		       int cred_count, const pjsip_cred_info cred_info[],
-		       pjsip_tx_data *tdata, const pjsip_rx_data *rdata);
+PJ_DECL(pj_status_t) pjsip_auth_clt_reinit_req(	pjsip_auth_clt_sess *sess,
+						const pjsip_rx_data *rdata,
+						pjsip_tx_data *old_request,
+						pjsip_tx_data **new_request );
+
+/**
+ * Initialize server authorization session data structure to serve the 
+ * specified realm and to use lookup_func function to look for the credential 
+ * info. 
+ *
+ * @param pool		Pool used to initialize the authentication server.
+ * @param auth_srv	The authentication server structure.
+ * @param realm		Realm to be served by the server.
+ * @param lookup	Account lookup function.
+ * @param options	Options, bitmask of:
+ *			- PJSIP_AUTH_SRV_IS_PROXY: to specify that the server
+ *			  will authorize clients as a proxy server (instead of
+ *			  as UAS), which means that Proxy-Authenticate will 
+ *			  be used instead of WWW-Authenticate.
+ *
+ * @return		PJ_SUCCESS on success.
+ */
+PJ_DECL(pj_status_t) pjsip_auth_srv_init( pj_pool_t *pool,
+					  pjsip_auth_srv *auth_srv,
+					  const pj_str_t *realm,
+					  pjsip_auth_lookup_cred *lookup,
+					  unsigned options );
+
+
+/**
+ * Request the authorization server framework to verify the authorization 
+ * information in the specified request in rdata.
+ *
+ * @param auth_srv	The server authentication structure.
+ * @param rdata		Incoming request to be authenticated.
+ * @param status_code	When not null, it will be filled with suitable 
+ *			status code to be sent to the client.
+ *
+ * @return		PJ_SUCCESS if request is successfully authenticated.
+ *			Otherwise the function may return one of the
+ *			following error codes:
+ *			- PJSIP_EAUTHNOAUTH
+ *			- PJSIP_EINVALIDAUTHSCHEME
+ *			- PJSIP_EAUTHACCNOTFOUND
+ *			- PJSIP_EAUTHACCDISABLED
+ *			- PJSIP_EAUTHINVALIDREALM
+ *			- PJSIP_EAUTHINVALIDDIGEST
+ */
+PJ_DECL(pj_status_t) pjsip_auth_srv_verify( pjsip_auth_srv *auth_srv,
+					    pjsip_rx_data *rdata,
+					    int *status_code );
+
+
+/**
+ * Add authentication challenge headers to the outgoing response in tdata. 
+ * Application may specify its customized nonce and opaque for the challenge, 
+ * or can leave the value to NULL to make the function fills them in with 
+ * random characters.
+ *
+ * @param auth_srv	The server authentication structure.
+ * @param qop		Optional qop value.
+ * @param nonce		Optional nonce value.
+ * @param opaque	Optional opaque value.
+ * @param stale		Stale indication.
+ * @param tdata		The outgoing response message. The response must have
+ *			401 or 407 response code.
+ *
+ * @return		PJ_SUCCESS on success.
+ */
+PJ_DECL(pj_status_t) pjsip_auth_srv_challenge( pjsip_auth_srv *auth_srv,
+					       const pj_str_t *qop,
+					       const pj_str_t *nonce,
+					       const pj_str_t *opaque,
+					       pj_bool_t stale,
+					       pjsip_tx_data *tdata);
+
 
 /**
  * @}
  */
 
+
 PJ_END_DECL
 
+
 #endif	/* __PJSIP_AUTH_SIP_AUTH_H__ */
 
diff --git a/pjsip/include/pjsip/sip_errno.h b/pjsip/include/pjsip/sip_errno.h
index 7c3dbbd..9c04c75 100644
--- a/pjsip/include/pjsip/sip_errno.h
+++ b/pjsip/include/pjsip/sip_errno.h
@@ -79,7 +79,7 @@
 #define PJSIP_ERRNO_START_PJSIP (PJSIP_ERRNO_START + 1000)
 
 /************************************************************
- * GENERIC SIP ERRORS
+ * GENERIC/GENERAL SIP ERRORS
  ***********************************************************/
 /**
  * @hideinitializer
@@ -96,6 +96,11 @@
  * SIP stack is shutting down.
  */
 #define PJSIP_ESHUTDOWN		(PJSIP_ERRNO_START_PJSIP + 3)	/* 171003 */
+/**
+ * @hideinitializer
+ * SIP object is not initialized.
+ */
+#define PJSIP_ENOTINITIALIZED	(PJSIP_ERRNO_START_PJSIP + 4)	/* 171004 */
 
 
 /************************************************************
@@ -108,64 +113,77 @@
 #define PJSIP_EINVALIDMSG       (PJSIP_ERRNO_START_PJSIP + 20)	/* 171020 */
 /**
  * @hideinitializer
- * Unsupported URL scheme.
- */
-#define PJSIP_EINVALIDSCHEME    (PJSIP_ERRNO_START_PJSIP + 21)	/* 171021 */
-/**
- * @hideinitializer
- * Message too long. See also PJSIP_ERXOVERFLOW.
- */
-#define PJSIP_EMSGTOOLONG	(PJSIP_ERRNO_START_PJSIP + 22)	/* 171022 */
-/**
- * @hideinitializer
- * Message not completely received.
- */
-#define PJSIP_EPARTIALMSG       (PJSIP_ERRNO_START_PJSIP + 23)	/* 171023 */
-/**
- * @hideinitializer
- * Missing Request-URI.
- */
-#define PJSIP_EMISSINGREQURI    (PJSIP_ERRNO_START_PJSIP + 24)	/* 171024 */
-/**
- * @hideinitializer
- * Missing required header(s).
- */
-#define PJSIP_EMISSINGHDR       (PJSIP_ERRNO_START_PJSIP + 25)	/* 171025 */
-/**
- * @hideinitializer
- * Missing message body.
- */
-#define PJSIP_EMISSINGBODY	(PJSIP_ERRNO_START_PJSIP + 26)	/* 171026 */
-/**
- * @hideinitializer
- * Invalid Via header in response (sent-by, etc).
- */
-#define PJSIP_EINVALIDVIA	(PJSIP_ERRNO_START_PJSIP + 27)	/* 171027 */
-/**
- * @hideinitializer
- * Multiple Via headers in response.
- */
-#define PJSIP_EMULTIPLEVIA	(PJSIP_ERRNO_START_PJSIP + 28)	/* 171028 */
-/**
- * @hideinitializer
- * Invalid request URI.
- */
-#define PJSIP_EINVALIDREQURI	(PJSIP_ERRNO_START_PJSIP + 29)	/* 171029 */
-/**
- * @hideinitializer
  * Expecting request message.
  */
-#define PJSIP_ENOTREQUESTMSG	(PJSIP_ERRNO_START_PJSIP + 30)	/* 171030 */
+#define PJSIP_ENOTREQUESTMSG	(PJSIP_ERRNO_START_PJSIP + 21)	/* 171021 */
 /**
  * @hideinitializer
  * Expecting response message.
  */
-#define PJSIP_ENOTRESPONSEMSG	(PJSIP_ERRNO_START_PJSIP + 31)	/* 171031 */
+#define PJSIP_ENOTRESPONSEMSG	(PJSIP_ERRNO_START_PJSIP + 22)	/* 171022 */
+/**
+ * @hideinitializer
+ * Message too long. See also PJSIP_ERXOVERFLOW.
+ */
+#define PJSIP_EMSGTOOLONG	(PJSIP_ERRNO_START_PJSIP + 23)	/* 171023 */
+/**
+ * @hideinitializer
+ * Message not completely received.
+ */
+#define PJSIP_EPARTIALMSG       (PJSIP_ERRNO_START_PJSIP + 24)	/* 171024 */
+
+/**
+ * @hideinitializer
+ * Status code is invalid.
+ */
+#define PJSIP_EINVALIDSTATUS	(PJSIP_ERRNO_START_PJSIP + 30)	/* 171030 */
+
+/**
+ * @hideinitializer
+ * Unsupported URL scheme.
+ */
+#define PJSIP_EINVALIDSCHEME    (PJSIP_ERRNO_START_PJSIP + 40)	/* 171040 */
+/**
+ * @hideinitializer
+ * Missing Request-URI.
+ */
+#define PJSIP_EMISSINGREQURI    (PJSIP_ERRNO_START_PJSIP + 41)	/* 171041 */
+/**
+ * @hideinitializer
+ * Invalid request URI.
+ */
+#define PJSIP_EINVALIDREQURI	(PJSIP_ERRNO_START_PJSIP + 42)	/* 171042 */
+/**
+ * @hideinitializer
+ * URI is too long.
+ */
+#define PJSIP_EURITOOLONG	(PJSIP_ERRNO_START_PJSIP + 43)	/* 171043 */
+
+/**
+ * @hideinitializer
+ * Missing required header(s).
+ */
+#define PJSIP_EMISSINGHDR       (PJSIP_ERRNO_START_PJSIP + 50)	/* 171050 */
 /**
  * @hideinitializer
  * Invalid header field.
  */
-#define PJSIP_EINVALIDHDR	(PJSIP_ERRNO_START_PJSIP + 32)	/* 171032 */
+#define PJSIP_EINVALIDHDR	(PJSIP_ERRNO_START_PJSIP + 51)	/* 171051 */
+/**
+ * @hideinitializer
+ * Invalid Via header in response (sent-by, etc).
+ */
+#define PJSIP_EINVALIDVIA	(PJSIP_ERRNO_START_PJSIP + 52)	/* 171052 */
+/**
+ * @hideinitializer
+ * Multiple Via headers in response.
+ */
+#define PJSIP_EMULTIPLEVIA	(PJSIP_ERRNO_START_PJSIP + 53)	/* 171053 */
+/**
+ * @hideinitializer
+ * Missing message body.
+ */
+#define PJSIP_EMISSINGBODY	(PJSIP_ERRNO_START_PJSIP + 54)	/* 171054 */
 
 
 /************************************************************
@@ -175,23 +193,23 @@
  * @hideinitializer
  * Unsupported transport type.
  */
-#define PJSIP_EUNSUPTRANSPORT	(PJSIP_ERRNO_START_PJSIP + 40)	/* 171040 */
+#define PJSIP_EUNSUPTRANSPORT	(PJSIP_ERRNO_START_PJSIP + 60)	/* 171060 */
 /**
  * @hideinitializer
  * Buffer is being sent, operation still pending.
  */
-#define PJSIP_EPENDINGTX	(PJSIP_ERRNO_START_PJSIP + 41)	/* 171041 */
+#define PJSIP_EPENDINGTX	(PJSIP_ERRNO_START_PJSIP + 61)	/* 171061 */
 /**
  * @hideinitializer
  * Rx buffer overflow. See also PJSIP_EMSGTOOLONG.
  */
-#define PJSIP_ERXOVERFLOW       (PJSIP_ERRNO_START_PJSIP + 42)	/* 171042 */
+#define PJSIP_ERXOVERFLOW       (PJSIP_ERRNO_START_PJSIP + 62)	/* 171062 */
 /**
  * @hideinitializer
  * This is not really an error, it just informs application that
  * transmit data has been deleted on return of pjsip_tx_data_dec_ref().
  */
-#define PJSIP_EBUFDESTROYED     (PJSIP_ERRNO_START_PJSIP + 43)	/* 171043 */
+#define PJSIP_EBUFDESTROYED     (PJSIP_ERRNO_START_PJSIP + 63)	/* 171063 */
 
 
 /************************************************************
@@ -201,7 +219,7 @@
  * @hideinitializer
  * Transaction has just been destroyed.
  */
-#define PJSIP_ETSXDESTROYED     (PJSIP_ERRNO_START_PJSIP + 60)	/* 171060 */
+#define PJSIP_ETSXDESTROYED     (PJSIP_ERRNO_START_PJSIP + 70)	/* 171070 */
 
 
 /************************************************************
@@ -269,6 +287,65 @@
 #define PJSIP_ECMPHEADERPARAM	(PJSIP_ERRNO_START_PJSIP + 91)	/* 171091 */
 
 
+/************************************************************
+ * AUTHENTICATION FRAMEWORK
+ ***********************************************************/
+/**
+ * @hideinitializer
+ * Credential failed to authenticate.
+ */
+#define PJSIP_EFAILEDCREDENTIAL	(PJSIP_ERRNO_START_PJSIP + 100)	/* 171100 */
+/**
+ * @hideinitializer
+ * No suitable credential.
+ */
+#define PJSIP_ENOCREDENTIAL	(PJSIP_ERRNO_START_PJSIP + 101)	/* 171101 */
+/**
+ * @hideinitializer
+ * Invalid/unsupported algorithm.
+ */
+#define PJSIP_EINVALIDALGORITHM	(PJSIP_ERRNO_START_PJSIP + 102)	/* 171102 */
+/**
+ * @hideinitializer
+ * Invalid/unsupported qop.
+ */
+#define PJSIP_EINVALIDQOP	(PJSIP_ERRNO_START_PJSIP + 103)	/* 171103 */
+/**
+ * @hideinitializer
+ * Invalid/unsupported authentication scheme.
+ */
+#define PJSIP_EINVALIDAUTHSCHEME (PJSIP_ERRNO_START_PJSIP + 104)/* 171104 */
+/**
+ * @hideinitializer
+ * No previous challenge.
+ */
+#define PJSIP_EAUTHNOPREVCHAL	(PJSIP_ERRNO_START_PJSIP + 105)	/* 171105 */
+/**
+ * @hideinitializer
+ * No authorization is found.
+ */
+#define PJSIP_EAUTHNOAUTH	(PJSIP_ERRNO_START_PJSIP + 106)	/* 171106 */
+/**
+ * @hideinitializer
+ * Account not found.
+ */
+#define PJSIP_EAUTHACCNOTFOUND	(PJSIP_ERRNO_START_PJSIP + 107)	/* 171107 */
+/**
+ * @hideinitializer
+ * Account is disabled.
+ */
+#define PJSIP_EAUTHACCDISABLED	(PJSIP_ERRNO_START_PJSIP + 108)	/* 171108 */
+/**
+ * @hideinitializer
+ * Invalid realm.
+ */
+#define PJSIP_EAUTHINVALIDREALM	(PJSIP_ERRNO_START_PJSIP + 109)	/* 171109 */
+/**
+ * @hideinitializer
+ * Invalid digest.
+ */
+#define PJSIP_EAUTHINVALIDDIGEST (PJSIP_ERRNO_START_PJSIP+110)	/* 171110 */
+
 
 PJ_END_DECL
 
diff --git a/pjsip/include/pjsip/sip_transport.h b/pjsip/include/pjsip/sip_transport.h
index f2cce0f..f8968f9 100644
--- a/pjsip/include/pjsip/sip_transport.h
+++ b/pjsip/include/pjsip/sip_transport.h
@@ -218,7 +218,7 @@
 	char			*info;
 
 	/** The Call-ID header as found in the message. */
-	pj_str_t		 call_id;
+	pjsip_cid_hdr		*cid;
 
 	/** The From header as found in the message. */
 	pjsip_from_hdr		*from;
diff --git a/pjsip/src/pjsip-ua/sip_ua.c b/pjsip/src/pjsip-ua/sip_ua.c
index 094be07..ac0980f 100644
--- a/pjsip/src/pjsip-ua/sip_ua.c
+++ b/pjsip/src/pjsip-ua/sip_ua.c
@@ -41,10 +41,8 @@
 /*
  * Static prototypes.
  */
-static pj_status_t ua_init( pjsip_endpoint *endpt,
-			    struct pjsip_module *mod, pj_uint32_t id );
-static pj_status_t ua_start( struct pjsip_module *mod );
-static pj_status_t ua_deinit( struct pjsip_module *mod );
+static pj_status_t ua_load(pjsip_endpoint *endpt);
+static pj_status_t ua_unload(void);
 static void ua_tsx_handler( struct pjsip_module *mod, pjsip_event *evt );
 static pjsip_dlg *find_dialog( pjsip_user_agent *ua,
 			       pjsip_rx_data *rdata );
@@ -53,65 +51,63 @@
 PJ_DECL(void) pjsip_on_dialog_destroyed( pjsip_dlg *dlg );
 
 /*
- * Default UA instance.
- */
-static pjsip_user_agent ua_instance;
-
-/*
  * Module interface.
  */
-static struct pjsip_module mod_ua = 
+static struct user_agent
 {
-    { "User-Agent", 10 },   /* Name.		*/
-    0,			    /* Flag		*/
-    128,		    /* Priority		*/
-    NULL,		    /* User agent instance, initialized by APP.	*/
-    0,			    /* Number of methods supported (will be initialized later). */
-    { 0 },		    /* Array of methods (will be initialized later) */
-    &ua_init,		    /* init_module()	*/
-    &ua_start,		    /* start_module()	*/
-    &ua_deinit,		    /* deinit_module()	*/
-    &ua_tsx_handler,	    /* tsx_handler()	*/
+    pjsip_module	 mod;
+    pj_pool_t		*pool;
+    pjsip_endpoint	*endpt;
+    pj_mutex_t		*mutex;
+    pj_hash_table_t	*dlg_table;
+    pjsip_dialog	 dlg_list;
+
+} mod_ua = 
+{
+  {
+    NULL, NULL,		    /* prev, next.			*/
+    { "mod-ua", 6 },	    /* Name.				*/
+    -1,			    /* Id				*/
+    PJSIP_MOD_PRIORITY_UA_PROXY_LAYER,	/* Priority		*/
+    NULL,		    /* User data.			*/
+    0,			    /* Number of methods supported.	*/
+    { 0 },		    /* Array of methods			*/
+    &ua_load,		    /* load()				*/
+    NULL,		    /* start()				*/
+    NULL,		    /* stop()				*/
+    &ua_unload,		    /* unload()				*/
+    NULL,		    /* on_rx_request()			*/
+    NULL,		    /* on_rx_response()			*/
+    NULL,		    /* on_tx_request.			*/
+    NULL,		    /* on_tx_response()			*/
+    NULL,		    /* on_tsx_state()			*/
+  }
 };
 
 /*
  * Initialize user agent instance.
  */
-static pj_status_t ua_init( pjsip_endpoint *endpt,
-			    struct pjsip_module *mod, pj_uint32_t id )
+static pj_status_t ua_load( pjsip_endpoint *endpt )
 {
-    static pjsip_method m_invite, m_ack, m_cancel, m_bye;
-    pjsip_user_agent *ua = mod->mod_data;
     extern int pjsip_dlg_lock_tls_id;	/* defined in sip_dialog.c */
-
-    pjsip_method_set( &m_invite, PJSIP_INVITE_METHOD );
-    pjsip_method_set( &m_ack, PJSIP_ACK_METHOD );
-    pjsip_method_set( &m_cancel, PJSIP_CANCEL_METHOD );
-    pjsip_method_set( &m_bye, PJSIP_BYE_METHOD );
-
-    mod->method_cnt = 4;
-    mod->methods[0] = &m_invite;
-    mod->methods[1] = &m_ack;
-    mod->methods[2] = &m_cancel;
-    mod->methods[3] = &m_bye;
+    pj_status_t status;
 
     /* Initialize the user agent. */
-    ua->endpt = endpt;
-    ua->pool = pjsip_endpt_create_pool(endpt, "pua%p", PJSIP_POOL_LEN_UA, 
-				       PJSIP_POOL_INC_UA);
-    if (!ua->pool) {
-	return -1;
-    }
-    ua->mod_id = id;
-    ua->mutex = pj_mutex_create(ua->pool, " ua%p", 0);
-    if (!ua->mutex) {
-	return -1;
-    }
-    ua->dlg_table = pj_hash_create(ua->pool, PJSIP_MAX_DIALOG_COUNT);
-    if (ua->dlg_table == NULL) {
-	return -1;
-    }
-    pj_list_init(&ua->dlg_list);
+    mod_ua.endpt = endpt;
+    status = pjsip_endpt_create_pool( endpt, "pua%p", PJSIP_POOL_LEN_UA, 
+				      PJSIP_POOL_INC_UA, &mod_ua.pool);
+    if (status != PJ_SUCCESS)
+	return status;
+
+    status = pj_mutex_create_recursive(mod_ua.pool, " ua%p", &mod_ua.mutex);
+    if (status != PJ_SUCCESS)
+	return status;
+
+    mod_ua.dlg_table = pj_hash_create(mod_ua.pool, PJSIP_MAX_DIALOG_COUNT);
+    if (ua->dlg_table == NULL)
+	return PJ_ENOMEM;
+
+    pj_list_init(&mod_ua.dlg_list);
 
     /* Initialize dialog lock. */
     pjsip_dlg_lock_tls_id = pj_thread_local_alloc();
@@ -120,50 +116,21 @@
     }
     pj_thread_local_set(pjsip_dlg_lock_tls_id, NULL);
 
-    return 0;
-}
-
-/*
- * Start user agent instance.
- */
-static pj_status_t ua_start( struct pjsip_module *mod )
-{
-    PJ_UNUSED_ARG(mod)
-    return 0;
+    return PJ_SUCCESS;
 }
 
 /*
  * Destroy user agent.
  */
-static pj_status_t ua_deinit( struct pjsip_module *mod )
+static pj_status_t ua_unload()
 {
-    pjsip_user_agent *ua = mod->mod_data;
-
-    pj_mutex_unlock(ua->mutex);
+    pj_mutex_unlock(mod_ua.mutex);
 
     /* Release pool */
-    if (ua->pool) {
-	pjsip_endpt_destroy_pool( ua->endpt, ua->pool );
+    if (mod_ua.pool) {
+	pjsip_endpt_destroy_pool( mod_ua.endpt, mod_ua.pool );
     }
-    return 0;
-}
-
-/*
- * Get the module interface for the UA module.
- */
-PJ_DEF(pjsip_module*) pjsip_ua_get_module(void)
-{
-    mod_ua.mod_data = &ua_instance;
-    return &mod_ua;
-}
-
-/*
- * Register callback to receive dialog notifications.
- */
-PJ_DEF(void) pjsip_ua_set_dialog_callback( pjsip_user_agent *ua, 
-					   pjsip_dlg_callback *cb )
-{
-    ua->dlg_cb = cb;
+    return PJ_SUCCESS;
 }
 
 /* 
diff --git a/pjsip/src/pjsip/sip_auth.c b/pjsip/src/pjsip/sip_auth.c
deleted file mode 100644
index 9b7c52b..0000000
--- a/pjsip/src/pjsip/sip_auth.c
+++ /dev/null
@@ -1,804 +0,0 @@
-/* $Id$ */
-/* 
- * Copyright (C) 2003-2006 Benny Prijono <benny@prijono.org>
- *
- * 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 
- */
-#include <pjsip/sip_auth.h>
-#include <pjsip/sip_auth_parser.h>	/* just to get pjsip_DIGEST_STR */
-#include <pjsip/sip_transport.h>
-#include <pjsip/sip_endpoint.h>
-#include <pjlib-util/md5.h>
-#include <pj/log.h>
-#include <pj/string.h>
-#include <pj/pool.h>
-#include <pj/guid.h>
-#include <pj/assert.h>
-#include <pj/ctype.h>
-
-/* Length of digest string. */
-#define MD5STRLEN 32
-
-/* Maximum stack size we use for storing username+realm+password etc. */
-#define MAX_TEMP  128
-
-/* A macro just to get rid of type mismatch between char and unsigned char */
-#define MD5_APPEND(pms,buf,len)	pj_md5_update(pms, (const pj_uint8_t*)buf, len)
-
-/* Logging. */
-#define THIS_FILE   "sip_auth.c"
-#if 0
-#  define AUTH_TRACE_(expr)  PJ_LOG(3, expr)
-#else
-#  define AUTH_TRACE_(expr)
-#endif
-
-static const char hex[] = "0123456789abcdef";
-
-/* Transform digest to string.
- * output must be at least MD5STRLEN+1 bytes.
- *
- * NOTE: THE OUTPUT STRING IS NOT NULL TERMINATED!
- */
-static void digest2str(const unsigned char digest[], char *output)
-{
-    char *p = output;
-    int i;
-
-    for (i = 0; i<16; ++i) {
-	int val = digest[i];
-	*p++ = hex[val >> 4];
-	*p++ = hex[val & 0x0F];
-    }
-}
-
-/*
- * Create response digest based on the parameters and store the
- * digest ASCII in 'result'. 
- */
-static void create_digest( pj_str_t *result,
-			   const pj_str_t *nonce,
-			   const pj_str_t *nc,
-			   const pj_str_t *cnonce,
-			   const pj_str_t *qop,
-			   const pj_str_t *uri,
-			   const pjsip_cred_info *cred_info,
-			   const pj_str_t *method)
-{
-    char ha1[MD5STRLEN];
-    char ha2[MD5STRLEN];
-    unsigned char digest[16];
-    pj_md5_context pms;
-
-    pj_assert(result->slen >= MD5STRLEN);
-
-    AUTH_TRACE_((THIS_FILE, "Begin creating digest"));
-
-    if (cred_info->data_type == PJSIP_CRED_DATA_PLAIN_PASSWD) {
-	/*** 
-	 *** ha1 = MD5(username ":" realm ":" password) 
-	 ***/
-	pj_md5_init(&pms);
-	MD5_APPEND( &pms, cred_info->username.ptr, cred_info->username.slen);
-	MD5_APPEND( &pms, ":", 1);
-	MD5_APPEND( &pms, cred_info->realm.ptr, cred_info->realm.slen);
-	MD5_APPEND( &pms, ":", 1);
-	MD5_APPEND( &pms, cred_info->data.ptr, cred_info->data.slen);
-	pj_md5_final(&pms, digest);
-
-	digest2str(digest, ha1);
-
-    } else if (cred_info->data_type == PJSIP_CRED_DATA_DIGEST) {
-	pj_assert(cred_info->data.slen == 32);
-	pj_memcpy( ha1, cred_info->data.ptr, cred_info->data.slen );
-    }
-
-    AUTH_TRACE_((THIS_FILE, "  ha1=%.32s", ha1));
-
-    /***
-     *** ha2 = MD5(method ":" req_uri) 
-     ***/
-    pj_md5_init(&pms);
-    MD5_APPEND( &pms, method->ptr, method->slen);
-    MD5_APPEND( &pms, ":", 1);
-    MD5_APPEND( &pms, uri->ptr, uri->slen);
-    pj_md5_final(&pms, digest);
-    digest2str(digest, ha2);
-
-    AUTH_TRACE_((THIS_FILE, "  ha2=%.32s", ha2));
-
-    /***
-     *** When qop is not used:
-     ***    response = MD5(ha1 ":" nonce ":" ha2) 
-     ***
-     *** When qop=auth is used:
-     ***    response = MD5(ha1 ":" nonce ":" nc ":" cnonce ":" qop ":" ha2)
-     ***/
-    pj_md5_init(&pms);
-    MD5_APPEND( &pms, ha1, MD5STRLEN);
-    MD5_APPEND( &pms, ":", 1);
-    MD5_APPEND( &pms, nonce->ptr, nonce->slen);
-    if (qop && qop->slen != 0) {
-	MD5_APPEND( &pms, ":", 1);
-	MD5_APPEND( &pms, nc->ptr, nc->slen);
-	MD5_APPEND( &pms, ":", 1);
-	MD5_APPEND( &pms, cnonce->ptr, cnonce->slen);
-	MD5_APPEND( &pms, ":", 1);
-	MD5_APPEND( &pms, qop->ptr, qop->slen);
-    }
-    MD5_APPEND( &pms, ":", 1);
-    MD5_APPEND( &pms, ha2, MD5STRLEN);
-
-    /* This is the final response digest. */
-    pj_md5_final(&pms, digest);
-    
-    /* Convert digest to string and store in chal->response. */
-    result->slen = MD5STRLEN;
-    digest2str(digest, result->ptr);
-
-    AUTH_TRACE_((THIS_FILE, "  digest=%.32s", result->ptr));
-    AUTH_TRACE_((THIS_FILE, "Digest created"));
-}
-
-/*
- * Finds out if qop offer contains "auth" token.
- */
-static pj_bool_t has_auth_qop( pj_pool_t *pool, const pj_str_t *qop_offer)
-{
-    pj_str_t qop;
-    char *p;
-
-    pj_strdup_with_null( pool, &qop, qop_offer);
-    p = qop.ptr;
-    while (*p) {
-	*p = (char)pj_tolower(*p);
-	++p;
-    }
-
-    p = qop.ptr;
-    while (*p) {
-	if (*p=='a' && *(p+1)=='u' && *(p+2)=='t' && *(p+3)=='h') {
-	    int e = *(p+4);
-	    if (e=='"' || e==',' || e==0)
-		return PJ_TRUE;
-	    else
-		p += 4;
-	} else {
-	    ++p;
-	}
-    }
-
-    return PJ_FALSE;
-}
-
-/*
- * Generate response digest. 
- * Most of the parameters to generate the digest (i.e. username, realm, uri,
- * and nonce) are expected to be in the credential. Additional parameters (i.e.
- * password and method param) should be supplied in the argument.
- *
- * The resulting digest will be stored in cred->response.
- * The pool is used to allocate 32 bytes to store the digest in cred->response.
- */
-static pj_status_t respond_digest( pj_pool_t *pool,
-				   pjsip_digest_credential *cred,
-				   const pjsip_digest_challenge *chal,
-				   const pj_str_t *uri,
-				   const pjsip_cred_info *cred_info,
-				   const pj_str_t *cnonce,
-				   pj_uint32_t nc,
-				   const pj_str_t *method)
-{
-    /* Check algorithm is supported. We only support MD5. */
-    if (chal->algorithm.slen && pj_stricmp(&chal->algorithm, &pjsip_MD5_STR))
-    {
-	PJ_LOG(4,(THIS_FILE, "Unsupported digest algorithm \"%.*s\"",
-		  chal->algorithm.slen, chal->algorithm.ptr));
-	return -1;
-    }
-
-    /* Build digest credential from arguments. */
-    pj_strdup(pool, &cred->username, &cred_info->username);
-    pj_strdup(pool, &cred->realm, &chal->realm);
-    pj_strdup(pool, &cred->nonce, &chal->nonce);
-    pj_strdup(pool, &cred->uri, uri);
-    cred->algorithm = pjsip_MD5_STR;
-    pj_strdup(pool, &cred->opaque, &chal->opaque);
-    
-    /* Allocate memory. */
-    cred->response.ptr = pj_pool_alloc(pool, MD5STRLEN);
-    cred->response.slen = MD5STRLEN;
-
-    if (chal->qop.slen == 0) {
-	/* Server doesn't require quality of protection. */
-
-	/* Convert digest to string and store in chal->response. */
-	create_digest( &cred->response, &cred->nonce, NULL, NULL, NULL,
-		       uri, cred_info, method);
-
-    } else if (has_auth_qop(pool, &chal->qop)) {
-	/* Server requires quality of protection. 
-	 * We respond with selecting "qop=auth" protection.
-	 */
-	cred->qop = pjsip_AUTH_STR;
-	cred->nc.ptr = pj_pool_alloc(pool, 16);
-	pj_snprintf(cred->nc.ptr, 16, "%06u", nc);
-
-	if (cnonce && cnonce->slen) {
-	    pj_strdup(pool, &cred->cnonce, cnonce);
-	} else {
-	    pj_str_t dummy_cnonce = { "b39971", 6};
-	    pj_strdup(pool, &cred->cnonce, &dummy_cnonce);
-	}
-
-	create_digest( &cred->response, &cred->nonce, &cred->nc, cnonce, 
-		       &pjsip_AUTH_STR, uri, cred_info, method );
-
-    } else {
-	/* Server requires quality protection that we don't support. */
-	PJ_LOG(4,(THIS_FILE, "Unsupported qop offer %.*s", 
-		  chal->qop.slen, chal->qop.ptr));
-	return -1;
-    }
-
-    return 0;
-}
-
-#if PJSIP_AUTH_QOP_SUPPORT
-/*
- * Update authentication session with a challenge.
- */
-static void update_digest_session( pj_pool_t *ses_pool, 
-				   pjsip_auth_session *auth_sess,
-				   const pjsip_www_authenticate_hdr *hdr )
-{
-    if (hdr->challenge.digest.qop.slen == 0)
-	return;
-
-    /* Initialize cnonce and qop if not present. */
-    if (auth_sess->cnonce.slen == 0) {
-	/* Save the whole challenge */
-	auth_sess->last_chal = pjsip_hdr_clone(ses_pool, hdr);
-
-	/* Create cnonce */
-	pj_create_unique_string( ses_pool, &auth_sess->cnonce );
-
-	/* Initialize nonce-count */
-	auth_sess->nc = 1;
-
-	/* Save realm. */
-	pj_assert(auth_sess->realm.slen != 0);
-	if (auth_sess->realm.slen == 0) {
-	    pj_strdup(ses_pool, &auth_sess->realm, 
-		      &hdr->challenge.digest.realm);
-	}
-
-    } else {
-	/* Update last_nonce and nonce-count */
-	if (!pj_strcmp(&hdr->challenge.digest.nonce, 
-		       &auth_sess->last_chal->challenge.digest.nonce)) 
-	{
-	    /* Same nonce, increment nonce-count */
-	    ++auth_sess->nc;
-	} else {
-	    /* Server gives new nonce. */
-	    pj_strdup(ses_pool, &auth_sess->last_chal->challenge.digest.nonce,
-		      &hdr->challenge.digest.nonce);
-	    /* Has the opaque changed? */
-	    if (pj_strcmp(&auth_sess->last_chal->challenge.digest.opaque,
-			  &hdr->challenge.digest.opaque)) 
-	    {
-		pj_strdup(ses_pool, 
-			  &auth_sess->last_chal->challenge.digest.opaque,
-			  &hdr->challenge.digest.opaque);
-	    }
-	    auth_sess->nc = 1;
-	}
-    }
-}
-#endif	/* PJSIP_AUTH_QOP_SUPPORT */
-
-
-/* Find authentication session in the list. */
-static pjsip_auth_session *find_session( pjsip_auth_session *sess_list,
-					 const pj_str_t *realm )
-{
-    pjsip_auth_session *sess = sess_list->next;
-    while (sess != sess_list) {
-	if (pj_stricmp(&sess->realm, realm) == 0)
-	    return sess;
-	sess = sess->next;
-    }
-
-    return NULL;
-}
-
-/* 
- * Create Authorization/Proxy-Authorization response header based on the challege
- * in WWW-Authenticate/Proxy-Authenticate header.
- */
-PJ_DEF(pjsip_authorization_hdr*)
-pjsip_auth_respond( pj_pool_t *req_pool,
-		    const pjsip_www_authenticate_hdr *hdr,
-		    const pjsip_uri *uri,
-		    const pjsip_cred_info *cred_info,
-		    const pjsip_method *method,
-		    pj_pool_t *sess_pool,
-		    pjsip_auth_session *auth_sess)
-{
-    pjsip_authorization_hdr *auth;
-    char tmp[PJSIP_MAX_URL_SIZE];
-    pj_str_t uri_str;
-    pj_pool_t *pool;
-
-    pj_assert(hdr != NULL);
-    pj_assert(uri != NULL);
-    pj_assert(cred_info != NULL);
-    pj_assert(method != NULL);
-
-    /* Print URL in the original request. */
-    uri_str.ptr = tmp;
-    uri_str.slen = pjsip_uri_print(PJSIP_URI_IN_REQ_URI, uri, tmp, sizeof(tmp));
-    if (uri_str.slen < 1) {
-	pj_assert(!"URL is too long!");
-	PJ_LOG(4,(THIS_FILE, "Unable to authorize: URI is too long!"));
-	return NULL;
-    }
-
-#   if (PJSIP_AUTH_HEADER_CACHING)
-    {
-	pool = sess_pool;
-	PJ_UNUSED_ARG(req_pool);
-    }
-#   else
-    {
-	pool = req_pool;
-	PJ_UNUSED_ARG(sess_pool);
-    }
-#   endif
-
-    if (hdr->type == PJSIP_H_WWW_AUTHENTICATE)
-	auth = pjsip_authorization_hdr_create(pool);
-    else if (hdr->type == PJSIP_H_PROXY_AUTHENTICATE)
-	auth = pjsip_proxy_authorization_hdr_create(pool);
-    else {
-	pj_assert(0);
-	return NULL;
-    }
-
-    /* Only support digest scheme at the moment. */
-    if (!pj_stricmp(&hdr->scheme, &pjsip_DIGEST_STR)) {
-	pj_status_t rc;
-	pj_str_t *cnonce = NULL;
-	pj_uint32_t nc = 1;
-
-	/* Update the session (nonce-count etc) if required. */
-#	if PJSIP_AUTH_QOP_SUPPORT
-	{
-	    if (auth_sess) {
-		update_digest_session( sess_pool, auth_sess, hdr );
-
-		cnonce = &auth_sess->cnonce;
-		nc = auth_sess->nc;
-	    }
-	}
-#	endif	/* PJSIP_AUTH_QOP_SUPPORT */
-
-	auth->scheme = pjsip_DIGEST_STR;
-	rc = respond_digest( pool, &auth->credential.digest,
-			     &hdr->challenge.digest, &uri_str, cred_info,
-			     cnonce, nc, &method->name);
-	if (rc != 0)
-	    return NULL;
-
-	/* Set qop type in auth session the first time only. */
-	if (hdr->challenge.digest.qop.slen != 0 && auth_sess) {
-	    if (auth_sess->qop_value == PJSIP_AUTH_QOP_NONE) {
-		pj_str_t *qop_val = &auth->credential.digest.qop;
-		if (!pj_strcmp(qop_val, &pjsip_AUTH_STR)) {
-		    auth_sess->qop_value = PJSIP_AUTH_QOP_AUTH;
-		} else {
-		    auth_sess->qop_value = PJSIP_AUTH_QOP_UNKNOWN;
-		}
-	    }
-	}
-    } else {
-	auth = NULL;
-    }
-
-    /* Keep the new authorization header in the cache, only
-     * if no qop is not present.
-     */
-#   if PJSIP_AUTH_HEADER_CACHING
-    {
-	if (auth && auth_sess && auth_sess->qop_value == PJSIP_AUTH_QOP_NONE) {
-	    pjsip_cached_auth_hdr *cached_hdr;
-
-	    /* Delete old header with the same method. */
-	    cached_hdr = auth_sess->cached_hdr.next;
-	    while (cached_hdr != &auth_sess->cached_hdr) {
-		if (pjsip_method_cmp(method, &cached_hdr->method)==0)
-		    break;
-		cached_hdr = cached_hdr->next;
-	    }
-
-	    /* Save the header to the list. */
-	    if (cached_hdr != &auth_sess->cached_hdr) {
-		cached_hdr->hdr = auth;
-	    } else {
-		cached_hdr = pj_pool_alloc(pool, sizeof(*cached_hdr));
-		pjsip_method_copy( pool, &cached_hdr->method, method);
-		cached_hdr->hdr = auth;
-		pj_list_insert_before( &auth_sess->cached_hdr, cached_hdr );
-	    }
-	}
-    }
-#   endif
-
-    return auth;
-
-}
-
-/* Verify incoming Authorization/Proxy-Authorization header against existing
- * credentials. Will return TRUE if the authorization request matches any of
- * the credential.
- */
-PJ_DEF(pj_bool_t) pjsip_auth_verify(const pjsip_authorization_hdr *hdr,
-				    const pj_str_t *method,
-				    const pjsip_cred_info *cred_info )
-{
-    if (pj_stricmp(&hdr->scheme, &pjsip_DIGEST_STR) == 0) {
-	char digest_buf[MD5STRLEN];
-	pj_str_t digest;
-	const pjsip_digest_credential *dig = &hdr->credential.digest;
-
-	/* Check that username match. */
-	if (pj_strcmp(&dig->username, &cred_info->username) != 0)
-	    return PJ_FALSE;
-
-	/* Check that realm match. */
-	if (pj_strcmp(&dig->realm, &cred_info->realm) != 0)
-	    return PJ_FALSE;
-
-	/* Prepare for our digest calculation. */
-	digest.ptr = digest_buf;
-	digest.slen = MD5STRLEN;
-
-	/* Create digest for comparison. */
-	create_digest(  &digest, 
-			&hdr->credential.digest.nonce,
-			&hdr->credential.digest.nc, 
-			&hdr->credential.digest.cnonce,
-			&hdr->credential.digest.qop,
-			&hdr->credential.digest.uri,
-			cred_info, 
-			method );
-
-	return pj_stricmp(&digest, &hdr->credential.digest.response) == 0;
-
-    } else {
-	pj_assert(0);
-	return PJ_FALSE;
-    }
-}
-
-/* Find credential to use for the specified realm and scheme. */
-PJ_DEF(const pjsip_cred_info*) pjsip_auth_find_cred( unsigned count,
-						     const pjsip_cred_info cred[],
-						     const pj_str_t *realm,
-						     const pj_str_t *scheme)
-{
-    unsigned i;
-    PJ_UNUSED_ARG(scheme);
-    for (i=0; i<count; ++i) {
-	if (pj_stricmp(&cred[i].realm, realm) == 0)
-	    return &cred[i];
-    }
-    return NULL;
-}
-
-#if PJSIP_AUTH_AUTO_SEND_NEXT
-static void new_auth_for_req( pjsip_tx_data *tdata,
-			      pj_pool_t *sess_pool,
-			      pjsip_auth_session *sess,
-			      int cred_count,
-			      const pjsip_cred_info cred_info[])
-{
-    const pjsip_cred_info *cred;
-    pjsip_authorization_hdr *hauth;
-
-    pj_assert(sess->last_chal != NULL);
-
-    cred = pjsip_auth_find_cred( cred_count, cred_info, &sess->realm,
-				 &sess->last_chal->scheme );
-    if (!cred)
-	return;
-
-    
-    hauth = pjsip_auth_respond( tdata->pool, sess->last_chal,
-				tdata->msg->line.req.uri,
-				cred, &tdata->msg->line.req.method,
-				sess_pool, sess);
-    if (hauth) {
-	pjsip_msg_add_hdr( tdata->msg, (pjsip_hdr*)hauth);
-    }
-}
-#endif
-
-/* 
- * Initialize new request message with authorization headers.
- * This function will put Authorization/Proxy-Authorization headers to the
- * outgoing request message. If caching is enabled (PJSIP_AUTH_HEADER_CACHING)
- * and the session has previously sent Authorization/Proxy-Authorization header
- * with the same method, then the same Authorization/Proxy-Authorization header
- * will be resent from the cache only if qop is not present. If the stack is 
- * configured to automatically generate next Authorization/Proxy-Authorization
- * headers (PJSIP_AUTH_AUTO_SEND_NEXT flag), then new Authorization/Proxy-
- * Authorization headers are calculated and generated when they are not present
- * in the case or if authorization session has qop.
- *
- * If both PJSIP_AUTH_HEADER_CACHING flag and PJSIP_AUTH_AUTO_SEND_NEXT flag
- * are not set, this function will do nothing. The stack then will only send
- * Authorization/Proxy-Authorization to respond 401/407 response.
- */
-PJ_DEF(pj_status_t) pjsip_auth_init_req( pj_pool_t *sess_pool,
-					 pjsip_tx_data *tdata,
-					 pjsip_auth_session *sess_list,
-					 int cred_count, 
-					 const pjsip_cred_info cred_info[])
-{
-    pjsip_auth_session *sess;
-    pjsip_method *method = &tdata->msg->line.req.method;
-
-    pj_assert(tdata->msg->type == PJSIP_REQUEST_MSG);
-
-    if (!sess_list)
-	return 0;
-
-    sess = sess_list->next;
-    while (sess != sess_list) {
-	if (sess->qop_value == PJSIP_AUTH_QOP_NONE) {
-#	    if (PJSIP_AUTH_HEADER_CACHING)
-	    {
-		pjsip_cached_auth_hdr *entry = sess->cached_hdr.next;
-		while (entry != &sess->cached_hdr) {
-		    if (pjsip_method_cmp(&entry->method, method)==0) {
-			pjsip_authorization_hdr *hauth;
-			hauth = pjsip_hdr_shallow_clone(tdata->pool, entry->hdr);
-			pjsip_msg_add_hdr(tdata->msg, (pjsip_hdr*)hauth);
-		    } else {
-#			if (PJSIP_AUTH_AUTO_SEND_NEXT)
-			{
-			    new_auth_for_req( tdata, sess_pool, sess, 
-					      cred_count, cred_info);
-			}
-#			else
-			{
-			    PJ_UNUSED_ARG(sess_pool);
-			    PJ_UNUSED_ARG(cred_count);
-			    PJ_UNUSED_ARG(cred_info);
-			}
-#			endif	/* PJSIP_AUTH_AUTO_SEND_NEXT */
-		    }
-		    entry = entry->next;
-		}
-	    }
-#	    elif (PJSIP_AUTH_AUTO_SEND_NEXT)
-	    {
-		new_auth_for_req( tdata, sess_pool, sess, 
-				  cred_count, cred_info);
-	    }
-#	    else
-	    {
-		PJ_UNUSED_ARG(sess_pool);
-		PJ_UNUSED_ARG(cred_count);
-		PJ_UNUSED_ARG(cred_info);
-	    }
-#	    endif   /* PJSIP_AUTH_HEADER_CACHING */
-
-	} 
-#	if (PJSIP_AUTH_QOP_SUPPORT && PJSIP_AUTH_AUTO_SEND_NEXT)
-	else if (sess->qop_value == PJSIP_AUTH_QOP_AUTH) {
-	    /* For qop="auth", we have to re-create the authorization header. 
-	     */
-	    const pjsip_cred_info *cred;
-	    pjsip_authorization_hdr *hauth;
-
-	    cred = pjsip_auth_find_cred( cred_count, cred_info, 
-					 &sess->realm, 
-					 &sess->last_chal->scheme);
-	    if (!cred) {
-		sess = sess->next;
-		continue;
-	    }
-
-	    hauth = pjsip_auth_respond( tdata->pool, sess->last_chal, 
-					tdata->msg->line.req.uri, 
-					cred,
-					&tdata->msg->line.req.method,
-					sess_pool, sess );
-	    if (hauth) {
-		pjsip_msg_add_hdr(tdata->msg, (pjsip_hdr*)hauth);
-	    }
-	}
-#	endif	/* PJSIP_AUTH_QOP_SUPPORT && PJSIP_AUTH_AUTO_SEND_NEXT */
-
-	sess = sess->next;
-    }
-    return 0;
-}
-
-/* Process authorization challenge */
-static pjsip_authorization_hdr *process_auth( pj_pool_t *req_pool,
-					      const pjsip_www_authenticate_hdr *hchal,
-					      const pjsip_uri *uri,
-					      pjsip_tx_data *tdata,
-					      int cred_count,
-					      const pjsip_cred_info cred_info[],
-					      pj_pool_t *ses_pool,
-					      pjsip_auth_session *auth_sess)
-{
-    const pjsip_cred_info *cred;
-    pjsip_authorization_hdr *sent_auth = NULL, *hauth;
-    pjsip_hdr *hdr;
-
-    /* See if we have sent authorization header for this realm */
-    hdr = tdata->msg->hdr.next;
-    while (hdr != &tdata->msg->hdr) {
-	if ((hchal->type == PJSIP_H_WWW_AUTHENTICATE &&
-	     hdr->type == PJSIP_H_AUTHORIZATION) ||
-	    (hchal->type == PJSIP_H_PROXY_AUTHENTICATE &&
-	     hdr->type == PJSIP_H_PROXY_AUTHORIZATION))
-	{
-	    sent_auth = (pjsip_authorization_hdr*) hdr;
-	    if (pj_stricmp(&hchal->challenge.common.realm, 
-			   &sent_auth->credential.common.realm )==0)
-	    {
-		break;
-	    }
-	}
-	hdr = hdr->next;
-    }
-
-    /* If we have sent, see if server rejected because of stale nonce or
-     * other causes.
-     */
-    if (hdr != &tdata->msg->hdr) {
-	if (hchal->challenge.digest.stale == 0) {
-	    /* Our credential is rejected. No point in trying to re-supply
-	     * the same credential.
-	     */
-	    PJ_LOG(4, (THIS_FILE, "Authorization failed for %.*s@%.*s",
-		       sent_auth->credential.digest.username.slen,
-		       sent_auth->credential.digest.username.ptr,
-		       sent_auth->credential.digest.realm.slen,
-		       sent_auth->credential.digest.realm.ptr));
-	    return NULL;
-	}
-
-	/* Otherwise remove old, stale authorization header from the mesasge.
-	 * We will supply a new one.
-	 */
-	pj_list_erase(sent_auth);
-    }
-
-    /* Find credential to be used for the challenge. */
-    cred = pjsip_auth_find_cred( cred_count, cred_info, 
-				 &hchal->challenge.common.realm, &hchal->scheme);
-    if (!cred) {
-	const pj_str_t *realm = &hchal->challenge.common.realm;
-	PJ_LOG(4,(THIS_FILE, 
-		  "Unable to set auth for %s: can not find credential for %.*s/%.*s",
-		  tdata->obj_name, 
-		  realm->slen, realm->ptr,
-		  hchal->scheme.slen, hchal->scheme.ptr));
-	return NULL;
-    }
-
-    /* Respond to authorization challenge. */
-    hauth = pjsip_auth_respond( req_pool, hchal, uri, cred, 
-				&tdata->msg->line.req.method, 
-				ses_pool, auth_sess);
-    return hauth;
-}
-
-
-/* Reinitialize outgoing request after 401/407 response is received.
- * The purpose of this function is:
- *  - to add a Authorization/Proxy-Authorization header.
- *  - to put the newly created Authorization/Proxy-Authorization header
- *    in cached_list.
- */
-PJ_DEF(pjsip_tx_data*) pjsip_auth_reinit_req( pjsip_endpoint *endpt, 
-					      pj_pool_t *ses_pool, 
-					      pjsip_auth_session *sess_list,
-					      int cred_count, 
-					      const pjsip_cred_info cred_info[],
-					      pjsip_tx_data *tdata, 
-					      const pjsip_rx_data *rdata)
-{
-    const pjsip_hdr *hdr;
-    pjsip_via_hdr *via;
-
-    PJ_UNUSED_ARG(endpt);
-
-    pj_assert(rdata->msg_info.msg->type == PJSIP_RESPONSE_MSG);
-    pj_assert(rdata->msg_info.msg->line.status.code == 401 ||
-	      rdata->msg_info.msg->line.status.code == 407 );
-
-    /*
-     * Respond to each authentication challenge.
-     */
-    hdr = rdata->msg_info.msg->hdr.next;
-    while (hdr != &rdata->msg_info.msg->hdr) {
-	pjsip_auth_session *sess;
-	const pjsip_www_authenticate_hdr *hchal;
-	pjsip_authorization_hdr *hauth;
-
-	/* Find WWW-Authenticate or Proxy-Authenticate header. */
-	while (hdr->type != PJSIP_H_WWW_AUTHENTICATE &&
-	       hdr->type != PJSIP_H_PROXY_AUTHENTICATE &&
-	       hdr != &rdata->msg_info.msg->hdr)
-	{
-	    hdr = hdr->next;
-	}
-	if (hdr == &rdata->msg_info.msg->hdr)
-	    break;
-
-	hchal = (const pjsip_www_authenticate_hdr*) hdr;
-
-	/* Find authentication session for this realm, create a new one
-	 * if not present.
-	 */
-	sess = find_session(sess_list, &hchal->challenge.common.realm );
-	if (!sess) {
-	    sess = pj_pool_calloc( ses_pool, 1, sizeof(*sess));
-	    pj_strdup( ses_pool, &sess->realm, &hchal->challenge.common.realm);
-	    sess->is_proxy = (hchal->type == PJSIP_H_PROXY_AUTHENTICATE);
-#	    if (PJSIP_AUTH_HEADER_CACHING)
-	    {
-		pj_list_init(&sess->cached_hdr);
-	    }
-#	    endif
-	    pj_list_insert_before( sess_list, sess );
-	}
-
-	/* Create authorization header for this challenge, and update
-	 * authorization session.
-	 */
-	hauth = process_auth( tdata->pool, hchal, tdata->msg->line.req.uri, 
-			      tdata, cred_count, cred_info, ses_pool, sess );
-	if (!hauth)
-	    return NULL;
-
-	/* Add to the message. */
-	pjsip_msg_add_hdr(tdata->msg, (pjsip_hdr*)hauth);
-
-	/* Process next header. */
-	hdr = hdr->next;
-    }
-
-
-    /* Remove branch param in Via header. */
-    via = pjsip_msg_find_hdr(tdata->msg, PJSIP_H_VIA, NULL);
-    via->branch_param.slen = 0;
-
-    /* Increment reference counter. */
-    pjsip_tx_data_add_ref(tdata);
-
-    /* Done. */
-    return tdata;
-}
-
diff --git a/pjsip/src/pjsip/sip_auth_client.c b/pjsip/src/pjsip/sip_auth_client.c
new file mode 100644
index 0000000..523b35a
--- /dev/null
+++ b/pjsip/src/pjsip/sip_auth_client.c
@@ -0,0 +1,783 @@
+/* $Id$ */
+/* 
+ * Copyright (C) 2003-2006 Benny Prijono <benny@prijono.org>
+ *
+ * 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 
+ */
+
+#include <pjsip/sip_auth.h>
+#include <pjsip/sip_auth_parser.h>	/* just to get pjsip_DIGEST_STR */
+#include <pjsip/sip_transport.h>
+#include <pjsip/sip_endpoint.h>
+#include <pjsip/sip_errno.h>
+#include <pjlib-util/md5.h>
+#include <pj/log.h>
+#include <pj/string.h>
+#include <pj/pool.h>
+#include <pj/guid.h>
+#include <pj/assert.h>
+#include <pj/ctype.h>
+
+
+
+/* A macro just to get rid of type mismatch between char and unsigned char */
+#define MD5_APPEND(pms,buf,len)	pj_md5_update(pms, (const pj_uint8_t*)buf, len)
+
+/* Logging. */
+#define THIS_FILE   "sip_auth_client.c"
+#if 0
+#  define AUTH_TRACE_(expr)  PJ_LOG(3, expr)
+#else
+#  define AUTH_TRACE_(expr)
+#endif
+
+/* Transform digest to string.
+ * output must be at least PJSIP_MD5STRLEN+1 bytes.
+ *
+ * NOTE: THE OUTPUT STRING IS NOT NULL TERMINATED!
+ */
+static void digest2str(const unsigned char digest[], char *output)
+{
+    int i;
+    for (i = 0; i<16; ++i) {
+	pj_val_to_hex_digit(digest[i], output);
+	output += 2;
+    }
+}
+
+
+/*
+ * Create response digest based on the parameters and store the
+ * digest ASCII in 'result'. 
+ */
+void pjsip_auth_create_digest( pj_str_t *result,
+			       const pj_str_t *nonce,
+			       const pj_str_t *nc,
+			       const pj_str_t *cnonce,
+			       const pj_str_t *qop,
+			       const pj_str_t *uri,
+			       const pjsip_cred_info *cred_info,
+			       const pj_str_t *method)
+{
+    char ha1[PJSIP_MD5STRLEN];
+    char ha2[PJSIP_MD5STRLEN];
+    unsigned char digest[16];
+    pj_md5_context pms;
+
+    pj_assert(result->slen >= PJSIP_MD5STRLEN);
+
+    AUTH_TRACE_((THIS_FILE, "Begin creating digest"));
+
+    if (cred_info->data_type == PJSIP_CRED_DATA_PLAIN_PASSWD) {
+	/*** 
+	 *** ha1 = MD5(username ":" realm ":" password) 
+	 ***/
+	pj_md5_init(&pms);
+	MD5_APPEND( &pms, cred_info->username.ptr, cred_info->username.slen);
+	MD5_APPEND( &pms, ":", 1);
+	MD5_APPEND( &pms, cred_info->realm.ptr, cred_info->realm.slen);
+	MD5_APPEND( &pms, ":", 1);
+	MD5_APPEND( &pms, cred_info->data.ptr, cred_info->data.slen);
+	pj_md5_final(&pms, digest);
+
+	digest2str(digest, ha1);
+
+    } else if (cred_info->data_type == PJSIP_CRED_DATA_DIGEST) {
+	pj_assert(cred_info->data.slen == 32);
+	pj_memcpy( ha1, cred_info->data.ptr, cred_info->data.slen );
+    }
+
+    AUTH_TRACE_((THIS_FILE, "  ha1=%.32s", ha1));
+
+    /***
+     *** ha2 = MD5(method ":" req_uri) 
+     ***/
+    pj_md5_init(&pms);
+    MD5_APPEND( &pms, method->ptr, method->slen);
+    MD5_APPEND( &pms, ":", 1);
+    MD5_APPEND( &pms, uri->ptr, uri->slen);
+    pj_md5_final(&pms, digest);
+    digest2str(digest, ha2);
+
+    AUTH_TRACE_((THIS_FILE, "  ha2=%.32s", ha2));
+
+    /***
+     *** When qop is not used:
+     ***    response = MD5(ha1 ":" nonce ":" ha2) 
+     ***
+     *** When qop=auth is used:
+     ***    response = MD5(ha1 ":" nonce ":" nc ":" cnonce ":" qop ":" ha2)
+     ***/
+    pj_md5_init(&pms);
+    MD5_APPEND( &pms, ha1, PJSIP_MD5STRLEN);
+    MD5_APPEND( &pms, ":", 1);
+    MD5_APPEND( &pms, nonce->ptr, nonce->slen);
+    if (qop && qop->slen != 0) {
+	MD5_APPEND( &pms, ":", 1);
+	MD5_APPEND( &pms, nc->ptr, nc->slen);
+	MD5_APPEND( &pms, ":", 1);
+	MD5_APPEND( &pms, cnonce->ptr, cnonce->slen);
+	MD5_APPEND( &pms, ":", 1);
+	MD5_APPEND( &pms, qop->ptr, qop->slen);
+    }
+    MD5_APPEND( &pms, ":", 1);
+    MD5_APPEND( &pms, ha2, PJSIP_MD5STRLEN);
+
+    /* This is the final response digest. */
+    pj_md5_final(&pms, digest);
+    
+    /* Convert digest to string and store in chal->response. */
+    result->slen = PJSIP_MD5STRLEN;
+    digest2str(digest, result->ptr);
+
+    AUTH_TRACE_((THIS_FILE, "  digest=%.32s", result->ptr));
+    AUTH_TRACE_((THIS_FILE, "Digest created"));
+}
+
+/*
+ * Finds out if qop offer contains "auth" token.
+ */
+static pj_bool_t has_auth_qop( pj_pool_t *pool, const pj_str_t *qop_offer)
+{
+    pj_str_t qop;
+    char *p;
+
+    pj_strdup_with_null( pool, &qop, qop_offer);
+    p = qop.ptr;
+    while (*p) {
+	*p = (char)pj_tolower(*p);
+	++p;
+    }
+
+    p = qop.ptr;
+    while (*p) {
+	if (*p=='a' && *(p+1)=='u' && *(p+2)=='t' && *(p+3)=='h') {
+	    int e = *(p+4);
+	    if (e=='"' || e==',' || e==0)
+		return PJ_TRUE;
+	    else
+		p += 4;
+	} else {
+	    ++p;
+	}
+    }
+
+    return PJ_FALSE;
+}
+
+/*
+ * Generate response digest. 
+ * Most of the parameters to generate the digest (i.e. username, realm, uri,
+ * and nonce) are expected to be in the credential. Additional parameters (i.e.
+ * password and method param) should be supplied in the argument.
+ *
+ * The resulting digest will be stored in cred->response.
+ * The pool is used to allocate 32 bytes to store the digest in cred->response.
+ */
+static pj_status_t respond_digest( pj_pool_t *pool,
+				   pjsip_digest_credential *cred,
+				   const pjsip_digest_challenge *chal,
+				   const pj_str_t *uri,
+				   const pjsip_cred_info *cred_info,
+				   const pj_str_t *cnonce,
+				   pj_uint32_t nc,
+				   const pj_str_t *method)
+{
+    /* Check algorithm is supported. We only support MD5. */
+    if (chal->algorithm.slen && pj_stricmp(&chal->algorithm, &pjsip_MD5_STR))
+    {
+	PJ_LOG(4,(THIS_FILE, "Unsupported digest algorithm \"%.*s\"",
+		  chal->algorithm.slen, chal->algorithm.ptr));
+	return PJSIP_EINVALIDALGORITHM;
+    }
+
+    /* Build digest credential from arguments. */
+    pj_strdup(pool, &cred->username, &cred_info->username);
+    pj_strdup(pool, &cred->realm, &chal->realm);
+    pj_strdup(pool, &cred->nonce, &chal->nonce);
+    pj_strdup(pool, &cred->uri, uri);
+    cred->algorithm = pjsip_MD5_STR;
+    pj_strdup(pool, &cred->opaque, &chal->opaque);
+    
+    /* Allocate memory. */
+    cred->response.ptr = pj_pool_alloc(pool, PJSIP_MD5STRLEN);
+    cred->response.slen = PJSIP_MD5STRLEN;
+
+    if (chal->qop.slen == 0) {
+	/* Server doesn't require quality of protection. */
+
+	/* Convert digest to string and store in chal->response. */
+	pjsip_auth_create_digest( &cred->response, &cred->nonce, NULL, NULL, 
+				  NULL, uri, cred_info, method);
+
+    } else if (has_auth_qop(pool, &chal->qop)) {
+	/* Server requires quality of protection. 
+	 * We respond with selecting "qop=auth" protection.
+	 */
+	cred->qop = pjsip_AUTH_STR;
+	cred->nc.ptr = pj_pool_alloc(pool, 16);
+	pj_snprintf(cred->nc.ptr, 16, "%06u", nc);
+
+	if (cnonce && cnonce->slen) {
+	    pj_strdup(pool, &cred->cnonce, cnonce);
+	} else {
+	    pj_str_t dummy_cnonce = { "b39971", 6};
+	    pj_strdup(pool, &cred->cnonce, &dummy_cnonce);
+	}
+
+	pjsip_auth_create_digest( &cred->response, &cred->nonce, &cred->nc, 
+				  cnonce, &pjsip_AUTH_STR, uri, cred_info, 
+				  method );
+
+    } else {
+	/* Server requires quality protection that we don't support. */
+	PJ_LOG(4,(THIS_FILE, "Unsupported qop offer %.*s", 
+		  chal->qop.slen, chal->qop.ptr));
+	return PJSIP_EINVALIDQOP;
+    }
+
+    return PJ_SUCCESS;
+}
+
+#if defined(PJSIP_AUTH_QOP_SUPPORT) && PJSIP_AUTH_QOP_SUPPORT!=0
+/*
+ * Update authentication session with a challenge.
+ */
+static void update_digest_session( pj_pool_t *ses_pool, 
+				   pjsip_cached_auth *cached_auth,
+				   const pjsip_www_authenticate_hdr *hdr )
+{
+    if (hdr->challenge.digest.qop.slen == 0)
+	return;
+
+    /* Initialize cnonce and qop if not present. */
+    if (cached_auth->cnonce.slen == 0) {
+	/* Save the whole challenge */
+	cached_auth->last_chal = pjsip_hdr_clone(ses_pool, hdr);
+
+	/* Create cnonce */
+	pj_create_unique_string( ses_pool, &cached_auth->cnonce );
+
+	/* Initialize nonce-count */
+	cached_auth->nc = 1;
+
+	/* Save realm. */
+	pj_assert(cached_auth->realm.slen != 0);
+	if (cached_auth->realm.slen == 0) {
+	    pj_strdup(ses_pool, &cached_auth->realm, 
+		      &hdr->challenge.digest.realm);
+	}
+
+    } else {
+	/* Update last_nonce and nonce-count */
+	if (!pj_strcmp(&hdr->challenge.digest.nonce, 
+		       &cached_auth->last_chal->challenge.digest.nonce)) 
+	{
+	    /* Same nonce, increment nonce-count */
+	    ++cached_auth->nc;
+	} else {
+	    /* Server gives new nonce. */
+	    pj_strdup(ses_pool, &cached_auth->last_chal->challenge.digest.nonce,
+		      &hdr->challenge.digest.nonce);
+	    /* Has the opaque changed? */
+	    if (pj_strcmp(&cached_auth->last_chal->challenge.digest.opaque,
+			  &hdr->challenge.digest.opaque)) 
+	    {
+		pj_strdup(ses_pool, 
+			  &cached_auth->last_chal->challenge.digest.opaque,
+			  &hdr->challenge.digest.opaque);
+	    }
+	    cached_auth->nc = 1;
+	}
+    }
+}
+#endif	/* PJSIP_AUTH_QOP_SUPPORT */
+
+
+/* Find cached authentication in the list for the specified realm. */
+static pjsip_cached_auth *find_cached_auth( pjsip_auth_clt_sess *sess,
+					    const pj_str_t *realm )
+{
+    pjsip_cached_auth *auth = sess->cached_auth.next;
+    while (auth != &sess->cached_auth) {
+	if (pj_stricmp(&auth->realm, realm) == 0)
+	    return auth;
+	auth = auth->next;
+    }
+
+    return NULL;
+}
+
+/* Find credential to use for the specified realm and auth scheme. */
+static const pjsip_cred_info* auth_find_cred( const pjsip_auth_clt_sess *sess,
+					      const pj_str_t *realm,
+					      const pj_str_t *auth_scheme)
+{
+    unsigned i;
+    PJ_UNUSED_ARG(auth_scheme);
+    for (i=0; i<sess->cred_cnt; ++i) {
+	if (pj_stricmp(&sess->cred_info[i].realm, realm) == 0)
+	    return &sess->cred_info[i];
+    }
+    return NULL;
+}
+
+
+/* Init client session. */
+PJ_DEF(pj_status_t) pjsip_auth_clt_init(  pjsip_auth_clt_sess *sess,
+					  pjsip_endpoint *endpt,
+					  pj_pool_t *pool, 
+					  unsigned options)
+{
+    PJ_ASSERT_RETURN(sess && endpt && pool && (options==0), PJ_EINVAL);
+
+    sess->pool = pool;
+    sess->endpt = endpt;
+    sess->cred_cnt = 0;
+    sess->cred_info = NULL;
+    pj_list_init(&sess->cached_auth);
+
+    return PJ_SUCCESS;
+}
+
+
+/* Set client credentials. */
+PJ_DEF(pj_status_t) pjsip_auth_clt_set_credentials( pjsip_auth_clt_sess *sess,
+						    int cred_cnt,
+						    const pjsip_cred_info *c)
+{
+    PJ_ASSERT_RETURN(sess && cred_cnt && c, PJ_EINVAL);
+
+    sess->cred_info = pj_pool_alloc(sess->pool, cred_cnt * sizeof(*c));
+    pj_memcpy(sess->cred_info, c, cred_cnt * sizeof(*c));
+    sess->cred_cnt = cred_cnt;
+
+    return PJ_SUCCESS;
+}
+
+
+/* 
+ * Create Authorization/Proxy-Authorization response header based on the challege
+ * in WWW-Authenticate/Proxy-Authenticate header.
+ */
+static pj_status_t auth_respond( pj_pool_t *req_pool,
+				 const pjsip_www_authenticate_hdr *hdr,
+				 const pjsip_uri *uri,
+				 const pjsip_cred_info *cred_info,
+				 const pjsip_method *method,
+				 pj_pool_t *sess_pool,
+				 pjsip_cached_auth *cached_auth,
+				 pjsip_authorization_hdr **p_h_auth)
+{
+    pjsip_authorization_hdr *hauth;
+    char tmp[PJSIP_MAX_URL_SIZE];
+    pj_str_t uri_str;
+    pj_pool_t *pool;
+    pj_status_t status;
+
+    /* Verify arguments. */
+    PJ_ASSERT_RETURN(req_pool && hdr && uri && cred_info && method &&
+		     sess_pool && cached_auth && p_h_auth, PJ_EINVAL);
+
+    /* Print URL in the original request. */
+    uri_str.ptr = tmp;
+    uri_str.slen = pjsip_uri_print(PJSIP_URI_IN_REQ_URI, uri, tmp,sizeof(tmp));
+    if (uri_str.slen < 1) {
+	pj_assert(!"URL is too long!");
+	return PJSIP_EURITOOLONG;
+    }
+
+#   if (PJSIP_AUTH_HEADER_CACHING)
+    {
+	pool = sess_pool;
+	PJ_UNUSED_ARG(req_pool);
+    }
+#   else
+    {
+	pool = req_pool;
+	PJ_UNUSED_ARG(sess_pool);
+    }
+#   endif
+
+    if (hdr->type == PJSIP_H_WWW_AUTHENTICATE)
+	hauth = pjsip_authorization_hdr_create(pool);
+    else if (hdr->type == PJSIP_H_PROXY_AUTHENTICATE)
+	hauth = pjsip_proxy_authorization_hdr_create(pool);
+    else {
+	pj_assert(!"Invalid response header!");
+	return PJSIP_EINVALIDHDR;
+    }
+
+    /* Only support digest scheme at the moment. */
+    if (!pj_stricmp(&hdr->scheme, &pjsip_DIGEST_STR)) {
+	pj_str_t *cnonce = NULL;
+	pj_uint32_t nc = 1;
+
+	/* Update the session (nonce-count etc) if required. */
+#	if PJSIP_AUTH_QOP_SUPPORT
+	{
+	    if (cached_auth) {
+		update_digest_session( sess_pool, cached_auth, hdr );
+
+		cnonce = &cached_auth->cnonce;
+		nc = cached_auth->nc;
+	    }
+	}
+#	endif	/* PJSIP_AUTH_QOP_SUPPORT */
+
+	hauth->scheme = pjsip_DIGEST_STR;
+	status = respond_digest( pool, &hauth->credential.digest,
+				 &hdr->challenge.digest, &uri_str, cred_info,
+				 cnonce, nc, &method->name);
+	if (status != PJ_SUCCESS)
+	    return status;
+
+	/* Set qop type in auth session the first time only. */
+	if (hdr->challenge.digest.qop.slen != 0 && cached_auth) {
+	    if (cached_auth->qop_value == PJSIP_AUTH_QOP_NONE) {
+		pj_str_t *qop_val = &hauth->credential.digest.qop;
+		if (!pj_strcmp(qop_val, &pjsip_AUTH_STR)) {
+		    cached_auth->qop_value = PJSIP_AUTH_QOP_AUTH;
+		} else {
+		    cached_auth->qop_value = PJSIP_AUTH_QOP_UNKNOWN;
+		}
+	    }
+	}
+    } else {
+	return PJSIP_EINVALIDAUTHSCHEME;
+    }
+
+    /* Keep the new authorization header in the cache, only
+     * if no qop is not present.
+     */
+#   if PJSIP_AUTH_HEADER_CACHING
+    {
+	if (hauth && cached_auth && cached_auth->qop_value == PJSIP_AUTH_QOP_NONE) {
+	    pjsip_cached_auth_hdr *cached_hdr;
+
+	    /* Delete old header with the same method. */
+	    cached_hdr = cached_auth->cached_hdr.next;
+	    while (cached_hdr != &cached_auth->cached_hdr) {
+		if (pjsip_method_cmp(method, &cached_hdr->method)==0)
+		    break;
+		cached_hdr = cached_hdr->next;
+	    }
+
+	    /* Save the header to the list. */
+	    if (cached_hdr != &cached_auth->cached_hdr) {
+		cached_hdr->hdr = hauth;
+	    } else {
+		cached_hdr = pj_pool_alloc(pool, sizeof(*cached_hdr));
+		pjsip_method_copy( pool, &cached_hdr->method, method);
+		cached_hdr->hdr = hauth;
+		pj_list_insert_before( &cached_auth->cached_hdr, cached_hdr );
+	    }
+	}
+    }
+#   endif
+
+    *p_h_auth = hauth;
+    return PJ_SUCCESS;
+
+}
+
+
+#if defined(PJSIP_AUTH_AUTO_SEND_NEXT) && PJSIP_AUTH_AUTO_SEND_NEXT!=0
+static pj_status_t new_auth_for_req( pjsip_tx_data *tdata,
+				     pjsip_auth_clt_sess *sess,
+				     pjsip_cached_auth *auth,
+				     pjsip_authorization_hdr **p_h_auth)
+{
+    const pjsip_cred_info *cred;
+    pjsip_authorization_hdr *hauth;
+    pj_status_t status;
+
+    PJ_ASSERT_RETURN(tdata && sess && auth, PJ_EINVAL);
+    PJ_ASSERT_RETURN(auth->last_chal != NULL, PJSIP_EAUTHNOPREVCHAL);
+
+    cred = auth_find_cred( sess, &auth->realm, &auth->last_chal->scheme );
+    if (!cred)
+	return PJSIP_ENOCREDENTIAL;
+
+    status = auth_respond( tdata->pool, auth->last_chal,
+			   tdata->msg->line.req.uri,
+			   cred, &tdata->msg->line.req.method,
+			   sess->pool, auth, &hauth);
+    if (status != PJ_SUCCESS)
+	return status;
+    
+    pjsip_msg_add_hdr( tdata->msg, (pjsip_hdr*)hauth);
+
+    if (p_h_auth)
+	*p_h_auth = hauth;
+
+    return PJ_SUCCESS;
+}
+#endif
+
+
+
+/* Initialize outgoing request. */
+PJ_DEF(pj_status_t) pjsip_auth_clt_init_req( pjsip_auth_clt_sess *sess,
+					     pjsip_tx_data *tdata )
+{
+    const pjsip_method *method;
+    pjsip_cached_auth *auth;
+    pj_status_t status;
+
+    PJ_ASSERT_RETURN(sess && tdata, PJ_EINVAL);
+    PJ_ASSERT_RETURN(sess->pool, PJSIP_ENOTINITIALIZED);
+    PJ_ASSERT_RETURN(tdata->msg->type==PJSIP_REQUEST_MSG,
+		     PJSIP_ENOTREQUESTMSG);
+
+    /* Get the method. */
+    method = &tdata->msg->line.req.method;
+
+    auth = sess->cached_auth.next;
+    while (auth != &sess->cached_auth) {
+	if (auth->qop_value == PJSIP_AUTH_QOP_NONE) {
+#	    if defined(PJSIP_AUTH_HEADER_CACHING) && \
+	       PJSIP_AUTH_HEADER_CACHING!=0
+	    {
+		pjsip_cached_auth_hdr *entry = auth->cached_hdr.next;
+		while (entry != &auth->cached_hdr) {
+		    if (pjsip_method_cmp(&entry->method, method)==0) {
+			pjsip_authorization_hdr *hauth;
+			hauth = pjsip_hdr_shallow_clone(tdata->pool, entry->hdr);
+			pjsip_msg_add_hdr(tdata->msg, (pjsip_hdr*)hauth);
+		    } else {
+#			if defined(PJSIP_AUTH_AUTO_SEND_NEXT) && \
+			   PJSIP_AUTH_AUTO_SEND_NEXT!=0
+			{
+			    new_auth_for_req( tdata, sess, auth, NULL);
+			}
+#			endif
+		    }
+		    entry = entry->next;
+		}
+	    }
+#	    elif defined(PJSIP_AUTH_AUTO_SEND_NEXT) && \
+		 PJSIP_AUTH_AUTO_SEND_NEXT!=0
+	    {
+		new_auth_for_req( tdata, sess, auth, NULL);
+	    }
+#	    endif
+
+	} 
+#	if defined(PJSIP_AUTH_QOP_SUPPORT) && \
+	   defined(PJSIP_AUTH_AUTO_SEND_NEXT) && \
+	   (PJSIP_AUTH_QOP_SUPPORT && PJSIP_AUTH_AUTO_SEND_NEXT)
+	else if (auth->qop_value == PJSIP_AUTH_QOP_AUTH) {
+	    /* For qop="auth", we have to re-create the authorization header. 
+	     */
+	    const pjsip_cred_info *cred;
+	    pjsip_authorization_hdr *hauth;
+
+	    cred = auth_find_cred(sess, &auth->realm, 
+				  &auth->last_chal->scheme);
+	    if (!cred) {
+		auth = auth->next;
+		continue;
+	    }
+
+	    status = auth_respond( tdata->pool, auth->last_chal, 
+				   tdata->msg->line.req.uri, 
+				   cred,
+				   &tdata->msg->line.req.method,
+				   sess->pool, auth, &hauth);
+	    if (status != PJ_SUCCESS)
+		return status;
+	    
+	    pjsip_msg_add_hdr(tdata->msg, (pjsip_hdr*)hauth);
+	}
+#	endif	/* PJSIP_AUTH_QOP_SUPPORT && PJSIP_AUTH_AUTO_SEND_NEXT */
+
+	auth = auth->next;
+    }
+
+    return PJ_SUCCESS;
+}
+
+
+/* Process authorization challenge */
+static pj_status_t process_auth( pj_pool_t *req_pool,
+				 const pjsip_www_authenticate_hdr *hchal,
+				 const pjsip_uri *uri,
+				 pjsip_tx_data *tdata,
+				 pjsip_auth_clt_sess *sess,
+				 pjsip_cached_auth *cached_auth,
+				 pjsip_authorization_hdr **h_auth)
+{
+    const pjsip_cred_info *cred;
+    pjsip_authorization_hdr *sent_auth = NULL;
+    pjsip_hdr *hdr;
+    pj_status_t status;
+
+    /* See if we have sent authorization header for this realm */
+    hdr = tdata->msg->hdr.next;
+    while (hdr != &tdata->msg->hdr) {
+	if ((hchal->type == PJSIP_H_WWW_AUTHENTICATE &&
+	     hdr->type == PJSIP_H_AUTHORIZATION) ||
+	    (hchal->type == PJSIP_H_PROXY_AUTHENTICATE &&
+	     hdr->type == PJSIP_H_PROXY_AUTHORIZATION))
+	{
+	    sent_auth = (pjsip_authorization_hdr*) hdr;
+	    if (pj_stricmp(&hchal->challenge.common.realm, 
+			   &sent_auth->credential.common.realm )==0)
+	    {
+		break;
+	    }
+	}
+	hdr = hdr->next;
+    }
+
+    /* If we have sent, see if server rejected because of stale nonce or
+     * other causes.
+     */
+    if (hdr != &tdata->msg->hdr) {
+	if (hchal->challenge.digest.stale == 0) {
+	    /* Our credential is rejected. No point in trying to re-supply
+	     * the same credential.
+	     */
+	    PJ_LOG(4, (THIS_FILE, "Authorization failed for %.*s@%.*s",
+		       sent_auth->credential.digest.username.slen,
+		       sent_auth->credential.digest.username.ptr,
+		       sent_auth->credential.digest.realm.slen,
+		       sent_auth->credential.digest.realm.ptr));
+	    return PJSIP_EFAILEDCREDENTIAL;
+	}
+
+	/* Otherwise remove old, stale authorization header from the mesasge.
+	 * We will supply a new one.
+	 */
+	pj_list_erase(sent_auth);
+    }
+
+    /* Find credential to be used for the challenge. */
+    cred = auth_find_cred( sess, &hchal->challenge.common.realm, 
+			   &hchal->scheme);
+    if (!cred) {
+	const pj_str_t *realm = &hchal->challenge.common.realm;
+	PJ_LOG(4,(THIS_FILE, 
+		  "Unable to set auth for %s: can not find credential for %.*s/%.*s",
+		  tdata->obj_name, 
+		  realm->slen, realm->ptr,
+		  hchal->scheme.slen, hchal->scheme.ptr));
+	return PJSIP_ENOCREDENTIAL;
+    }
+
+    /* Respond to authorization challenge. */
+    status = auth_respond( req_pool, hchal, uri, cred, 
+			   &tdata->msg->line.req.method, 
+			   sess->pool, cached_auth, h_auth);
+    return status;
+}
+
+
+/* Reinitialize outgoing request after 401/407 response is received.
+ * The purpose of this function is:
+ *  - to add a Authorization/Proxy-Authorization header.
+ *  - to put the newly created Authorization/Proxy-Authorization header
+ *    in cached_list.
+ */
+PJ_DEF(pj_status_t) pjsip_auth_clt_reinit_req(	pjsip_auth_clt_sess *sess,
+						const pjsip_rx_data *rdata,
+						pjsip_tx_data *old_request,
+						pjsip_tx_data **new_request )
+{
+    pjsip_tx_data *tdata;
+    const pjsip_hdr *hdr;
+    pjsip_via_hdr *via;
+    pj_status_t status;
+
+    PJ_ASSERT_RETURN(sess && rdata && old_request && new_request,
+		     PJ_EINVAL);
+    PJ_ASSERT_RETURN(sess->pool, PJSIP_ENOTINITIALIZED);
+    PJ_ASSERT_RETURN(rdata->msg_info.msg->type == PJSIP_RESPONSE_MSG,
+		     PJSIP_ENOTRESPONSEMSG);
+    PJ_ASSERT_RETURN(old_request->msg->type == PJSIP_REQUEST_MSG,
+		     PJSIP_ENOTREQUESTMSG);
+    PJ_ASSERT_RETURN(rdata->msg_info.msg->line.status.code == 401 ||
+		     rdata->msg_info.msg->line.status.code == 407,
+		     PJSIP_EINVALIDSTATUS);
+
+    tdata = old_request;
+    
+    /*
+     * Respond to each authentication challenge.
+     */
+    hdr = rdata->msg_info.msg->hdr.next;
+    while (hdr != &rdata->msg_info.msg->hdr) {
+	pjsip_cached_auth *cached_auth;
+	const pjsip_www_authenticate_hdr *hchal;
+	pjsip_authorization_hdr *hauth;
+
+	/* Find WWW-Authenticate or Proxy-Authenticate header. */
+	while (hdr->type != PJSIP_H_WWW_AUTHENTICATE &&
+	       hdr->type != PJSIP_H_PROXY_AUTHENTICATE &&
+	       hdr != &rdata->msg_info.msg->hdr)
+	{
+	    hdr = hdr->next;
+	}
+	if (hdr == &rdata->msg_info.msg->hdr)
+	    break;
+
+	hchal = (const pjsip_www_authenticate_hdr*) hdr;
+
+	/* Find authentication session for this realm, create a new one
+	 * if not present.
+	 */
+	cached_auth = find_cached_auth(sess, &hchal->challenge.common.realm );
+	if (!cached_auth) {
+	    cached_auth = pj_pool_zalloc( sess->pool, sizeof(*cached_auth));
+	    pj_strdup( sess->pool, &cached_auth->realm, &hchal->challenge.common.realm);
+	    cached_auth->is_proxy = (hchal->type == PJSIP_H_PROXY_AUTHENTICATE);
+#	    if (PJSIP_AUTH_HEADER_CACHING)
+	    {
+		pj_list_init(&cached_auth->cached_hdr);
+	    }
+#	    endif
+	    pj_list_insert_before( &sess->cached_auth, cached_auth );
+	}
+
+	/* Create authorization header for this challenge, and update
+	 * authorization session.
+	 */
+	status = process_auth( tdata->pool, hchal, tdata->msg->line.req.uri, 
+			       tdata, sess, cached_auth, &hauth);
+	if (status != PJ_SUCCESS)
+	    return status;
+
+	/* Add to the message. */
+	pjsip_msg_add_hdr(tdata->msg, (pjsip_hdr*)hauth);
+
+	/* Process next header. */
+	hdr = hdr->next;
+    }
+
+
+    /* Remove branch param in Via header. */
+    via = pjsip_msg_find_hdr(tdata->msg, PJSIP_H_VIA, NULL);
+    via->branch_param.slen = 0;
+
+    /* Increment reference counter. */
+    pjsip_tx_data_add_ref(tdata);
+
+    /* Done. */
+    *new_request = tdata;
+    return PJ_SUCCESS;
+
+}
+
diff --git a/pjsip/src/pjsip/sip_auth_server.c b/pjsip/src/pjsip/sip_auth_server.c
new file mode 100644
index 0000000..7bd347b
--- /dev/null
+++ b/pjsip/src/pjsip/sip_auth_server.c
@@ -0,0 +1,234 @@
+/* $Id$ */
+/* 
+ * Copyright (C) 2003-2006 Benny Prijono <benny@prijono.org>
+ *
+ * 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 
+ */
+
+#include <pjsip/sip_auth.h>
+#include <pjsip/sip_auth_parser.h>	/* just to get pjsip_DIGEST_STR */
+#include <pjsip/sip_auth_msg.h>
+#include <pjsip/sip_errno.h>
+#include <pjsip/sip_transport.h>
+#include <pj/string.h>
+#include <pj/assert.h>
+
+
+/* Defined in sip_auth_client.c */
+void pjsip_auth_create_digest( pj_str_t *result,
+			       const pj_str_t *nonce,
+			       const pj_str_t *nc,
+			       const pj_str_t *cnonce,
+			       const pj_str_t *qop,
+			       const pj_str_t *uri,
+			       const pjsip_cred_info *cred_info,
+			       const pj_str_t *method);
+
+
+/*
+ * Initialize server authorization session data structure to serve the 
+ * specified realm and to use lookup_func function to look for the credential 
+ * info. 
+ */
+PJ_DEF(pj_status_t) pjsip_auth_srv_init(  pj_pool_t *pool,
+					  pjsip_auth_srv *auth_srv,
+					  const pj_str_t *realm,
+					  pjsip_auth_lookup_cred *lookup,
+					  unsigned options )
+{
+    PJ_ASSERT_RETURN(pool && auth_srv && realm && lookup, PJ_EINVAL);
+
+    pj_strdup( pool, &auth_srv->realm, realm);
+    auth_srv->lookup = lookup;
+    auth_srv->is_proxy = (options & PJSIP_AUTH_SRV_IS_PROXY);
+
+    return PJ_SUCCESS;
+}
+
+
+/* Verify incoming Authorization/Proxy-Authorization header against the 
+ * specified credential.
+ */
+static pj_status_t pjsip_auth_verify( const pjsip_authorization_hdr *hdr,
+				      const pj_str_t *method,
+				      const pjsip_cred_info *cred_info )
+{
+    if (pj_stricmp(&hdr->scheme, &pjsip_DIGEST_STR) == 0) {
+	char digest_buf[PJSIP_MD5STRLEN];
+	pj_str_t digest;
+	const pjsip_digest_credential *dig = &hdr->credential.digest;
+
+	/* Check that username and realm match. 
+	 * These checks should have been performed before entering this
+	 * function.
+	 */
+	PJ_ASSERT_RETURN(pj_strcmp(&dig->username, &cred_info->username) == 0,
+			 PJ_EINVALIDOP);
+	PJ_ASSERT_RETURN(pj_strcmp(&dig->realm, &cred_info->realm) == 0,
+			 PJ_EINVALIDOP);
+
+	/* Prepare for our digest calculation. */
+	digest.ptr = digest_buf;
+	digest.slen = PJSIP_MD5STRLEN;
+
+	/* Create digest for comparison. */
+	pjsip_auth_create_digest(&digest, 
+				 &hdr->credential.digest.nonce,
+				 &hdr->credential.digest.nc, 
+				 &hdr->credential.digest.cnonce,
+				 &hdr->credential.digest.qop,
+				 &hdr->credential.digest.uri,
+				 cred_info, 
+				 method );
+
+	/* Compare digest. */
+	return (pj_stricmp(&digest, &hdr->credential.digest.response) == 0) ?
+	       PJ_SUCCESS : PJSIP_EAUTHINVALIDDIGEST;
+
+    } else {
+	pj_assert(!"Unsupported authentication scheme");
+	return PJSIP_EINVALIDAUTHSCHEME;
+    }
+}
+
+
+/*
+ * Request the authorization server framework to verify the authorization 
+ * information in the specified request in rdata.
+ */
+PJ_DEF(pj_status_t) pjsip_auth_srv_verify( pjsip_auth_srv *auth_srv,
+					   pjsip_rx_data *rdata,
+					   int *status_code)
+{
+    pjsip_authorization_hdr *h_auth;
+    pjsip_msg *msg = rdata->msg_info.msg;
+    int htype;
+    pj_str_t acc_name;
+    pjsip_cred_info cred_info;
+    pj_status_t status;
+
+    PJ_ASSERT_RETURN(auth_srv && rdata, PJ_EINVAL);
+    PJ_ASSERT_RETURN(msg->type == PJSIP_REQUEST_MSG, PJSIP_ENOTREQUESTMSG);
+
+    htype = auth_srv->is_proxy ? PJSIP_H_PROXY_AUTHORIZATION : 
+				 PJSIP_H_AUTHORIZATION;
+
+    /* Initialize status with 200. */
+    *status_code = 200;
+
+    /* Find authorization header for our realm. */
+    h_auth = (pjsip_authorization_hdr*) pjsip_msg_find_hdr(msg, htype, NULL);
+    while (h_auth) {
+	if (!pj_stricmp(&h_auth->credential.common.realm, &auth_srv->realm))
+	    break;
+
+	h_auth = h_auth->next;
+	if (h_auth == (void*) &msg->hdr) {
+	    h_auth = NULL;
+	    break;
+	}
+
+	h_auth=(pjsip_authorization_hdr*)pjsip_msg_find_hdr(msg,htype,h_auth);
+    }
+
+    if (!h_auth) {
+	*status_code = auth_srv->is_proxy ? 407 : 401;
+	return PJSIP_EAUTHNOAUTH;
+    }
+
+    /* Check authorization scheme. */
+    if (pj_stricmp(&h_auth->scheme, &pjsip_DIGEST_STR) == 0)
+	acc_name = h_auth->credential.digest.username;
+    else {
+	*status_code = auth_srv->is_proxy ? 407 : 401;
+	return PJSIP_EINVALIDAUTHSCHEME;
+    }
+
+    /* Find the credential information for the account. */
+    status = (*auth_srv->lookup)(rdata->tp_info.pool, &auth_srv->realm,
+				 &acc_name, &cred_info);
+    if (status != PJ_SUCCESS) {
+	*status_code = PJSIP_SC_FORBIDDEN;
+	return status;
+    }
+
+    /* Authenticate with the specified credential. */
+    status = pjsip_auth_verify(h_auth, &msg->line.req.method.name, 
+			       &cred_info);
+    if (status != PJ_SUCCESS) {
+	*status_code = PJSIP_SC_FORBIDDEN;
+    }
+    return status;
+}
+
+
+/*
+ * Add authentication challenge headers to the outgoing response in tdata. 
+ * Application may specify its customized nonce and opaque for the challenge, 
+ * or can leave the value to NULL to make the function fills them in with 
+ * random characters.
+ */
+PJ_DEF(pj_status_t) pjsip_auth_srv_challenge(  pjsip_auth_srv *auth_srv,
+					       const pj_str_t *qop,
+					       const pj_str_t *nonce,
+					       const pj_str_t *opaque,
+					       pj_bool_t stale,
+					       pjsip_tx_data *tdata)
+{
+    pjsip_www_authenticate_hdr *hdr;
+    char nonce_buf[16];
+    pj_str_t random;
+
+    PJ_ASSERT_RETURN( auth_srv && tdata, PJ_EINVAL );
+
+    random.ptr = nonce_buf;
+    random.slen = sizeof(nonce_buf);
+
+    /* Create the header. */
+    if (auth_srv->is_proxy)
+	hdr = pjsip_proxy_authenticate_hdr_create(tdata->pool);
+    else
+	hdr = pjsip_www_authenticate_hdr_create(tdata->pool);
+
+    /* Initialize header. 
+     * Note: only support digest authentication now.
+     */
+    hdr->scheme = pjsip_DIGEST_STR;
+    hdr->challenge.digest.algorithm = pjsip_MD5_STR;
+    if (nonce) {
+	pj_strdup(tdata->pool, &hdr->challenge.digest.nonce, nonce);
+    } else {
+	pj_create_random_string(nonce_buf, sizeof(nonce_buf));
+	pj_strdup(tdata->pool, &hdr->challenge.digest.nonce, &random);
+    }
+    if (opaque) {
+	pj_strdup(tdata->pool, &hdr->challenge.digest.opaque, opaque);
+    } else {
+	pj_create_random_string(nonce_buf, sizeof(nonce_buf));
+	pj_strdup(tdata->pool, &hdr->challenge.digest.opaque, &random);
+    }
+    if (qop) {
+	pj_strdup(tdata->pool, &hdr->challenge.digest.qop, qop);
+    } else {
+	hdr->challenge.digest.qop.slen = 0;
+    }
+    pj_strdup(tdata->pool, &hdr->challenge.digest.realm, &auth_srv->realm);
+    hdr->challenge.digest.stale = stale;
+
+    pjsip_msg_add_hdr(tdata->msg, (pjsip_hdr*)hdr);
+
+    return PJ_SUCCESS;
+}
+
diff --git a/pjsip/src/pjsip/sip_errno.c b/pjsip/src/pjsip/sip_errno.c
index 62de365..a2faf1a 100644
--- a/pjsip/src/pjsip/sip_errno.c
+++ b/pjsip/src/pjsip/sip_errno.c
@@ -34,21 +34,28 @@
     { PJSIP_EBUSY,		"Object is busy" },
     { PJSIP_ETYPEEXISTS ,	"Object with the same type exists" },
     { PJSIP_ESHUTDOWN,		"SIP stack shutting down" },
+    { PJSIP_ENOTINITIALIZED,	"SIP object is not initialized." },
 
     /* Messaging errors */
     { PJSIP_EINVALIDMSG,	"Invalid message/syntax error" },
-    { PJSIP_EINVALIDSCHEME,	"Invalid URI scheme" },
-    { PJSIP_EMSGTOOLONG,	"Message too long" },
-    { PJSIP_EPARTIALMSG,	"Partial message" },
-    { PJSIP_EMISSINGREQURI,	"Missing Request-URI" },
-    { PJSIP_EMISSINGHDR,	"Missing required header(s)" },
-    { PJSIP_EMISSINGBODY,	"Missing message body" },
-    { PJSIP_EINVALIDVIA,	"Invalid Via header" },
-    { PJSIP_EMULTIPLEVIA,	"Multiple Via headers in response" },
-    { PJSIP_EINVALIDREQURI,	"Invalid Request URI" },
     { PJSIP_ENOTREQUESTMSG,	"Expecting request message"},
     { PJSIP_ENOTRESPONSEMSG,	"Expecting response message"},
+    { PJSIP_EMSGTOOLONG,	"Message too long" },
+    { PJSIP_EPARTIALMSG,	"Partial message" },
+
+    { PJSIP_EINVALIDSTATUS,	"Invalid status code"},
+
+    { PJSIP_EINVALIDSCHEME,	"Invalid URI scheme" },
+    { PJSIP_EMISSINGREQURI,	"Missing Request-URI" },
+    { PJSIP_EINVALIDREQURI,	"Invalid Request URI" },
+    { PJSIP_EURITOOLONG,	"URI is too long" }, 
+
+    { PJSIP_EMISSINGHDR,	"Missing required header(s)" },
     { PJSIP_EINVALIDHDR,	"Invalid header field"},
+    { PJSIP_EINVALIDVIA,	"Invalid Via header" },
+    { PJSIP_EMULTIPLEVIA,	"Multiple Via headers in response" },
+
+    { PJSIP_EMISSINGBODY,	"Missing message body" },
 
     /* Transport errors */
     { PJSIP_EUNSUPTRANSPORT,	"Unsupported transport"},
@@ -58,6 +65,19 @@
 
     /* Transaction errors */
     { PJSIP_ETSXDESTROYED,	"Transaction has been destroyed"},
+
+    /* Authentication. */
+    { PJSIP_EFAILEDCREDENTIAL,	"Credential failed to authenticate"},
+    { PJSIP_ENOCREDENTIAL,	"No suitable credential"},
+    { PJSIP_EINVALIDALGORITHM,	"Invalid/unsupported digest algorithm" },
+    { PJSIP_EINVALIDQOP,	"Invalid/unsupported digest qop" },
+    { PJSIP_EINVALIDAUTHSCHEME,	"Unsupported authentication scheme" },
+    { PJSIP_EAUTHNOPREVCHAL,	"No previous challenge" },
+    { PJSIP_EAUTHNOAUTH,	"No suitable authorization header" },
+    { PJSIP_EAUTHACCNOTFOUND,	"Account or credential not found" },
+    { PJSIP_EAUTHACCDISABLED,	"Account or credential is disabled" },
+    { PJSIP_EAUTHINVALIDREALM,	"Invalid authorization realm"},
+    { PJSIP_EAUTHINVALIDDIGEST,	"Invalid authorization digest" }
 };
 
 
diff --git a/pjsip/src/pjsip/sip_parser.c b/pjsip/src/pjsip/sip_parser.c
index 5ce5433..f281824 100644
--- a/pjsip/src/pjsip/sip_parser.c
+++ b/pjsip/src/pjsip/sip_parser.c
@@ -1399,7 +1399,7 @@
     parse_hdr_end(ctx->scanner);
 
     if (ctx->rdata)
-        ctx->rdata->msg_info.call_id = hdr->id;
+        ctx->rdata->msg_info.cid = hdr;
 
     return (pjsip_hdr*)hdr;
 }
diff --git a/pjsip/src/pjsip/sip_transaction.c b/pjsip/src/pjsip/sip_transaction.c
index ef59652..61632ed 100644
--- a/pjsip/src/pjsip/sip_transaction.c
+++ b/pjsip/src/pjsip/sip_transaction.c
@@ -269,7 +269,7 @@
     /* Calculate length required. */
     len_required = 9 +			    /* CSeq number */
 		   rdata->msg_info.from->tag.slen +   /* From tag. */
-		   rdata->msg_info.call_id.slen +    /* Call-ID */
+		   rdata->msg_info.cid->id.slen +    /* Call-ID */
 		   host->slen +		    /* Via host. */
 		   9 +			    /* Via port. */
 		   16;			    /* Separator+Allowance. */
@@ -299,8 +299,8 @@
     *p++ = SEPARATOR;
 
     /* Add Call-ID. */
-    len = rdata->msg_info.call_id.slen;
-    pj_memcpy( p, rdata->msg_info.call_id.ptr, len );
+    len = rdata->msg_info.cid->id.slen;
+    pj_memcpy( p, rdata->msg_info.cid->id.ptr, len );
     p += len;
     *p++ = SEPARATOR;
 
@@ -1589,12 +1589,11 @@
 
     ++tsx->retransmit_count;
 
-    status = tsx_send_msg( tsx, tsx->last_tx);
-    if (status != PJ_SUCCESS)
-	return status;
-    
-    /* Restart timer T1. */
+    /* Restart timer T1 first before sending the message to ensure that
+     * retransmission timer is not engaged when loop transport is used.
+     */
     if (resched) {
+	pj_assert(tsx->state != PJSIP_TSX_STATE_CONFIRMED);
 	if (tsx->transport_flag & TSX_HAS_PENDING_TRANSPORT) {
 	    tsx->transport_flag |= TSX_HAS_PENDING_RESCHED;
 	} else {
@@ -1602,6 +1601,11 @@
 	}
     }
 
+    status = tsx_send_msg( tsx, tsx->last_tx);
+    if (status != PJ_SUCCESS) {
+	return status;
+    }
+
     return PJ_SUCCESS;
 }
 
diff --git a/pjsip/src/pjsip/sip_transport.c b/pjsip/src/pjsip/sip_transport.c
index 6c8da28..46d98d9 100644
--- a/pjsip/src/pjsip/sip_transport.c
+++ b/pjsip/src/pjsip/sip_transport.c
@@ -878,7 +878,7 @@
 	}
 
 	/* Perform basic header checking. */
-	if (rdata->msg_info.call_id.ptr == NULL || 
+	if (rdata->msg_info.cid->id.ptr == NULL || 
 	    rdata->msg_info.from == NULL || 
 	    rdata->msg_info.to == NULL || 
 	    rdata->msg_info.via == NULL || 
diff --git a/pjsip/src/pjsip/sip_transport_loop.c b/pjsip/src/pjsip/sip_transport_loop.c
index 33b9f22..98011b9 100644
--- a/pjsip/src/pjsip/sip_transport_loop.c
+++ b/pjsip/src/pjsip/sip_transport_loop.c
@@ -250,7 +250,7 @@
 }
 
 /* Worker thread for loop transport. */
-static int loop_thread(void *arg)
+static int loop_transport_worker_thread(void *arg)
 {
     struct loop_transport *loop = arg;
     struct recv_list r;
@@ -384,7 +384,8 @@
     pj_list_init(&loop->send_list);
 
     /* Create worker thread. */
-    status = pj_thread_create(pool, "loop", &loop_thread, loop, 0, 
+    status = pj_thread_create(pool, "loop", 
+			      &loop_transport_worker_thread, loop, 0, 
 			      PJ_THREAD_SUSPENDED, &loop->thread);
     if (status != PJ_SUCCESS)
 	goto on_error;
diff --git a/pjsip/src/pjsip/sip_util.c b/pjsip/src/pjsip/sip_util.c
index 607b398..f69ad9b 100644
--- a/pjsip/src/pjsip/sip_util.c
+++ b/pjsip/src/pjsip/sip_util.c
@@ -351,6 +351,12 @@
     pjsip_rr_hdr *rr;
     pj_status_t status;
 
+    /* Check arguments. */
+    PJ_ASSERT_RETURN(endpt && rdata && p_tdata, PJ_EINVAL);
+
+    /* Check status code. */
+    PJ_ASSERT_RETURN(st_code >= 100 && st_code <= 699, PJ_EINVAL);
+
     /* rdata must be a request message. */
     req_msg = rdata->msg_info.msg;
     pj_assert(req_msg->type == PJSIP_REQUEST_MSG);
diff --git a/pjsip/src/test-pjsip/transport_test.c b/pjsip/src/test-pjsip/transport_test.c
index b57fedb..74f6d79 100644
--- a/pjsip/src/test-pjsip/transport_test.c
+++ b/pjsip/src/test-pjsip/transport_test.c
@@ -116,7 +116,7 @@
 static pj_bool_t my_on_rx_request(pjsip_rx_data *rdata)
 {
     /* Check that this is our request. */
-    if (pj_strcmp2(&rdata->msg_info.call_id, CALL_ID_HDR) == 0) {
+    if (pj_strcmp2(&rdata->msg_info.cid->id, CALL_ID_HDR) == 0) {
 	/* It is! */
 	/* Send response. */
 	pjsip_tx_data *tdata;
@@ -149,7 +149,7 @@
 
 static pj_bool_t my_on_rx_response(pjsip_rx_data *rdata)
 {
-    if (pj_strcmp2(&rdata->msg_info.call_id, CALL_ID_HDR) == 0) {
+    if (pj_strcmp2(&rdata->msg_info.cid->id, CALL_ID_HDR) == 0) {
 	pj_get_timestamp(&my_recv_time);
 	recv_status = PJ_SUCCESS;
 	return PJ_TRUE;
@@ -334,8 +334,8 @@
 
 static pj_bool_t rt_on_rx_request(pjsip_rx_data *rdata)
 {
-    if (!pj_strncmp(&rdata->msg_info.call_id, &rt_call_id, rt_call_id.slen)) {
-	char *pos = pj_strchr(&rdata->msg_info.call_id, '/');
+    if (!pj_strncmp(&rdata->msg_info.cid->id, &rt_call_id, rt_call_id.slen)) {
+	char *pos = pj_strchr(&rdata->msg_info.cid->id, '/');
 	int thread_id = (*pos - '0');
 
 	pjsip_tx_data *tdata;
@@ -422,8 +422,8 @@
 
 static pj_bool_t rt_on_rx_response(pjsip_rx_data *rdata)
 {
-    if (!pj_strncmp(&rdata->msg_info.call_id, &rt_call_id, rt_call_id.slen)) {
-	char *pos = pj_strchr(&rdata->msg_info.call_id, '/')+1;
+    if (!pj_strncmp(&rdata->msg_info.cid->id, &rt_call_id, rt_call_id.slen)) {
+	char *pos = pj_strchr(&rdata->msg_info.cid->id, '/')+1;
 	int thread_id = (*pos - '0');
 	pj_timestamp recv_time;
 
diff --git a/pjsip/src/test-pjsip/tsx_uac_test.c b/pjsip/src/test-pjsip/tsx_uac_test.c
index 182269c..2ec94e5 100644
--- a/pjsip/src/test-pjsip/tsx_uac_test.c
+++ b/pjsip/src/test-pjsip/tsx_uac_test.c
@@ -398,12 +398,14 @@
 		test_complete = -747;
 	    }
 
-	    /* last_tx is ACK in this case. */
+	    /* last_tx MUST be the INVITE request
+	     * (authorization depends on this behavior)
+	     */
 	    if (tsx->last_tx && tsx->last_tx->msg->line.req.method.id !=
-		PJSIP_ACK_METHOD)
+		PJSIP_INVITE_METHOD)
 	    {
 		PJ_LOG(3,(THIS_FILE, 
-			  "    error: last_tx is not ACK"));
+			  "    error: last_tx is not INVITE"));
 		test_complete = -748;
 	    }
 	}
@@ -461,12 +463,14 @@
 		test_complete = -763;
 	    }
 
-	    /* last_tx is ACK in this case. */
+	    /* last_tx MUST be INVITE. 
+	     * (authorization depends on this behavior)
+	     */
 	    if (tsx->last_tx && tsx->last_tx->msg->line.req.method.id !=
-		PJSIP_ACK_METHOD)
+		PJSIP_INVITE_METHOD)
 	    {
 		PJ_LOG(3,(THIS_FILE, 
-			  "    error: last_tx is not ACK"));
+			  "    error: last_tx is not INVITE"));
 		test_complete = -764;
 	    }
 
diff --git a/pjsip/src/test-pjsip/tsx_uas_test.c b/pjsip/src/test-pjsip/tsx_uas_test.c
index bed6fd0..ecc174c 100644
--- a/pjsip/src/test-pjsip/tsx_uas_test.c
+++ b/pjsip/src/test-pjsip/tsx_uas_test.c
@@ -66,10 +66,19 @@
  **	until it's terminated by user).
  **     Transaction also MUST terminate in T4 seconds.
  **
- ** TEST10_BRANCH_ID
- **	Test where INVITE UAS transaction never receives ACK
- **
  ** TEST11_BRANCH_ID
+ **	Test scenario where transport fails before response is sent (i.e.
+ **	in TRYING state).
+ **
+ ** TEST12_BRANCH_ID
+ **	As above, after provisional response is sent but before final
+ **	response is sent (i.e. in PROCEEDING state).
+ **
+ ** TEST13_BRANCH_ID
+ **	As above, for INVITE, after final response has been sent but before
+ **	ACK is received (i.e. in CONNECTED state).
+ **
+ ** TEST14_BRANCH_ID
  **	When UAS failed to deliver the response with the selected transport,
  **	it should try contacting the client with other transport or begin
  **	RFC 3263 server resolution procedure.
@@ -79,11 +88,6 @@
  **	       upon receiving request retransmission).
  **	    c. COMPLETED state.
  **
- ** TEST12_BRANCH_ID
- **	Variant of previous test, where transaction fails to deliver the 
- **	response using any kind of transports. Transaction should report
- **	transport error to its transaction user.
- **
  **/
 
 static char *TEST1_BRANCH_ID = PJSIP_RFC3261_BRANCH_ID "-UAS-Test1";
@@ -98,6 +102,7 @@
 static char *TEST10_BRANCH_ID = PJSIP_RFC3261_BRANCH_ID "-UAS-Test10";
 static char *TEST11_BRANCH_ID = PJSIP_RFC3261_BRANCH_ID "-UAS-Test11";
 static char *TEST12_BRANCH_ID = PJSIP_RFC3261_BRANCH_ID "-UAS-Test12";
+static char *TEST13_BRANCH_ID = PJSIP_RFC3261_BRANCH_ID "-UAS-Test13";
 
 #define TEST1_STATUS_CODE	200
 #define TEST2_STATUS_CODE	301
@@ -115,6 +120,7 @@
 #define TEST6_RESPONSE_COUNT	3
 #define TEST7_STATUS_CODE	301
 #define TEST8_STATUS_CODE	302
+#define TEST9_STATUS_CODE	301
 
 
 #define TEST4_TITLE "test4: absorbing request retransmission"
@@ -210,10 +216,14 @@
 
     status = pjsip_tsx_send_msg(tsx, r->tdata);
     if (status != PJ_SUCCESS) {
-	PJ_LOG(3,(THIS_FILE,"    error: timer unable to send response"));
+	// Some tests do expect failure!
+	//PJ_LOG(3,(THIS_FILE,"    error: timer unable to send response"));
+	pj_mutex_unlock(tsx->mutex);
 	pjsip_tx_data_dec_ref(r->tdata);
 	return;
     }
+
+    pj_mutex_unlock(tsx->mutex);
 }
 
 /* Utility to send response. */
@@ -234,9 +244,10 @@
 
     status = pjsip_tsx_send_msg(tsx, tdata);
     if (status != PJ_SUCCESS) {
-	app_perror("    error: unable to send response", status);
 	pjsip_tx_data_dec_ref(tdata);
-	test_complete = -197;
+	// Some tests do expect failure!
+	//app_perror("    error: unable to send response", status);
+	//test_complete = -197;
 	return;
     }
 }
@@ -249,6 +260,7 @@
 {
     pj_status_t status;
     pjsip_tx_data *tdata;
+    pj_timer_entry *t;
     struct response *r;
     pj_time_val delay;
 
@@ -268,14 +280,15 @@
     delay.msec = msec_delay;
     pj_time_val_normalize(&delay);
 
-    timer.user_data = r;
-    timer.cb = &send_response_timer;
+    t = pj_pool_zalloc(tdata->pool, sizeof(*t));
+    t->user_data = r;
+    t->cb = &send_response_timer;
 
-    status = pjsip_endpt_schedule_timer(endpt, &timer, &delay);
+    status = pjsip_endpt_schedule_timer(endpt, t, &delay);
     if (status != PJ_SUCCESS) {
+	pjsip_tx_data_dec_ref(tdata);
 	app_perror("    error: unable to schedule timer", status);
 	test_complete = -199;
-	pjsip_tx_data_dec_ref(tdata);
 	return;
     }
 }
@@ -574,6 +587,83 @@
 
 	}
 
+
+    } else
+    if (pj_strcmp2(&tsx->branch, TEST9_BRANCH_ID)==0)  {
+	/*
+	 * TEST9_BRANCH_ID tests that retransmission of INVITE final response
+	 * must cease when ACK is received.
+	 */
+
+	if (tsx->state == PJSIP_TSX_STATE_TERMINATED) {
+
+	    if (test_complete == 0)
+		test_complete = 1;
+
+	    /* Check status code. */
+	    if (tsx->status_code != TEST9_STATUS_CODE) {
+		PJ_LOG(3,(THIS_FILE, "    error: incorrect status code"));
+		test_complete = -160;
+	    }
+	    
+	    /* Previous state. */
+	    if (e->body.tsx_state.prev_state != PJSIP_TSX_STATE_CONFIRMED) {
+		PJ_LOG(3,(THIS_FILE, "    error: incorrect prev_state"));
+		test_complete = -161;
+	    }
+
+	} else if (tsx->state == PJSIP_TSX_STATE_COMPLETED) {
+
+	    /* Check that status code is status_code. */
+	    if (tsx->status_code != TEST9_STATUS_CODE) {
+		PJ_LOG(3,(THIS_FILE, "    error: incorrect status code"));
+		test_complete = -162;
+	    }
+	    
+	    /* Previous state. */
+	    if (e->body.tsx_state.prev_state != PJSIP_TSX_STATE_TRYING) {
+		PJ_LOG(3,(THIS_FILE, "    error: incorrect prev_state"));
+		test_complete = -163;
+	    }
+
+
+	} else if (tsx->state == PJSIP_TSX_STATE_CONFIRMED) {
+
+	    /* Check that status code is status_code. */
+	    if (tsx->status_code != TEST9_STATUS_CODE) {
+		PJ_LOG(3,(THIS_FILE, "    error: incorrect status code"));
+		test_complete = -164;
+	    }
+	    
+	    /* Previous state. */
+	    if (e->body.tsx_state.prev_state != PJSIP_TSX_STATE_COMPLETED) {
+		PJ_LOG(3,(THIS_FILE, "    error: incorrect prev_state"));
+		test_complete = -165;
+	    }
+
+	} else if (tsx->state != PJSIP_TSX_STATE_DESTROYED)  {
+
+	    PJ_LOG(3,(THIS_FILE, "    error: unexpected state"));
+	    test_complete = -166;
+
+	}
+
+
+    } else
+    if (pj_strcmp2(&tsx->branch, TEST10_BRANCH_ID)==0 ||
+	pj_strcmp2(&tsx->branch, TEST11_BRANCH_ID)==0 ||
+	pj_strcmp2(&tsx->branch, TEST12_BRANCH_ID)==0)  
+    {
+	if (tsx->state == PJSIP_TSX_STATE_TERMINATED) {
+	    
+	    if (!test_complete)
+		test_complete = 1;
+
+	    if (tsx->status_code != PJSIP_SC_TSX_TRANSPORT_ERROR) {
+		PJ_LOG(3,(THIS_FILE,"    error: incorrect status code"));
+		test_complete = -170;
+	    }
+	}
     }
 
 }
@@ -612,7 +702,7 @@
 			  TEST1_STATUS_CODE : TEST2_STATUS_CODE;
 
 	if (msg->type == PJSIP_REQUEST_MSG) {
-	    /* On received response, create UAS and respond with final 
+	    /* On received request, create UAS and respond with final 
 	     * response. 
 	     */
 	    pjsip_transaction *tsx;
@@ -652,7 +742,7 @@
 	/* TEST3_BRANCH_ID tests provisional response. */
 
 	if (msg->type == PJSIP_REQUEST_MSG) {
-	    /* On received response, create UAS and respond with provisional
+	    /* On received request, create UAS and respond with provisional
 	     * response, then schedule timer to send final response.
 	     */
 	    pjsip_transaction *tsx;
@@ -705,7 +795,7 @@
 	/* TEST6_BRANCH_ID: retransmit last response in COMPLETED state. */
 
 	if (msg->type == PJSIP_REQUEST_MSG) {
-	    /* On received response, create UAS. */
+	    /* On received request, create UAS. */
 	    pjsip_transaction *tsx;
 
 	    status = pjsip_tsx_create_uas(&tsx_user, rdata, &tsx);
@@ -786,7 +876,7 @@
 	 */
 	if (msg->type == PJSIP_REQUEST_MSG) {
 
-	    /* On received response, create UAS. */
+	    /* On received request, create UAS. */
 	    pjsip_transaction *tsx;
 
 	    status = pjsip_tsx_create_uas(&tsx_user, rdata, &tsx);
@@ -861,7 +951,7 @@
 	}
 	return PJ_TRUE;
 
-    } else if (pj_strcmp2(&branch_param, TEST9_BRANCH_ID)) {
+    } else if (pj_strcmp2(&branch_param, TEST9_BRANCH_ID) == 0) {
 
 	/*
 	 * TEST9_BRANCH_ID tests that the retransmission of INVITE final 
@@ -870,49 +960,36 @@
 	 */
 	if (msg->type == PJSIP_REQUEST_MSG) {
 
-	    /* On received response, create UAS. */
+	    /* On received request, create UAS. */
 	    pjsip_transaction *tsx;
 
 	    status = pjsip_tsx_create_uas(&tsx_user, rdata, &tsx);
 	    if (status != PJ_SUCCESS) {
 		app_perror("    error: unable to create transaction", status);
-		test_complete = -140;
+		test_complete = -150;
 		return PJ_TRUE;
 	    }
 
 	    save_key(tsx);
+	    send_response(rdata, tsx, TEST9_STATUS_CODE);
 
-	    if (pj_strcmp2(&branch_param, TEST7_BRANCH_ID) == 0) {
-
-		send_response(rdata, tsx, TEST7_STATUS_CODE);
-
-	    } else {
-
-		send_response(rdata, tsx, TEST8_STATUS_CODE);
-
-	    }
 
 	} else {
-	    int code;
 
 	    ++recv_count;
 
-	    if (pj_strcmp2(&branch_param, TEST7_BRANCH_ID) == 0)
-		code = TEST7_STATUS_CODE;
-	    else
-		code = TEST8_STATUS_CODE;
+	    if (rdata->msg_info.msg->line.status.code != TEST9_STATUS_CODE) {
+		PJ_LOG(3,(THIS_FILE,"    error: invalid status code"));
+		test_complete = -151;
+	    }
 
 	    if (recv_count==1) {
-		
-		if (rdata->msg_info.msg->line.status.code != code) {
-		    PJ_LOG(3,(THIS_FILE,"    error: invalid status code"));
-		    test_complete = -141;
-		}
 
 		recv_last = rdata->pkt_info.timestamp;
 
-	    } else {
+	    } else if (recv_count < 5) {
 
+		/* Let UAS retransmit some messages before we send ACK. */
 		pj_time_val now;
 		unsigned msec, msec_expected;
 
@@ -930,20 +1007,91 @@
 			      "    error: incorrect retransmission "
 			      "time (%d ms expected, %d ms received",
 			      msec_expected, msec));
-		    test_complete = -142;
-		}
-
-		if (recv_count > 11) {
-		    PJ_LOG(3,(THIS_FILE,"    error: too many responses (%d)",
-					recv_count));
-		    test_complete = -143;
+		    test_complete = -152;
 		}
 
 		recv_last = rdata->pkt_info.timestamp;
+
+	    } else if (recv_count == 5) {
+		pjsip_tx_data *tdata;
+		pjsip_sip_uri *uri;
+		pjsip_via_hdr *via;
+
+		status = pjsip_endpt_create_request_from_hdr(
+			    endpt, &pjsip_ack_method, 
+			    rdata->msg_info.to->uri,
+			    rdata->msg_info.from,
+			    rdata->msg_info.to,
+			    NULL, 
+			    rdata->msg_info.cid,
+			    rdata->msg_info.cseq->cseq,
+			    NULL,
+			    &tdata);
+		if (status != PJ_SUCCESS) {
+		    app_perror("    error: unable to create ACK", status);
+		    test_complete = -153;
+		    return PJ_TRUE;
+		}
+
+		uri=(pjsip_sip_uri*)pjsip_uri_get_uri(tdata->msg->line.req.uri);
+		uri->transport_param = pj_str("loop-dgram");
+
+		via = pjsip_msg_find_hdr(tdata->msg, PJSIP_H_VIA, NULL);
+		via->branch_param = pj_str(TEST9_BRANCH_ID);
+
+		status = pjsip_endpt_send_request_stateless(endpt, tdata,
+							    NULL, NULL);
+		if (status != PJ_SUCCESS) {
+		    app_perror("    error: unable to send ACK", status);
+		    test_complete = -154;
+		}
+
+	    } else {
+		PJ_LOG(3,(THIS_FILE,"    error: too many responses (%d)",
+				    recv_count));
+		test_complete = -155;
 	    }
 
 	}
 	return PJ_TRUE;
+
+    } else if (pj_strcmp2(&branch_param, TEST10_BRANCH_ID) == 0 ||
+	       pj_strcmp2(&branch_param, TEST11_BRANCH_ID) == 0 ||
+	       pj_strcmp2(&branch_param, TEST12_BRANCH_ID) == 0) 
+    {
+	int test_num, code1, code2;
+
+	if (pj_strcmp2(&branch_param, TEST10_BRANCH_ID) == 0)
+	    test_num=10, code1 = 100, code2 = 0;
+	else if (pj_strcmp2(&branch_param, TEST11_BRANCH_ID) == 0)
+	    test_num=11, code1 = 100, code2 = 200;
+	else
+	    test_num=12, code1 = 200, code2 = 0;
+
+	if (rdata->msg_info.msg->type == PJSIP_REQUEST_MSG) {
+
+	    /* On received response, create UAS. */
+	    pjsip_transaction *tsx;
+
+	    status = pjsip_tsx_create_uas(&tsx_user, rdata, &tsx);
+	    if (status != PJ_SUCCESS) {
+		app_perror("    error: unable to create transaction", status);
+		test_complete = -150;
+		return PJ_TRUE;
+	    }
+
+	    save_key(tsx);
+	    
+	    schedule_send_response(rdata, &tsx_key, code1, 1000);
+
+	    if (code2)
+		schedule_send_response(rdata, &tsx_key, code2, 2000);
+
+	} else {
+
+	}
+
+	return PJ_TRUE;
     }
 
     return PJ_FALSE;
@@ -1212,6 +1360,113 @@
 }
 
 
+/*****************************************************************************
+ **
+ ** TEST9_BRANCH_ID: retransmission of non-2xx INVITE final response must 
+ ** cease when ACK is received
+ **
+ *****************************************************************************
+ */
+static int tsx_ack_test(void)
+{
+    int status;
+
+    PJ_LOG(3,(THIS_FILE,
+	      "  test9: receiving ACK for non-2xx final response"));
+
+    status = perform_test("sip:129.0.0.1;transport=loop-dgram",
+		          "sip:129.0.0.1;transport=loop-dgram",
+			  TEST9_BRANCH_ID,
+			  20, /* allow 5 retransmissions */
+			  &pjsip_invite_method, 1, 0, 0);
+    if (status != 0)
+	return status;
+
+
+    return 0;
+}
+
+
+
+/*****************************************************************************
+ **
+ ** TEST10_BRANCH_ID: test transport failure in TRYING state.
+ ** TEST11_BRANCH_ID: test transport failure in PROCEEDING state.
+ ** TEST12_BRANCH_ID: test transport failure in CONNECTED state.
+ ** TEST13_BRANCH_ID: test transport failure in CONFIRMED state.
+ **
+ *****************************************************************************
+ */
+static int tsx_transport_failure_test(void)
+{
+    struct test_desc
+    {
+	int transport_delay;
+	int fail_delay;
+	char *branch_id;
+	char *title;
+    } tests[] = 
+    {
+	{ 0,  10,   TEST10_BRANCH_ID, "test10: failed transport in TRYING state (no delay)" },
+	{ 50, 10,   TEST10_BRANCH_ID, "test10: failed transport in TRYING state (50 ms delay)" },
+	{ 0,  1500, TEST11_BRANCH_ID, "test11: failed transport in PROCEEDING state (no delay)" },
+	{ 50, 1500, TEST11_BRANCH_ID, "test11: failed transport in PROCEEDING state (50 ms delay)" },
+	{ 0,  2500, TEST12_BRANCH_ID, "test12: failed transport in COMPLETED state (no delay)" },
+	{ 50, 2500, TEST12_BRANCH_ID, "test12: failed transport in COMPLETED state (50 ms delay)" },
+    };
+    int i, status;
+
+    for (i=0; i<PJ_ARRAY_SIZE(tests); ++i) {
+	pj_time_val fail_time, end_test, now;
+
+	PJ_LOG(3,(THIS_FILE, "  %s", tests[i].title));
+	pjsip_loop_set_failure(loop, 0, NULL);
+	pjsip_loop_set_delay(loop, tests[i].transport_delay);
+
+	status = perform_test("sip:129.0.0.1;transport=loop-dgram",
+			      "sip:129.0.0.1;transport=loop-dgram",
+			      tests[i].branch_id,
+			      0,
+			      &pjsip_invite_method, 1, 0, 1);
+	if (status && status != TEST_TIMEOUT_ERROR)
+	    return status;
+	if (!status) {
+	    PJ_LOG(3,(THIS_FILE, "   error: expecting timeout"));
+	    return -40;
+	}
+
+	pj_gettimeofday(&fail_time);
+	fail_time.msec += tests[i].fail_delay;
+	pj_time_val_normalize(&fail_time);
+
+	do {
+	    pj_time_val interval = { 0, 1 };
+	    pj_gettimeofday(&now);
+	    pjsip_endpt_handle_events(endpt, &interval);
+	} while (PJ_TIME_VAL_LT(now, fail_time));
+
+	pjsip_loop_set_failure(loop, 1, NULL);
+
+	end_test = now;
+	end_test.sec += 5;
+
+	do {
+	    pj_time_val interval = { 0, 1 };
+	    pj_gettimeofday(&now);
+	    pjsip_endpt_handle_events(endpt, &interval);
+	} while (!test_complete && PJ_TIME_VAL_LT(now, end_test));
+
+	if (test_complete == 0) {
+	    PJ_LOG(3,(THIS_FILE, "   error: test has timed out"));
+	    return -41;
+	}
+
+	if (test_complete != 1)
+	    return test_complete;
+    }
+
+    return 0;
+}
 
 /*****************************************************************************
  **
@@ -1244,7 +1499,6 @@
 	return -4;
     }
 
-#if 0
     /* TEST1_BRANCH_ID: Basic 2xx final response. 
      * TEST2_BRANCH_ID: Basic non-2xx final response. 
      */
@@ -1293,7 +1547,22 @@
     if (status != 0)
 	return status;
 
-#endif
+    /* TEST9_BRANCH_ID: retransmission of non-2xx INVITE final response must 
+     * cease when ACK is received
+     */
+    status = tsx_ack_test();
+    if (status != 0)
+	return status;
+
+    /* TEST10_BRANCH_ID: test transport failure in TRYING state.
+     * TEST11_BRANCH_ID: test transport failure in PROCEEDING state.
+     * TEST12_BRANCH_ID: test transport failure in CONNECTED state.
+     * TEST13_BRANCH_ID: test transport failure in CONFIRMED state.
+     */
+    status = tsx_transport_failure_test();
+    if (status != 0)
+	return status;
+
 
     pjsip_transport_dec_ref(loop);
     return 0;
diff --git a/pjsip/src/test-pjsip/txdata_test.c b/pjsip/src/test-pjsip/txdata_test.c
index def46b5..327ea08 100644
--- a/pjsip/src/test-pjsip/txdata_test.c
+++ b/pjsip/src/test-pjsip/txdata_test.c
@@ -154,7 +154,7 @@
      * We should never do this in real application, as there are many
      * many more fields need to be initialized!!
      */
-    dummy_rdata.msg_info.call_id = (HFIND(invite->msg, cid, CALL_ID))->id;
+    dummy_rdata.msg_info.cid = HFIND(invite->msg, cid, CALL_ID);
     dummy_rdata.msg_info.clen = NULL;
     dummy_rdata.msg_info.cseq = HFIND(invite->msg, cseq, CSEQ);
     dummy_rdata.msg_info.ctype = NULL;