Index: /pjproject/trunk/pjsip/build/pjsip_simple.dsp
===================================================================
--- /pjproject/trunk/pjsip/build/pjsip_simple.dsp	(revision 267)
+++ /pjproject/trunk/pjsip/build/pjsip_simple.dsp	(revision 268)
@@ -98,4 +98,8 @@
 # Begin Source File
 
+SOURCE="..\src\pjsip-simple\iscomposing.c"
+# End Source File
+# Begin Source File
+
 SOURCE="..\src\pjsip-simple\pidf.c"
 # End Source File
@@ -126,4 +130,8 @@
 # Begin Source File
 
+SOURCE="..\include\pjsip-simple\iscomposing.h"
+# End Source File
+# Begin Source File
+
 SOURCE="..\include\pjsip-simple\pidf.h"
 # End Source File
Index: /pjproject/trunk/pjsip/build/pjsua_lib.dsp
===================================================================
--- /pjproject/trunk/pjsip/build/pjsua_lib.dsp	(revision 267)
+++ /pjproject/trunk/pjsip/build/pjsua_lib.dsp	(revision 268)
@@ -66,5 +66,5 @@
 # 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 "../include" /I "../../pjmedia/include" /I "../../pjlib-util/include" /I "../../pjlib/include" /D "_DEBUG" /D PJ_WIN32=1 /D PJ_M_I386=1 /D "WIN32" /D "_MBCS" /D "_LIB" /FD /GZ /c
+# ADD CPP /nologo /MTd /W4 /Gm /GX /ZI /Od /I "../include" /I "../../pjmedia/include" /I "../../pjlib-util/include" /I "../../pjlib/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"
@@ -100,5 +100,5 @@
 # Begin Source File
 
-SOURCE="..\src\pjsua-lib\pjsua_opt.c"
+SOURCE="..\src\pjsua-lib\pjsua_im.c"
 # End Source File
 # Begin Source File
@@ -109,4 +109,8 @@
 
 SOURCE="..\src\pjsua-lib\pjsua_reg.c"
+# End Source File
+# Begin Source File
+
+SOURCE="..\src\pjsua-lib\pjsua_settings.c"
 # End Source File
 # End Group
Index: /pjproject/trunk/pjsip/include/pjsip-simple/errno.h
===================================================================
--- /pjproject/trunk/pjsip/include/pjsip-simple/errno.h	(revision 267)
+++ /pjproject/trunk/pjsip/include/pjsip-simple/errno.h	(revision 268)
@@ -29,4 +29,7 @@
 
 
+/************************************************************
+ * EVENT PACKAGE ERRORS
+ ***********************************************************/
 /**
  * @hideinitializer
@@ -41,4 +44,7 @@
 
 
+/************************************************************
+ * PRESENCE ERROR
+ ***********************************************************/
 /**
  * @hideinitializer
@@ -73,4 +79,13 @@
 
 
+/************************************************************
+ * ISCOMPOSING ERRORS
+ ***********************************************************/
+/**
+ * @hideinitializer
+ * Bad isComposing XML message.
+ */
+#define PJSIP_SIMPLE_EBADISCOMPOSE  (PJSIP_SIMPLE_ERRNO_START+40)   /*270040*/
+
 
 #endif	/* __PJSIP_SIMPLE_ERRNO_H__ */
Index: /pjproject/trunk/pjsip/include/pjsip-simple/iscomposing.h
===================================================================
--- /pjproject/trunk/pjsip/include/pjsip-simple/iscomposing.h	(revision 268)
+++ /pjproject/trunk/pjsip/include/pjsip-simple/iscomposing.h	(revision 268)
@@ -0,0 +1,121 @@
+/* $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 __PJSIP_SIMPLE_ISCOMPOSING_H__
+#define __PJSIP_SIMPLE_ISCOMPOSING_H__
+
+/**
+ * @file iscomposing.h
+ * @brief Support for Indication of Message Composition (RFC 3994)
+ */
+#include <pjsip-simple/types.h>
+#include <pjlib-util/xml.h>
+
+
+PJ_BEGIN_DECL
+
+
+/**
+ * Create XML message with MIME type "application/im-iscomposing+xml"
+ * to indicate the message composition status.
+ *
+ * @param pool		    Pool to allocate memory.
+ * @param is_composing	    Message composition indication status. Set to
+ *			    PJ_TRUE (or non-zero) to indicate that application
+ *			    is currently composing an instant message.
+ * @param lst_actv	    Optional attribute to indicate time of last
+ *			    activity. If none is to be specified, the value
+ *			    MUST be set to NULL.
+ * @param content_tp	    Optional attribute to indicate the content type of
+ *			    message being composed. If none is to be specified, 
+ *			    the value MUST be set to NULL.
+ * @param refresh	    Optional attribute to indicate the interval when
+ *			    next indication will be sent, only when 
+ *			    is_composing is non-zero. If none is to be 
+ *			    specified, the value MUST be set to -1.
+ *
+ * @return		    An XML message containing the message indication.
+ *			    NULL will be returned when there's not enough
+ *			    memory to allocate the message.
+ */
+PJ_DECL(pj_xml_node*) pjsip_iscomposing_create_xml(pj_pool_t *pool,
+						   pj_bool_t is_composing,
+						   const pj_time_val *lst_actv,
+						   const pj_str_t *content_tp,
+						   int refresh);
+
+
+/**
+ * Create message body with Content-Type "application/im-iscomposing+xml"
+ * to indicate the message composition status.
+ *
+ * @param pool		    Pool to allocate memory.
+ * @param is_composing	    Message composition indication status. Set to
+ *			    PJ_TRUE (or non-zero) to indicate that application
+ *			    is currently composing an instant message.
+ * @param lst_actv	    Optional attribute to indicate time of last
+ *			    activity. If none is to be specified, the value
+ *			    MUST be set to NULL.
+ * @param content_tp	    Optional attribute to indicate the content type of
+ *			    message being composed. If none is to be specified, 
+ *			    the value MUST be set to NULL.
+ * @param refresh	    Optional attribute to indicate the interval when
+ *			    next indication will be sent, only when 
+ *			    is_composing is non-zero. If none is to be 
+ *			    specified, the value MUST be set to -1.
+ *
+ * @return		    The SIP message body containing XML message 
+ *			    indication. NULL will be returned when there's not
+ *			    enough memory to allocate the message.
+ */
+PJ_DECL(pjsip_msg_body*) pjsip_iscomposing_create_body( pj_pool_t *pool,
+						   pj_bool_t is_composing,
+						   const pj_time_val *lst_actv,
+						   const pj_str_t *content_tp,
+						   int refresh);
+
+
+/**
+ * Parse the buffer and return message composition indication in the 
+ * message.
+ *
+ * @param pool		    Pool to allocate memory for the parsing process.
+ * @param msg		    The message to be parsed.
+ * @param len		    Length of the message.
+ * @param p_is_composing    Optional pointer to receive iscomposing status.
+ * @param p_last_active	    Optional pointer to receive last active attribute.
+ * @param p_content_type    Optional pointer to receive content type attribute.
+ * @param p_refresh	    Optional pointer to receive refresh time.
+ *
+ * @return		    PJ_SUCCESS if message can be successfully parsed.
+ */
+PJ_DECL(pj_status_t) pjsip_iscomposing_parse( pj_pool_t *pool,
+					      char *msg,
+					      pj_size_t len,
+					      pj_bool_t *p_is_composing,
+					      pj_str_t **p_last_active,
+					      pj_str_t **p_content_type,
+					      int *p_refresh );
+
+
+
+PJ_END_DECL
+
+
+#endif	/* __PJSIP_SIMPLE_ISCOMPOSING_H__ */
+
Index: /pjproject/trunk/pjsip/include/pjsip_simple.h
===================================================================
--- /pjproject/trunk/pjsip/include/pjsip_simple.h	(revision 267)
+++ /pjproject/trunk/pjsip/include/pjsip_simple.h	(revision 268)
@@ -35,4 +35,5 @@
 
 #include <pjsip-simple/evsub.h>
+#include <pjsip-simple/iscomposing.h>
 #include <pjsip-simple/presence.h>
 #include <pjsip-simple/pidf.h>
Index: /pjproject/trunk/pjsip/include/pjsua-lib/pjsua.h
===================================================================
--- /pjproject/trunk/pjsip/include/pjsua-lib/pjsua.h	(revision 267)
+++ /pjproject/trunk/pjsip/include/pjsua-lib/pjsua.h	(revision 268)
@@ -355,4 +355,15 @@
 
 /**
+ * Send instant messaging inside INVITE session.
+ */
+void pjsua_call_send_im(int call_index, const char *text);
+
+
+/**
+ * Send IM typing indication inside INVITE session.
+ */
+void pjsua_call_typing(int call_index, pj_bool_t is_typing);
+
+/**
  * Terminate all calls.
  */
@@ -406,4 +417,55 @@
 
 /*****************************************************************************
+ * PJSUA Instant Messaging (pjsua_im.c)
+ */
+
+/**
+ * The MESSAGE method (defined in pjsua_im.c)
+ */
+extern const pjsip_method pjsip_message_method;
+
+
+/**
+ * Init IM module handler to handle incoming MESSAGE outside dialog.
+ */
+pj_status_t pjsua_im_init();
+
+
+/**
+ * Create Accept header for MESSAGE.
+ */
+pjsip_accept_hdr* pjsua_im_create_accept(pj_pool_t *pool);
+
+/**
+ * Send IM outside dialog.
+ */
+pj_status_t pjsua_im_send(int acc_index, const char *dst_uri, 
+			  const char *text);
+
+
+/**
+ * Send typing indication outside dialog.
+ */
+pj_status_t pjsua_im_typing(int acc_index, const char *dst_uri, 
+			    pj_bool_t is_typing);
+
+
+/**
+ * Private: check if we can accept the message.
+ *	    If not, then p_accept header will be filled with a valid
+ *	    Accept header.
+ */
+pj_bool_t pjsua_im_accept_pager(pjsip_rx_data *rdata,
+				const pjsip_accept_hdr **p_accept_hdr);
+
+/**
+ * Private: process pager message.
+ *	    This may trigger pjsua_ui_on_pager() or pjsua_ui_on_typing().
+ */
+void pjsua_im_process_pager(int call_id, const pj_str_t *from,
+			    const pj_str_t *to, pjsip_rx_data *rdata);
+
+
+/*****************************************************************************
  * User Interface API.
  *
@@ -415,10 +477,25 @@
  * Notify UI when invite state has changed.
  */
-void pjsua_ui_inv_on_state_changed(int call_index, pjsip_event *e);
+void pjsua_ui_on_call_state(int call_index, pjsip_event *e);
 
 /**
  * Notify UI when registration status has changed.
  */
-void pjsua_ui_regc_on_state_changed(int acc_index);
+void pjsua_ui_on_reg_state(int acc_index);
+
+/**
+ * Notify UI on incoming pager (i.e. MESSAGE request).
+ * Argument call_index will be -1 if MESSAGE request is not related to an 
+ * existing call.
+ */
+void pjsua_ui_on_pager(int call_index, const pj_str_t *from,
+		       const pj_str_t *to, const pj_str_t *txt);
+
+
+/**
+ * Notify UI about typing indication.
+ */
+void pjsua_ui_on_typing(int call_index, const pj_str_t *from,
+		        const pj_str_t *to, pj_bool_t is_typing);
 
 
Index: /pjproject/trunk/pjsip/src/pjsip-simple/errno.c
===================================================================
--- /pjproject/trunk/pjsip/src/pjsip-simple/errno.c	(revision 267)
+++ /pjproject/trunk/pjsip/src/pjsip-simple/errno.c	(revision 268)
@@ -30,7 +30,9 @@
 } err_str[] = 
 {
+    /* Event errors */
     { PJSIP_SIMPLE_ENOPKG,	    "No SIP event package with the specified name" },
     { PJSIP_SIMPLE_EPKGEXISTS,	    "SIP event package already exist" },
 
+    /* Presence errors */
     { PJSIP_SIMPLE_ENOTSUBSCRIBE,   "Expecting SUBSCRIBE request" },
     { PJSIP_SIMPLE_ENOPRESENCE,	    "No presence associated with the subscription" },
@@ -39,4 +41,7 @@
     { PJSIP_SIMPLE_EBADPIDF,	    "Bad PIDF content for presence" },
     { PJSIP_SIMPLE_EBADXPIDF,	    "Bad XPIDF content for presence" },
+
+    /* isComposing errors. */
+    { PJSIP_SIMPLE_EBADISCOMPOSE,   "Bad isComposing indication/XML message" },
 };
 
Index: /pjproject/trunk/pjsip/src/pjsip-simple/evsub.c
===================================================================
--- /pjproject/trunk/pjsip/src/pjsip-simple/evsub.c	(revision 267)
+++ /pjproject/trunk/pjsip/src/pjsip-simple/evsub.c	(revision 268)
@@ -957,4 +957,13 @@
 		       pjsip_hdr_shallow_clone(tdata->pool, sub->expires));
 
+    /* Add additional header, if any. */
+    if (hdr_list) {
+	const pjsip_hdr *hdr = hdr_list->next;
+	while (hdr != hdr_list) {
+	    pjsip_msg_add_hdr( tdata->msg,
+			       pjsip_hdr_clone(tdata->pool, hdr));
+	    hdr = hdr->next;
+	}
+    }
 
     /* Send the response: */
