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/pjsua-lib | |
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/pjsua-lib')
-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 |
5 files changed, 545 insertions, 9 deletions
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" |