Initial implementation of invite session abstraction, and updated pjsua for the new framework

git-svn-id: https://svn.pjsip.org/repos/pjproject/trunk@139 74dad513-b988-da41-8d7b-12977e46ad98
diff --git a/pjsip/build/pjsip_ua.dsp b/pjsip/build/pjsip_ua.dsp
index c598607..669a805 100644
--- a/pjsip/build/pjsip_ua.dsp
+++ b/pjsip/build/pjsip_ua.dsp
@@ -41,7 +41,7 @@
 # PROP Intermediate_Dir ".\output\pjsip_ua_vc6_Release"

 # PROP Target_Dir ""

 # ADD BASE CPP /nologo /W3 /GX /O2 /D "WIN32" /D "NDEBUG" /D "_MBCS" /D "_LIB" /YX /FD /c

-# ADD CPP /nologo /MD /W4 /Zi /O2 /I "../src" /I "../../pjlib/src" /D "WIN32" /D "NDEBUG" /D "_MBCS" /D "_LIB" /FR /FD /c

+# ADD CPP /nologo /MD /W4 /Zi /O2 /I "../include" /I "../../pjlib/include" /I "../../pjlib-util/include" /I "../../pjmedia/include" /D "NDEBUG" /D PJ_WIN32=1 /D PJ_M_I386=1 /D "WIN32" /D "_MBCS" /D "_LIB" /FR /FD /c

 # SUBTRACT CPP /YX

 # ADD BASE RSC /l 0x409 /d "NDEBUG"

 # ADD RSC /l 0x409 /d "NDEBUG"

@@ -65,7 +65,7 @@
 # PROP Intermediate_Dir ".\output\pjsip_ua_vc6_Debug"

 # PROP Target_Dir ""

 # ADD BASE CPP /nologo /W3 /Gm /GX /ZI /Od /D "WIN32" /D "_DEBUG" /D "_MBCS" /D "_LIB" /YX /FD /GZ /c

-# ADD CPP /nologo /MTd /W4 /Gm /GX /ZI /Od /I "../src" /I "../../pjlib/src" /D "WIN32" /D "_DEBUG" /D "_MBCS" /D "_LIB" /FR /FD /GZ /c

+# ADD CPP /nologo /MTd /W4 /Gm /GX /ZI /Od /I "../include" /I "../../pjlib/include" /I "../../pjlib-util/include" /I "../../pjmedia/include" /D "_DEBUG" /D PJ_WIN32=1 /D PJ_M_I386=1 /D "WIN32" /D "_MBCS" /D "_LIB" /FR /FD /GZ /c

 # SUBTRACT CPP /YX

 # ADD BASE RSC /l 0x409 /d "_DEBUG"

 # ADD RSC /l 0x409 /d "_DEBUG"

@@ -87,6 +87,10 @@
 # PROP Default_Filter "cpp;c;cxx;rc;def;r;odl;idl;hpj;bat"

 # Begin Source File

 

+SOURCE="..\src\pjsip-ua\sip_inv.c"

+# End Source File

+# Begin Source File

+

 SOURCE="..\src\pjsip-ua\sip_reg.c"

 

 !IF  "$(CFG)" == "pjsip_ua - Win32 Release"

@@ -104,6 +108,10 @@
 # PROP Default_Filter "h;hpp;hxx;hm;inl"

 # Begin Source File

 

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

+# End Source File

+# Begin Source File

+

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

 # End Source File

 # End Group

diff --git a/pjsip/build/pjsua.dsp b/pjsip/build/pjsua.dsp
index e355d9a..7b79266 100644
--- a/pjsip/build/pjsua.dsp
+++ b/pjsip/build/pjsua.dsp
@@ -42,7 +42,7 @@
 # PROP Ignore_Export_Lib 0

 # PROP Target_Dir ""

 # ADD BASE CPP /nologo /W3 /GX /O2 /D "WIN32" /D "NDEBUG" /D "_CONSOLE" /D "_MBCS" /YX /FD /c

-# ADD CPP /nologo /MD /W4 /GX /Zi /O2 /I "../src" /I "../../pjlib/src" /I "../../pjmedia/src" /I "../../pjsdp/src" /D "WIN32" /D "NDEBUG" /D "_CONSOLE" /D "_MBCS" /FR /FD /c

+# ADD CPP /nologo /MD /W4 /GX /Zi /O2 /I "../include" /I "../../pjlib/include" /I "../../pjlib-util/include" /I "../../pjmedia/include" /D "NDEBUG" /D PJ_WIN32=1 /D PJ_M_I386=1 /D "WIN32" /D "_CONSOLE" /D "_MBCS" /FR /FD /c

 # SUBTRACT CPP /YX

 # ADD BASE RSC /l 0x409 /d "NDEBUG"

 # ADD RSC /l 0x409 /d "NDEBUG"

@@ -68,7 +68,7 @@
 # PROP Ignore_Export_Lib 0

 # PROP Target_Dir ""

 # ADD BASE CPP /nologo /W3 /Gm /GX /ZI /Od /D "WIN32" /D "_DEBUG" /D "_CONSOLE" /D "_MBCS" /YX /FD /GZ /c

-# ADD CPP /nologo /MTd /W4 /Gm /GX /ZI /Od /I "../src" /I "../../pjlib/src" /I "../../pjmedia/src" /I "../../pjsdp/src" /D "WIN32" /D "_DEBUG" /D "_CONSOLE" /D "_MBCS" /FR /FD /GZ /c

+# ADD CPP /nologo /MTd /W4 /Gm /GX /ZI /Od /I "../include" /I "../../pjlib/include" /I "../../pjlib-util/include" /I "../../pjmedia/include" /D "_DEBUG" /D PJ_WIN32=1 /D PJ_M_I386=1 /D "WIN32" /D "_CONSOLE" /D "_MBCS" /FR /FD /GZ /c

 # SUBTRACT CPP /YX

 # ADD BASE RSC /l 0x409 /d "_DEBUG"

 # ADD RSC /l 0x409 /d "_DEBUG"

@@ -95,6 +95,15 @@
 # Begin Source File

 

 SOURCE=..\src\pjsua\main.c

+

+!IF  "$(CFG)" == "pjsua - Win32 Release"

+

+!ELSEIF  "$(CFG)" == "pjsua - Win32 Debug"

+

+# PROP Exclude_From_Build 1

+

+!ENDIF 

+

 # End Source File

 # Begin Source File

 

diff --git a/pjsip/include/pjsip-ua/sip_inv.h b/pjsip/include/pjsip-ua/sip_inv.h
new file mode 100644
index 0000000..26a3897
--- /dev/null
+++ b/pjsip/include/pjsip-ua/sip_inv.h
@@ -0,0 +1,507 @@
+/* $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 
+ */
+#ifndef __SIP_INVITE_SESSION_H__
+#define __SIP_INVITE_SESSION_H__
+
+#include <pjsip/sip_dialog.h>
+#include <pjmedia/sdp_neg.h>
+
+typedef enum pjsip_inv_state pjsip_inv_state;
+typedef struct pjsip_inv_session pjsip_inv_session;
+typedef struct pjsip_inv_callback pjsip_inv_callback;
+
+/**
+ * This enumeration describes invite session state.
+ */
+enum pjsip_inv_state
+{
+    PJSIP_INV_STATE_NULL,	    /**< Before INVITE is sent or received  */
+    PJSIP_INV_STATE_CALLING,	    /**< After INVITE is sent		    */
+    PJSIP_INV_STATE_INCOMING,	    /**< After INVITE is received.	    */
+    PJSIP_INV_STATE_EARLY,	    /**< After response with To tag.	    */
+    PJSIP_INV_STATE_CONNECTING,	    /**< After 2xx is sent/received.	    */
+    PJSIP_INV_STATE_CONFIRMED,	    /**< After ACK is sent/received.	    */
+    PJSIP_INV_STATE_DISCONNECTED,   /**< Session is terminated.		    */
+    PJSIP_INV_STATE_TERMINATED,	    /**< Session will be destroyed soon.    */
+};
+
+/**
+ * This structure contains callbacks to be registered by application to 
+ * receieve notifications from the framework about various events in
+ * the invite session.
+ */
+struct pjsip_inv_callback
+{
+    /**
+     * This callback is called when the invite sesion state has changed. 
+     * Application should inspect the session state (inv_sess->state) to get 
+     * the current state of the session.
+     *
+     * This callback is mandatory.
+     *
+     * @param inv	The invite session.
+     * @param e		The event which has caused the invite session's 
+     *			state to change.
+     */
+    void (*on_state_changed)(pjsip_inv_session *inv, pjsip_event *e);
+
+
+    /**
+     * This callback is called when the invite usage module has created 
+     * a new dialog and invite because of forked outgoing request.
+     *
+     * This callback is mandatory.
+     *
+     * @param inv	The new invite session.
+     * @param e		The event which has caused the dialog to fork.
+     *			The type of this event can be either
+     *			PJSIP_EVENT_RX_MSG or PJSIP_EVENT_RX_200_MSG.
+     */
+    void (*on_new_session)(pjsip_inv_session *inv, pjsip_event *e);
+
+    /**
+     * This callback is called whenever any transactions within the session
+     * has changed their state. Application MAY implement this callback, 
+     * e.g. to monitor the progress of an outgoing request.
+     *
+     * This callback is optional.
+     *
+     * @param inv	The invite session.
+     * @param tsx	The transaction, which state has changed.
+     * @param e		The event which has caused the transation state's
+     *			to change.
+     */
+    void (*on_tsx_state_changed)(pjsip_inv_session *inv,
+				 pjsip_transaction *tsx,
+				 pjsip_event *e);
+
+    /**
+     * This callback is called when the invite session has received 
+     * new offer from peer. Application can inspect the remote offer 
+     * by calling negotiator's pjmedia_sdp_neg_get_neg_remote(), and 
+     * optionally specify a modified answer. 
+     *
+     * This callback is optional. When it's not specified, the default 
+     * behavior is nothing. After calling this callback, the negotiator 
+     * will negotiate remote offer with session's initial capability.
+     *
+     * @param inv	The invite session.
+     */
+    void (*on_rx_offer)(pjsip_inv_session *inv);
+
+    /**
+     * This callback is called after SDP offer/answer session has completed.
+     * The status argument specifies the status of the offer/answer, 
+     * as returned by pjmedia_sdp_neg_negotiate().
+     * 
+     * This callback is optional (from the point of view of the framework), 
+     * but all useful applications normally need to implement this callback.
+     *
+     * @param inv	The invite session.
+     * @param status	The negotiation status.
+     */
+    void (*on_media_update)(pjsip_inv_session *inv_ses, 
+			    pj_status_t status);
+
+};
+
+
+/**
+ * This enumeration shows various options that can be applied to a session. 
+ * The bitmask combination of these options need to be specified when 
+ * creating a session. After the dialog is established (including early), 
+ * the options member of #pjsip_inv_session shows which capabilities are 
+ * common in both endpoints.
+ */
+enum pjsip_inv_option
+{	
+    /** 
+     * Indicate support for reliable provisional response extension 
+     */
+    PJSIP_INV_SUPPORT_100REL	= 1,
+
+    /** 
+     * Indicate support for session timer extension. 
+     */
+    PJSIP_INV_SUPPORT_TIMER	= 2,
+
+    /** 
+     * Indicate support for UPDATE method. This is automatically implied
+     * when creating outgoing dialog. After the dialog is established,
+     * the options member of #pjsip_inv_session shows whether peer supports
+     * this method as well.
+     */
+    PJSIP_INV_SUPPORT_UPDATE	= 4,
+
+    /** 
+     * Require reliable provisional response extension. 
+     */
+    PJSIP_INV_REQUIRE_100REL	= 32,
+
+    /**  
+     * Require session timer extension. 
+     */
+    PJSIP_INV_REQUIRE_TIMER	= 64,
+
+};
+
+
+/**
+ * This structure describes the invite session.
+ */
+struct pjsip_inv_session
+{
+    pj_pool_t		*pool;
+    pjsip_inv_state	 state;
+    pjsip_dialog	*dlg;
+    pjsip_role_e	 role;
+    unsigned		 options;
+    pjmedia_sdp_neg	*neg;
+    pjsip_transaction	*invite_tsx;
+    void		*mod_data[PJSIP_MAX_MODULE];
+};
+
+
+/**
+ * Initialize the invite usage module and register it to the endpoint. 
+ * The callback argument contains pointer to functions to be called on 
+ * occurences of events in invite sessions.
+ *
+ * @param endpt		The endpoint instance.
+ * @param app_module	Application module.
+ * @param callback	Callback structure.
+ *
+ * @return		PJ_SUCCESS on success, or the appropriate error code.
+ */
+PJ_DECL(pj_status_t) pjsip_inv_usage_init(pjsip_endpoint *endpt,
+					  pjsip_module *app_module,
+					  const pjsip_inv_callback *cb);
+
+/**
+ * Get the INVITE usage module instance.
+ *
+ * @return		PJ_SUCCESS on success, or the appropriate error code.
+ */
+PJ_DECL(pjsip_module*) pjsip_inv_usage_instance(void);
+
+
+
+/**
+ * Create UAC invite session for the specified dialog in dlg. 
+ *
+ * @param dlg		The dialog which will be used by this invite session.
+ * @param local_sdp	If application has determined its media capability, 
+ *			it can specify the SDP here. Otherwise it can leave 
+ *			this to NULL, to let remote UAS specifies an offer.
+ * @param options	The options argument is bitmask combination of SIP 
+ *			features in pjsip_inv_options enumeration.
+ * @param p_inv		On successful return, the invite session will be put 
+ *			in this argument.
+ *
+ * @return		The function will return PJ_SUCCESS if it can create
+ *			the session. Otherwise the appropriate error status 
+ *			will be returned on failure.
+ */
+PJ_DECL(pj_status_t) pjsip_inv_create_uac(pjsip_dialog *dlg,
+					  const pjmedia_sdp_session *local_sdp,
+					  unsigned options,
+					  pjsip_inv_session **p_inv);
+
+
+/**
+ * Application SHOULD call this function upon receiving the initial INVITE 
+ * request in rdata before creating the invite session (or even dialog), 
+ * to verify that the invite session can handle the INVITE request. 
+ * This function verifies that local endpoint is capable to handle required 
+ * SIP extensions in the request (i.e. Require header) and also the media, 
+ * if media description is present in the request.
+ *
+ * @param rdata		The incoming INVITE request.
+ *
+ * @param options	Upon calling this function, the options argument 
+ *			MUST contain the desired SIP extensions to be 
+ *			applied to the session. Upon return, this argument 
+ *			will contain the SIP extension that will be applied 
+ *			to the session, after considering the Supported, 
+ *			Require, and Allow headers in the request.
+ *
+ * @param sdp		If local media capability has been determined, 
+ *			and if application wishes to verify that it can 
+ *			handle the media offer in the incoming INVITE 
+ *			request, it SHOULD specify its local media capability
+ *			in this argument. 
+ *			If it is not specified, media verification will not
+ *			be performed by this function.
+ *
+ * @param dlg		If tdata is not NULL, application needs to specify
+ *			how to create the response. Either dlg or endpt
+ *			argument MUST be specified, with dlg argument takes
+ *			precedence when both are specified.
+ *
+ *			If a dialog has been created prior to calling this 
+ *			function, then it MUST be specified in dlg argument. 
+ *			Otherwise application MUST specify the endpt argument
+ *			(this is useful e.g. when application wants to send 
+ *			the response statelessly).
+ *
+ * @param endpt		If tdata is not NULL, application needs to specify
+ *			how to create the response. Either dlg or endpt
+ *			argument MUST be specified, with dlg argument takes
+ *			precedence when both are specified.
+ *
+ * @param tdata		If this argument is not NULL, this function will 
+ *			create the appropriate non-2xx final response message
+ *			when the verification fails.
+ *
+ * @return		If everything has been negotiated successfully, 
+ *			the function will return PJ_SUCCESS. Otherwise it 
+ *			will return the reason of the failure as the return
+ *			code.
+ *
+ *			This function is capable to create the appropriate 
+ *			response message when the verification has failed. 
+ *			If tdata is specified, then a non-2xx final response
+ *			will be created and put in this argument upon return,
+ *			when the verification has failed. 
+ *
+ *			If a dialog has been created prior to calling this 
+ *			function, then it MUST be specified in dlg argument. 
+ *			Otherwise application MUST specify the endpt argument
+ *			(this is useful e.g. when application wants to send 
+ *			the response statelessly).
+ */
+PJ_DECL(pj_status_t) pjsip_inv_verify_request(	pjsip_rx_data *rdata,
+						unsigned *options,
+						const pjmedia_sdp_session *sdp,
+						pjsip_dialog *dlg,
+						pjsip_endpoint *endpt,
+						pjsip_tx_data **tdata);
+
+
+/**
+ * Create UAS invite session for the specified dialog in dlg. Application 
+ * SHOULD call the verification function before calling this function, 
+ * to ensure that it can create the session successfully.
+ *
+ * @param dlg		The dialog to be used.
+ * @param rdata		Application MUST specify the received INVITE request 
+ *			in rdata. The invite session needs to inspect the 
+ *			received request to see if the request contains 
+ *			features that it supports.
+ * @param sdp		If application has determined its media capability, 
+ *			it can specify this capability in this argument. 
+ *			If SDP is received in the initial INVITE, the UAS 
+ *			capability specified in this argument doesn't have to
+ *			match the received offer; the SDP negotiator is able 
+ *			to rearrange the media lines in the answer so that it
+ *			matches the offer. 
+ * @param options	The options argument is bitmask combination of SIP 
+ *			features in pjsip_inv_options enumeration.
+ * @param p_inv		Pointer to receive the newly created invite session.
+ *
+ * @return		On successful, the invite session will be put in 
+ *			p_inv argument and the function will return PJ_SUCCESS.
+ *			Otherwise the appropriate error status will be returned 
+ *			on failure.
+ */
+PJ_DECL(pj_status_t) pjsip_inv_create_uas(pjsip_dialog *dlg,
+					  pjsip_rx_data *rdata,
+					  const pjmedia_sdp_session *local_sdp,
+					  unsigned options,
+					  pjsip_inv_session **p_inv);
+
+
+/**
+ * Create the initial INVITE request for this session. This function can only 
+ * be called for UAC session. If local media capability is specified when 
+ * the invite session was created, then this function will put an SDP offer 
+ * in the outgoing INVITE request. Otherwise the outgoing request will not 
+ * contain SDP body.
+ *
+ * @param inv		The UAC invite session.
+ * @param p_tdata	The initial INVITE request will be put in this 
+ *			argument if it can be created successfully.
+ *
+ * @return		PJ_SUCCESS if the INVITE request can be created.
+ */
+PJ_DECL(pj_status_t) pjsip_inv_invite( pjsip_inv_session *inv,
+				       pjsip_tx_data **p_tdata );
+
+
+/**
+ * Create a response message to the initial INVITE request. This function
+ * can only be called for the initial INVITE request, as subsequent
+ * re-INVITE request will be answered automatically.
+ *
+ * @param inv		The UAS invite session.
+ * @param st_code	The st_code contains the status code to be sent, 
+ *			which may be a provisional or final response. 
+ * @param st_text	If custom status text is desired, application can 
+ *			specify the text in st_text; otherwise if this 
+ *			argument is NULL, default status text will be used.
+ * @param local_sdp	If application has specified its media capability
+ *			during creation of UAS invite session, the local_sdp
+ *			argument MUST be NULL. This is because application 
+ *			can not perform more than one SDP offer/answer session
+ *			in a single INVITE transaction.
+ *			If application has not specified its media capability 
+ *			during creation of UAS invite session, it MAY or MUST
+ *			specify its capability in local_sdp argument, 
+ *			depending whether st_code indicates a 2xx final 
+ *			response.
+ * @param p_tdata	Pointer to receive the response message created by
+ *			this function.
+ *
+ * @return		PJ_SUCCESS if response message was created
+ *			successfully.
+ */
+PJ_DECL(pj_status_t) pjsip_inv_answer(	pjsip_inv_session *inv,
+					int st_code,
+					const pj_str_t *st_text,
+					const pjmedia_sdp_session *local_sdp,
+					pjsip_tx_data **p_tdata );
+
+
+/**
+ * Create a SIP message to initiate invite session termination. Depending on 
+ * the state of the session, this function may return CANCEL request, 
+ * a non-2xx final response, or a BYE request. If the session has not answered
+ * the incoming INVITE, this function creates the non-2xx final response with 
+ * the specified status code in st_code and optional status text in st_text.
+ *
+ * @param inv		The invite session.
+ * @param st_code	Status code to be used for terminating the session.
+ * @param st_text	Optional status text.
+ * @param p_tdata	Pointer to receive the message to be created.
+ *
+ * @return		PJ_SUCCESS if termination message can be created.
+ */
+PJ_DECL(pj_status_t) pjsip_inv_end_session( pjsip_inv_session *inv,
+					    int st_code,
+					    const pj_str_t *st_text,
+					    pjsip_tx_data **p_tdata );
+
+
+
+/**
+ * Create a re-INVITE request. 
+ *
+ * @param inv		The invite session.
+ * @param new_contact	If application wants to update its local contact and
+ *			inform peer to perform target refresh with a new 
+ *			contact, it can specify the new contact in this 
+ *			argument; otherwise this argument must be NULL.
+ * @param new_offer	Application MAY initiate a new SDP offer/answer 
+ *			session in the request when there is no pending 
+ *			answer to be sent or received. It can detect this 
+ *			condition by observing the state of the SDP 
+ *			negotiator of the invite session. If new offer 
+ *			should be sent to remote, the offer must be specified
+ *			in this argument, otherwise it must be NULL.
+ * @param p_tdata	Pointer to receive the re-INVITE request message to
+ *			be created.
+ *
+ * @return		PJ_SUCCESS if a re-INVITE request with the specified
+ *			characteristics (e.g. to contain new offer) can be 
+ *			created.
+ */
+PJ_DECL(pj_status_t) pjsip_inv_reinvite(pjsip_inv_session *inv,
+					const pj_str_t *new_contact,
+					const pjmedia_sdp_session *new_offer,
+					pjsip_tx_data **p_tdata );
+
+
+
+/**
+ * Create an UPDATE request. 
+ *
+ * @param inv		The invite session.
+ * @param new_contact	If application wants to update its local contact
+ *			and inform peer to perform target refresh with a new
+ *			contact, it can specify the new contact in this 
+ *			argument; otherwise this argument must be NULL.
+ * @param new_offer	Application MAY initiate a new SDP offer/answer 
+ *			session in the request when there is no pending answer
+ *			to be sent or received. It can detect this condition
+ *			by observing the state of the SDP negotiator of the 
+ *			invite session. If new offer should be sent to remote,
+ *			the offer must be specified in this argument; otherwise
+ *			this argument must be NULL.
+ * @param p_tdata	Pointer to receive the UPDATE request message to
+ *			be created.
+ *
+ * @return		PJ_SUCCESS if a UPDATE request with the specified
+ *			characteristics (e.g. to contain new offer) can be 
+ *			created.
+ */
+PJ_DECL(pj_status_t) pjsip_inv_update (	pjsip_inv_session *inv,
+					const pj_str_t *new_contact,
+					const pjmedia_sdp_session *new_offer,
+					pjsip_tx_data **p_tdata );
+
+
+/**
+ * Send request or response message in tdata. 
+ *
+ * @param inv		The invite session.
+ * @param tdata		The message to be sent.
+ * @param token		The token is an arbitrary application data that 
+ *			will be put in the transaction's mod_data array, 
+ *			at application module's index. Application can inspect
+ *			this value when the framework reports the completion
+ *			of the transaction that sends this message.
+ *
+ * @return		PJ_SUCCESS if transaction can be initiated 
+ *			successfully to send this message. Note that the
+ *			actual final state of the transaction itself will
+ *			be reported later, in on_tsx_state_changed()
+ *			callback.
+ */
+PJ_DECL(pj_status_t) pjsip_inv_send_msg(pjsip_inv_session *inv,
+					pjsip_tx_data *tdata,
+					void *token );
+
+
+
+/**
+ * Get the invite session for the dialog, if any.
+ *
+ * @param dlg		The dialog which invite session is being queried.
+ *
+ * @return		The invite session instance which has been 
+ *			associated with this dialog, or NULL.
+ */
+PJ_DECL(pjsip_inv_session*) pjsip_dlg_get_inv_session(pjsip_dialog *dlg);
+
+/**
+ * Get the invite session instance associated with transaction tsx, if any.
+ *
+ * @param tsx		The transaction, which invite session is being 
+ *			queried.
+ *
+ * @return		The invite session instance which has been 
+ *			associated with this transaction, or NULL.
+ */
+PJ_DECL(pjsip_inv_session*) pjsip_tsx_get_inv_session(pjsip_transaction *tsx);
+
+
+
+
+
+#endif	/* __SIP_INVITE_SESSION_H__ */
diff --git a/pjsip/include/pjsip/sip_dialog.h b/pjsip/include/pjsip/sip_dialog.h
index fde16ad..6a5418b 100644
--- a/pjsip/include/pjsip/sip_dialog.h
+++ b/pjsip/include/pjsip/sip_dialog.h
@@ -61,6 +61,7 @@
     pj_pool_t	       *pool;	    /**< Dialog's pool.			    */
     pj_mutex_t	       *mutex;	    /**< Dialog's mutex.		    */
     pjsip_user_agent   *ua;	    /**< User agent instance.		    */
+    pjsip_endpoint     *endpt;	    /**< Endpoint instance.		    */
 
     /* The dialog set. */
     void	       *dlg_set;
diff --git a/pjsip/include/pjsip/sip_event.h b/pjsip/include/pjsip/sip_event.h
index 03260a5..268a60e 100644
--- a/pjsip/include/pjsip/sip_event.h
+++ b/pjsip/include/pjsip/sip_event.h
@@ -57,21 +57,9 @@
     /** Transaction state changed event. */
     PJSIP_EVENT_TSX_STATE,
 
