summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBenny Prijono <bennylp@teluu.com>2006-03-02 21:18:58 +0000
committerBenny Prijono <bennylp@teluu.com>2006-03-02 21:18:58 +0000
commitaad76a0bb1ea62caaf67e6fecaea5786b5f2811a (patch)
tree8183f5a20882579ad310e060115a832a2e6eb444
parentcf626e119a5f0b8b424c7cf473f514f1710ce209 (diff)
Added IM and composition indication, and tested
git-svn-id: http://svn.pjsip.org/repos/pjproject/trunk@268 74dad513-b988-da41-8d7b-12977e46ad98
-rw-r--r--pjsip/build/pjsip_simple.dsp8
-rw-r--r--pjsip/build/pjsua_lib.dsp8
-rw-r--r--pjsip/include/pjsip-simple/errno.h15
-rw-r--r--pjsip/include/pjsip-simple/iscomposing.h121
-rw-r--r--pjsip/include/pjsip_simple.h1
-rw-r--r--pjsip/include/pjsua-lib/pjsua.h81
-rw-r--r--pjsip/src/pjsip-simple/errno.c5
-rw-r--r--pjsip/src/pjsip-simple/evsub.c21
-rw-r--r--pjsip/src/pjsip-simple/iscomposing.c215
-rw-r--r--pjsip/src/pjsip-simple/presence.c63
-rw-r--r--pjsip/src/pjsua-lib/pjsua_call.c157
-rw-r--r--pjsip/src/pjsua-lib/pjsua_core.c4
-rw-r--r--pjsip/src/pjsua-lib/pjsua_im.c385
-rw-r--r--pjsip/src/pjsua-lib/pjsua_reg.c2
-rw-r--r--pjsip/src/pjsua-lib/pjsua_settings.c (renamed from pjsip/src/pjsua-lib/pjsua_opt.c)6
15 files changed, 1060 insertions, 32 deletions
diff --git a/pjsip/build/pjsip_simple.dsp b/pjsip/build/pjsip_simple.dsp
index 6e6643f6..efa4847f 100644
--- a/pjsip/build/pjsip_simple.dsp
+++ b/pjsip/build/pjsip_simple.dsp
@@ -97,6 +97,10 @@ SOURCE="..\src\pjsip-simple\evsub_msg.c"
# End Source File
# Begin Source File
+SOURCE="..\src\pjsip-simple\iscomposing.c"
+# End Source File
+# Begin Source File
+
SOURCE="..\src\pjsip-simple\pidf.c"
# End Source File
# Begin Source File
@@ -125,6 +129,10 @@ SOURCE="..\include\pjsip-simple\evsub_msg.h"
# End Source File
# Begin Source File
+SOURCE="..\include\pjsip-simple\iscomposing.h"
+# End Source File
+# Begin Source File
+
SOURCE="..\include\pjsip-simple\pidf.h"
# End Source File
# Begin Source File
diff --git a/pjsip/build/pjsua_lib.dsp b/pjsip/build/pjsua_lib.dsp
index 806b6316..42482e9e 100644
--- a/pjsip/build/pjsua_lib.dsp
+++ b/pjsip/build/pjsua_lib.dsp
@@ -65,7 +65,7 @@ LIB32=link.exe -lib
# PROP Intermediate_Dir ".\output\pjsua-lib-i386-win32-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 "../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"
# ADD RSC /l 0x409 /d "_DEBUG"
@@ -99,7 +99,7 @@ SOURCE="..\src\pjsua-lib\pjsua_core.c"
# End Source File
# Begin Source File
-SOURCE="..\src\pjsua-lib\pjsua_opt.c"
+SOURCE="..\src\pjsua-lib\pjsua_im.c"
# End Source File
# Begin Source File
@@ -109,6 +109,10 @@ SOURCE="..\src\pjsua-lib\pjsua_pres.c"
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
# Begin Group "Header Files"
diff --git a/pjsip/include/pjsip-simple/errno.h b/pjsip/include/pjsip-simple/errno.h
index 966aed93..c3d4fe83 100644
--- a/pjsip/include/pjsip-simple/errno.h
+++ b/pjsip/include/pjsip-simple/errno.h
@@ -28,6 +28,9 @@
#define PJSIP_SIMPLE_ERRNO_START (PJ_ERRNO_START_USER + PJ_ERRNO_SPACE_SIZE*2)
+/************************************************************
+ * EVENT PACKAGE ERRORS
+ ***********************************************************/
/**
* @hideinitializer
* No event package with the specified name.
@@ -40,6 +43,9 @@
#define PJSIP_SIMPLE_EPKGEXISTS (PJSIP_SIMPLE_ERRNO_START+2) /*270002*/
+/************************************************************
+ * PRESENCE ERROR
+ ***********************************************************/
/**
* @hideinitializer
* Expecting SUBSCRIBE request
@@ -72,6 +78,15 @@
#define PJSIP_SIMPLE_EBADXPIDF (PJSIP_SIMPLE_ERRNO_START+25) /*270025*/
+/************************************************************
+ * ISCOMPOSING ERRORS
+ ***********************************************************/
+/**
+ * @hideinitializer
+ * Bad isComposing XML message.
+ */
+#define PJSIP_SIMPLE_EBADISCOMPOSE (PJSIP_SIMPLE_ERRNO_START+40) /*270040*/
+
#endif /* __PJSIP_SIMPLE_ERRNO_H__ */
diff --git a/pjsip/include/pjsip-simple/iscomposing.h b/pjsip/include/pjsip-simple/iscomposing.h
new file mode 100644
index 00000000..b5544ea4
--- /dev/null
+++ b/pjsip/include/pjsip-simple/iscomposing.h
@@ -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__ */
+
diff --git a/pjsip/include/pjsip_simple.h b/pjsip/include/pjsip_simple.h
index a8c955ca..fd7c41ae 100644
--- a/pjsip/include/pjsip_simple.h
+++ b/pjsip/include/pjsip_simple.h
@@ -34,6 +34,7 @@
#define __PJSIP_SIMPLE_H__
#include <pjsip-simple/evsub.h>
+#include <pjsip-simple/iscomposing.h>
#include <pjsip-simple/presence.h>
#include <pjsip-simple/pidf.h>
#include <pjsip-simple/xpidf.h>
diff --git a/pjsip/include/pjsua-lib/pjsua.h b/pjsip/include/pjsua-lib/pjsua.h
index b6d2fcd6..d120a062 100644
--- a/pjsip/include/pjsua-lib/pjsua.h
+++ b/pjsip/include/pjsua-lib/pjsua.h
@@ -354,6 +354,17 @@ void pjsua_call_xfer(int call_index, const char *dest);
/**
+ * 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.
*/
void pjsua_call_hangup_all(void);
@@ -405,6 +416,57 @@ void pjsua_pres_dump(pj_bool_t detail);
/*****************************************************************************
+ * 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.
*
* The UI API specifies functions that will be called by pjsua upon
@@ -414,12 +476,27 @@ void pjsua_pres_dump(pj_bool_t detail);
/**
* 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);
/*****************************************************************************
diff --git a/pjsip/src/pjsip-simple/errno.c b/pjsip/src/pjsip-simple/errno.c
index e26b5a7b..66d86767 100644
--- a/pjsip/src/pjsip-simple/errno.c
+++ b/pjsip/src/pjsip-simple/errno.c
@@ -29,15 +29,20 @@ static const struct
const char *msg;
} 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" },
{ PJSIP_SIMPLE_ENOPRESENCEINFO, "No presence info in the server subscription" },
{ PJSIP_SIMPLE_EBADCONTENT, "Bad Content-Type for presence" },
{ 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" },
};
diff --git a/pjsip/src/pjsip-simple/evsub.c b/pjsip/src/pjsip-simple/evsub.c
index 874f9204..0f0b974c 100644
--- a/pjsip/src/pjsip-simple/evsub.c
+++ b/pjsip/src/pjsip-simple/evsub.c
@@ -956,6 +956,15 @@ PJ_DEF(pj_status_t) pjsip_evsub_accept( pjsip_evsub *sub,
pjsip_msg_add_hdr( tdata->msg,
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: */
status = pjsip_dlg_send_response( sub->dlg, tsx, tdata );
@@ -1324,13 +1333,11 @@ static pj_status_t create_response( pjsip_evsub *sub,
/* 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;
}
diff --git a/pjsip/src/pjsip-simple/iscomposing.c b/pjsip/src/pjsip-simple/iscomposing.c
new file mode 100644
index 00000000..369281af
--- /dev/null
+++ b/pjsip/src/pjsip-simple/iscomposing.c
@@ -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;
+}
+
+
diff --git a/pjsip/src/pjsip-simple/presence.c b/pjsip/src/pjsip-simple/presence.c
index b2cdcf34..f8014fce 100644
--- a/pjsip/src/pjsip-simple/presence.c
+++ b/pjsip/src/pjsip-simple/presence.c
@@ -786,21 +786,22 @@ static pj_status_t pres_parse_xpidf( pjsip_pres *pres,
/*
- * Called when NOTIFY is received.
+ * 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 void pres_on_evsub_rx_notify( pjsip_evsub *sub,
- pjsip_rx_data *rdata,
- int *p_st_code,
- pj_str_t **p_st_text,
- pjsip_hdr *res_hdr,
- pjsip_msg_body **p_body)
+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;
- pjsip_pres *pres;
pj_status_t status;
- pres = pjsip_evsub_get_mod_data(sub, mod_presence.id);
- PJ_ASSERT_ON_FAIL(pres!=NULL, {return;});
+ *p_st_text = NULL;
/* Check Content-Type and msg body are present. */
ctype_hdr = rdata->msg_info.ctype;
@@ -818,7 +819,7 @@ static void pres_on_evsub_rx_notify( pjsip_evsub *sub,
&warn_text);
pj_list_push_back(res_hdr, warn_hdr);
- return;
+ return PJSIP_ERRNO_FROM_SIP_STATUS(PJSIP_SC_BAD_REQUEST);
}
/* Parse content. */
@@ -859,7 +860,7 @@ static void pres_on_evsub_rx_notify( pjsip_evsub *sub,
status);
pj_list_push_back(res_hdr, warn_hdr);
- return;
+ return status;
}
/* If application calls pres_get_status(), redirect the call to
@@ -867,6 +868,44 @@ static void pres_on_evsub_rx_notify( pjsip_evsub *sub,
*/
pres->tmp_status._is_valid = PJ_TRUE;
+ return PJ_SUCCESS;
+}
+
+
+/*
+ * Called when NOTIFY is received.
+ */
+static void pres_on_evsub_rx_notify( pjsip_evsub *sub,
+ pjsip_rx_data *rdata,
+ int *p_st_code,
+ pj_str_t **p_st_text,
+ pjsip_hdr *res_hdr,
+ pjsip_msg_body **p_body)
+{
+ pjsip_pres *pres;
+ pj_status_t status;
+
+ pres = pjsip_evsub_get_mod_data(sub, mod_presence.id);
+ PJ_ASSERT_ON_FAIL(pres!=NULL, {return;});
+
+ /* Only process the message body if it exists, otherwise treat as
+ * presence status is closed.
+ */
+ 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. */
if (pres->user_cb.on_rx_notify) {
(*pres->user_cb.on_rx_notify)(sub, rdata, p_st_code, p_st_text,
diff --git a/pjsip/src/pjsua-lib/pjsua_call.c b/pjsip/src/pjsua-lib/pjsua_call.c
index 1862e380..612ccd0a 100644
--- a/pjsip/src/pjsua-lib/pjsua_call.c
+++ b/pjsip/src/pjsua-lib/pjsua_call.c
@@ -21,9 +21,9 @@
/*
- * pjsua_inv.c
+ * pjsua_call.c
*
- * Invite session specific functionalities.
+ * Call (INVITE) related stuffs.
*/
#define THIS_FILE "pjsua_inv.c"
@@ -259,7 +259,7 @@ pj_bool_t pjsua_call_on_incoming(pjsip_rx_data *rdata)
status = pjsip_inv_create_uas( dlg, rdata, answer, 0, &inv);
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
return PJ_TRUE;
@@ -285,7 +285,7 @@ pj_bool_t pjsua_call_on_incoming(pjsip_rx_data *rdata)
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
@@ -385,7 +385,7 @@ static void pjsua_call_on_state_changed(pjsip_inv_session *inv,
}
- pjsua_ui_inv_on_state_changed(call->index, e);
+ pjsua_ui_on_call_state(call->index, e);
/* call->inv may be NULL now */
@@ -463,7 +463,7 @@ static void on_call_transfered( pjsip_inv_session *inv,
* No Refer-To header!
*/
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;
}
@@ -481,7 +481,7 @@ static void on_call_transfered( pjsip_inv_session *inv,
status = pjsip_xfer_create_uas( inv->dlg, &xfer_cb, rdata, &sub);
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;
}
@@ -545,7 +545,9 @@ static void on_call_transfered( pjsip_inv_session *inv,
/*
* 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,
pjsip_transaction *tsx,
@@ -561,7 +563,48 @@ static void pjsua_call_on_tsx_state_changed(pjsip_inv_session *inv,
* Incoming REFER request.
*/
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);
+ }
+
}
@@ -1045,6 +1088,104 @@ void pjsua_call_xfer(int call_index, const char *dest)
}
+/**
+ * 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.
*/
diff --git a/pjsip/src/pjsua-lib/pjsua_core.c b/pjsip/src/pjsua-lib/pjsua_core.c
index 4b8b763d..081a3a87 100644
--- a/pjsip/src/pjsua-lib/pjsua_core.c
+++ b/pjsip/src/pjsua-lib/pjsua_core.c
@@ -509,6 +509,10 @@ pj_status_t pjsua_init(void)
pjsua_pres_init();
+ /* Init out-of-dialog MESSAGE request handler. */
+
+ pjsua_im_init();
+
/* Init media endpoint: */
diff --git a/pjsip/src/pjsua-lib/pjsua_im.c b/pjsip/src/pjsua-lib/pjsua_im.c
new file mode 100644
index 00000000..4778d8fa
--- /dev/null
+++ b/pjsip/src/pjsua-lib/pjsua_im.c
@@ -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;
+}
+
diff --git a/pjsip/src/pjsua-lib/pjsua_reg.c b/pjsip/src/pjsua-lib/pjsua_reg.c
index 1552acf6..83485f12 100644
--- a/pjsip/src/pjsua-lib/pjsua_reg.c
+++ b/pjsip/src/pjsua-lib/pjsua_reg.c
@@ -77,7 +77,7 @@ static void regc_cb(struct pjsip_regc_cbparam *param)
acc->reg_last_err = param->status;
acc->reg_last_code = param->code;
- pjsua_ui_regc_on_state_changed(acc->index);
+ pjsua_ui_on_reg_state(acc->index);
}
diff --git a/pjsip/src/pjsua-lib/pjsua_opt.c b/pjsip/src/pjsua-lib/pjsua_settings.c
index f9bada9d..e861f5c6 100644
--- a/pjsip/src/pjsua-lib/pjsua_opt.c
+++ b/pjsip/src/pjsua-lib/pjsua_settings.c
@@ -19,6 +19,12 @@
#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"