diff options
author | Benny Prijono <bennylp@teluu.com> | 2006-03-02 21:18:58 +0000 |
---|---|---|
committer | Benny Prijono <bennylp@teluu.com> | 2006-03-02 21:18:58 +0000 |
commit | aad76a0bb1ea62caaf67e6fecaea5786b5f2811a (patch) | |
tree | 8183f5a20882579ad310e060115a832a2e6eb444 /pjsip/src | |
parent | cf626e119a5f0b8b424c7cf473f514f1710ce209 (diff) |
Added IM and composition indication, and tested
git-svn-id: http://svn.pjsip.org/repos/pjproject/trunk@268 74dad513-b988-da41-8d7b-12977e46ad98
Diffstat (limited to 'pjsip/src')
-rw-r--r-- | pjsip/src/pjsip-simple/errno.c | 5 | ||||
-rw-r--r-- | pjsip/src/pjsip-simple/evsub.c | 21 | ||||
-rw-r--r-- | pjsip/src/pjsip-simple/iscomposing.c | 215 | ||||
-rw-r--r-- | pjsip/src/pjsip-simple/presence.c | 63 | ||||
-rw-r--r-- | pjsip/src/pjsua-lib/pjsua_call.c | 157 | ||||
-rw-r--r-- | pjsip/src/pjsua-lib/pjsua_core.c | 4 | ||||
-rw-r--r-- | pjsip/src/pjsua-lib/pjsua_im.c | 385 | ||||
-rw-r--r-- | pjsip/src/pjsua-lib/pjsua_reg.c | 2 | ||||
-rw-r--r-- | pjsip/src/pjsua-lib/pjsua_settings.c (renamed from pjsip/src/pjsua-lib/pjsua_opt.c) | 6 |
9 files changed, 830 insertions, 28 deletions
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" |