-    /** 2xx response received event. */
-    PJSIP_EVENT_RX_200_MSG,
-
-    /** ACK request received event. */
-    PJSIP_EVENT_RX_ACK_MSG,
-
-    /** Message discarded event. */
-    PJSIP_EVENT_DISCARD_MSG,
-
     /** Indicates that the event was triggered by user action. */
     PJSIP_EVENT_USER,
 
-    /** On before transmitting message. */
-    PJSIP_EVENT_PRE_TX_MSG,
-
 } pjsip_event_id_e;
 
 
@@ -140,14 +128,6 @@
 
         } tx_msg;
 
-        /** Pre-transmission event. */
-        struct
-        {
-            pjsip_tx_data       *tdata; /**< Msg to be transmitted.     */
-            pjsip_transaction   *tsx;   /**< The transaction.           */
-            int                  retcnt;/**< Retransmission count.      */
-        } pre_tx_msg;
-
         /** Transmission error event. */
         struct
         {
@@ -162,25 +142,6 @@
             pjsip_transaction   *tsx;   /**< The transaction.           */
         } rx_msg;
 
-        /** Receipt of 200/INVITE response. */
-        struct
-        {
-            pjsip_rx_data       *rdata; /**< The 200 response msg.      */
-        } rx_200_msg;
-
-        /** Receipt of ACK message. */
-        struct
-        {
-            pjsip_rx_data       *rdata; /**< The ack message.           */
-        } rx_ack_msg;
-
-        /** Notification that endpoint has discarded a message. */
-        struct
-        {
-            pjsip_rx_data       *rdata; /**< The discarded message.     */
-            pj_status_t          reason;/**< The reason.                */
-        } discard_msg;
-
         /** User event. */
         struct
         {
@@ -245,34 +206,6 @@
         } while (0)
 
 /**
- * Init rx 200/INVITE event.
- */
-#define PJSIP_EVENT_INIT_RX_200_MSG(event,prdata)       \
-        do { \
-            (event).type = PJSIP_EVENT_RX_200_MSG;      \
-            (event).body.rx_200_msg.rdata = prdata;	\
-        } while (0)
-
-/**
- * Init rx ack msg event.
- */
-#define PJSIP_EVENT_INIT_RX_ACK_MSG(event,prdata)       \
-        do { \
-            (event).type = PJSIP_EVENT_RX_ACK_MSG;      \
-            (event).body.rx_ack_msg.rdata = prdata;	\
-        } while (0)
-
-/**
- * Init discard msg event.
- */
-#define PJSIP_EVENT_INIT_DISCARD_MSG(event,prdata,preason)  \
-        do { \
-            (event).type = PJSIP_EVENT_DISCARD_MSG;         \
-            (event).body.discard_msg.rdata = prdata;	    \
-            (event).body.discard_msg.reason = preason;	    \
-        } while (0)
-
-/**
  * Init user event.
  */
 #define PJSIP_EVENT_INIT_USER(event,u1,u2,u3,u4)    \
@@ -285,18 +218,6 @@
         } while (0)
 
 /**
- * Init pre tx msg event.
- */
-#define PJSIP_EVENT_INIT_PRE_TX_MSG(event,ptsx,ptdata,pretcnt) \
-        do { \
-            (event).type = PJSIP_EVENT_PRE_TX_MSG;	\
-            (event).body.pre_tx_msg.tsx = ptsx;		\
-            (event).body.pre_tx_msg.tdata = ptdata;	\
-            (event).body.pre_tx_msg.retcnt = pretcnt;	\
-        } while (0)
-
-
-/**
  * Get the event string from the event ID.
  * @param e the event ID.
  * @notes defined in sip_util.c
diff --git a/pjsip/include/pjsip/sip_msg.h b/pjsip/include/pjsip/sip_msg.h
index cebaf3f..e67d015 100644
--- a/pjsip/include/pjsip/sip_msg.h
+++ b/pjsip/include/pjsip/sip_msg.h
@@ -1821,6 +1821,58 @@
  * @}
  */
 
+
+///////////////////////////////////////////////////////////////////////////////
+/**
+ * @defgroup PJSIP_MSG_HDR_WARNING Header Field: Warning
+ * @brief Warning header field.
+ * @ingroup PJSIP_MSG
+ * @{
+ */
+/**
+ * SIP Warning header.
+ * In this version, Warning header is just a typedef for generic string 
+ * header.
+ */
+typedef pjsip_generic_string_hdr pjsip_warning_hdr;
+
+/**
+ * Create a warning header with the specified contents.
+ *
+ * @param pool	    Pool to allocate memory from.
+ * @param code	    Warning code, 300-399.
+ * @param host	    The host portion of the Warning header.
+ * @param text	    The warning text, which MUST not be quoted with
+ *		    double quote.
+ *
+ * @return	    The Warning header field.
+ */
+PJ_DECL(pjsip_warning_hdr*) pjsip_warning_hdr_create( pj_pool_t *pool,
+						      int code,
+						      const pj_str_t *host,
+						      const pj_str_t *text);
+
+/**
+ * Create a warning header and initialize the contents from the error
+ * message for the specified status code. The warning code will be
+ * set to 399.
+ *
+ * @param pool	    Pool to allocate memory from.
+ * @param host	    The host portion of the Warning header.
+ * @param status    The error status code, which error text will be
+ *		    put in as the Warning text.
+ *
+ * @return	    The Warning header field.
+ */
+PJ_DECL(pjsip_warning_hdr*) 
+pjsip_warning_hdr_create_from_status( pj_pool_t *pool,
+				      const pj_str_t *host,
+				      pj_status_t status);
+
+/**
+ * @}
+ */
+
 /**
  * @bug Once a header is put in the message, the header CAN NOT be put in
  *      other list. Solution:
@@ -1953,11 +2005,6 @@
 /** Create User-Agent header. */
 #define pjsip_user_agent_hdr_create pjsip_generic_string_hdr_create
 
-/** Warning header. */
-typedef pjsip_generic_string_hdr pjsip_warning_hdr;
-
-/** Create Warning header. */
-#define pjsip_warning_hdr_create pjsip_generic_string_hdr_create
 
 /**
  * @}
diff --git a/pjsip/include/pjsip_ua.h b/pjsip/include/pjsip_ua.h
index e2c640e..c32ada4 100644
--- a/pjsip/include/pjsip_ua.h
+++ b/pjsip/include/pjsip_ua.h
@@ -16,13 +16,10 @@
  * along with this program; if not, write to the Free Software
  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA 
  */
-
 #ifndef __PJSIP_UA_H__
 #define __PJSIP_UA_H__
 
-#include <pjsip_mod_ua/sip_dialog.h>
-#include <pjsip_mod_ua/sip_reg.h>
-#include <pjsip_mod_ua/sip_ua.h>
+#include <pjsip-ua/sip_inv.h>
 
 #endif	/* __PJSIP_UA_H__ */
 
