From aad76a0bb1ea62caaf67e6fecaea5786b5f2811a Mon Sep 17 00:00:00 2001 From: Benny Prijono Date: Thu, 2 Mar 2006 21:18:58 +0000 Subject: Added IM and composition indication, and tested git-svn-id: http://svn.pjsip.org/repos/pjproject/trunk@268 74dad513-b988-da41-8d7b-12977e46ad98 --- pjsip/src/pjsip-simple/errno.c | 5 + pjsip/src/pjsip-simple/evsub.c | 21 +- pjsip/src/pjsip-simple/iscomposing.c | 215 +++++++++ pjsip/src/pjsip-simple/presence.c | 63 ++- pjsip/src/pjsua-lib/pjsua_call.c | 157 ++++++- pjsip/src/pjsua-lib/pjsua_core.c | 4 + pjsip/src/pjsua-lib/pjsua_im.c | 385 +++++++++++++++ pjsip/src/pjsua-lib/pjsua_opt.c | 878 ---------------------------------- pjsip/src/pjsua-lib/pjsua_reg.c | 2 +- pjsip/src/pjsua-lib/pjsua_settings.c | 884 +++++++++++++++++++++++++++++++++++ 10 files changed, 1708 insertions(+), 906 deletions(-) create mode 100644 pjsip/src/pjsip-simple/iscomposing.c create mode 100644 pjsip/src/pjsua-lib/pjsua_im.c delete mode 100644 pjsip/src/pjsua-lib/pjsua_opt.c create mode 100644 pjsip/src/pjsua-lib/pjsua_settings.c (limited to 'pjsip/src') 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 + * + * 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 +#include +#include +#include +#include +#include + + +/* 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; itmp_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 + * + * 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 +#include + +/* + * 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_opt.c b/pjsip/src/pjsua-lib/pjsua_opt.c deleted file mode 100644 index f9bada9d..00000000 --- a/pjsip/src/pjsua-lib/pjsua_opt.c +++ /dev/null @@ -1,878 +0,0 @@ -/* $Id$ */ -/* - * Copyright (C) 2003-2006 Benny Prijono - * - * 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 -#include - -#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; ilocal_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; ireg_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_settings.c b/pjsip/src/pjsua-lib/pjsua_settings.c new file mode 100644 index 00000000..e861f5c6 --- /dev/null +++ b/pjsip/src/pjsua-lib/pjsua_settings.c @@ -0,0 +1,884 @@ +/* $Id$ */ +/* + * Copyright (C) 2003-2006 Benny Prijono + * + * 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 +#include + +/* + * 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; ilocal_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