@@ -1325,11 +1334,9 @@
     /* Add msg body, if any */
     if (body) {
-	tdata->msg->body = pj_pool_zalloc(tdata->pool, 
-					  sizeof(pjsip_msg_body));
-	status = pjsip_msg_body_clone(tdata->pool, 
-				      tdata->msg->body, 
-				      body);
-	if (status != PJ_SUCCESS) {
-	    tdata->msg->body = NULL;
+	tdata->msg->body = pjsip_msg_body_clone(tdata->pool, body);
+	if (tdata->msg->body == NULL) {
+
+	    PJ_LOG(4,(THIS_FILE, "Error: unable to clone msg body"));
+
 	    /* Ignore */
 	    return PJ_SUCCESS;
Index: /pjproject/trunk/pjsip/src/pjsip-simple/iscomposing.c
===================================================================
--- /pjproject/trunk/pjsip/src/pjsip-simple/iscomposing.c	(revision 268)
+++ /pjproject/trunk/pjsip/src/pjsip-simple/iscomposing.c	(revision 268)
@@ -0,0 +1,215 @@
+/* $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-simple/iscomposing.h>
+#include <pjsip-simple/errno.h>
+#include <pjsip/sip_msg.h>
+#include <pjlib-util/errno.h>
+#include <pj/pool.h>
+#include <pj/string.h>
+
+
+/* MIME */
+static const pj_str_t STR_MIME_TYPE = { "application", 11 };
+static const pj_str_t STR_MIME_SUBTYPE = { "im-iscomposing+xml", 18 };
+
+
+/* XML node constants. */
+static const pj_str_t STR_ISCOMPOSING	= { "isComposing", 11 };
+static const pj_str_t STR_STATE		= { "state", 5 };
+static const pj_str_t STR_ACTIVE	= { "active", 6 };
+static const pj_str_t STR_IDLE		= { "idle", 4 };
+static const pj_str_t STR_LASTACTIVE	= { "lastactive", 10 };
+static const pj_str_t STR_CONTENTTYPE	= { "contenttype", 11 };
+static const pj_str_t STR_REFRESH	= { "refresh", 7 };
+
+
+/* XML attributes constants */
+static const pj_str_t STR_XMLNS_NAME =     { "xmlns", 5 };
+static const pj_str_t STR_XMLNS_VAL =      { "urn:ietf:params:xml:ns:im-iscomposing", 37 };
+static const pj_str_t STR_XMLNS_XSI_NAME = { "xmlns:xsi", 9 };
+static const pj_str_t STR_XMLNS_XSI_VAL  = { "http://www.w3.org/2001/XMLSchema-instance", 41 };
+static const pj_str_t STR_XSI_SLOC_NAME =  { "xsi:schemaLocation", 18 };
+static const pj_str_t STR_XSI_SLOC_VAL =   { "urn:ietf:params:xml:ns:im-composing iscomposing.xsd", 51 };
+
+
+PJ_DEF(pj_xml_node*) pjsip_iscomposing_create_xml( pj_pool_t *pool,
+						   pj_bool_t is_composing,
+						   const pj_time_val *lst_actv,
+						   const pj_str_t *content_tp,
+						   int refresh)
+{
+    pj_xml_node *doc, *node;
+    pj_xml_attr *attr;
+
+    /* Root document. */
+    doc = pj_xml_node_new(pool, &STR_ISCOMPOSING);
+
+    /* Add attributes */
+    attr = pj_xml_attr_new(pool, &STR_XMLNS_NAME, &STR_XMLNS_VAL);
+    pj_xml_add_attr(doc, attr);
+
+    attr = pj_xml_attr_new(pool, &STR_XMLNS_XSI_NAME, &STR_XMLNS_XSI_VAL);
+    pj_xml_add_attr(doc, attr);
+
+    attr = pj_xml_attr_new(pool, &STR_XSI_SLOC_NAME, &STR_XSI_SLOC_VAL);
+    pj_xml_add_attr(doc, attr);
+
+
+    /* Add state. */
+    node = pj_xml_node_new(pool, &STR_STATE);
+    if (is_composing)
+	node->content = STR_ACTIVE;
+    else
+	node->content = STR_IDLE;
+    pj_xml_add_node(doc, node);
+
+    /* Add lastactive, if any. */
+    if (!is_composing && lst_actv) {
+	PJ_TODO(IMPLEMENT_LAST_ACTIVE_ATTRIBUTE);
+    }
+
+    /* Add contenttype, if any. */
+    if (content_tp) {
+	node = pj_xml_node_new(pool, &STR_CONTENTTYPE);
+	pj_strdup(pool, &node->content, content_tp);
+	pj_xml_add_node(doc, node);
+    }
+
+    /* Add refresh, if any. */
+    if (is_composing && refresh > 1 && refresh < 3601) {
+	node = pj_xml_node_new(pool, &STR_REFRESH);
+	node->content.ptr = pj_pool_alloc(pool, 10);
+	node->content.slen = pj_utoa(refresh, node->content.ptr);
+	pj_xml_add_node(doc, node);
+    }
+
+    /* Done! */
+
+    return doc;
+}
+
+
+
+/*
+ * Function to print XML message body.
+ */
+static int xml_print_body( struct pjsip_msg_body *msg_body, 
+			   char *buf, pj_size_t size)
+{
+    return pj_xml_print(msg_body->data, buf, size, PJ_TRUE);
+}
+
+
+/*
+ * Function to clone XML document.
+ */
+static void* xml_clone_data(pj_pool_t *pool, const void *data, unsigned len)
+{
+    PJ_UNUSED_ARG(len);
+    return pj_xml_clone( pool, data);
+}
+
+
+
+PJ_DEF(pjsip_msg_body*) pjsip_iscomposing_create_body( pj_pool_t *pool,
+						   pj_bool_t is_composing,
+						   const pj_time_val *lst_actv,
+						   const pj_str_t *content_tp,
+						   int refresh)
+{
+    pj_xml_node *doc;
+    pjsip_msg_body *body;
+
+    doc = pjsip_iscomposing_create_xml( pool, is_composing, lst_actv,
+					content_tp, refresh);
+    if (doc == NULL)
+	return NULL;
+
+
+    body = pj_pool_zalloc(pool, sizeof(pjsip_msg_body));
+    body->content_type.type = STR_MIME_TYPE;
+    body->content_type.subtype = STR_MIME_SUBTYPE;
+
+    body->data = doc;
+    body->len = 0;
+
+    body->print_body = &xml_print_body;
+    body->clone_data = &xml_clone_data;
+
+    return body;
+}
+
+
+PJ_DEF(pj_status_t) pjsip_iscomposing_parse( pj_pool_t *pool,
+					     char *msg,
+					     pj_size_t len,
+					     pj_bool_t *p_is_composing,
+					     pj_str_t **p_last_active,
+					     pj_str_t **p_content_type,
+					     int *p_refresh )
+{
+    pj_xml_node *doc, *node;
+
+    /* Set defaults: */
+    if (p_is_composing) *p_is_composing = PJ_FALSE;
+    if (p_last_active) *p_last_active = NULL; 
+    if (p_content_type) *p_content_type = NULL;
+
+    /* Parse XML */
+    doc = pj_xml_parse( pool, msg, len);
+    if (!doc)
+	return PJLIB_UTIL_EINXML;
+
+    /* Root document must be "isComposing" */
+    if (pj_stricmp(&doc->name, &STR_ISCOMPOSING) != 0)
+	return PJSIP_SIMPLE_EBADISCOMPOSE;
+
+    /* Get the status. */
+    if (p_is_composing) {
+	node = pj_xml_find_node(doc, &STR_STATE);
+	if (node == NULL)
+	    return PJSIP_SIMPLE_EBADISCOMPOSE;
+	*p_is_composing = (pj_stricmp(&node->content, &STR_ACTIVE)==0);
+    }
+
+    /* Get last active. */
+    if (p_last_active) {
+	node = pj_xml_find_node(doc, &STR_LASTACTIVE);
+	if (node)
+	    *p_last_active = &node->content;
+    }
+
+    /* Get content type */
+    if (p_content_type) {
+	node = pj_xml_find_node(doc, &STR_CONTENTTYPE);
+	if (node)
+	    *p_content_type = &node->content;
+    }
+
+    /* Get refresh */
+    if (p_refresh) {
+	node = pj_xml_find_node(doc, &STR_REFRESH);
+	if (node)
+	    *p_refresh = pj_strtoul(&node->content);
+    }
+
+    return PJ_SUCCESS;
+}
+
+
Index: /pjproject/trunk/pjsip/src/pjsip-simple/presence.c
===================================================================
--- /pjproject/trunk/pjsip/src/pjsip-simple/presence.c	(revision 267)
+++ /pjproject/trunk/pjsip/src/pjsip-simple/presence.c	(revision 268)
@@ -787,4 +787,91 @@
 
 /*
+ * Process the content of incoming NOTIFY request and update temporary
+ * status.
+ *
+ * return PJ_SUCCESS if incoming request is acceptable. If return value
+ *	  is not PJ_SUCCESS, res_hdr may be added with Warning header.
+ */
+static pj_status_t pres_process_rx_notify( pjsip_pres *pres,
+					   pjsip_rx_data *rdata, 
+					   int *p_st_code,
+					   pj_str_t **p_st_text,
+					   pjsip_hdr *res_hdr)
+{
+    pjsip_ctype_hdr *ctype_hdr;
+    pj_status_t status;
+
+    *p_st_text = NULL;
+
+    /* Check Content-Type and msg body are present. */
+    ctype_hdr = rdata->msg_info.ctype;
+
+    if (ctype_hdr==NULL || rdata->msg_info.msg->body==NULL) {
+	
+	pjsip_warning_hdr *warn_hdr;
+	pj_str_t warn_text;
+
+	*p_st_code = PJSIP_SC_BAD_REQUEST;
+
+	warn_text = pj_str("Message body is not present");
+	warn_hdr = pjsip_warning_hdr_create(rdata->tp_info.pool, 399,
+					    pjsip_endpt_name(pres->dlg->endpt),
+					    &warn_text);
+	pj_list_push_back(res_hdr, warn_hdr);
+
+	return PJSIP_ERRNO_FROM_SIP_STATUS(PJSIP_SC_BAD_REQUEST);
+    }
+
+    /* Parse content. */
+
+    if (pj_stricmp(&ctype_hdr->media.type, &STR_APPLICATION)==0 &&
+	pj_stricmp(&ctype_hdr->media.subtype, &STR_PIDF_XML)==0)
+    {
+	status = pres_parse_pidf( pres, rdata, &pres->tmp_status);
+    }
+    else 
+    if (pj_stricmp(&ctype_hdr->media.type, &STR_APPLICATION)==0 &&
+	pj_stricmp(&ctype_hdr->media.subtype, &STR_XPIDF_XML)==0)
+    {
+	status = pres_parse_xpidf( pres, rdata, &pres->tmp_status);
+    }
+    else
+    {
+	status = PJSIP_SIMPLE_EBADCONTENT;
+    }
+
+    if (status != PJ_SUCCESS) {
+	/* Unsupported or bad Content-Type */
+	pjsip_accept_hdr *accept_hdr;
+	pjsip_warning_hdr *warn_hdr;
+
+	*p_st_code = PJSIP_SC_NOT_ACCEPTABLE_HERE;
+
+	/* Add Accept header */
+	accept_hdr = pjsip_accept_hdr_create(rdata->tp_info.pool);
+	accept_hdr->values[accept_hdr->count++] = STR_APP_PIDF_XML;
+	accept_hdr->values[accept_hdr->count++] = STR_APP_XPIDF_XML;
+	pj_list_push_back(res_hdr, accept_hdr);
+
+	/* Add Warning header */
+	warn_hdr = pjsip_warning_hdr_create_from_status(
+				    rdata->tp_info.pool,
+				    pjsip_endpt_name(pres->dlg->endpt),
+				    status);
+	pj_list_push_back(res_hdr, warn_hdr);
+
+	return status;
+    }
+
+    /* If application calls pres_get_status(), redirect the call to
+     * retrieve the temporary status.
+     */
+    pres->tmp_status._is_valid = PJ_TRUE;
+
+    return PJ_SUCCESS;
+}
+
+
+/*
  * Called when NOTIFY is received.
  */
@@ -796,5 +883,4 @@
 				     pjsip_msg_body **p_body)
 {
-    pjsip_ctype_hdr *ctype_hdr;
     pjsip_pres *pres;
     pj_status_t status;
@@ -803,68 +889,21 @@
     PJ_ASSERT_ON_FAIL(pres!=NULL, {return;});
 
-    /* Check Content-Type and msg body are present. */
-    ctype_hdr = rdata->msg_info.ctype;
-
-    if (ctype_hdr==NULL || rdata->msg_info.msg->body==NULL) {
-	
-	pjsip_warning_hdr *warn_hdr;
-	pj_str_t warn_text;
-
-	*p_st_code = PJSIP_SC_BAD_REQUEST;
-
-	warn_text = pj_str("Message body is not present");
-	warn_hdr = pjsip_warning_hdr_create(rdata->tp_info.pool, 399,
-					    pjsip_endpt_name(pres->dlg->endpt),
-					    &warn_text);
-	pj_list_push_back(res_hdr, warn_hdr);
-
-	return;
-    }
-
-    /* Parse content. */
-
-    if (pj_stricmp(&ctype_hdr->media.type, &STR_APPLICATION)==0 &&
-	pj_stricmp(&ctype_hdr->media.subtype, &STR_PIDF_XML)==0)
-    {
-	status = pres_parse_pidf( pres, rdata, &pres->tmp_status);
-    }
-    else 
-    if (pj_stricmp(&ctype_hdr->media.type, &STR_APPLICATION)==0 &&
-	pj_stricmp(&ctype_hdr->media.subtype, &STR_XPIDF_XML)==0)
-    {
-	status = pres_parse_xpidf( pres, rdata, &pres->tmp_status);
-    }
-    else
-    {
-	status = PJSIP_SIMPLE_EBADCONTENT;
-    }
-
-    if (status != PJ_SUCCESS) {
-	/* Unsupported or bad Content-Type */
-	pjsip_accept_hdr *accept_hdr;
-	pjsip_warning_hdr *warn_hdr;
-
-	*p_st_code = PJSIP_SC_NOT_ACCEPTABLE_HERE;
-
-	/* Add Accept header */
-	accept_hdr = pjsip_accept_hdr_create(rdata->tp_info.pool);
-	accept_hdr->values[accept_hdr->count++] = STR_APP_PIDF_XML;
-	accept_hdr->values[accept_hdr->count++] = STR_APP_XPIDF_XML;
-	pj_list_push_back(res_hdr, accept_hdr);
-
-	/* Add Warning header */
-	warn_hdr = pjsip_warning_hdr_create_from_status(
-				    rdata->tp_info.pool,
-				    pjsip_endpt_name(pres->dlg->endpt),
-				    status);
-	pj_list_push_back(res_hdr, warn_hdr);
-
-	return;
-    }
-
-    /* If application calls pres_get_status(), redirect the call to
-     * retrieve the temporary status.
+    /* Only process the message body if it exists, otherwise treat as
+     * presence status is closed. 
      */
-    pres->tmp_status._is_valid = PJ_TRUE;
+    if (rdata->msg_info.msg->body) {
+	status = pres_process_rx_notify( pres, rdata, p_st_code, p_st_text,
+					 res_hdr );
+	if (status != PJ_SUCCESS)
+	    return;
+
+    } else {
+	unsigned i;
+
+	/* Subscription is terminated. Consider contact is offline */
+	pres->tmp_status._is_valid = PJ_TRUE;
+	for (i=0; i<pres->tmp_status.info_cnt; ++i)
+	    pres->tmp_status.info[i].basic_open = PJ_FALSE;
+    }
 
     /* Notify application. */
Index: /pjproject/trunk/pjsip/src/pjsua-lib/pjsua_call.c
===================================================================
--- /pjproject/trunk/pjsip/src/pjsua-lib/pjsua_call.c	(revision 267)
+++ /pjproject/trunk/pjsip/src/pjsua-lib/pjsua_call.c	(revision 268)
@@ -22,7 +22,7 @@
 
 /*
- * pjsua_inv.c
+ * pjsua_call.c
  *
- * Invite session specific functionalities.
+ * Call (INVITE) related stuffs.
  */
 
@@ -260,5 +260,5 @@
     if (status != PJ_SUCCESS) {
 
-	pjsip_dlg_respond(dlg, rdata, 500, NULL);
+	pjsip_dlg_respond(dlg, rdata, 500, NULL, NULL, NULL);
 
 	// TODO: Need to delete dialog
@@ -286,5 +286,5 @@
 	pjsua_perror(THIS_FILE, "Unable to create 100 response", status);
 
-	pjsip_dlg_respond(dlg, rdata, 500, NULL);
+	pjsip_dlg_respond(dlg, rdata, 500, NULL, NULL, NULL);
 
 	// TODO: Need to delete dialog
@@ -386,5 +386,5 @@
 
 
-    pjsua_ui_inv_on_state_changed(call->index, e);
+    pjsua_ui_on_call_state(call->index, e);
 
     /* call->inv may be NULL now */
@@ -464,5 +464,5 @@
 	 */
 	PJ_LOG(4,(THIS_FILE, "Received REFER without Refer-To header!"));
-	pjsip_dlg_respond( inv->dlg, rdata, 400, NULL);
+	pjsip_dlg_respond( inv->dlg, rdata, 400, NULL, NULL, NULL);
 	return;
     }
@@ -482,5 +482,5 @@
     if (status != PJ_SUCCESS) {
 	pjsua_perror(THIS_FILE, "Unable to create xfer uas", status);
-	pjsip_dlg_respond( inv->dlg, rdata, 500, NULL);
+	pjsip_dlg_respond( inv->dlg, rdata, 500, NULL, NULL, NULL);
 	return;
     }
@@ -546,5 +546,7 @@
 /*
  * This callback is called when transaction state has changed in INVITE
- * session. We use this to trap incoming REFER request.
+ * session. We use this to trap:
+ *  - incoming REFER request.
+ *  - incoming MESSAGE request.
  */
 static void pjsua_call_on_tsx_state_changed(pjsip_inv_session *inv,
@@ -562,5 +564,46 @@
 	 */
 	on_call_transfered(call->inv, e->body.tsx_state.src.rdata);
-    }
+
+    }
+    else if (tsx->role==PJSIP_ROLE_UAS &&
+	     tsx->state==PJSIP_TSX_STATE_TRYING &&
+	     pjsip_method_cmp(&tsx->method, &pjsip_message_method)==0)
+    {
+	/*
+	 * Incoming MESSAGE request!
+	 */
+	pjsip_rx_data *rdata;
+	pjsip_msg *msg;
+	pjsip_accept_hdr *accept_hdr;
+	pj_status_t status;
+
+	rdata = e->body.tsx_state.src.rdata;
+	msg = rdata->msg_info.msg;
+
+	/* Request MUST have message body, with Content-Type equal to
+	 * "text/plain".
+	 */
+	if (pjsua_im_accept_pager(rdata, &accept_hdr) == PJ_FALSE) {
+
+	    pjsip_hdr hdr_list;
+
+	    pj_list_init(&hdr_list);
+	    pj_list_push_back(&hdr_list, accept_hdr);
+
+	    pjsip_dlg_respond( inv->dlg, rdata, PJSIP_SC_NOT_ACCEPTABLE_HERE, 
+			       NULL, &hdr_list, NULL );
+	    return;
+	}
+
+	/* Respond with 200 first, so that remote doesn't retransmit in case
+	 * the UI takes too long to process the message. 
+	 */
+	status = pjsip_dlg_respond( inv->dlg, rdata, 200, NULL, NULL, NULL);
+
+	/* Process MESSAGE request */
+	pjsua_im_process_pager(call->index, &inv->dlg->remote.info_str,
+			       &inv->dlg->local.info_str, rdata);
+    }
+
 }
 
@@ -1046,4 +1089,102 @@
 
 
+/**
+ * Send instant messaging inside INVITE session.
+ */
+void pjsua_call_send_im(int call_index, const char *str)
+{
+    pjsua_call *call;
+    const pj_str_t mime_text = pj_str("text");
+    const pj_str_t mime_plain = pj_str("plain");
+    pj_str_t text;
+    pjsip_tx_data *tdata;
+    pj_status_t status;
+
+    call = &pjsua.calls[call_index];
+
+    if (!call->inv) {
+	PJ_LOG(3,(THIS_FILE,"Call has been disconnected"));
+	return;
+    }
+
+    /* Lock dialog. */
+    pjsip_dlg_inc_lock(call->inv->dlg);
+    
+    /* Create request message. */
+    status = pjsip_dlg_create_request( call->inv->dlg, &pjsip_message_method,
+				       -1, &tdata);
+    if (status != PJ_SUCCESS) {
+	pjsua_perror(THIS_FILE, "Unable to create MESSAGE request", status);
+	goto on_return;
+    }
+
+    /* Add accept header. */
+    pjsip_msg_add_hdr( tdata->msg, 
+		       (pjsip_hdr*)pjsua_im_create_accept(tdata->pool));
+
+    /* Create "text/plain" message body. */
+    tdata->msg->body = pjsip_msg_body_create( tdata->pool, &mime_text,
+					      &mime_plain, 
+					      pj_cstr(&text, str));
+    if (tdata->msg->body == NULL) {
+	pjsua_perror(THIS_FILE, "Unable to create msg body", PJ_ENOMEM);
+	pjsip_tx_data_dec_ref(tdata);
+	goto on_return;
+    }
+
+    /* Send the request. */
+    status = pjsip_dlg_send_request( call->inv->dlg, tdata, NULL);
+    if (status != PJ_SUCCESS) {
+	pjsua_perror(THIS_FILE, "Unable to send MESSAGE request", status);
+	goto on_return;
+    }
+
+on_return:
+    pjsip_dlg_dec_lock(call->inv->dlg);
+}
+
+
+/**
+ * Send IM typing indication inside INVITE session.
+ */
+void pjsua_call_typing(int call_index, pj_bool_t is_typing)
+{
+    pjsua_call *call;
+    pjsip_tx_data *tdata;
+    pj_status_t status;
+
+    call = &pjsua.calls[call_index];
+
+    if (!call->inv) {
+	PJ_LOG(3,(THIS_FILE,"Call has been disconnected"));
+	return;
+    }
+
+    /* Lock dialog. */
+    pjsip_dlg_inc_lock(call->inv->dlg);
+    
+    /* Create request message. */
+    status = pjsip_dlg_create_request( call->inv->dlg, &pjsip_message_method,
+				       -1, &tdata);
+    if (status != PJ_SUCCESS) {
+	pjsua_perror(THIS_FILE, "Unable to create MESSAGE request", status);
+	goto on_return;
+    }
+
+    /* Create "application/im-iscomposing+xml" msg body. */
+    tdata->msg->body = pjsip_iscomposing_create_body(tdata->pool, is_typing,
+						     NULL, NULL, -1);
+
+    /* Send the request. */
+    status = pjsip_dlg_send_request( call->inv->dlg, tdata, NULL);
+    if (status != PJ_SUCCESS) {
+	pjsua_perror(THIS_FILE, "Unable to send MESSAGE request", status);
+	goto on_return;
+    }
+
+on_return:
+    pjsip_dlg_dec_lock(call->inv->dlg);}
+
+
 /*
  * Terminate all calls.
Index: /pjproject/trunk/pjsip/src/pjsua-lib/pjsua_core.c
===================================================================
--- /pjproject/trunk/pjsip/src/pjsua-lib/pjsua_core.c	(revision 267)
+++ /pjproject/trunk/pjsip/src/pjsua-lib/pjsua_core.c	(revision 268)
@@ -510,4 +510,8 @@
     pjsua_pres_init();
 
+    /* Init out-of-dialog MESSAGE request handler. */
+
+    pjsua_im_init();
+
 
     /* Init media endpoint: */
Index: /pjproject/trunk/pjsip/src/pjsua-lib/pjsua_im.c
===================================================================
--- /pjproject/trunk/pjsip/src/pjsua-lib/pjsua_im.c	(revision 268)
+++ /pjproject/trunk/pjsip/src/pjsua-lib/pjsua_im.c	(revision 268)
@@ -0,0 +1,385 @@
+/* $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-lib/pjsua.h>
+#include <pj/log.h>
+
+/*
+ * pjsua_im.c
+ *
+ * To handle incoming MESSAGE outside dialog.
+ * Incoming MESSAGE inside dialog is hanlded in pjsua_call.c.
+ */
+
+#define THIS_FILE   "pjsua_im.c"
+
+
+/* Declare MESSAGE method */
+/* We put PJSIP_MESSAGE_METHOD as the enum here, so that when
+ * somebody add that method into pjsip_method_e in sip_msg.h we
+ * will get an error here.
+ */
+enum
+{
+    PJSIP_MESSAGE_METHOD = PJSIP_OTHER_METHOD
+};
+
+const pjsip_method pjsip_message_method =
+{
+    PJSIP_MESSAGE_METHOD,
+    { "MESSAGE", 7 }
+};
+
+
+/* Proto */
+static pj_bool_t im_on_rx_request(pjsip_rx_data *rdata);
+
+/* The module instance. */
+static pjsip_module mod_pjsua_im = 
+{
+    NULL, NULL,				/* prev, next.		*/
+    { "mod-pjsua-im", 12 },		/* Name.		*/
+    -1,					/* Id			*/
+    PJSIP_MOD_PRIORITY_APPLICATION,	/* Priority	        */
+    NULL,				/* load()		*/
+    NULL,				/* start()		*/
+    NULL,				/* stop()		*/
+    NULL,				/* unload()		*/
+    &im_on_rx_request,			/* on_rx_request()	*/
+    NULL,				/* on_rx_response()	*/
+    NULL,				/* on_tx_request.	*/
+    NULL,				/* on_tx_response()	*/
+    NULL,				/* on_tsx_state()	*/
+
+};
+
+
+/* MIME constants. */
+static const pj_str_t STR_MIME_APP	   = { "application", 11 };
+static const pj_str_t STR_MIME_ISCOMPOSING = { "im-iscomposing+xml", 18 };
+static const pj_str_t STR_MIME_TEXT	   = { "text", 4 };
+static const pj_str_t STR_MIME_PLAIN	   = { "plain", 5 };
+
+
+/* Check if content type is acceptable */
+static pj_bool_t acceptable_message(const pjsip_media_type *mime)
+{
+    return (pj_stricmp(&mime->type, &STR_MIME_TEXT)==0 &&
+	    pj_stricmp(&mime->subtype, &STR_MIME_PLAIN)==0)
+	    ||
+	   (pj_stricmp(&mime->type, &STR_MIME_APP)==0 &&
+	    pj_stricmp(&mime->subtype, &STR_MIME_ISCOMPOSING)==0);
+}
+
+
+/**
+ * Create Accept header for MESSAGE.
+ */
+pjsip_accept_hdr* pjsua_im_create_accept(pj_pool_t *pool)
+{
+    /* Create Accept header. */
+    pjsip_accept_hdr *accept;
+
+    accept = pjsip_accept_hdr_create(pool);
+    accept->values[0] = pj_str("text/plain");
+    accept->values[1] = pj_str("application/im-iscomposing+xml");
+    accept->count = 2;
+
+    return accept;
+}
+
+/**
+ * Private: check if we can accept the message.
+ */
+pj_bool_t pjsua_im_accept_pager(pjsip_rx_data *rdata,
+				const pjsip_accept_hdr **p_accept_hdr)
+{
+    pjsip_ctype_hdr *ctype;
+    pjsip_msg *msg;
+
+    msg = rdata->msg_info.msg;
+
+    /* Request MUST have message body, with Content-Type equal to
+     * "text/plain".
+     */
+    ctype = pjsip_msg_find_hdr(msg, PJSIP_H_CONTENT_TYPE, NULL);
+    if (msg->body == NULL || ctype == NULL || 
+	!acceptable_message(&ctype->media)) 
+    {
+	/* Create Accept header. */
+	if (p_accept_hdr)
+	    *p_accept_hdr = pjsua_im_create_accept(rdata->tp_info.pool);
+
+	return PJ_FALSE;
+    }
+
+    return PJ_TRUE;
+}
+
+/**
+ * Private: process pager message.
+ *	    This may trigger pjsua_ui_on_pager() or pjsua_ui_on_typing().
+ */
+void pjsua_im_process_pager(int call_index, const pj_str_t *from,
+			    const pj_str_t *to, pjsip_rx_data *rdata)
+{
+    pjsip_msg_body *body = rdata->msg_info.msg->body;
+
+    /* Body MUST have been checked before */
+    pj_assert(body != NULL);
+
+    if (pj_stricmp(&body->content_type.type, &STR_MIME_TEXT)==0 &&
+	pj_stricmp(&body->content_type.subtype, &STR_MIME_PLAIN)==0)
+    {
+	pj_str_t text;
+
+	/* Build the text. */
+	text.ptr = rdata->msg_info.msg->body->data;
+	text.slen = rdata->msg_info.msg->body->len;
+
+	pjsua_ui_on_pager(call_index, from, to, &text);
+
+    } else {
+
+	/* Expecting typing indication */
+
+	pj_status_t status;
+	pj_bool_t is_typing;
+
+	status = pjsip_iscomposing_parse( rdata->tp_info.pool, body->data,
+					  body->len, &is_typing, NULL, NULL,
+					  NULL );
+	if (status != PJ_SUCCESS) {
+	    pjsua_perror(THIS_FILE, "Invalid MESSAGE body", status);
+	    return;
+	}
+
+	pjsua_ui_on_typing(call_index, from, to, is_typing);
+    }
+
+}
+
+
+/*
+ * Handler to receive incoming MESSAGE
+ */
+static pj_bool_t im_on_rx_request(pjsip_rx_data *rdata)
+{
+    pj_str_t from, to;
+    pjsip_accept_hdr *accept_hdr;
+    pjsip_msg *msg;
+    pj_status_t status;
+
+    msg = rdata->msg_info.msg;
+
+    /* Only want to handle MESSAGE requests. */
+    if (pjsip_method_cmp(&msg->line.req.method, &pjsip_message_method) != 0) {
+	return PJ_FALSE;
+    }
+
+
+    /* Should not have any transaction attached to rdata. */
+    PJ_ASSERT_RETURN(pjsip_rdata_get_tsx(rdata)==NULL, PJ_FALSE);
+
+    /* Should not have any dialog attached to rdata. */
+    PJ_ASSERT_RETURN(pjsip_rdata_get_dlg(rdata)==NULL, PJ_FALSE);
+
+    /* Check if we can accept the message. */
+    if (!pjsua_im_accept_pager(rdata, &accept_hdr)) {
+	pjsip_hdr hdr_list;
+
+	pj_list_init(&hdr_list);
+	pj_list_push_back(&hdr_list, accept_hdr);
+
+	pjsip_endpt_respond_stateless(pjsua.endpt, rdata, 
+				      PJSIP_SC_NOT_ACCEPTABLE_HERE, NULL, 
+				      &hdr_list, NULL);
+	return PJ_TRUE;
+    }
+    
+    /* Respond with 200 first, so that remote doesn't retransmit in case
+     * the UI takes too long to process the message. 
+     */
+    status = pjsip_endpt_respond( pjsua.endpt, NULL, rdata, 200, NULL,
+				  NULL, NULL, NULL);
+
+    /* Build the From text. */
+    from.ptr = pj_pool_alloc(rdata->tp_info.pool, PJSIP_MAX_URL_SIZE);
+    from.slen = pjsip_uri_print(PJSIP_URI_IN_FROMTO_HDR, 
+				rdata->msg_info.from->uri,
+				from.ptr, PJSIP_MAX_URL_SIZE);
+    if (from.slen < 1)
+	from = pj_str("<--URI is too long-->");
+
+    /* Build the To text. */
+    to.ptr = pj_pool_alloc(rdata->tp_info.pool, PJSIP_MAX_URL_SIZE);
+    to.slen = pjsip_uri_print( PJSIP_URI_IN_FROMTO_HDR, 
+			       rdata->msg_info.to->uri,
+			       to.ptr, PJSIP_MAX_URL_SIZE);
+    if (to.slen < 1)
+	to = pj_str("<--URI is too long-->");
+
+    /* Process pager. */
+    pjsua_im_process_pager(-1, &from, &to, rdata);
+
+    /* Done. */
+    return PJ_TRUE;
+}
+
+
+/* Outgoing IM callback. */
+static void im_callback(void *token, pjsip_event *e)
+{
+    pj_str_t *text = token;
+
+    if (e->type == PJSIP_EVENT_TSX_STATE) {
+
+	pjsip_transaction *tsx = e->body.tsx_state.tsx;
+
+	if (tsx->status_code/100 == 2) {
+	    PJ_LOG(4,(THIS_FILE, 
+		      "Message \'%s\' delivered successfully",
+		      text->ptr));
+	} else {
+	    PJ_LOG(3,(THIS_FILE, 
+		      "Failed to deliver message \'%s\': %s [st_code=%d]",
+		      text->ptr,
+		      pjsip_get_status_text(tsx->status_code)->ptr,
+		      tsx->status_code));
+	}
+    }
+}
+
+
+/**
+ * Send IM outside dialog.
+ */
+pj_status_t pjsua_im_send(int acc_index, const char *dst_uri, 
+			  const char *str)
+{
+    pjsip_tx_data *tdata;
+    const pj_str_t STR_CONTACT = { "Contact", 7 };
+    const pj_str_t mime_text = pj_str("text");
+    const pj_str_t mime_plain = pj_str("plain");
+    pj_str_t *text;
+    const pj_str_t dst = pj_str((char*)dst_uri);
+    pj_status_t status;
+
+    /* Create request. */
+    status = pjsip_endpt_create_request( pjsua.endpt, &pjsip_message_method,
+					 &dst, &pjsua.acc[acc_index].local_uri,
+					 &dst, NULL, NULL, -1, NULL, &tdata);
+    if (status != PJ_SUCCESS) {
+	pjsua_perror(THIS_FILE, "Unable to create request", status);
+	return status;
+    }
+
+    /* Add accept header. */
+    pjsip_msg_add_hdr( tdata->msg, 
+		       (pjsip_hdr*)pjsua_im_create_accept(tdata->pool));
+
+    /* Add contact. */
+    pjsip_msg_add_hdr( tdata->msg, (pjsip_hdr*)
+		       pjsip_generic_string_hdr_create(tdata->pool, 
+						       &STR_CONTACT,
+						       &pjsua.acc[acc_index].contact_uri));
+
+    /* Duplicate text.
+     * We need to keep the text because we will display it when we fail to
+     * send the message.
+     */
+    text = pj_pool_alloc(tdata->pool, sizeof(pj_str_t));
+    pj_strdup2_with_null(tdata->pool, text, str);
+
+    /* Add message body */
+    tdata->msg->body = pjsip_msg_body_create( tdata->pool, &mime_text,
+					      &mime_plain, text);
+    if (tdata->msg->body == NULL) {
+	pjsua_perror(THIS_FILE, "Unable to create msg body", PJ_ENOMEM);
+	pjsip_tx_data_dec_ref(tdata);
+	return PJ_ENOMEM;
+    }
+
+    /* Send request (statefully) */
+    status = pjsip_endpt_send_request( pjsua.endpt, tdata, -1, 
+				       text, &im_callback);
+    if (status != PJ_SUCCESS) {
+	pjsua_perror(THIS_FILE, "Unable to send request", status);
+	return status;
+    }
+
+    return PJ_SUCCESS;
+}
+
+
+/**
+ * Send typing indication outside dialog.
+ */
+pj_status_t pjsua_im_typing(int acc_index, const char *dst_uri, 
+			    pj_bool_t is_typing)
+{
+    const pj_str_t dst = pj_str((char*)dst_uri);
+    pjsip_tx_data *tdata;
+    pj_status_t status;
+
+    /* Create request. */
+    status = pjsip_endpt_create_request( pjsua.endpt, &pjsip_message_method,
+					 &dst, &pjsua.acc[acc_index].local_uri,
+					 &dst, NULL, NULL, -1, NULL, &tdata);
+    if (status != PJ_SUCCESS) {
+	pjsua_perror(THIS_FILE, "Unable to create request", status);
+	return status;
+    }
+
+
+    /* Create "application/im-iscomposing+xml" msg body. */
+    tdata->msg->body = pjsip_iscomposing_create_body( tdata->pool, is_typing,
+						      NULL, NULL, -1);
+
+    /* Send request (statefully) */
+    status = pjsip_endpt_send_request( pjsua.endpt, tdata, -1, 
+				       NULL, NULL);
+    if (status != PJ_SUCCESS) {
+	pjsua_perror(THIS_FILE, "Unable to send request", status);
+	return status;
+    }
+
+    return PJ_SUCCESS;
+}
+
+
+/*
+ * Init pjsua IM module.
+ */
+pj_status_t pjsua_im_init(void)
+{
+    const pj_str_t msg_tag = { "MESSAGE", 7 };
+    pj_status_t status;
+
+    /* Register module */
+    status = pjsip_endpt_register_module(pjsua.endpt, &mod_pjsua_im);
+    if (status != PJ_SUCCESS)
+	return status;
+
+    /* Register support for MESSAGE method. */
+    pjsip_endpt_add_capability( pjsua.endpt, &mod_pjsua_im, PJSIP_H_ALLOW,
+				NULL, 1, &msg_tag);
+
+    return PJ_SUCCESS;
+}
+
Index: project/trunk/pjsip/src/pjsua-lib/pjsua_opt.c
===================================================================
--- /pjproject/trunk/pjsip/src/pjsua-lib/pjsua_opt.c	(revision 267)
+++ 	(revision )
@@ -1,878 +1,0 @@
-/* $Id$ */
-/* 
- * Copyright (C) 2003-2006 Benny Prijono <benny@prijono.org>
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA 
- */
-#include <pjsua-lib/pjsua.h>
-#include <pjsua-lib/getopt.h>
-
-#define THIS_FILE   "pjsua_opt.c"
-
-
-const char *pjsua_inv_state_names[] =
-{
-    "NULL      ",
-    "CALLING   ",
-    "INCOMING  ",
-    "EARLY     ",
-    "CONNECTING",
-    "CONFIRMED ",
-    "DISCONNCTD",
-    "TERMINATED",
-};
-
-
-
-/* Show usage */
-static void usage(void)
-{
-    puts("Usage:");
-    puts("  pjsua [options]");
-    puts("");
-    puts("General options:");
-    puts("  --help              Display this help screen");
-    puts("  --version           Display version info");
-    puts("");
-    puts("Logging options:");
-    puts("  --config-file=file  Read the config/arguments from file.");
-    puts("  --log-file=fname    Log to filename (default stderr)");
-    puts("  --log-level=N       Set log max level to N (0(none) to 6(trace))");
-    puts("  --app-log-level=N   Set log max level for stdout display to N");
-    puts("");
-    puts("SIP Account options:");
-    puts("  --id=url            Set the URL of local ID (used in From header)");
-    puts("  --contact=url       Override the Contact information");
-    puts("  --proxy=url         Set the URL of proxy server");
-    puts("");
-    puts("SIP Account Registration Options:");
-    puts("  --registrar=url     Set the URL of registrar server");
-    puts("  --reg-timeout=secs  Set registration interval to secs (default 3600)");
-    puts("");
-    puts("SIP Account Control:");
-    puts("  --next-account      Add more account");
-    puts("");
-    puts("Authentication options:");
-    puts("  --realm=string      Set realm");
-    puts("  --username=string   Set authentication username");
-    puts("  --password=string   Set authentication password");
-    puts("  --next-cred         Add more credential");
-    puts("");
-    puts("Transport Options:");
-    puts("  --local-port=port        Set TCP/UDP port");
-    puts("  --outbound=url           Set the URL of outbound proxy server");
-    puts("  --use-stun1=host[:port]");
-    puts("  --use-stun2=host[:port]  Resolve local IP with the specified STUN servers");
-    puts("");
-    puts("Media Options:");
-    puts("  --null-audio        Use NULL audio device");
-    puts("  --play-file=file    Play WAV file in conference bridge");
-    puts("  --auto-play         Automatically play the file (to incoming calls only)");
-    puts("  --auto-loop         Automatically loop incoming RTP to outgoing RTP");
-    puts("  --auto-conf         Automatically put incoming calls to conference");
-    puts("  --rtp-port=N	Base port to try for RTP");
-    puts("");
-    puts("Buddy List (can be more than one):");
-    puts("  --add-buddy url     Add the specified URL to the buddy list.");
-    puts("");
-    puts("User Agent options:");
-    puts("  --auto-answer=code  Automatically answer incoming calls with code (e.g. 200)");
-    puts("  --max-calls=N       Maximum number of concurrent calls (default:4, max:255)");
-    puts("");
-    fflush(stdout);
-}
-
-
-
-/*
- * Verify that valid SIP url is given.
- */
-pj_status_t pjsua_verify_sip_url(const char *c_url)
-{
-    pjsip_uri *p;
-    pj_pool_t *pool;
-    char *url;
-    int len = (c_url ? pj_ansi_strlen(c_url) : 0);
-
-    if (!len) return -1;
-
-    pool = pj_pool_create(&pjsua.cp.factory, "check%p", 1024, 0, NULL);
-    if (!pool) return -1;
-
-    url = pj_pool_alloc(pool, len+1);
-    pj_ansi_strcpy(url, c_url);
-
-    p = pjsip_parse_uri(pool, url, len, 0);
-    if (!p || pj_stricmp2(pjsip_uri_get_scheme(p), "sip") != 0)
-	p = NULL;
-
-    pj_pool_release(pool);
-    return p ? 0 : -1;
-}
-
-
-/*
- * Read command arguments from config file.
- */
-static int read_config_file(pj_pool_t *pool, const char *filename, 
-			    int *app_argc, char ***app_argv)
-{
-    int i;
-    FILE *fhnd;
-    char line[200];
-    int argc = 0;
-    char **argv;
-    enum { MAX_ARGS = 64 };
-
-    /* Allocate MAX_ARGS+1 (argv needs to be terminated with NULL argument) */
-    argv = pj_pool_calloc(pool, MAX_ARGS+1, sizeof(char*));
-    argv[argc++] = *app_argv[0];
-
-    /* Open config file. */
-    fhnd = fopen(filename, "rt");
-    if (!fhnd) {
-	printf("Unable to open config file %s\n", filename);
-	fflush(stdout);
-	return -1;
-    }
-
-    /* Scan tokens in the file. */
-    while (argc < MAX_ARGS && !feof(fhnd)) {
-	char *token, *p = line;
-
-	if (fgets(line, sizeof(line), fhnd) == NULL) break;
-
-	for (token = strtok(p, " \t\r\n"); argc < MAX_ARGS; 
-	     token = strtok(NULL, " \t\r\n"))
-	{
-	    int token_len;
-	    
-	    if (!token) break;
-	    if (*token == '#') break;
-
-	    token_len = strlen(token);
-	    if (!token_len)
-		continue;
-	    argv[argc] = pj_pool_alloc(pool, token_len+1);
-	    pj_memcpy(argv[argc], token, token_len+1);
-	    ++argc;
-	}
-    }
-
-    /* Copy arguments from command line */
-    for (i=1; i<*app_argc && argc < MAX_ARGS; ++i)
-	argv[argc++] = (*app_argv)[i];
-
-    if (argc == MAX_ARGS && (i!=*app_argc || !feof(fhnd))) {
-	printf("Too many arguments specified in cmd line/config file\n");
-	fflush(stdout);
-	fclose(fhnd);
-	return -1;
-    }
-
-    fclose(fhnd);
-
-    /* Assign the new command line back to the original command line. */
-    *app_argc = argc;
-    *app_argv = argv;
-    return 0;
-
-}
-
-static int my_atoi(const char *cs)
-{
-    pj_str_t s;
-    return pj_strtoul(pj_cstr(&s, cs));
-}
-
-
-/* Parse arguments. */
-pj_status_t pjsua_parse_args(int argc, char *argv[])
-{
-    int c;
-    int option_index;
-    enum { OPT_CONFIG_FILE, OPT_LOG_FILE, OPT_LOG_LEVEL, OPT_APP_LOG_LEVEL, 
-	   OPT_HELP, OPT_VERSION, OPT_NULL_AUDIO,
-	   OPT_LOCAL_PORT, OPT_PROXY, OPT_OUTBOUND_PROXY, OPT_REGISTRAR,
-	   OPT_REG_TIMEOUT, OPT_ID, OPT_CONTACT, 
-	   OPT_REALM, OPT_USERNAME, OPT_PASSWORD,
-	   OPT_USE_STUN1, OPT_USE_STUN2, 
-	   OPT_ADD_BUDDY, OPT_OFFER_X_MS_MSG, OPT_NO_PRESENCE,
-	   OPT_AUTO_ANSWER, OPT_AUTO_HANGUP, OPT_AUTO_PLAY, OPT_AUTO_LOOP,
-	   OPT_AUTO_CONF,
-	   OPT_PLAY_FILE, OPT_RTP_PORT,
-	   OPT_NEXT_ACCOUNT, OPT_NEXT_CRED, OPT_MAX_CALLS,
-    };
-    struct option long_options[] = {
-	{ "config-file",1, 0, OPT_CONFIG_FILE},
-	{ "log-file",	1, 0, OPT_LOG_FILE},
-	{ "log-level",	1, 0, OPT_LOG_LEVEL},
-	{ "app-log-level",1,0,OPT_APP_LOG_LEVEL},
-	{ "help",	0, 0, OPT_HELP},
-	{ "version",	0, 0, OPT_VERSION},
-	{ "null-audio", 0, 0, OPT_NULL_AUDIO},
-	{ "local-port", 1, 0, OPT_LOCAL_PORT},
-	{ "proxy",	1, 0, OPT_PROXY},
-	{ "outbound",	1, 0, OPT_OUTBOUND_PROXY},
-	{ "registrar",	1, 0, OPT_REGISTRAR},
-	{ "reg-timeout",1, 0, OPT_REG_TIMEOUT},
-	{ "id",		1, 0, OPT_ID},
-	{ "contact",	1, 0, OPT_CONTACT},
-	{ "realm",	1, 0, OPT_REALM},
-	{ "username",	1, 0, OPT_USERNAME},
-	{ "password",	1, 0, OPT_PASSWORD},
-	{ "use-stun1",  1, 0, OPT_USE_STUN1},
-	{ "use-stun2",  1, 0, OPT_USE_STUN2},
-	{ "add-buddy",  1, 0, OPT_ADD_BUDDY},
-	{ "offer-x-ms-msg",0,0,OPT_OFFER_X_MS_MSG},
-	{ "no-presence", 0, 0, OPT_NO_PRESENCE},
-	{ "auto-answer",1, 0, OPT_AUTO_ANSWER},
-	{ "auto-hangup",1, 0, OPT_AUTO_HANGUP},
-	{ "auto-play",  0, 0, OPT_AUTO_PLAY},
-	{ "auto-loop",  0, 0, OPT_AUTO_LOOP},
-	{ "auto-conf",  0, 0, OPT_AUTO_CONF},
-	{ "play-file",  1, 0, OPT_PLAY_FILE},
-	{ "rtp-port",	1, 0, OPT_RTP_PORT},
-	{ "next-account",0,0, OPT_NEXT_ACCOUNT},
-	{ "next-cred",	0, 0, OPT_NEXT_CRED},
-	{ "max-calls",	1, 0, OPT_MAX_CALLS},
-	{ NULL, 0, 0, 0}
-    };
-    pj_status_t status;
-    pjsua_acc *cur_acc;
-    pjsip_cred_info *cur_cred;
-    char *config_file = NULL;
-
-    /* Run getopt once to see if user specifies config file to read. */
-    while ((c=getopt_long(argc, argv, "", long_options, &option_index)) != -1) {
-	switch (c) {
-	case OPT_CONFIG_FILE:
-	    config_file = optarg;
-	    break;
-	}
-	if (config_file)
-	    break;
-    }
-
-    if (config_file) {
-	status = read_config_file(pjsua.pool, config_file, &argc, &argv);
-	if (status != 0)
-	    return status;
-    }
-
-
-    cur_acc = &pjsua.acc[0];
-    cur_cred = &pjsua.cred_info[0];
-
-
-    /* Reinitialize and re-run getopt again, possibly with new arguments
-     * read from config file.
-     */
-    optind = 0;
-    while ((c=getopt_long(argc, argv, "", long_options, &option_index)) != -1) {
-	char *p;
-	pj_str_t tmp;
-	long lval;
-
-	switch (c) {
-
-	case OPT_LOG_FILE:
-	    pjsua.log_filename = optarg;
-	    break;
-
-	case OPT_LOG_LEVEL:
-	    c = pj_strtoul(pj_cstr(&tmp, optarg));
-	    if (c < 0 || c > 6) {
-		printf("Error: expecting integer value 0-6 for --log-level\n");
-		return PJ_EINVAL;
-	    }
-	    pj_log_set_level( c );
-	    break;
-
-	case OPT_APP_LOG_LEVEL:
-	    pjsua.app_log_level = pj_strtoul(pj_cstr(&tmp, optarg));
-	    if (pjsua.app_log_level < 0 || pjsua.app_log_level > 6) {
-		printf("Error: expecting integer value 0-6 for --app-log-level\n");
-		return PJ_EINVAL;
-	    }
-	    break;
-
-	case OPT_HELP:
-	    usage();
-	    return PJ_EINVAL;
-
-	case OPT_VERSION:   /* version */
-	    pj_dump_config();
-	    return PJ_EINVAL;
-
-	case OPT_NULL_AUDIO:
-	    pjsua.null_audio = 1;
-	    break;
-
-	case OPT_LOCAL_PORT:   /* local-port */
-	    lval = pj_strtoul(pj_cstr(&tmp, optarg));
-	    if (lval < 1 || lval > 65535) {
-		printf("Error: expecting integer value for --local-port\n");
-		return PJ_EINVAL;
-	    }
-	    pjsua.sip_port = (pj_uint16_t)lval;
-	    break;
-
-	case OPT_PROXY:   /* proxy */
-	    if (pjsua_verify_sip_url(optarg) != 0) {
-		printf("Error: invalid SIP URL '%s' in proxy argument\n", optarg);
-		return PJ_EINVAL;
-	    }
-	    cur_acc->proxy = pj_str(optarg);
-	    break;
-
-	case OPT_OUTBOUND_PROXY:   /* outbound proxy */
-	    if (pjsua_verify_sip_url(optarg) != 0) {
-		printf("Error: invalid SIP URL '%s' in outbound proxy argument\n", optarg);
-		return PJ_EINVAL;
-	    }
-	    pjsua.outbound_proxy = pj_str(optarg);
-	    break;
-
-	case OPT_REGISTRAR:   /* registrar */
-	    if (pjsua_verify_sip_url(optarg) != 0) {
-		printf("Error: invalid SIP URL '%s' in registrar argument\n", optarg);
-		return PJ_EINVAL;
-	    }
-	    cur_acc->reg_uri = pj_str(optarg);
-	    break;
-
-	case OPT_REG_TIMEOUT:   /* reg-timeout */
-	    cur_acc->reg_timeout = pj_strtoul(pj_cstr(&tmp,optarg));
-	    if (cur_acc->reg_timeout < 1 || cur_acc->reg_timeout > 3600) {
-		printf("Error: invalid value for --reg-timeout (expecting 1-3600)\n");
-		return PJ_EINVAL;
-	    }
-	    break;
-
-	case OPT_ID:   /* id */
-	    if (pjsua_verify_sip_url(optarg) != 0) {
-		printf("Error: invalid SIP URL '%s' in local id argument\n", optarg);
-		return PJ_EINVAL;
-	    }
-	    cur_acc->local_uri = pj_str(optarg);
-	    break;
-
-	case OPT_CONTACT:   /* contact */
-	    if (pjsua_verify_sip_url(optarg) != 0) {
-		printf("Error: invalid SIP URL '%s' in contact argument\n", optarg);
-		return PJ_EINVAL;
-	    }
-	    cur_acc->contact_uri = pj_str(optarg);
-	    break;
-
-	case OPT_NEXT_ACCOUNT: /* Add more account. */
-	    pjsua.acc_cnt++;
-	    cur_acc = &pjsua.acc[pjsua.acc_cnt - 1];
-	    break;
-
-	case OPT_USERNAME:   /* Default authentication user */
-	    if (pjsua.cred_count==0) pjsua.cred_count=1;
-	    cur_cred->username = pj_str(optarg);
-	    break;
-
-	case OPT_REALM:	    /* Default authentication realm. */
-	    if (pjsua.cred_count==0) pjsua.cred_count=1;
-	    cur_cred->realm = pj_str(optarg);
-	    break;
-
-	case OPT_PASSWORD:   /* authentication password */
-	    if (pjsua.cred_count==0) pjsua.cred_count=1;
-	    cur_cred->data_type = 0;
-	    cur_cred->data = pj_str(optarg);
-	    break;
-
-	case OPT_NEXT_CRED: /* Next credential */
-	    pjsua.cred_count++;
-	    cur_cred = &pjsua.cred_info[pjsua.cred_count - 1];
-	    break;
-
-	case OPT_USE_STUN1:   /* STUN server 1 */
-	    p = pj_ansi_strchr(optarg, ':');
-	    if (p) {
-		*p = '\0';
-		pjsua.stun_srv1 = pj_str(optarg);
-		pjsua.stun_port1 = pj_strtoul(pj_cstr(&tmp, p+1));
-		if (pjsua.stun_port1 < 1 || pjsua.stun_port1 > 65535) {
-		    printf("Error: expecting port number with option --use-stun1\n");
-		    return PJ_EINVAL;
-		}
-	    } else {
-		pjsua.stun_port1 = 3478;
-		pjsua.stun_srv1 = pj_str(optarg);
-	    }
-	    break;
-
-	case OPT_USE_STUN2:   /* STUN server 2 */
-	    p = pj_ansi_strchr(optarg, ':');
-	    if (p) {
-		*p = '\0';
-		pjsua.stun_srv2 = pj_str(optarg);
-		pjsua.stun_port2 = pj_strtoul(pj_cstr(&tmp,p+1));
-		if (pjsua.stun_port2 < 1 || pjsua.stun_port2 > 65535) {
-		    printf("Error: expecting port number with option --use-stun2\n");
-		    return PJ_EINVAL;
-		}
-	    } else {
-		pjsua.stun_port2 = 3478;
-		pjsua.stun_srv2 = pj_str(optarg);
-	    }
-	    break;
-
-	case OPT_ADD_BUDDY: /* Add to buddy list. */
-	    if (pjsua_verify_sip_url(optarg) != 0) {
-		printf("Error: invalid URL '%s' in --add-buddy option\n", optarg);
-		return -1;
-	    }
-	    if (pjsua.buddy_cnt == PJSUA_MAX_BUDDIES) {
-		printf("Error: too many buddies in buddy list.\n");
-		return -1;
-	    }
-	    pjsua.buddies[pjsua.buddy_cnt++].uri = pj_str(optarg);
-	    break;
-
-	case OPT_AUTO_PLAY:
-	    pjsua.auto_play = 1;
-	    break;
-
-	case OPT_AUTO_LOOP:
-	    pjsua.auto_loop = 1;
-	    break;
-
-	case OPT_AUTO_CONF:
-	    pjsua.auto_conf = 1;
-	    break;
-
-	case OPT_PLAY_FILE:
-	    pjsua.wav_file = optarg;
-	    break;
-
-	case OPT_RTP_PORT:
-	    pjsua.start_rtp_port = my_atoi(optarg);
-	    if (pjsua.start_rtp_port < 1 || pjsua.start_rtp_port > 65535) {
-		PJ_LOG(1,(THIS_FILE,
-			  "Error: rtp-port argument value (expecting 1-65535"));
-		return -1;
-	    }
-
-	case OPT_AUTO_ANSWER:
-	    pjsua.auto_answer = my_atoi(optarg);
-	    if (pjsua.auto_answer < 100 || pjsua.auto_answer > 699) {
-		PJ_LOG(1,(THIS_FILE,
-			  "Error: invalid code in --auto-answer (expecting 100-699"));
-		return -1;
-	    }
-	    break;
-
-	case OPT_MAX_CALLS:
-	    pjsua.max_calls = my_atoi(optarg);
-	    if (pjsua.max_calls < 1 || pjsua.max_calls > 255) {
-		PJ_LOG(1,(THIS_FILE,"Too many calls for max-calls (1-255)"));
-		return -1;
-	    }
-	    break;
-	}
-    }
-
-    if (optind != argc) {
-	printf("Error: unknown options %s\n", argv[optind]);
-	return PJ_EINVAL;
-    }
-
-    return PJ_SUCCESS;
-}
-
-
-
-static void print_call(const char *title,
-		       int call_index, 
-		       char *buf, pj_size_t size)
-{
-    int len;
-    pjsip_inv_session *inv = pjsua.calls[call_index].inv;
-    pjsip_dialog *dlg = inv->dlg;
-    char userinfo[128];
-
-    /* Dump invite sesion info. */
-
-    len = pjsip_hdr_print_on(dlg->remote.info, userinfo, sizeof(userinfo));
-    if (len < 1)
-	pj_ansi_strcpy(userinfo, "<--uri too long-->");
-    else
-	userinfo[len] = '\0';
-    
-    len = pj_snprintf(buf, size, "%s[%s] %s",
-		      title,
-		      pjsua_inv_state_names[inv->state],
-		      userinfo);
-    if (len < 1 || len >= (int)size) {
-	pj_ansi_strcpy(buf, "<--uri too long-->");
-	len = 18;
-    } else
-	buf[len] = '\0';
-}
-
-static void dump_media_session(pjmedia_session *session)
-{
-    unsigned i;
-    pjmedia_session_info info;
-
-    pjmedia_session_get_info(session, &info);
-
-    for (i=0; i<info.stream_cnt; ++i) {
-	pjmedia_stream_stat strm_stat;
-	const char *rem_addr;
-	int rem_port;
-	const char *dir;
-
-	pjmedia_session_get_stream_stat(session, i, &strm_stat);
-	rem_addr = pj_inet_ntoa(info.stream_info[i].rem_addr.sin_addr);
-	rem_port = pj_ntohs(info.stream_info[i].rem_addr.sin_port);
-
-	if (info.stream_info[i].dir == PJMEDIA_DIR_ENCODING)
-	    dir = "sendonly";
-	else if (info.stream_info[i].dir == PJMEDIA_DIR_DECODING)
-	    dir = "recvonly";
-	else if (info.stream_info[i].dir == PJMEDIA_DIR_ENCODING_DECODING)
-	    dir = "sendrecv";
-	else
-	    dir = "inactive";
-
-	
-	PJ_LOG(3,(THIS_FILE, 
-		  "%s[Media strm#%d] %.*s, %s, peer=%s:%d",
-		  "               ",
-		  i,
-		  info.stream_info[i].fmt.encoding_name.slen,
-		  info.stream_info[i].fmt.encoding_name.ptr,
-		  dir,
-		  rem_addr, rem_port));
-	PJ_LOG(3,(THIS_FILE, 
-		  "%s tx {pkt=%u, bytes=%u} rx {pkt=%u, bytes=%u}",
-		  "                             ",
-		  strm_stat.enc.pkt, strm_stat.enc.bytes,
-		  strm_stat.dec.pkt, strm_stat.dec.bytes));
-
-    }
-}
-
-/*
- * Dump application states.
- */
-void pjsua_dump(pj_bool_t detail)
-{
-    char buf[128];
-    unsigned old_decor;
-
-    PJ_LOG(3,(THIS_FILE, "Start dumping application states:"));
-
-    old_decor = pj_log_get_decor();
-    pj_log_set_decor(old_decor & (PJ_LOG_HAS_NEWLINE | PJ_LOG_HAS_CR));
-
-    pjsip_endpt_dump(pjsua.endpt, detail);
-    pjmedia_endpt_dump(pjsua.med_endpt);
-    pjsip_tsx_layer_dump(detail);
-    pjsip_ua_dump(detail);
-
-
-    /* Dump all invite sessions: */
-    if (detail) {
-	PJ_LOG(3,(THIS_FILE, "Dumping invite sessions:"));
-
-	if (pjsua.call_cnt == 0) {
-
-	    PJ_LOG(3,(THIS_FILE, "  - no sessions -"));
-
-	} else {
-	    int i;
-
-	    for (i=0; i<pjsua.max_calls; ++i) {
-
-		if (pjsua.calls[i].inv == NULL)
-		    continue;
-
-		print_call("  ", i, buf, sizeof(buf));
-		PJ_LOG(3,(THIS_FILE, "%s", buf));
-
-		if (pjsua.calls[i].session)
-		    dump_media_session(pjsua.calls[i].session);
-	    }
-	}
-    }
-
-    /* Dump presence status */
-    pjsua_pres_dump(detail);
-
-    pj_log_set_decor(old_decor);
-    PJ_LOG(3,(THIS_FILE, "Dump complete"));
-}
-
-
-/*
- * Load settings.
- */
-pj_status_t pjsua_load_settings(const char *filename)
-{
-    int argc = 3;
-    char *argv[4] = { "pjsua", "--config-file", NULL, NULL};
-
-    argv[3] = (char*)filename;
-    return pjsua_parse_args(argc, argv);
-}
-
-
-/*
- * Save account settings
- */
-static void save_account_settings(int acc_index, pj_str_t *result)
-{
-    char line[128];
-    pjsua_acc *acc = &pjsua.acc[acc_index];
-
-    
-    pj_ansi_sprintf(line, "#\n# Account %d:\n#\n", acc_index);
-    pj_strcat2(result, line);
-
-
-    /* Identity */
-    if (acc->local_uri.slen) {
-	pj_ansi_sprintf(line, "--id %.*s\n", 
-			(int)acc->local_uri.slen, 
-			acc->local_uri.ptr);
-	pj_strcat2(result, line);
-    }
-
-    /* Registrar server */
-    if (acc->reg_uri.slen) {
-	pj_ansi_sprintf(line, "--registrar %.*s\n",
-			      (int)acc->reg_uri.slen,
-			      acc->reg_uri.ptr);
-	pj_strcat2(result, line);
-
-	pj_ansi_sprintf(line, "--reg-timeout %u\n",
-			      acc->reg_timeout);
-	pj_strcat2(result, line);
-    }
-
-
-    /* Proxy */
-    if (acc->proxy.slen) {
-	pj_ansi_sprintf(line, "--proxy %.*s\n",
-			      (int)acc->proxy.slen,
-			      acc->proxy.ptr);
-	pj_strcat2(result, line);
-    }
-}
-
-
-
-/*
- * Dump settings.
- */
-int pjsua_dump_settings(char *buf, pj_size_t max)
-{
-    int acc_index;
-    int i;
-    pj_str_t cfg;
-    char line[128];
-
-    cfg.ptr = buf;
-    cfg.slen = 0;
-
-
-    /* Logging. */
-    pj_strcat2(&cfg, "#\n# Logging options:\n#\n");
-    pj_ansi_sprintf(line, "--log-level %d\n",
-		    pjsua.log_level);
-    pj_strcat2(&cfg, line);
-
-    pj_ansi_sprintf(line, "--app-log-level %d\n",
-		    pjsua.app_log_level);
-    pj_strcat2(&cfg, line);
-
-    if (pjsua.log_filename) {
-	pj_ansi_sprintf(line, "--log-file %s\n",
-			pjsua.log_filename);
-	pj_strcat2(&cfg, line);
-    }
-
-
-    /* Save account settings. */
-    for (acc_index=0; acc_index < pjsua.acc_cnt; ++acc_index) {
-	
-	save_account_settings(acc_index, &cfg);
-
-	if (acc_index < pjsua.acc_cnt-1)
-	    pj_strcat2(&cfg, "--next-account\n");
-    }
-
-    /* Credentials. */
-    for (i=0; i<pjsua.cred_count; ++i) {
-
-	pj_ansi_sprintf(line, "#\n# Credential %d:\n#\n", i);
-	pj_strcat2(&cfg, line);
-
-	if (pjsua.cred_info[i].realm.slen) {
-	    pj_ansi_sprintf(line, "--realm %.*s\n",
-				  (int)pjsua.cred_info[i].realm.slen,
-				  pjsua.cred_info[i].realm.ptr);
-	    pj_strcat2(&cfg, line);
-	}
-
-	pj_ansi_sprintf(line, "--username %.*s\n",
-			      (int)pjsua.cred_info[i].username.slen,
-			      pjsua.cred_info[i].username.ptr);
-	pj_strcat2(&cfg, line);
-
-	pj_ansi_sprintf(line, "--password %.*s\n",
-			      (int)pjsua.cred_info[i].data.slen,
-			      pjsua.cred_info[i].data.ptr);
-	pj_strcat2(&cfg, line);
-
-	if (i < pjsua.cred_count-1)
-	    pj_strcat2(&cfg, "--next-cred\n");
-    }
-
-
-    pj_strcat2(&cfg, "#\n# Network settings:\n#\n");
-
-    /* Outbound proxy */
-    if (pjsua.outbound_proxy.slen) {
-	pj_ansi_sprintf(line, "--outbound %.*s\n",
-			      (int)pjsua.outbound_proxy.slen,
-			      pjsua.outbound_proxy.ptr);
-	pj_strcat2(&cfg, line);
-    }
-
-
-    /* Transport. */
-    pj_ansi_sprintf(line, "--local-port %d\n", pjsua.sip_port);
-    pj_strcat2(&cfg, line);
-
-
-    /* STUN */
-    if (pjsua.stun_port1) {
-	pj_ansi_sprintf(line, "--use-stun1 %.*s:%d\n",
-			(int)pjsua.stun_srv1.slen, 
-			pjsua.stun_srv1.ptr, 
-			pjsua.stun_port1);
-	pj_strcat2(&cfg, line);
-    }
-
-    if (pjsua.stun_port2) {
-	pj_ansi_sprintf(line, "--use-stun2 %.*s:%d\n",
-			(int)pjsua.stun_srv2.slen, 
-			pjsua.stun_srv2.ptr, 
-			pjsua.stun_port2);
-	pj_strcat2(&cfg, line);
-    }
-
-
-    pj_strcat2(&cfg, "#\n# Media settings:\n#\n");
-
-
-    /* Media */
-    if (pjsua.null_audio)
-	pj_strcat2(&cfg, "--null-audio\n");
-    if (pjsua.auto_play)
-	pj_strcat2(&cfg, "--auto-play\n");
-    if (pjsua.auto_loop)
-	pj_strcat2(&cfg, "--auto-loop\n");
-    if (pjsua.auto_conf)
-	pj_strcat2(&cfg, "--auto-conf\n");
-    if (pjsua.wav_file) {
-	pj_ansi_sprintf(line, "--play-file %s\n",
-			pjsua.wav_file);
-	pj_strcat2(&cfg, line);
-    }
-
-
-    pj_strcat2(&cfg, "#\n# User agent:\n#\n");
-
-    /* Auto-answer. */
-    if (pjsua.auto_answer != 0) {
-	pj_ansi_sprintf(line, "--auto-answer %d\n",
-			pjsua.auto_answer);
-	pj_strcat2(&cfg, line);
-    }
-
-    /* Max calls. */
-    pj_ansi_sprintf(line, "--max-calls %d\n",
-		    pjsua.max_calls);
-    pj_strcat2(&cfg, line);
-
-
-    pj_strcat2(&cfg, "#\n# Buddies:\n#\n");
-
-    /* Add buddies. */
-    for (i=0; i<pjsua.buddy_cnt; ++i) {
-	pj_ansi_sprintf(line, "--add-buddy %.*s\n",
-			      (int)pjsua.buddies[i].uri.slen,
-			      pjsua.buddies[i].uri.ptr);
-	pj_strcat2(&cfg, line);
-    }
-
-
-    *(cfg.ptr + cfg.slen) = '\0';
-    return cfg.slen;
-}
-
-/*
- * Save settings.
- */
-pj_status_t pjsua_save_settings(const char *filename)
-{
-    pj_str_t cfg;
-    pj_pool_t *pool;
-    FILE *fhnd;
-
-    /* Create pool for temporary buffer. */
-    pool = pj_pool_create(&pjsua.cp.factory, "settings", 4000, 0, NULL);
-    if (!pool)
-	return PJ_ENOMEM;
-
-
-    cfg.ptr = pj_pool_alloc(pool, 3800);
-    if (!cfg.ptr) {
-	pj_pool_release(pool);
-	return PJ_EBUG;
-    }
-
-
-    cfg.slen = pjsua_dump_settings(cfg.ptr, 3800);
-    if (cfg.slen < 1) {
-	pj_pool_release(pool);
-	return PJ_ENOMEM;
-    }
-
-
-    /* Write to file. */
-    fhnd = fopen(filename, "wt");
-    if (!fhnd) {
-	pj_pool_release(pool);
-	return pj_get_os_error();
-    }
-
-    fwrite(cfg.ptr, cfg.slen, 1, fhnd);
-    fclose(fhnd);
-
-    pj_pool_release(pool);
-    return PJ_SUCCESS;
-}
Index: /pjproject/trunk/pjsip/src/pjsua-lib/pjsua_reg.c
===================================================================
--- /pjproject/trunk/pjsip/src/pjsua-lib/pjsua_reg.c	(revision 267)
+++ /pjproject/trunk/pjsip/src/pjsua-lib/pjsua_reg.c	(revision 268)
@@ -78,5 +78,5 @@
     acc->reg_last_code = param->code;
 
-    pjsua_ui_regc_on_state_changed(acc->index);
+    pjsua_ui_on_reg_state(acc->index);
 }
 