diff --git a/pjsip/src/pjsip-ua/sip_inv.c b/pjsip/src/pjsip-ua/sip_inv.c
new file mode 100644
index 0000000..f9eac98
--- /dev/null
+++ b/pjsip/src/pjsip-ua/sip_inv.c
@@ -0,0 +1,1327 @@
+/* $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-ua/sip_inv.h>
+#include <pjsip/sip_module.h>
+#include <pjsip/sip_endpoint.h>
+#include <pjsip/sip_event.h>
+#include <pjsip/sip_transaction.h>
+#include <pjmedia/sdp.h>
+#include <pjmedia/sdp_neg.h>
+#include <pj/string.h>
+#include <pj/pool.h>
+#include <pj/assert.h>
+
+#define THIS_FILE	"sip_invite_session.c"
+
+/*
+ * Static prototypes.
+ */
+static pj_status_t mod_inv_load(pjsip_endpoint *endpt);
+static pj_status_t mod_inv_unload(void);
+static pj_bool_t   mod_inv_on_rx_request(pjsip_rx_data *rdata);
+static pj_bool_t   mod_inv_on_rx_response(pjsip_rx_data *rdata);
+static void	   mod_inv_on_tsx_state(pjsip_transaction*, pjsip_event*);
+
+static void inv_on_state_null( pjsip_inv_session *s, pjsip_event *e);
+static void inv_on_state_calling( pjsip_inv_session *s, pjsip_event *e);
+static void inv_on_state_incoming( pjsip_inv_session *s, pjsip_event *e);
+static void inv_on_state_early( pjsip_inv_session *s, pjsip_event *e);
+static void inv_on_state_connecting( pjsip_inv_session *s, pjsip_event *e);
+static void inv_on_state_confirmed( pjsip_inv_session *s, pjsip_event *e);
+static void inv_on_state_disconnected( pjsip_inv_session *s, pjsip_event *e);
+static void inv_on_state_terminated( pjsip_inv_session *s, pjsip_event *e);
+
+static void (*inv_state_handler[])( pjsip_inv_session *s, pjsip_event *e) = 
+{
+    &inv_on_state_null,
+    &inv_on_state_calling,
+    &inv_on_state_incoming,
+    &inv_on_state_early,
+    &inv_on_state_connecting,
+    &inv_on_state_confirmed,
+    &inv_on_state_disconnected,
+    &inv_on_state_terminated,
+};
+
+static struct mod_inv
+{
+    pjsip_module	 mod;
+    pjsip_endpoint	*endpt;
+    pjsip_inv_callback	 cb;
+    pjsip_module	*app_user;
+} mod_inv = 
+{
+    {
+	NULL, NULL,		    /* prev, next.			*/
+	{ "mod-invite", 10 },	    /* Name.				*/
+	-1,			    /* Id				*/
+	PJSIP_MOD_PRIORITY_APPLICATION-1,	/* Priority		*/
+	NULL,			    /* User data.			*/
+	&mod_inv_load,		    /* load()				*/
+	NULL,			    /* start()				*/
+	NULL,			    /* stop()				*/
+	&mod_inv_unload,	    /* unload()				*/
+	&mod_inv_on_rx_request,	    /* on_rx_request()			*/
+	&mod_inv_on_rx_response,    /* on_rx_response()			*/
+	NULL,			    /* on_tx_request.			*/
+	NULL,			    /* on_tx_response()			*/
+	&mod_inv_on_tsx_state,	    /* on_tsx_state()			*/
+    }
+};
+
+
+
+static pj_status_t mod_inv_load(pjsip_endpoint *endpt)
+{
+    pj_str_t allowed[] = {{"INVITE", 6}, {"ACK",3}, {"BYE",3}, {"CANCEL",6}};
+
+    /* Register supported methods: INVITE, ACK, BYE, CANCEL */
+    pjsip_endpt_add_capability(endpt, &mod_inv.mod, PJSIP_H_ALLOW, NULL,
+			       PJ_ARRAY_SIZE(allowed), allowed);
+
+    return PJ_SUCCESS;
+}
+
+static pj_status_t mod_inv_unload(void)
+{
+    /* Should remove capability here */
+    return PJ_SUCCESS;
+}
+
+static pj_bool_t mod_inv_on_rx_request(pjsip_rx_data *rdata)
+{
+    /* Ignore requests outside dialog */
+    if (pjsip_rdata_get_dlg(rdata) == NULL)
+	return PJ_FALSE;
+
+    /* Ignore all. */
+    return PJ_FALSE;
+}
+
+static pj_status_t send_ack(pjsip_inv_session *inv, pjsip_rx_data *rdata)
+{
+    pjsip_tx_data *tdata;
+    pj_status_t status;
+
+    status = pjsip_dlg_create_request(inv->dlg, &pjsip_ack_method, 
+				      rdata->msg_info.cseq->cseq, &tdata);
+    if (status != PJ_SUCCESS) {
+	/* Better luck next time */
+	pj_assert(!"Unable to create ACK!");
+	return status;
+    }
+
+    status = pjsip_dlg_send_request(inv->dlg, tdata, NULL);
+    if (status != PJ_SUCCESS) {
+	/* Better luck next time */
+	pj_assert(!"Unable to send ACK!");
+	return status;
+    }
+
+    return PJ_SUCCESS;
+}
+
+static pj_bool_t mod_inv_on_rx_response(pjsip_rx_data *rdata)
+{
+    pjsip_dialog *dlg;
+    pjsip_inv_session *inv;
+    pjsip_msg *msg = rdata->msg_info.msg;
+
+    dlg = pjsip_rdata_get_dlg(rdata);
+
+    /* Ignore responses outside dialog */
+    if (dlg == NULL)
+	return PJ_FALSE;
+
+    /* Ignore responses not belonging to invite session */
+    inv = pjsip_dlg_get_inv_session(dlg);
+    if (inv == NULL)
+	return PJ_FALSE;
+
+    /* This MAY be retransmission of 2xx response to INVITE. 
+     * If it is, we need to send ACK.
+     */
+    if (msg->type == PJSIP_RESPONSE_MSG && msg->line.status.code/100==2 &&
+	rdata->msg_info.cseq->method.id == PJSIP_INVITE_METHOD) {
+
+	send_ack(inv, rdata);
+	return PJ_TRUE;
+
+    }
+
+    /* No other processing needs to be done here. */
+    return PJ_FALSE;
+}
+
+static void mod_inv_on_tsx_state(pjsip_transaction *tsx, pjsip_event *e)
+{
+    pjsip_dialog *dlg;
+    pjsip_inv_session *inv;
+
+    dlg = pjsip_tsx_get_dlg(tsx);
+    if (dlg == NULL)
+	return;
+
+    inv = pjsip_dlg_get_inv_session(dlg);
+    if (inv == NULL)
+	return;
+
+    /* Call state handler for the invite session. */
+    (*inv_state_handler[inv->state])(inv, e);
+
+    /* Call on_tsx_state */
+    if (mod_inv.cb.on_tsx_state_changed)
+	(*mod_inv.cb.on_tsx_state_changed)(inv, tsx, e);
+
+    /* Clear invite transaction when tsx is terminated. */
+    if (tsx->state==PJSIP_TSX_STATE_TERMINATED && tsx == inv->invite_tsx)
+	inv->invite_tsx = NULL;
+}
+
+PJ_DEF(pj_status_t) pjsip_inv_usage_init( pjsip_endpoint *endpt,
+					  pjsip_module *app_module,
+					  const pjsip_inv_callback *cb)
+{
+    pj_status_t status;
+
+    /* Check arguments. */
+    PJ_ASSERT_RETURN(endpt && app_module && cb, PJ_EINVAL);
+
+    /* Some callbacks are mandatory */
+    PJ_ASSERT_RETURN(cb->on_state_changed && cb->on_new_session, PJ_EINVAL);
+
+    /* Check if module already registered. */
+    PJ_ASSERT_RETURN(mod_inv.mod.id == -1, PJ_EINVALIDOP);
+
+    /* Copy param. */
+    pj_memcpy(&mod_inv.cb, cb, sizeof(pjsip_inv_callback));
+
+    mod_inv.endpt = endpt;
+    mod_inv.app_user = app_module;
+
+    /* Register the module. */
+    status = pjsip_endpt_register_module(endpt, &mod_inv.mod);
+
+    return status;
+}
+
+PJ_DEF(pjsip_module*) pjsip_inv_usage_instance(void)
+{
+    return &mod_inv.mod;
+}
+
+
+PJ_DEF(pjsip_inv_session*) pjsip_dlg_get_inv_session(pjsip_dialog *dlg)
+{
+    return dlg->mod_data[mod_inv.mod.id];
+}
+
+/*
+ * Create UAC session.
+ */
+PJ_DEF(pj_status_t) pjsip_inv_create_uac( pjsip_dialog *dlg,
+					  const pjmedia_sdp_session *local_sdp,
+					  unsigned options,
+					  pjsip_inv_session **p_inv)
+{
+    pjsip_inv_session *inv;
+    pj_status_t status;
+
+    /* Verify arguments. */
+    PJ_ASSERT_RETURN(dlg && p_inv, PJ_EINVAL);
+
+    /* Normalize options */
+    if (options & PJSIP_INV_REQUIRE_100REL)
+	options |= PJSIP_INV_SUPPORT_100REL;
+
+    if (options & PJSIP_INV_REQUIRE_TIMER)
+	options |= PJSIP_INV_SUPPORT_TIMER;
+
+    /* Create the session */
+    inv = pj_pool_zalloc(dlg->pool, sizeof(pjsip_inv_session));
+    PJ_ASSERT_RETURN(inv != NULL, PJ_ENOMEM);
+
+    inv->pool = dlg->pool;
+    inv->role = PJSIP_ROLE_UAC;
+    inv->state = PJSIP_INV_STATE_NULL;
+    inv->dlg = dlg;
+    inv->options = options;
+
+    /* Create negotiator if local_sdp is specified. */
+    if (local_sdp) {
+	status = pjmedia_sdp_neg_create_w_local_offer(dlg->pool, local_sdp,
+						      &inv->neg);
+	if (status != PJ_SUCCESS)
+	    return status;
+    }
+
+    /* Register invite as dialog usage. */
+    status = pjsip_dlg_add_usage(dlg, &mod_inv.mod, inv);
+    if (status != PJ_SUCCESS)
+	return status;
+
+    /* Increment dialog session */
+    pjsip_dlg_inc_session(dlg);
+
+    /* Done */
+    *p_inv = inv;
+    return PJ_SUCCESS;
+}
+
+/*
+ * Verify incoming INVITE request.
+ */
+PJ_DEF(pj_status_t) pjsip_inv_verify_request(pjsip_rx_data *rdata,
+					     unsigned *options,
+					     const pjmedia_sdp_session *l_sdp,
+					     pjsip_dialog *dlg,
+					     pjsip_endpoint *endpt,
+					     pjsip_tx_data **p_tdata)
+{
+    pjsip_msg *msg;
+    pjsip_allow_hdr *allow;
+    pjsip_supported_hdr *sup_hdr;
+    pjsip_require_hdr *req_hdr;
+    int code = 200;
+    unsigned rem_option = 0;
+    pj_status_t status = PJ_SUCCESS;
+    pjsip_hdr res_hdr_list;
+
+    /* Init return arguments. */
+    if (p_tdata) *p_tdata = NULL;
+
+    /* Verify arguments. */
+    PJ_ASSERT_RETURN(rdata != NULL && options != NULL, PJ_EINVAL);
+
+    /* Normalize options */
+    if (*options & PJSIP_INV_REQUIRE_100REL)
+	*options |= PJSIP_INV_SUPPORT_100REL;
+
+    if (*options & PJSIP_INV_REQUIRE_TIMER)
+	*options |= PJSIP_INV_SUPPORT_TIMER;
+
+    /* Get the message in rdata */
+    msg = rdata->msg_info.msg;
+
+    /* Must be INVITE request. */
+    PJ_ASSERT_RETURN(msg->type == PJSIP_REQUEST_MSG &&
+		     msg->line.req.method.id == PJSIP_INVITE_METHOD,
+		     PJ_EINVAL);
+
+    /* If tdata is specified, then either dlg or endpt must be specified */
+    PJ_ASSERT_RETURN((!p_tdata) || (endpt || dlg), PJ_EINVAL);
+
+    /* Get the endpoint */
+    endpt = endpt ? endpt : dlg->endpt;
+
+    /* Init response header list */
+    pj_list_init(&res_hdr_list);
+
+    /* Check the request body, see if it's something that we support
+     * (i.e. SDP). 
+     */
+    if (msg->body) {
+	pjsip_msg_body *body = msg->body;
+	pj_str_t str_application = {"application", 11};
+	pj_str_t str_sdp = { "sdp", 3 };
+	pjmedia_sdp_session *sdp;
+
+	/* Check content type. */
+	if (pj_stricmp(&body->content_type.type, &str_application) != 0 ||
+	    pj_stricmp(&body->content_type.subtype, &str_sdp) != 0)
+	{
+	    /* Not "application/sdp" */
+	    code = PJSIP_SC_UNSUPPORTED_MEDIA_TYPE;
+	    status = PJSIP_ERRNO_FROM_SIP_STATUS(code);
+
+	    if (p_tdata) {
+		/* Add Accept header to response */
+		pjsip_accept_hdr *acc;
+
+		acc = pjsip_accept_hdr_create(rdata->tp_info.pool);
+		PJ_ASSERT_RETURN(acc, PJ_ENOMEM);
+		acc->values[acc->count++] = pj_str("application/sdp");
+		pj_list_push_back(&res_hdr_list, acc);
+	    }
+
+	    goto on_return;
+	}
+
+	/* Parse and validate SDP */
+	status = pjmedia_sdp_parse(rdata->tp_info.pool, body->data, body->len,
+				   &sdp);
+	if (status == PJ_SUCCESS)
+	    status = pjmedia_sdp_validate(sdp);
+
+	if (status != PJ_SUCCESS) {
+	    /* Unparseable or invalid SDP */
+	    code = PJSIP_SC_BAD_REQUEST;
+
+	    if (p_tdata) {
+		/* Add Warning header. */
+		pjsip_warning_hdr *w;
+
+		w = pjsip_warning_hdr_create_from_status(rdata->tp_info.pool,
+							 pjsip_endpt_name(endpt),
+							 status);
+		PJ_ASSERT_RETURN(w, PJ_ENOMEM);
+
+		pj_list_push_back(&res_hdr_list, w);
+	    }
+
+	    goto on_return;
+	}
+
+	/* Negotiate with local SDP */
+	if (l_sdp) {
+	    pjmedia_sdp_neg *neg;
+
+	    /* Local SDP must be valid! */
+	    PJ_ASSERT_RETURN((status=pjmedia_sdp_validate(l_sdp))==PJ_SUCCESS,
+			     status);
+
+	    /* Create SDP negotiator */
+	    status = pjmedia_sdp_neg_create_w_remote_offer(
+			    rdata->tp_info.pool, l_sdp, sdp, &neg);
+	    PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
+
+	    /* Negotiate SDP */
+	    status = pjmedia_sdp_neg_negotiate(rdata->tp_info.pool, neg, 0);
+	    if (status != PJ_SUCCESS) {
+
+		/* Incompatible media */
+		code = PJSIP_SC_NOT_ACCEPTABLE_HERE;
+		status = PJSIP_ERRNO_FROM_SIP_STATUS(code);
+
+		if (p_tdata) {
+		    pjsip_accept_hdr *acc;
+		    pjsip_warning_hdr *w;
+
+		    /* Add Warning header. */
+		    w = pjsip_warning_hdr_create_from_status(
+					    rdata->tp_info.pool, 
+					    pjsip_endpt_name(endpt), status);
+		    PJ_ASSERT_RETURN(w, PJ_ENOMEM);
+
+		    pj_list_push_back(&res_hdr_list, w);
+
+		    /* Add Accept header to response */
+		    acc = pjsip_accept_hdr_create(rdata->tp_info.pool);
+		    PJ_ASSERT_RETURN(acc, PJ_ENOMEM);
+		    acc->values[acc->count++] = pj_str("application/sdp");
+		    pj_list_push_back(&res_hdr_list, acc);
+
+		}
+
+		goto on_return;
+	    }
+	}
+    }
+
+    /* Check supported methods, see if peer supports UPDATE.
+     * We just assume that peer supports standard INVITE, ACK, CANCEL, and BYE
+     * implicitly by sending this INVITE.
+     */
+    allow = pjsip_msg_find_hdr(msg, PJSIP_H_ALLOW, NULL);
+    if (allow) {
+	unsigned i;
+	const pj_str_t STR_UPDATE = { "UPDATE", 6 };
+
+	for (i=0; i<allow->count; ++i) {
+	    if (pj_stricmp(&allow->values[i], &STR_UPDATE)==0)
+		break;
+	}
+
+	if (i != allow->count) {
+	    /* UPDATE is present in Allow */
+	    rem_option |= PJSIP_INV_SUPPORT_UPDATE;
+	}
+
+    }
+
+    /* Check Supported header */
+    sup_hdr = pjsip_msg_find_hdr(msg, PJSIP_H_SUPPORTED, NULL);
+    if (sup_hdr) {
+	unsigned i;
+	pj_str_t STR_100REL = { "100rel", 6};
+	pj_str_t STR_TIMER = { "timer", 5 };
+
+	for (i=0; i<sup_hdr->count; ++i) {
+	    if (pj_stricmp(&sup_hdr->values[i], &STR_100REL)==0)
+		rem_option |= PJSIP_INV_SUPPORT_100REL;
+	    else if (pj_stricmp(&sup_hdr->values[i], &STR_TIMER)==0)
+		rem_option |= PJSIP_INV_SUPPORT_TIMER;
+	}
+    }
+
+    /* Check Require header */
+    req_hdr = pjsip_msg_find_hdr(msg, PJSIP_H_REQUIRE, NULL);
+    if (req_hdr) {
+	unsigned i;
+	pj_str_t STR_100REL = { "100rel", 6};
+	pj_str_t STR_TIMER = { "timer", 5 };
+	unsigned unsupp_cnt = 0;
+	pj_str_t unsupp_tags[PJSIP_GENERIC_ARRAY_MAX_COUNT];
+	
+	for (i=0; i<req_hdr->count; ++i) {
+	    if ((*options & PJSIP_INV_SUPPORT_100REL) && 
+		pj_stricmp(&req_hdr->values[i], &STR_100REL)==0)
+	    {
+		rem_option |= PJSIP_INV_REQUIRE_100REL;
+
+	    } else if ((*options && PJSIP_INV_SUPPORT_TIMER) &&
+		       pj_stricmp(&req_hdr->values[i], &STR_TIMER)==0)
+	    {
+		rem_option |= PJSIP_INV_REQUIRE_TIMER;
+
+	    } else {
+		/* Unknown/unsupported extension tag!  */
+		unsupp_tags[unsupp_cnt++] = req_hdr->values[i];
+	    }
+	}
+
+	/* Check if there are required tags that we don't support */
+	if (unsupp_cnt) {
+
+	    code = PJSIP_SC_BAD_EXTENSION;
+	    status = PJSIP_ERRNO_FROM_SIP_STATUS(code);
+
+	    if (p_tdata) {
+		pjsip_unsupported_hdr *unsupp_hdr;
+		const pjsip_hdr *h;
+
+		/* Add Unsupported header. */
+		unsupp_hdr = pjsip_unsupported_hdr_create(rdata->tp_info.pool);
+		PJ_ASSERT_RETURN(unsupp_hdr != NULL, PJ_ENOMEM);
+
+		unsupp_hdr->count = unsupp_cnt;
+		for (i=0; i<unsupp_cnt; ++i)
+		    unsupp_hdr->values[i] = unsupp_tags[i];
+
+		pj_list_push_back(&res_hdr_list, unsupp_hdr);
+
+		/* Add Supported header. */
+		h = pjsip_endpt_get_capability(endpt, PJSIP_H_SUPPORTED, 
+					       NULL);
+		pj_assert(h);
+		if (h) {
+		    sup_hdr = pjsip_hdr_clone(rdata->tp_info.pool, h);
+		    pj_list_push_back(&res_hdr_list, sup_hdr);
+		}
+	    }
+
+	    goto on_return;
+	}
+    }
+
+    /* Check if there are local requirements that are not supported
+     * by peer.
+     */
+    if ( ((*options & PJSIP_INV_REQUIRE_100REL)!=0 && 
+	  (rem_option & PJSIP_INV_SUPPORT_100REL)==0) ||
+	 ((*options & PJSIP_INV_REQUIRE_TIMER)!=0 &&
+	  (rem_option & PJSIP_INV_SUPPORT_TIMER)==0))
+    {
+	code = PJSIP_SC_EXTENSION_REQUIRED;
+	status = PJSIP_ERRNO_FROM_SIP_STATUS(code);
+
+	if (p_tdata) {
+	    const pjsip_hdr *h;
+
+	    /* Add Require header. */
+	    req_hdr = pjsip_require_hdr_create(rdata->tp_info.pool);
+	    PJ_ASSERT_RETURN(req_hdr != NULL, PJ_ENOMEM);
+
+	    if (*options & PJSIP_INV_REQUIRE_100REL)
+		req_hdr->values[req_hdr->count++] = pj_str("100rel");
+
+	    if (*options & PJSIP_INV_REQUIRE_TIMER)
+		req_hdr->values[req_hdr->count++] = pj_str("timer");
+
+	    pj_list_push_back(&res_hdr_list, req_hdr);
+
+	    /* Add Supported header. */
+	    h = pjsip_endpt_get_capability(endpt, PJSIP_H_SUPPORTED, 
+					   NULL);
+	    pj_assert(h);
+	    if (h) {
+		sup_hdr = pjsip_hdr_clone(rdata->tp_info.pool, h);
+		pj_list_push_back(&res_hdr_list, sup_hdr);
+	    }
+
+	}
+
+	goto on_return;
+    }
+
+on_return:
+
+    /* Create response if necessary */
+    if (code != 200 && p_tdata) {
+	pjsip_tx_data *tdata;
+	const pjsip_hdr *h;
+
+	if (dlg) {
+	    status = pjsip_dlg_create_response(dlg, rdata, code, NULL, 
+					       &tdata);
+	} else {
+	    status = pjsip_endpt_create_response(endpt, rdata, code, NULL, 
+						 &tdata);
+	}
+
+	if (status != PJ_SUCCESS)
+	    return status;
+
+	/* Add response headers. */
+	h = res_hdr_list.next;
+	while (h != &res_hdr_list) {
+	    pjsip_hdr *cloned;
+
+	    cloned = pjsip_hdr_clone(tdata->pool, h);
+	    PJ_ASSERT_RETURN(cloned, PJ_ENOMEM);
+
+	    pjsip_msg_add_hdr(tdata->msg, cloned);
+
+	    h = h->next;
+	}
+
+	*p_tdata = tdata;
+    }
+
+    return status;
+}
+
+/*
+ * Create UAS session.
+ */
+PJ_DEF(pj_status_t) pjsip_inv_create_uas( pjsip_dialog *dlg,
+					  pjsip_rx_data *rdata,
+					  const pjmedia_sdp_session *local_sdp,
+					  unsigned options,
+					  pjsip_inv_session **p_inv)
+{
+    pjsip_inv_session *inv;
+    pjsip_msg *msg;
+    pjmedia_sdp_session *rem_sdp = NULL;
+    pj_status_t status;
+
+    /* Verify arguments. */
+    PJ_ASSERT_RETURN(dlg && rdata && p_inv, PJ_EINVAL);
+
+    /* Dialog MUST have been initialised. */
+    PJ_ASSERT_RETURN(pjsip_rdata_get_tsx(rdata) != NULL, PJ_EINVALIDOP);
+
+    msg = rdata->msg_info.msg;
+
+    /* rdata MUST contain INVITE request */
+    PJ_ASSERT_RETURN(msg->type == PJSIP_REQUEST_MSG &&
+		     msg->line.req.method.id == PJSIP_INVITE_METHOD,
+		     PJ_EINVALIDOP);
+
+    /* Normalize options */
+    if (options & PJSIP_INV_REQUIRE_100REL)
+	options |= PJSIP_INV_SUPPORT_100REL;
+
+    if (options & PJSIP_INV_REQUIRE_TIMER)
+	options |= PJSIP_INV_SUPPORT_TIMER;
+
+    /* Create the session */
+    inv = pj_pool_zalloc(dlg->pool, sizeof(pjsip_inv_session));
+    PJ_ASSERT_RETURN(inv != NULL, PJ_ENOMEM);
+
+    inv->pool = dlg->pool;
+    inv->role = PJSIP_ROLE_UAS;
+    inv->state = PJSIP_INV_STATE_NULL;
+    inv->dlg = dlg;
+    inv->options = options;
+
+    /* Parse SDP in message body, if present. */
+    if (msg->body) {
+	pjsip_msg_body *body = msg->body;
+
+	/* Parse and validate SDP */
+	status = pjmedia_sdp_parse(inv->pool, body->data, body->len,
+				   &rem_sdp);
+	if (status == PJ_SUCCESS)
+	    status = pjmedia_sdp_validate(rem_sdp);
+
+	if (status != PJ_SUCCESS)
+	    return status;
+    }
+
+    /* Create negotiator. */
+    if (rem_sdp) {
+	status = pjmedia_sdp_neg_create_w_remote_offer(inv->pool, local_sdp,
+						       rem_sdp, &inv->neg);
+						
+    } else if (local_sdp) {
+	status = pjmedia_sdp_neg_create_w_local_offer(inv->pool, local_sdp,
+						      &inv->neg);
+    } else {
+	pj_assert(!"UAS dialog without remote and local offer is not supported!");
+	PJ_TODO(IMPLEMENT_DELAYED_UAS_OFFER);
+	status = PJ_ENOTSUP;
+    }
+
+    if (status != PJ_SUCCESS)
+	return status;
+
+    /* Register invite as dialog usage. */
+    status = pjsip_dlg_add_usage(dlg, &mod_inv.mod, inv);
+    if (status != PJ_SUCCESS)
+	return status;
+
+    /* Increment session in the dialog. */
+    pjsip_dlg_inc_session(dlg);
+
+    /* Save the invite transaction. */
+    inv->invite_tsx = pjsip_rdata_get_tsx(rdata);
+    inv->invite_tsx->mod_data[mod_inv.mod.id] = inv;
+
+    /* Done */
+    *p_inv = inv;
+    return PJ_SUCCESS;
+}
+
+static void *clone_sdp(pj_pool_t *pool, const void *data, unsigned len)
+{
+    PJ_UNUSED_ARG(len);
+    return pjmedia_sdp_session_clone(pool, data);
+}
+
+static int print_sdp(pjsip_msg_body *body, char *buf, pj_size_t len)
+{
+    return pjmedia_sdp_print(body->data, buf, len);
+}
+
+static pjsip_msg_body *create_sdp_body(pj_pool_t *pool,
+				       const pjmedia_sdp_session *c_sdp)
+{
+    pjsip_msg_body *body;
+
+
+    body = pj_pool_zalloc(pool, sizeof(pjsip_msg_body));
+    PJ_ASSERT_RETURN(body != NULL, NULL);
+
+    body->content_type.type = pj_str("application");
+    body->content_type.subtype = pj_str("sdp");
+    body->data = pjmedia_sdp_session_clone(pool, c_sdp);
+    body->len = 0;
+    body->clone_data = &clone_sdp;
+    body->print_body = &print_sdp;
+
+    return body;
+}
+
+/*
+ * Create initial INVITE request.
+ */
+PJ_DEF(pj_status_t) pjsip_inv_invite( pjsip_inv_session *inv,
+				      pjsip_tx_data **p_tdata )
+{
+    pjsip_tx_data *tdata;
+    const pjsip_hdr *hdr;
+    pj_status_t status;
+
+    /* Verify arguments. */
+    PJ_ASSERT_RETURN(inv && p_tdata, PJ_EINVAL);
+
+    /* State MUST be NULL. */
+    PJ_ASSERT_RETURN(inv->state == PJSIP_INV_STATE_NULL, PJ_EINVAL);
+
+    /* Create the INVITE request. */
+    status = pjsip_dlg_create_request(inv->dlg, &pjsip_invite_method, -1,
+				      &tdata);
+    if (status != PJ_SUCCESS)
+	return status;
+
+    /* Add SDP, if any. */
+    if (inv->neg && 
+	pjmedia_sdp_neg_get_state(inv->neg)==PJMEDIA_SDP_NEG_STATE_LOCAL_OFFER)
+    {
+	const pjmedia_sdp_session *offer;
+
+	status = pjmedia_sdp_neg_get_neg_local(inv->neg, &offer);
+	if (status != PJ_SUCCESS)
+	    return status;
+
+	tdata->msg->body = create_sdp_body(tdata->pool, offer);
+    }
+
+    /* Add Allow header. */
+    hdr = pjsip_endpt_get_capability(inv->dlg->endpt, PJSIP_H_ALLOW, NULL);
+    if (hdr) {
+	pjsip_msg_add_hdr(tdata->msg,
+			  pjsip_hdr_shallow_clone(tdata->pool, hdr));
+    }
+
+    /* Add Supported header */
+    hdr = pjsip_endpt_get_capability(inv->dlg->endpt, PJSIP_H_SUPPORTED, NULL);
+    if (hdr) {
+	pjsip_msg_add_hdr(tdata->msg,
+			  pjsip_hdr_shallow_clone(tdata->pool, hdr));
+    }
+
+    /* Add Require header. */
+    PJ_TODO(INVITE_ADD_REQUIRE_HEADER);
+
+    /* Done. */
+    *p_tdata = tdata;
+
+    return PJ_SUCCESS;
+}
+
+
+/*
+ * Answer initial INVITE.
+ */ 
+PJ_DEF(pj_status_t) pjsip_inv_answer(	pjsip_inv_session *inv,
+					int st_code,
+					const pj_str_t *st_text,
+					const pjmedia_sdp_session *local_sdp,
+					pjsip_tx_data **p_tdata )
+{
+    pjsip_tx_data *last_res;
+    pj_status_t status;
+
+    /* Verify arguments. */
+    PJ_ASSERT_RETURN(inv && p_tdata, PJ_EINVAL);
+
+    /* Must have INVITE transaction. */
+    PJ_ASSERT_RETURN(inv->invite_tsx, PJ_EBUG);
+
+    /* INVITE transaction MUST have transmitted a response (e.g. 100) */
+    PJ_ASSERT_RETURN(inv->invite_tsx->last_tx, PJ_EINVALIDOP);
+
+    /* If local_sdp is specified, then we MUST NOT have answered the
+     * offer before. 
+     */
+    PJ_ASSERT_RETURN(!local_sdp ||
+		     (pjmedia_sdp_neg_get_state(inv->neg)==PJMEDIA_SDP_NEG_STATE_REMOTE_OFFER),
+		     PJ_EINVALIDOP);
+    if (local_sdp) {
+	status = pjmedia_sdp_neg_set_local_answer(inv->pool, inv->neg,
+						  local_sdp);
+	if (status != PJ_SUCCESS)
+	    return status;
+    }
+
+    last_res = inv->invite_tsx->last_tx;
+
+    /* Modify last response. */
+    status = pjsip_dlg_modify_response(inv->dlg, last_res, st_code, st_text);
+    if (status != PJ_SUCCESS)
+	return status;
+
+    /* Include SDP for 18x and 2xx response. */
+    if (st_code/10 == 18 || st_code/10 == 20) {
+	pjmedia_sdp_session *local;
+
+	status = pjmedia_sdp_neg_get_neg_local(inv->neg, &local);
+	if (status == PJ_SUCCESS)
+	    last_res->msg->body = create_sdp_body(last_res->pool, local);
+					      ;
+    }
+
+    /* Do we need to increment tdata's reference counter? */
+    PJ_TODO(INV_ANSWER_MAY_HAVE_TO_INCREMENT_REF_COUNTER);
+
+    *p_tdata = last_res;
+
+    return PJ_SUCCESS;
+}
+
+
+/*
+ * End session.
+ */
+PJ_DEF(pj_status_t) pjsip_inv_end_session(  pjsip_inv_session *inv,
+					    int st_code,
+					    const pj_str_t *st_text,
+					    pjsip_tx_data **p_tdata )
+{
+    pjsip_tx_data *tdata;
+    pj_status_t status;
+
+    /* Verify arguments. */
+    PJ_ASSERT_RETURN(inv && p_tdata, PJ_EINVAL);
+
+    /* Create appropriate message. */
+    switch (inv->state) {
+    case PJSIP_INV_STATE_CALLING:
+    case PJSIP_INV_STATE_EARLY:
+    case PJSIP_INV_STATE_INCOMING:
+
+	if (inv->role == PJSIP_ROLE_UAC) {
+
+	    /* For UAC when session has not been confirmed, create CANCEL. */
+
+	    /* MUST have the original UAC INVITE transaction. */
+	    PJ_ASSERT_RETURN(inv->invite_tsx != NULL, PJ_EBUG);
+
+	    /* But CANCEL should only be called when we have received a
+	     * provisional response. If we haven't received any responses,
+	     * just destroy the transaction.
+	     */
+	    if (inv->invite_tsx->status_code < 100) {
+
+		pjsip_tsx_terminate(inv->invite_tsx, 487);
+
+		return PJSIP_ETSXDESTROYED;
+	    }
+
+	    /* The CSeq here assumes that the dialog is started with an
+	     * INVITE session. This may not be correct; dialog can be 
+	     * started as SUBSCRIBE session.
+	     * So fix this!
+	     */
+	    status = pjsip_endpt_create_cancel(inv->dlg->endpt, 
+					       inv->invite_tsx->last_tx,
+					       &tdata);
+
+	} else {
+
+	    /* For UAS, send a final response. */
+	    tdata = inv->invite_tsx->last_tx;
+	    PJ_ASSERT_RETURN(tdata != NULL, PJ_EINVALIDOP);
+
+	    status = pjsip_dlg_modify_response(inv->dlg, tdata, st_code,
+					       st_text);
+	}
+	break;
+
+    case PJSIP_INV_STATE_CONNECTING:
+    case PJSIP_INV_STATE_CONFIRMED:
+	/* For established dialog, send BYE */
+	status = pjsip_dlg_create_request(inv->dlg, &pjsip_bye_method, -1, 
+					  &tdata);
+	break;
+
+    case PJSIP_INV_STATE_DISCONNECTED:
+    case PJSIP_INV_STATE_TERMINATED:
+	/* No need to do anything. */
+	PJ_TODO(RETURN_A_PROPER_STATUS_CODE_HERE);
+	return PJ_EINVALIDOP;
+
+    default:
+	pj_assert("!Invalid operation!");
+	return PJ_EINVALIDOP;
+    }
+
+    if (status != PJ_SUCCESS)
+	return status;
+
+
+    /* Done */
+
+    *p_tdata = tdata;
+
+    return PJ_SUCCESS;
+}
+
+
+/*
+ * Create re-INVITE.
+ */
+PJ_DEF(pj_status_t) pjsip_inv_reinvite( pjsip_inv_session *inv,
+					const pj_str_t *new_contact,
+					const pjmedia_sdp_session *new_offer,
+					pjsip_tx_data **p_tdata )
+{
+    PJ_UNUSED_ARG(inv);
+    PJ_UNUSED_ARG(new_contact);
+    PJ_UNUSED_ARG(new_offer);
+    PJ_UNUSED_ARG(p_tdata);
+
+    PJ_TODO(CREATE_REINVITE_REQUEST);
+    return PJ_ENOTSUP;
+}
+
+/*
+ * Create UPDATE.
+ */
+PJ_DEF(pj_status_t) pjsip_inv_update (	pjsip_inv_session *inv,
+					const pj_str_t *new_contact,
+					const pjmedia_sdp_session *new_offer,
+					pjsip_tx_data **p_tdata )
+{
+    PJ_UNUSED_ARG(inv);
+    PJ_UNUSED_ARG(new_contact);
+    PJ_UNUSED_ARG(new_offer);
+    PJ_UNUSED_ARG(p_tdata);
+
+    PJ_TODO(CREATE_UPDATE_REQUEST);
+    return PJ_ENOTSUP;
+}
+
+/*
+ * Send a request or response message.
+ */
+PJ_DEF(pj_status_t) pjsip_inv_send_msg( pjsip_inv_session *inv,
+					pjsip_tx_data *tdata,
+					void *token )
+{
+    pj_status_t status;
+
+    /* Verify arguments. */
+    PJ_ASSERT_RETURN(inv && tdata, PJ_EINVAL);
+
+    if (tdata->msg->type == PJSIP_REQUEST_MSG) {
+	pjsip_transaction *tsx;
+
+	status = pjsip_dlg_send_request(inv->dlg, tdata, &tsx);
+	if (status != PJ_SUCCESS)
+	    return status;
+
+	tsx->mod_data[mod_inv.mod.id] = inv;
+	tsx->mod_data[mod_inv.app_user->id] = token;
+
+    } else {
+	pjsip_cseq_hdr *cseq;
+
+	/* Can only do this to send response to original INVITE
+	 * request.
+	 */
+	PJ_ASSERT_RETURN((cseq=pjsip_msg_find_hdr(tdata->msg, PJSIP_H_CSEQ, NULL)) != NULL &&
+			 (cseq->cseq == inv->invite_tsx->cseq),
+			 PJ_EINVALIDOP);
+
+	status = pjsip_dlg_send_response(inv->dlg, inv->invite_tsx, tdata);
+	if (status != PJ_SUCCESS)
+	    return status;
+    }
+
+    /* Done (?) */
+    return PJ_SUCCESS;
+}
+
+
+void inv_set_state(pjsip_inv_session *inv, pjsip_inv_state state,
+		   pjsip_event *e)
+{
+    inv->state = state;
+    if (mod_inv.cb.on_state_changed)
+	(*mod_inv.cb.on_state_changed)(inv, e);
+
+    if (inv->state == PJSIP_INV_STATE_DISCONNECTED)
+	pjsip_dlg_dec_session(inv->dlg);
+}
+
+static void inv_on_state_null( pjsip_inv_session *s, pjsip_event *e)
+{
+    pjsip_transaction *tsx = e->body.tsx_state.tsx;
+    pjsip_dialog *dlg = pjsip_tsx_get_dlg(tsx);
+
+    PJ_ASSERT_ON_FAIL(tsx && dlg, return);
+
+    if (tsx->method.id == PJSIP_INVITE_METHOD) {
+
+	if (dlg->role == PJSIP_ROLE_UAC) {
+
+	    /* Keep the initial INVITE transaction. */
+	    if (s->invite_tsx == NULL)
+		s->invite_tsx = tsx;
+
+	    switch (tsx->state) {
+	    case PJSIP_TSX_STATE_CALLING:
+		inv_set_state(s, PJSIP_INV_STATE_CALLING, e);
+		break;
+	    default:
+		pj_assert(!"Unexpected state");
+		break;
+	    }
+
+	} else {
+	    switch (tsx->state) {
+	    case PJSIP_TSX_STATE_TRYING:
+		inv_set_state(s, PJSIP_INV_STATE_INCOMING, e);
+		break;
+	    default:
+		pj_assert(!"Unexpected state");
+	    }
+	}
+
+    } else {
+	pj_assert(!"Unexpected transaction type");
+    }
+}
+
+static void inv_on_state_calling( pjsip_inv_session *s, pjsip_event *e)
+{
+    pjsip_transaction *tsx = e->body.tsx_state.tsx;
+    pjsip_dialog *dlg = pjsip_tsx_get_dlg(tsx);
+
+    PJ_ASSERT_ON_FAIL(tsx && dlg, return);
+    
+    if (tsx->method.id == PJSIP_INVITE_METHOD) {
+
+	switch (tsx->state) {
+
+	case PJSIP_TSX_STATE_PROCEEDING:
+	    if (dlg->remote.info->tag.slen) {
+		inv_set_state(s, PJSIP_INV_STATE_EARLY, e);
+	    } else {
+		/* Ignore 100 (Trying) response, as it doesn't change
+		 * session state. It only ceases retransmissions.
+		 */
+	    }
+	    break;
+
+	case PJSIP_TSX_STATE_COMPLETED:
+	    if (tsx->status_code/100 == 2) {
+		
+		/* This should not happen.
+		 * When transaction receives 2xx, it should be terminated
+		 */
+		pj_assert(0);
+		inv_set_state(s, PJSIP_INV_STATE_CONNECTING, e);
+
+	    } else {
+		inv_set_state(s, PJSIP_INV_STATE_DISCONNECTED, e);
+	    }
+	    break;
+
+	case PJSIP_TSX_STATE_TERMINATED:
+	    /* INVITE transaction can be terminated either because UAC
+	     * transaction received 2xx response or because of transport
+	     * error.
+	     */
+	    if (tsx->status_code/100 == 2) {
+		/* This must be receipt of 2xx response */
+
+		/* Set state to CONNECTING */
+		inv_set_state(s, PJSIP_INV_STATE_CONNECTING, e);
+
+		/* Send ACK */
+		pj_assert(e->body.tsx_state.type == PJSIP_EVENT_RX_MSG);
+
+		send_ack(s, e->body.tsx_state.src.rdata);
+
+	    } else  {
+		inv_set_state(s, PJSIP_INV_STATE_DISCONNECTED, e);
+		inv_set_state(s, PJSIP_INV_STATE_TERMINATED, e);
+	    }
+	    break;
+
+	default:
+	    pj_assert(!"Unexpected state");
+	}
+
+    } else {
+	pj_assert(!"Unexpected transaction type");
+    }
+}
+
+static void inv_on_state_incoming( pjsip_inv_session *s, pjsip_event *e)
+{
+    pjsip_transaction *tsx = e->body.tsx_state.tsx;
+    pjsip_dialog *dlg = pjsip_tsx_get_dlg(tsx);
+
+    PJ_ASSERT_ON_FAIL(tsx && dlg, return);
+
+    if (tsx->method.id == PJSIP_INVITE_METHOD) {
+	switch (tsx->state) {
+	case PJSIP_TSX_STATE_PROCEEDING:
+	    if (tsx->status_code > 100)
+		inv_set_state(s, PJSIP_INV_STATE_EARLY, e);
+	    break;
+	case PJSIP_TSX_STATE_COMPLETED:
+	    if (tsx->status_code/100 == 2)
+		inv_set_state(s, PJSIP_INV_STATE_CONNECTING, e);
+	    else
+		inv_set_state(s, PJSIP_INV_STATE_DISCONNECTED, e);
+	    break;
+	case PJSIP_TSX_STATE_TERMINATED:
+	    /* This happens on transport error */
+	    inv_set_state(s, PJSIP_INV_STATE_DISCONNECTED, e);
+	    inv_set_state(s, PJSIP_INV_STATE_TERMINATED, e);
+	    break;
+	default:
+	    pj_assert(!"Unexpected state");
+	}
+    } else {
+	pj_assert(!"Unexpected transaction type");
+    }
+}
+
+static void inv_on_state_early( pjsip_inv_session *s, pjsip_event *e)
+{
+    pjsip_transaction *tsx = e->body.tsx_state.tsx;
+    pjsip_dialog *dlg = pjsip_tsx_get_dlg(tsx);
+
+    PJ_ASSERT_ON_FAIL(tsx && dlg, return);
+
+    if (tsx->method.id == PJSIP_INVITE_METHOD) {
+
+	switch (tsx->state) {
+
+	case PJSIP_TSX_STATE_PROCEEDING:
+	    /* Send/received another provisional response. */
+	    inv_set_state(s, PJSIP_INV_STATE_EARLY, e);
+	    break;
+
+	case PJSIP_TSX_STATE_COMPLETED:
+	    if (tsx->status_code/100 == 2)
+		inv_set_state(s, PJSIP_INV_STATE_CONNECTING, e);
+	    else
+		inv_set_state(s, PJSIP_INV_STATE_DISCONNECTED, e);
+	    break;
+
+	case PJSIP_TSX_STATE_TERMINATED:
+	    /* INVITE transaction can be terminated either because UAC
+	     * transaction received 2xx response or because of transport
+	     * error.
+	     */
+	    if (tsx->status_code/100 == 2) {
+
+		/* This must be receipt of 2xx response */
+
+		/* Set state to CONNECTING */
+		inv_set_state(s, PJSIP_INV_STATE_CONNECTING, e);
+
+		/* if UAC, send ACK and move state to confirmed. */
+		if (tsx->role == PJSIP_ROLE_UAC) {
+		    pj_assert(e->body.tsx_state.type == PJSIP_EVENT_RX_MSG);
+
+		    send_ack(s, e->body.tsx_state.src.rdata);
+
+		    inv_set_state(s, PJSIP_INV_STATE_CONFIRMED, e);
+		}
+
+	    } else  {
+		inv_set_state(s, PJSIP_INV_STATE_DISCONNECTED, e);
+		inv_set_state(s, PJSIP_INV_STATE_TERMINATED, e);
+	    }
+	    break;
+
+	default:
+	    pj_assert(!"Unexpected state");
+	}
+
+    } else if (tsx->method.id == PJSIP_CANCEL_METHOD) {
+
+	/* Handle incoming CANCEL request. */
+	if (tsx->role == PJSIP_ROLE_UAS && s->role == PJSIP_ROLE_UAS &&
+	    e->body.tsx_state.type == PJSIP_EVENT_RX_MSG) 
+	{
+
+	    pj_status_t status;
+	    pjsip_tx_data *tdata;
+
+	    /* Respond CANCEL with 200 (OK) */
+	    status = pjsip_dlg_create_response(dlg, e->body.tsx_state.src.rdata,
+					       200, NULL, &tdata);
+	    if (status == PJ_SUCCESS) {
+		pjsip_dlg_send_response(dlg, tsx, tdata);
+	    }
+    
+	    /* Respond the original UAS transaction with 487 (Request 
+	     * Terminated) response.
+	     */
+	    pj_assert(s->invite_tsx && s->invite_tsx->last_tx);
+	    tdata = s->invite_tsx->last_tx;
+
+	    status = pjsip_dlg_modify_response(dlg, tdata, 487, NULL);
+	    if (status == PJ_SUCCESS) {
+		pjsip_dlg_send_response(dlg, s->invite_tsx, tdata);
+	    }
+	}
+
+    } else {
+	pj_assert(!"Unexpected transaction type");
+    }
+}
+
+static void inv_on_state_connecting( pjsip_inv_session *s, pjsip_event *e)
+{
+    pjsip_transaction *tsx = e->body.tsx_state.tsx;
+    pjsip_dialog *dlg = pjsip_tsx_get_dlg(tsx);
+
+    PJ_ASSERT_ON_FAIL(tsx && dlg, return);
+
+    if (tsx->method.id == PJSIP_INVITE_METHOD) {
+
+	switch (tsx->state) {
+
+	case PJSIP_TSX_STATE_CONFIRMED:
+	    break;
+
+	case PJSIP_TSX_STATE_TERMINATED:
+	    /* INVITE transaction can be terminated either because UAC
+	     * transaction received 2xx response or because of transport
+	     * error.
+	     */
+	    if (tsx->status_code/100 != 2) {
+		inv_set_state(s, PJSIP_INV_STATE_DISCONNECTED, e);
+		inv_set_state(s, PJSIP_INV_STATE_TERMINATED, e);
+	    }
+	    break;
+
+	case PJSIP_TSX_STATE_DESTROYED:
+	    /* Do nothing. */
+	    break;
+
+	default:
+	    pj_assert(!"Unexpected state");
+	}
+
+    } else {
+	pj_assert(!"Unexpected transaction type");
+    }
+}
+
+static void inv_on_state_confirmed( pjsip_inv_session *s, pjsip_event *e)
+{
+    pjsip_transaction *tsx = e->body.tsx_state.tsx;
+    pjsip_dialog *dlg = pjsip_tsx_get_dlg(tsx);
+
+    PJ_ASSERT_ON_FAIL(tsx && dlg, return);
+
+    if (tsx->method.id == PJSIP_BYE_METHOD) {
+	inv_set_state(s, PJSIP_INV_STATE_DISCONNECTED, e);
+
+    } else if (tsx->method.id == PJSIP_INVITE_METHOD) {
+	
+	switch (tsx->state) {
+	case PJSIP_TSX_STATE_TERMINATED:
+	case PJSIP_TSX_STATE_DESTROYED:
+	    break;
+	default:
+	    pj_assert(!"Unexpected state");
+	    break;
+	}
+
+
+    } else {
+	pj_assert(!"Unexpected transaction type");
+    }
+}
+
+static void inv_on_state_disconnected( pjsip_inv_session *s, pjsip_event *e)
+{
+    PJ_UNUSED_ARG(s);
+    PJ_UNUSED_ARG(e);
+}
+
+static void inv_on_state_terminated( pjsip_inv_session *s, pjsip_event *e)
+{
+    PJ_UNUSED_ARG(s);
+    PJ_UNUSED_ARG(e);
+}
+
diff --git a/pjsip/src/pjsip/sip_dialog.c b/pjsip/src/pjsip/sip_dialog.c
index a9a4010..b4ea16d 100644
--- a/pjsip/src/pjsip/sip_dialog.c
+++ b/pjsip/src/pjsip/sip_dialog.c
@@ -37,6 +37,7 @@
 
 #define THIS_FILE	"sip_dialog.c"
 
+long pjsip_dlg_lock_tls_id;
 
 PJ_DEF(pj_bool_t) pjsip_method_creates_dialog(const pjsip_method *m)
 {
@@ -72,6 +73,7 @@
     dlg->pool = pool;
     pj_sprintf(dlg->obj_name, "dlg%p", dlg);
     dlg->ua = ua;
+    dlg->endpt = endpt;
 
     status = pj_mutex_create_recursive(pool, "dlg%p", &dlg->mutex);
     if (status != PJ_SUCCESS)
@@ -92,7 +94,7 @@
 {
     if (dlg->mutex)
 	pj_mutex_destroy(dlg->mutex);
-    pjsip_endpt_release_pool(pjsip_ua_get_endpt(dlg->ua), dlg->pool);
+    pjsip_endpt_release_pool(dlg->endpt, dlg->pool);
 }
 
 
@@ -144,7 +146,7 @@
 
     /* Randomize local CSeq. */
     dlg->local.first_cseq = pj_rand() % 0x7FFFFFFFL;
-    dlg->local.cseq = dlg->local.cseq;
+    dlg->local.cseq = dlg->local.first_cseq;
 
     /* Init local contact. */
     dlg->local.contact = pjsip_contact_hdr_create(dlg->pool);
@@ -183,7 +185,7 @@
     pj_list_init(&dlg->route_set);
 
     /* Init client authentication session. */
-    status = pjsip_auth_clt_init(&dlg->auth_sess, pjsip_ua_get_endpt(ua), 
+    status = pjsip_auth_clt_init(&dlg->auth_sess, dlg->endpt, 
 				 dlg->pool, 0);
     if (status != PJ_SUCCESS)
 	goto on_error;
@@ -342,7 +344,7 @@
     }
 
     /* Init client authentication session. */
-    status = pjsip_auth_clt_init(&dlg->auth_sess, pjsip_ua_get_endpt(ua),
+    status = pjsip_auth_clt_init(&dlg->auth_sess, dlg->endpt,
 				 dlg->pool, 0);
     if (status != PJ_SUCCESS)
 	goto on_error;
@@ -506,9 +508,12 @@
 	return status;
     }
 
+    /* Log */
+    PJ_LOG(5,(dlg->obj_name, "Dialog destroyed"));
+
     /* Destroy this dialog. */
     pj_mutex_destroy(dlg->mutex);
-    pjsip_endpt_release_pool(pjsip_ua_get_endpt(dlg->ua), dlg->pool);
+    pjsip_endpt_release_pool(dlg->endpt, dlg->pool);
 
     return PJ_SUCCESS;
 }
@@ -629,6 +634,9 @@
     /* Set module data. */
     dlg->mod_data[mod->id] = mod_data;
 
+    /* Increment count. */
+    ++dlg->usage_cnt;
+
     pj_mutex_unlock(dlg->mutex);
 
     return PJ_SUCCESS;
@@ -663,7 +671,7 @@
      * Create the request by cloning from the headers in the
      * dialog.
      */
-    status = pjsip_endpt_create_request_from_hdr(pjsip_ua_get_endpt(dlg->ua),
+    status = pjsip_endpt_create_request_from_hdr(dlg->endpt,
 						 method,
 						 dlg->target,
 						 dlg->local.info,
@@ -809,15 +817,17 @@
 	    goto on_error;
 	}
 
-	*p_tsx = tsx;
+	if (p_tsx)
+	    *p_tsx = tsx;
 
     } else {
-	status = pjsip_endpt_send_request_stateless(
-		    pjsip_ua_get_endpt(dlg->ua), tdata, NULL, NULL);
+	status = pjsip_endpt_send_request_stateless(dlg->endpt, tdata, 
+						    NULL, NULL);
 	if (status != PJ_SUCCESS)
 	    goto on_error;
 
-	*p_tsx = NULL;
+	if (p_tsx)
+	    *p_tsx = NULL;
     }
 
     /* Unlock dialog. */
@@ -832,7 +842,8 @@
     /* Whatever happen delete the message. */
     pjsip_tx_data_dec_ref( tdata );
 
-    *p_tsx = NULL;
+    if (p_tsx)
+	*p_tsx = NULL;
     return status;
 }
 
@@ -852,7 +863,7 @@
     int st_class;
 
     /* Create generic response. */