Index: /pjproject/trunk/pjsip/src/pjsua-lib/pjsua_settings.c
===================================================================
--- /pjproject/trunk/pjsip/src/pjsua-lib/pjsua_settings.c	(revision 268)
+++ /pjproject/trunk/pjsip/src/pjsua-lib/pjsua_settings.c	(revision 268)
@@ -0,0 +1,884 @@
+/* $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-lib/pjsua.h>
+#include <pjsua-lib/getopt.h>
+
+/*
+ * pjsua_settings.c
+ *
+ * Anything to do with configuration and state dump.
+ */
+
+#define THIS_FILE   "pjsua_opt.c"
+
+
+const char *pjsua_inv_state_names[] =
+{
+    "NULL      ",
+    "CALLING   ",
+    "INCOMING  ",
+    "EARLY     ",
+    "CONNECTING",
+    "CONFIRMED ",
+    "DISCONNCTD",
+    "TERMINATED",
+};
+
+
+
+/* Show usage */
+static void usage(void)
+{
+    puts("Usage:");
+    puts("  pjsua [options]");
+    puts("");
+    puts("General options:");
+    puts("  --help              Display this help screen");
+    puts("  --version           Display version info");
+    puts("");
+    puts("Logging options:");
+    puts("  --config-file=file  Read the config/arguments from file.");
+    puts("  --log-file=fname    Log to filename (default stderr)");
+    puts("  --log-level=N       Set log max level to N (0(none) to 6(trace))");
+    puts("  --app-log-level=N   Set log max level for stdout display to N");
+    puts("");
+    puts("SIP Account options:");
+    puts("  --id=url            Set the URL of local ID (used in From header)");
+    puts("  --contact=url       Override the Contact information");
+    puts("  --proxy=url         Set the URL of proxy server");
+    puts("");
+    puts("SIP Account Registration Options:");
+    puts("  --registrar=url     Set the URL of registrar server");
+    puts("  --reg-timeout=secs  Set registration interval to secs (default 3600)");
+    puts("");
+    puts("SIP Account Control:");
+    puts("  --next-account      Add more account");
+    puts("");
+    puts("Authentication options:");
+    puts("  --realm=string      Set realm");
+    puts("  --username=string   Set authentication username");
+    puts("  --password=string   Set authentication password");
+    puts("  --next-cred         Add more credential");
+    puts("");
+    puts("Transport Options:");
+    puts("  --local-port=port        Set TCP/UDP port");
+    puts("  --outbound=url           Set the URL of outbound proxy server");
+    puts("  --use-stun1=host[:port]");
+    puts("  --use-stun2=host[:port]  Resolve local IP with the specified STUN servers");
+    puts("");
+    puts("Media Options:");
+    puts("  --null-audio        Use NULL audio device");
+    puts("  --play-file=file    Play WAV file in conference bridge");
+    puts("  --auto-play         Automatically play the file (to incoming calls only)");
+    puts("  --auto-loop         Automatically loop incoming RTP to outgoing RTP");
+    puts("  --auto-conf         Automatically put incoming calls to conference");
+    puts("  --rtp-port=N	Base port to try for RTP");
+    puts("");
+    puts("Buddy List (can be more than one):");
+    puts("  --add-buddy url     Add the specified URL to the buddy list.");
+    puts("");
+    puts("User Agent options:");
+    puts("  --auto-answer=code  Automatically answer incoming calls with code (e.g. 200)");
+    puts("  --max-calls=N       Maximum number of concurrent calls (default:4, max:255)");
+    puts("");
+    fflush(stdout);
+}
+
+
+
+/*
+ * Verify that valid SIP url is given.
+ */
+pj_status_t pjsua_verify_sip_url(const char *c_url)
+{
+    pjsip_uri *p;
+    pj_pool_t *pool;
+    char *url;
+    int len = (c_url ? pj_ansi_strlen(c_url) : 0);
+
+    if (!len) return -1;
+
+    pool = pj_pool_create(&pjsua.cp.factory, "check%p", 1024, 0, NULL);
+    if (!pool) return -1;
+
+    url = pj_pool_alloc(pool, len+1);
+    pj_ansi_strcpy(url, c_url);
+
+    p = pjsip_parse_uri(pool, url, len, 0);
+    if (!p || pj_stricmp2(pjsip_uri_get_scheme(p), "sip") != 0)
+	p = NULL;
+
+    pj_pool_release(pool);
+    return p ? 0 : -1;
+}
+
+
+/*
+ * Read command arguments from config file.
+ */
+static int read_config_file(pj_pool_t *pool, const char *filename, 
+			    int *app_argc, char ***app_argv)
+{
+    int i;
+    FILE *fhnd;
+    char line[200];
+    int argc = 0;
+    char **argv;
+    enum { MAX_ARGS = 64 };
+
+    /* Allocate MAX_ARGS+1 (argv needs to be terminated with NULL argument) */
+    argv = pj_pool_calloc(pool, MAX_ARGS+1, sizeof(char*));
+    argv[argc++] = *app_argv[0];
+
+    /* Open config file. */
+    fhnd = fopen(filename, "rt");
+    if (!fhnd) {
+	printf("Unable to open config file %s\n", filename);
+	fflush(stdout);
+	return -1;
+    }
+
+    /* Scan tokens in the file. */
+    while (argc < MAX_ARGS && !feof(fhnd)) {
+	char *token, *p = line;
+
+	if (fgets(line, sizeof(line), fhnd) == NULL) break;
+
+	for (token = strtok(p, " \t\r\n"); argc < MAX_ARGS; 
+	     token = strtok(NULL, " \t\r\n"))
+	{
+	    int token_len;
+	    
+	    if (!token) break;
+	    if (*token == '#') break;
+
+	    token_len = strlen(token);
+	    if (!token_len)
+		continue;
+	    argv[argc] = pj_pool_alloc(pool, token_len+1);
+	    pj_memcpy(argv[argc], token, token_len+1);
+	    ++argc;
+	}
+    }
+
+    /* Copy arguments from command line */
+    for (i=1; i<*app_argc && argc < MAX_ARGS; ++i)
+	argv[argc++] = (*app_argv)[i];
+
+    if (argc == MAX_ARGS && (i!=*app_argc || !feof(fhnd))) {
+	printf("Too many arguments specified in cmd line/config file\n");
+	fflush(stdout);
+	fclose(fhnd);
+	return -1;
+    }
+
+    fclose(fhnd);
+
+    /* Assign the new command line back to the original command line. */
+    *app_argc = argc;
+    *app_argv = argv;
+    return 0;
+
+}
+
+static int my_atoi(const char *cs)
+{
+    pj_str_t s;
+    return pj_strtoul(pj_cstr(&s, cs));
+}
+
+
+/* Parse arguments. */
+pj_status_t pjsua_parse_args(int argc, char *argv[])
+{
+    int c;
+    int option_index;
+    enum { OPT_CONFIG_FILE, OPT_LOG_FILE, OPT_LOG_LEVEL, OPT_APP_LOG_LEVEL, 
+	   OPT_HELP, OPT_VERSION, OPT_NULL_AUDIO,
+	   OPT_LOCAL_PORT, OPT_PROXY, OPT_OUTBOUND_PROXY, OPT_REGISTRAR,
+	   OPT_REG_TIMEOUT, OPT_ID, OPT_CONTACT, 
+	   OPT_REALM, OPT_USERNAME, OPT_PASSWORD,
+	   OPT_USE_STUN1, OPT_USE_STUN2, 
+	   OPT_ADD_BUDDY, OPT_OFFER_X_MS_MSG, OPT_NO_PRESENCE,
+	   OPT_AUTO_ANSWER, OPT_AUTO_HANGUP, OPT_AUTO_PLAY, OPT_AUTO_LOOP,
+	   OPT_AUTO_CONF,
+	   OPT_PLAY_FILE, OPT_RTP_PORT,
+	   OPT_NEXT_ACCOUNT, OPT_NEXT_CRED, OPT_MAX_CALLS,
+    };
+    struct option long_options[] = {
+	{ "config-file",1, 0, OPT_CONFIG_FILE},
+	{ "log-file",	1, 0, OPT_LOG_FILE},
+	{ "log-level",	1, 0, OPT_LOG_LEVEL},
+	{ "app-log-level",1,0,OPT_APP_LOG_LEVEL},
+	{ "help",	0, 0, OPT_HELP},
+	{ "version",	0, 0, OPT_VERSION},
+	{ "null-audio", 0, 0, OPT_NULL_AUDIO},
+	{ "local-port", 1, 0, OPT_LOCAL_PORT},
+	{ "proxy",	1, 0, OPT_PROXY},
+	{ "outbound",	1, 0, OPT_OUTBOUND_PROXY},
+	{ "registrar",	1, 0, OPT_REGISTRAR},
+	{ "reg-timeout",1, 0, OPT_REG_TIMEOUT},
+	{ "id",		1, 0, OPT_ID},
+	{ "contact",	1, 0, OPT_CONTACT},
+	{ "realm",	1, 0, OPT_REALM},
+	{ "username",	1, 0, OPT_USERNAME},
+	{ "password",	1, 0, OPT_PASSWORD},
+	{ "use-stun1",  1, 0, OPT_USE_STUN1},
+	{ "use-stun2",  1, 0, OPT_USE_STUN2},
+	{ "add-buddy",  1, 0, OPT_ADD_BUDDY},
+	{ "offer-x-ms-msg",0,0,OPT_OFFER_X_MS_MSG},
+	{ "no-presence", 0, 0, OPT_NO_PRESENCE},
+	{ "auto-answer",1, 0, OPT_AUTO_ANSWER},
+	{ "auto-hangup",1, 0, OPT_AUTO_HANGUP},
+	{ "auto-play",  0, 0, OPT_AUTO_PLAY},
+	{ "auto-loop",  0, 0, OPT_AUTO_LOOP},
+	{ "auto-conf",  0, 0, OPT_AUTO_CONF},
+	{ "play-file",  1, 0, OPT_PLAY_FILE},
+	{ "rtp-port",	1, 0, OPT_RTP_PORT},
+	{ "next-account",0,0, OPT_NEXT_ACCOUNT},
+	{ "next-cred",	0, 0, OPT_NEXT_CRED},
+	{ "max-calls",	1, 0, OPT_MAX_CALLS},
+	{ NULL, 0, 0, 0}
+    };
+    pj_status_t status;
+    pjsua_acc *cur_acc;
+    pjsip_cred_info *cur_cred;
+    char *config_file = NULL;
+
+    /* Run getopt once to see if user specifies config file to read. */
+    while ((c=getopt_long(argc, argv, "", long_options, &option_index)) != -1) {
+	switch (c) {
+	case OPT_CONFIG_FILE:
+	    config_file = optarg;
+	    break;
+	}
+	if (config_file)
+	    break;
+    }
+
+    if (config_file) {
+	status = read_config_file(pjsua.pool, config_file, &argc, &argv);
+	if (status != 0)
+	    return status;
+    }
+
+
+    cur_acc = &pjsua.acc[0];
+    cur_cred = &pjsua.cred_info[0];
+
+
+    /* Reinitialize and re-run getopt again, possibly with new arguments
+     * read from config file.
+     */
+    optind = 0;
+    while ((c=getopt_long(argc, argv, "", long_options, &option_index)) != -1) {
+	char *p;
+	pj_str_t tmp;
+	long lval;
+
+	switch (c) {
+
+	case OPT_LOG_FILE:
+	    pjsua.log_filename = optarg;
+	    break;
+
+	case OPT_LOG_LEVEL:
+	    c = pj_strtoul(pj_cstr(&tmp, optarg));
+	    if (c < 0 || c > 6) {
+		printf("Error: expecting integer value 0-6 for --log-level\n");
+		return PJ_EINVAL;
+	    }
+	    pj_log_set_level( c );
+	    break;
+
+	case OPT_APP_LOG_LEVEL:
+	    pjsua.app_log_level = pj_strtoul(pj_cstr(&tmp, optarg));
+	    if (pjsua.app_log_level < 0 || pjsua.app_log_level > 6) {
+		printf("Error: expecting integer value 0-6 for --app-log-level\n");
+		return PJ_EINVAL;
+	    }
+	    break;
+
+	case OPT_HELP:
+	    usage();
+	    return PJ_EINVAL;
+
+	case OPT_VERSION:   /* version */
+	    pj_dump_config();
+	    return PJ_EINVAL;
+
+	case OPT_NULL_AUDIO:
+	    pjsua.null_audio = 1;
+	    break;
+
+	case OPT_LOCAL_PORT:   /* local-port */
+	    lval = pj_strtoul(pj_cstr(&tmp, optarg));
+	    if (lval < 1 || lval > 65535) {
+		printf("Error: expecting integer value for --local-port\n");
+		return PJ_EINVAL;
+	    }
+	    pjsua.sip_port = (pj_uint16_t)lval;
+	    break;
+
+	case OPT_PROXY:   /* proxy */
+	    if (pjsua_verify_sip_url(optarg) != 0) {
+		printf("Error: invalid SIP URL '%s' in proxy argument\n", optarg);
+		return PJ_EINVAL;
+	    }
+	    cur_acc->proxy = pj_str(optarg);
+	    break;
+
+	case OPT_OUTBOUND_PROXY:   /* outbound proxy */
+	    if (pjsua_verify_sip_url(optarg) != 0) {
+		printf("Error: invalid SIP URL '%s' in outbound proxy argument\n", optarg);
+		return PJ_EINVAL;
+	    }
+	    pjsua.outbound_proxy = pj_str(optarg);
+	    break;
+
+	case OPT_REGISTRAR:   /* registrar */
+	    if (pjsua_verify_sip_url(optarg) != 0) {
+		printf("Error: invalid SIP URL '%s' in registrar argument\n", optarg);
+		return PJ_EINVAL;
+	    }
+	    cur_acc->reg_uri = pj_str(optarg);
+	    break;
+
+	case OPT_REG_TIMEOUT:   /* reg-timeout */
+	    cur_acc->reg_timeout = pj_strtoul(pj_cstr(&tmp,optarg));
+	    if (cur_acc->reg_timeout < 1 || cur_acc->reg_timeout > 3600) {
+		printf("Error: invalid value for --reg-timeout (expecting 1-3600)\n");
+		return PJ_EINVAL;
+	    }
+	    break;
+
+	case OPT_ID:   /* id */
+	    if (pjsua_verify_sip_url(optarg) != 0) {
+		printf("Error: invalid SIP URL '%s' in local id argument\n", optarg);
+		return PJ_EINVAL;
+	    }
+	    cur_acc->local_uri = pj_str(optarg);
+	    break;
+
+	case OPT_CONTACT:   /* contact */
+	    if (pjsua_verify_sip_url(optarg) != 0) {
+		printf("Error: invalid SIP URL '%s' in contact argument\n", optarg);
+		return PJ_EINVAL;
+	    }
+	    cur_acc->contact_uri = pj_str(optarg);
+	    break;
+
+	case OPT_NEXT_ACCOUNT: /* Add more account. */
+	    pjsua.acc_cnt++;
+	    cur_acc = &pjsua.acc[pjsua.acc_cnt - 1];
+	    break;
+
+	case OPT_USERNAME:   /* Default authentication user */
+	    if (pjsua.cred_count==0) pjsua.cred_count=1;
+	    cur_cred->username = pj_str(optarg);
+	    break;
+
+	case OPT_REALM:	    /* Default authentication realm. */
+	    if (pjsua.cred_count==0) pjsua.cred_count=1;
+	    cur_cred->realm = pj_str(optarg);
+	    break;
+
+	case OPT_PASSWORD:   /* authentication password */
+	    if (pjsua.cred_count==0) pjsua.cred_count=1;
+	    cur_cred->data_type = 0;
+	    cur_cred->data = pj_str(optarg);
+	    break;
+
+	case OPT_NEXT_CRED: /* Next credential */
+	    pjsua.cred_count++;
+	    cur_cred = &pjsua.cred_info[pjsua.cred_count - 1];
+	    break;
+
+	case OPT_USE_STUN1:   /* STUN server 1 */
+	    p = pj_ansi_strchr(optarg, ':');
+	    if (p) {
+		*p = '\0';
+		pjsua.stun_srv1 = pj_str(optarg);
+		pjsua.stun_port1 = pj_strtoul(pj_cstr(&tmp, p+1));
+		if (pjsua.stun_port1 < 1 || pjsua.stun_port1 > 65535) {
+		    printf("Error: expecting port number with option --use-stun1\n");
+		    return PJ_EINVAL;
+		}
+	    } else {
+		pjsua.stun_port1 = 3478;
+		pjsua.stun_srv1 = pj_str(optarg);
+	    }
+	    break;
+
+	case OPT_USE_STUN2:   /* STUN server 2 */
+	    p = pj_ansi_strchr(optarg, ':');
+	    if (p) {
+		*p = '\0';
+		pjsua.stun_srv2 = pj_str(optarg);
+		pjsua.stun_port2 = pj_strtoul(pj_cstr(&tmp,p+1));
+		if (pjsua.stun_port2 < 1 || pjsua.stun_port2 > 65535) {
+		    printf("Error: expecting port number with option --use-stun2\n");
+		    return PJ_EINVAL;
+		}
+	    } else {
+		pjsua.stun_port2 = 3478;
+		pjsua.stun_srv2 = pj_str(optarg);
+	    }
+	    break;
+
+	case OPT_ADD_BUDDY: /* Add to buddy list. */
+	    if (pjsua_verify_sip_url(optarg) != 0) {
+		printf("Error: invalid URL '%s' in --add-buddy option\n", optarg);
+		return -1;
+	    }
+	    if (pjsua.buddy_cnt == PJSUA_MAX_BUDDIES) {
+		printf("Error: too many buddies in buddy list.\n");
+		return -1;
+	    }
+	    pjsua.buddies[pjsua.buddy_cnt++].uri = pj_str(optarg);
+	    break;
+
+	case OPT_AUTO_PLAY:
+	    pjsua.auto_play = 1;
+	    break;
+
+	case OPT_AUTO_LOOP:
+	    pjsua.auto_loop = 1;
+	    break;
+
+	case OPT_AUTO_CONF:
+	    pjsua.auto_conf = 1;
+	    break;
+
+	case OPT_PLAY_FILE:
+	    pjsua.wav_file = optarg;
+	    break;
+
+	case OPT_RTP_PORT:
+	    pjsua.start_rtp_port = my_atoi(optarg);
+	    if (pjsua.start_rtp_port < 1 || pjsua.start_rtp_port > 65535) {
+		PJ_LOG(1,(THIS_FILE,
+			  "Error: rtp-port argument value (expecting 1-65535"));
+		return -1;
+	    }
+
+	case OPT_AUTO_ANSWER:
+	    pjsua.auto_answer = my_atoi(optarg);
+	    if (pjsua.auto_answer < 100 || pjsua.auto_answer > 699) {
+		PJ_LOG(1,(THIS_FILE,
+			  "Error: invalid code in --auto-answer (expecting 100-699"));
+		return -1;
+	    }
+	    break;
+
+	case OPT_MAX_CALLS:
+	    pjsua.max_calls = my_atoi(optarg);
+	    if (pjsua.max_calls < 1 || pjsua.max_calls > 255) {
+		PJ_LOG(1,(THIS_FILE,"Too many calls for max-calls (1-255)"));
+		return -1;
+	    }
+	    break;
+	}
+    }
+
+    if (optind != argc) {
+	printf("Error: unknown options %s\n", argv[optind]);
+	return PJ_EINVAL;
+    }
+
+    return PJ_SUCCESS;
+}
+
+
+
+static void print_call(const char *title,
+		       int call_index, 
+		       char *buf, pj_size_t size)
+{
+    int len;
+    pjsip_inv_session *inv = pjsua.calls[call_index].inv;
+    pjsip_dialog *dlg = inv->dlg;
+    char userinfo[128];
+
+    /* Dump invite sesion info. */
+
+    len = pjsip_hdr_print_on(dlg->remote.info, userinfo, sizeof(userinfo));
+    if (len < 1)
+	pj_ansi_strcpy(userinfo, "<--uri too long-->");
+    else
+	userinfo[len] = '\0';
+    
+    len = pj_snprintf(buf, size, "%s[%s] %s",
+		      title,
+		      pjsua_inv_state_names[inv->state],
+		      userinfo);
+    if (len < 1 || len >= (int)size) {
+	pj_ansi_strcpy(buf, "<--uri too long-->");
+	len = 18;
+    } else
+	buf[len] = '\0';
+}
+
+static void dump_media_session(pjmedia_session *session)
+{
+    unsigned i;
+    pjmedia_session_info info;
+
+    pjmedia_session_get_info(session, &info);
+
+    for (i=0; i<info.stream_cnt; ++i) {
+	pjmedia_stream_stat strm_stat;
+	const char *rem_addr;
+	int rem_port;
+	const char *dir;
+
+	pjmedia_session_get_stream_stat(session, i, &strm_stat);
+	rem_addr = pj_inet_ntoa(info.stream_info[i].rem_addr.sin_addr);
+	rem_port = pj_ntohs(info.stream_info[i].rem_addr.sin_port);
+
+	if (info.stream_info[i].dir == PJMEDIA_DIR_ENCODING)
+	    dir = "sendonly";
+	else if (info.stream_info[i].dir == PJMEDIA_DIR_DECODING)
+	    dir = "recvonly";
+	else if (info.stream_info[i].dir == PJMEDIA_DIR_ENCODING_DECODING)
+	    dir = "sendrecv";
+	else
+	    dir = "inactive";
+
+	
+	PJ_LOG(3,(THIS_FILE, 
+		  "%s[Media strm#%d] %.*s, %s, peer=%s:%d",
+		  "               ",
+		  i,
+		  info.stream_info[i].fmt.encoding_name.slen,
+		  info.stream_info[i].fmt.encoding_name.ptr,
+		  dir,
+		  rem_addr, rem_port));
+	PJ_LOG(3,(THIS_FILE, 
+		  "%s tx {pkt=%u, bytes=%u} rx {pkt=%u, bytes=%u}",
+		  "                             ",
+		  strm_stat.enc.pkt, strm_stat.enc.bytes,
+		  strm_stat.dec.pkt, strm_stat.dec.bytes));
+
+    }
+}
+
+/*
+ * Dump application states.
+ */
+void pjsua_dump(pj_bool_t detail)
+{
+    char buf[128];
+    unsigned old_decor;
+
+    PJ_LOG(3,(THIS_FILE, "Start dumping application states:"));
+
+    old_decor = pj_log_get_decor();
+    pj_log_set_decor(old_decor & (PJ_LOG_HAS_NEWLINE | PJ_LOG_HAS_CR));
+
+    pjsip_endpt_dump(pjsua.endpt, detail);
+    pjmedia_endpt_dump(pjsua.med_endpt);
+    pjsip_tsx_layer_dump(detail);
+    pjsip_ua_dump(detail);
+
+
+    /* Dump all invite sessions: */
+    if (detail) {
+	PJ_LOG(3,(THIS_FILE, "Dumping invite sessions:"));
+
+	if (pjsua.call_cnt == 0) {
+
+	    PJ_LOG(3,(THIS_FILE, "  - no sessions -"));
+
+	} else {
+	    int i;
+
+	    for (i=0; i<pjsua.max_calls; ++i) {
+
+		if (pjsua.calls[i].inv == NULL)
+		    continue;
+
+		print_call("  ", i, buf, sizeof(buf));
+		PJ_LOG(3,(THIS_FILE, "%s", buf));
+
+		if (pjsua.calls[i].session)
+		    dump_media_session(pjsua.calls[i].session);
+	    }
+	}
+    }
+
+    /* Dump presence status */
+    pjsua_pres_dump(detail);
+
+    pj_log_set_decor(old_decor);
+    PJ_LOG(3,(THIS_FILE, "Dump complete"));
+}
+
+
+/*
+ * Load settings.
+ */
+pj_status_t pjsua_load_settings(const char *filename)
+{
+    int argc = 3;
+    char *argv[4] = { "pjsua", "--config-file", NULL, NULL};
+
+    argv[3] = (char*)filename;
+    return pjsua_parse_args(argc, argv);
+}
+
+
+/*
+ * Save account settings
+ */
+static void save_account_settings(int acc_index, pj_str_t *result)
+{
+    char line[128];
+    pjsua_acc *acc = &pjsua.acc[acc_index];
+
+    
+    pj_ansi_sprintf(line, "#\n# Account %d:\n#\n", acc_index);
+    pj_strcat2(result, line);
+
+
+    /* Identity */
+    if (acc->local_uri.slen) {
+	pj_ansi_sprintf(line, "--id %.*s\n", 
+			(int)acc->local_uri.slen, 
+			acc->local_uri.ptr);
+	pj_strcat2(result, line);
+    }
+
+    /* Registrar server */
+    if (acc->reg_uri.slen) {
+	pj_ansi_sprintf(line, "--registrar %.*s\n",
+			      (int)acc->reg_uri.slen,
+			      acc->reg_uri.ptr);
+	pj_strcat2(result, line);
+
+	pj_ansi_sprintf(line, "--reg-timeout %u\n",
+			      acc->reg_timeout);
+	pj_strcat2(result, line);
+    }
+
+
+    /* Proxy */
+    if (acc->proxy.slen) {
+	pj_ansi_sprintf(line, "--proxy %.*s\n",
+			      (int)acc->proxy.slen,
+			      acc->proxy.ptr);
+	pj_strcat2(result, line);
+    }
+}
+
+
+
+/*
+ * Dump settings.
+ */
+int pjsua_dump_settings(char *buf, pj_size_t max)
+{
+    int acc_index;
+    int i;
+    pj_str_t cfg;
+    char line[128];
+
+    cfg.ptr = buf;
+    cfg.slen = 0;
+
+
+    /* Logging. */
+    pj_strcat2(&cfg, "#\n# Logging options:\n#\n");
+    pj_ansi_sprintf(line, "--log-level %d\n",
+		    pjsua.log_level);
+    pj_strcat2(&cfg, line);
+
+    pj_ansi_sprintf(line, "--app-log-level %d\n",
+		    pjsua.app_log_level);
+    pj_strcat2(&cfg, line);
+
+    if (pjsua.log_filename) {
+	pj_ansi_sprintf(line, "--log-file %s\n",
+			pjsua.log_filename);
+	pj_strcat2(&cfg, line);
+    }
+
+
+    /* Save account settings. */
+    for (acc_index=0; acc_index < pjsua.acc_cnt; ++acc_index) {
+	
+	save_account_settings(acc_index, &cfg);
+
+	if (acc_index < pjsua.acc_cnt-1)
+	    pj_strcat2(&cfg, "--next-account\n");
+    }
+
+    /* Credentials. */
+    for (i=0; i<pjsua.cred_count; ++i) {
+
+	pj_ansi_sprintf(line, "#\n# Credential %d:\n#\n", i);
+	pj_strcat2(&cfg, line);
+
+	if (pjsua.cred_info[i].realm.slen) {
+	    pj_ansi_sprintf(line, "--realm %.*s\n",
+				  (int)pjsua.cred_info[i].realm.slen,
+				  pjsua.cred_info[i].realm.ptr);
+	    pj_strcat2(&cfg, line);
+	}
+
+	pj_ansi_sprintf(line, "--username %.*s\n",
+			      (int)pjsua.cred_info[i].username.slen,
+			      pjsua.cred_info[i].username.ptr);
+	pj_strcat2(&cfg, line);
+
+	pj_ansi_sprintf(line, "--password %.*s\n",
+			      (int)pjsua.cred_info[i].data.slen,
+			      pjsua.cred_info[i].data.ptr);
+	pj_strcat2(&cfg, line);
+
+	if (i < pjsua.cred_count-1)
+	    pj_strcat2(&cfg, "--next-cred\n");
+    }
+
+
+    pj_strcat2(&cfg, "#\n# Network settings:\n#\n");
+
+    /* Outbound proxy */
+    if (pjsua.outbound_proxy.slen) {
+	pj_ansi_sprintf(line, "--outbound %.*s\n",
+			      (int)pjsua.outbound_proxy.slen,
+			      pjsua.outbound_proxy.ptr);
+	pj_strcat2(&cfg, line);
+    }
+
+
+    /* Transport. */
+    pj_ansi_sprintf(line, "--local-port %d\n", pjsua.sip_port);
+    pj_strcat2(&cfg, line);
+
+
+    /* STUN */
+    if (pjsua.stun_port1) {
+	pj_ansi_sprintf(line, "--use-stun1 %.*s:%d\n",
+			(int)pjsua.stun_srv1.slen, 
+			pjsua.stun_srv1.ptr, 
+			pjsua.stun_port1);
+	pj_strcat2(&cfg, line);
+    }
+
+    if (pjsua.stun_port2) {
+	pj_ansi_sprintf(line, "--use-stun2 %.*s:%d\n",
+			(int)pjsua.stun_srv2.slen, 
+			pjsua.stun_srv2.ptr, 
+			pjsua.stun_port2);
+	pj_strcat2(&cfg, line);
+    }
+
+
+    pj_strcat2(&cfg, "#\n# Media settings:\n#\n");
+
+
+    /* Media */
+    if (pjsua.null_audio)
+	pj_strcat2(&cfg, "--null-audio\n");
+    if (pjsua.auto_play)
+	pj_strcat2(&cfg, "--auto-play\n");
+    if (pjsua.auto_loop)
+	pj_strcat2(&cfg, "--auto-loop\n");
+    if (pjsua.auto_conf)
+	pj_strcat2(&cfg, "--auto-conf\n");
+    if (pjsua.wav_file) {
+	pj_ansi_sprintf(line, "--play-file %s\n",
+			pjsua.wav_file);
+	pj_strcat2(&cfg, line);
+    }
+
+
+    pj_strcat2(&cfg, "#\n# User agent:\n#\n");
+
+    /* Auto-answer. */
+    if (pjsua.auto_answer != 0) {
+	pj_ansi_sprintf(line, "--auto-answer %d\n",
+			pjsua.auto_answer);
+	pj_strcat2(&cfg, line);
+    }
+
+    /* Max calls. */
+    pj_ansi_sprintf(line, "--max-calls %d\n",
+		    pjsua.max_calls);
+    pj_strcat2(&cfg, line);
+
+
+    pj_strcat2(&cfg, "#\n# Buddies:\n#\n");
+
+    /* Add buddies. */
+    for (i=0; i<pjsua.buddy_cnt; ++i) {
+	pj_ansi_sprintf(line, "--add-buddy %.*s\n",
+			      (int)pjsua.buddies[i].uri.slen,
+			      pjsua.buddies[i].uri.ptr);
+	pj_strcat2(&cfg, line);
+    }
+
+
+    *(cfg.ptr + cfg.slen) = '\0';
+    return cfg.slen;
+}
+
+/*
+ * Save settings.
+ */
+pj_status_t pjsua_save_settings(const char *filename)
+{
+    pj_str_t cfg;
+    pj_pool_t *pool;
+    FILE *fhnd;
+
+    /* Create pool for temporary buffer. */
+    pool = pj_pool_create(&pjsua.cp.factory, "settings", 4000, 0, NULL);
+    if (!pool)
+	return PJ_ENOMEM;
+
+
+    cfg.ptr = pj_pool_alloc(pool, 3800);
+    if (!cfg.ptr) {
+	pj_pool_release(pool);
+	return PJ_EBUG;
+    }
+
+
+    cfg.slen = pjsua_dump_settings(cfg.ptr, 3800);
+    if (cfg.slen < 1) {
+	pj_pool_release(pool);
+	return PJ_ENOMEM;
+    }
+
+
+    /* Write to file. */
+    fhnd = fopen(filename, "wt");
+    if (!fhnd) {
+	pj_pool_release(pool);
+	return pj_get_os_error();
+    }
+
+    fwrite(cfg.ptr, cfg.slen, 1, fhnd);
+    fclose(fhnd);
+
+    pj_pool_release(pool);
+    return PJ_SUCCESS;
+}