-    status = pjsip_endpt_create_response(pjsip_ua_get_endpt(dlg->ua),
+    status = pjsip_endpt_create_response(dlg->endpt,
 					 rdata, st_code, st_text, &tdata);
     if (status != PJ_SUCCESS)
 	return status;
@@ -914,7 +925,7 @@
 	/* Add Allow header in 2xx and 405 response. */
 	if (st_class==2 || st_code==405) {
 	    const pjsip_hdr *c_hdr;
-	    c_hdr = pjsip_endpt_get_capability(pjsip_ua_get_endpt(dlg->ua),
+	    c_hdr = pjsip_endpt_get_capability(dlg->endpt,
 					       PJSIP_H_ALLOW, NULL);
 	    if (c_hdr) {
 		pjsip_hdr *hdr = pjsip_hdr_clone(tdata->pool, c_hdr);
@@ -925,7 +936,7 @@
 	/* Add Supported header in 2xx response. */
 	if (st_class==2) {
 	    const pjsip_hdr *c_hdr;
-	    c_hdr = pjsip_endpt_get_capability(pjsip_ua_get_endpt(dlg->ua),
+	    c_hdr = pjsip_endpt_get_capability(dlg->endpt,
 					       PJSIP_H_SUPPORTED, NULL);
 	    if (c_hdr) {
 		pjsip_hdr *hdr = pjsip_hdr_clone(tdata->pool, c_hdr);
@@ -1056,7 +1067,7 @@
 	 * Respond statelessly with 500 (Internal Server Error)
 	 */
 	pj_mutex_unlock(dlg->mutex);
-	pjsip_endpt_respond_stateless(pjsip_ua_get_endpt(dlg->ua),
+	pjsip_endpt_respond_stateless(dlg->endpt,
 				      rdata, 500, NULL, NULL, NULL);
 	return;
     }
@@ -1094,7 +1105,7 @@
 		  "%s is unhandled by dialog usages. "
 		  "Dialog will response with 500 (Internal Server Error)",
 		  pjsip_rx_data_get_info(rdata)));
-	status = pjsip_endpt_create_response(pjsip_ua_get_endpt(dlg->ua), 
+	status = pjsip_endpt_create_response(dlg->endpt, 
 					     rdata, 
 					     PJSIP_SC_INTERNAL_SERVER_ERROR, 
 					     NULL, &tdata);
@@ -1125,6 +1136,9 @@
     /* Lock the dialog. */
     pj_mutex_lock(dlg->mutex);
 
+    /* Check that rdata already has dialog in mod_data. */
+    pj_assert(pjsip_rdata_get_dlg(rdata) == dlg);
+
     /* Update the remote tag, if none is specified yet. */
     if (dlg->remote.info->tag.slen == 0 && rdata->msg_info.to->tag.slen != 0) {
 
@@ -1133,8 +1147,19 @@
 	/* No need to update remote's tag_hval since its never used. */
     }
 
-    /* Check that rdata already has dialog in mod_data. */
-    pj_assert(pjsip_rdata_get_dlg(rdata) == dlg);
+    /* Update remote target when receiving certain response messages. */
+    if (pjsip_method_creates_dialog(&rdata->msg_info.cseq->method) &&
+	rdata->msg_info.msg->line.status.code/100 == 2)
+    {
+	pjsip_contact_hdr *contact;
+
+	contact = pjsip_msg_find_hdr(rdata->msg_info.msg, PJSIP_H_CONTACT, 
+				     NULL);
+	if (contact) {
+	    dlg->remote.contact = pjsip_hdr_clone(dlg->pool, contact);
+	    dlg->target = dlg->remote.contact->uri;
+	}
+    }
 
     /* Pass to dialog usages. */
     for (i=0; i<dlg->usage_cnt; ++i) {
@@ -1185,6 +1210,9 @@
     if (tsx->state == PJSIP_TSX_STATE_TERMINATED && dlg->tsx_count == 0 && 
 	dlg->sess_count == 0) 
     {
+	/* Unregister this dialog from the transaction. */
+	tsx->mod_data[dlg->ua->id] = NULL;
+
 	/* Time to destroy dialog. */
 	unregister_and_destroy_dialog(dlg);
 
diff --git a/pjsip/src/pjsip/sip_endpoint.c b/pjsip/src/pjsip/sip_endpoint.c
index 3d109e4..5356103 100644
--- a/pjsip/src/pjsip/sip_endpoint.c
+++ b/pjsip/src/pjsip/sip_endpoint.c
@@ -311,6 +311,10 @@
 	default:
 	    return PJ_EINVAL;
 	}
+
+	if (hdr) {
+	    pj_list_push_back(&endpt->cap_hdr, hdr);
+	}
     }
 
     /* Add the tags to the header. */
@@ -755,6 +759,11 @@
     }
 
     pj_rwmutex_unlock_read(endpt->mod_mutex);
+
+    /* Must clear mod_data before returning rdata to transport, since
+     * rdata may be reused.
+     */
+    pj_memset(&rdata->endpt_info, 0, sizeof(rdata->endpt_info));
 }
 
 /*
diff --git a/pjsip/src/pjsip/sip_msg.c b/pjsip/src/pjsip/sip_msg.c
index 23e97f7..bee6721 100644
--- a/pjsip/src/pjsip/sip_msg.c
+++ b/pjsip/src/pjsip/sip_msg.c
@@ -1702,6 +1702,40 @@
 
 ///////////////////////////////////////////////////////////////////////////////
 /*
+ * Warning header.
+ */
+PJ_DEF(pjsip_warning_hdr*) pjsip_warning_hdr_create(  pj_pool_t *pool,
+						      int code,
+						      const pj_str_t *host,
+						      const pj_str_t *text)
+{
+    const pj_str_t str_warning = { "Warning", 7 };
+    pj_str_t hvalue;
+
+    hvalue.ptr = pj_pool_alloc(pool, 10 +		/* code */
+				     host->slen + 2 +	/* host */
+				     text->slen + 2);	/* text */
+    hvalue.slen = pj_sprintf(hvalue.ptr, "%u %.*s \"%.*s\"",
+			     code, host->slen, host->ptr,
+			     text->slen, text->ptr);
+
+    return pjsip_generic_string_hdr_create(pool, &str_warning, &hvalue);
+}
+
+PJ_DEF(pjsip_warning_hdr*) 
+pjsip_warning_hdr_create_from_status( pj_pool_t *pool,
+				      const pj_str_t *host,
+				      pj_status_t status)
+{
+    char errbuf[PJ_ERR_MSG_SIZE];
+    pj_str_t text;
+    
+    text = pj_strerror(status, errbuf, sizeof(errbuf));
+    return pjsip_warning_hdr_create(pool, 399, host, &text);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+/*
  * Message body manipulations.
  */
 PJ_DEF(int) pjsip_print_text_body(pjsip_msg_body *msg_body, char *buf, pj_size_t size)
diff --git a/pjsip/src/pjsip/sip_transaction.c b/pjsip/src/pjsip/sip_transaction.c
index cf07ddf..20d90c7 100644
--- a/pjsip/src/pjsip/sip_transaction.c
+++ b/pjsip/src/pjsip/sip_transaction.c
@@ -32,6 +32,13 @@
 
 #define THIS_FILE   "sip_transaction.c"
 
+#if 0
+#define TSX_TRACE_(expr)    PJ_LOG(3,expr)
+#else
+#define TSX_TRACE_(expr)
+#endif
+
+
 /*****************************************************************************
  **
  ** Declarations and static variable definitions section.
@@ -504,20 +511,26 @@
     /* Lock hash table mutex. */
     pj_mutex_lock(mod_tsx_layer.mutex);
 
-    /* Check if no transaction with the same key exists. */
-    PJ_ASSERT_ON_FAIL(pj_hash_get( mod_tsx_layer.htable, 
-				   &tsx->transaction_key.ptr,
-				   tsx->transaction_key.slen, 
-				   &tsx->hashed_key) == NULL,
-			{
-			    pj_mutex_unlock(mod_tsx_layer.mutex);
-			    return PJ_EEXISTS;
-			}
-		      );
+    /* Check if no transaction with the same key exists. 
+     * Do not use PJ_ASSERT_RETURN since it evaluates the expression
+     * twice!
+     */
+    pj_assert(pj_hash_get( mod_tsx_layer.htable, 
+			   &tsx->transaction_key.ptr,
+			   tsx->transaction_key.slen, 
+			   NULL) == NULL);
+
+    TSX_TRACE_((THIS_FILE, 
+		"Transaction %p registered with hkey=0x%p and key=%.*s",
+		tsx, tsx->hashed_key, tsx->transaction_key.slen,
+		tsx->transaction_key.ptr));
 
     /* Register the transaction to the hash table. */
+    //pj_hash_set( tsx->pool, mod_tsx_layer.htable, tsx->transaction_key.ptr,
+    //		 tsx->transaction_key.slen, tsx->hashed_key, tsx);
+    PJ_TODO(USE_PRECALCULATED_HASHED_VALUE);
     pj_hash_set( tsx->pool, mod_tsx_layer.htable, tsx->transaction_key.ptr,
-		 tsx->transaction_key.slen, tsx->hashed_key, tsx);
+    		 tsx->transaction_key.slen, 0, tsx);
 
     /* Unlock mutex. */
     pj_mutex_unlock(mod_tsx_layer.mutex);
@@ -538,8 +551,16 @@
     pj_mutex_lock(mod_tsx_layer.mutex);
 
     /* Register the transaction to the hash table. */
+    //pj_hash_set( NULL, mod_tsx_layer.htable, tsx->transaction_key.ptr,
+    //		 tsx->transaction_key.slen, tsx->hashed_key, NULL);
+    PJ_TODO(USE_PRECALCULATED_HASHED_VALUE);
     pj_hash_set( NULL, mod_tsx_layer.htable, tsx->transaction_key.ptr,
-		 tsx->transaction_key.slen, tsx->hashed_key, NULL);
+		 tsx->transaction_key.slen, 0, NULL);
+
+    TSX_TRACE_((THIS_FILE, 
+		"Transaction %p unregistered, hkey=0x%p and key=%.*s",
+		tsx, tsx->hashed_key, tsx->transaction_key.slen,
+		tsx->transaction_key.ptr));
 
     /* Unlock mutex. */
     pj_mutex_unlock(mod_tsx_layer.mutex);
@@ -553,11 +574,15 @@
 						     pj_bool_t lock )
 {
     pjsip_transaction *tsx;
+    pj_uint32_t hval = 0;
 
     pj_mutex_lock(mod_tsx_layer.mutex);
-    tsx = pj_hash_get( mod_tsx_layer.htable, key->ptr, key->slen, NULL );
+    tsx = pj_hash_get( mod_tsx_layer.htable, key->ptr, key->slen, &hval );
     pj_mutex_unlock(mod_tsx_layer.mutex);
 
+    TSX_TRACE_((THIS_FILE, 
+		"Finding tsx with hkey=0x%p and key=%.*s: found %p",
+		hval, key->slen, key->ptr, tsx));
 
     /* Race condition!
      * Transaction may gets deleted before we have chance to lock it.
@@ -641,6 +666,7 @@
 static pj_bool_t mod_tsx_layer_on_rx_request(pjsip_rx_data *rdata)
 {
     pj_str_t key;
+    pj_uint32_t hval = 0;
     pjsip_transaction *tsx;
 
     pjsip_tsx_create_key(rdata->tp_info.pool, &key, PJSIP_ROLE_UAS,
@@ -649,7 +675,13 @@
     /* Find transaction. */
     pj_mutex_lock( mod_tsx_layer.mutex );
 
-    tsx = pj_hash_get( mod_tsx_layer.htable, key.ptr, key.slen, NULL );
+    tsx = pj_hash_get( mod_tsx_layer.htable, key.ptr, key.slen, &hval );
+
+
+    TSX_TRACE_((THIS_FILE, 
+		"Finding tsx for request, hkey=0x%p and key=%.*s, found %p",
+		hval, key.slen, key.ptr, tsx));
+
 
     if (tsx == NULL || tsx->state == PJSIP_TSX_STATE_TERMINATED) {
 	/* Transaction not found.
@@ -682,6 +714,7 @@
 static pj_bool_t mod_tsx_layer_on_rx_response(pjsip_rx_data *rdata)
 {
     pj_str_t key;
+    pj_uint32_t hval = 0;
     pjsip_transaction *tsx;
 
     pjsip_tsx_create_key(rdata->tp_info.pool, &key, PJSIP_ROLE_UAC,
@@ -690,7 +723,13 @@
     /* Find transaction. */
     pj_mutex_lock( mod_tsx_layer.mutex );
 
-    tsx = pj_hash_get( mod_tsx_layer.htable, key.ptr, key.slen, NULL );
+    tsx = pj_hash_get( mod_tsx_layer.htable, key.ptr, key.slen, &hval );
+
+
+    TSX_TRACE_((THIS_FILE, 
+		"Finding tsx for response, hkey=0x%p and key=%.*s, found %p",
+		hval, key.slen, key.ptr, tsx));
+
 
     if (tsx == NULL || tsx->state == PJSIP_TSX_STATE_TERMINATED) {
 	/* Transaction not found.
@@ -918,7 +957,28 @@
 	tsx->state_handler = tsx_state_handler_uas[state];
     }
 
-    /* Inform TU */
+    /* Before informing TU about state changed, inform TU about
+     * rx event.
+     */
+    if (event_src_type==PJSIP_EVENT_RX_MSG && tsx->tsx_user) {
+	pjsip_rx_data *rdata = event_src;
+
+	pj_assert(rdata != NULL);
+
+	if (rdata->msg_info.msg->type == PJSIP_REQUEST_MSG &&
+	    tsx->tsx_user->on_rx_request)
+	{
+	    (*tsx->tsx_user->on_rx_request)(rdata);
+
+	} else if (rdata->msg_info.msg->type == PJSIP_RESPONSE_MSG &&
+		   tsx->tsx_user->on_rx_response)
+	{
+	    (*tsx->tsx_user->on_rx_response)(rdata);
+	}
+
+    }
+
+    /* Inform TU about state changed. */
     if (tsx->tsx_user && tsx->tsx_user->on_tsx_state) {
 	pjsip_event e;
 	PJSIP_EVENT_INIT_TSX_STATE(e, tsx, event_src_type, event_src,
@@ -1044,8 +1104,13 @@
 			 &via->branch_param);
 
     /* Calculate hashed key value. */
+    PJ_TODO(OPTIMIZE_TSX_BY_PRECALCULATING_HASHED_KEY_VALUE);
+    /*
+     blp: somehow this yields different hashed value!!
+
     tsx->hashed_key = pj_hash_calc(0, tsx->transaction_key.ptr,
 				   tsx->transaction_key.slen);
+     */
 
     PJ_LOG(6, (tsx->obj_name, "tsx_key=%.*s", tsx->transaction_key.slen,
 	       tsx->transaction_key.ptr));
@@ -1158,8 +1223,13 @@
     }
 
     /* Calculate hashed key value. */
+    PJ_TODO(OPTIMIZE_TSX_BY_PRECALCULATING_HASHED_KEY_VALUE);
+    /*
+     blp: somehow this yields different hashed value!!
+
     tsx->hashed_key = pj_hash_calc(0, tsx->transaction_key.ptr,
 				   tsx->transaction_key.slen);
+     */
 
     /* Duplicate branch parameter for transaction. */
     branch = &rdata->msg_info.via->branch_param;
diff --git a/pjsip/src/pjsip/sip_ua_layer.c b/pjsip/src/pjsip/sip_ua_layer.c
index 9088584..c01e78e 100644
--- a/pjsip/src/pjsip/sip_ua_layer.c
+++ b/pjsip/src/pjsip/sip_ua_layer.c
@@ -168,8 +168,11 @@
     /* Get the dialog where this transaction belongs. */
     dlg = tsx->mod_data[mod_ua.mod.id];
     
-    /* Must have the dialog instance! */
-    PJ_ASSERT_ON_FAIL(dlg != NULL, return);
+    /* If dialog instance has gone, it could mean that the dialog
+     * may has been destroyed.
+     */
+    if (dlg == NULL)
+	return;
 
     /* Hand over the event to the dialog. */
     pjsip_dlg_on_tsx_state(dlg, tsx, e);
@@ -208,6 +211,17 @@
 
 
 /*
+ * Get the endpoint where this UA is currently registered.
+ */
+PJ_DEF(pjsip_endpoint*) pjsip_ua_get_endpt(pjsip_user_agent *ua)
+{
+    PJ_UNUSED_ARG(ua);
+    pj_assert(ua == &mod_ua.mod);
+    return mod_ua.endpt;
+}
+
+
+/*
  * Destroy the user agent layer.
  */
 PJ_DEF(pj_status_t) pjsip_ua_destroy(void)
@@ -460,17 +474,27 @@
     pj_str_t *from_tag;
     pjsip_dialog *dlg;
 
+    /* Optimized path: bail out early if request doesn't have To tag */
+    if (rdata->msg_info.to->tag.slen == 0)
+	return PJ_FALSE;
+
     /* Lock user agent before looking up the dialog hash table. */
     pj_mutex_lock(mod_ua.mutex);
 
     /* Lookup the dialog set, based on the To tag header. */
     dlg_set = find_dlg_set_for_msg(rdata);
 
-    /* Bail out if dialog is not found. */
+    /* If dialog is not found, respond with 481 (Call/Transaction
+     * Does Not Exist).
+     */
     if (dlg_set == NULL) {
-	/* Not ours. */
+	/* Unable to find dialog. */
 	pj_mutex_unlock(mod_ua.mutex);
-	return PJ_FALSE;
+
+	/* Respond with 481 . */
+	pjsip_endpt_respond_stateless( mod_ua.endpt, rdata, 481, NULL, NULL,
+				       NULL );
+	return PJ_TRUE;
     }
 
     /* Dialog set has been found.
@@ -530,7 +554,7 @@
 
     /* Check if transaction is present. */
     tsx = pjsip_rdata_get_tsx(rdata);
-    if (!tsx) {
+    if (tsx) {
 	/* Check if dialog is present in the transaction. */
 	dlg = pjsip_tsx_get_dlg(tsx);
 	if (!dlg)
diff --git a/pjsip/src/pjsip/sip_util.c b/pjsip/src/pjsip/sip_util.c
index 8684f50..ba06867 100644
--- a/pjsip/src/pjsip/sip_util.c
+++ b/pjsip/src/pjsip/sip_util.c
@@ -43,11 +43,7 @@
     "RX_MSG",
     "TRANSPORT_ERROR",
     "TSX_STATE",
-    "RX_2XX_RESPONSE",
-    "RX_ACK",
-    "DISCARD_MSG",
     "USER",
-    "BEFORE_TX",
 };
 
 static pj_str_t str_TEXT = { "text", 4},
diff --git a/pjsip/src/pjsua/getopt.c b/pjsip/src/pjsua/getopt.c
index b522c57..eae96f1 100644
--- a/pjsip/src/pjsua/getopt.c
+++ b/pjsip/src/pjsua/getopt.c
@@ -36,6 +36,8 @@
 #include "config.h"
 #endif
 
+#include <pj/string.h>
+
 #ifndef HAVE_GETOPT_LONG
 
 /* getopt_long and getopt_long_only entry points for GNU getopt.
diff --git a/pjsip/src/pjsua/main.c b/pjsip/src/pjsua/main.c
index 00cb40d..c921794 100644
--- a/pjsip/src/pjsua/main.c
+++ b/pjsip/src/pjsua/main.c
@@ -16,1812 +16,243 @@
  * along with this program; if not, write to the Free Software
  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA 
  */
-#include <pjlib.h>
-#include <pjsip_core.h>
-#include <pjsip_ua.h>
-#include <pjsip_simple.h>
-#include <pjmedia.h>
-#include <ctype.h>
-#include <stdlib.h>
-#include <pj/stun.h>
+#include "pjsua.h"
 
-#define START_PORT	    5060
-#define MAX_BUDDIES	    32
-#define THIS_FILE	    "main.c"
-#define MAX_PRESENTITY	    32
-#define PRESENCE_TIMEOUT    60
+/* For debugging, disable threading. */
+//#define NO_WORKER_THREAD
 
-/* By default we'll have one worker thread, except when threading 
- * is disabled. 
- */
-#if PJ_HAS_THREADS
-#  define WORKER_COUNT	1
-#else
-#  define WORKER_COUNT	0
+#ifdef NO_WORKER_THREAD
+#include <conio.h>
 #endif
 
-/* Global variable. */
-static struct
-{
-    /* Control. */
-    pj_pool_factory *pf;
-    pjsip_endpoint  *endpt;
-    pj_pool_t	    *pool;
-    pjsip_user_agent *user_agent;
-    int		     worker_cnt;
-    int		     worker_quit_flag;
+#define THIS_FILE	"main.c"
 
-    /* User info. */
-    char	     user_id[64];
-    pj_str_t	     local_uri;
-    pj_str_t	     contact;
-    pj_str_t	     real_contact;
-
-    /* Dialog. */
-    pjsip_dlg	    *cur_dlg;
-
-    /* Authentication. */
-    int		     cred_count;
-    pjsip_cred_info  cred_info[4];
-
-    /* Media stack. */
-    pj_bool_t	     null_audio;
-    pj_med_mgr_t    *mmgr;
-
-    /* Misc. */
-    int		     app_log_level;
-    char	    *log_filename;
-    FILE	    *log_file;
-
-    /* Proxy URLs */
-    pj_str_t	     proxy;
-    pj_str_t	     outbound_proxy;
-
-    /* UA auto options. */
-    int		     auto_answer;	/* -1 to disable. */
-    int		     auto_hangup;	/* -1 to disable */
-
-    /* Registration. */
-    pj_str_t	     registrar_uri;
-    pjsip_regc	    *regc;
-    pj_int32_t	     reg_timeout;
-    pj_timer_entry   regc_timer;
-
-    /* STUN */
-    pj_str_t	     stun_srv1;
-    int		     stun_port1;
-    pj_str_t	     stun_srv2;
-    int		     stun_port2;
-
-    /* UDP sockets and their public address. */
-    int		     sip_port;
-    pj_sock_t	     sip_sock;
-    pj_sockaddr_in   sip_sock_name;
-    pj_sock_t	     rtp_sock;
-    pj_sockaddr_in   rtp_sock_name;
-    pj_sock_t	     rtcp_sock;
-    pj_sockaddr_in   rtcp_sock_name;
-
-    /* SIMPLE */
-    pj_bool_t	     hide_status;
-    pj_bool_t	     offer_x_ms_msg;
-    int		     im_counter;
-    int		     buddy_cnt;
-    pj_str_t	     buddy[MAX_BUDDIES];
-    pj_bool_t	     buddy_status[MAX_BUDDIES];
-    pj_bool_t	     no_presence;
-    pjsip_presentity *buddy_pres[MAX_BUDDIES];
-
-    int		    pres_cnt;
-    pjsip_presentity *pres[MAX_PRESENTITY];
-
-} global;
-
-enum { AUTO_ANSWER, AUTO_HANGUP };
-
-/* This is the data that will be 'attached' on per dialog basis. */
-struct dialog_data
-{
-    /* Media session. */
-    pj_media_session_t *msession;
-
-    /* x-ms-chat session. */
-    pj_bool_t		x_ms_msg_session;
-
-    /* Cached SDP body, updated when media session changed. */
-    pjsip_msg_body *body;
-
-    /* Timer. */
-    pj_bool_t	     has_auto_timer;
-    pj_timer_entry   auto_timer;
-};
+static pjsip_inv_session *inv_session;
 
 /*
- * These are the callbacks to be registered to dialog to receive notifications
- * about various events in the dialog.
+ * Notify UI when invite state has changed.
  */
-static void dlg_on_all_events	(pjsip_dlg *dlg, pjsip_dlg_event_e dlg_evt,
-				 pjsip_event *event );
-static void dlg_on_before_tx	(pjsip_dlg *dlg, pjsip_transaction *tsx, 
-				 pjsip_tx_data *tdata, int retransmission);
-static void dlg_on_tx_msg	(pjsip_dlg *dlg, pjsip_transaction *tsx, 
-				 pjsip_tx_data *tdata);
-static void dlg_on_rx_msg	(pjsip_dlg *dlg, pjsip_transaction *tsx, 
-				 pjsip_rx_data *rdata);
-static void dlg_on_incoming	(pjsip_dlg *dlg, pjsip_transaction *tsx,
-				 pjsip_rx_data *rdata);
-static void dlg_on_calling	(pjsip_dlg *dlg, pjsip_transaction *tsx,
-				 pjsip_tx_data *tdata);
-static void dlg_on_provisional	(pjsip_dlg *dlg, pjsip_transaction *tsx,
-				 pjsip_event *event);
-static void dlg_on_connecting	(pjsip_dlg *dlg, pjsip_event *event);
-static void dlg_on_established	(pjsip_dlg *dlg, pjsip_event *event);
-static void dlg_on_disconnected	(pjsip_dlg *dlg, pjsip_event *event);
-static void dlg_on_terminated	(pjsip_dlg *dlg);
-static void dlg_on_mid_call_evt	(pjsip_dlg *dlg, pjsip_event *event);
-
-/* The callback structure that will be registered to UA layer. */
-struct pjsip_dlg_callback dlg_callback = {
-    &dlg_on_all_events,
-    &dlg_on_before_tx,
-    &dlg_on_tx_msg,
-    &dlg_on_rx_msg,
-    &dlg_on_incoming,
-    &dlg_on_calling,
-    &dlg_on_provisional,
-    &dlg_on_connecting,
-    &dlg_on_established,
-    &dlg_on_disconnected,
-    &dlg_on_terminated,
-    &dlg_on_mid_call_evt
-};
-
-
-/* 
- * Auxiliary things are put in misc.c, so that this main.c file is more 
- * readable. 
- */
-#include "misc.c"
-
-static void dlg_auto_timer_callback( pj_timer_heap_t *timer_heap,
-				     struct pj_timer_entry *entry)
+void ui_inv_on_state_changed(pjsip_inv_session *inv, pjsip_event *e)
 {
-    pjsip_dlg *dlg = entry->user_data;
-    struct dialog_data *dlg_data = dlg->user_data;
-    
-    PJ_UNUSED_ARG(timer_heap)
-
-    dlg_data->has_auto_timer = 0;
-
-    if (entry->id == AUTO_ANSWER) {
-	pjsip_tx_data *tdata = pjsip_dlg_answer(dlg, 200);
-	if (tdata) {
-	    struct dialog_data *dlg_data = global.cur_dlg->user_data;
-	    tdata->msg->body = dlg_data->body;
-	    pjsip_dlg_send_msg(dlg, tdata);
-	}
-    } else {
-	pjsip_tx_data *tdata = pjsip_dlg_disconnect(dlg, 500);
-	if (tdata) 
-	    pjsip_dlg_send_msg(dlg, tdata);
-    }
-}
-
-static void update_registration(pjsip_regc *regc, int renew)
-{
-    pjsip_tx_data *tdata;
-
-    PJ_LOG(3,(THIS_FILE, "Performing SIP registration..."));
-
-    if (renew) {
-	tdata = pjsip_regc_register(regc, 1);
-    } else {
-	tdata = pjsip_regc_unregister(regc);
-    }
-
-    pjsip_regc_send( regc, tdata );
-}
-
-static void regc_cb(struct pjsip_regc_cbparam *param)
-{
-    /*
-     * Print registration status.
-     */
-    if (param->code < 0 || param->code >= 300) {
-	PJ_LOG(2, (THIS_FILE, "SIP registration failed, status=%d (%s)", 
-		   param->code, pjsip_get_status_text(param->code)->ptr));
-	global.regc = NULL;
-
-    } else if (PJSIP_IS_STATUS_IN_CLASS(param->code, 200)) {
-	PJ_LOG(3, (THIS_FILE, "SIP registration success, status=%d (%s), "
-			      "will re-register in %d seconds", 
-			      param->code,
-			      pjsip_get_status_text(param->code)->ptr,
-			      param->expiration));
-
-    } else {
-	PJ_LOG(4, (THIS_FILE, "SIP registration updated status=%d", param->code));
-    }
-}
-
-static void pres_on_received_request(pjsip_presentity *pres, pjsip_rx_data *rdata,
-				     int *timeout)
-{
-    int state;
-    int i;
-    char url[PJSIP_MAX_URL_SIZE];
-    int urllen;
-
-    PJ_UNUSED_ARG(rdata)
-
-    if (*timeout > 0) {
-	state = PJSIP_EVENT_SUB_STATE_ACTIVE;
-	if (*timeout > 300)
-	    *timeout = 300;
-    } else {
-	state = PJSIP_EVENT_SUB_STATE_TERMINATED;
-    }
-
-    urllen = pjsip_uri_print(PJSIP_URI_IN_FROMTO_HDR, rdata->from->uri, url, sizeof(url)-1);
-    if (urllen < 1) {
-	pj_native_strcpy(url, "<unknown>");
-    } else {
-	url[urllen] = '\0';
-    }
-    PJ_LOG(3,(THIS_FILE, "Received presence request from %s, sub_state=%s", 
-			 url, 
-			 (state==PJSIP_EVENT_SUB_STATE_ACTIVE?"active":"terminated")));
-
-    for (i=0; i<global.pres_cnt; ++i)
-	if (global.pres[i] == pres)
-	    break;
-    if (i == global.pres_cnt)
-	global.pres[global.pres_cnt++] = pres;
-
-    pjsip_presence_set_credentials( pres, global.cred_count, global.cred_info );
-    pjsip_presence_notify(pres, state, !global.hide_status);
-
-}
-
-static void pres_on_received_refresh(pjsip_presentity *pres, pjsip_rx_data *rdata)
-{
-    pres_on_received_request(pres, rdata, &pres->sub->default_interval);
-}
-
-/* This is called by presence framework when we receives presence update
- * of a resource (buddy).
- */
-static void pres_on_received_update(pjsip_presentity *pres, pj_bool_t is_open)
-{
-    int buddy_index = (int)pres->user_data;
-
-    global.buddy_status[buddy_index] = is_open;
-    PJ_LOG(3,(THIS_FILE, "Presence update: %s is %s", 
-			 global.buddy[buddy_index].ptr,
-			 (is_open ? "Online" : "Offline")));
-}
-
-/* This is called when the subscription is terminated. */
-static void pres_on_terminated(pjsip_presentity *pres, const pj_str_t *reason)
-{
-    if (pres->sub->role == PJSIP_ROLE_UAC) {
-	int buddy_index = (int)pres->user_data;
-	PJ_LOG(3,(THIS_FILE, "Presence subscription for %s is terminated (reason=%.*s)", 
-			    global.buddy[buddy_index].ptr,
-			    reason->slen, reason->ptr));
-	global.buddy_pres[buddy_index] = NULL;
-	global.buddy_status[buddy_index] = 0;
-    } else {
-	int i;
-	PJ_LOG(3,(THIS_FILE, "Notifier terminated (reason=%.*s)", 
-			    reason->slen, reason->ptr));
-	pjsip_presence_notify(pres, PJSIP_EVENT_SUB_STATE_TERMINATED, 1);
-	for (i=0; i<global.pres_cnt; ++i) {
-	    if (global.pres[i] == pres) {
-		int j;
-		global.pres[i] = NULL;
-		for (j=i+1; j<global.pres_cnt; ++j)
-		    global.pres[j-1] = global.pres[j];
-		global.pres_cnt--;
-		break;
-	    }
-	}
-    }
-    pjsip_presence_destroy(pres);
-}
-
-
-/* Callback attached to SIP body to print the body to message buffer. */
-static int print_msg_body(pjsip_msg_body *msg_body, char *buf, pj_size_t size)
-{
-    pjsip_msg_body *body = msg_body;
-    return pjsdp_print ((pjsdp_session_desc*)body->data, buf, size);
-}
-
-/* When media session has changed, call this function to update the cached body
- * information in the dialog. 
- */
-static pjsip_msg_body *create_msg_body (pjsip_dlg *dlg, pj_bool_t is_ack_msg)
-{
-    struct dialog_data *dlg_data = dlg->user_data;
-    pjsdp_session_desc *sdp;
-
-    sdp = pj_media_session_create_sdp (dlg_data->msession, dlg->pool, is_ack_msg);
-    if (!sdp) {
-	dlg_data->body = NULL;
-	return NULL;
-    }
-
-    /* For outgoing INVITE, if we offer "x-ms-message" line, then add a new
-     * "m=" line in the SDP.
-     */
-    if (dlg_data->x_ms_msg_session >= 0 && 
-	dlg_data->x_ms_msg_session >= (int)sdp->media_count) 
+    const char *state_names[] =
     {
-	pjsdp_media_desc *m = pj_pool_calloc(dlg->pool, 1, sizeof(*m));
-	sdp->media[sdp->media_count] = m;
-	dlg_data->x_ms_msg_session = sdp->media_count++;
-    }
+	"NULL",
+	"CALLING",
+	"INCOMING",
+	"EARLY",
+	"CONNECTING",
+	"CONFIRMED",
+	"DISCONNECTED",
+	"TERMINATED",
+    };
 
-    /*
-     * For "x-ms-message" line, remove all attributes and connection line etc.
-     */
-    if (dlg_data->x_ms_msg_session >= 0) {
-	pjsdp_media_desc *m = sdp->media[dlg_data->x_ms_msg_session];
-	if (m) {
-	    m->desc.media = pj_str("x-ms-message");
-	    m->desc.port = 5060;
-	    m->desc.transport = pj_str("sip");
-	    m->desc.fmt_count = 1;
-	    m->desc.fmt[0] = pj_str("null");
-	    m->attr_count = 0;
-	    m->conn = NULL;
-	}
-    }
+    PJ_UNUSED_ARG(e);
 
-    dlg_data->body = pj_pool_calloc(dlg->pool, 1, sizeof(*dlg_data->body));
-    dlg_data->body->content_type.type = pj_str("application");
-    dlg_data->body->content_type.subtype = pj_str("sdp");
-    dlg_data->body->len = 0;	/* ignored */
-    dlg_data->body->print_body = &print_msg_body;
+    PJ_LOG(3,(THIS_FILE, "INVITE session state changed to %s", state_names[inv->state]));
 
-    dlg_data->body->data = sdp;
-    return dlg_data->body;
-}
+    if (inv->state == PJSIP_INV_STATE_DISCONNECTED ||
+	inv->state == PJSIP_INV_STATE_TERMINATED)
+    {
+	if (inv == inv_session)
+	    inv_session = NULL;
 
-/* This callback will be called on every occurence of events in dialogs */
-static void dlg_on_all_events(pjsip_dlg *dlg, pjsip_dlg_event_e dlg_evt,
-			      pjsip_event *event )
-{
-    PJ_UNUSED_ARG(dlg_evt)
-    PJ_UNUSED_ARG(event)
+    } else {
 
-    PJ_LOG(4, (THIS_FILE, "dlg_on_all_events %p", dlg));
-}
+	inv_session = inv;
 
-/* This callback is called before each outgoing msg is sent (including 
- * retransmission). Application can override this notification if it wants
- * to modify the message before transmission or if it wants to do something
- * else for each transmission.
- */
-static void dlg_on_before_tx(pjsip_dlg *dlg, pjsip_transaction *tsx, 
-			     pjsip_tx_data *tdata, int ret_cnt)
-{
-    PJ_UNUSED_ARG(tsx)
-    PJ_UNUSED_ARG(tdata)
-
-    if (ret_cnt > 0) {
-	PJ_LOG(3, (THIS_FILE, "Dialog %s: retransmitting message (cnt=%d)", 
-			      dlg->obj_name, ret_cnt));
     }
 }
 
-/* This callback is called after a message is sent. */
-static void dlg_on_tx_msg(pjsip_dlg *dlg, pjsip_transaction *tsx, 
-			  pjsip_tx_data *tdata)
+static void ui_help(void)
 {
-    PJ_UNUSED_ARG(tsx)
-    PJ_UNUSED_ARG(tdata)
-
-    PJ_LOG(4, (THIS_FILE, "dlg_on_tx_msg %p", dlg));
+    puts("");
+    puts("Console keys:");
+    puts("  m    Make a call");
+    puts("  h    Hangup current call");
+    puts("  q    Quit");
+    puts("");
 }
 
-/* This callback is called on receipt of incoming message. */
-static void dlg_on_rx_msg(pjsip_dlg *dlg, pjsip_transaction *tsx, 
-			  pjsip_rx_data *rdata)
+static void ui_console_main(void)
 {
-    PJ_UNUSED_ARG(tsx)
-    PJ_UNUSED_ARG(rdata)
-    PJ_LOG(4, (THIS_FILE, "dlg_on_tx_msg %p", dlg));
-}
-
-/* This callback is called after dialog has sent INVITE */
-static void dlg_on_calling(pjsip_dlg *dlg, pjsip_transaction *tsx,
-			   pjsip_tx_data *tdata)
-{
-    PJ_UNUSED_ARG(tsx)
-    PJ_UNUSED_ARG(tdata)
-
-    pj_assert(tdata->msg->type == PJSIP_REQUEST_MSG &&
-	      tdata->msg->line.req.method.id == PJSIP_INVITE_METHOD &&
-	      tsx->method.id == PJSIP_INVITE_METHOD);
-
-    PJ_LOG(3, (THIS_FILE, "Dialog %s: start calling...", dlg->obj_name));
-}
-
-static void create_session_from_sdp( pjsip_dlg *dlg, pjsdp_session_desc *sdp)
-{
-    struct dialog_data *dlg_data = dlg->user_data;
-    pj_bool_t sdp_x_ms_msg_index = -1;
-    int i;
-    int mcnt;
-    const pj_media_stream_info *mi[PJSDP_MAX_MEDIA];
-    int has_active;
-    pj_media_sock_info sock_info;
-
-    /* Find "m=x-ms-message" line in the SDP. */
-    for (i=0; i<(int)sdp->media_count; ++i) {
-	if (pj_stricmp2(&sdp->media[i]->desc.media, "x-ms-message")==0)
-	    sdp_x_ms_msg_index = i;
-    }
-
-    /*
-     * Create media session.
-     */
-    pj_memset(&sock_info, 0, sizeof(sock_info));
-    sock_info.rtp_sock = global.rtp_sock;
-    sock_info.rtcp_sock = global.rtcp_sock;
-    pj_memcpy(&sock_info.rtp_addr_name, &global.rtp_sock_name, sizeof(pj_sockaddr_in));
-
-    dlg_data->msession = pj_media_session_create_from_sdp (global.mmgr, sdp, &sock_info);
-
-    /* A session will always be created, unless there is memory
-     * alloc problem.
-     */
-    pj_assert(dlg_data->msession);
-
-    /* See if we can take the offer by checking that we have at least
-     * one media stream active.
-     */
-    mcnt = pj_media_session_enum_streams(dlg_data->msession, PJSDP_MAX_MEDIA, mi);
-    for (i=0, has_active=0; i<mcnt; ++i) {
-	if (mi[i]->fmt_cnt>0 && mi[i]->dir!=PJ_MEDIA_DIR_NONE) {
-	    has_active = 1;
-	    break;
-	}
-    }
-
-    if (!has_active && sdp_x_ms_msg_index==-1) {
-	pjsip_tx_data *tdata;
-
-	/* Unable to accept remote's SDP. 
-	 * Answer with 488 (Not Acceptable Here)
-	 */
-	/* Create 488 response. */
-	tdata = pjsip_dlg_answer(dlg, PJSIP_SC_NOT_ACCEPTABLE_HERE);
-
-	/* Send response. */
-	if (tdata)
-	    pjsip_dlg_send_msg(dlg, tdata);
-	return;
-    }
-
-    dlg_data->x_ms_msg_session = sdp_x_ms_msg_index;
-
-    /* Create msg body to be used later in 2xx/response */
-    create_msg_body(dlg, 0);
-
-}
-
-/* This callback is called after an INVITE is received. */
-static void dlg_on_incoming(pjsip_dlg *dlg, pjsip_transaction *tsx,
-			    pjsip_rx_data *rdata)
-{
-    struct dialog_data *dlg_data;
-    pjsip_msg *msg;
-    pjsip_tx_data *tdata;
+    char keyin[10];
     char buf[128];
-    int len;
+    char *p;
+    pjsip_inv_session *inv;
 
-    PJ_UNUSED_ARG(tsx)
+    //ui_help();
 
-    pj_assert(rdata->msg->type == PJSIP_REQUEST_MSG &&
-	      rdata->msg->line.req.method.id == PJSIP_INVITE_METHOD &&
-	      tsx->method.id == PJSIP_INVITE_METHOD);
+    for (;;) {
 
-    /*
-     * Notify user!
-     */
-    PJ_LOG(3, (THIS_FILE, ""));
-    PJ_LOG(3, (THIS_FILE, "INCOMING CALL ON DIALOG %s!!", dlg->obj_name));
-    PJ_LOG(3, (THIS_FILE, ""));
-    len = pjsip_uri_print( PJSIP_URI_IN_FROMTO_HDR, 
-			   (pjsip_name_addr*)dlg->remote.info->uri, 
-			   buf, sizeof(buf)-1);
-    if (len > 0) {
-	buf[len] = '\0';
-	PJ_LOG(3,(THIS_FILE, "From:\t%s", buf));
-    }
-    len = pjsip_uri_print( PJSIP_URI_IN_FROMTO_HDR, 
-			   (pjsip_name_addr*)dlg->local.info->uri, 
-			   buf, sizeof(buf)-1);
-    if (len > 0) {
-	buf[len] = '\0';
-	PJ_LOG(3,(THIS_FILE, "To:\t%s", buf));
-    }
-    PJ_LOG(3, (THIS_FILE, "Press 'a' to answer, or 'h' to hangup!!", dlg->obj_name));
-    PJ_LOG(3, (THIS_FILE, ""));
-
-    /*
-     * Process incoming dialog.
-     */
-
-    dlg_data = pj_pool_calloc(dlg->pool, 1, sizeof(struct dialog_data));
-    dlg->user_data = dlg_data;
-
-    /* Update contact. */
-    pjsip_dlg_set_contact(dlg, &global.contact);
-
-    /* Initialize credentials. */
-    pjsip_dlg_set_credentials(dlg, global.cred_count, global.cred_info);
-
-    /* Create media session if the request has "application/sdp" body. */
-    msg = rdata->msg;
-    if (msg->body && 
-	pj_stricmp2(&msg->body->content_type.type, "application")==0 &&
-	pj_stricmp2(&msg->body->content_type.subtype, "sdp")==0)
-    {
-	pjsdp_session_desc *sdp;
-
-	/* Parse SDP body, and instantiate media session based on remote's SDP.
-	 * Then create our SDP body from the session.
-	 */
-	sdp = pjsdp_parse (msg->body->data, msg->body->len, rdata->pool);
-	if (!sdp)
-	    goto send_answer;
-
-	create_session_from_sdp(dlg, sdp);
-
-    } else if (msg->body) {
-	/* The request has a message body other than "application/sdp" */
-	pjsip_accept_hdr *accept;
-
-	/* Create response. */
-	tdata = pjsip_dlg_answer(dlg, PJSIP_SC_UNSUPPORTED_MEDIA_TYPE);
-
-	/* Add "Accept" header. */
-	accept = pjsip_accept_hdr_create(tdata->pool);
-	accept->values[0] = pj_str("application/sdp");
-	accept->count = 1;
-	pjsip_msg_add_hdr(tdata->msg, (pjsip_hdr*)accept);
-
-	/* Send response. */
-	pjsip_dlg_send_msg(dlg, tdata);
-	return;
-
-    } else {
-	/* The request has no message body. We can take this request, but
-	 * no media session will be activated.
-	 */
-	/* Nothing to do here. */
-    }
-
-send_answer:
-    /* Immediately answer with 100 (or 180? */
-    tdata = pjsip_dlg_answer( dlg, PJSIP_SC_RINGING );
-    pjsip_dlg_send_msg(dlg, tdata);
-
-    /* Set current dialog to this dialog if we don't currently have
-     * current dialog.
-     */
-    if (global.cur_dlg == NULL) {
-	global.cur_dlg = dlg;
-    }
-
-    /* Auto-answer if option is specified. */
-    if (global.auto_answer >= 0) {
-	pj_time_val delay = { 0, 0};
-	struct dialog_data *dlg_data = dlg->user_data;
-
-	PJ_LOG(4, (THIS_FILE, "Scheduling auto-answer in %d seconds", 
-			      global.auto_answer));
-
-	delay.sec = global.auto_answer;
-	dlg_data->auto_timer.user_data = dlg;
-	dlg_data->auto_timer.id = AUTO_ANSWER;
-	dlg_data->auto_timer.cb = &dlg_auto_timer_callback;
-	dlg_data->has_auto_timer = 1;
-	pjsip_endpt_schedule_timer(dlg->ua->endpt, &dlg_data->auto_timer, &delay);
-    }
-}
-
-/* This callback is called when dialog has sent/received a provisional response
- * to INVITE.
- */
-static void dlg_on_provisional(pjsip_dlg *dlg, pjsip_transaction *tsx,
-			       pjsip_event *event)
-{
-    const char *action;
-
-    pj_assert((event->src_type == PJSIP_EVENT_TX_MSG &&
-	       event->src.tdata->msg->type == PJSIP_RESPONSE_MSG &&
-	       event->src.tdata->msg->line.status.code/100 == 1 &&
-	       tsx->method.id == PJSIP_INVITE_METHOD) 
-	       ||
-	       (event->src_type == PJSIP_EVENT_RX_MSG &&
-	       event->src.rdata->msg->type == PJSIP_RESPONSE_MSG &&
-	       event->src.rdata->msg->line.status.code/100 == 1 &&
-	       tsx->method.id == PJSIP_INVITE_METHOD));
-
-    if (event->src_type == PJSIP_EVENT_TX_MSG)
-	action = "Sending";
-    else
-	action = "Received";
-
-    PJ_LOG(3, (THIS_FILE, "Dialog %s: %s %d (%s)", 
-			  dlg->obj_name, action, tsx->status_code,
-			  pjsip_get_status_text(tsx->status_code)->ptr));
-}
-
-/* This callback is called when 200 response to INVITE is sent/received. */
-static void dlg_on_connecting(pjsip_dlg *dlg, pjsip_event *event)
-{
-    struct dialog_data *dlg_data = dlg->user_data;
-    const char *action;
-
-    pj_assert((event->src_type == PJSIP_EVENT_TX_MSG &&
-	       event->src.tdata->msg->type == PJSIP_RESPONSE_MSG &&
-	       event->src.tdata->msg->line.status.code/100 == 2)
-	       ||
-	       (event->src_type == PJSIP_EVENT_RX_MSG &&
-	       event->src.rdata->msg->type == PJSIP_RESPONSE_MSG &&
-	       event->src.rdata->msg->line.status.code/100 == 2));
-
-    if (event->src_type == PJSIP_EVENT_RX_MSG)
-	action = "Received";
-    else
-	action = "Sending";
-
-    PJ_LOG(3, (THIS_FILE, "Dialog %s: %s 200 (OK)", dlg->obj_name, action));
-
-    if (event->src_type == PJSIP_EVENT_RX_MSG) {
-	/* On receipt of 2xx response, negotiate our media capability
-	 * and start media.
-	 */
-	pjsip_msg *msg = event->src.rdata->msg;
-	pjsip_msg_body *body;
-	pjsdp_session_desc *sdp;
-
-	/* Get SDP from message. */
-
-	/* Ignore if no SDP body is present. */
-	body = msg->body;
-	if (!body) {
-	    PJ_LOG(3, (THIS_FILE, "Dialog %s: the 200/OK response has no body!",
-				  dlg->obj_name));
-	    return;
-	}
-
-	if (pj_stricmp2(&body->content_type.type, "application") != 0 &&
-	    pj_stricmp2(&body->content_type.subtype, "sdp") != 0) 
-	{
-	    PJ_LOG(3, (THIS_FILE, "Dialog %s: the 200/OK response has no SDP body!",
-				   dlg->obj_name));
-	    return;
-	}
-
-	/* Got what seems to be a SDP content. Parse it. */
-	sdp = pjsdp_parse (body->data, body->len, event->src.rdata->pool);
-	if (!sdp) {
-	    PJ_LOG(3, (THIS_FILE, "Dialog %s: SDP syntax error!",
-				  dlg->obj_name));
-	    return;
-	}
-
-	/* Negotiate media session with remote's media capability. */
-	if (pj_media_session_update (dlg_data->msession, sdp) != 0) {
-	    PJ_LOG(3, (THIS_FILE, "Dialog %s: media session update error!",
-				  dlg->obj_name));
-	    return;
-	}
-
-	/* Update the saved SDP body because media session has changed. 
-	 * Also set ack flag to '1', because we only want to send one format/
-	 * codec for each media streams.
-	 */
-	create_msg_body(dlg, 1);
-
-	/* Activate media. */
-	pj_media_session_activate (dlg_data->msession);
-
-    } else {
-	pjsip_msg *msg = event->src.tdata->msg;
-
-	if (msg->body) {
-	    /* On transmission of 2xx response, start media session. */
-	    pj_media_session_activate (dlg_data->msession);
-	}
-    }
-
-}
-
-/* This callback is called when ACK to initial INVITE is sent/received. */
-static void dlg_on_established(pjsip_dlg *dlg, pjsip_event *event)
-{
-    const char *action;
-
-    pj_assert((event->src_type == PJSIP_EVENT_TX_MSG &&
-	       event->src.tdata->msg->type == PJSIP_REQUEST_MSG &&
-	       event->src.tdata->msg->line.req.method.id == PJSIP_ACK_METHOD)
-	       ||
-	       (event->src_type == PJSIP_EVENT_RX_MSG &&
-	       event->src.rdata->msg->type == PJSIP_REQUEST_MSG &&
-	       event->src.rdata->msg->line.req.method.id == PJSIP_ACK_METHOD));
-
-    if (event->src_type == PJSIP_EVENT_RX_MSG)
-	action = "Received";
-    else
-	action = "Sending";
-
-    PJ_LOG(3, (THIS_FILE, "Dialog %s: %s ACK, dialog is ESTABLISHED", 
-			  dlg->obj_name, action));
-
-    /* Attach SDP body for outgoing ACK. */
-    if (event->src_type == PJSIP_EVENT_TX_MSG &&
-	event->src.tdata->msg->line.req.method.id == PJSIP_ACK_METHOD)
-    {
-	struct dialog_data *dlg_data = dlg->user_data;
-	event->src.tdata->msg->body = dlg_data->body;
-    }
-
-    /* Auto-hangup if option is specified. */
-    if (global.auto_hangup >= 0) {
-	pj_time_val delay = { 0, 0};
-	struct dialog_data *dlg_data = dlg->user_data;
-
-	PJ_LOG(4, (THIS_FILE, "Scheduling auto-hangup in %d seconds", 
-			      global.auto_hangup));
-
-	delay.sec = global.auto_hangup;
-	dlg_data->auto_timer.user_data = dlg;
-	dlg_data->auto_timer.id = AUTO_HANGUP;
-	dlg_data->auto_timer.cb = &dlg_auto_timer_callback;
-	dlg_data->has_auto_timer = 1;
-	pjsip_endpt_schedule_timer(dlg->ua->endpt, &dlg_data->auto_timer, &delay);
-    }
-}
-
-
-/* This callback is called when dialog is disconnected (because of final
- * response, BYE, or timer).
- */
-static void dlg_on_disconnected(pjsip_dlg *dlg, pjsip_event *event)
-{
-    struct dialog_data *dlg_data = dlg->user_data;
-    int status_code;
-    const pj_str_t *reason;
-    
-    PJ_UNUSED_ARG(event)
-
-    /* Cancel auto-answer/auto-hangup timer. */
-    if (dlg_data->has_auto_timer) {
-	pjsip_endpt_cancel_timer(dlg->ua->endpt, &dlg_data->auto_timer);
-	dlg_data->has_auto_timer = 0;
-    }
-
-    if (dlg->invite_tsx)
-	status_code = dlg->invite_tsx->status_code;
-    else
-	status_code = 200;
-
-    if (event->obj.tsx->method.id == PJSIP_INVITE_METHOD) {
-	if (event->src_type == PJSIP_EVENT_RX_MSG)
-	    reason = &event->src.rdata->msg->line.status.reason;
-	else if (event->src_type == PJSIP_EVENT_TX_MSG)
-	    reason = &event->src.tdata->msg->line.status.reason;
-	else
-	    reason = pjsip_get_status_text(event->obj.tsx->status_code);
-    } else {
-	reason = &event->obj.tsx->method.name;
-    }
-
-    PJ_LOG(3, (THIS_FILE, "Dialog %s: DISCONNECTED! Reason=%d (%.*s)", 
-			  dlg->obj_name, status_code, 
-			  reason->slen, reason->ptr));
-
-    if (dlg_data->msession) {
-	pj_media_session_destroy (dlg_data->msession);
-	dlg_data->msession = NULL;
-    }
-}
-
-/* This callback is called when dialog is about to be destroyed. */
-static void dlg_on_terminated(pjsip_dlg *dlg)
-{
-    PJ_LOG(3, (THIS_FILE, "Dialog %s: terminated!", dlg->obj_name));
-
-    /* If current dialog is equal to this dialog, update it. */
-    if (global.cur_dlg == dlg) {
-	global.cur_dlg = global.cur_dlg->next;
-	if (global.cur_dlg == (void*)&global.user_agent->dlg_list) {
-	    global.cur_dlg = NULL;
-	}
-    }
-}
-
-/* This callback is called for any requests when dialog is established. */
-static void dlg_on_mid_call_evt	(pjsip_dlg *dlg, pjsip_event *event)
-{
-    pjsip_transaction *tsx = event->obj.tsx;
-
-    if (event->src_type == PJSIP_EVENT_RX_MSG &&
-	event->src.rdata->msg->type == PJSIP_REQUEST_MSG) 
-    {
-	if (event->src.rdata->cseq->method.id == PJSIP_INVITE_METHOD) {
-	    /* Re-invitation. */
-	    pjsip_tx_data *tdata;
-
-	    PJ_LOG(3,(THIS_FILE, "Dialog %s: accepting re-invitation (dummy)",
-				 dlg->obj_name));
-	    tdata = pjsip_dlg_answer(dlg, 200);
-	    if (tdata) {
-		struct dialog_data *dlg_data = dlg->user_data;
-		tdata->msg->body = dlg_data->body;
-		pjsip_dlg_send_msg(dlg, tdata);
-	    }
-	} else {
-	    /* Don't worry, endpoint will answer with 500 or whetever. */
-	}
-
-    } else if (tsx->status_code/100 == 2) {
-	PJ_LOG(3,(THIS_FILE, "Dialog %s: outgoing %.*s success: %d (%s)",
-		  dlg->obj_name, 
-		  tsx->method.name.slen, tsx->method.name.ptr,
-		  tsx->status_code, 
-		  pjsip_get_status_text(tsx->status_code)->ptr));
-
-
-    } else if (tsx->status_code >= 300) {
-	pj_bool_t report_failure = PJ_TRUE;
-
-	/* Check for authentication failures. */
-	if (tsx->status_code==401 || tsx->status_code==407) {
-	    pjsip_tx_data *tdata;
-	    tdata = pjsip_auth_reinit_req( global.endpt,
-					   dlg->pool, &dlg->auth_sess,
-					   dlg->cred_count, dlg->cred_info,
-					   tsx->last_tx, event->src.rdata );
-	    if (tdata) {
-		int rc;
-		rc = pjsip_dlg_send_msg( dlg, tdata);
-		report_failure = (rc != 0);
-	    }
-	}
-	if (report_failure) {
-	    const pj_str_t *reason;
-	    if (event->src_type == PJSIP_EVENT_RX_MSG) {
-		reason = &event->src.rdata->msg->line.status.reason;
-	    } else {
-		reason = pjsip_get_status_text(tsx->status_code);
-	    }
-	    PJ_LOG(2,(THIS_FILE, "Dialog %s: outgoing request failed: %d (%.*s)",
-		      dlg->obj_name, tsx->status_code, 
-		      reason->slen, reason->ptr));
-	}
-    }
-}
-
-/* Initialize sockets and optionally get the public address via STUN. */
-static pj_status_t init_sockets()
-{
-    enum { 
-	RTP_START_PORT = 4000,
-	RTP_RANDOM_START = 2,
-	RTP_RETRY = 10 
-    };
-    enum {
-	SIP_SOCK,
-	RTP_SOCK,
-	RTCP_SOCK,
-    };
-    int i;
-    int rtp_port;
-    pj_sock_t sock[3];
-    pj_sockaddr_in mapped_addr[3];
-
-    for (i=0; i<3; ++i)
-	sock[i] = PJ_INVALID_SOCKET;
-
-    /* Create and bind SIP UDP socket. */
-    sock[SIP_SOCK] = pj_sock_socket(PJ_AF_INET, PJ_SOCK_DGRAM, 0, 0);
-    if (sock[SIP_SOCK] == PJ_INVALID_SOCKET) {
-	PJ_LOG(2,(THIS_FILE, "Unable to create socket"));
-	goto on_error;
-    }
-    if (pj_sock_bind_in(sock[SIP_SOCK], 0, (pj_uint16_t)global.sip_port) != 0) {
-	PJ_LOG(2,(THIS_FILE, "Unable to bind to SIP port"));
-	goto on_error;
-    }
-
-    /* Initialize start of RTP port to try. */
-    rtp_port = RTP_START_PORT + (pj_rand() % RTP_RANDOM_START) / 2;
-
-    /* Loop retry to bind RTP and RTCP sockets. */
-    for (i=0; i<RTP_RETRY; ++i, rtp_port += 2) {
-
-	/* Create and bind RTP socket. */
-	sock[RTP_SOCK] = pj_sock_socket(PJ_AF_INET, PJ_SOCK_DGRAM, 0, 0);
-	if (sock[RTP_SOCK] == PJ_INVALID_SOCKET)
-	    goto on_error;
-	if (pj_sock_bind_in(sock[RTP_SOCK], 0, (pj_uint16_t)rtp_port) != 0) {
-	    pj_sock_close(sock[RTP_SOCK]); sock[RTP_SOCK] = PJ_INVALID_SOCKET;
-	    continue;
-	}
-
-	/* Create and bind RTCP socket. */
-	sock[RTCP_SOCK] = pj_sock_socket(PJ_AF_INET, PJ_SOCK_DGRAM, 0, 0);
-	if (sock[RTCP_SOCK] == PJ_INVALID_SOCKET)
-	    goto on_error;
-	if (pj_sock_bind_in(sock[RTCP_SOCK], 0, (pj_uint16_t)(rtp_port+1)) != 0) {
-	    pj_sock_close(sock[RTP_SOCK]); sock[RTP_SOCK] = PJ_INVALID_SOCKET;
-	    pj_sock_close(sock[RTCP_SOCK]); sock[RTCP_SOCK] = PJ_INVALID_SOCKET;
-	    continue;
-	}
-
-	/*
-	 * If we're configured to use STUN, then find out the mapped address,
-	 * and make sure that the mapped RTCP port is adjacent with the RTP.
-	 */
-	if (global.stun_port1 == 0) {
-	    pj_str_t hostname;
-	    pj_sockaddr_in addr;
-
-	    /* Get local IP address. */
-	    char hostname_buf[PJ_MAX_HOSTNAME];
-	    if (gethostname(hostname_buf, sizeof(hostname_buf)))
-		goto on_error;
-	    hostname = pj_str(hostname_buf);
-
-	    pj_memset( &addr, 0, sizeof(addr));
-	    addr.sin_family = PJ_AF_INET;
-	    if (pj_sockaddr_set_str_addr( &addr, &hostname) != PJ_SUCCESS)
-		goto on_error;
-
-	    for (i=0; i<3; ++i)
-		pj_memcpy(&mapped_addr[i], &addr, sizeof(addr));
-
-	    mapped_addr[SIP_SOCK].sin_port = pj_htons((pj_uint16_t)global.sip_port);
-	    mapped_addr[RTP_SOCK].sin_port = pj_htons((pj_uint16_t)rtp_port);
-	    mapped_addr[RTCP_SOCK].sin_port = pj_htons((pj_uint16_t)(rtp_port+1));
-	    break;
-	} else {
-	    pj_status_t rc;
-	    rc = pj_stun_get_mapped_addr( global.pf, 3, sock,
-					  &global.stun_srv1, global.stun_port1,
-					  &global.stun_srv2, global.stun_port2,
-					  mapped_addr);
-	    if (rc != 0) {
-		PJ_LOG(3,(THIS_FILE, "Error: %s", pj_stun_get_err_msg(rc)));
-		goto on_error;
-	    }
-
-	    if (pj_ntohs(mapped_addr[2].sin_port) == pj_ntohs(mapped_addr[1].sin_port)+1)
-		break;
-
-	    pj_sock_close(sock[RTP_SOCK]); sock[RTP_SOCK] = PJ_INVALID_SOCKET;
-	    pj_sock_close(sock[RTCP_SOCK]); sock[RTCP_SOCK] = PJ_INVALID_SOCKET;
-	}
-    }
-
-    if (sock[RTP_SOCK] == PJ_INVALID_SOCKET) {
-	PJ_LOG(2,(THIS_FILE, "Unable to find appropriate RTP/RTCP ports combination"));
-	goto on_error;
-    }
-
-    global.sip_sock = sock[SIP_SOCK];
-    pj_memcpy(&global.sip_sock_name, &mapped_addr[SIP_SOCK], sizeof(pj_sockaddr_in));
-    global.rtp_sock = sock[RTP_SOCK];
-    pj_memcpy(&global.rtp_sock_name, &mapped_addr[RTP_SOCK], sizeof(pj_sockaddr_in));
-    global.rtcp_sock = sock[RTCP_SOCK];
-    pj_memcpy(&global.rtcp_sock_name, &mapped_addr[RTCP_SOCK], sizeof(pj_sockaddr_in));
-
-    PJ_LOG(4,(THIS_FILE, "SIP UDP socket reachable at %s:%d",
-	      pj_inet_ntoa(global.sip_sock_name.sin_addr), 
-	      pj_ntohs(global.sip_sock_name.sin_port)));
-    PJ_LOG(4,(THIS_FILE, "RTP socket reachable at %s:%d",
-	      pj_inet_ntoa(global.rtp_sock_name.sin_addr), 
-	      pj_ntohs(global.rtp_sock_name.sin_port)));
-    PJ_LOG(4,(THIS_FILE, "RTCP UDP socket reachable at %s:%d",
-	      pj_inet_ntoa(global.rtcp_sock_name.sin_addr), 
-	      pj_ntohs(global.rtcp_sock_name.sin_port)));
-    return 0;
-
-on_error:
-    for (i=0; i<3; ++i) {
-	if (sock[i] != PJ_INVALID_SOCKET)
-	    pj_sock_close(sock[i]);
-    }
-    return -1;
-}
-
-static void log_function(int level, const char *buffer, int len)
-{
-    /* Write to both stdout and file. */
-    if (level <= global.app_log_level)
-	pj_log_to_stdout(level, buffer, len);
-    if (global.log_file) {
-	fwrite(buffer, len, 1, global.log_file);
-	fflush(global.log_file);
-    }
-}
-
-/* Initialize stack. */
-static pj_status_t init_stack()
-{
-    pj_status_t status;
-    pj_sockaddr_in bind_addr;
-    pj_sockaddr_in bind_name;
-    const char *local_addr;
-    static char local_uri[128];
-
-    /* Optionally set logging file. */
-    if (global.log_filename) {
-	global.log_file = fopen(global.log_filename, "wt");
-    }
-
-    /* Initialize endpoint. This will also call initialization to all the
-     * modules.
-     */
-    global.endpt = pjsip_endpt_create(global.pf);
-    if (global.endpt == NULL) {
-	return -1;
-    }
-
-    /* Set dialog callback. */
-    pjsip_ua_set_dialog_callback(global.user_agent, &dlg_callback);
-
-    /* Init listener's bound address and port. */
-    pj_sockaddr_init2(&bind_addr, "0.0.0.0", global.sip_port);
-    pj_sockaddr_init(&bind_name, pj_gethostname(), global.sip_port);
-
-    /* Add UDP transport listener. */
-    status = pjsip_endpt_create_udp_listener( global.endpt, global.sip_sock,
-					      &global.sip_sock_name);
-    if (status != 0)
-	return -1;
-
-    local_addr = pj_inet_ntoa(global.sip_sock_name.sin_addr);
-
-#if PJ_HAS_TCP
-    /* Add TCP transport listener. */
-    status = pjsip_endpt_create_listener( global.endpt, PJSIP_TRANSPORT_TCP, 
-					  &bind_addr, &bind_name);
-    if (status != 0)
-	return -1;
-#endif
-
-    /* Determine user_id to be put in Contact */
-    if (global.local_uri.slen) {
-	pj_pool_t *pool = pj_pool_create(global.pf, "parser", 1024, 0, NULL);
-	pjsip_uri *uri;
-
-	uri = pjsip_parse_uri(pool, global.local_uri.ptr, global.local_uri.slen, 0);
-	if (uri) {
-	    if (pj_stricmp2(pjsip_uri_get_scheme(uri), "sip")==0) {
-		pjsip_sip_uri *url = (pjsip_sip_uri*)pjsip_uri_get_uri(uri);
-		if (url->user.slen)
-		    strncpy(global.user_id, url->user.ptr, url->user.slen);
-	    }
-	} 
-	pj_pool_release(pool);
-    } 
-    
-    if (global.user_id[0]=='\0') {
-	pj_native_strcpy(global.user_id, "user");
-    }
-
-    /* build contact */
-    global.real_contact.ptr = local_uri;
-    global.real_contact.slen = 
-	sprintf(local_uri, "<sip:%s@%s:%d>", global.user_id, local_addr, global.sip_port);
-
-    if (global.contact.slen == 0)
-	global.contact = global.real_contact;
-
-    /* initialize local_uri with contact if it's not specified in cmdline */
-    if (global.local_uri.slen == 0)
-	global.local_uri = global.contact;
-
-    /* Init proxy. */
-    if (global.proxy.slen || global.outbound_proxy.slen) {
-	int count = 0;
-	pj_str_t proxy_url[2];
-
-	if (global.outbound_proxy.slen) {
-	    proxy_url[count++] = global.outbound_proxy;
-	}
-	if (global.proxy.slen) {
-	    proxy_url[count++] = global.proxy;
-	}
-
-	if (pjsip_endpt_set_proxies(global.endpt, count, proxy_url) != 0) {
-	    PJ_LOG(2,(THIS_FILE, "Error setting proxy address!"));
-	    return -1;
-	}
-    }
-
-    /* initialize SIP registration if registrar is configured */
-    if (global.registrar_uri.slen) {
-	global.regc = pjsip_regc_create( global.endpt, NULL, &regc_cb);
-	pjsip_regc_init( global.regc, &global.registrar_uri, 
-			 &global.local_uri, 
-			 &global.local_uri,
-			 1, &global.contact, 
-			 global.reg_timeout);
-	pjsip_regc_set_credentials( global.regc, global.cred_count, global.cred_info );
-    }
-
-    return PJ_SUCCESS;
-}
-
-/* Worker thread function, only used when threading is enabled. */
-static void *PJ_THREAD_FUNC worker_thread(void *unused)
-{
-    PJ_UNUSED_ARG(unused)
-
-    while (!global.worker_quit_flag) {
+#ifdef NO_WORKER_THREAD
 	pj_time_val timeout = { 0, 10 };
-	pjsip_endpt_handle_events (global.endpt, &timeout);
-    }
-    return NULL;
-}
+	pjsip_endpt_handle_events (pjsua.endpt, &timeout);
 
-
-/* Make call to the specified URI. */
-static pjsip_dlg *make_call(pj_str_t *remote_uri)
-{
-    pjsip_dlg *dlg;
-    pj_str_t local = global.contact;
-    pj_str_t remote = *remote_uri;
-    struct dialog_data *dlg_data;
-    pjsip_tx_data *tdata;
-    pj_media_sock_info sock_info;
-
-    /* Create new dialog instance. */
-    dlg = pjsip_ua_create_dialog(global.user_agent, PJSIP_ROLE_UAC);
-
-    /* Attach our own user data. */
-    dlg_data = pj_pool_calloc(dlg->pool, 1, sizeof(struct dialog_data));
-    dlg->user_data = dlg_data;
-
-    /* Create media session. */
-    pj_memset(&sock_info, 0, sizeof(sock_info));
-    sock_info.rtp_sock = global.rtp_sock;
-    sock_info.rtcp_sock = global.rtcp_sock;
-    pj_memcpy(&sock_info.rtp_addr_name, &global.rtp_sock_name, sizeof(pj_sockaddr_in));
-
-    dlg_data->msession = pj_media_session_create (global.mmgr, &sock_info);
-    dlg_data->x_ms_msg_session = -1;
-
-    if (global.offer_x_ms_msg) {
-	const pj_media_stream_info *minfo[32];
-	unsigned cnt;
-
-	cnt = pj_media_session_enum_streams(dlg_data->msession, 32, minfo);
-	if (cnt > 0)
-	    dlg_data->x_ms_msg_session = cnt;
-    } 
-
-    /* Initialize dialog with local and remote URI. */
-    if (pjsip_dlg_init(dlg, &local, &remote, NULL) != PJ_SUCCESS) {
-	pjsip_ua_destroy_dialog(dlg);
-	return NULL;
-    }
-
-    /* Initialize credentials. */
-    pjsip_dlg_set_credentials(dlg, global.cred_count, global.cred_info);
-
-    /* Send INVITE! */
-    tdata = pjsip_dlg_invite(dlg);
-    tdata->msg->body = create_msg_body (dlg, 0);
-
-    if (pjsip_dlg_send_msg(dlg, tdata) != PJ_SUCCESS) {
-	pjsip_ua_destroy_dialog(dlg);
-	return NULL;
-    }
-
-    return dlg;
-}
-
-/*
- * Callback to receive incoming IM message.
- */
-static int on_incoming_im_msg(pjsip_rx_data *rdata)
-{
-    pjsip_msg *msg = rdata->msg;
-    pjsip_msg_body *body = msg->body;
-    int len;
-    char to[128], from[128];
-
-
-    len = pjsip_uri_print( PJSIP_URI_IN_CONTACT_HDR, 
-			   rdata->from->uri, from, sizeof(from));
-    if (len > 0) from[len] = '\0';
-    else pj_native_strcpy(from, "<URL too long..>");
-
-    len = pjsip_uri_print( PJSIP_URI_IN_CONTACT_HDR, 
-			   rdata->to->uri, to, sizeof(to));
-    if (len > 0) to[len] = '\0';
-    else pj_native_strcpy(to, "<URL too long..>");
-
-    PJ_LOG(3,(THIS_FILE, "Incoming instant message:"));
-    
-    printf("----- BEGIN INSTANT MESSAGE ----->\n");
-    printf("From:\t%s\n", from);
-    printf("To:\t%s\n", to);
-    printf("Body:\n%.*s\n", (body ? body->len : 0), (body ? (char*)body->data : ""));
-    printf("<------ END INSTANT MESSAGE ------\n");
-
-    fflush(stdout);
-
-    /* Must answer with final response. */
-    return 200;
-}
-
-/*
- * Input URL.
- */
-static pj_str_t *ui_input_url(pj_str_t *out, char *buf, int len, int *selection)
-{
-    int i;
-
-    *selection = -1;
-
-    printf("\nBuddy list:\n");
-    printf("---------------------------------------\n");
-    for (i=0; i<global.buddy_cnt; ++i) {
-	printf(" %d\t%s  <%s>\n", i+1, global.buddy[i].ptr,
-		(global.buddy_status[i]?"Online":"Offline"));
-    }
-    printf("-------------------------------------\n");
-
-    printf("Choices\n"
-	   "\t0        For current dialog.\n"
-	   "\t[1-%02d]   Select from buddy list\n"
-	   "\tURL      An URL\n"
-	   , global.buddy_cnt);
-    printf("Input: ");
-
-    fflush(stdout);
-    fgets(buf, len, stdin);
-    buf[strlen(buf)-1] = '\0'; /* remove trailing newline. */
-
-    while (isspace(*buf)) ++buf;
-
-    if (!*buf || *buf=='\n' || *buf=='\r')
-	return NULL;
-
-    i = atoi(buf);
-
-    if (i == 0) {
-	if (isdigit(*buf)) {
-	    *selection = 0;
-	    *out = pj_str("0");
-	    return out;
-	} else {
-	    if (verify_sip_url(buf) != 0) {
-		puts("Invalid URL specified!");
-		return NULL;
-	    }
-	    *out = pj_str(buf);
-	    return out;
-	}
-    } else if (i > global.buddy_cnt || i < 0) {
-	printf("Error: invalid selection!\n");
-	return NULL;
-    } else {
-	*out = global.buddy[i-1];
-	*selection = i;
-	return out;
-    }
-}
-
-
-static void generic_request_callback( void *token, pjsip_event *event )
-{
-    pjsip_transaction *tsx = event->obj.tsx;
-    
-    PJ_UNUSED_ARG(token)
-
-    if (tsx->status_code/100 == 2) {
-	PJ_LOG(3,(THIS_FILE, "Outgoing %.*s %d (%s)",
-		  event->obj.tsx->method.name.slen,
-		  event->obj.tsx->method.name.ptr,
-		  tsx->status_code,
-		  pjsip_get_status_text(tsx->status_code)->ptr));
-    } else if (tsx->status_code==401 || tsx->status_code==407)  {
-	pjsip_tx_data *tdata;
-	tdata = pjsip_auth_reinit_req( global.endpt,
-				       global.pool, NULL, global.cred_count, global.cred_info,
-				       tsx->last_tx, event->src.rdata);
-	if (tdata) {
-	    int rc;
-	    pjsip_cseq_hdr *cseq;
-	    cseq = (pjsip_cseq_hdr*)pjsip_msg_find_hdr(tdata->msg, PJSIP_H_CSEQ, NULL);
-	    cseq->cseq++;
-	    rc = pjsip_endpt_send_request( global.endpt, tdata, -1, NULL, 
-					    &generic_request_callback);
-	    if (rc == 0)
-		return;
-	}
-	PJ_LOG(2,(THIS_FILE, "Outgoing %.*s failed, status=%d (%s)",
-		  event->obj.tsx->method.name.slen,
-		  event->obj.tsx->method.name.ptr,
-		  event->obj.tsx->status_code,
-		  pjsip_get_status_text(event->obj.tsx->status_code)->ptr));
-    } else {
-	const pj_str_t *reason;
-	if (event->src_type == PJSIP_EVENT_RX_MSG)
-	    reason = &event->src.rdata->msg->line.status.reason;
-	else
-	    reason = pjsip_get_status_text(tsx->status_code);
-	PJ_LOG(2,(THIS_FILE, "Outgoing %.*s failed, status=%d (%.*s)",
-		  event->obj.tsx->method.name.slen,
-		  event->obj.tsx->method.name.ptr,
-		  event->obj.tsx->status_code,
-		  reason->slen, reason->ptr));
-    }
-}
-
-
-static void ui_send_im_message()
-{
-    char line[100];
-    char text_buf[100];
-    pj_str_t str;
-    pj_str_t text_msg;
-    int selection, rc;
-    pjsip_tx_data *tdata;
-  
-    if (ui_input_url(&str, line, sizeof(line), &selection) == NULL)
-	return;
-
-	
-    printf("Enter text to send (empty to cancel): "); fflush(stdout);
-    fgets(text_buf, sizeof(text_buf), stdin);
-    text_buf[strlen(text_buf)-1] = '\0';
-    if (!*text_buf)
-	return;
-
-    text_msg = pj_str(text_buf);
-    
-    if (selection==0) {
-	pjsip_method message_method;
-	pj_str_t str_MESSAGE = { "MESSAGE", 7 };
-
-	/* Send IM to current dialog. */
-	if (global.cur_dlg == NULL || global.cur_dlg->state != PJSIP_DIALOG_STATE_ESTABLISHED) {
-	    printf("No current dialog or dialog state is not ESTABLISHED!\n");
-	    return;
-	}
-
-	pjsip_method_init( &message_method, global.cur_dlg->pool, &str_MESSAGE);
-	tdata = pjsip_dlg_create_request( global.cur_dlg, &message_method, -1 );
-
-	if (tdata) {
-	    /* Create message body for the text. */
-	    pjsip_msg_body *body = pj_pool_calloc(tdata->pool, 1, sizeof(*body));
-	    body->content_type.type = pj_str("text");
-	    body->content_type.subtype = pj_str("plain");
-	    body->data = pj_pool_alloc(tdata->pool, text_msg.slen);
-	    pj_memcpy(body->data, text_msg.ptr, text_msg.slen);
-	    body->len = text_msg.slen;
-	    body->print_body = &pjsip_print_text_body;
-
-	    /* Assign body to message, and send the message! */
-	    tdata->msg->body = body;
-	    pjsip_dlg_send_msg( global.cur_dlg, tdata );
-	}
-
-    } else {
-	/* Send IM to buddy list. */
-	pjsip_method message;
-	static pj_str_t MESSAGE = { "MESSAGE", 7 };
-	pjsip_method_init_np(&message, &MESSAGE);
-	tdata = pjsip_endpt_create_request(global.endpt, &message, 
-					   &str,
-					   &global.real_contact,
-				           &str, &global.real_contact, NULL, -1, 
-					   &text_msg);
-	if (!tdata) {
-	    puts("Error creating request");
-	    return;
-	}
-	rc = pjsip_endpt_send_request(global.endpt, tdata, -1, NULL, &generic_request_callback);
-	if (rc == 0) {
-	    printf("Sending IM message %d\n", global.im_counter);
-	    ++global.im_counter;
-	} else {
-	    printf("Error: unable to send IM message!\n");
-	}
-    }
-}
-
-static void ui_send_options()
-{
-    char line[100];
-    pj_str_t str;
-    int selection, rc;
-    pjsip_tx_data *tdata;
-    pjsip_method options;
-
-    if (ui_input_url(&str, line, sizeof(line), &selection) == NULL)
-	return;
-
-    pjsip_method_set( &options, PJSIP_OPTIONS_METHOD );
-
-    if (selection == 0) {
-	/* Send OPTIONS to current dialog. */
-	tdata = pjsip_dlg_create_request(global.cur_dlg, &options, -1);
-	if (tdata)
-	    pjsip_dlg_send_msg( global.cur_dlg, tdata );
-    } else {
-	/* Send OPTIONS to arbitrary party. */
-	tdata = pjsip_endpt_create_request( global.endpt, &options,
-					    &str,
-					    &global.local_uri, &str, 
-					    &global.real_contact,
-					    NULL, -1, NULL);
-	if (tdata) {
-	    rc = pjsip_endpt_send_request( global.endpt, tdata, -1, NULL, 
-					   &generic_request_callback);
-	    if (rc != 0)
-		PJ_LOG(2,(THIS_FILE, "Error sending OPTIONS!"));
-	}
-    }
-}
-
-static void init_presence()
-{
-    const pjsip_presence_cb pres_cb = {
-	NULL,
-	&pres_on_received_request,
-	&pres_on_received_refresh,
-	&pres_on_received_update,
-	&pres_on_terminated
-    };
-
-    pjsip_presence_init(&pres_cb);
-}
-
-/* Subscribe presence information for all buddies. */
-static void subscribe_buddies_presence()
-{
-    int i;
-    for (i=0; i<global.buddy_cnt; ++i) {
-	pjsip_presentity *pres;
-	if (global.buddy_pres[i])
-	    continue;
-	pres = pjsip_presence_create( global.endpt, &global.local_uri,
-				      &global.buddy[i], PRESENCE_TIMEOUT, (void*)i);
-	if (pres) {
-	    pjsip_presence_set_credentials( pres, global.cred_count, global.cred_info );
-	    pjsip_presence_subscribe( pres );
-	}
-	global.buddy_pres[i] = pres;
-    }
-}
-
-/* Unsubscribe presence information for all buddies. */
-static void unsubscribe_buddies_presence()
-{
-    int i;
-    for (i=0; i<global.buddy_cnt; ++i) {
-	pjsip_presentity *pres = global.buddy_pres[i];
-	if (pres) {
-	    pjsip_presence_unsubscribe(pres);
-	    pjsip_presence_destroy(pres);
-	    global.buddy_pres[i] = NULL;
-	}
-    }
-}
-
-/* Unsubscribe presence. */
-static void unsubscribe_presence()
-{
-    int i;
-
-    unsubscribe_buddies_presence();
-    for (i=0; i<global.pres_cnt; ++i) {
-	pjsip_presentity *pres = global.pres[i];
-	pjsip_presence_notify( pres, PJSIP_EVENT_SUB_STATE_TERMINATED, 0);
-	pjsip_presence_destroy( pres );
-    }
-}
-
-/* Advertise online status to subscribers. */
-static void update_im_status()
-{
-    int i;
-    for (i=0; i<global.pres_cnt; ++i) {
-	pjsip_presentity *pres = global.pres[i];
-	pjsip_presence_notify( pres, PJSIP_EVENT_SUB_STATE_ACTIVE, 
-			       !global.hide_status);
-    }
-}
-
-/*
- * Main program.
- */
-int main(int argc, char *argv[])
-{
-    /* set to WORKER_COUNT+1 to avoid zero size warning 
-     * when threading is disabled. */
-    pj_thread_t *thread[WORKER_COUNT+1];
-    pj_caching_pool cp;
-    int i;
-
-    global.sip_port = 5060;
-    global.auto_answer = -1;
-    global.auto_hangup = -1;
-    global.app_log_level = 3;
-
-    pj_log_set_level(4);
-    pj_log_set_log_func(&log_function);
-
-    /* Init PJLIB */
-    if (pj_init() != PJ_SUCCESS)
-	return 1;
-
-    /* Init caching pool. */
-    pj_caching_pool_init(&cp, &pj_pool_factory_default_policy, 0);
-    global.pf = &cp.factory;
-
-    /* Create memory pool for application. */
-    global.pool = pj_pool_create(global.pf, "main", 1024, 0, NULL);
-
-    /* Parse command line arguments. */
-    if (parse_args(global.pool, argc, argv) != PJ_SUCCESS) {
-	pj_caching_pool_destroy(&cp);
-	return 1;
-    }
-
-    /* Init sockets */
-    if (init_sockets() != 0) {
-	pj_caching_pool_destroy(&cp);
-	return 1;
-    }
-
-    /* Initialize stack. */
-    if (init_stack() != PJ_SUCCESS) {
-	pj_caching_pool_destroy(&cp);
-	return 1;
-    }
-
-    /* Set callback to receive incoming IM */
-    pjsip_messaging_set_incoming_callback( &on_incoming_im_msg );
-
-    /* Set default worker count (can be zero) */
-    global.worker_cnt = WORKER_COUNT;
-
-    /* Create user worker thread(s), only when threading is enabled. */
-    for (i=0; i<global.worker_cnt; ++i) {
-	thread[i] = pj_thread_create( global.pool, "sip%p", 
-				      &worker_thread, 
-				      NULL, 0, NULL, 0);
-	if (thread == NULL) {
-	    global.worker_quit_flag = 1;
-	    for (--i; i>=0; --i) {
-		pj_thread_join(thread[i]);
-		pj_thread_destroy(thread[i]);
-	    }
-	    pj_caching_pool_destroy(&cp);
-	    return 1;
-	}
-    }
-
-    printf("Worker thread count: %d\n", global.worker_cnt);
-
-    /* Perform registration, if required. */
-    if (global.regc) {
-	update_registration(global.regc, 1);
-    }
-
-    /* Initialize media manager. */
-    global.mmgr = pj_med_mgr_create(global.pf);
-
-    /* Init presence. */
-    init_presence();
-
-    /* Subscribe presence information of all buddies. */
-    if (!global.no_presence)
-	subscribe_buddies_presence();
-
-    /* Initializatio completes, loop waiting for commands. */
-    for (;!global.worker_quit_flag;) {
-	pj_str_t str;
-	char line[128];
-
-#if WORKER_COUNT==0
-	/* If worker thread does not exist, main thread must poll for evetns. 
-	 * But this won't work very well since main thread is blocked by 
-	 * fgets(). So keep pressing the ENTER key to get the events!
-	 */
-	pj_time_val timeout = { 0, 100 };
-	pjsip_endpt_handle_events(global.endpt, &timeout);
-	puts("Keep pressing ENTER key to get the events!");
+	if (kbhit())
+	    fgets(keyin, sizeof(keyin), stdin);
+#else
+	ui_help();
+	fgets(keyin, sizeof(keyin), stdin);
 #endif
 
-	printf("\nCurrent dialog: ");
-	print_dialog(global.cur_dlg);
-	puts("");
+	switch (keyin[0]) {
 
-	keystroke_help();
-
-	fgets(line, sizeof(line), stdin);
-
-	switch (*line) {
 	case 'm':
-	    puts("Make outgoing call");
-	    if (ui_input_url(&str, line, sizeof(line), &i) != NULL) {
-		pjsip_dlg *dlg = make_call(&str);
-		if (global.cur_dlg == NULL) {
-		    global.cur_dlg = dlg;
-		}
+	    if (inv_session != NULL) {
+		puts("Can not make call while another one is in progress");
+		continue;
 	    }
-	    break;
-	case 'i':
-	    puts("Send Instant Messaging");
-	    ui_send_im_message();
-	    break;
-	case 'o':
-	    puts("Send OPTIONS");
-	    ui_send_options();
-	    break;
-	case 'a':
-	    if (global.cur_dlg) {
-		unsigned code;
-		pjsip_tx_data *tdata;
-		struct dialog_data *dlg_data = global.cur_dlg->user_data;
 
-		printf("Answer with status code (1xx-6xx): ");
-		fflush(stdout);
-		fgets(line, sizeof(line), stdin);
-		str = pj_str(line);
-		str.slen -= 1;
+#if 0
+	    printf("Enter URL to call: ");
+	    fgets(buf, sizeof(buf), stdin);
 
-		code = pj_strtoul(&str);
-		tdata = pjsip_dlg_answer(global.cur_dlg, code);
-		if (tdata) {
-		    if (code/100 == 2) {
-			tdata->msg->body = dlg_data->body;
-		    }
-		    pjsip_dlg_send_msg(global.cur_dlg, tdata);
-
-		}
-	    } else {
-		puts("No current dialog");
+	    if (buf[0]=='\r' || buf[0]=='\n') {
+		/* Cancelled. */
+		puts("<cancelled>");
+		continue;
 	    }
+
+	    /* Remove trailing newlines. */
+	    for (p=buf; ; ++p) {
+		if (*p=='\r' || *p=='\n') *p='\0';
+		else if (!*p) break;
+	    }
+	    /* Make call! : */
+
+	    pjsua_invite(buf, &inv);
+#endif
+
+	    pjsua_invite("sip:localhost:5061", &inv);
 	    break;
+
+
 	case 'h':
-	    if (global.cur_dlg) {
+
+	    if (inv_session == NULL) {
+		puts("No current call");
+		continue;
+
+	    } else {
+		pj_status_t status;
 		pjsip_tx_data *tdata;
-		tdata = pjsip_dlg_disconnect(global.cur_dlg, PJSIP_SC_DECLINE);
-		if (tdata) {
-		    pjsip_dlg_send_msg(global.cur_dlg, tdata);
+
+		status = pjsip_inv_end_session(inv_session, PJSIP_SC_DECLINE, 
+					       NULL, &tdata);
+		if (status != PJ_SUCCESS) {
+		    pjsua_perror("Failed to create end session message", status);
+		    continue;
 		}
-	    } else {
-		puts("No current dialog");
-	    }
-	    break;
-	case ']':
-	    if (global.cur_dlg) {
-		global.cur_dlg = global.cur_dlg->next;
-		if (global.cur_dlg == (void*)&global.user_agent->dlg_list) {
-		    global.cur_dlg = global.cur_dlg->next;
+
+		status = pjsip_inv_send_msg(inv_session, tdata, NULL);
+		if (status != PJ_SUCCESS) {
+		    pjsua_perror("Failed to send end session message", status);
+		    continue;
 		}
-	    } else {
-		puts("No current dialog");
 	    }
+
 	    break;
-	case '[':
-	    if (global.cur_dlg) {
-		global.cur_dlg = global.cur_dlg->prev;
-		if (global.cur_dlg == (void*)&global.user_agent->dlg_list) {
-		    global.cur_dlg = global.cur_dlg->prev;
-		}
-	    } else {
-		puts("No current dialog");
-	    }
-	    break;
-	case 'd':
-	    pjsip_endpt_dump(global.endpt, *(line+1)=='1');
-	    pjsip_ua_dump(global.user_agent);
-	    break;
-	case 's':
-	    if (*(line+1) == 'u')
-		subscribe_buddies_presence();
-	    break;
-	case 'u':
-	    if (*(line+1) == 's')
-		unsubscribe_presence();
-	    break;
-	case 't':
-	    global.hide_status = !global.hide_status;
-	    update_im_status();
-	    break;
+
 	case 'q':
 	    goto on_exit;
-	case 'l':
-	    print_all_dialogs();
-	    break;
 	}
     }
 
 on_exit:
-    /* Unregister, if required. */
-    if (global.regc) {
-	update_registration(global.regc, 0);
-    }
+    ;
+}
 
-    /* Unsubscribe presence. */
-    unsubscribe_presence();
+static pj_bool_t console_on_rx_msg(pjsip_rx_data *rdata)
+{
+    PJ_LOG(4,(THIS_FILE, "RX %d bytes %s from %s:%d:\n"
+			 "%s\n"
+			 "--end msg--",
+			 rdata->msg_info.len,
+			 pjsip_rx_data_get_info(rdata),
+			 rdata->pkt_info.src_name,
+			 rdata->pkt_info.src_port,
+			 rdata->msg_info.msg_buf));
+    
+    /* Must return false for logger! */
+    return PJ_FALSE;
+}
 
-    /* Allow one second to get all events. */
-    if (1) {
-	pj_time_val end_time;
+static pj_status_t console_on_tx_msg(pjsip_tx_data *tdata)
+{
+    PJ_LOG(4,(THIS_FILE, "TX %d bytes %s to %s:%d:\n"
+			 "%s\n"
+			 "--end msg--",
+			 (tdata->buf.cur - tdata->buf.start),
+			 pjsip_tx_data_get_info(tdata),
+			 tdata->tp_info.dst_name,
+			 tdata->tp_info.dst_port,
+			 tdata->buf.start));
 
-	pj_gettimeofday(&end_time);
-	end_time.sec++;
+    return PJ_SUCCESS;
+}
 
-	PJ_LOG(3,(THIS_FILE, "Shutting down.."));
-	for (;;) {
-	    pj_time_val timeout = { 0, 20 }, now;
-	    pjsip_endpt_handle_events (global.endpt, &timeout);
-	    pj_gettimeofday(&now);
-	    PJ_TIME_VAL_SUB(now, end_time);
-	    if (now.sec >= 1)
-		break;
-	}
-    }
+static pjsip_module console_msg_logger = 
+{
+    NULL, NULL,				/* prev, next.		*/
+    { "mod-console-msg-logger", 22 },	/* Name.		*/
+    -1,					/* Id			*/
+    PJSIP_MOD_PRIORITY_TRANSPORT_LAYER-1,/* Priority	        */
+    NULL,				/* User data.		*/
+    NULL,				/* load()		*/
+    NULL,				/* start()		*/
+    NULL,				/* stop()		*/
+    NULL,				/* unload()		*/
+    &console_on_rx_msg,			/* on_rx_request()	*/
+    &console_on_rx_msg,			/* on_rx_response()	*/
+    &console_on_tx_msg,			/* on_tx_request.	*/
+    &console_on_tx_msg,			/* on_tx_response()	*/
+    NULL,				/* on_tsx_state()	*/
 
-    global.worker_quit_flag = 1;
+};
 
-    pj_med_mgr_destroy(global.mmgr);
 
-    /* Wait all threads to quit. */
-    for (i=0; i<global.worker_cnt; ++i) {
-	pj_thread_join(thread[i]);
-	pj_thread_destroy(thread[i]);
-    }
+int main()
+{
+    /* Init default settings. */
 
-    /* Destroy endpoint. */
-    pjsip_endpt_destroy(global.endpt);
+    pjsua_default();
 
-    /* Destroy caching pool. */
-    pj_caching_pool_destroy(&cp);
 
-    /* Close log file, if any. */
-    if (global.log_file)
-	fclose(global.log_file);
+#ifdef NO_WORKER_THREAD
+    pjsua.thread_cnt = 0;
+#endif
+
+
+    /* Initialize pjsua.
+     * This will start worker thread, client registration, etc.
+     */
+
+    if (pjsua_init() != PJ_SUCCESS)
+	return 1;
+
+    /* Register message logger to print incoming and outgoing
+     * messages.
+     */
+
+    pjsip_endpt_register_module(pjsua.endpt, &console_msg_logger);
+
+
+    /* Sleep for a while, let any messages get printed to console: */
+
+    pj_thread_sleep(500);
+
+
+    /* Start UI console main loop: */
+
+    ui_console_main();
+
+
+    /* Destroy pjsua: */
+
+    pjsua_destroy();
+
+    /* Exit... */
 
     return 0;
 }
 
-/*
- * Register static modules to the endpoint.
- */
-pj_status_t register_static_modules( pj_size_t *count,
-				     pjsip_module **modules )
-{
-    /* Reset count. */
-    *count = 0;
-
-    /* Register user agent module. */
-    modules[(*count)++] = pjsip_ua_get_module();
-    global.user_agent = modules[0]->mod_data;
-    modules[(*count)++] = pjsip_messaging_get_module();
-    modules[(*count)++] = pjsip_event_sub_get_module();
-
-    return PJ_SUCCESS;
-}
diff --git a/pjsip/src/pjsua/pjsua.c b/pjsip/src/pjsua/pjsua.c
new file mode 100644
index 0000000..8e9cbde
--- /dev/null
+++ b/pjsip/src/pjsua/pjsua.c
@@ -0,0 +1,648 @@
+/* $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 "pjsua.h"
+
+struct pjsua pjsua;
+
+#define THIS_FILE   "pjsua.c"
+
+
+#define PJSUA_LOCAL_URI	    "<sip:bennylp@192.168.0.7>"
+#define PJSUA_CONTACT_URI   "<sip:bennylp@192.168.0.7>"
+
+static char *PJSUA_DUMMY_SDP_OFFER = 
+	    "v=0\r\n"
+	    "o=offer 2890844526 2890844526 IN IP4 127.0.0.1\r\n"
+	    "s= \r\n"
+	    "c=IN IP4 127.0.0.1\r\n"
+	    "t=0 0\r\n"
+	    "m=audio 49170 RTP/AVP 0\r\n"
+	    "a=rtpmap:0 PCMU/8000\r\n";
+
+static char *PJSUA_DUMMY_SDP_ANSWER = 
+	    "v=0\r\n"
+	    "o=answer 2890844730 2890844730 IN IP4 127.0.0.1\r\n"
+	    "s= \r\n"
+	    "c=IN IP4 127.0.0.1\r\n"
+	    "t=0 0\r\n"
+	    "m=audio 49920 RTP/AVP 0\r\n"
+	    "a=rtpmap:0 PCMU/8000\r\n";
+
+/*
+ * Init default application parameters.
+ */
+void pjsua_default(void)
+{
+
+    /* Normally need another thread for console application, because main 
+     * thread will be blocked in fgets().
+     */
+    pjsua.thread_cnt = 1;
+
+
+    /* Default transport settings: */
+
+    pjsua.sip_port = 5060;
+
+
+    /* Default logging settings: */
+
+    pjsua.log_level = 5;
+    pjsua.app_log_level = 4;
+    pjsua.log_decor = PJ_LOG_HAS_SENDER | PJ_LOG_HAS_TIME | 
+		      PJ_LOG_HAS_MICRO_SEC | PJ_LOG_HAS_NEWLINE;
+
+    /* Default: do not use STUN: */
+
+    pjsua.stun_port1 = pjsua.stun_port2 = 0;
+}
+
+
+/*
+ * Display error message for the specified error code.
+ */
+void pjsua_perror(const char *title, pj_status_t status)
+{
+    char errmsg[PJ_ERR_MSG_SIZE];
+
+    pj_strerror(status, errmsg, sizeof(errmsg));
+
+    PJ_LOG(1,(THIS_FILE, "%s: %s", title, errmsg));
+}
+
+
+/*
+ * Handler for receiving incoming requests.
+ *
+ * This handler serves multiple purposes:
+ *  - it receives requests outside dialogs.
+ *  - it receives requests inside dialogs, when the requests are
+ *    unhandled by other dialog usages. Example of these
+ *    requests are: MESSAGE.
+ */
+static pj_bool_t mod_pjsua_on_rx_request(pjsip_rx_data *rdata)
+{
+    PJ_UNUSED_ARG(rdata);
+    PJ_TODO(IMPLEMENT_UAS);
+    return PJ_FALSE;
+}
+
+
+/*
+ * Handler for receiving incoming responses.
+ *
+ * This handler serves multiple purposes:
+ *  - it receives strayed responses (i.e. outside any dialog and
+ *    outside any transactions).
+ *  - it receives responses coming to a transaction, when pjsua
+ *    module is set as transaction user for the transaction.
+ *  - it receives responses inside a dialog, when these responses
+ *    are unhandled by other dialog usages.
+ */
+static pj_bool_t mod_pjsua_on_rx_response(pjsip_rx_data *rdata)
+{
+    PJ_UNUSED_ARG(rdata);
+    PJ_TODO(IMPLEMENT_UAS);
+    return PJ_FALSE;
+}
+
+
+/*
+ * This callback receives notification from invite session when the
+ * session state has changed.
+ */
+static void pjsua_inv_on_state_changed(pjsip_inv_session *inv, pjsip_event *e)
+{
+    ui_inv_on_state_changed(inv, e);
+}
+
+
+/*
+ * This callback is called by invite session framework when UAC session
+ * has forked.
+ */
+static void pjsua_inv_on_new_session(pjsip_inv_session *inv, pjsip_event *e)
+{
+    PJ_UNUSED_ARG(inv);
+    PJ_UNUSED_ARG(e);
+
+    PJ_TODO(HANDLE_FORKED_DIALOG);
+}
+
+/* 
+ * Initialize sockets and optionally get the public address via STUN. 
+ */
+static pj_status_t init_sockets()
+{
+    enum { 
+	RTP_START_PORT = 4000,
+	RTP_RANDOM_START = 2,
+	RTP_RETRY = 10 
+    };
+    enum {
+	SIP_SOCK,
+	RTP_SOCK,
+	RTCP_SOCK,
+    };
+    int i;
+    pj_uint16_t rtp_port;
+    pj_sock_t sock[3];
+    pj_sockaddr_in mapped_addr[3];
+    pj_status_t status;
+
+    for (i=0; i<3; ++i)
+	sock[i] = PJ_INVALID_SOCKET;
+
+    /* Create and bind SIP UDP socket. */
+    status = pj_sock_socket(PJ_AF_INET, PJ_SOCK_DGRAM, 0, &sock[SIP_SOCK]);
+    if (status != PJ_SUCCESS) {
+	pjsua_perror("socket() error", status);
+	goto on_error;
+    }
+    
+    status = pj_sock_bind_in(sock[SIP_SOCK], 0, pjsua.sip_port);
+    if (status != PJ_SUCCESS) {
+	pjsua_perror("bind() error", status);
+	goto on_error;
+    }
+
+    /* Initialize start of RTP port to try. */
+    rtp_port = (pj_uint16_t)(RTP_START_PORT + (pj_rand() % RTP_RANDOM_START) / 2);
+
+    /* Loop retry to bind RTP and RTCP sockets. */
+    for (i=0; i<RTP_RETRY; ++i, rtp_port += 2) {
+
+	/* Create and bind RTP socket. */
+	status = pj_sock_socket(PJ_AF_INET, PJ_SOCK_DGRAM, 0, &sock[RTP_SOCK]);
+	if (status != PJ_SUCCESS) {
+	    pjsua_perror("socket() error", status);
+	    goto on_error;
+	}
+
+	status = pj_sock_bind_in(sock[RTP_SOCK], 0, rtp_port);
+	if (status != PJ_SUCCESS) {
+	    pj_sock_close(sock[RTP_SOCK]); 
+	    sock[RTP_SOCK] = PJ_INVALID_SOCKET;
+	    continue;
+	}
+
+	/* Create and bind RTCP socket. */
+	status = pj_sock_socket(PJ_AF_INET, PJ_SOCK_DGRAM, 0, &sock[RTCP_SOCK]);
+	if (status != PJ_SUCCESS) {
+	    pjsua_perror("socket() error", status);
+	    goto on_error;
+	}
+
+	status = pj_sock_bind_in(sock[RTCP_SOCK], 0, (pj_uint16_t)(rtp_port+1));
+	if (status != PJ_SUCCESS) {
+	    pj_sock_close(sock[RTP_SOCK]); 
+	    sock[RTP_SOCK] = PJ_INVALID_SOCKET;
+
+	    pj_sock_close(sock[RTCP_SOCK]); 
+	    sock[RTCP_SOCK] = PJ_INVALID_SOCKET;
+	    continue;
+	}
+
+	/*
+	 * If we're configured to use STUN, then find out the mapped address,
+	 * and make sure that the mapped RTCP port is adjacent with the RTP.
+	 */
+	if (pjsua.stun_port1 == 0) {
+	    const pj_str_t *hostname;
+	    pj_sockaddr_in addr;
+
+	    /* Get local IP address. */
+	    hostname = pj_gethostname();
+
+	    pj_memset( &addr, 0, sizeof(addr));
+	    addr.sin_family = PJ_AF_INET;
+	    status = pj_sockaddr_in_set_str_addr( &addr, hostname);
+	    if (status != PJ_SUCCESS) {
+		pjsua_perror("Unresolvable local hostname", status);
+		goto on_error;
+	    }
+
+	    for (i=0; i<3; ++i)
+		pj_memcpy(&mapped_addr[i], &addr, sizeof(addr));
+
+	    mapped_addr[SIP_SOCK].sin_port = pj_htons((pj_uint16_t)pjsua.sip_port);
+	    mapped_addr[RTP_SOCK].sin_port = pj_htons((pj_uint16_t)rtp_port);
+	    mapped_addr[RTCP_SOCK].sin_port = pj_htons((pj_uint16_t)(rtp_port+1));
+	    break;
+	} else {
+	    status = pj_stun_get_mapped_addr( &pjsua.cp.factory, 3, sock,
+					      &pjsua.stun_srv1, pjsua.stun_port1,
+					      &pjsua.stun_srv2, pjsua.stun_port2,
+					      mapped_addr);
+	    if (status != PJ_SUCCESS) {
+		pjsua_perror("STUN error", status);
+		goto on_error;
+	    }
+
+	    if (pj_ntohs(mapped_addr[2].sin_port) == pj_ntohs(mapped_addr[1].sin_port)+1)
+		break;
+
+	    pj_sock_close(sock[RTP_SOCK]); sock[RTP_SOCK] = PJ_INVALID_SOCKET;
+	    pj_sock_close(sock[RTCP_SOCK]); sock[RTCP_SOCK] = PJ_INVALID_SOCKET;
+	}
+    }
+
+    if (sock[RTP_SOCK] == PJ_INVALID_SOCKET) {
+	PJ_LOG(1,(THIS_FILE, "Unable to find appropriate RTP/RTCP ports combination"));
+	goto on_error;
+    }
+
+    pjsua.sip_sock = sock[SIP_SOCK];
+    pj_memcpy(&pjsua.sip_sock_name, &mapped_addr[SIP_SOCK], sizeof(pj_sockaddr_in));
+    pjsua.rtp_sock = sock[RTP_SOCK];
+    pj_memcpy(&pjsua.rtp_sock_name, &mapped_addr[RTP_SOCK], sizeof(pj_sockaddr_in));
+    pjsua.rtcp_sock = sock[RTCP_SOCK];
+    pj_memcpy(&pjsua.rtcp_sock_name, &mapped_addr[RTCP_SOCK], sizeof(pj_sockaddr_in));
+
+    PJ_LOG(4,(THIS_FILE, "SIP UDP socket reachable at %s:%d",
+	      pj_inet_ntoa(pjsua.sip_sock_name.sin_addr), 
+	      pj_ntohs(pjsua.sip_sock_name.sin_port)));
+    PJ_LOG(4,(THIS_FILE, "RTP socket reachable at %s:%d",
+	      pj_inet_ntoa(pjsua.rtp_sock_name.sin_addr), 
+	      pj_ntohs(pjsua.rtp_sock_name.sin_port)));
+    PJ_LOG(4,(THIS_FILE, "RTCP UDP socket reachable at %s:%d",
+	      pj_inet_ntoa(pjsua.rtcp_sock_name.sin_addr), 
+	      pj_ntohs(pjsua.rtcp_sock_name.sin_port)));
+
+    return PJ_SUCCESS;
+
+on_error:
+    for (i=0; i<3; ++i) {
+	if (sock[i] != PJ_INVALID_SOCKET)
+	    pj_sock_close(sock[i]);
+    }
+    return status;
+}
+
+
+
+/* 
+ * Initialize stack. 
+ */
+static pj_status_t init_stack(void)
+{
+    pj_status_t status;
+
+    /* Create global endpoint: */
+
+    {
+	const pj_str_t *hostname;
+	const char *endpt_name;
+
+	/* Endpoint MUST be assigned a globally unique name.
+	 * The name will be used as the hostname in Warning header.
+	 */
+
+	/* For this implementation, we'll use hostname for simplicity */
+	hostname = pj_gethostname();
+	endpt_name = hostname->ptr;
+
+	/* Create the endpoint: */
+
+	status = pjsip_endpt_create(&pjsua.cp.factory, endpt_name, 
+				    &pjsua.endpt);
+	if (status != PJ_SUCCESS) {
+	    pjsua_perror("Unable to create SIP endpoint", status);
+	    return status;
+	}
+    }
+
+
+    /* Initialize transaction layer: */
+
+    status = pjsip_tsx_layer_init(pjsua.endpt);
+    if (status != PJ_SUCCESS) {
+	pjsua_perror("Transaction layer initialization error", status);
+	goto on_error;
+    }
+
+    /* Initialize UA layer module: */
+
+    status = pjsip_ua_init( pjsua.endpt, NULL );
+    if (status != PJ_SUCCESS) {
+	pjsua_perror("UA layer initialization error", status);
+	goto on_error;
+    }
+
+    /* Initialize and register pjsua's application module: */
+
+    {
+	pjsip_module my_mod = 
+	{
+	NULL, NULL,		    /* prev, next.			*/
+	{ "mod-pjsua", 9 },	    /* Name.				*/
+	-1,			    /* Id				*/
+	PJSIP_MOD_PRIORITY_APPLICATION,	/* Priority			*/
+	NULL,			    /* User data.			*/
+	NULL,			    /* load()				*/
+	NULL,			    /* start()				*/
+	NULL,			    /* stop()				*/
+	NULL,			    /* unload()				*/
+	&mod_pjsua_on_rx_request,   /* on_rx_request()			*/
+	&mod_pjsua_on_rx_response,  /* on_rx_response()			*/
+	NULL,			    /* on_tx_request.			*/
+	NULL,			    /* on_tx_response()			*/
+	NULL,			    /* on_tsx_state()			*/
+	};
+
+	pjsua.mod = my_mod;
+
+	status = pjsip_endpt_register_module(pjsua.endpt, &pjsua.mod);
+	if (status != PJ_SUCCESS) {
+	    pjsua_perror("Unable to register pjsua module", status);
+	    goto on_error;
+	}
+    }
+
+    /* Initialize invite session module: */
+
+    {
+	
+	/* Initialize invite session callback. */
+	pjsip_inv_callback inv_cb;
+
+	pj_memset(&inv_cb, 0, sizeof(inv_cb));
+	inv_cb.on_state_changed = &pjsua_inv_on_state_changed;
+	inv_cb.on_new_session = &pjsua_inv_on_new_session;
+
+	/* Initialize invite session module: */
+	status = pjsip_inv_usage_init(pjsua.endpt, &pjsua.mod, &inv_cb);
+	if (status != PJ_SUCCESS) {
+	    pjsua_perror("Invite usage initialization error", status);
+	    goto on_error;
+	}
+
+    }
+
+
+    /* Add UDP transport: */
+
+    {
+	/* Init the published name for the transport.
+         * Depending whether STUN is used, this may be the STUN mapped
+	 * address, or socket's bound address.
+	 */
+	pjsip_host_port addr_name;
+
+	addr_name.host.ptr = pj_inet_ntoa(pjsua.sip_sock_name.sin_addr);
+	addr_name.host.slen = pj_native_strlen(addr_name.host.ptr);
+	addr_name.port = pj_ntohs(pjsua.sip_sock_name.sin_port);
+
+	/* Create UDP transport from previously created UDP socket: */
+
+	status = pjsip_udp_transport_attach( pjsua.endpt, pjsua.sip_sock,
+					     &addr_name, 1, NULL);
+	if (status != PJ_SUCCESS) {
+	    pjsua_perror("Unable to start UDP transport", status);
+	    goto on_error;
+	}
+    }
+
+    /* Initialize local user info and contact: */
+
+    {
+	pj_strdup2(pjsua.pool, &pjsua.local_uri, PJSUA_LOCAL_URI);
+	pj_strdup2(pjsua.pool, &pjsua.contact_uri, PJSUA_CONTACT_URI);
+    }
+
+    /* Initialize global route_set: */
+
+    PJ_TODO(INIT_GLOBAL_ROUTE_SET);
+
+
+    /* Start registration: */
+
+    PJ_TODO(START_REGISTRATION);
+
+    /* Done? */
+
+    return PJ_SUCCESS;
+
+
+on_error:
+    pjsip_endpt_destroy(pjsua.endpt);
+    pjsua.endpt = NULL;
+    return status;
+}
+
+
+static int PJ_THREAD_FUNC pjsua_worker_thread(void *arg)
+{
+    PJ_UNUSED_ARG(arg);
+
+    while (!pjsua.quit_flag) {
+	pj_time_val timeout = { 0, 10 };
+	pjsip_endpt_handle_events (pjsua.endpt, &timeout);
+    }
+
+    return 0;
+}
+
+/*
+ * Initialize pjsua application.
+ * This will start the registration process, if registration is configured.
+ */
+pj_status_t pjsua_init(void)
+{
+    int i;  /* Must be signed */
+    pj_status_t status;
+
+    /* Init PJLIB logging: */
+
+    pj_log_set_level(pjsua.log_level);
+    pj_log_set_decor(pjsua.log_decor);
+
+
+    /* Init PJLIB: */
+
+    status = pj_init();
+    if (status != PJ_SUCCESS) {
+	pjsua_perror("pj_init() error", status);
+	return status;
+    }
+
+    /* Init memory pool: */
+
+    /* Init caching pool. */
+    pj_caching_pool_init(&pjsua.cp, &pj_pool_factory_default_policy, 0);
+
+    /* Create memory pool for application. */
+    pjsua.pool = pj_pool_create(&pjsua.cp.factory, "pjsua", 4000, 4000, NULL);
+
+
+    /* Init sockets (STUN etc): */
+
+    status = init_sockets();
+    if (status != PJ_SUCCESS) {
+	pj_caching_pool_destroy(&pjsua.cp);
+	pjsua_perror("init_sockets() has returned error", status);
+	return status;
+    }
+
+
+    /* Init PJSIP and all the modules: */
+
+    status = init_stack();
+    if (status != PJ_SUCCESS) {
+	pj_caching_pool_destroy(&pjsua.cp);
+	pjsua_perror("Stack initialization has returned error", status);
+	return status;
+    }
+
+    /* Create worker thread(s), if required: */
+
+    for (i=0; i<pjsua.thread_cnt; ++i) {
+	status = pj_thread_create( pjsua.pool, "pjsua", &pjsua_worker_thread,
+				   NULL, 0, 0, &pjsua.threads[i]);
+	if (status != PJ_SUCCESS) {
+	    pjsua.quit_flag = 1;
+	    for (--i; i>=0; --i) {
+		pj_thread_join(pjsua.threads[i]);
+		pj_thread_destroy(pjsua.threads[i]);
+	    }
+	    pj_caching_pool_destroy(&pjsua.cp);
+	    return status;
+	}
+    }
+
+    /* Done. */
+    return PJ_SUCCESS;
+}
+
+
+/*
+ * Destroy pjsua.
+ */
+pj_status_t pjsua_destroy(void)
+{
+    int i;
+
+    /* Signal threads to quit: */
+
+    pjsua.quit_flag = 1;
+
+    /* Wait worker threads to quit: */
+
+    for (i=0; i<pjsua.thread_cnt; ++i) {
+	
+	pj_thread_join(pjsua.threads[i]);
+	pj_thread_destroy(pjsua.threads[i]);
+    }
+
+    /* Destroy endpoint. */
+    pjsip_endpt_destroy(pjsua.endpt);
+    pjsua.endpt = NULL;
+
+    /* Destroy caching pool. */
+    pj_caching_pool_destroy(&pjsua.cp);
+
+
+    /* Done. */
+
+    return PJ_SUCCESS;
+}
+
+
+/**
+ * Make outgoing call.
+ */
+pj_status_t pjsua_invite(const char *cstr_dest_uri,
+			 pjsip_inv_session **p_inv)
+{
+    pj_str_t dest_uri;
+    pjsip_dialog *dlg;
+    pjmedia_sdp_session *offer;
+    pjsip_inv_session *inv;
+    pjsip_tx_data *tdata;
+    pj_status_t status;
+
+    /* Convert cstr_dest_uri to dest_uri */
+    
+    dest_uri = pj_str((char*)cstr_dest_uri);
+
+    /* Create outgoing dialog: */
+
+    status = pjsip_dlg_create_uac( pjsip_ua_instance(), &pjsua.local_uri,
+				   &pjsua.contact_uri, &dest_uri, &dest_uri,
+				   &dlg);
+    if (status != PJ_SUCCESS) {
+	pjsua_perror("Dialog creation failed", status);
+	return status;
+    }
+
+    /* Create dummy SDP for offer: */
+
+    status = pjmedia_sdp_parse(dlg->pool, PJSUA_DUMMY_SDP_OFFER,
+			       pj_native_strlen(PJSUA_DUMMY_SDP_OFFER),
+			       &offer);
+    if (status != PJ_SUCCESS) {
+	pjsua_perror("Dummy SDP offer parsing failed", status);
+	goto on_error;
+    }
+
+    /* Create the INVITE session: */
+
+    status = pjsip_inv_create_uac( dlg, offer, 0, &inv);
+    if (status != PJ_SUCCESS) {
+	pjsua_perror("Invite session creation failed", status);
+	goto on_error;
+    }
+
+
+    /* Set credentials: */
+
+    PJ_TODO(SET_DIALOG_CREDENTIALS);
+
+
+    /* Create initial INVITE: */
+
+    status = pjsip_inv_invite(inv, &tdata);
+    if (status != PJ_SUCCESS) {
+	pjsua_perror("Unable to create initial INVITE request", status);
+	goto on_error;
+    }
+
+
+    /* Send initial INVITE: */
+
+    status = pjsip_inv_send_msg(inv, tdata, NULL);
+    if (status != PJ_SUCCESS) {
+	pjsua_perror("Unable to send initial INVITE request", status);
+	goto on_error;
+    }
+
+
+    /* Done. */
+
+    *p_inv = inv;
+
+    return PJ_SUCCESS;
+
+
+on_error:
+
+    PJ_TODO(DESTROY_DIALOG_ON_FAIL);
+    return status;
+}
+
diff --git a/pjsip/src/pjsua/pjsua.h b/pjsip/src/pjsua/pjsua.h
new file mode 100644
index 0000000..20e300f
--- /dev/null
+++ b/pjsip/src/pjsua/pjsua.h
@@ -0,0 +1,136 @@
+/* $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 
+ */
+#ifndef __PJSUA_H__
+#define __PJSUA_H__
+
+/* Include all PJSIP core headers. */
+#include <pjsip.h>
+
+/* Include all PJMEDIA headers. */
+#include <pjmedia.h>
+
+/* Include all PJSIP-UA headers */
+#include <pjsip_ua.h>
+
+/* Include all PJLIB-UTIL headers. */
+#include <pjlib-util.h>
+
+/* Include all PJLIB headers. */
+#include <pjlib.h>
+
+
+/* PJSUA application variables. */
+extern struct pjsua
+{
+    /* Control: */
+
+    pj_caching_pool   cp;	    /**< Global pool factory.		*/
+    pjsip_endpoint   *endpt;	    /**< Global endpoint.		*/
+    pj_pool_t	     *pool;	    /**< pjsua's private pool.		*/
+    pjsip_module      mod;	    /**< pjsua's PJSIP module.		*/
+    
+
+    /* User info: */
+    pj_str_t	     local_uri;	    /**< Uri in From: header.		*/
+    pj_str_t	     contact_uri;   /**< Uri in Contact: header.	*/
+
+    /* Threading: */
+
+    int		     thread_cnt;    /**< Thread count.			*/
+    pj_thread_t	    *threads[8];    /**< Thread instances.		*/
+    pj_bool_t	     quit_flag;	    /**< To signal thread to quit.	*/
+
+    /* Transport (UDP): */
+
+    pj_uint16_t	     sip_port;	    /**< SIP signaling port.		*/
+    pj_sock_t	     sip_sock;	    /**< SIP UDP socket.		*/
+    pj_sockaddr_in   sip_sock_name; /**< Public/STUN UDP socket addr.	*/
+    pj_sock_t	     rtp_sock;	    /**< RTP socket.			*/
+    pj_sockaddr_in   rtp_sock_name; /**< Public/STUN UDP socket addr.	*/
+    pj_sock_t	     rtcp_sock;	    /**< RTCP socket.			*/
+    pj_sockaddr_in   rtcp_sock_name;/**< Public/STUN UDP socket addr.	*/
+
+
+
+    /* STUN: */
+
+    pj_str_t	     stun_srv1;
+    int		     stun_port1;
+    pj_str_t	     stun_srv2;
+    int		     stun_port2;
+
+
+    /* Misc: */
+    
+    int		     log_level;	    /**< Logging verbosity.		*/
+    int		     app_log_level; /**< stdout log verbosity.		*/
+    unsigned	     log_decor;	    /**< Log decoration.		*/
+
+} pjsua;
+
+
+/*****************************************************************************
+ * PJSUA API.
+ */
+
+/**
+ * Initialize pjsua settings with default parameters.
+ */
+void pjsua_default(void);
+
+
+/**
+ * Display error message for the specified error code.
+ */
+void pjsua_perror(const char *title, pj_status_t status);
+
+
+/**
+ * Initialize pjsua application.
+ * This will start the registration process, if registration is configured.
+ */
+pj_status_t pjsua_init(void);
+
+
+/**
+ * Destroy pjsua.
+ */
+pj_status_t pjsua_destroy(void);
+
+
+/**
+ * Make outgoing call.
+ */
+pj_status_t pjsua_invite(const char *cstr_dest_uri,
+			 pjsip_inv_session **p_inv);
+
+
+/*****************************************************************************
+ * User Interface API.
+ * The UI API specifies functions that will be called by pjsua upon
+ * occurence of various events.
+ */
+
+/**
+ * Notify UI when invite state has changed.
+ */
+void ui_inv_on_state_changed(pjsip_inv_session *inv, pjsip_event *e);
+
+
+#endif	/* __PJSUA_H__ */