initial commit

This commit is contained in:
Dan Bastone committed 2021-10-29 14:41:03 -04:00
commit 636959b37b
2474 files changed
+1343178

No files matched your search

@@ -0,0 +1,117 @@
/* $Id$ */
/*
* Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
* Copyright (C) 2003-2008 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/errno.h>
#include <pj/string.h>
/* PJSIP-SIMPLE's own error codes/messages
* MUST KEEP THIS ARRAY SORTED!!
* Message must be limited to 64 chars!
*/
#if defined(PJ_HAS_ERROR_STRING) && (PJ_HAS_ERROR_STRING != 0)
static const struct
{
int code;
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" },
{ PJSIP_SIMPLE_EBADRPID, "Invalid or bad RPID document"},
/* isComposing errors. */
{ PJSIP_SIMPLE_EBADISCOMPOSE, "Bad isComposing indication/XML message" },
};
#endif /* PJ_HAS_ERROR_STRING */
/*
* pjsipsimple_strerror()
*/
PJ_DEF(pj_str_t) pjsipsimple_strerror( pj_status_t statcode,
char *buf, pj_size_t bufsize )
{
pj_str_t errstr;
#if defined(PJ_HAS_ERROR_STRING) && (PJ_HAS_ERROR_STRING != 0)
if (statcode >= PJSIP_SIMPLE_ERRNO_START &&
statcode < PJSIP_SIMPLE_ERRNO_START + PJ_ERRNO_SPACE_SIZE)
{
/* Find the error in the table.
* Use binary search!
*/
int first = 0;
int n = PJ_ARRAY_SIZE(err_str);
while (n > 0) {
int half = n/2;
int mid = first + half;
if (err_str[mid].code < statcode) {
first = mid+1;
n -= (half+1);
} else if (err_str[mid].code > statcode) {
n = half;
} else {
first = mid;
break;
}
}
if (PJ_ARRAY_SIZE(err_str) && err_str[first].code == statcode) {
pj_str_t msg;
msg.ptr = (char*)err_str[first].msg;
msg.slen = pj_ansi_strlen(err_str[first].msg);
errstr.ptr = buf;
pj_strncpy_with_null(&errstr, &msg, bufsize);
return errstr;
}
}
#endif /* PJ_HAS_ERROR_STRING */
/* Error not found. */
errstr.ptr = buf;
errstr.slen = pj_ansi_snprintf(buf, bufsize,
"Unknown pjsip-simple error %d",
statcode);
if (errstr.slen < 1 || errstr.slen >= (pj_ssize_t)bufsize)
errstr.slen = bufsize - 1;
return errstr;
}
File diff suppressed because it is too large. Load diff
@@ -0,0 +1,306 @@
/* $Id$ */
/*
* Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
* Copyright (C) 2003-2008 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/evsub_msg.h>
#include <pjsip/print_util.h>
#include <pjsip/sip_parser.h>
#include <pjlib-util/string.h>
#include <pj/pool.h>
#include <pj/string.h>
#include <pj/except.h>
/*
* Event header.
*/
static int pjsip_event_hdr_print( pjsip_event_hdr *hdr,
char *buf, pj_size_t size);
static pjsip_event_hdr* pjsip_event_hdr_clone( pj_pool_t *pool,
const pjsip_event_hdr *hdr);
static pjsip_event_hdr* pjsip_event_hdr_shallow_clone( pj_pool_t *pool,
const pjsip_event_hdr*);
static pjsip_hdr_vptr event_hdr_vptr =
{
(pjsip_hdr_clone_fptr) &pjsip_event_hdr_clone,
(pjsip_hdr_clone_fptr) &pjsip_event_hdr_shallow_clone,
(pjsip_hdr_print_fptr) &pjsip_event_hdr_print,
};
PJ_DEF(pjsip_event_hdr*) pjsip_event_hdr_create(pj_pool_t *pool)
{
pjsip_event_hdr *hdr = PJ_POOL_ZALLOC_T(pool, pjsip_event_hdr);
hdr->type = PJSIP_H_OTHER;
hdr->name.ptr = "Event";
hdr->name.slen = 5;
hdr->sname.ptr = "o";
hdr->sname.slen = 1;
hdr->vptr = &event_hdr_vptr;
pj_list_init(hdr);
pj_list_init(&hdr->other_param);
return hdr;
}
static int pjsip_event_hdr_print( pjsip_event_hdr *hdr,
char *buf, pj_size_t size)
{
char *p = buf;
char *endbuf = buf+size;
pj_ssize_t printed;
const pjsip_parser_const_t *pc = pjsip_parser_const();
copy_advance(p, hdr->name);
*p++ = ':';
*p++ = ' ';
copy_advance(p, hdr->event_type);
copy_advance_pair(p, ";id=", 4, hdr->id_param);
printed = pjsip_param_print_on(&hdr->other_param, p, endbuf-p,
&pc->pjsip_TOKEN_SPEC,
&pc->pjsip_TOKEN_SPEC, ';');
if (printed < 0)
return (int)printed;
p += printed;
return (int)(p - buf);
}
static pjsip_event_hdr* pjsip_event_hdr_clone( pj_pool_t *pool,
const pjsip_event_hdr *rhs)
{
pjsip_event_hdr *hdr = pjsip_event_hdr_create(pool);
pj_strdup(pool, &hdr->event_type, &rhs->event_type);
pj_strdup(pool, &hdr->id_param, &rhs->id_param);
pjsip_param_clone(pool, &hdr->other_param, &rhs->other_param);
return hdr;
}
static pjsip_event_hdr*
pjsip_event_hdr_shallow_clone( pj_pool_t *pool,
const pjsip_event_hdr *rhs )
{
pjsip_event_hdr *hdr = PJ_POOL_ALLOC_T(pool, pjsip_event_hdr);
pj_memcpy(hdr, rhs, sizeof(*hdr));
pjsip_param_shallow_clone(pool, &hdr->other_param, &rhs->other_param);
return hdr;
}
/*
* Allow-Events header.
*/
PJ_DEF(pjsip_allow_events_hdr*) pjsip_allow_events_hdr_create(pj_pool_t *pool)
{
const pj_str_t STR_ALLOW_EVENTS = { "Allow-Events", 12};
pjsip_allow_events_hdr *hdr;
hdr = pjsip_generic_array_hdr_create(pool, &STR_ALLOW_EVENTS);
if (hdr) {
hdr->sname.ptr = "u";
hdr->sname.slen = 1;
}
return hdr;
}
/*
* Subscription-State header.
*/
static int pjsip_sub_state_hdr_print(pjsip_sub_state_hdr *hdr,
char *buf, pj_size_t size);
static pjsip_sub_state_hdr*
pjsip_sub_state_hdr_clone(pj_pool_t *pool,
const pjsip_sub_state_hdr *hdr);
static pjsip_sub_state_hdr*
pjsip_sub_state_hdr_shallow_clone(pj_pool_t *pool,
const pjsip_sub_state_hdr*);
static pjsip_hdr_vptr sub_state_hdr_vptr =
{
(pjsip_hdr_clone_fptr) &pjsip_sub_state_hdr_clone,
(pjsip_hdr_clone_fptr) &pjsip_sub_state_hdr_shallow_clone,
(pjsip_hdr_print_fptr) &pjsip_sub_state_hdr_print,
};
PJ_DEF(pjsip_sub_state_hdr*) pjsip_sub_state_hdr_create(pj_pool_t *pool)
{
pj_str_t sub_state = { "Subscription-State", 18 };
pjsip_sub_state_hdr *hdr = PJ_POOL_ZALLOC_T(pool, pjsip_sub_state_hdr);
hdr->type = PJSIP_H_OTHER;
hdr->name = hdr->sname = sub_state;
hdr->vptr = &sub_state_hdr_vptr;
hdr->expires_param = PJSIP_EXPIRES_NOT_SPECIFIED;
hdr->retry_after = -1;
pj_list_init(hdr);
pj_list_init(&hdr->other_param);
return hdr;
}
static int pjsip_sub_state_hdr_print(pjsip_sub_state_hdr *hdr,
char *buf, pj_size_t size)
{
char *p = buf;
char *endbuf = buf+size;
pj_ssize_t printed;
const pjsip_parser_const_t *pc = pjsip_parser_const();
copy_advance(p, hdr->name);
*p++ = ':';
*p++ = ' ';
copy_advance_escape(p, hdr->sub_state, pc->pjsip_TOKEN_SPEC);
copy_advance_pair_escape(p, ";reason=", 8, hdr->reason_param,
pc->pjsip_TOKEN_SPEC);
if (hdr->expires_param != PJSIP_EXPIRES_NOT_SPECIFIED) {
pj_memcpy(p, ";expires=", 9);
p += 9;
printed = pj_utoa(hdr->expires_param, p);
p += printed;
}
if (hdr->retry_after >= 0) {
pj_memcpy(p, ";retry-after=", 13);
p += 13;
printed = pj_utoa(hdr->retry_after, p);
p += printed;
}
printed = pjsip_param_print_on( &hdr->other_param, p, endbuf-p,
&pc->pjsip_TOKEN_SPEC,
&pc->pjsip_TOKEN_SPEC,
';');
if (printed < 0)
return (int)printed;
p += printed;
return (int)(p - buf);
}
static pjsip_sub_state_hdr*
pjsip_sub_state_hdr_clone(pj_pool_t *pool,
const pjsip_sub_state_hdr *rhs)
{
pjsip_sub_state_hdr *hdr = pjsip_sub_state_hdr_create(pool);
pj_strdup(pool, &hdr->sub_state, &rhs->sub_state);
pj_strdup(pool, &hdr->reason_param, &rhs->reason_param);
hdr->retry_after = rhs->retry_after;
hdr->expires_param = rhs->expires_param;
pjsip_param_clone(pool, &hdr->other_param, &rhs->other_param);
return hdr;
}
static pjsip_sub_state_hdr*
pjsip_sub_state_hdr_shallow_clone(pj_pool_t *pool,
const pjsip_sub_state_hdr *rhs)
{
pjsip_sub_state_hdr *hdr = PJ_POOL_ALLOC_T(pool, pjsip_sub_state_hdr);
pj_memcpy(hdr, rhs, sizeof(*hdr));
pjsip_param_shallow_clone(pool, &hdr->other_param, &rhs->other_param);
return hdr;
}
/*
* Parse Event header.
*/
static pjsip_hdr *parse_hdr_event(pjsip_parse_ctx *ctx)
{
pjsip_event_hdr *hdr = pjsip_event_hdr_create(ctx->pool);
const pj_str_t id_param = { "id", 2 };
const pjsip_parser_const_t *pc = pjsip_parser_const();
pj_scan_get(ctx->scanner, &pc->pjsip_TOKEN_SPEC, &hdr->event_type);
while (*ctx->scanner->curptr == ';') {
pj_str_t pname, pvalue;
pj_scan_get_char(ctx->scanner);
pjsip_parse_param_imp(ctx->scanner, ctx->pool, &pname, &pvalue, 0);
if (pj_stricmp(&pname, &id_param)==0) {
hdr->id_param = pvalue;
} else {
pjsip_param *param = PJ_POOL_ALLOC_T(ctx->pool, pjsip_param);
param->name = pname;
param->value = pvalue;
pj_list_push_back(&hdr->other_param, param);
}
}
pjsip_parse_end_hdr_imp( ctx->scanner );
return (pjsip_hdr*)hdr;
}
/*
* Parse Subscription-State header.
*/
static pjsip_hdr* parse_hdr_sub_state( pjsip_parse_ctx *ctx )
{
pjsip_sub_state_hdr *hdr = pjsip_sub_state_hdr_create(ctx->pool);
const pj_str_t reason = { "reason", 6 },
expires = { "expires", 7 },
retry_after = { "retry-after", 11 };
const pjsip_parser_const_t *pc = pjsip_parser_const();
pj_scan_get(ctx->scanner, &pc->pjsip_TOKEN_SPEC, &hdr->sub_state);
while (*ctx->scanner->curptr == ';') {
pj_str_t pname, pvalue;
pj_scan_get_char(ctx->scanner);
pjsip_parse_param_imp(ctx->scanner, ctx->pool, &pname, &pvalue, 0);
if (pj_stricmp(&pname, &reason) == 0) {
hdr->reason_param = pvalue;
} else if (pj_stricmp(&pname, &expires) == 0) {
hdr->expires_param = pj_strtoul(&pvalue);
if (hdr->expires_param == PJSIP_EXPIRES_NOT_SPECIFIED)
hdr->expires_param--;
} else if (pj_stricmp(&pname, &retry_after) == 0) {
hdr->retry_after = pj_strtoul(&pvalue);
} else {
pjsip_param *param = PJ_POOL_ALLOC_T(ctx->pool, pjsip_param);
param->name = pname;
param->value = pvalue;
pj_list_push_back(&hdr->other_param, param);
}
}
pjsip_parse_end_hdr_imp( ctx->scanner );
return (pjsip_hdr*)hdr;
}
/*
* Register header parsers.
*/
PJ_DEF(void) pjsip_evsub_init_parser(void)
{
pjsip_register_hdr_parser( "Event", "o",
&parse_hdr_event);
pjsip_register_hdr_parser( "Subscription-State", NULL,
&parse_hdr_sub_state);
}
@@ -0,0 +1,218 @@
/* $Id$ */
/*
* Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
* Copyright (C) 2003-2008 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. */
PJ_UNUSED_ARG(lst_actv);
//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 = (char*) 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((const pj_xml_node*)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, (const pj_xml_node*)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_T(pool, 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;
}
@@ -0,0 +1,598 @@
/* $Id$ */
/*
* Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
* Copyright (C) 2003-2008 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/mwi.h>
#include <pjsip-simple/errno.h>
#include <pjsip-simple/evsub_msg.h>
#include <pjsip/sip_module.h>
#include <pjsip/sip_endpoint.h>
#include <pjsip/sip_dialog.h>
#include <pj/assert.h>
#include <pj/guid.h>
#include <pj/log.h>
#include <pj/os.h>
#include <pj/pool.h>
#include <pj/string.h>
#define THIS_FILE "mwi.c"
/*
* MWI module (mod-mdi)
*/
static struct pjsip_module mod_mwi =
{
NULL, NULL, /* prev, next. */
{ "mod-mwi", 7 }, /* Name. */
-1, /* Id */
PJSIP_MOD_PRIORITY_DIALOG_USAGE,/* Priority */
NULL, /* load() */
NULL, /* start() */
NULL, /* stop() */
NULL, /* unload() */
NULL, /* on_rx_request() */
NULL, /* on_rx_response() */
NULL, /* on_tx_request. */
NULL, /* on_tx_response() */
NULL, /* on_tsx_state() */
};
/*
* This structure describe an mwi agent (both client and server)
*/
typedef struct pjsip_mwi
{
pjsip_evsub *sub; /**< Event subscribtion record. */
pjsip_dialog *dlg; /**< The dialog. */
pjsip_evsub_user user_cb; /**< The user callback. */
/* These are for server subscriptions */
pj_pool_t *body_pool; /**< Pool to save message body */
pjsip_media_type mime_type; /**< MIME type of last msg body */
pj_str_t body; /**< Last sent message body */
} pjsip_mwi;
/*
* Forward decl for evsub callbacks.
*/
static void mwi_on_evsub_state( pjsip_evsub *sub, pjsip_event *event);
static void mwi_on_evsub_tsx_state( pjsip_evsub *sub, pjsip_transaction *tsx,
pjsip_event *event);
static void mwi_on_evsub_rx_refresh( 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 void mwi_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 void mwi_on_evsub_client_refresh(pjsip_evsub *sub);
static void mwi_on_evsub_server_timeout(pjsip_evsub *sub);
/*
* Event subscription callback for mwi.
*/
static pjsip_evsub_user mwi_user =
{
&mwi_on_evsub_state,
&mwi_on_evsub_tsx_state,
&mwi_on_evsub_rx_refresh,
&mwi_on_evsub_rx_notify,
&mwi_on_evsub_client_refresh,
&mwi_on_evsub_server_timeout,
};
/*
* Some static constants.
*/
static const pj_str_t STR_EVENT = { "Event", 5 };
static const pj_str_t STR_MWI = { "message-summary", 15 };
static const pj_str_t STR_APP_SIMPLE_SMS = { "application/simple-message-summary", 34};
/*
* Init mwi module.
*/
PJ_DEF(pj_status_t) pjsip_mwi_init_module( pjsip_endpoint *endpt,
pjsip_module *mod_evsub)
{
pj_status_t status;
pj_str_t accept[1];
/* Check arguments. */
PJ_ASSERT_RETURN(endpt && mod_evsub, PJ_EINVAL);
/* Must have not been registered */
PJ_ASSERT_RETURN(mod_mwi.id == -1, PJ_EINVALIDOP);
/* Register to endpoint */
status = pjsip_endpt_register_module(endpt, &mod_mwi);
if (status != PJ_SUCCESS)
return status;
accept[0] = STR_APP_SIMPLE_SMS;
/* Register event package to event module. */
status = pjsip_evsub_register_pkg( &mod_mwi, &STR_MWI,
PJSIP_MWI_DEFAULT_EXPIRES,
PJ_ARRAY_SIZE(accept), accept);
if (status != PJ_SUCCESS) {
pjsip_endpt_unregister_module(endpt, &mod_mwi);
return status;
}
return PJ_SUCCESS;
}
/*
* Get mwi module instance.
*/
PJ_DEF(pjsip_module*) pjsip_mwi_instance(void)
{
return &mod_mwi;
}
/*
* Create client subscription.
*/
PJ_DEF(pj_status_t) pjsip_mwi_create_uac( pjsip_dialog *dlg,
const pjsip_evsub_user *user_cb,
unsigned options,
pjsip_evsub **p_evsub )
{
pj_status_t status;
pjsip_mwi *mwi;
pjsip_evsub *sub;
PJ_ASSERT_RETURN(dlg && p_evsub, PJ_EINVAL);
PJ_UNUSED_ARG(options);
pjsip_dlg_inc_lock(dlg);
/* Create event subscription */
status = pjsip_evsub_create_uac( dlg, &mwi_user, &STR_MWI,
options, &sub);
if (status != PJ_SUCCESS)
goto on_return;
/* Create mwi */
mwi = PJ_POOL_ZALLOC_T(dlg->pool, pjsip_mwi);
mwi->dlg = dlg;
mwi->sub = sub;
if (user_cb)
pj_memcpy(&mwi->user_cb, user_cb, sizeof(pjsip_evsub_user));
/* Attach to evsub */
pjsip_evsub_set_mod_data(sub, mod_mwi.id, mwi);
*p_evsub = sub;
on_return:
pjsip_dlg_dec_lock(dlg);
return status;
}
/*
* Create server subscription.
*/
PJ_DEF(pj_status_t) pjsip_mwi_create_uas( pjsip_dialog *dlg,
const pjsip_evsub_user *user_cb,
pjsip_rx_data *rdata,
pjsip_evsub **p_evsub )
{
pjsip_accept_hdr *accept;
pjsip_event_hdr *event;
pjsip_evsub *sub;
pjsip_mwi *mwi;
char obj_name[PJ_MAX_OBJ_NAME];
pj_status_t status;
/* Check arguments */
PJ_ASSERT_RETURN(dlg && rdata && p_evsub, PJ_EINVAL);
/* Must be request message */
PJ_ASSERT_RETURN(rdata->msg_info.msg->type == PJSIP_REQUEST_MSG,
PJSIP_ENOTREQUESTMSG);
/* Check that request is SUBSCRIBE */
PJ_ASSERT_RETURN(pjsip_method_cmp(&rdata->msg_info.msg->line.req.method,
&pjsip_subscribe_method)==0,
PJSIP_SIMPLE_ENOTSUBSCRIBE);
/* Check that Event header contains "mwi" */
event = (pjsip_event_hdr*)
pjsip_msg_find_hdr_by_name(rdata->msg_info.msg, &STR_EVENT, NULL);
if (!event) {
return PJSIP_ERRNO_FROM_SIP_STATUS(PJSIP_SC_BAD_REQUEST);
}
if (pj_stricmp(&event->event_type, &STR_MWI) != 0) {
return PJSIP_ERRNO_FROM_SIP_STATUS(PJSIP_SC_BAD_EVENT);
}
/* Check that request contains compatible Accept header. */
accept = (pjsip_accept_hdr*)
pjsip_msg_find_hdr(rdata->msg_info.msg, PJSIP_H_ACCEPT, NULL);
if (accept) {
unsigned i;
for (i=0; i<accept->count; ++i) {
if (pj_stricmp(&accept->values[i], &STR_APP_SIMPLE_SMS)==0) {
break;
}
}
if (i==accept->count) {
/* Nothing is acceptable */
return PJSIP_ERRNO_FROM_SIP_STATUS(PJSIP_SC_NOT_ACCEPTABLE);
}
} else {
/* No Accept header.
* Assume client supports "application/simple-message-summary"
*/
}
/* Lock dialog */
pjsip_dlg_inc_lock(dlg);
/* Create server subscription */
status = pjsip_evsub_create_uas( dlg, &mwi_user, rdata, 0, &sub);
if (status != PJ_SUCCESS)
goto on_return;
/* Create server mwi subscription */
mwi = PJ_POOL_ZALLOC_T(dlg->pool, pjsip_mwi);
mwi->dlg = dlg;
mwi->sub = sub;
if (user_cb)
pj_memcpy(&mwi->user_cb, user_cb, sizeof(pjsip_evsub_user));
pj_ansi_snprintf(obj_name, PJ_MAX_OBJ_NAME, "mwibd%p", dlg->pool);
mwi->body_pool = pj_pool_create(dlg->pool->factory, obj_name,
512, 512, NULL);
/* Attach to evsub */
pjsip_evsub_set_mod_data(sub, mod_mwi.id, mwi);
/* Done: */
*p_evsub = sub;
on_return:
pjsip_dlg_dec_lock(dlg);
return status;
}
/*
* Forcefully terminate mwi.
*/
PJ_DEF(pj_status_t) pjsip_mwi_terminate( pjsip_evsub *sub,
pj_bool_t notify )
{
return pjsip_evsub_terminate(sub, notify);
}
/*
* Create SUBSCRIBE
*/
PJ_DEF(pj_status_t) pjsip_mwi_initiate( pjsip_evsub *sub,
pj_uint32_t expires,
pjsip_tx_data **p_tdata)
{
return pjsip_evsub_initiate(sub, &pjsip_subscribe_method, expires,
p_tdata);
}
/*
* Accept incoming subscription.
*/
PJ_DEF(pj_status_t) pjsip_mwi_accept( pjsip_evsub *sub,
pjsip_rx_data *rdata,
int st_code,
const pjsip_hdr *hdr_list )
{
return pjsip_evsub_accept( sub, rdata, st_code, hdr_list );
}
/*
* Create message body and attach it to the (NOTIFY) request.
*/
static pj_status_t mwi_create_msg_body( pjsip_mwi *mwi,
pjsip_tx_data *tdata)
{
pjsip_msg_body *body;
pj_str_t dup_text;
PJ_ASSERT_RETURN(mwi->mime_type.type.slen && mwi->body.slen, PJ_EINVALIDOP);
/* Clone the message body and mime type */
pj_strdup(tdata->pool, &dup_text, &mwi->body);
/* Create the message body */
body = PJ_POOL_ZALLOC_T(tdata->pool, pjsip_msg_body);
pjsip_media_type_cp(tdata->pool, &body->content_type, &mwi->mime_type);
body->data = dup_text.ptr;
body->len = (unsigned)dup_text.slen;
body->print_body = &pjsip_print_text_body;
body->clone_data = &pjsip_clone_text_data;
/* Attach to tdata */
tdata->msg->body = body;
return PJ_SUCCESS;
}
/*
* Create NOTIFY
*/
PJ_DEF(pj_status_t) pjsip_mwi_notify( pjsip_evsub *sub,
pjsip_evsub_state state,
const pj_str_t *state_str,
const pj_str_t *reason,
const pjsip_media_type *mime_type,
const pj_str_t *body,
pjsip_tx_data **p_tdata)
{
pjsip_mwi *mwi;
pjsip_tx_data *tdata;
pj_status_t status;
/* Check arguments. */
PJ_ASSERT_RETURN(sub && mime_type && body && p_tdata, PJ_EINVAL);
/* Get the mwi object. */
mwi = (pjsip_mwi*) pjsip_evsub_get_mod_data(sub, mod_mwi.id);
PJ_ASSERT_RETURN(mwi != NULL, PJ_EINVALIDOP);
/* Lock object. */
pjsip_dlg_inc_lock(mwi->dlg);
/* Create the NOTIFY request. */
status = pjsip_evsub_notify( sub, state, state_str, reason, &tdata);
if (status != PJ_SUCCESS)
goto on_return;
/* Update the cached message body */
if (mime_type || body)
pj_pool_reset(mwi->body_pool);
if (mime_type)
pjsip_media_type_cp(mwi->body_pool, &mwi->mime_type, mime_type);
if (body)
pj_strdup(mwi->body_pool, &mwi->body, body);
/* Create message body */
status = mwi_create_msg_body( mwi, tdata );
if (status != PJ_SUCCESS)
goto on_return;
/* Done. */
*p_tdata = tdata;
on_return:
pjsip_dlg_dec_lock(mwi->dlg);
return status;
}
/*
* Create NOTIFY that reflect current state.
*/
PJ_DEF(pj_status_t) pjsip_mwi_current_notify( pjsip_evsub *sub,
pjsip_tx_data **p_tdata )
{
pjsip_mwi *mwi;
pjsip_tx_data *tdata;
pj_status_t status;
/* Check arguments. */
PJ_ASSERT_RETURN(sub && p_tdata, PJ_EINVAL);
/* Get the mwi object. */
mwi = (pjsip_mwi*) pjsip_evsub_get_mod_data(sub, mod_mwi.id);
PJ_ASSERT_RETURN(mwi != NULL, PJ_EINVALIDOP);
/* Lock object. */
pjsip_dlg_inc_lock(mwi->dlg);
/* Create the NOTIFY request. */
status = pjsip_evsub_current_notify( sub, &tdata);
if (status != PJ_SUCCESS)
goto on_return;
/* Create message body to reflect the mwi status. */
status = mwi_create_msg_body( mwi, tdata );
if (status != PJ_SUCCESS)
goto on_return;
/* Done. */
*p_tdata = tdata;
on_return:
pjsip_dlg_dec_lock(mwi->dlg);
return status;
}
/*
* Send request.
*/
PJ_DEF(pj_status_t) pjsip_mwi_send_request( pjsip_evsub *sub,
pjsip_tx_data *tdata )
{
return pjsip_evsub_send_request(sub, tdata);
}
/*
* This callback is called by event subscription when subscription
* state has changed.
*/
static void mwi_on_evsub_state( pjsip_evsub *sub, pjsip_event *event)
{
pjsip_mwi *mwi;
mwi = (pjsip_mwi*) pjsip_evsub_get_mod_data(sub, mod_mwi.id);
PJ_ASSERT_ON_FAIL(mwi!=NULL, {return;});
if (mwi->user_cb.on_evsub_state)
(*mwi->user_cb.on_evsub_state)(sub, event);
if (pjsip_evsub_get_state(sub) == PJSIP_EVSUB_STATE_TERMINATED) {
if (mwi->body_pool) {
pj_pool_release(mwi->body_pool);
mwi->body_pool = NULL;
}
}
}
/*
* Called when transaction state has changed.
*/
static void mwi_on_evsub_tsx_state( pjsip_evsub *sub, pjsip_transaction *tsx,
pjsip_event *event)
{
pjsip_mwi *mwi;
mwi = (pjsip_mwi*) pjsip_evsub_get_mod_data(sub, mod_mwi.id);
PJ_ASSERT_ON_FAIL(mwi!=NULL, {return;});
if (mwi->user_cb.on_tsx_state)
(*mwi->user_cb.on_tsx_state)(sub, tsx, event);
}
/*
* Called when SUBSCRIBE is received.
*/
static void mwi_on_evsub_rx_refresh( 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_mwi *mwi;
mwi = (pjsip_mwi*) pjsip_evsub_get_mod_data(sub, mod_mwi.id);
PJ_ASSERT_ON_FAIL(mwi!=NULL, {return;});
if (mwi->user_cb.on_rx_refresh) {
(*mwi->user_cb.on_rx_refresh)(sub, rdata, p_st_code, p_st_text,
res_hdr, p_body);
} else {
/* Implementors MUST send NOTIFY if it implements on_rx_refresh */
pjsip_tx_data *tdata;
pj_str_t timeout = { "timeout", 7};
pj_status_t status;
if (pjsip_evsub_get_state(sub)==PJSIP_EVSUB_STATE_TERMINATED) {
status = pjsip_mwi_notify( sub, PJSIP_EVSUB_STATE_TERMINATED,
NULL, &timeout, NULL, NULL, &tdata);
} else {
status = pjsip_mwi_current_notify(sub, &tdata);
}
if (status == PJ_SUCCESS)
pjsip_mwi_send_request(sub, tdata);
}
}
/*
* Called when NOTIFY is received.
*/
static void mwi_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_mwi *mwi;
mwi = (pjsip_mwi*) pjsip_evsub_get_mod_data(sub, mod_mwi.id);
PJ_ASSERT_ON_FAIL(mwi!=NULL, {return;});
/* Just notify application. */
if (mwi->user_cb.on_rx_notify) {
(*mwi->user_cb.on_rx_notify)(sub, rdata, p_st_code, p_st_text,
res_hdr, p_body);
}
}
/*
* Called when it's time to send SUBSCRIBE.
*/
static void mwi_on_evsub_client_refresh(pjsip_evsub *sub)
{
pjsip_mwi *mwi;
mwi = (pjsip_mwi*) pjsip_evsub_get_mod_data(sub, mod_mwi.id);
PJ_ASSERT_ON_FAIL(mwi!=NULL, {return;});
if (mwi->user_cb.on_client_refresh) {
(*mwi->user_cb.on_client_refresh)(sub);
} else {
pj_status_t status;
pjsip_tx_data *tdata;
status = pjsip_mwi_initiate(sub, PJSIP_EXPIRES_NOT_SPECIFIED, &tdata);
if (status == PJ_SUCCESS)
pjsip_mwi_send_request(sub, tdata);
}
}
/*
* Called when no refresh is received after the interval.
*/
static void mwi_on_evsub_server_timeout(pjsip_evsub *sub)
{
pjsip_mwi *mwi;
mwi = (pjsip_mwi*) pjsip_evsub_get_mod_data(sub, mod_mwi.id);
PJ_ASSERT_ON_FAIL(mwi!=NULL, {return;});
if (mwi->user_cb.on_server_timeout) {
(*mwi->user_cb.on_server_timeout)(sub);
} else {
pj_status_t status;
pjsip_tx_data *tdata;
pj_str_t reason = { "timeout", 7 };
status = pjsip_mwi_notify(sub, PJSIP_EVSUB_STATE_TERMINATED,
NULL, &reason, NULL, NULL, &tdata);
if (status == PJ_SUCCESS)
pjsip_mwi_send_request(sub, tdata);
}
}
@@ -0,0 +1,365 @@
/* $Id$ */
/*
* Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
* Copyright (C) 2003-2008 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/pidf.h>
#include <pj/string.h>
#include <pj/pool.h>
#include <pj/assert.h>
struct pjpidf_op_desc pjpidf_op =
{
{
&pjpidf_pres_construct,
&pjpidf_pres_add_tuple,
&pjpidf_pres_get_first_tuple,
&pjpidf_pres_get_next_tuple,
&pjpidf_pres_find_tuple,
&pjpidf_pres_remove_tuple,
&pjpidf_pres_add_note,
&pjpidf_pres_get_first_note,
&pjpidf_pres_get_next_note
},
{
&pjpidf_tuple_construct,
&pjpidf_tuple_get_id,
&pjpidf_tuple_set_id,
&pjpidf_tuple_get_status,
&pjpidf_tuple_get_contact,
&pjpidf_tuple_set_contact,
&pjpidf_tuple_set_contact_prio,
&pjpidf_tuple_get_contact_prio,
&pjpidf_tuple_add_note,
&pjpidf_tuple_get_first_note,
&pjpidf_tuple_get_next_note,
&pjpidf_tuple_get_timestamp,
&pjpidf_tuple_set_timestamp,
&pjpidf_tuple_set_timestamp_np
},
{
&pjpidf_status_construct,
&pjpidf_status_is_basic_open,
&pjpidf_status_set_basic_open
}
};
static pj_str_t PRESENCE = { "presence", 8 };
static pj_str_t ENTITY = { "entity", 6};
static pj_str_t TUPLE = { "tuple", 5 };
static pj_str_t ID = { "id", 2 };
static pj_str_t NOTE = { "note", 4 };
static pj_str_t STATUS = { "status", 6 };
static pj_str_t CONTACT = { "contact", 7 };
static pj_str_t PRIORITY = { "priority", 8 };
static pj_str_t TIMESTAMP = { "timestamp", 9 };
static pj_str_t BASIC = { "basic", 5 };
static pj_str_t OPEN = { "open", 4 };
static pj_str_t CLOSED = { "closed", 6 };
static pj_str_t EMPTY_STRING = { NULL, 0 };
static pj_str_t XMLNS = { "xmlns", 5 };
static pj_str_t PIDF_XMLNS = { "urn:ietf:params:xml:ns:pidf", 27 };
static void xml_init_node(pj_pool_t *pool, pj_xml_node *node,
pj_str_t *name, const pj_str_t *value)
{
pj_list_init(&node->attr_head);
pj_list_init(&node->node_head);
node->name = *name;
if (value) pj_strdup(pool, &node->content, value);
else node->content.ptr=NULL, node->content.slen=0;
}
static pj_xml_attr* xml_create_attr(pj_pool_t *pool, pj_str_t *name,
const pj_str_t *value)
{
pj_xml_attr *attr = PJ_POOL_ALLOC_T(pool, pj_xml_attr);
attr->name = *name;
pj_strdup(pool, &attr->value, value);
return attr;
}
/* Presence */
PJ_DEF(void) pjpidf_pres_construct(pj_pool_t *pool, pjpidf_pres *pres,
const pj_str_t *entity)
{
pj_xml_attr *attr;
xml_init_node(pool, pres, &PRESENCE, NULL);
attr = xml_create_attr(pool, &ENTITY, entity);
pj_xml_add_attr(pres, attr);
attr = xml_create_attr(pool, &XMLNS, &PIDF_XMLNS);
pj_xml_add_attr(pres, attr);
}
PJ_DEF(pjpidf_tuple*) pjpidf_pres_add_tuple(pj_pool_t *pool, pjpidf_pres *pres,
const pj_str_t *id)
{
pjpidf_tuple *t = PJ_POOL_ALLOC_T(pool, pjpidf_tuple);
pjpidf_tuple_construct(pool, t, id);
pj_xml_add_node(pres, t);
return t;
}
PJ_DEF(pjpidf_tuple*) pjpidf_pres_get_first_tuple(pjpidf_pres *pres)
{
return pj_xml_find_node(pres, &TUPLE);
}
PJ_DEF(pjpidf_tuple*) pjpidf_pres_get_next_tuple(pjpidf_pres *pres,
pjpidf_tuple *tuple)
{
return pj_xml_find_next_node(pres, tuple, &TUPLE);
}
static pj_bool_t find_tuple_by_id(const pj_xml_node *node, const void *id)
{
return pj_xml_find_attr(node, &ID, (const pj_str_t*)id) != NULL;
}
PJ_DEF(pjpidf_tuple*) pjpidf_pres_find_tuple(pjpidf_pres *pres, const pj_str_t *id)
{
return pj_xml_find(pres, &TUPLE, id, &find_tuple_by_id);
}
PJ_DEF(void) pjpidf_pres_remove_tuple(pjpidf_pres *pres, pjpidf_tuple *t)
{
PJ_UNUSED_ARG(pres);
pj_list_erase(t);
}
PJ_DEF(pjpidf_note*) pjpidf_pres_add_note(pj_pool_t *pool, pjpidf_pres *pres,
const pj_str_t *text)
{
pjpidf_note *note = PJ_POOL_ALLOC_T(pool, pjpidf_note);
xml_init_node(pool, note, &NOTE, text);
pj_xml_add_node(pres, note);
return note;
}
PJ_DEF(pjpidf_note*) pjpidf_pres_get_first_note(pjpidf_pres *pres)
{
return pj_xml_find_node( pres, &NOTE);
}
PJ_DEF(pjpidf_note*) pjpidf_pres_get_next_note(pjpidf_pres *t, pjpidf_note *note)
{
return pj_xml_find_next_node(t, note, &NOTE);
}
/* Tuple */
PJ_DEF(void) pjpidf_tuple_construct(pj_pool_t *pool, pjpidf_tuple *t,
const pj_str_t *id)
{
pj_xml_attr *attr;
pjpidf_status *st;
xml_init_node(pool, t, &TUPLE, NULL);
attr = xml_create_attr(pool, &ID, id);
pj_xml_add_attr(t, attr);
st = PJ_POOL_ALLOC_T(pool, pjpidf_status);
pjpidf_status_construct(pool, st);
pj_xml_add_node(t, st);
}
PJ_DEF(const pj_str_t*) pjpidf_tuple_get_id(const pjpidf_tuple *t)
{
const pj_xml_attr *attr = pj_xml_find_attr((pj_xml_node*)t, &ID, NULL);
pj_assert(attr);
return &attr->value;
}
PJ_DEF(void) pjpidf_tuple_set_id(pj_pool_t *pool, pjpidf_tuple *t, const pj_str_t *id)
{
pj_xml_attr *attr = pj_xml_find_attr(t, &ID, NULL);
pj_assert(attr);
pj_strdup(pool, &attr->value, id);
}
PJ_DEF(pjpidf_status*) pjpidf_tuple_get_status(pjpidf_tuple *t)
{
pjpidf_status *st = (pjpidf_status*)pj_xml_find_node(t, &STATUS);
pj_assert(st);
return st;
}
PJ_DEF(const pj_str_t*) pjpidf_tuple_get_contact(const pjpidf_tuple *t)
{
pj_xml_node *node = pj_xml_find_node((pj_xml_node*)t, &CONTACT);
if (!node)
return &EMPTY_STRING;
return &node->content;
}
PJ_DEF(void) pjpidf_tuple_set_contact(pj_pool_t *pool, pjpidf_tuple *t,
const pj_str_t *contact)
{
pj_xml_node *node = pj_xml_find_node(t, &CONTACT);
if (!node) {
node = PJ_POOL_ALLOC_T(pool, pj_xml_node);
xml_init_node(pool, node, &CONTACT, contact);
pj_xml_add_node(t, node);
} else {
pj_strdup(pool, &node->content, contact);
}
}
PJ_DEF(void) pjpidf_tuple_set_contact_prio(pj_pool_t *pool, pjpidf_tuple *t,
const pj_str_t *prio)
{
pj_xml_node *node = pj_xml_find_node(t, &CONTACT);
pj_xml_attr *attr;
if (!node) {
node = PJ_POOL_ALLOC_T(pool, pj_xml_node);
xml_init_node(pool, node, &CONTACT, NULL);
pj_xml_add_node(t, node);
}
attr = pj_xml_find_attr(node, &PRIORITY, NULL);
if (!attr) {
attr = xml_create_attr(pool, &PRIORITY, prio);
pj_xml_add_attr(node, attr);
} else {
pj_strdup(pool, &attr->value, prio);
}
}
PJ_DEF(const pj_str_t*) pjpidf_tuple_get_contact_prio(const pjpidf_tuple *t)
{
pj_xml_node *node = pj_xml_find_node((pj_xml_node*)t, &CONTACT);
pj_xml_attr *attr;
if (!node)
return &EMPTY_STRING;
attr = pj_xml_find_attr(node, &PRIORITY, NULL);
if (!attr)
return &EMPTY_STRING;
return &attr->value;
}
PJ_DEF(pjpidf_note*) pjpidf_tuple_add_note(pj_pool_t *pool, pjpidf_tuple *t,
const pj_str_t *text)
{
pjpidf_note *note = PJ_POOL_ALLOC_T(pool, pjpidf_note);
xml_init_node(pool, note, &NOTE, text);
pj_xml_add_node(t, note);
return note;
}
PJ_DEF(pjpidf_note*) pjpidf_tuple_get_first_note(pjpidf_tuple *t)
{
return pj_xml_find_node(t, &NOTE);
}
PJ_DEF(pjpidf_note*) pjpidf_tuple_get_next_note(pjpidf_tuple *t, pjpidf_note *n)
{
return pj_xml_find_next_node(t, n, &NOTE);
}
PJ_DEF(const pj_str_t*) pjpidf_tuple_get_timestamp(const pjpidf_tuple *t)
{
pj_xml_node *node = pj_xml_find_node((pj_xml_node*)t, &TIMESTAMP);
return node ? &node->content : &EMPTY_STRING;
}
PJ_DEF(void) pjpidf_tuple_set_timestamp(pj_pool_t *pool, pjpidf_tuple *t,
const pj_str_t *ts)
{
pj_xml_node *node = pj_xml_find_node(t, &TIMESTAMP);
if (!node) {
node = PJ_POOL_ALLOC_T(pool, pj_xml_node);
xml_init_node(pool, node, &TIMESTAMP, ts);
pj_xml_add_node(t, node);
} else {
pj_strdup(pool, &node->content, ts);
}
}
PJ_DEF(void) pjpidf_tuple_set_timestamp_np(pj_pool_t *pool, pjpidf_tuple *t,
pj_str_t *ts)
{
pj_xml_node *node = pj_xml_find_node(t, &TIMESTAMP);
if (!node) {
node = PJ_POOL_ALLOC_T(pool, pj_xml_node);
xml_init_node(pool, node, &TIMESTAMP, ts);
} else {
node->content = *ts;
}
}
/* Status */
PJ_DEF(void) pjpidf_status_construct(pj_pool_t *pool, pjpidf_status *st)
{
pj_xml_node *node;
xml_init_node(pool, st, &STATUS, NULL);
node = PJ_POOL_ALLOC_T(pool, pj_xml_node);
xml_init_node(pool, node, &BASIC, &CLOSED);
pj_xml_add_node(st, node);
}
PJ_DEF(pj_bool_t) pjpidf_status_is_basic_open(const pjpidf_status *st)
{
pj_xml_node *node = pj_xml_find_node((pj_xml_node*)st, &BASIC);
if (!node)
return PJ_FALSE;
return pj_stricmp(&node->content, &OPEN)==0;
}
PJ_DEF(void) pjpidf_status_set_basic_open(pjpidf_status *st, pj_bool_t open)
{
pj_xml_node *node = pj_xml_find_node(st, &BASIC);
if (node)
node->content = open ? OPEN : CLOSED;
}
PJ_DEF(pjpidf_pres*) pjpidf_create(pj_pool_t *pool, const pj_str_t *entity)
{
pjpidf_pres *pres = PJ_POOL_ALLOC_T(pool, pjpidf_pres);
pjpidf_pres_construct(pool, pres, entity);
return pres;
}
PJ_DEF(pjpidf_pres*) pjpidf_parse(pj_pool_t *pool, char *text, int len)
{
pjpidf_pres *pres = pj_xml_parse(pool, text, len);
if (pres && pres->name.slen >= 8) {
pj_str_t name;
name.ptr = pres->name.ptr + (pres->name.slen - 8);
name.slen = 8;
if (pj_stricmp(&name, &PRESENCE) == 0)
return pres;
}
return NULL;
}
PJ_DEF(int) pjpidf_print(const pjpidf_pres* pres, char *buf, int len)
{
return pj_xml_print(pres, buf, len, PJ_TRUE);
}
@@ -0,0 +1,942 @@
/* $Id$ */
/*
* Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
* Copyright (C) 2003-2008 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/presence.h>
#include <pjsip-simple/errno.h>
#include <pjsip-simple/evsub_msg.h>
#include <pjsip/sip_module.h>
#include <pjsip/sip_multipart.h>
#include <pjsip/sip_endpoint.h>
#include <pjsip/sip_dialog.h>
#include <pj/assert.h>
#include <pj/guid.h>
#include <pj/log.h>
#include <pj/os.h>
#include <pj/pool.h>
#include <pj/string.h>
#define THIS_FILE "presence.c"
#define PRES_DEFAULT_EXPIRES PJSIP_PRES_DEFAULT_EXPIRES
#if PJSIP_PRES_BAD_CONTENT_RESPONSE < 200 || \
PJSIP_PRES_BAD_CONTENT_RESPONSE > 699 || \
PJSIP_PRES_BAD_CONTENT_RESPONSE/100 == 3
# error Invalid PJSIP_PRES_BAD_CONTENT_RESPONSE value
#endif
/*
* Presence module (mod-presence)
*/
static struct pjsip_module mod_presence =
{
NULL, NULL, /* prev, next. */
{ "mod-presence", 12 }, /* Name. */
-1, /* Id */
PJSIP_MOD_PRIORITY_DIALOG_USAGE,/* Priority */
NULL, /* load() */
NULL, /* start() */
NULL, /* stop() */
NULL, /* unload() */
NULL, /* on_rx_request() */
NULL, /* on_rx_response() */
NULL, /* on_tx_request. */
NULL, /* on_tx_response() */
NULL, /* on_tsx_state() */
};
/*
* Presence message body type.
*/
typedef enum content_type_e
{
CONTENT_TYPE_NONE,
CONTENT_TYPE_PIDF,
CONTENT_TYPE_XPIDF,
} content_type_e;
/*
* This structure describe a presentity, for both subscriber and notifier.
*/
struct pjsip_pres
{
pjsip_evsub *sub; /**< Event subscribtion record. */
pjsip_dialog *dlg; /**< The dialog. */
content_type_e content_type; /**< Content-Type. */
pj_pool_t *status_pool; /**< Pool for pres_status */
pjsip_pres_status status; /**< Presence status. */
pj_pool_t *tmp_pool; /**< Pool for tmp_status */
pjsip_pres_status tmp_status; /**< Temp, before NOTIFY is answred.*/
pjsip_evsub_user user_cb; /**< The user callback. */
};
typedef struct pjsip_pres pjsip_pres;
/*
* Forward decl for evsub callback.
*/
static void pres_on_evsub_state( pjsip_evsub *sub, pjsip_event *event);
static void pres_on_evsub_tsx_state( pjsip_evsub *sub, pjsip_transaction *tsx,
pjsip_event *event);
static void pres_on_evsub_rx_refresh( 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 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 void pres_on_evsub_client_refresh(pjsip_evsub *sub);
static void pres_on_evsub_server_timeout(pjsip_evsub *sub);
/*
* Event subscription callback for presence.
*/
static pjsip_evsub_user pres_user =
{
&pres_on_evsub_state,
&pres_on_evsub_tsx_state,
&pres_on_evsub_rx_refresh,
&pres_on_evsub_rx_notify,
&pres_on_evsub_client_refresh,
&pres_on_evsub_server_timeout,
};
/*
* Some static constants.
*/
static const pj_str_t STR_EVENT = { "Event", 5 };
static const pj_str_t STR_PRESENCE = { "presence", 8 };
static const pj_str_t STR_APPLICATION = { "application", 11 };
static const pj_str_t STR_PIDF_XML = { "pidf+xml", 8};
static const pj_str_t STR_XPIDF_XML = { "xpidf+xml", 9};
static const pj_str_t STR_APP_PIDF_XML = { "application/pidf+xml", 20 };
static const pj_str_t STR_APP_XPIDF_XML = { "application/xpidf+xml", 21 };
/*
* Init presence module.
*/
PJ_DEF(pj_status_t) pjsip_pres_init_module( pjsip_endpoint *endpt,
pjsip_module *mod_evsub)
{
pj_status_t status;
pj_str_t accept[2];
/* Check arguments. */
PJ_ASSERT_RETURN(endpt && mod_evsub, PJ_EINVAL);
/* Must have not been registered */
PJ_ASSERT_RETURN(mod_presence.id == -1, PJ_EINVALIDOP);
/* Register to endpoint */
status = pjsip_endpt_register_module(endpt, &mod_presence);
if (status != PJ_SUCCESS)
return status;
accept[0] = STR_APP_PIDF_XML;
accept[1] = STR_APP_XPIDF_XML;
/* Register event package to event module. */
status = pjsip_evsub_register_pkg( &mod_presence, &STR_PRESENCE,
PRES_DEFAULT_EXPIRES,
PJ_ARRAY_SIZE(accept), accept);
if (status != PJ_SUCCESS) {
pjsip_endpt_unregister_module(endpt, &mod_presence);
return status;
}
return PJ_SUCCESS;
}
/*
* Get presence module instance.
*/
PJ_DEF(pjsip_module*) pjsip_pres_instance(void)
{
return &mod_presence;
}
/*
* Create client subscription.
*/
PJ_DEF(pj_status_t) pjsip_pres_create_uac( pjsip_dialog *dlg,
const pjsip_evsub_user *user_cb,
unsigned options,
pjsip_evsub **p_evsub )
{
pj_status_t status;
pjsip_pres *pres;
char obj_name[PJ_MAX_OBJ_NAME];
pjsip_evsub *sub;
PJ_ASSERT_RETURN(dlg && p_evsub, PJ_EINVAL);
pjsip_dlg_inc_lock(dlg);
/* Create event subscription */
status = pjsip_evsub_create_uac( dlg, &pres_user, &STR_PRESENCE,
options, &sub);
if (status != PJ_SUCCESS)
goto on_return;
/* Create presence */
pres = PJ_POOL_ZALLOC_T(dlg->pool, pjsip_pres);
pres->dlg = dlg;
pres->sub = sub;
if (user_cb)
pj_memcpy(&pres->user_cb, user_cb, sizeof(pjsip_evsub_user));
pj_ansi_snprintf(obj_name, PJ_MAX_OBJ_NAME, "pres%p", dlg->pool);
pres->status_pool = pj_pool_create(dlg->pool->factory, obj_name,
512, 512, NULL);
pj_ansi_snprintf(obj_name, PJ_MAX_OBJ_NAME, "tmpres%p", dlg->pool);
pres->tmp_pool = pj_pool_create(dlg->pool->factory, obj_name,
512, 512, NULL);
/* Attach to evsub */
pjsip_evsub_set_mod_data(sub, mod_presence.id, pres);
*p_evsub = sub;
on_return:
pjsip_dlg_dec_lock(dlg);
return status;
}
/*
* Create server subscription.
*/
PJ_DEF(pj_status_t) pjsip_pres_create_uas( pjsip_dialog *dlg,
const pjsip_evsub_user *user_cb,
pjsip_rx_data *rdata,
pjsip_evsub **p_evsub )
{
pjsip_accept_hdr *accept;
pjsip_event_hdr *event;
content_type_e content_type = CONTENT_TYPE_NONE;
pjsip_evsub *sub;
pjsip_pres *pres;
char obj_name[PJ_MAX_OBJ_NAME];
pj_status_t status;
/* Check arguments */
PJ_ASSERT_RETURN(dlg && rdata && p_evsub, PJ_EINVAL);
/* Must be request message */
PJ_ASSERT_RETURN(rdata->msg_info.msg->type == PJSIP_REQUEST_MSG,
PJSIP_ENOTREQUESTMSG);
/* Check that request is SUBSCRIBE */
PJ_ASSERT_RETURN(pjsip_method_cmp(&rdata->msg_info.msg->line.req.method,
&pjsip_subscribe_method)==0,
PJSIP_SIMPLE_ENOTSUBSCRIBE);
/* Check that Event header contains "presence" */
event = (pjsip_event_hdr*)
pjsip_msg_find_hdr_by_name(rdata->msg_info.msg, &STR_EVENT, NULL);
if (!event) {
return PJSIP_ERRNO_FROM_SIP_STATUS(PJSIP_SC_BAD_REQUEST);
}
if (pj_stricmp(&event->event_type, &STR_PRESENCE) != 0) {
return PJSIP_ERRNO_FROM_SIP_STATUS(PJSIP_SC_BAD_EVENT);
}
/* Check that request contains compatible Accept header. */
accept = (pjsip_accept_hdr*)
pjsip_msg_find_hdr(rdata->msg_info.msg, PJSIP_H_ACCEPT, NULL);
if (accept) {
unsigned i;
for (i=0; i<accept->count; ++i) {
if (pj_stricmp(&accept->values[i], &STR_APP_PIDF_XML)==0) {
content_type = CONTENT_TYPE_PIDF;
break;
} else
if (pj_stricmp(&accept->values[i], &STR_APP_XPIDF_XML)==0) {
content_type = CONTENT_TYPE_XPIDF;
break;
}
}
if (i==accept->count) {
/* Nothing is acceptable */
return PJSIP_ERRNO_FROM_SIP_STATUS(PJSIP_SC_NOT_ACCEPTABLE);
}
} else {
/* No Accept header.
* Treat as "application/pidf+xml"
*/
content_type = CONTENT_TYPE_PIDF;
}
/* Lock dialog */
pjsip_dlg_inc_lock(dlg);
/* Create server subscription */
status = pjsip_evsub_create_uas( dlg, &pres_user, rdata, 0, &sub);
if (status != PJ_SUCCESS)
goto on_return;
/* Create server presence subscription */
pres = PJ_POOL_ZALLOC_T(dlg->pool, pjsip_pres);
pres->dlg = dlg;
pres->sub = sub;
pres->content_type = content_type;
if (user_cb)
pj_memcpy(&pres->user_cb, user_cb, sizeof(pjsip_evsub_user));
pj_ansi_snprintf(obj_name, PJ_MAX_OBJ_NAME, "pres%p", dlg->pool);
pres->status_pool = pj_pool_create(dlg->pool->factory, obj_name,
512, 512, NULL);
pj_ansi_snprintf(obj_name, PJ_MAX_OBJ_NAME, "tmpres%p", dlg->pool);
pres->tmp_pool = pj_pool_create(dlg->pool->factory, obj_name,
512, 512, NULL);
/* Attach to evsub */
pjsip_evsub_set_mod_data(sub, mod_presence.id, pres);
/* Done: */
*p_evsub = sub;
on_return:
pjsip_dlg_dec_lock(dlg);
return status;
}
/*
* Forcefully terminate presence.
*/
PJ_DEF(pj_status_t) pjsip_pres_terminate( pjsip_evsub *sub,
pj_bool_t notify )
{
return pjsip_evsub_terminate(sub, notify);
}
/*
* Create SUBSCRIBE
*/
PJ_DEF(pj_status_t) pjsip_pres_initiate( pjsip_evsub *sub,
pj_uint32_t expires,
pjsip_tx_data **p_tdata)
{
return pjsip_evsub_initiate(sub, &pjsip_subscribe_method, expires,
p_tdata);
}
/*
* Add custom headers.
*/
PJ_DEF(pj_status_t) pjsip_pres_add_header( pjsip_evsub *sub,
const pjsip_hdr *hdr_list )
{
return pjsip_evsub_add_header( sub, hdr_list );
}
/*
* Accept incoming subscription.
*/
PJ_DEF(pj_status_t) pjsip_pres_accept( pjsip_evsub *sub,
pjsip_rx_data *rdata,
int st_code,
const pjsip_hdr *hdr_list )
{
return pjsip_evsub_accept( sub, rdata, st_code, hdr_list );
}
/*
* Get presence status.
*/
PJ_DEF(pj_status_t) pjsip_pres_get_status( pjsip_evsub *sub,
pjsip_pres_status *status )
{
pjsip_pres *pres;
PJ_ASSERT_RETURN(sub && status, PJ_EINVAL);
pres = (pjsip_pres*) pjsip_evsub_get_mod_data(sub, mod_presence.id);
PJ_ASSERT_RETURN(pres!=NULL, PJSIP_SIMPLE_ENOPRESENCE);
if (pres->tmp_status._is_valid) {
PJ_ASSERT_RETURN(pres->tmp_pool!=NULL, PJSIP_SIMPLE_ENOPRESENCE);
pj_memcpy(status, &pres->tmp_status, sizeof(pjsip_pres_status));
} else {
PJ_ASSERT_RETURN(pres->status_pool!=NULL, PJSIP_SIMPLE_ENOPRESENCE);
pj_memcpy(status, &pres->status, sizeof(pjsip_pres_status));
}
return PJ_SUCCESS;
}
/*
* Set presence status.
*/
PJ_DEF(pj_status_t) pjsip_pres_set_status( pjsip_evsub *sub,
const pjsip_pres_status *status )
{
unsigned i;
pj_pool_t *tmp;
pjsip_pres *pres;
PJ_ASSERT_RETURN(sub && status, PJ_EINVAL);
pres = (pjsip_pres*) pjsip_evsub_get_mod_data(sub, mod_presence.id);
PJ_ASSERT_RETURN(pres!=NULL, PJSIP_SIMPLE_ENOPRESENCE);
for (i=0; i<status->info_cnt; ++i) {
pres->status.info[i].basic_open = status->info[i].basic_open;
if (pres->status.info[i].id.slen) {
/* Id already set */
} else if (status->info[i].id.slen == 0) {
pj_create_unique_string(pres->dlg->pool,
&pres->status.info[i].id);
} else {
pj_strdup(pres->dlg->pool,
&pres->status.info[i].id,
&status->info[i].id);
}
pj_strdup(pres->tmp_pool,
&pres->status.info[i].contact,
&status->info[i].contact);
/* Duplicate <person> */
pres->status.info[i].rpid.activity =
status->info[i].rpid.activity;
pj_strdup(pres->tmp_pool,
&pres->status.info[i].rpid.id,
&status->info[i].rpid.id);
pj_strdup(pres->tmp_pool,
&pres->status.info[i].rpid.note,
&status->info[i].rpid.note);
}
pres->status.info_cnt = status->info_cnt;
/* Swap pools */
tmp = pres->tmp_pool;
pres->tmp_pool = pres->status_pool;
pres->status_pool = tmp;
pj_pool_reset(pres->tmp_pool);
return PJ_SUCCESS;
}
/*
* Create message body.
*/
static pj_status_t pres_create_msg_body( pjsip_pres *pres,
pjsip_tx_data *tdata)
{
pj_str_t entity;
/* Get publisher URI */
entity.ptr = (char*) pj_pool_alloc(tdata->pool, PJSIP_MAX_URL_SIZE);
entity.slen = pjsip_uri_print(PJSIP_URI_IN_REQ_URI,
pres->dlg->local.info->uri,
entity.ptr, PJSIP_MAX_URL_SIZE);
if (entity.slen < 1)
return PJ_ENOMEM;
if (pres->content_type == CONTENT_TYPE_PIDF) {
return pjsip_pres_create_pidf(tdata->pool, &pres->status,
&entity, &tdata->msg->body);
} else if (pres->content_type == CONTENT_TYPE_XPIDF) {
return pjsip_pres_create_xpidf(tdata->pool, &pres->status,
&entity, &tdata->msg->body);
} else {
return PJSIP_SIMPLE_EBADCONTENT;
}
}
/*
* Create NOTIFY
*/
PJ_DEF(pj_status_t) pjsip_pres_notify( pjsip_evsub *sub,
pjsip_evsub_state state,
const pj_str_t *state_str,
const pj_str_t *reason,
pjsip_tx_data **p_tdata)
{
pjsip_pres *pres;
pjsip_tx_data *tdata;
pj_status_t status;
/* Check arguments. */
PJ_ASSERT_RETURN(sub, PJ_EINVAL);
/* Get the presence object. */
pres = (pjsip_pres*) pjsip_evsub_get_mod_data(sub, mod_presence.id);
PJ_ASSERT_RETURN(pres != NULL, PJSIP_SIMPLE_ENOPRESENCE);
/* Must have at least one presence info, unless state is
* PJSIP_EVSUB_STATE_TERMINATED. This could happen if subscription
* has not been active (e.g. we're waiting for user authorization)
* and remote cancels the subscription.
*/
PJ_ASSERT_RETURN(state==PJSIP_EVSUB_STATE_TERMINATED ||
pres->status.info_cnt > 0, PJSIP_SIMPLE_ENOPRESENCEINFO);
/* Lock object. */
pjsip_dlg_inc_lock(pres->dlg);
/* Create the NOTIFY request. */
status = pjsip_evsub_notify( sub, state, state_str, reason, &tdata);
if (status != PJ_SUCCESS)
goto on_return;
/* Create message body to reflect the presence status.
* Only do this if we have presence status info to send (see above).
*/
if (pres->status.info_cnt > 0) {
status = pres_create_msg_body( pres, tdata );
if (status != PJ_SUCCESS)
goto on_return;
}
/* Done. */
*p_tdata = tdata;
on_return:
pjsip_dlg_dec_lock(pres->dlg);
return status;
}
/*
* Create NOTIFY that reflect current state.
*/
PJ_DEF(pj_status_t) pjsip_pres_current_notify( pjsip_evsub *sub,
pjsip_tx_data **p_tdata )
{
pjsip_pres *pres;
pjsip_tx_data *tdata;
pj_status_t status;
/* Check arguments. */
PJ_ASSERT_RETURN(sub, PJ_EINVAL);
/* Get the presence object. */
pres = (pjsip_pres*) pjsip_evsub_get_mod_data(sub, mod_presence.id);
PJ_ASSERT_RETURN(pres != NULL, PJSIP_SIMPLE_ENOPRESENCE);
/* We may not have a presence info yet, e.g. when we receive SUBSCRIBE
* to refresh subscription while we're waiting for user authorization.
*/
//PJ_ASSERT_RETURN(pres->status.info_cnt > 0,
// PJSIP_SIMPLE_ENOPRESENCEINFO);
/* Lock object. */
pjsip_dlg_inc_lock(pres->dlg);
/* Create the NOTIFY request. */
status = pjsip_evsub_current_notify( sub, &tdata);
if (status != PJ_SUCCESS)
goto on_return;
/* Create message body to reflect the presence status. */
if (pres->status.info_cnt > 0) {
status = pres_create_msg_body( pres, tdata );
if (status != PJ_SUCCESS)
goto on_return;
}
/* Done. */
*p_tdata = tdata;
on_return:
pjsip_dlg_dec_lock(pres->dlg);
return status;
}
/*
* Send request.
*/
PJ_DEF(pj_status_t) pjsip_pres_send_request( pjsip_evsub *sub,
pjsip_tx_data *tdata )
{
return pjsip_evsub_send_request(sub, tdata);
}
/*
* This callback is called by event subscription when subscription
* state has changed.
*/
static void pres_on_evsub_state( pjsip_evsub *sub, pjsip_event *event)
{
pjsip_pres *pres;
pres = (pjsip_pres*) pjsip_evsub_get_mod_data(sub, mod_presence.id);
PJ_ASSERT_ON_FAIL(pres!=NULL, {return;});
if (pres->user_cb.on_evsub_state)
(*pres->user_cb.on_evsub_state)(sub, event);
if (pjsip_evsub_get_state(sub) == PJSIP_EVSUB_STATE_TERMINATED) {
if (pres->status_pool) {
pj_pool_release(pres->status_pool);
pres->status_pool = NULL;
}
if (pres->tmp_pool) {
pj_pool_release(pres->tmp_pool);
pres->tmp_pool = NULL;
}
}
}
/*
* Called when transaction state has changed.
*/
static void pres_on_evsub_tsx_state( pjsip_evsub *sub, pjsip_transaction *tsx,
pjsip_event *event)
{
pjsip_pres *pres;
pres = (pjsip_pres*) pjsip_evsub_get_mod_data(sub, mod_presence.id);
PJ_ASSERT_ON_FAIL(pres!=NULL, {return;});
if (pres->user_cb.on_tsx_state)
(*pres->user_cb.on_tsx_state)(sub, tsx, event);
}
/*
* Called when SUBSCRIBE is received.
*/
static void pres_on_evsub_rx_refresh( 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;
pres = (pjsip_pres*) pjsip_evsub_get_mod_data(sub, mod_presence.id);
PJ_ASSERT_ON_FAIL(pres!=NULL, {return;});
if (pres->user_cb.on_rx_refresh) {
(*pres->user_cb.on_rx_refresh)(sub, rdata, p_st_code, p_st_text,
res_hdr, p_body);
} else {
/* Implementors MUST send NOTIFY if it implements on_rx_refresh */
pjsip_tx_data *tdata;
pj_str_t timeout = { "timeout", 7};
pj_status_t status;
if (pjsip_evsub_get_state(sub)==PJSIP_EVSUB_STATE_TERMINATED) {
status = pjsip_pres_notify( sub, PJSIP_EVSUB_STATE_TERMINATED,
NULL, &timeout, &tdata);
} else {
status = pjsip_pres_current_notify(sub, &tdata);
}
if (status == PJ_SUCCESS)
pjsip_pres_send_request(sub, tdata);
}
}
/*
* Process the content of incoming NOTIFY request and update temporary
* status.
*
* return PJ_SUCCESS if incoming request is acceptable. If return value
* is not PJ_SUCCESS, res_hdr may be added with Warning header.
*/
static pj_status_t pres_process_rx_notify( pjsip_pres *pres,
pjsip_rx_data *rdata,
int *p_st_code,
pj_str_t **p_st_text,
pjsip_hdr *res_hdr)
{
const pj_str_t STR_MULTIPART = { "multipart", 9 };
pjsip_ctype_hdr *ctype_hdr;
pj_status_t status = PJ_SUCCESS;
*p_st_text = NULL;
/* Check Content-Type and msg body are present. */
ctype_hdr = rdata->msg_info.ctype;
if (ctype_hdr==NULL || rdata->msg_info.msg->body==NULL) {
pjsip_warning_hdr *warn_hdr;
pj_str_t warn_text;
*p_st_code = PJSIP_SC_BAD_REQUEST;
warn_text = pj_str("Message body is not present");
warn_hdr = pjsip_warning_hdr_create(rdata->tp_info.pool, 399,
pjsip_endpt_name(pres->dlg->endpt),
&warn_text);
pj_list_push_back(res_hdr, warn_hdr);
return PJSIP_ERRNO_FROM_SIP_STATUS(PJSIP_SC_BAD_REQUEST);
}
/* Parse content. */
if (pj_stricmp(&ctype_hdr->media.type, &STR_MULTIPART)==0) {
pjsip_multipart_part *mpart;
pjsip_media_type ctype;
pjsip_media_type_init(&ctype, (pj_str_t*)&STR_APPLICATION,
(pj_str_t*)&STR_PIDF_XML);
mpart = pjsip_multipart_find_part(rdata->msg_info.msg->body,
&ctype, NULL);
if (mpart) {
status = pjsip_pres_parse_pidf2((char*)mpart->body->data,
mpart->body->len, pres->tmp_pool,
&pres->tmp_status);
}
if (mpart==NULL) {
pjsip_media_type_init(&ctype, (pj_str_t*)&STR_APPLICATION,
(pj_str_t*)&STR_XPIDF_XML);
mpart = pjsip_multipart_find_part(rdata->msg_info.msg->body,
&ctype, NULL);
if (mpart) {
status = pjsip_pres_parse_xpidf2((char*)mpart->body->data,
mpart->body->len,
pres->tmp_pool,
&pres->tmp_status);
} else {
status = PJSIP_SIMPLE_EBADCONTENT;
}
}
}
else
if (pj_stricmp(&ctype_hdr->media.type, &STR_APPLICATION)==0 &&
pj_stricmp(&ctype_hdr->media.subtype, &STR_PIDF_XML)==0)
{
status = pjsip_pres_parse_pidf( rdata, pres->tmp_pool,
&pres->tmp_status);
}
else
if (pj_stricmp(&ctype_hdr->media.type, &STR_APPLICATION)==0 &&
pj_stricmp(&ctype_hdr->media.subtype, &STR_XPIDF_XML)==0)
{
status = pjsip_pres_parse_xpidf( rdata, pres->tmp_pool,
&pres->tmp_status);
}
else
{
status = PJSIP_SIMPLE_EBADCONTENT;
}
if (status != PJ_SUCCESS) {
/* Unsupported or bad Content-Type */
if (PJSIP_PRES_BAD_CONTENT_RESPONSE >= 300) {
pjsip_accept_hdr *accept_hdr;
pjsip_warning_hdr *warn_hdr;
*p_st_code = PJSIP_PRES_BAD_CONTENT_RESPONSE;
/* Add Accept header */
accept_hdr = pjsip_accept_hdr_create(rdata->tp_info.pool);
accept_hdr->values[accept_hdr->count++] = STR_APP_PIDF_XML;
accept_hdr->values[accept_hdr->count++] = STR_APP_XPIDF_XML;
pj_list_push_back(res_hdr, accept_hdr);
/* Add Warning header */
warn_hdr = pjsip_warning_hdr_create_from_status(
rdata->tp_info.pool,
pjsip_endpt_name(pres->dlg->endpt),
status);
pj_list_push_back(res_hdr, warn_hdr);
return status;
} else {
pj_assert(PJSIP_PRES_BAD_CONTENT_RESPONSE/100 == 2);
PJ_PERROR(4,(THIS_FILE, status,
"Ignoring presence error due to "
"PJSIP_PRES_BAD_CONTENT_RESPONSE setting [%d]",
PJSIP_PRES_BAD_CONTENT_RESPONSE));
*p_st_code = PJSIP_PRES_BAD_CONTENT_RESPONSE;
status = PJ_SUCCESS;
}
}
/* If application calls pres_get_status(), redirect the call to
* retrieve the temporary status.
*/
pres->tmp_status._is_valid = PJ_TRUE;
return PJ_SUCCESS;
}
/*
* Called when NOTIFY is received.
*/
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_pres*) pjsip_evsub_get_mod_data(sub, mod_presence.id);
PJ_ASSERT_ON_FAIL(pres!=NULL, {return;});
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 {
#if 1
/* This is the newest change, http://trac.pjsip.org/repos/ticket/873
* Some app want to be notified about the empty NOTIFY, e.g. to
* decide whether it should consider the buddy as offline.
* In this case, leave the buddy state unchanged, but set the
* "tuple_node" in pjsip_pres_status to NULL.
*/
unsigned i;
for (i=0; i<pres->status.info_cnt; ++i) {
pres->status.info[i].tuple_node = NULL;
}
#elif 0
/* This has just been changed. Previously, we treat incoming NOTIFY
* with no message body as having the presence subscription closed.
* Now we treat it as no change in presence status (ref: EyeBeam).
*/
*p_st_code = 200;
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;
#endif
}
/* Notify application. */
if (pres->user_cb.on_rx_notify) {
(*pres->user_cb.on_rx_notify)(sub, rdata, p_st_code, p_st_text,
res_hdr, p_body);
}
/* If application responded NOTIFY with 2xx, copy temporary status
* to main status, and mark the temporary status as invalid.
*/
if ((*p_st_code)/100 == 2) {
pj_pool_t *tmp;
pj_memcpy(&pres->status, &pres->tmp_status, sizeof(pjsip_pres_status));
/* Swap the pool */
tmp = pres->tmp_pool;
pres->tmp_pool = pres->status_pool;
pres->status_pool = tmp;
}
pres->tmp_status._is_valid = PJ_FALSE;
pj_pool_reset(pres->tmp_pool);
/* Done */
}
/*
* Called when it's time to send SUBSCRIBE.
*/
static void pres_on_evsub_client_refresh(pjsip_evsub *sub)
{
pjsip_pres *pres;
pres = (pjsip_pres*) pjsip_evsub_get_mod_data(sub, mod_presence.id);
PJ_ASSERT_ON_FAIL(pres!=NULL, {return;});
if (pres->user_cb.on_client_refresh) {
(*pres->user_cb.on_client_refresh)(sub);
} else {
pj_status_t status;
pjsip_tx_data *tdata;
status = pjsip_pres_initiate(sub, PJSIP_EXPIRES_NOT_SPECIFIED,
&tdata);
if (status == PJ_SUCCESS)
pjsip_pres_send_request(sub, tdata);
}
}
/*
* Called when no refresh is received after the interval.
*/
static void pres_on_evsub_server_timeout(pjsip_evsub *sub)
{
pjsip_pres *pres;
pres = (pjsip_pres*) pjsip_evsub_get_mod_data(sub, mod_presence.id);
PJ_ASSERT_ON_FAIL(pres!=NULL, {return;});
if (pres->user_cb.on_server_timeout) {
(*pres->user_cb.on_server_timeout)(sub);
} else {
pj_status_t status;
pjsip_tx_data *tdata;
pj_str_t reason = { "timeout", 7 };
status = pjsip_pres_notify(sub, PJSIP_EVSUB_STATE_TERMINATED,
NULL, &reason, &tdata);
if (status == PJ_SUCCESS)
pjsip_pres_send_request(sub, tdata);
}
}
@@ -0,0 +1,297 @@
/* $Id$ */
/*
* Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
* Copyright (C) 2003-2008 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/presence.h>
#include <pjsip-simple/errno.h>
#include <pjsip/sip_msg.h>
#include <pjsip/sip_transport.h>
#include <pj/guid.h>
#include <pj/log.h>
#include <pj/os.h>
#include <pj/pool.h>
#include <pj/string.h>
#define THIS_FILE "presence_body.c"
static const pj_str_t STR_APPLICATION = { "application", 11 };
static const pj_str_t STR_PIDF_XML = { "pidf+xml", 8 };
static const pj_str_t STR_XPIDF_XML = { "xpidf+xml", 9 };
/*
* Function to print XML message body.
*/
static int pres_print_body(struct pjsip_msg_body *msg_body,
char *buf, pj_size_t size)
{
return pj_xml_print((const pj_xml_node*)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, (const pj_xml_node*) data);
}
/*
* This is a utility function to create PIDF message body from PJSIP
* presence status (pjsip_pres_status).
*/
PJ_DEF(pj_status_t) pjsip_pres_create_pidf( pj_pool_t *pool,
const pjsip_pres_status *status,
const pj_str_t *entity,
pjsip_msg_body **p_body )
{
pjpidf_pres *pidf;
pjsip_msg_body *body;
unsigned i;
/* Create <presence>. */
pidf = pjpidf_create(pool, entity);
/* Create <tuple> */
for (i=0; i<status->info_cnt; ++i) {
pjpidf_tuple *pidf_tuple;
pjpidf_status *pidf_status;
pj_str_t id;
/* Add tuple id. */
if (status->info[i].id.slen == 0) {
/* xs:ID must start with letter */
//pj_create_unique_string(pool, &id);
id.ptr = (char*)pj_pool_alloc(pool, pj_GUID_STRING_LENGTH()+2);
id.ptr += 2;
pj_generate_unique_string(&id);
id.ptr -= 2;
id.ptr[0] = 'p';
id.ptr[1] = 'j';
id.slen += 2;
} else {
id = status->info[i].id;
}
pidf_tuple = pjpidf_pres_add_tuple(pool, pidf, &id);
/* Set <contact> */
if (status->info[i].contact.slen)
pjpidf_tuple_set_contact(pool, pidf_tuple,
&status->info[i].contact);
/* Set basic status */
pidf_status = pjpidf_tuple_get_status(pidf_tuple);
pjpidf_status_set_basic_open(pidf_status,
status->info[i].basic_open);
/* Add <timestamp> if configured */
#if defined(PJSIP_PRES_PIDF_ADD_TIMESTAMP) && PJSIP_PRES_PIDF_ADD_TIMESTAMP
if (PJSIP_PRES_PIDF_ADD_TIMESTAMP) {
char buf[50];
int tslen = 0;
pj_time_val tv;
pj_parsed_time pt;
pj_status_t s;
pj_gettimeofday(&tv);
/* Convert time to GMT (some platforms may not support it
* such as WinCE).
*/
s = pj_time_local_to_gmt(&tv);
if (s != PJ_SUCCESS) {
PJ_PERROR(4,(THIS_FILE,s,
"Warning: failed to convert PIDF time to GMT"));
}
pj_time_decode( &tv, &pt);
tslen = pj_ansi_snprintf(buf, sizeof(buf),
"%04d-%02d-%02dT%02d:%02d:%02d.%03dZ",
pt.year, pt.mon+1, pt.day,
pt.hour, pt.min, pt.sec, pt.msec);
if (tslen > 0 && tslen < (int)sizeof(buf)) {
pj_str_t time = pj_str(buf);
pjpidf_tuple_set_timestamp(pool, pidf_tuple, &time);
}
}
#endif
}
/* Create <person> (RPID) */
if (status->info_cnt) {
pjrpid_add_element(pidf, pool, 0, &status->info[0].rpid);
}
body = PJ_POOL_ZALLOC_T(pool, pjsip_msg_body);
body->data = pidf;
body->content_type.type = STR_APPLICATION;
body->content_type.subtype = STR_PIDF_XML;
body->print_body = &pres_print_body;
body->clone_data = &xml_clone_data;
*p_body = body;
return PJ_SUCCESS;
}
/*
* This is a utility function to create X-PIDF message body from PJSIP
* presence status (pjsip_pres_status).
*/
PJ_DEF(pj_status_t) pjsip_pres_create_xpidf( pj_pool_t *pool,
const pjsip_pres_status *status,
const pj_str_t *entity,
pjsip_msg_body **p_body )
{
/* Note: PJSIP implementation of XPIDF is not complete!
*/
pjxpidf_pres *xpidf;
pjsip_msg_body *body;
PJ_LOG(4,(THIS_FILE, "Warning: XPIDF format is not fully supported "
"by PJSIP"));
/* Create XPIDF document. */
xpidf = pjxpidf_create(pool, entity);
/* Set basic status. */
if (status->info_cnt > 0)
pjxpidf_set_status( xpidf, status->info[0].basic_open);
else
pjxpidf_set_status( xpidf, PJ_FALSE);
body = PJ_POOL_ZALLOC_T(pool, pjsip_msg_body);
body->data = xpidf;
body->content_type.type = STR_APPLICATION;
body->content_type.subtype = STR_XPIDF_XML;
body->print_body = &pres_print_body;
body->clone_data = &xml_clone_data;
*p_body = body;
return PJ_SUCCESS;
}
/*
* This is a utility function to parse PIDF body into PJSIP presence status.
*/
PJ_DEF(pj_status_t) pjsip_pres_parse_pidf( pjsip_rx_data *rdata,
pj_pool_t *pool,
pjsip_pres_status *pres_status)
{
return pjsip_pres_parse_pidf2((char*)rdata->msg_info.msg->body->data,
rdata->msg_info.msg->body->len,
pool, pres_status);
}
PJ_DEF(pj_status_t) pjsip_pres_parse_pidf2(char *body, unsigned body_len,
pj_pool_t *pool,
pjsip_pres_status *pres_status)
{
pjpidf_pres *pidf;
pjpidf_tuple *pidf_tuple;
pidf = pjpidf_parse(pool, body, body_len);
if (pidf == NULL)
return PJSIP_SIMPLE_EBADPIDF;
pres_status->info_cnt = 0;
pidf_tuple = pjpidf_pres_get_first_tuple(pidf);
while (pidf_tuple && pres_status->info_cnt < PJSIP_PRES_STATUS_MAX_INFO) {
pjpidf_status *pidf_status;
pres_status->info[pres_status->info_cnt].tuple_node =
pj_xml_clone(pool, pidf_tuple);
pj_strdup(pool,
&pres_status->info[pres_status->info_cnt].id,
pjpidf_tuple_get_id(pidf_tuple));
pj_strdup(pool,
&pres_status->info[pres_status->info_cnt].contact,
pjpidf_tuple_get_contact(pidf_tuple));
pidf_status = pjpidf_tuple_get_status(pidf_tuple);
if (pidf_status) {
pres_status->info[pres_status->info_cnt].basic_open =
pjpidf_status_is_basic_open(pidf_status);
} else {
pres_status->info[pres_status->info_cnt].basic_open = PJ_FALSE;
}
pidf_tuple = pjpidf_pres_get_next_tuple( pidf, pidf_tuple );
pres_status->info_cnt++;
}
/* Parse <person> (RPID) */
pjrpid_get_element(pidf, pool, &pres_status->info[0].rpid);
return PJ_SUCCESS;
}
/*
* This is a utility function to parse X-PIDF body into PJSIP presence status.
*/
PJ_DEF(pj_status_t) pjsip_pres_parse_xpidf(pjsip_rx_data *rdata,
pj_pool_t *pool,
pjsip_pres_status *pres_status)
{
return pjsip_pres_parse_xpidf2((char*)rdata->msg_info.msg->body->data,
rdata->msg_info.msg->body->len,
pool, pres_status);
}
PJ_DEF(pj_status_t) pjsip_pres_parse_xpidf2(char *body, unsigned body_len,
pj_pool_t *pool,
pjsip_pres_status *pres_status)
{
pjxpidf_pres *xpidf;
xpidf = pjxpidf_parse(pool, body, body_len);
if (xpidf == NULL)
return PJSIP_SIMPLE_EBADXPIDF;
pres_status->info_cnt = 1;
pj_strdup(pool,
&pres_status->info[0].contact,
pjxpidf_get_uri(xpidf));
pres_status->info[0].basic_open = pjxpidf_get_status(xpidf);
pres_status->info[0].id.slen = 0;
pres_status->info[0].tuple_node = NULL;
return PJ_SUCCESS;
}
@@ -0,0 +1,814 @@
/* $Id$ */
/*
* Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
* Copyright (C) 2003-2008 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/publish.h>
#include <pjsip/sip_auth.h>
#include <pjsip/sip_endpoint.h>
#include <pjsip/sip_errno.h>
#include <pjsip/sip_event.h>
#include <pjsip/sip_msg.h>
#include <pjsip/sip_transaction.h>
#include <pjsip/sip_uri.h>
#include <pjsip/sip_util.h>
#include <pj/assert.h>
#include <pj/guid.h>
#include <pj/log.h>
#include <pj/os.h>
#include <pj/pool.h>
#include <pj/rand.h>
#include <pj/string.h>
#include <pj/timer.h>
#define REFRESH_TIMER 1
#define DELAY_BEFORE_REFRESH PJSIP_PUBLISHC_DELAY_BEFORE_REFRESH
#define THIS_FILE "publishc.c"
/* Let's define this enum, so that it'll trigger compilation error
* when somebody define the same enum in sip_msg.h
*/
enum
{
PJSIP_PUBLISH_METHOD = PJSIP_OTHER_METHOD,
};
const pjsip_method pjsip_publish_method =
{
(pjsip_method_e)PJSIP_PUBLISH_METHOD,
{ "PUBLISH", 7 }
};
/**
* Pending request list.
*/
typedef struct pending_publish
{
PJ_DECL_LIST_MEMBER(struct pending_publish);
pjsip_tx_data *tdata;
} pending_publish;
/**
* SIP client publication structure.
*/
struct pjsip_publishc
{
pj_pool_t *pool;
pjsip_endpoint *endpt;
pj_bool_t _delete_flag;
int pending_tsx;
pj_bool_t in_callback;
pj_mutex_t *mutex;
pjsip_publishc_opt opt;
void *token;
pjsip_publishc_cb *cb;
pj_str_t event;
pj_str_t str_target_uri;
pjsip_uri *target_uri;
pjsip_cid_hdr *cid_hdr;
pjsip_cseq_hdr *cseq_hdr;
pj_str_t from_uri;
pjsip_from_hdr *from_hdr;
pjsip_to_hdr *to_hdr;
pj_str_t etag;
pjsip_expires_hdr *expires_hdr;
pj_uint32_t expires;
pjsip_route_hdr route_set;
pjsip_hdr usr_hdr;
pjsip_host_port via_addr;
const void *via_tp;
/* Authorization sessions. */
pjsip_auth_clt_sess auth_sess;
/* Auto refresh publication. */
pj_bool_t auto_refresh;
pj_time_val last_refresh;
pj_time_val next_refresh;
pj_timer_entry timer;
/* Pending PUBLISH request */
pending_publish pending_reqs;
pending_publish pending_reqs_empty;
};
PJ_DEF(void) pjsip_publishc_opt_default(pjsip_publishc_opt *opt)
{
pj_bzero(opt, sizeof(*opt));
opt->queue_request = PJSIP_PUBLISHC_QUEUE_REQUEST;
}
/*
* Initialize client publication module.
*/
PJ_DEF(pj_status_t) pjsip_publishc_init_module(pjsip_endpoint *endpt)
{
/* Note:
Commented out the capability registration below, since it's
wrong to include PUBLISH in Allow header of INVITE requests/
responses.
13.2.1 Creating the Initial INVITE
An Allow header field (Section 20.5) SHOULD be present in the
INVITE. It indicates what methods can be invoked within a dialog
20.5 Allow
The Allow header field lists the set of methods supported by the
UA generating the message.
While the semantic of Allow header in non-dialog requests is unclear,
it's probably best not to include PUBLISH in Allow header for now
until we can find out how to customize the inclusion of methods in
Allow header for in-dialog vs out-dialog requests.
return pjsip_endpt_add_capability( endpt, NULL, PJSIP_H_ALLOW, NULL,
1, &pjsip_publish_method.name);
*/
PJ_UNUSED_ARG(endpt);
return PJ_SUCCESS;
}
PJ_DEF(pj_status_t) pjsip_publishc_create( pjsip_endpoint *endpt,
const pjsip_publishc_opt *opt,
void *token,
pjsip_publishc_cb *cb,
pjsip_publishc **p_pubc)
{
pj_pool_t *pool;
pjsip_publishc *pubc;
pjsip_publishc_opt default_opt;
pj_status_t status;
/* Verify arguments. */
PJ_ASSERT_RETURN(endpt && cb && p_pubc, PJ_EINVAL);
pool = pjsip_endpt_create_pool(endpt, "pubc%p", 1024, 1024);
PJ_ASSERT_RETURN(pool != NULL, PJ_ENOMEM);
pubc = PJ_POOL_ZALLOC_T(pool, pjsip_publishc);
pubc->pool = pool;
pubc->endpt = endpt;
pubc->token = token;
pubc->cb = cb;
pubc->expires = PJSIP_PUBC_EXPIRATION_NOT_SPECIFIED;
if (!opt) {
pjsip_publishc_opt_default(&default_opt);
opt = &default_opt;
}
pj_memcpy(&pubc->opt, opt, sizeof(*opt));
pj_list_init(&pubc->pending_reqs);
pj_list_init(&pubc->pending_reqs_empty);
status = pj_mutex_create_recursive(pubc->pool, "pubc%p", &pubc->mutex);
if (status != PJ_SUCCESS) {
pj_pool_release(pool);
return status;
}
status = pjsip_auth_clt_init(&pubc->auth_sess, endpt, pubc->pool, 0);
if (status != PJ_SUCCESS) {
pj_mutex_destroy(pubc->mutex);
pj_pool_release(pool);
return status;
}
pj_list_init(&pubc->route_set);
pj_list_init(&pubc->usr_hdr);
/* Done */
*p_pubc = pubc;
return PJ_SUCCESS;
}
PJ_DEF(pj_status_t) pjsip_publishc_destroy(pjsip_publishc *pubc)
{
PJ_ASSERT_RETURN(pubc, PJ_EINVAL);
if (pubc->pending_tsx || pubc->in_callback) {
pubc->_delete_flag = 1;
pubc->cb = NULL;
} else {
/* Cancel existing timer, if any */
if (pubc->timer.id != 0) {
pjsip_endpt_cancel_timer(pubc->endpt, &pubc->timer);
pubc->timer.id = 0;
}
if (pubc->mutex)
pj_mutex_destroy(pubc->mutex);
pjsip_auth_clt_deinit(&pubc->auth_sess);
pjsip_endpt_release_pool(pubc->endpt, pubc->pool);
}
return PJ_SUCCESS;
}
PJ_DEF(pj_pool_t*) pjsip_publishc_get_pool(pjsip_publishc *pubc)
{
return pubc->pool;
}
static void set_expires( pjsip_publishc *pubc, pj_uint32_t expires)
{
if (expires != pubc->expires &&
expires != PJSIP_PUBC_EXPIRATION_NOT_SPECIFIED)
{
pubc->expires_hdr = pjsip_expires_hdr_create(pubc->pool, expires);
} else {
pubc->expires_hdr = NULL;
}
}
PJ_DEF(pj_status_t) pjsip_publishc_init(pjsip_publishc *pubc,
const pj_str_t *event,
const pj_str_t *target_uri,
const pj_str_t *from_uri,
const pj_str_t *to_uri,
pj_uint32_t expires)
{
pj_str_t tmp;
PJ_ASSERT_RETURN(pubc && event && target_uri && from_uri && to_uri &&
expires, PJ_EINVAL);
/* Copy event type */
pj_strdup_with_null(pubc->pool, &pubc->event, event);
/* Copy server URL. */
pj_strdup_with_null(pubc->pool, &pubc->str_target_uri, target_uri);
/* Set server URL. */
tmp = pubc->str_target_uri;
pubc->target_uri = pjsip_parse_uri( pubc->pool, tmp.ptr, tmp.slen, 0);
if (pubc->target_uri == NULL) {
return PJSIP_EINVALIDURI;
}
/* Set "From" header. */
pj_strdup_with_null(pubc->pool, &pubc->from_uri, from_uri);
tmp = pubc->from_uri;
pubc->from_hdr = pjsip_from_hdr_create(pubc->pool);
pubc->from_hdr->uri = pjsip_parse_uri(pubc->pool, tmp.ptr, tmp.slen,
PJSIP_PARSE_URI_AS_NAMEADDR);
if (!pubc->from_hdr->uri) {
return PJSIP_EINVALIDURI;
}
/* Set "To" header. */
pj_strdup_with_null(pubc->pool, &tmp, to_uri);
pubc->to_hdr = pjsip_to_hdr_create(pubc->pool);
pubc->to_hdr->uri = pjsip_parse_uri(pubc->pool, tmp.ptr, tmp.slen,
PJSIP_PARSE_URI_AS_NAMEADDR);
if (!pubc->to_hdr->uri) {
return PJSIP_EINVALIDURI;
}
/* Set "Expires" header, if required. */
set_expires( pubc, expires);
/* Set "Call-ID" header. */
pubc->cid_hdr = pjsip_cid_hdr_create(pubc->pool);
pj_create_unique_string(pubc->pool, &pubc->cid_hdr->id);
/* Set "CSeq" header. */
pubc->cseq_hdr = pjsip_cseq_hdr_create(pubc->pool);
pubc->cseq_hdr->cseq = pj_rand() % 0xFFFF;
pjsip_method_set( &pubc->cseq_hdr->method, PJSIP_REGISTER_METHOD);
/* Done. */
return PJ_SUCCESS;
}
PJ_DEF(pj_status_t) pjsip_publishc_set_credentials( pjsip_publishc *pubc,
int count,
const pjsip_cred_info cred[] )
{
PJ_ASSERT_RETURN(pubc && count && cred, PJ_EINVAL);
return pjsip_auth_clt_set_credentials(&pubc->auth_sess, count, cred);
}
PJ_DEF(pj_status_t) pjsip_publishc_set_route_set( pjsip_publishc *pubc,
const pjsip_route_hdr *route_set)
{
const pjsip_route_hdr *chdr;
PJ_ASSERT_RETURN(pubc && route_set, PJ_EINVAL);
pj_list_init(&pubc->route_set);
chdr = route_set->next;
while (chdr != route_set) {
pj_list_push_back(&pubc->route_set, pjsip_hdr_clone(pubc->pool, chdr));
chdr = chdr->next;
}
return PJ_SUCCESS;
}
PJ_DEF(pj_status_t) pjsip_publishc_set_headers( pjsip_publishc *pubc,
const pjsip_hdr *hdr_list)
{
const pjsip_hdr *h;
PJ_ASSERT_RETURN(pubc && hdr_list, PJ_EINVAL);
pj_list_init(&pubc->usr_hdr);
h = hdr_list->next;
while (h != hdr_list) {
pj_list_push_back(&pubc->usr_hdr, pjsip_hdr_clone(pubc->pool, h));
h = h->next;
}
return PJ_SUCCESS;
}
PJ_DEF(pj_status_t) pjsip_publishc_set_via_sent_by(pjsip_publishc *pubc,
pjsip_host_port *via_addr,
pjsip_transport *via_tp)
{
PJ_ASSERT_RETURN(pubc, PJ_EINVAL);
if (!via_addr)
pj_bzero(&pubc->via_addr, sizeof(pubc->via_addr));
else {
if (pj_strcmp(&pubc->via_addr.host, &via_addr->host))
pj_strdup(pubc->pool, &pubc->via_addr.host, &via_addr->host);
pubc->via_addr.port = via_addr->port;
}
pubc->via_tp = via_tp;
return PJ_SUCCESS;
}
static pj_status_t create_request(pjsip_publishc *pubc,
pjsip_tx_data **p_tdata)
{
const pj_str_t STR_EVENT = { "Event", 5 };
pj_status_t status;
pjsip_generic_string_hdr *hdr;
pjsip_tx_data *tdata;
PJ_ASSERT_RETURN(pubc && p_tdata, PJ_EINVAL);
/* Create the request. */
status = pjsip_endpt_create_request_from_hdr( pubc->endpt,
&pjsip_publish_method,
pubc->target_uri,
pubc->from_hdr,
pubc->to_hdr,
NULL,
pubc->cid_hdr,
pubc->cseq_hdr->cseq,
NULL,
&tdata);
if (status != PJ_SUCCESS)
return status;
/* Add cached authorization headers. */
pjsip_auth_clt_init_req( &pubc->auth_sess, tdata );
/* Add Route headers from route set, ideally after Via header */
if (!pj_list_empty(&pubc->route_set)) {
pjsip_hdr *route_pos;
const pjsip_route_hdr *route;
route_pos = (pjsip_hdr*)
pjsip_msg_find_hdr(tdata->msg, PJSIP_H_VIA, NULL);
if (!route_pos)
route_pos = &tdata->msg->hdr;
route = pubc->route_set.next;
while (route != &pubc->route_set) {
pjsip_hdr *new_hdr = (pjsip_hdr*)
pjsip_hdr_shallow_clone(tdata->pool, route);
pj_list_insert_after(route_pos, new_hdr);
route_pos = new_hdr;
route = route->next;
}
}
/* Add Event header */
hdr = pjsip_generic_string_hdr_create(tdata->pool, &STR_EVENT,
&pubc->event);
if (hdr)
pjsip_msg_add_hdr(tdata->msg, (pjsip_hdr*)hdr);
/* Add SIP-If-Match if we have etag */
if (pubc->etag.slen) {
const pj_str_t STR_HNAME = { "SIP-If-Match", 12 };
hdr = pjsip_generic_string_hdr_create(tdata->pool, &STR_HNAME,
&pubc->etag);
if (hdr)
pjsip_msg_add_hdr(tdata->msg, (pjsip_hdr*)hdr);
}
/* Add user headers */
if (!pj_list_empty(&pubc->usr_hdr)) {
const pjsip_hdr *uhdr;
uhdr = pubc->usr_hdr.next;
while (uhdr != &pubc->usr_hdr) {
pjsip_hdr *new_hdr = (pjsip_hdr*)
pjsip_hdr_shallow_clone(tdata->pool, uhdr);
pjsip_msg_add_hdr(tdata->msg, new_hdr);
uhdr = uhdr->next;
}
}
/* Done. */
*p_tdata = tdata;
return PJ_SUCCESS;
}
PJ_DEF(pj_status_t) pjsip_publishc_publish(pjsip_publishc *pubc,
pj_bool_t auto_refresh,
pjsip_tx_data **p_tdata)
{
pj_status_t status;
pjsip_tx_data *tdata;
PJ_ASSERT_RETURN(pubc && p_tdata, PJ_EINVAL);
status = create_request(pubc, &tdata);
if (status != PJ_SUCCESS)
return status;
/* Add Expires header */
if (pubc->expires_hdr) {
pjsip_hdr *dup;
dup = (pjsip_hdr*)
pjsip_hdr_shallow_clone(tdata->pool, pubc->expires_hdr);
if (dup)
pjsip_msg_add_hdr(tdata->msg, dup);
}
/* Cancel existing timer */
if (pubc->timer.id != 0) {
pjsip_endpt_cancel_timer(pubc->endpt, &pubc->timer);
pubc->timer.id = 0;
}
pubc->auto_refresh = auto_refresh;
/* Done */
*p_tdata = tdata;
return PJ_SUCCESS;
}
PJ_DEF(pj_status_t) pjsip_publishc_unpublish(pjsip_publishc *pubc,
pjsip_tx_data **p_tdata)
{
pjsip_tx_data *tdata;
pjsip_msg *msg;
pjsip_expires_hdr *expires;
pj_status_t status;
PJ_ASSERT_RETURN(pubc && p_tdata, PJ_EINVAL);
if (pubc->timer.id != 0) {
pjsip_endpt_cancel_timer(pubc->endpt, &pubc->timer);
pubc->timer.id = 0;
}
status = create_request(pubc, &tdata);
if (status != PJ_SUCCESS)
return status;
msg = tdata->msg;
/* Add Expires:0 header */
expires = pjsip_expires_hdr_create(tdata->pool, 0);
pjsip_msg_add_hdr( msg, (pjsip_hdr*)expires);
*p_tdata = tdata;
return PJ_SUCCESS;
}
PJ_DEF(pj_status_t) pjsip_publishc_update_expires( pjsip_publishc *pubc,
pj_uint32_t expires )
{
PJ_ASSERT_RETURN(pubc, PJ_EINVAL);
set_expires( pubc, expires );
return PJ_SUCCESS;
}
static void call_callback(pjsip_publishc *pubc, pj_status_t status,
int st_code, const pj_str_t *reason,
pjsip_rx_data *rdata, pj_uint32_t expiration)
{
struct pjsip_publishc_cbparam cbparam;
cbparam.pubc = pubc;
cbparam.token = pubc->token;
cbparam.status = status;
cbparam.code = st_code;
cbparam.reason = *reason;
cbparam.rdata = rdata;
cbparam.expiration = expiration;
(*pubc->cb)(&cbparam);
}
static void pubc_refresh_timer_cb( pj_timer_heap_t *timer_heap,
struct pj_timer_entry *entry)
{
pjsip_publishc *pubc = (pjsip_publishc*) entry->user_data;
pjsip_tx_data *tdata;
pj_status_t status;
PJ_UNUSED_ARG(timer_heap);
entry->id = 0;
status = pjsip_publishc_publish(pubc, 1, &tdata);
if (status != PJ_SUCCESS) {
char errmsg[PJ_ERR_MSG_SIZE];
pj_str_t reason = pj_strerror(status, errmsg, sizeof(errmsg));
call_callback(pubc, status, 400, &reason, NULL,
PJSIP_PUBC_EXPIRATION_NOT_SPECIFIED);
return;
}
status = pjsip_publishc_send(pubc, tdata);
/* No need to call callback as it should have been called */
}
static void tsx_callback(void *token, pjsip_event *event)
{
pj_status_t status;
pjsip_publishc *pubc = (pjsip_publishc*) token;
pjsip_transaction *tsx = event->body.tsx_state.tsx;
/* Decrement pending transaction counter. */
pj_assert(pubc->pending_tsx > 0);
--pubc->pending_tsx;
/* Mark that we're in callback to prevent deletion (#1164) */
++pubc->in_callback;
/* If publication data has been deleted by user then remove publication
* data from transaction's callback, and don't call callback.
*/
if (pubc->_delete_flag) {
/* Nothing to do */
;
} else if (tsx->status_code == PJSIP_SC_PROXY_AUTHENTICATION_REQUIRED ||
tsx->status_code == PJSIP_SC_UNAUTHORIZED)
{
pjsip_rx_data *rdata = event->body.tsx_state.src.rdata;
pjsip_tx_data *tdata;
status = pjsip_auth_clt_reinit_req( &pubc->auth_sess,
rdata,
tsx->last_tx,
&tdata);
if (status != PJ_SUCCESS) {
call_callback(pubc, status, tsx->status_code,
&rdata->msg_info.msg->line.status.reason,
rdata, PJSIP_PUBC_EXPIRATION_NOT_SPECIFIED);
} else {
status = pjsip_publishc_send(pubc, tdata);
}
} else {
pjsip_rx_data *rdata;
pj_uint32_t expiration = PJSIP_PUBC_EXPIRATION_NOT_SPECIFIED;
if (tsx->status_code/100 == 2) {
pjsip_msg *msg;
pjsip_expires_hdr *expires;
pjsip_generic_string_hdr *etag_hdr;
const pj_str_t STR_ETAG = { "SIP-ETag", 8 };
rdata = event->body.tsx_state.src.rdata;
msg = rdata->msg_info.msg;
/* Save ETag value */
etag_hdr = (pjsip_generic_string_hdr*)
pjsip_msg_find_hdr_by_name(msg, &STR_ETAG, NULL);
if (etag_hdr) {
pj_strdup(pubc->pool, &pubc->etag, &etag_hdr->hvalue);
} else {
pubc->etag.slen = 0;
}
/* Update expires value */
expires = (pjsip_expires_hdr*)
pjsip_msg_find_hdr(msg, PJSIP_H_EXPIRES, NULL);
if (pubc->auto_refresh && expires)
expiration = expires->ivalue;
if (pubc->auto_refresh && expiration!=0 &&
expiration!=PJSIP_PUBC_EXPIRATION_NOT_SPECIFIED)
{
pj_time_val delay = { 0, 0};
/* Cancel existing timer, if any */
if (pubc->timer.id != 0) {
pjsip_endpt_cancel_timer(pubc->endpt, &pubc->timer);
pubc->timer.id = 0;
}
delay.sec = expiration - DELAY_BEFORE_REFRESH;
if (pubc->expires != PJSIP_PUBC_EXPIRATION_NOT_SPECIFIED &&
delay.sec > (pj_int32_t)pubc->expires)
{
delay.sec = pubc->expires;
}
if (delay.sec < DELAY_BEFORE_REFRESH)
delay.sec = DELAY_BEFORE_REFRESH;
pubc->timer.cb = &pubc_refresh_timer_cb;
pubc->timer.id = REFRESH_TIMER;
pubc->timer.user_data = pubc;
pjsip_endpt_schedule_timer( pubc->endpt, &pubc->timer, &delay);
pj_gettimeofday(&pubc->last_refresh);
pubc->next_refresh = pubc->last_refresh;
pubc->next_refresh.sec += delay.sec;
}
} else {
rdata = (event->body.tsx_state.type==PJSIP_EVENT_RX_MSG) ?
event->body.tsx_state.src.rdata : NULL;
}
/* Call callback. */
// if (expiration == 0xFFFF) expiration = -1;
/* Temporarily increment pending_tsx to prevent callback from
* destroying pubc.
*/
++pubc->pending_tsx;
call_callback(pubc, PJ_SUCCESS, tsx->status_code,
(rdata ? &rdata->msg_info.msg->line.status.reason
: pjsip_get_status_text(tsx->status_code)),
rdata, expiration);
--pubc->pending_tsx;
/* If we have pending request(s), send them now */
pj_mutex_lock(pubc->mutex);
while (!pj_list_empty(&pubc->pending_reqs)) {
pending_publish *pp = pubc->pending_reqs.next;
pjsip_tx_data *tdata = pp->tdata;
/* Remove the request from pending request list,
* and keep the unused entry into pending_reqs_empty pool.
*/
pj_list_erase(pp);
pj_list_push_back(&pubc->pending_reqs_empty, pp);
/* Add SIP-If-Match if we have etag and the request doesn't have
* one (http://trac.pjsip.org/repos/ticket/996)
*/
if (pubc->etag.slen) {
const pj_str_t STR_HNAME = { "SIP-If-Match", 12 };
pjsip_generic_string_hdr *sim_hdr;
sim_hdr = (pjsip_generic_string_hdr*)
pjsip_msg_find_hdr_by_name(tdata->msg, &STR_HNAME, NULL);
if (!sim_hdr) {
/* Create the header */
sim_hdr = pjsip_generic_string_hdr_create(tdata->pool,
&STR_HNAME,
&pubc->etag);
pjsip_msg_add_hdr(tdata->msg, (pjsip_hdr*)sim_hdr);
} else {
/* Update */
if (pj_strcmp(&pubc->etag, &sim_hdr->hvalue))
pj_strdup(tdata->pool, &sim_hdr->hvalue, &pubc->etag);
}
}
status = pjsip_publishc_send(pubc, tdata);
if (status == PJ_EPENDING) {
pj_assert(!"Not expected");
pjsip_tx_data_dec_ref(tdata);
} else if (status == PJ_SUCCESS) {
break;
}
}
pj_mutex_unlock(pubc->mutex);
}
/* No longer in callback. */
--pubc->in_callback;
/* Delete the record if user destroy pubc during the callback. */
if (pubc->_delete_flag && pubc->pending_tsx==0) {
pjsip_publishc_destroy(pubc);
}
}
PJ_DEF(pj_status_t) pjsip_publishc_send(pjsip_publishc *pubc,
pjsip_tx_data *tdata)
{
pj_status_t status;
pjsip_cseq_hdr *cseq_hdr;
pj_uint32_t cseq;
PJ_ASSERT_RETURN(pubc && tdata, PJ_EINVAL);
/* Make sure we don't have pending transaction. */
pj_mutex_lock(pubc->mutex);
if (pubc->pending_tsx) {
if (pubc->opt.queue_request) {
pending_publish *pp = NULL;
if (pj_list_empty(&pubc->pending_reqs_empty)) {
pp = PJ_POOL_ZALLOC_T(pubc->pool, pending_publish);
} else {
pp = pubc->pending_reqs_empty.next;
pj_list_erase(pp);
}
pp->tdata = tdata;
pj_list_push_back(&pubc->pending_reqs, pp);
pj_mutex_unlock(pubc->mutex);
PJ_LOG(4,(THIS_FILE, "Request is queued, pubc has another "
"transaction pending"));
return PJ_EPENDING;
} else {
pjsip_tx_data_dec_ref(tdata);
pj_mutex_unlock(pubc->mutex);
PJ_LOG(4,(THIS_FILE, "Unable to send request, pubc has another "
"transaction pending"));
return PJ_EBUSY;
}
}
pj_mutex_unlock(pubc->mutex);
/* If via_addr is set, use this address for the Via header. */
if (pubc->via_addr.host.slen > 0) {
tdata->via_addr = pubc->via_addr;
tdata->via_tp = pubc->via_tp;
}
/* Invalidate message buffer. */
pjsip_tx_data_invalidate_msg(tdata);
/* Increment CSeq */
cseq = ++pubc->cseq_hdr->cseq;
cseq_hdr = (pjsip_cseq_hdr*)
pjsip_msg_find_hdr(tdata->msg, PJSIP_H_CSEQ, NULL);
cseq_hdr->cseq = cseq;
/* Increment pending transaction first, since transaction callback
* may be called even before send_request() returns!
*/
++pubc->pending_tsx;
status = pjsip_endpt_send_request(pubc->endpt, tdata, -1, pubc,
&tsx_callback);
if (status!=PJ_SUCCESS) {
// no need to decrement, callback has been called and it should
// already decremented pending_tsx. Decrementing this here may
// cause accessing freed memory location.
//--pubc->pending_tsx;
PJ_PERROR(4,(THIS_FILE, status, "Error sending request"));
}
return status;
}
@@ -0,0 +1,278 @@
/* $Id$ */
/*
* Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
* Copyright (C) 2003-2008 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/rpid.h>
#include <pjsip-simple/errno.h>
#include <pj/assert.h>
#include <pj/guid.h>
#include <pj/pool.h>
#include <pj/string.h>
static const pj_str_t DM_NAME = {"xmlns:dm", 8};
static const pj_str_t DM_VAL = {"urn:ietf:params:xml:ns:pidf:data-model", 38};
static const pj_str_t RPID_NAME = {"xmlns:rpid", 10};
static const pj_str_t RPID_VAL = {"urn:ietf:params:xml:ns:pidf:rpid", 32};
static const pj_str_t DM_NOTE = {"dm:note", 7};
static const pj_str_t DM_PERSON = {"dm:person", 9};
static const pj_str_t ID = {"id", 2};
static const pj_str_t NOTE = {"note", 4};
static const pj_str_t RPID_ACTIVITIES = {"rpid:activities", 15};
static const pj_str_t RPID_AWAY = {"rpid:away", 9};
static const pj_str_t RPID_BUSY = {"rpid:busy", 9};
static const pj_str_t RPID_UNKNOWN = {"rpid:unknown", 12};
/* Duplicate RPID element */
PJ_DEF(void) pjrpid_element_dup(pj_pool_t *pool, pjrpid_element *dst,
const pjrpid_element *src)
{
pj_memcpy(dst, src, sizeof(pjrpid_element));
pj_strdup(pool, &dst->id, &src->id);
pj_strdup(pool, &dst->note, &src->note);
}
/* Update RPID namespaces. */
static void update_namespaces(pjpidf_pres *pres,
pj_pool_t *pool)
{
/* Check if namespace is already present. */
if (pj_xml_find_attr(pres, &DM_NAME, NULL) != NULL)
return;
pj_xml_add_attr(pres, pj_xml_attr_new(pool, &DM_NAME, &DM_VAL));
pj_xml_add_attr(pres, pj_xml_attr_new(pool, &RPID_NAME, &RPID_VAL));
}
/* Comparison function to find node name substring */
static pj_bool_t substring_match(const pj_xml_node *node,
const char *part_name,
pj_ssize_t part_len)
{
pj_str_t end_name;
if (part_len < 1)
part_len = pj_ansi_strlen(part_name);
if (node->name.slen < part_len)
return PJ_FALSE;
end_name.ptr = node->name.ptr + (node->name.slen - part_len);
end_name.slen = part_len;
return pj_strnicmp2(&end_name, part_name, part_len)==0;
}
/* Util to find child node with the specified substring */
static pj_xml_node *find_node(const pj_xml_node *parent,
const char *part_name)
{
const pj_xml_node *node = parent->node_head.next,
*head = (pj_xml_node*) &parent->node_head;
pj_ssize_t part_len = pj_ansi_strlen(part_name);
while (node != head) {
if (substring_match(node, part_name, part_len))
return (pj_xml_node*) node;
node = node->next;
}
return NULL;
}
/*
* Add RPID element into existing PIDF document.
*/
PJ_DEF(pj_status_t) pjrpid_add_element(pjpidf_pres *pres,
pj_pool_t *pool,
unsigned options,
const pjrpid_element *elem)
{
pj_xml_node *nd_person, *nd_activities, *nd_activity, *nd_note;
pj_xml_attr *attr;
PJ_ASSERT_RETURN(pres && pool && options==0 && elem, PJ_EINVAL);
PJ_UNUSED_ARG(options);
/* Add <note> to <tuple>, if none */
if (elem->note.slen != 0) {
pj_xml_node *nd_tuple;
nd_tuple = find_node(pres, "tuple");
nd_note = nd_tuple? find_node(nd_tuple, "note"):NULL;
if (!nd_note) {
nd_note = pj_xml_node_new(pool, &NOTE);
pj_strdup(pool, &nd_note->content, &elem->note);
pj_xml_add_node(nd_tuple, nd_note);
nd_note = NULL;
}
}
/* Check if we need to add RPID information into the PIDF document. */
if (elem->id.slen==0 &&
elem->activity==PJRPID_ACTIVITY_UNKNOWN)
{
/* No RPID information to be added. */
return PJ_SUCCESS;
}
/* Update namespace */
update_namespaces(pres, pool);
/* Add <person> */
nd_person = pj_xml_node_new(pool, &DM_PERSON);
if (elem->id.slen != 0) {
attr = pj_xml_attr_new(pool, &ID, &elem->id);
} else {
pj_str_t person_id;
/* xs:ID must start with letter */
//pj_create_unique_string(pool, &person_id);
person_id.ptr = (char*)pj_pool_alloc(pool, pj_GUID_STRING_LENGTH()+2);
person_id.ptr += 2;
pj_generate_unique_string(&person_id);
person_id.ptr -= 2;
person_id.ptr[0] = 'p';
person_id.ptr[1] = 'j';
person_id.slen += 2;
attr = pj_xml_attr_new(pool, &ID, &person_id);
}
pj_xml_add_attr(nd_person, attr);
pj_xml_add_node(pres, nd_person);
/* Add <activities> */
nd_activities = pj_xml_node_new(pool, &RPID_ACTIVITIES);
pj_xml_add_node(nd_person, nd_activities);
/* Add the activity */
switch (elem->activity) {
case PJRPID_ACTIVITY_AWAY:
nd_activity = pj_xml_node_new(pool, &RPID_AWAY);
break;
case PJRPID_ACTIVITY_BUSY:
nd_activity = pj_xml_node_new(pool, &RPID_BUSY);
break;
case PJRPID_ACTIVITY_UNKNOWN:
default:
nd_activity = pj_xml_node_new(pool, &RPID_UNKNOWN);
break;
}
pj_xml_add_node(nd_activities, nd_activity);
/* Add custom text if required. */
if (elem->note.slen != 0) {
nd_note = pj_xml_node_new(pool, &DM_NOTE);
pj_strdup(pool, &nd_note->content, &elem->note);
pj_xml_add_node(nd_person, nd_note);
}
/* Done */
return PJ_SUCCESS;
}
/* Get <note> element from PIDF <tuple> element */
static pj_status_t get_tuple_note(const pjpidf_pres *pres,
pj_pool_t *pool,
pjrpid_element *elem)
{
const pj_xml_node *nd_tuple, *nd_note;
nd_tuple = find_node(pres, "tuple");
if (!nd_tuple)
return PJSIP_SIMPLE_EBADRPID;
nd_note = find_node(nd_tuple, "note");
if (nd_note) {
pj_strdup(pool, &elem->note, &nd_note->content);
return PJ_SUCCESS;
}
return PJSIP_SIMPLE_EBADRPID;
}
/*
* Get RPID element from PIDF document, if any.
*/
PJ_DEF(pj_status_t) pjrpid_get_element(const pjpidf_pres *pres,
pj_pool_t *pool,
pjrpid_element *elem)
{
const pj_xml_node *nd_person, *nd_activities, *nd_note = NULL;
const pj_xml_attr *attr;
/* Reset */
pj_bzero(elem, sizeof(*elem));
elem->activity = PJRPID_ACTIVITY_UNKNOWN;
/* Find <person> */
nd_person = find_node(pres, "person");
if (!nd_person) {
/* <person> not found, try to get <note> from <tuple> */
return get_tuple_note(pres, pool, elem);
}
/* Get element id attribute */
attr = pj_xml_find_attr((pj_xml_node*)nd_person, &ID, NULL);
if (attr)
pj_strdup(pool, &elem->id, &attr->value);
/* Get <activities> */
nd_activities = find_node(nd_person, "activities");
if (nd_activities) {
const pj_xml_node *nd_activity;
/* Try to get <note> from <activities> */
nd_note = find_node(nd_activities, "note");
/* Get the activity */
nd_activity = nd_activities->node_head.next;
if (nd_activity == nd_note)
nd_activity = nd_activity->next;
if (nd_activity != (pj_xml_node*) &nd_activities->node_head) {
if (substring_match(nd_activity, "busy", -1))
elem->activity = PJRPID_ACTIVITY_BUSY;
else if (substring_match(nd_activity, "away", -1))
elem->activity = PJRPID_ACTIVITY_AWAY;
else
elem->activity = PJRPID_ACTIVITY_UNKNOWN;
}
}
/* If <note> is not found, get <note> from <person> */
if (nd_note == NULL)
nd_note = find_node(nd_person, "note");
if (nd_note) {
pj_strdup(pool, &elem->note, &nd_note->content);
} else {
get_tuple_note(pres, pool, elem);
}
return PJ_SUCCESS;
}
@@ -0,0 +1,301 @@
/* $Id$ */
/*
* Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
* Copyright (C) 2003-2008 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/xpidf.h>
#include <pj/assert.h>
#include <pj/guid.h>
#include <pj/pool.h>
#include <pj/string.h>
static pj_str_t STR_PRESENCE = { "presence", 8 };
static pj_str_t STR_STATUS = { "status", 6 };
static pj_str_t STR_OPEN = { "open", 4 };
static pj_str_t STR_CLOSED = { "closed", 6 };
static pj_str_t STR_URI = { "uri", 3 };
static pj_str_t STR_ATOM = { "atom", 4 };
static pj_str_t STR_ATOMID = { "atomid", 6 };
static pj_str_t STR_ID = { "id", 2 };
static pj_str_t STR_ADDRESS = { "address", 7 };
static pj_str_t STR_SUBSCRIBE_PARAM = { ";method=SUBSCRIBE", 17 };
static pj_str_t STR_PRESENTITY = { "presentity", 10 };
static pj_str_t STR_EMPTY_STRING = { NULL, 0 };
static pj_xml_node* xml_create_node(pj_pool_t *pool,
pj_str_t *name, const pj_str_t *value)
{
pj_xml_node *node;
node = PJ_POOL_ALLOC_T(pool, pj_xml_node);
pj_list_init(&node->attr_head);
pj_list_init(&node->node_head);
node->name = *name;
if (value) pj_strdup(pool, &node->content, value);
else node->content.ptr=NULL, node->content.slen=0;
return node;
}
static pj_xml_attr* xml_create_attr(pj_pool_t *pool, pj_str_t *name,
const pj_str_t *value)
{
pj_xml_attr *attr = PJ_POOL_ALLOC_T(pool, pj_xml_attr);
attr->name = *name;
pj_strdup(pool, &attr->value, value);
return attr;
}
PJ_DEF(pjxpidf_pres*) pjxpidf_create(pj_pool_t *pool, const pj_str_t *uri_cstr)
{
pjxpidf_pres *pres;
pj_xml_node *presentity;
pj_xml_node *atom;
pj_xml_node *addr;
pj_xml_node *status;
pj_xml_attr *attr;
pj_str_t uri;
pj_str_t tmp;
/* <presence> */
pres = xml_create_node(pool, &STR_PRESENCE, NULL);
/* <presentity> */
presentity = xml_create_node(pool, &STR_PRESENTITY, NULL);
pj_xml_add_node(pres, presentity);
/* uri attribute */
uri.ptr = (char*) pj_pool_alloc(pool, uri_cstr->slen +
STR_SUBSCRIBE_PARAM.slen);
pj_strcpy( &uri, uri_cstr);
pj_strcat( &uri, &STR_SUBSCRIBE_PARAM);
attr = xml_create_attr(pool, &STR_URI, &uri);
pj_xml_add_attr(presentity, attr);
/* <atom> */
atom = xml_create_node(pool, &STR_ATOM, NULL);
pj_xml_add_node(pres, atom);
/* atom id */
pj_create_unique_string(pool, &tmp);
attr = xml_create_attr(pool, &STR_ATOMID, &tmp);
pj_xml_add_attr(atom, attr);
/* address */
addr = xml_create_node(pool, &STR_ADDRESS, NULL);
pj_xml_add_node(atom, addr);
/* address'es uri */
attr = xml_create_attr(pool, &STR_URI, uri_cstr);
pj_xml_add_attr(addr, attr);
/* status */
status = xml_create_node(pool, &STR_STATUS, NULL);
pj_xml_add_node(addr, status);
/* status attr */
attr = xml_create_attr(pool, &STR_STATUS, &STR_OPEN);
pj_xml_add_attr(status, attr);
return pres;
}
PJ_DEF(pjxpidf_pres*) pjxpidf_parse(pj_pool_t *pool, char *text, pj_size_t len)
{
pjxpidf_pres *pres;
pj_xml_node *node;
pres = pj_xml_parse(pool, text, len);
if (!pres)
return NULL;
/* Validate <presence> */
if (pj_stricmp(&pres->name, &STR_PRESENCE) != 0)
return NULL;
/* Validate <presentity> */
node = pj_xml_find_node(pres, &STR_PRESENTITY);
if (node == NULL)
return NULL;
if (pj_xml_find_attr(node, &STR_URI, NULL) == NULL)
return NULL;
/* Validate <atom> */
node = pj_xml_find_node(pres, &STR_ATOM);
if (node == NULL)
return NULL;
if (pj_xml_find_attr(node, &STR_ATOMID, NULL) == NULL &&
pj_xml_find_attr(node, &STR_ID, NULL) == NULL)
{
return NULL;
}
/* Address */
node = pj_xml_find_node(node, &STR_ADDRESS);
if (node == NULL)
return NULL;
if (pj_xml_find_attr(node, &STR_URI, NULL) == NULL)
return NULL;
/* Status */
node = pj_xml_find_node(node, &STR_STATUS);
if (node == NULL)
return NULL;
if (pj_xml_find_attr(node, &STR_STATUS, NULL) == NULL)
return NULL;
return pres;
}
PJ_DEF(int) pjxpidf_print( pjxpidf_pres *pres, char *text, pj_size_t len)
{
return pj_xml_print(pres, text, len, PJ_TRUE);
}
PJ_DEF(pj_str_t*) pjxpidf_get_uri(pjxpidf_pres *pres)
{
pj_xml_node *presentity;
pj_xml_attr *attr;
presentity = pj_xml_find_node(pres, &STR_PRESENTITY);
if (!presentity)
return &STR_EMPTY_STRING;
attr = pj_xml_find_attr(presentity, &STR_URI, NULL);
if (!attr)
return &STR_EMPTY_STRING;
return &attr->value;
}
PJ_DEF(pj_status_t) pjxpidf_set_uri(pj_pool_t *pool, pjxpidf_pres *pres,
const pj_str_t *uri)
{
pj_xml_node *presentity;
pj_xml_node *atom;
pj_xml_node *addr;
pj_xml_attr *attr;
pj_str_t dup_uri;
presentity = pj_xml_find_node(pres, &STR_PRESENTITY);
if (!presentity) {
pj_assert(0);
return -1;
}
atom = pj_xml_find_node(pres, &STR_ATOM);
if (!atom) {
pj_assert(0);
return -1;
}
addr = pj_xml_find_node(atom, &STR_ADDRESS);
if (!addr) {
pj_assert(0);
return -1;
}
/* Set uri in presentity */
attr = pj_xml_find_attr(presentity, &STR_URI, NULL);
if (!attr) {
pj_assert(0);
return -1;
}
pj_strdup(pool, &dup_uri, uri);
attr->value = dup_uri;
/* Set uri in address. */
attr = pj_xml_find_attr(addr, &STR_URI, NULL);
if (!attr) {
pj_assert(0);
return -1;
}
attr->value = dup_uri;
return 0;
}
PJ_DEF(pj_bool_t) pjxpidf_get_status(pjxpidf_pres *pres)
{
pj_xml_node *atom;
pj_xml_node *addr;
pj_xml_node *status;
pj_xml_attr *attr;
atom = pj_xml_find_node(pres, &STR_ATOM);
if (!atom) {
pj_assert(0);
return PJ_FALSE;
}
addr = pj_xml_find_node(atom, &STR_ADDRESS);
if (!addr) {
pj_assert(0);
return PJ_FALSE;
}
status = pj_xml_find_node(addr, &STR_STATUS);
if (!status) {
pj_assert(0);
return PJ_FALSE;
}
attr = pj_xml_find_attr(status, &STR_STATUS, NULL);
if (!attr) {
pj_assert(0);
return PJ_FALSE;
}
return pj_stricmp(&attr->value, &STR_OPEN)==0 ? PJ_TRUE : PJ_FALSE;
}
PJ_DEF(pj_status_t) pjxpidf_set_status(pjxpidf_pres *pres, pj_bool_t online_status)
{
pj_xml_node *atom;
pj_xml_node *addr;
pj_xml_node *status;
pj_xml_attr *attr;
atom = pj_xml_find_node(pres, &STR_ATOM);
if (!atom) {
pj_assert(0);
return -1;
}
addr = pj_xml_find_node(atom, &STR_ADDRESS);
if (!addr) {
pj_assert(0);
return -1;
}
status = pj_xml_find_node(addr, &STR_STATUS);
if (!status) {
pj_assert(0);
return -1;
}
attr = pj_xml_find_attr(status, &STR_STATUS, NULL);
if (!attr) {
pj_assert(0);
return -1;
}
attr->value = ( online_status ? STR_OPEN : STR_CLOSED );
return 0;
}
@@ -0,0 +1,882 @@
/* $Id$ */
/*
* Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
* Copyright (C) 2003-2008 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-ua/sip_100rel.h>
#include <pjsip/sip_endpoint.h>
#include <pjsip/sip_event.h>
#include <pjsip/sip_module.h>
#include <pjsip/sip_transaction.h>
#include <pj/assert.h>
#include <pj/ctype.h>
#include <pj/log.h>
#include <pj/os.h>
#include <pj/pool.h>
#include <pj/rand.h>
#include <pj/string.h>
#define THIS_FILE "sip_100rel.c"
/* PRACK method */
PJ_DEF_DATA(const pjsip_method) pjsip_prack_method =
{
PJSIP_OTHER_METHOD,
{ "PRACK", 5 }
};
typedef struct dlg_data dlg_data;
/*
* Static prototypes.
*/
static pj_status_t mod_100rel_load(pjsip_endpoint *endpt);
static void on_retransmit(pj_timer_heap_t *timer_heap,
struct pj_timer_entry *entry);
static const pj_str_t tag_100rel = { "100rel", 6 };
static const pj_str_t RSEQ = { "RSeq", 4 };
static const pj_str_t RACK = { "RAck", 4 };
/* 100rel module */
static struct mod_100rel
{
pjsip_module mod;
pjsip_endpoint *endpt;
} mod_100rel =
{
{
NULL, NULL, /* prev, next. */
{ "mod-100rel", 10 }, /* Name. */
-1, /* Id */
PJSIP_MOD_PRIORITY_DIALOG_USAGE, /* Priority */
&mod_100rel_load, /* load() */
NULL, /* start() */
NULL, /* stop() */
NULL, /* unload() */
NULL, /* on_rx_request() */
NULL, /* on_rx_response() */
NULL, /* on_tx_request. */
NULL, /* on_tx_response() */
NULL, /* on_tsx_state() */
}
};
/* List of pending transmission (may include the final response as well) */
typedef struct tx_data_list_t
{
PJ_DECL_LIST_MEMBER(struct tx_data_list_t);
pj_uint32_t rseq;
pjsip_tx_data *tdata;
} tx_data_list_t;
/* Below, UAS and UAC roles are of the INVITE transaction */
/* UAS state. */
typedef struct uas_state_t
{
pj_int32_t cseq;
pj_uint32_t rseq; /* Initialized to -1 */
tx_data_list_t tx_data_list;
unsigned retransmit_count;
pj_timer_entry retransmit_timer;
} uas_state_t;
/* UAC state */
typedef struct uac_state_t
{
pj_str_t tag; /* To tag */
pj_int32_t cseq;
pj_uint32_t rseq; /* Initialized to -1 */
struct uac_state_t *next; /* next call leg */
} uac_state_t;
/* State attached to each dialog. */
struct dlg_data
{
pjsip_inv_session *inv;
uas_state_t *uas_state;
uac_state_t *uac_state_list;
};
/*****************************************************************************
**
** Module
**
*****************************************************************************
*/
static pj_status_t mod_100rel_load(pjsip_endpoint *endpt)
{
mod_100rel.endpt = endpt;
pjsip_endpt_add_capability(endpt, &mod_100rel.mod,
PJSIP_H_ALLOW, NULL,
1, &pjsip_prack_method.name);
pjsip_endpt_add_capability(endpt, &mod_100rel.mod,
PJSIP_H_SUPPORTED, NULL,
1, &tag_100rel);
return PJ_SUCCESS;
}
static pjsip_require_hdr *find_req_hdr(pjsip_msg *msg)
{
pjsip_require_hdr *hreq;
hreq = (pjsip_require_hdr*)
pjsip_msg_find_hdr(msg, PJSIP_H_REQUIRE, NULL);
while (hreq) {
unsigned i;
for (i=0; i<hreq->count; ++i) {
if (!pj_stricmp(&hreq->values[i], &tag_100rel)) {
return hreq;
}
}
if ((void*)hreq->next == (void*)&msg->hdr)
return NULL;
hreq = (pjsip_require_hdr*)
pjsip_msg_find_hdr(msg, PJSIP_H_REQUIRE, hreq->next);
}
return NULL;
}
/*
* Get PRACK method constant.
*/
PJ_DEF(const pjsip_method*) pjsip_get_prack_method(void)
{
return &pjsip_prack_method;
}
/*
* init module
*/
PJ_DEF(pj_status_t) pjsip_100rel_init_module(pjsip_endpoint *endpt)
{
if (mod_100rel.mod.id != -1)
return PJ_SUCCESS;
return pjsip_endpt_register_module(endpt, &mod_100rel.mod);
}
/*
* API: attach 100rel support in invite session. Called by
* sip_inv.c
*/
PJ_DEF(pj_status_t) pjsip_100rel_attach(pjsip_inv_session *inv)
{
dlg_data *dd;
/* Check that 100rel module has been initialized */
PJ_ASSERT_RETURN(mod_100rel.mod.id >= 0, PJ_EINVALIDOP);
/* Create and attach as dialog usage */
dd = PJ_POOL_ZALLOC_T(inv->dlg->pool, dlg_data);
dd->inv = inv;
pjsip_dlg_add_usage(inv->dlg, &mod_100rel.mod, (void*)dd);
PJ_LOG(5,(dd->inv->dlg->obj_name, "100rel module attached"));
return PJ_SUCCESS;
}
/*
* Check if incoming response has reliable provisional response feature.
*/
PJ_DEF(pj_bool_t) pjsip_100rel_is_reliable(pjsip_rx_data *rdata)
{
pjsip_msg *msg = rdata->msg_info.msg;
PJ_ASSERT_RETURN(msg->type == PJSIP_RESPONSE_MSG, PJ_FALSE);
return msg->line.status.code > 100 && msg->line.status.code < 200 &&
rdata->msg_info.require != NULL &&
find_req_hdr(msg) != NULL;
}
/*
* Create PRACK request for the incoming reliable provisional response.
*/
PJ_DEF(pj_status_t) pjsip_100rel_create_prack( pjsip_inv_session *inv,
pjsip_rx_data *rdata,
pjsip_tx_data **p_tdata)
{
dlg_data *dd;
uac_state_t *uac_state = NULL;
const pj_str_t *to_tag = &rdata->msg_info.to->tag;
pjsip_transaction *tsx;
pjsip_msg *msg;
pjsip_generic_string_hdr *rseq_hdr;
pjsip_generic_string_hdr *rack_hdr;
unsigned rseq;
pj_str_t rack;
char rack_buf[80];
pjsip_tx_data *tdata;
pj_status_t status;
*p_tdata = NULL;
dd = (dlg_data*) inv->dlg->mod_data[mod_100rel.mod.id];
PJ_ASSERT_RETURN(dd != NULL, PJSIP_ENOTINITIALIZED);
tsx = pjsip_rdata_get_tsx(rdata);
msg = rdata->msg_info.msg;
/* Check our assumptions */
pj_assert( tsx->role == PJSIP_ROLE_UAC &&
tsx->method.id == PJSIP_INVITE_METHOD &&
msg->line.status.code > 100 &&
msg->line.status.code < 200);
/* Get the RSeq header */
rseq_hdr = (pjsip_generic_string_hdr*)
pjsip_msg_find_hdr_by_name(msg, &RSEQ, NULL);
if (rseq_hdr == NULL) {
PJ_LOG(4,(dd->inv->dlg->obj_name,
"Ignoring 100rel response with no RSeq header"));
return PJSIP_EMISSINGHDR;
}
rseq = (pj_uint32_t) pj_strtoul(&rseq_hdr->hvalue);
/* Find UAC state for the specified call leg */
uac_state = dd->uac_state_list;
while (uac_state) {
if (pj_stricmp(&uac_state->tag, to_tag)==0)
break;
uac_state = uac_state->next;
}
/* Create new UAC state if we don't have one */
if (uac_state == NULL) {
uac_state = PJ_POOL_ZALLOC_T(dd->inv->dlg->pool, uac_state_t);
uac_state->cseq = rdata->msg_info.cseq->cseq;
uac_state->rseq = rseq - 1;
pj_strdup(dd->inv->dlg->pool, &uac_state->tag, to_tag);
uac_state->next = dd->uac_state_list;
dd->uac_state_list = uac_state;
}
/* If this is from new INVITE transaction, reset UAC state. */
if (rdata->msg_info.cseq->cseq != uac_state->cseq) {
uac_state->cseq = rdata->msg_info.cseq->cseq;
uac_state->rseq = rseq - 1;
}
/* Ignore provisional response retransmission */
if (rseq <= uac_state->rseq) {
/* This should have been handled before */
return PJ_EIGNORED;
/* Ignore provisional response with out-of-order RSeq */
} else if (rseq != uac_state->rseq + 1) {
PJ_LOG(4,(dd->inv->dlg->obj_name,
"Ignoring 100rel response because RSeq jump "
"(expecting %u, got %u)",
uac_state->rseq+1, rseq));
return PJ_EIGNORED;
}
/* Update our RSeq */
uac_state->rseq = rseq;
/* Create PRACK */
status = pjsip_dlg_create_request(dd->inv->dlg, &pjsip_prack_method,
-1, &tdata);
if (status != PJ_SUCCESS)
return status;
/* If this response is a forked response from a different call-leg,
* update the req URI (https://trac.pjsip.org/repos/ticket/1364)
*/
if (pj_stricmp(&uac_state->tag, &dd->inv->dlg->remote.info->tag)) {
const pjsip_contact_hdr *mhdr;
mhdr = (const pjsip_contact_hdr*)
pjsip_msg_find_hdr(rdata->msg_info.msg,
PJSIP_H_CONTACT, NULL);
if (!mhdr || !mhdr->uri) {
PJ_LOG(4,(dd->inv->dlg->obj_name,
"Ignoring 100rel response with no or "
"invalid Contact header"));
pjsip_tx_data_dec_ref(tdata);
return PJ_EIGNORED;
}
tdata->msg->line.req.uri = (pjsip_uri*)
pjsip_uri_clone(tdata->pool, mhdr->uri);
}
/* Create RAck header */
rack.ptr = rack_buf;
rack.slen = pj_ansi_snprintf(rack.ptr, sizeof(rack_buf),
"%u %u %.*s",
rseq, rdata->msg_info.cseq->cseq,
(int)tsx->method.name.slen,
tsx->method.name.ptr);
if (rack.slen < 1 || rack.slen >= (int)sizeof(rack_buf)) {
return PJ_ETOOSMALL;
}
rack_hdr = pjsip_generic_string_hdr_create(tdata->pool, &RACK, &rack);
pjsip_msg_add_hdr(tdata->msg, (pjsip_hdr*) rack_hdr);
/* Done */
*p_tdata = tdata;
return PJ_SUCCESS;
}
/*
* Send PRACK request.
*/
PJ_DEF(pj_status_t) pjsip_100rel_send_prack( pjsip_inv_session *inv,
pjsip_tx_data *tdata)
{
dlg_data *dd;
dd = (dlg_data*) inv->dlg->mod_data[mod_100rel.mod.id];
PJ_ASSERT_ON_FAIL(dd != NULL,
{pjsip_tx_data_dec_ref(tdata); return PJSIP_ENOTINITIALIZED; });
return pjsip_dlg_send_request(inv->dlg, tdata,
mod_100rel.mod.id, (void*) dd);
}
/* Clear all responses in the transmission list */
static void clear_all_responses(dlg_data *dd)
{
tx_data_list_t *tl;
tl = dd->uas_state->tx_data_list.next;
while (tl != &dd->uas_state->tx_data_list) {
pjsip_tx_data_dec_ref(tl->tdata);
tl = tl->next;
}
pj_list_init(&dd->uas_state->tx_data_list);
}
/*
* Notify 100rel module that the invite session has been disconnected.
*/
PJ_DEF(pj_status_t) pjsip_100rel_end_session(pjsip_inv_session *inv)
{
dlg_data *dd;
dd = (dlg_data*) inv->dlg->mod_data[mod_100rel.mod.id];
if (!dd)
return PJ_SUCCESS;
/* Make sure we don't have pending transmission */
if (dd->uas_state) {
/* Cancel the retransmit timer */
if (dd->uas_state->retransmit_timer.id) {
pjsip_endpt_cancel_timer(dd->inv->dlg->endpt,
&dd->uas_state->retransmit_timer);
dd->uas_state->retransmit_timer.id = PJ_FALSE;
}
if (!pj_list_empty(&dd->uas_state->tx_data_list)) {
/* Clear all pending responses (drop 'em) */
clear_all_responses(dd);
}
}
return PJ_SUCCESS;
}
static void parse_rack(const pj_str_t *rack,
pj_uint32_t *p_rseq, pj_int32_t *p_seq,
pj_str_t *p_method)
{
const char *p = rack->ptr, *end = p + rack->slen;
pj_str_t token;
token.ptr = (char*)p;
while (p < end && pj_isdigit(*p))
++p;
token.slen = p - token.ptr;
*p_rseq = pj_strtoul(&token);
++p;
token.ptr = (char*)p;
while (p < end && pj_isdigit(*p))
++p;
token.slen = p - token.ptr;
*p_seq = pj_strtoul(&token);
++p;
if (p < end) {
p_method->ptr = (char*)p;
p_method->slen = end - p;
} else {
p_method->ptr = NULL;
p_method->slen = 0;
}
}
/*
* Handle incoming PRACK request.
*/
PJ_DEF(pj_status_t) pjsip_100rel_on_rx_prack( pjsip_inv_session *inv,
pjsip_rx_data *rdata)
{
dlg_data *dd;
pjsip_transaction *tsx;
pjsip_msg *msg;
pjsip_generic_string_hdr *rack_hdr;
pjsip_tx_data *tdata;
pj_uint32_t rseq;
pj_int32_t cseq;
pj_str_t method;
pj_status_t status;
tsx = pjsip_rdata_get_tsx(rdata);
pj_assert(tsx != NULL);
msg = rdata->msg_info.msg;
dd = (dlg_data*) inv->dlg->mod_data[mod_100rel.mod.id];
if (dd == NULL) {
/* UAC sends us PRACK while we didn't send reliable provisional
* response. Respond with 400 (?)
*/
const pj_str_t reason = pj_str("Unexpected PRACK");
status = pjsip_dlg_create_response(inv->dlg, rdata, 400,
&reason, &tdata);
if (status == PJ_SUCCESS) {
status = pjsip_dlg_send_response(inv->dlg, tsx, tdata);
}
return PJSIP_ENOTINITIALIZED;
}
/* Always reply with 200/OK for PRACK */
status = pjsip_dlg_create_response(inv->dlg, rdata, 200, NULL, &tdata);
if (status == PJ_SUCCESS) {
status = pjsip_dlg_send_response(inv->dlg, tsx, tdata);
}
/* Ignore if we don't have pending transmission */
if (dd->uas_state == NULL || pj_list_empty(&dd->uas_state->tx_data_list)) {
PJ_LOG(4,(dd->inv->dlg->obj_name,
"PRACK ignored - no pending response"));
return PJ_EIGNORED;
}
/* Find RAck header */
rack_hdr = (pjsip_generic_string_hdr*)
pjsip_msg_find_hdr_by_name(msg, &RACK, NULL);
if (!rack_hdr) {
/* RAck header not found */
PJ_LOG(4,(dd->inv->dlg->obj_name, "No RAck header"));
return PJSIP_EMISSINGHDR;
}
/* Parse RAck header */
parse_rack(&rack_hdr->hvalue, &rseq, &cseq, &method);
/* Match RAck against outgoing transmission */
if (rseq == dd->uas_state->tx_data_list.next->rseq &&
cseq == dd->uas_state->cseq)
{
/*
* Yes this PRACK matches outgoing transmission.
*/
tx_data_list_t *tl = dd->uas_state->tx_data_list.next;
if (dd->uas_state->retransmit_timer.id) {
pjsip_endpt_cancel_timer(dd->inv->dlg->endpt,
&dd->uas_state->retransmit_timer);
dd->uas_state->retransmit_timer.id = PJ_FALSE;
}
/* Remove from the list */
if (tl != &dd->uas_state->tx_data_list) {
pj_list_erase(tl);
/* Destroy the response */
pjsip_tx_data_dec_ref(tl->tdata);
}
/* Schedule next packet */
dd->uas_state->retransmit_count = 0;
if (!pj_list_empty(&dd->uas_state->tx_data_list)) {
on_retransmit(NULL, &dd->uas_state->retransmit_timer);
}
} else {
/* No it doesn't match */
PJ_LOG(4,(dd->inv->dlg->obj_name,
"Rx PRACK with no matching reliable response"));
return PJ_EIGNORED;
}
return PJ_SUCCESS;
}
/*
* This is retransmit timer callback, called initially to send the response,
* and subsequently when the retransmission time elapses.
*/
static void on_retransmit(pj_timer_heap_t *timer_heap,
struct pj_timer_entry *entry)
{
dlg_data *dd;
tx_data_list_t *tl;
pjsip_tx_data *tdata;
pj_bool_t final;
pj_time_val delay;
PJ_UNUSED_ARG(timer_heap);
dd = (dlg_data*) entry->user_data;
entry->id = PJ_FALSE;
++dd->uas_state->retransmit_count;
if (dd->uas_state->retransmit_count >= 7) {
/* If a reliable provisional response is retransmitted for
64*T1 seconds without reception of a corresponding PRACK,
the UAS SHOULD reject the original request with a 5xx
response.
*/
pj_str_t reason = pj_str("Reliable response timed out");
pj_status_t status;
/* Clear all pending responses */
clear_all_responses(dd);
/* Send 500 response */
status = pjsip_inv_end_session(dd->inv, 500, &reason, &tdata);
if (status == PJ_SUCCESS) {
pjsip_dlg_send_response(dd->inv->dlg,
dd->inv->invite_tsx,
tdata);
}
return;
}
pj_assert(!pj_list_empty(&dd->uas_state->tx_data_list));
tl = dd->uas_state->tx_data_list.next;
tdata = tl->tdata;
pjsip_tx_data_add_ref(tdata);
final = tdata->msg->line.status.code >= 200;
if (dd->uas_state->retransmit_count == 1) {
pj_status_t status;
status = pjsip_tsx_send_msg(dd->inv->invite_tsx, tdata);
if (status != PJ_SUCCESS) {
PJ_PERROR(3, (THIS_FILE, status,
"Failed to send message"));
return;
}
} else {
pjsip_tsx_retransmit_no_state(dd->inv->invite_tsx, tdata);
}
if (final) {
/* This is final response, which will be retransmitted by
* UA layer. There's no more task to do, so clear the
* transmission list and bail out.
*/
clear_all_responses(dd);
return;
}
/* Schedule next retransmission */
if (dd->uas_state->retransmit_count < 6) {
delay.sec = 0;
delay.msec = (1 << dd->uas_state->retransmit_count) *
(long)pjsip_cfg()->tsx.t1;
pj_time_val_normalize(&delay);
} else {
delay.sec = 1;
delay.msec = 500;
}
pjsip_endpt_schedule_timer(dd->inv->dlg->endpt,
&dd->uas_state->retransmit_timer,
&delay);
entry->id = PJ_TRUE;
}
/* Check if any pending response in transmission list has SDP */
static pj_bool_t has_sdp(dlg_data *dd)
{
tx_data_list_t *tl;
tl = dd->uas_state->tx_data_list.next;
while (tl != &dd->uas_state->tx_data_list) {
if (tl->tdata->msg->body)
return PJ_TRUE;
tl = tl->next;
}
return PJ_FALSE;
}
/* Send response reliably */
PJ_DEF(pj_status_t) pjsip_100rel_tx_response(pjsip_inv_session *inv,
pjsip_tx_data *tdata)
{
pjsip_cseq_hdr *cseq_hdr;
pjsip_generic_string_hdr *rseq_hdr;
pjsip_require_hdr *req_hdr;
int status_code;
dlg_data *dd;
pjsip_tx_data *old_tdata;
pj_status_t status;
PJ_ASSERT_RETURN(tdata->msg->type == PJSIP_RESPONSE_MSG,
PJSIP_ENOTRESPONSEMSG);
status_code = tdata->msg->line.status.code;
/* 100 response doesn't need PRACK */
if (status_code == 100)
return pjsip_dlg_send_response(inv->dlg, inv->invite_tsx, tdata);
/* Get the 100rel data attached to this dialog */
dd = (dlg_data*) inv->dlg->mod_data[mod_100rel.mod.id];
PJ_ASSERT_RETURN(dd != NULL, PJ_EINVALIDOP);
/* Clone tdata.
* We need to clone tdata because we may need to keep it in our
* retransmission list, while the original dialog may modify it
* if it wants to send another response.
*/
old_tdata = tdata;
pjsip_tx_data_clone(old_tdata, 0, &tdata);
pjsip_tx_data_dec_ref(old_tdata);
/* Get CSeq header, and make sure this is INVITE response */
cseq_hdr = (pjsip_cseq_hdr*)
pjsip_msg_find_hdr(tdata->msg, PJSIP_H_CSEQ, NULL);
PJ_ASSERT_RETURN(cseq_hdr != NULL, PJ_EBUG);
PJ_ASSERT_RETURN(cseq_hdr->method.id == PJSIP_INVITE_METHOD,
PJ_EINVALIDOP);
/* Remove existing Require header */
req_hdr = find_req_hdr(tdata->msg);
if (req_hdr) {
pj_list_erase(req_hdr);
}
/* Remove existing RSeq header */
rseq_hdr = (pjsip_generic_string_hdr*)
pjsip_msg_find_hdr_by_name(tdata->msg, &RSEQ, NULL);
if (rseq_hdr)
pj_list_erase(rseq_hdr);
/* Different treatment for provisional and final response */
if (status_code/100 == 2) {
/* RFC 3262 Section 3: UAS Behavior:
The UAS MAY send a final response to the initial request
before having received PRACKs for all unacknowledged
reliable provisional responses, unless the final response
is 2xx and any of the unacknowledged reliable provisional
responses contained a session description. In that case,
it MUST NOT send a final response until those provisional
responses are acknowledged.
*/
if (dd->uas_state && has_sdp(dd)) {
/* Yes we have transmitted 1xx with SDP reliably.
* In this case, must queue the 2xx response.
*/
tx_data_list_t *tl;
tl = PJ_POOL_ZALLOC_T(tdata->pool, tx_data_list_t);
tl->tdata = tdata;
tl->rseq = (pj_uint32_t)-1;
pj_list_push_back(&dd->uas_state->tx_data_list, tl);
/* Will send later */
status = PJ_SUCCESS;
PJ_LOG(4,(dd->inv->dlg->obj_name,
"2xx response will be sent after PRACK"));
} else if (dd->uas_state) {
/*
RFC 3262 Section 3: UAS Behavior:
If the UAS does send a final response when reliable
responses are still unacknowledged, it SHOULD NOT
continue to retransmit the unacknowledged reliable
provisional responses, but it MUST be prepared to
process PRACK requests for those outstanding
responses.
*/
PJ_LOG(4,(dd->inv->dlg->obj_name,
"No SDP sent so far, sending 2xx now"));
/* Cancel the retransmit timer */
if (dd->uas_state->retransmit_timer.id) {
pjsip_endpt_cancel_timer(dd->inv->dlg->endpt,
&dd->uas_state->retransmit_timer);
dd->uas_state->retransmit_timer.id = PJ_FALSE;
}
/* Clear all pending responses (drop 'em) */
clear_all_responses(dd);
/* And transmit the 2xx response */
status=pjsip_dlg_send_response(inv->dlg,
inv->invite_tsx, tdata);
} else {
/* We didn't send any reliable provisional response */
/* Transmit the 2xx response */
status=pjsip_dlg_send_response(inv->dlg,
inv->invite_tsx, tdata);
}
} else if (status_code >= 300) {
/*
RFC 3262 Section 3: UAS Behavior:
If the UAS does send a final response when reliable
responses are still unacknowledged, it SHOULD NOT
continue to retransmit the unacknowledged reliable
provisional responses, but it MUST be prepared to
process PRACK requests for those outstanding
responses.
*/
/* Cancel the retransmit timer */
if (dd->uas_state && dd->uas_state->retransmit_timer.id) {
pjsip_endpt_cancel_timer(dd->inv->dlg->endpt,
&dd->uas_state->retransmit_timer);
dd->uas_state->retransmit_timer.id = PJ_FALSE;
/* Clear all pending responses (drop 'em) */
clear_all_responses(dd);
}
/* And transmit the 2xx response */
status=pjsip_dlg_send_response(inv->dlg,
inv->invite_tsx, tdata);
} else {
/*
* This is provisional response.
*/
char rseq_str[32];
pj_str_t rseq;
tx_data_list_t *tl;
/* Create UAS state if we don't have one */
if (dd->uas_state == NULL) {
dd->uas_state = PJ_POOL_ZALLOC_T(inv->dlg->pool,
uas_state_t);
dd->uas_state->cseq = cseq_hdr->cseq;
dd->uas_state->rseq = (pj_rand() % 0x7FFF) + 1;
pj_list_init(&dd->uas_state->tx_data_list);
dd->uas_state->retransmit_timer.user_data = dd;
dd->uas_state->retransmit_timer.cb = &on_retransmit;
}
/* Check that CSeq match */
PJ_ASSERT_RETURN(cseq_hdr->cseq == dd->uas_state->cseq,
PJ_EINVALIDOP);
/* Add Require header */
req_hdr = pjsip_require_hdr_create(tdata->pool);
req_hdr->count = 1;
req_hdr->values[0] = tag_100rel;
pjsip_msg_add_hdr(tdata->msg, (pjsip_hdr*)req_hdr);
/* Add RSeq header */
pj_ansi_snprintf(rseq_str, sizeof(rseq_str), "%u",
dd->uas_state->rseq);
rseq = pj_str(rseq_str);
rseq_hdr = pjsip_generic_string_hdr_create(tdata->pool,
&RSEQ, &rseq);
pjsip_msg_add_hdr(tdata->msg, (pjsip_hdr*)rseq_hdr);
/* Create list entry for this response */
tl = PJ_POOL_ZALLOC_T(tdata->pool, tx_data_list_t);
tl->tdata = tdata;
tl->rseq = dd->uas_state->rseq++;
/* Add to queue if there's pending response, otherwise
* transmit immediately.
*/
if (!pj_list_empty(&dd->uas_state->tx_data_list)) {
int code = tdata->msg->line.status.code;
/* Will send later */
pj_list_push_back(&dd->uas_state->tx_data_list, tl);
status = PJ_SUCCESS;
PJ_LOG(4,(dd->inv->dlg->obj_name,
"Reliable %d response enqueued (%d pending)",
code, pj_list_size(&dd->uas_state->tx_data_list)));
} else {
pj_list_push_back(&dd->uas_state->tx_data_list, tl);
dd->uas_state->retransmit_count = 0;
on_retransmit(NULL, &dd->uas_state->retransmit_timer);
status = PJ_SUCCESS;
}
}
return status;
}
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
@@ -0,0 +1,393 @@
/* $Id$ */
/*
* Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
* Copyright (C) 2003-2008 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-ua/sip_replaces.h>
#include <pjsip-ua/sip_inv.h>
#include <pjsip/print_util.h>
#include <pjsip/sip_endpoint.h>
#include <pjsip/sip_errno.h>
#include <pjsip/sip_parser.h>
#include <pjsip/sip_transport.h>
#include <pjsip/sip_ua_layer.h>
#include <pjsip/sip_util.h>
#include <pj/assert.h>
#include <pj/log.h>
#include <pj/pool.h>
#include <pj/string.h>
#define THIS_FILE "sip_replaces.c"
/*
* Replaces header vptr.
*/
static int replaces_hdr_print( pjsip_replaces_hdr *hdr,
char *buf, pj_size_t size);
static pjsip_replaces_hdr* replaces_hdr_clone( pj_pool_t *pool,
const pjsip_replaces_hdr *hdr);
static pjsip_replaces_hdr* replaces_hdr_shallow_clone( pj_pool_t *pool,
const pjsip_replaces_hdr*);
static pjsip_hdr_vptr replaces_hdr_vptr =
{
(pjsip_hdr_clone_fptr) &replaces_hdr_clone,
(pjsip_hdr_clone_fptr) &replaces_hdr_shallow_clone,
(pjsip_hdr_print_fptr) &replaces_hdr_print,
};
/* Globals */
static pjsip_endpoint *the_endpt;
static pj_bool_t is_initialized;
PJ_DEF(pjsip_replaces_hdr*) pjsip_replaces_hdr_create(pj_pool_t *pool)
{
pjsip_replaces_hdr *hdr = PJ_POOL_ZALLOC_T(pool, pjsip_replaces_hdr);
hdr->type = PJSIP_H_OTHER;
hdr->name.ptr = "Replaces";
hdr->name.slen = 8;
hdr->vptr = &replaces_hdr_vptr;
pj_list_init(hdr);
pj_list_init(&hdr->other_param);
return hdr;
}
static int replaces_hdr_print( pjsip_replaces_hdr *hdr,
char *buf, pj_size_t size)
{
char *p = buf;
char *endbuf = buf+size;
pj_ssize_t printed;
const pjsip_parser_const_t *pc = pjsip_parser_const();
copy_advance(p, hdr->name);
*p++ = ':';
*p++ = ' ';
copy_advance(p, hdr->call_id);
copy_advance_pair(p, ";to-tag=", 8, hdr->to_tag);
copy_advance_pair(p, ";from-tag=", 10, hdr->from_tag);
if (hdr->early_only) {
const pj_str_t str_early_only = { ";early-only", 11 };
copy_advance(p, str_early_only);
}
printed = pjsip_param_print_on(&hdr->other_param, p, endbuf-p,
&pc->pjsip_TOKEN_SPEC,
&pc->pjsip_TOKEN_SPEC, ';');
if (printed < 0)
return (int)printed;
p += printed;
return (int)(p - buf);
}
static pjsip_replaces_hdr* replaces_hdr_clone( pj_pool_t *pool,
const pjsip_replaces_hdr *rhs)
{
pjsip_replaces_hdr *hdr = pjsip_replaces_hdr_create(pool);
pj_strdup(pool, &hdr->call_id, &rhs->call_id);
pj_strdup(pool, &hdr->to_tag, &rhs->to_tag);
pj_strdup(pool, &hdr->from_tag, &rhs->from_tag);
hdr->early_only = rhs->early_only;
pjsip_param_clone(pool, &hdr->other_param, &rhs->other_param);
return hdr;
}
static pjsip_replaces_hdr*
replaces_hdr_shallow_clone( pj_pool_t *pool,
const pjsip_replaces_hdr *rhs )
{
pjsip_replaces_hdr *hdr = PJ_POOL_ALLOC_T(pool, pjsip_replaces_hdr);
pj_memcpy(hdr, rhs, sizeof(*hdr));
pjsip_param_shallow_clone(pool, &hdr->other_param, &rhs->other_param);
return hdr;
}
/*
* Parse Replaces header.
*/
static pjsip_hdr *parse_hdr_replaces(pjsip_parse_ctx *ctx)
{
pjsip_replaces_hdr *hdr = pjsip_replaces_hdr_create(ctx->pool);
const pj_str_t to_tag = { "to-tag", 6 };
const pj_str_t from_tag = { "from-tag", 8 };
const pj_str_t early_only_tag = { "early-only", 10 };
/*pj_scan_get(ctx->scanner, &pjsip_TOKEN_SPEC, &hdr->call_id);*/
/* Get Call-ID (until ';' is found). using pjsip_TOKEN_SPEC doesn't work
* because it stops parsing when '@' character is found.
*/
pj_scan_get_until_ch(ctx->scanner, ';', &hdr->call_id);
while (*ctx->scanner->curptr == ';') {
pj_str_t pname, pvalue;
pj_scan_get_char(ctx->scanner);
pjsip_parse_param_imp(ctx->scanner, ctx->pool, &pname, &pvalue, 0);
if (pj_stricmp(&pname, &to_tag)==0) {
hdr->to_tag = pvalue;
} else if (pj_stricmp(&pname, &from_tag)==0) {
hdr->from_tag = pvalue;
} else if (pj_stricmp(&pname, &early_only_tag)==0) {
hdr->early_only = PJ_TRUE;
} else {
pjsip_param *param = PJ_POOL_ALLOC_T(ctx->pool, pjsip_param);
param->name = pname;
param->value = pvalue;
pj_list_push_back(&hdr->other_param, param);
}
}
pjsip_parse_end_hdr_imp( ctx->scanner );
return (pjsip_hdr*)hdr;
}
/* Deinitialize Replaces */
static void pjsip_replaces_deinit_module(pjsip_endpoint *endpt)
{
PJ_TODO(provide_initialized_flag_for_each_endpoint);
PJ_UNUSED_ARG(endpt);
is_initialized = PJ_FALSE;
}
/*
* Initialize Replaces support in PJSIP.
*/
PJ_DEF(pj_status_t) pjsip_replaces_init_module(pjsip_endpoint *endpt)
{
pj_status_t status;
const pj_str_t STR_REPLACES = { "replaces", 8 };
the_endpt = endpt;
if (is_initialized)
return PJ_SUCCESS;
/* Register Replaces header parser */
status = pjsip_register_hdr_parser( "Replaces", NULL,
&parse_hdr_replaces);
if (status != PJ_SUCCESS)
return status;
/* Register "replaces" capability */
status = pjsip_endpt_add_capability(endpt, NULL, PJSIP_H_SUPPORTED, NULL,
1, &STR_REPLACES);
/* Register deinit module to be executed when PJLIB shutdown */
if (pjsip_endpt_atexit(endpt, &pjsip_replaces_deinit_module) != PJ_SUCCESS)
{
/* Failure to register this function may cause this module won't
* work properly when the stack is restarted (without quitting
* application).
*/
pj_assert(!"Failed to register Replaces deinit.");
PJ_LOG(1, (THIS_FILE, "Failed to register Replaces deinit."));
}
is_initialized = PJ_TRUE;
return PJ_SUCCESS;
}
/*
* Verify that incoming request with Replaces header can be processed.
*/
PJ_DEF(pj_status_t) pjsip_replaces_verify_request( pjsip_rx_data *rdata,
pjsip_dialog **p_dlg,
pj_bool_t lock_dlg,
pjsip_tx_data **p_tdata)
{
const pj_str_t STR_REPLACES = { "Replaces", 8 };
pjsip_replaces_hdr *rep_hdr;
int code = 200;
const char *warn_text = NULL;
pjsip_hdr res_hdr_list;
pjsip_dialog *dlg = NULL;
pjsip_inv_session *inv;
pj_status_t status = PJ_SUCCESS;
PJ_ASSERT_RETURN(rdata && p_dlg, PJ_EINVAL);
/* Check that pjsip_replaces_init_module() has been called. */
PJ_ASSERT_RETURN(the_endpt != NULL, PJ_EINVALIDOP);
/* Init output arguments */
*p_dlg = NULL;
if (p_tdata) *p_tdata = NULL;
pj_list_init(&res_hdr_list);
/* Find Replaces header */
rep_hdr = (pjsip_replaces_hdr*)
pjsip_msg_find_hdr_by_name(rdata->msg_info.msg, &STR_REPLACES,
NULL);
if (!rep_hdr) {
/* No Replaces header. No further processing is necessary. */
return PJ_SUCCESS;
}
/* Check that there's no other Replaces header and return 400 Bad Request
* if not.
*/
if (pjsip_msg_find_hdr_by_name(rdata->msg_info.msg, &STR_REPLACES,
rep_hdr->next)) {
code = PJSIP_SC_BAD_REQUEST;
warn_text = "Found multiple Replaces headers";
goto on_return;
}
/* Find the dialog identified by Replaces header (and always lock the
* dialog no matter what application wants).
*/
dlg = pjsip_ua_find_dialog(&rep_hdr->call_id, &rep_hdr->to_tag,
&rep_hdr->from_tag, PJ_TRUE);
/* Respond with 481 "Call/Transaction Does Not Exist" response if
* no dialog is found.
*/
if (dlg == NULL) {
code = PJSIP_SC_CALL_TSX_DOES_NOT_EXIST;
warn_text = "No dialog found for Replaces request";
goto on_return;
}
/* Get the invite session within the dialog */
inv = pjsip_dlg_get_inv_session(dlg);
/* Return 481 if no invite session is present. */
if (inv == NULL) {
code = PJSIP_SC_CALL_TSX_DOES_NOT_EXIST;
warn_text = "No INVITE session found for Replaces request";
goto on_return;
}
/* Return 603 Declined response if invite session has already
* terminated
*/
if (inv->state >= PJSIP_INV_STATE_DISCONNECTED) {
code = PJSIP_SC_DECLINE;
warn_text = "INVITE session already terminated";
goto on_return;
}
/* If "early-only" flag is present, check that the invite session
* has not been confirmed yet. If the session has been confirmed,
* return 486 "Busy Here" response.
*/
if (rep_hdr->early_only && inv->state >= PJSIP_INV_STATE_CONNECTING) {
code = PJSIP_SC_BUSY_HERE;
warn_text = "INVITE session already established";
goto on_return;
}
/* If the Replaces header field matches an early dialog that was not
* initiated by this UA, it returns a 481 (Call/Transaction Does Not
* Exist) response to the new INVITE.
*/
if (inv->state <= PJSIP_INV_STATE_EARLY && inv->role != PJSIP_ROLE_UAC)
{
/* Really return 481 only if call haven't reached early state or
* accept-replace-in-early-state (ticket #1587) is not allowed.
*/
if (inv->state != PJSIP_INV_STATE_EARLY ||
pjsip_cfg()->endpt.accept_replace_in_early_state == PJ_FALSE)
{
code = PJSIP_SC_CALL_TSX_DOES_NOT_EXIST;
warn_text = "Found early INVITE session but not initiated by "
"this UA";
goto on_return;
}
}
/*
* Looks like everything is okay!!
*/
*p_dlg = dlg;
status = PJ_SUCCESS;
code = 200;
on_return:
/* Create response if necessary */
if (code != 200) {
/* If we have dialog we must unlock it */
if (dlg)
pjsip_dlg_dec_lock(dlg);
/* Create response */
if (p_tdata) {
pjsip_tx_data *tdata;
const pjsip_hdr *h;
status = pjsip_endpt_create_response(the_endpt, rdata, code,
NULL, &tdata);
if (status != PJ_SUCCESS)
return status;
/* Add response headers. */
h = res_hdr_list.next;
while (h != &res_hdr_list) {
pjsip_hdr *cloned;
cloned = (pjsip_hdr*) pjsip_hdr_clone(tdata->pool, h);
PJ_ASSERT_RETURN(cloned, PJ_ENOMEM);
pjsip_msg_add_hdr(tdata->msg, cloned);
h = h->next;
}
/* Add warn text, if any */
if (warn_text) {
pjsip_warning_hdr *warn_hdr;
pj_str_t warn_value = pj_str((char*)warn_text);
warn_hdr=pjsip_warning_hdr_create(tdata->pool, 399,
pjsip_endpt_name(the_endpt),
&warn_value);
pjsip_msg_add_hdr(tdata->msg, (pjsip_hdr*)warn_hdr);
}
*p_tdata = tdata;
}
/* Can not return PJ_SUCCESS when response message is produced.
* Ref: PROTOS test ~#2490
*/
if (status == PJ_SUCCESS)
status = PJSIP_ERRNO_FROM_SIP_STATUS(code);
} else {
/* If application doesn't want to lock the dialog, unlock it */
if (!lock_dlg)
pjsip_dlg_dec_lock(dlg);
}
return status;
}
File diff suppressed because it is too large. Load diff
@@ -0,0 +1,629 @@
/* $Id$ */
/*
* Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
* Copyright (C) 2003-2008 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-ua/sip_xfer.h>
#include <pjsip-simple/evsub_msg.h>
#include <pjsip/sip_dialog.h>
#include <pjsip/sip_errno.h>
#include <pjsip/sip_endpoint.h>
#include <pjsip/sip_module.h>
#include <pjsip/sip_transport.h>
#include <pj/assert.h>
#include <pj/pool.h>
#include <pj/string.h>
/* Subscription expiration */
#ifndef PJSIP_XFER_EXPIRES
# define PJSIP_XFER_EXPIRES 600
#endif
/*
* Refer module (mod-refer)
*/
static struct pjsip_module mod_xfer =
{
NULL, NULL, /* prev, next. */
{ "mod-refer", 9 }, /* Name. */
-1, /* Id */
PJSIP_MOD_PRIORITY_DIALOG_USAGE, /* Priority */
NULL, /* load() */
NULL, /* start() */
NULL, /* stop() */
NULL, /* unload() */
NULL, /* on_rx_request() */
NULL, /* on_rx_response() */
NULL, /* on_tx_request. */
NULL, /* on_tx_response() */
NULL, /* on_tsx_state() */
};
/* Declare PJSIP_REFER_METHOD, so that if somebody declares this in
* sip_msg.h we can catch the error here.
*/
enum
{
PJSIP_REFER_METHOD = PJSIP_OTHER_METHOD
};
PJ_DEF_DATA(const pjsip_method) pjsip_refer_method = {
(pjsip_method_e) PJSIP_REFER_METHOD,
{ "REFER", 5}
};
PJ_DEF(const pjsip_method*) pjsip_get_refer_method()
{
return &pjsip_refer_method;
}
/*
* String constants
*/
static const pj_str_t STR_REFER = { "refer", 5 };
static const pj_str_t STR_MESSAGE = { "message", 7 };
static const pj_str_t STR_SIPFRAG = { "sipfrag", 7 };
/*
* Transfer struct.
*/
struct pjsip_xfer
{
pjsip_evsub *sub; /**< Event subscribtion record. */
pjsip_dialog *dlg; /**< The dialog. */
pjsip_evsub_user user_cb; /**< The user callback. */
pj_str_t refer_to_uri; /**< The full Refer-To URI. */
int last_st_code; /**< st_code sent in last NOTIFY */
pj_str_t last_st_text; /**< st_text sent in last NOTIFY */
};
typedef struct pjsip_xfer pjsip_xfer;
/*
* Forward decl for evsub callback.
*/
static void xfer_on_evsub_state( pjsip_evsub *sub, pjsip_event *event);
static void xfer_on_evsub_tsx_state( pjsip_evsub *sub, pjsip_transaction *tsx,
pjsip_event *event);
static void xfer_on_evsub_rx_refresh( 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 void xfer_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 void xfer_on_evsub_client_refresh(pjsip_evsub *sub);
static void xfer_on_evsub_server_timeout(pjsip_evsub *sub);
/*
* Event subscription callback for xference.
*/
static pjsip_evsub_user xfer_user =
{
&xfer_on_evsub_state,
&xfer_on_evsub_tsx_state,
&xfer_on_evsub_rx_refresh,
&xfer_on_evsub_rx_notify,
&xfer_on_evsub_client_refresh,
&xfer_on_evsub_server_timeout,
};
/*
* Initialize the REFER subsystem.
*/
PJ_DEF(pj_status_t) pjsip_xfer_init_module(pjsip_endpoint *endpt)
{
const pj_str_t accept = { "message/sipfrag;version=2.0", 27 };
pj_status_t status;
PJ_ASSERT_RETURN(endpt != NULL, PJ_EINVAL);
PJ_ASSERT_RETURN(mod_xfer.id == -1, PJ_EINVALIDOP);
status = pjsip_endpt_register_module(endpt, &mod_xfer);
if (status != PJ_SUCCESS)
return status;
status = pjsip_endpt_add_capability( endpt, &mod_xfer, PJSIP_H_ALLOW,
NULL, 1,
&pjsip_get_refer_method()->name);
if (status != PJ_SUCCESS)
return status;
status = pjsip_evsub_register_pkg(&mod_xfer, &STR_REFER,
PJSIP_XFER_EXPIRES, 1, &accept);
if (status != PJ_SUCCESS)
return status;
return PJ_SUCCESS;
}
/*
* Create transferer (sender of REFER request).
*
*/
PJ_DEF(pj_status_t) pjsip_xfer_create_uac( pjsip_dialog *dlg,
const pjsip_evsub_user *user_cb,
pjsip_evsub **p_evsub )
{
pj_status_t status;
pjsip_xfer *xfer;
pjsip_evsub *sub;
PJ_ASSERT_RETURN(dlg && p_evsub, PJ_EINVAL);
pjsip_dlg_inc_lock(dlg);
/* Create event subscription */
status = pjsip_evsub_create_uac( dlg, &xfer_user, &STR_REFER,
PJSIP_EVSUB_NO_EVENT_ID, &sub);
if (status != PJ_SUCCESS)
goto on_return;
/* Create xfer session */
xfer = PJ_POOL_ZALLOC_T(dlg->pool, pjsip_xfer);
xfer->dlg = dlg;
xfer->sub = sub;
if (user_cb)
pj_memcpy(&xfer->user_cb, user_cb, sizeof(pjsip_evsub_user));
/* Attach to evsub */
pjsip_evsub_set_mod_data(sub, mod_xfer.id, xfer);
*p_evsub = sub;
on_return:
pjsip_dlg_dec_lock(dlg);
return status;
}
/*
* Create transferee (receiver of REFER request).
*
*/
PJ_DEF(pj_status_t) pjsip_xfer_create_uas( pjsip_dialog *dlg,
const pjsip_evsub_user *user_cb,
pjsip_rx_data *rdata,
pjsip_evsub **p_evsub )
{
pjsip_evsub *sub;
pjsip_xfer *xfer;
const pj_str_t STR_EVENT = {"Event", 5 };
pjsip_event_hdr *event_hdr;
pj_status_t status;
/* Check arguments */
PJ_ASSERT_RETURN(dlg && rdata && p_evsub, PJ_EINVAL);
/* Must be request message */
PJ_ASSERT_RETURN(rdata->msg_info.msg->type == PJSIP_REQUEST_MSG,
PJSIP_ENOTREQUESTMSG);
/* Check that request is REFER */
PJ_ASSERT_RETURN(pjsip_method_cmp(&rdata->msg_info.msg->line.req.method,
pjsip_get_refer_method())==0,
PJSIP_ENOTREFER);
/* Lock dialog */
pjsip_dlg_inc_lock(dlg);
/* The evsub framework expects an Event header in the request,
* while a REFER request conveniently doesn't have one (pun intended!).
* So create a dummy Event header.
*/
if (pjsip_msg_find_hdr_by_name(rdata->msg_info.msg,
&STR_EVENT, NULL)==NULL)
{
event_hdr = pjsip_event_hdr_create(rdata->tp_info.pool);
event_hdr->event_type = STR_REFER;
pjsip_msg_add_hdr(rdata->msg_info.msg, (pjsip_hdr*)event_hdr);
}
/* Create server subscription */
status = pjsip_evsub_create_uas( dlg, &xfer_user, rdata,
PJSIP_EVSUB_NO_EVENT_ID, &sub);
if (status != PJ_SUCCESS)
goto on_return;
/* Create server xfer subscription */
xfer = PJ_POOL_ZALLOC_T(dlg->pool, pjsip_xfer);
xfer->dlg = dlg;
xfer->sub = sub;
if (user_cb)
pj_memcpy(&xfer->user_cb, user_cb, sizeof(pjsip_evsub_user));
/* Attach to evsub */
pjsip_evsub_set_mod_data(sub, mod_xfer.id, xfer);
/* Done: */
*p_evsub = sub;
on_return:
pjsip_dlg_dec_lock(dlg);
return status;
}
/*
* Call this function to create request to initiate REFER subscription.
*
*/
PJ_DEF(pj_status_t) pjsip_xfer_initiate( pjsip_evsub *sub,
const pj_str_t *refer_to_uri,
pjsip_tx_data **p_tdata)
{
pjsip_xfer *xfer;
const pj_str_t refer_to = { "Refer-To", 8};
pjsip_tx_data *tdata;
pjsip_generic_string_hdr *hdr;
pj_status_t status;
/* sub and p_tdata argument must be valid. */
PJ_ASSERT_RETURN(sub && p_tdata, PJ_EINVAL);
/* Get the xfer object. */
xfer = (pjsip_xfer*) pjsip_evsub_get_mod_data(sub, mod_xfer.id);
PJ_ASSERT_RETURN(xfer != NULL, PJSIP_ENOREFERSESSION);
/* refer_to_uri argument MAY be NULL for subsequent REFER requests,
* but it MUST be specified in the first REFER.
*/
PJ_ASSERT_RETURN((refer_to_uri || xfer->refer_to_uri.slen), PJ_EINVAL);
/* Lock dialog. */
pjsip_dlg_inc_lock(xfer->dlg);
/* Create basic REFER request */
status = pjsip_evsub_initiate(sub, pjsip_get_refer_method(),
PJSIP_EXPIRES_NOT_SPECIFIED, &tdata);
if (status != PJ_SUCCESS)
goto on_return;
/* Save Refer-To URI. */
if (refer_to_uri == NULL) {
refer_to_uri = &xfer->refer_to_uri;
} else {
pj_strdup(xfer->dlg->pool, &xfer->refer_to_uri, refer_to_uri);
}
/* Create and add Refer-To header. */
hdr = pjsip_generic_string_hdr_create(tdata->pool, &refer_to,
refer_to_uri);
if (!hdr) {
pjsip_tx_data_dec_ref(tdata);
status = PJ_ENOMEM;
goto on_return;
}
pjsip_msg_add_hdr(tdata->msg, (pjsip_hdr*)hdr);
/* Done. */
*p_tdata = tdata;
status = PJ_SUCCESS;
on_return:
pjsip_dlg_dec_lock(xfer->dlg);
return status;
}
/*
* Accept the incoming REFER request by sending 2xx response.
*
*/
PJ_DEF(pj_status_t) pjsip_xfer_accept( pjsip_evsub *sub,
pjsip_rx_data *rdata,
int st_code,
const pjsip_hdr *hdr_list )
{
/*
* Don't need to add custom headers, so just call basic
* evsub response.
*/
return pjsip_evsub_accept( sub, rdata, st_code, hdr_list );
}
/*
* For notifier, create NOTIFY request to subscriber, and set the state
* of the subscription.
*/
PJ_DEF(pj_status_t) pjsip_xfer_notify( pjsip_evsub *sub,
pjsip_evsub_state state,
int xfer_st_code,
const pj_str_t *xfer_st_text,
pjsip_tx_data **p_tdata)
{
pjsip_tx_data *tdata;
pjsip_xfer *xfer;
pjsip_param *param;
const pj_str_t reason = { "noresource", 10 };
char *body;
int bodylen;
pjsip_msg_body *msg_body;
pj_status_t status;
/* Check arguments. */
PJ_ASSERT_RETURN(sub, PJ_EINVAL);
/* Get the xfer object. */
xfer = (pjsip_xfer*) pjsip_evsub_get_mod_data(sub, mod_xfer.id);
PJ_ASSERT_RETURN(xfer != NULL, PJSIP_ENOREFERSESSION);
/* Lock object. */
pjsip_dlg_inc_lock(xfer->dlg);
/* Create the NOTIFY request.
* Note that reason is only used when state is TERMINATED, and
* the defined termination reason for REFER is "noresource".
*/
status = pjsip_evsub_notify( sub, state, NULL, &reason, &tdata);
if (status != PJ_SUCCESS)
goto on_return;
/* Check status text */
if (xfer_st_text==NULL || xfer_st_text->slen==0)
xfer_st_text = pjsip_get_status_text(xfer_st_code);
/* Save st_code and st_text, for current_notify() */
xfer->last_st_code = xfer_st_code;
pj_strdup(xfer->dlg->pool, &xfer->last_st_text, xfer_st_text);
/* Create sipfrag content. */
body = (char*) pj_pool_alloc(tdata->pool, 128);
bodylen = pj_ansi_snprintf(body, 128, "SIP/2.0 %u %.*s\r\n",
xfer_st_code,
(int)xfer_st_text->slen,
xfer_st_text->ptr);
PJ_ASSERT_ON_FAIL(bodylen > 0 && bodylen < 128,
{status=PJ_EBUG; pjsip_tx_data_dec_ref(tdata);
goto on_return; });
/* Create SIP message body. */
msg_body = PJ_POOL_ZALLOC_T(tdata->pool, pjsip_msg_body);
pjsip_media_type_init(&msg_body->content_type, (pj_str_t*)&STR_MESSAGE,
(pj_str_t*)&STR_SIPFRAG);
msg_body->data = body;
msg_body->len = bodylen;
msg_body->print_body = &pjsip_print_text_body;
msg_body->clone_data = &pjsip_clone_text_data;
param = PJ_POOL_ALLOC_T(tdata->pool, pjsip_param);
param->name = pj_str("version");
param->value = pj_str("2.0");
pj_list_push_back(&msg_body->content_type.param, param);
/* Attach sipfrag body. */
tdata->msg->body = msg_body;
/* Done. */
*p_tdata = tdata;
on_return:
pjsip_dlg_dec_lock(xfer->dlg);
return status;
}
/*
* Send current state and the last sipfrag body.
*/
PJ_DEF(pj_status_t) pjsip_xfer_current_notify( pjsip_evsub *sub,
pjsip_tx_data **p_tdata )
{
pjsip_xfer *xfer;
pj_status_t status;
/* Check arguments. */
PJ_ASSERT_RETURN(sub, PJ_EINVAL);
/* Get the xfer object. */
xfer = (pjsip_xfer*) pjsip_evsub_get_mod_data(sub, mod_xfer.id);
PJ_ASSERT_RETURN(xfer != NULL, PJSIP_ENOREFERSESSION);
pjsip_dlg_inc_lock(xfer->dlg);
status = pjsip_xfer_notify(sub, pjsip_evsub_get_state(sub),
xfer->last_st_code, &xfer->last_st_text,
p_tdata);
pjsip_dlg_dec_lock(xfer->dlg);
return status;
}
/*
* Send request message.
*/
PJ_DEF(pj_status_t) pjsip_xfer_send_request( pjsip_evsub *sub,
pjsip_tx_data *tdata)
{
return pjsip_evsub_send_request(sub, tdata);
}
/*
* This callback is called by event subscription when subscription
* state has changed.
*/
static void xfer_on_evsub_state( pjsip_evsub *sub, pjsip_event *event)
{
pjsip_xfer *xfer;
xfer = (pjsip_xfer*) pjsip_evsub_get_mod_data(sub, mod_xfer.id);
PJ_ASSERT_ON_FAIL(xfer!=NULL, {return;});
if (xfer->user_cb.on_evsub_state)
(*xfer->user_cb.on_evsub_state)(sub, event);
}
/*
* Called when transaction state has changed.
*/
static void xfer_on_evsub_tsx_state( pjsip_evsub *sub, pjsip_transaction *tsx,
pjsip_event *event)
{
pjsip_xfer *xfer;
xfer = (pjsip_xfer*) pjsip_evsub_get_mod_data(sub, mod_xfer.id);
PJ_ASSERT_ON_FAIL(xfer!=NULL, {return;});
if (xfer->user_cb.on_tsx_state)
(*xfer->user_cb.on_tsx_state)(sub, tsx, event);
}
/*
* Called when REFER is received to refresh subscription.
*/
static void xfer_on_evsub_rx_refresh( 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_xfer *xfer;
xfer = (pjsip_xfer*) pjsip_evsub_get_mod_data(sub, mod_xfer.id);
PJ_ASSERT_ON_FAIL(xfer!=NULL, {return;});
if (xfer->user_cb.on_rx_refresh) {
(*xfer->user_cb.on_rx_refresh)(sub, rdata, p_st_code, p_st_text,
res_hdr, p_body);
} else {
/* Implementors MUST send NOTIFY if it implements on_rx_refresh
* (implementor == "us" from evsub point of view.
*/
pjsip_tx_data *tdata;
pj_status_t status;
if (pjsip_evsub_get_state(sub)==PJSIP_EVSUB_STATE_TERMINATED) {
status = pjsip_xfer_notify( sub, PJSIP_EVSUB_STATE_TERMINATED,
xfer->last_st_code,
&xfer->last_st_text,
&tdata);
} else {
status = pjsip_xfer_current_notify(sub, &tdata);
}
if (status == PJ_SUCCESS)
pjsip_xfer_send_request(sub, tdata);
}
}
/*
* Called when NOTIFY is received.
*/
static void xfer_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_xfer *xfer;
xfer = (pjsip_xfer*) pjsip_evsub_get_mod_data(sub, mod_xfer.id);
PJ_ASSERT_ON_FAIL(xfer!=NULL, {return;});
if (xfer->user_cb.on_rx_notify)
(*xfer->user_cb.on_rx_notify)(sub, rdata, p_st_code, p_st_text,
res_hdr, p_body);
}
/*
* Called when it's time to send SUBSCRIBE.
*/
static void xfer_on_evsub_client_refresh(pjsip_evsub *sub)
{
pjsip_xfer *xfer;
xfer = (pjsip_xfer*) pjsip_evsub_get_mod_data(sub, mod_xfer.id);
PJ_ASSERT_ON_FAIL(xfer!=NULL, {return;});
if (xfer->user_cb.on_client_refresh) {
(*xfer->user_cb.on_client_refresh)(sub);
} else {
pj_status_t status;
pjsip_tx_data *tdata;
status = pjsip_evsub_initiate(sub, NULL, PJSIP_XFER_EXPIRES, &tdata);
if (status == PJ_SUCCESS)
pjsip_xfer_send_request(sub, tdata);
}
}
/*
* Called when no refresh is received after the interval.
*/
static void xfer_on_evsub_server_timeout(pjsip_evsub *sub)
{
pjsip_xfer *xfer;
xfer = (pjsip_xfer*) pjsip_evsub_get_mod_data(sub, mod_xfer.id);
PJ_ASSERT_ON_FAIL(xfer!=NULL, {return;});
if (xfer->user_cb.on_server_timeout) {
(*xfer->user_cb.on_server_timeout)(sub);
} else {
pj_status_t status;
pjsip_tx_data *tdata;
status = pjsip_xfer_notify(sub, PJSIP_EVSUB_STATE_TERMINATED,
xfer->last_st_code,
&xfer->last_st_text, &tdata);
if (status == PJ_SUCCESS)
pjsip_xfer_send_request(sub, tdata);
}
}
@@ -0,0 +1,204 @@
/* $Id$ */
/*
* Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
* Copyright (C) 2003-2008 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/sip_auth_aka.h>
#include <pjsip/sip_errno.h>
#include <pjlib-util/base64.h>
#include <pjlib-util/md5.h>
#include <pjlib-util/hmac_md5.h>
#include <pj/assert.h>
#include <pj/log.h>
#include <pj/pool.h>
#include <pj/string.h>
#if PJSIP_HAS_DIGEST_AKA_AUTH
#include "../../third_party/milenage/milenage.h"
/*
* Create MD5-AKA1 digest response.
*/
PJ_DEF(pj_status_t) pjsip_auth_create_aka_response(
pj_pool_t *pool,
const pjsip_digest_challenge*chal,
const pjsip_cred_info *cred,
const pj_str_t *method,
pjsip_digest_credential *auth)
{
pj_str_t nonce_bin;
int aka_version;
const pj_str_t pjsip_AKAv1_MD5 = { "AKAv1-MD5", 9 };
const pj_str_t pjsip_AKAv2_MD5 = { "AKAv2-MD5", 9 };
pj_uint8_t *chal_rand, *chal_sqnxoraka, *chal_mac;
pj_uint8_t k[PJSIP_AKA_KLEN];
pj_uint8_t op[PJSIP_AKA_OPLEN];
pj_uint8_t amf[PJSIP_AKA_AMFLEN];
pj_uint8_t res[PJSIP_AKA_RESLEN];
pj_uint8_t ck[PJSIP_AKA_CKLEN];
pj_uint8_t ik[PJSIP_AKA_IKLEN];
pj_uint8_t ak[PJSIP_AKA_AKLEN];
pj_uint8_t sqn[PJSIP_AKA_SQNLEN];
pj_uint8_t xmac[PJSIP_AKA_MACLEN];
pjsip_cred_info aka_cred;
int i, len;
pj_status_t status;
/* Check the algorithm is supported. */
if (chal->algorithm.slen==0 || pj_stricmp2(&chal->algorithm, "md5") == 0) {
/*
* A normal MD5 authentication is requested. Fallbackt to the usual
* MD5 digest creation.
*/
pjsip_auth_create_digest(&auth->response, &auth->nonce, &auth->nc,
&auth->cnonce, &auth->qop, &auth->uri,
&auth->realm, cred, method);
return PJ_SUCCESS;
} else if (pj_stricmp(&chal->algorithm, &pjsip_AKAv1_MD5) == 0) {
/*
* AKA version 1 is requested.
*/
aka_version = 1;
} else if (pj_stricmp(&chal->algorithm, &pjsip_AKAv2_MD5) == 0) {
/*
* AKA version 2 is requested.
*/
aka_version = 2;
} else {
/* Unsupported algorithm */
return PJSIP_EINVALIDALGORITHM;
}
/* Decode nonce */
nonce_bin.slen = len = PJ_BASE64_TO_BASE256_LEN(chal->nonce.slen);
nonce_bin.ptr = pj_pool_alloc(pool, nonce_bin.slen + 1);
status = pj_base64_decode(&chal->nonce, (pj_uint8_t*)nonce_bin.ptr, &len);
nonce_bin.slen = len;
if (status != PJ_SUCCESS)
return PJSIP_EAUTHINNONCE;
if (nonce_bin.slen < PJSIP_AKA_RANDLEN + PJSIP_AKA_AUTNLEN)
return PJSIP_EAUTHINNONCE;
/* Get RAND, AUTN, and MAC */
chal_rand = (pj_uint8_t*)(nonce_bin.ptr + 0);
chal_sqnxoraka = (pj_uint8_t*) (nonce_bin.ptr + PJSIP_AKA_RANDLEN);
chal_mac = (pj_uint8_t*) (nonce_bin.ptr + PJSIP_AKA_RANDLEN +
PJSIP_AKA_SQNLEN + PJSIP_AKA_AMFLEN);
/* Copy k. op, and amf */
pj_bzero(k, sizeof(k));
pj_bzero(op, sizeof(op));
pj_bzero(amf, sizeof(amf));
if (cred->ext.aka.k.slen)
pj_memcpy(k, cred->ext.aka.k.ptr, cred->ext.aka.k.slen);
if (cred->ext.aka.op.slen)
pj_memcpy(op, cred->ext.aka.op.ptr, cred->ext.aka.op.slen);
if (cred->ext.aka.amf.slen)
pj_memcpy(amf, cred->ext.aka.amf.ptr, cred->ext.aka.amf.slen);
/* Given key K and random challenge RAND, compute response RES,
* confidentiality key CK, integrity key IK and anonymity key AK.
*/
f2345(k, chal_rand, res, ck, ik, ak, op);
/* Compute sequence number SQN */
for (i=0; i<PJSIP_AKA_SQNLEN; ++i)
sqn[i] = (pj_uint8_t) (chal_sqnxoraka[i] ^ ak[i]);
/* Verify MAC in the challenge */
/* Compute XMAC */
f1(k, chal_rand, sqn, amf, xmac, op);
if (pj_memcmp(chal_mac, xmac, PJSIP_AKA_MACLEN) != 0) {
return PJSIP_EAUTHINNONCE;
}
/* Build a temporary credential info to create MD5 digest, using
* "res" as the password.
*/
pj_memcpy(&aka_cred, cred, sizeof(aka_cred));
aka_cred.data_type = PJSIP_CRED_DATA_PLAIN_PASSWD;
/* Create a response */
if (aka_version == 1) {
/*
* For AKAv1, the password is RES
*/
aka_cred.data.ptr = (char*)res;
aka_cred.data.slen = PJSIP_AKA_RESLEN;
pjsip_auth_create_digest(&auth->response, &chal->nonce,
&auth->nc, &auth->cnonce, &auth->qop,
&auth->uri, &chal->realm, &aka_cred, method);
} else if (aka_version == 2) {
/*
* For AKAv2, password is base64 encoded [1] parameters:
* PRF(RES||IK||CK,"http-digest-akav2-password")
*
* The pseudo-random function (PRF) is HMAC-MD5 in this case.
*/
pj_str_t resikck;
const pj_str_t AKAv2_Passwd = { "http-digest-akav2-password", 26 };
pj_uint8_t hmac_digest[16];
char tmp_buf[48];
int hmac64_len;
resikck.slen = PJSIP_AKA_RESLEN + PJSIP_AKA_IKLEN + PJSIP_AKA_CKLEN;
pj_assert(resikck.slen <= PJ_ARRAY_SIZE(tmp_buf));
resikck.ptr = tmp_buf;
pj_memcpy(resikck.ptr + 0, res, PJSIP_AKA_RESLEN);
pj_memcpy(resikck.ptr + PJSIP_AKA_RESLEN, ik, PJSIP_AKA_IKLEN);
pj_memcpy(resikck.ptr + PJSIP_AKA_RESLEN + PJSIP_AKA_IKLEN,
ck, PJSIP_AKA_CKLEN);
pj_hmac_md5((const pj_uint8_t*)AKAv2_Passwd.ptr, AKAv2_Passwd.slen,
(const pj_uint8_t*)resikck.ptr, resikck.slen,
hmac_digest);
aka_cred.data.slen = hmac64_len =
PJ_BASE256_TO_BASE64_LEN(PJ_ARRAY_SIZE(hmac_digest));
pj_assert(aka_cred.data.slen+1 <= PJ_ARRAY_SIZE(tmp_buf));
aka_cred.data.ptr = tmp_buf;
pj_base64_encode(hmac_digest, PJ_ARRAY_SIZE(hmac_digest),
aka_cred.data.ptr, &len);
aka_cred.data.slen = hmac64_len;
pjsip_auth_create_digest(&auth->response, &chal->nonce,
&auth->nc, &auth->cnonce, &auth->qop,
&auth->uri, &chal->realm, &aka_cred, method);
} else {
pj_assert(!"Bug!");
return PJ_EBUG;
}
/* Done */
return PJ_SUCCESS;
}
#endif /* PJSIP_HAS_DIGEST_AKA_AUTH */
File diff suppressed because it is too large. Load diff
@@ -0,0 +1,366 @@
/* $Id$ */
/*
* Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
* Copyright (C) 2003-2008 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/sip_auth_msg.h>
#include <pjsip/sip_auth_parser.h>
#include <pjsip/sip_parser.h>
#include <pj/pool.h>
#include <pj/list.h>
#include <pj/string.h>
#include <pj/assert.h>
#include <pjsip/print_util.h>
///////////////////////////////////////////////////////////////////////////////
/*
* Authorization and Proxy-Authorization header.
*/
static pjsip_authorization_hdr* pjsip_authorization_hdr_clone( pj_pool_t *pool,
const pjsip_authorization_hdr *hdr);
static pjsip_authorization_hdr* pjsip_authorization_hdr_shallow_clone( pj_pool_t *pool,
const pjsip_authorization_hdr *hdr);
static int pjsip_authorization_hdr_print( pjsip_authorization_hdr *hdr,
char *buf, pj_size_t size);
static pjsip_hdr_vptr authorization_hdr_vptr =
{
(pjsip_hdr_clone_fptr) &pjsip_authorization_hdr_clone,
(pjsip_hdr_clone_fptr) &pjsip_authorization_hdr_shallow_clone,
(pjsip_hdr_print_fptr) &pjsip_authorization_hdr_print,
};
PJ_DEF(pjsip_authorization_hdr*) pjsip_authorization_hdr_create(pj_pool_t *pool)
{
pjsip_authorization_hdr *hdr;
hdr = PJ_POOL_ZALLOC_T(pool, pjsip_authorization_hdr);
init_hdr(hdr, PJSIP_H_AUTHORIZATION, &authorization_hdr_vptr);
pj_list_init(&hdr->credential.common.other_param);
return hdr;
}
PJ_DEF(pjsip_proxy_authorization_hdr*) pjsip_proxy_authorization_hdr_create(pj_pool_t *pool)
{
pjsip_proxy_authorization_hdr *hdr;
hdr = PJ_POOL_ZALLOC_T(pool, pjsip_proxy_authorization_hdr);
init_hdr(hdr, PJSIP_H_PROXY_AUTHORIZATION, &authorization_hdr_vptr);
pj_list_init(&hdr->credential.common.other_param);
return hdr;
}
static int print_digest_credential(pjsip_digest_credential *cred, char *buf, pj_size_t size)
{
pj_ssize_t printed;
char *startbuf = buf;
char *endbuf = buf + size;
const pjsip_parser_const_t *pc = pjsip_parser_const();
copy_advance_pair_quote_cond(buf, "username=", 9, cred->username, '"', '"');
copy_advance_pair_quote_cond_always(buf, ", realm=", 8, cred->realm, '"',
'"');
copy_advance_pair_quote(buf, ", nonce=", 8, cred->nonce, '"', '"');
copy_advance_pair_quote_cond(buf, ", uri=", 6, cred->uri, '"', '"');
copy_advance_pair_quote(buf, ", response=", 11, cred->response, '"', '"');
copy_advance_pair(buf, ", algorithm=", 12, cred->algorithm);
copy_advance_pair_quote_cond(buf, ", cnonce=", 9, cred->cnonce, '"', '"');
copy_advance_pair_quote_cond(buf, ", opaque=", 9, cred->opaque, '"', '"');
//Note: there's no dbl-quote in qop in Authorization header
// (unlike WWW-Authenticate)
//copy_advance_pair_quote_cond(buf, ", qop=", 6, cred->qop, '"', '"');
copy_advance_pair(buf, ", qop=", 6, cred->qop);
copy_advance_pair(buf, ", nc=", 5, cred->nc);
printed = pjsip_param_print_on(&cred->other_param, buf, endbuf-buf,
&pc->pjsip_TOKEN_SPEC,
&pc->pjsip_TOKEN_SPEC, ',');
if (printed < 0)
return -1;
buf += printed;
return (int) (buf-startbuf);
}
static int print_pgp_credential(pjsip_pgp_credential *cred, char *buf, pj_size_t size)
{
PJ_UNUSED_ARG(cred);
PJ_UNUSED_ARG(buf);
PJ_UNUSED_ARG(size);
return -1;
}
static int print_oauth_credential(pjsip_oauth_credential *cred, char *buf,
pj_size_t size)
{
pj_ssize_t printed;
char *startbuf = buf;
char *endbuf = buf + size;
copy_advance_pair_quote_cond_always(buf, "token=", 6, cred->token,
'"', '"');
copy_advance_pair_quote_cond_always(buf, ", username=", 11, cred->username,
'"', '"');
copy_advance_pair_quote_cond_always(buf, ", realm=", 8, cred->realm,
'"', '"');
return (int) (buf-startbuf);
}
static int pjsip_authorization_hdr_print( pjsip_authorization_hdr *hdr,
char *buf, pj_size_t size)
{
int printed;
char *startbuf = buf;
char *endbuf = buf + size;
copy_advance(buf, hdr->name);
*buf++ = ':';
*buf++ = ' ';
copy_advance(buf, hdr->scheme);
*buf++ = ' ';
if (pj_stricmp(&hdr->scheme, &pjsip_DIGEST_STR) == 0)
{
printed = print_digest_credential(&hdr->credential.digest, buf, endbuf - buf);
}
else if (pj_stricmp(&hdr->scheme, &pjsip_PGP_STR) == 0)
{
printed = print_pgp_credential(&hdr->credential.pgp, buf, endbuf - buf);
}
else if (pj_stricmp(&hdr->scheme, &pjsip_BEARER_STR) == 0)
{
printed = print_oauth_credential(&hdr->credential.oauth, buf,
endbuf - buf);
}
else {
pj_assert(0);
return -1;
}
if (printed == -1)
return -1;
buf += printed;
*buf = '\0';
return (int)(buf-startbuf);
}
static pjsip_authorization_hdr* pjsip_authorization_hdr_clone( pj_pool_t *pool,
const pjsip_authorization_hdr *rhs)
{
/* This function also serves Proxy-Authorization header. */
pjsip_authorization_hdr *hdr;
if (rhs->type == PJSIP_H_AUTHORIZATION)
hdr = pjsip_authorization_hdr_create(pool);
else
hdr = pjsip_proxy_authorization_hdr_create(pool);
pj_strdup(pool, &hdr->scheme, &rhs->scheme);
if (pj_stricmp2(&hdr->scheme, "digest") == 0) {
pj_strdup(pool, &hdr->credential.digest.username, &rhs->credential.digest.username);
pj_strdup(pool, &hdr->credential.digest.realm, &rhs->credential.digest.realm);
pj_strdup(pool, &hdr->credential.digest.nonce, &rhs->credential.digest.nonce);
pj_strdup(pool, &hdr->credential.digest.uri, &rhs->credential.digest.uri);
pj_strdup(pool, &hdr->credential.digest.response, &rhs->credential.digest.response);
pj_strdup(pool, &hdr->credential.digest.algorithm, &rhs->credential.digest.algorithm);
pj_strdup(pool, &hdr->credential.digest.cnonce, &rhs->credential.digest.cnonce);
pj_strdup(pool, &hdr->credential.digest.opaque, &rhs->credential.digest.opaque);
pj_strdup(pool, &hdr->credential.digest.qop, &rhs->credential.digest.qop);
pj_strdup(pool, &hdr->credential.digest.nc, &rhs->credential.digest.nc);
pjsip_param_clone(pool, &hdr->credential.digest.other_param, &rhs->credential.digest.other_param);
} else if (pj_stricmp2(&hdr->scheme, "pgp") == 0) {
pj_assert(0);
return NULL;
} else {
pj_assert(0);
return NULL;
}
return hdr;
}
static pjsip_authorization_hdr*
pjsip_authorization_hdr_shallow_clone( pj_pool_t *pool,
const pjsip_authorization_hdr *rhs)
{
/* This function also serves Proxy-Authorization header. */
pjsip_authorization_hdr *hdr;
hdr = PJ_POOL_ALLOC_T(pool, pjsip_authorization_hdr);
pj_memcpy(hdr, rhs, sizeof(*hdr));
pjsip_param_shallow_clone(pool, &hdr->credential.common.other_param,
&rhs->credential.common.other_param);
return hdr;
}
///////////////////////////////////////////////////////////////////////////////
/*
* Proxy-Authenticate and WWW-Authenticate header.
*/
static int pjsip_www_authenticate_hdr_print( pjsip_www_authenticate_hdr *hdr,
char *buf, pj_size_t size);
static pjsip_www_authenticate_hdr* pjsip_www_authenticate_hdr_clone( pj_pool_t *pool,
const pjsip_www_authenticate_hdr *hdr);
static pjsip_www_authenticate_hdr* pjsip_www_authenticate_hdr_shallow_clone( pj_pool_t *pool,
const pjsip_www_authenticate_hdr *hdr);
static pjsip_hdr_vptr www_authenticate_hdr_vptr =
{
(pjsip_hdr_clone_fptr) &pjsip_www_authenticate_hdr_clone,
(pjsip_hdr_clone_fptr) &pjsip_www_authenticate_hdr_shallow_clone,
(pjsip_hdr_print_fptr) &pjsip_www_authenticate_hdr_print,
};
PJ_DEF(pjsip_www_authenticate_hdr*) pjsip_www_authenticate_hdr_create(pj_pool_t *pool)
{
pjsip_www_authenticate_hdr *hdr;
hdr = PJ_POOL_ZALLOC_T(pool, pjsip_www_authenticate_hdr);
init_hdr(hdr, PJSIP_H_WWW_AUTHENTICATE, &www_authenticate_hdr_vptr);
pj_list_init(&hdr->challenge.common.other_param);
return hdr;
}
PJ_DEF(pjsip_proxy_authenticate_hdr*) pjsip_proxy_authenticate_hdr_create(pj_pool_t *pool)
{
pjsip_proxy_authenticate_hdr *hdr;
hdr = PJ_POOL_ZALLOC_T(pool, pjsip_proxy_authenticate_hdr);
init_hdr(hdr, PJSIP_H_PROXY_AUTHENTICATE, &www_authenticate_hdr_vptr);
pj_list_init(&hdr->challenge.common.other_param);
return hdr;
}
static int print_digest_challenge( pjsip_digest_challenge *chal,
char *buf, pj_size_t size)
{
pj_ssize_t printed;
char *startbuf = buf;
char *endbuf = buf + size;
const pjsip_parser_const_t *pc = pjsip_parser_const();
/* Allow empty realm, see http://trac.pjsip.org/repos/ticket/1061 */
copy_advance_pair_quote(buf, "realm=", 6, chal->realm, '"', '"');
copy_advance_pair_quote_cond(buf, ",domain=", 8, chal->domain, '"', '"');
copy_advance_pair_quote_cond(buf, ",nonce=", 7, chal->nonce, '"', '"');
copy_advance_pair_quote_cond(buf, ",opaque=", 8, chal->opaque, '"', '"');
if (chal->stale) {
pj_str_t true_str = { "true", 4 };
copy_advance_pair(buf, ",stale=", 7, true_str);
}
copy_advance_pair(buf, ",algorithm=", 11, chal->algorithm);
copy_advance_pair_quote_cond(buf, ",qop=", 5, chal->qop, '"', '"');
printed = pjsip_param_print_on(&chal->other_param, buf, endbuf-buf,
&pc->pjsip_TOKEN_SPEC,
&pc->pjsip_TOKEN_SPEC, ',');
if (printed < 0)
return -1;
buf += printed;
return (int)(buf-startbuf);
}
static int print_pgp_challenge( pjsip_pgp_challenge *chal,
char *buf, pj_size_t size)
{
PJ_UNUSED_ARG(chal);
PJ_UNUSED_ARG(buf);
PJ_UNUSED_ARG(size);
return -1;
}
static int pjsip_www_authenticate_hdr_print( pjsip_www_authenticate_hdr *hdr,
char *buf, pj_size_t size)
{
int printed;
char *startbuf = buf;
char *endbuf = buf + size;
copy_advance(buf, hdr->name);
*buf++ = ':';
*buf++ = ' ';
copy_advance(buf, hdr->scheme);
*buf++ = ' ';
if (pj_stricmp2(&hdr->scheme, "digest") == 0)
printed = print_digest_challenge(&hdr->challenge.digest, buf, endbuf - buf);
else if (pj_stricmp2(&hdr->scheme, "pgp") == 0)
printed = print_pgp_challenge(&hdr->challenge.pgp, buf, endbuf - buf);
else {
pj_assert(0);
return -1;
}
if (printed == -1)
return -1;
buf += printed;
*buf = '\0';
return (int)(buf-startbuf);
}
static pjsip_www_authenticate_hdr* pjsip_www_authenticate_hdr_clone( pj_pool_t *pool,
const pjsip_www_authenticate_hdr *rhs)
{
/* This function also serves Proxy-Authenticate header. */
pjsip_www_authenticate_hdr *hdr;
if (rhs->type == PJSIP_H_WWW_AUTHENTICATE)
hdr = pjsip_www_authenticate_hdr_create(pool);
else
hdr = pjsip_proxy_authenticate_hdr_create(pool);
pj_strdup(pool, &hdr->scheme, &rhs->scheme);
if (pj_stricmp2(&hdr->scheme, "digest") == 0) {
pj_strdup(pool, &hdr->challenge.digest.realm, &rhs->challenge.digest.realm);
pj_strdup(pool, &hdr->challenge.digest.domain, &rhs->challenge.digest.domain);
pj_strdup(pool, &hdr->challenge.digest.nonce, &rhs->challenge.digest.nonce);
pj_strdup(pool, &hdr->challenge.digest.opaque, &rhs->challenge.digest.opaque);
hdr->challenge.digest.stale = rhs->challenge.digest.stale;
pj_strdup(pool, &hdr->challenge.digest.algorithm, &rhs->challenge.digest.algorithm);
pj_strdup(pool, &hdr->challenge.digest.qop, &rhs->challenge.digest.qop);
pjsip_param_clone(pool, &hdr->challenge.digest.other_param,
&rhs->challenge.digest.other_param);
} else if (pj_stricmp2(&hdr->scheme, "pgp") == 0) {
pj_assert(0);
return NULL;
} else {
pj_assert(0);
return NULL;
}
return hdr;
}
static pjsip_www_authenticate_hdr* pjsip_www_authenticate_hdr_shallow_clone( pj_pool_t *pool,
const pjsip_www_authenticate_hdr *rhs)
{
/* This function also serves Proxy-Authenticate header. */
pjsip_www_authenticate_hdr *hdr;
hdr = PJ_POOL_ALLOC_T(pool, pjsip_www_authenticate_hdr);
pj_memcpy(hdr, rhs, sizeof(*hdr));
pjsip_param_shallow_clone(pool, &hdr->challenge.common.other_param,
&rhs->challenge.common.other_param);
return hdr;
}
@@ -0,0 +1,316 @@
/* $Id$ */
/*
* Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
* Copyright (C) 2003-2008 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/sip_auth_parser.h>
#include <pjsip/sip_auth_msg.h>
#include <pjsip/sip_parser.h>
#include <pj/assert.h>
#include <pj/string.h>
#include <pj/except.h>
#include <pj/pool.h>
static pjsip_hdr* parse_hdr_authorization ( pjsip_parse_ctx *ctx );
static pjsip_hdr* parse_hdr_proxy_authorization ( pjsip_parse_ctx *ctx );
static pjsip_hdr* parse_hdr_www_authenticate ( pjsip_parse_ctx *ctx );
static pjsip_hdr* parse_hdr_proxy_authenticate ( pjsip_parse_ctx *ctx );
static void parse_digest_credential ( pj_scanner *scanner, pj_pool_t *pool,
pjsip_digest_credential *cred);
static void parse_pgp_credential ( pj_scanner *scanner, pj_pool_t *pool,
pjsip_pgp_credential *cred);
static void parse_digest_challenge ( pj_scanner *scanner, pj_pool_t *pool,
pjsip_digest_challenge *chal);
static void parse_pgp_challenge ( pj_scanner *scanner, pj_pool_t *pool,
pjsip_pgp_challenge *chal);
const pj_str_t pjsip_USERNAME_STR = { "username", 8 },
pjsip_REALM_STR = { "realm", 5},
pjsip_NONCE_STR = { "nonce", 5},
pjsip_URI_STR = { "uri", 3 },
pjsip_RESPONSE_STR = { "response", 8 },
pjsip_ALGORITHM_STR = { "algorithm", 9 },
pjsip_DOMAIN_STR = { "domain", 6 },
pjsip_STALE_STR = { "stale", 5},
pjsip_QOP_STR = { "qop", 3},
pjsip_CNONCE_STR = { "cnonce", 6},
pjsip_OPAQUE_STR = { "opaque", 6},
pjsip_NC_STR = { "nc", 2},
pjsip_TRUE_STR = { "true", 4},
pjsip_QUOTED_TRUE_STR = { "\"true\"", 6},
pjsip_FALSE_STR = { "false", 5},
pjsip_QUOTED_FALSE_STR = { "\"false\"", 7},
pjsip_DIGEST_STR = { "Digest", 6},
pjsip_QUOTED_DIGEST_STR = { "\"Digest\"", 8},
pjsip_PGP_STR = { "PGP", 3 },
pjsip_QUOTED_PGP_STR = { "\"PGP\"", 5 },
pjsip_BEARER_STR = { "Bearer", 6 },
pjsip_MD5_STR = { "md5", 3 },
pjsip_QUOTED_MD5_STR = { "\"md5\"", 5},
pjsip_AUTH_STR = { "auth", 4},
pjsip_QUOTED_AUTH_STR = { "\"auth\"", 6 };
static void parse_digest_credential( pj_scanner *scanner, pj_pool_t *pool,
pjsip_digest_credential *cred)
{
pj_list_init(&cred->other_param);
for (;;) {
pj_str_t name, value;
pjsip_parse_param_imp(scanner, pool, &name, &value,
PJSIP_PARSE_REMOVE_QUOTE);
if (!pj_stricmp(&name, &pjsip_USERNAME_STR)) {
cred->username = value;
} else if (!pj_stricmp(&name, &pjsip_REALM_STR)) {
cred->realm = value;
} else if (!pj_stricmp(&name, &pjsip_NONCE_STR)) {
cred->nonce = value;
} else if (!pj_stricmp(&name, &pjsip_URI_STR)) {
cred->uri = value;
} else if (!pj_stricmp(&name, &pjsip_RESPONSE_STR)) {
cred->response = value;
} else if (!pj_stricmp(&name, &pjsip_ALGORITHM_STR)) {
cred->algorithm = value;
} else if (!pj_stricmp(&name, &pjsip_CNONCE_STR)) {
cred->cnonce = value;
} else if (!pj_stricmp(&name, &pjsip_OPAQUE_STR)) {
cred->opaque = value;
} else if (!pj_stricmp(&name, &pjsip_QOP_STR)) {
cred->qop = value;
} else if (!pj_stricmp(&name, &pjsip_NC_STR)) {
cred->nc = value;
} else {
pjsip_param *p = PJ_POOL_ALLOC_T(pool, pjsip_param);
p->name = name;
p->value = value;
pj_list_insert_before(&cred->other_param, p);
}
/* Eat comma */
if (!pj_scan_is_eof(scanner) && *scanner->curptr == ',')
pj_scan_get_char(scanner);
else
break;
}
}
static void parse_pgp_credential( pj_scanner *scanner, pj_pool_t *pool,
pjsip_pgp_credential *cred)
{
PJ_UNUSED_ARG(scanner);
PJ_UNUSED_ARG(pool);
PJ_UNUSED_ARG(cred);
PJ_THROW(PJSIP_SYN_ERR_EXCEPTION);
}
static void parse_digest_challenge( pj_scanner *scanner, pj_pool_t *pool,
pjsip_digest_challenge *chal)
{
pj_list_init(&chal->other_param);
for (;;) {
pj_str_t name, value, unquoted_value;
pjsip_parse_param_imp(scanner, pool, &name, &value, 0);
if (value.ptr && (value.ptr[0] == '"')) {
unquoted_value.ptr = value.ptr + 1;
unquoted_value.slen = value.slen - 2;
} else {
unquoted_value.ptr = value.ptr;
unquoted_value.slen = value.slen;
}
if (!pj_stricmp(&name, &pjsip_REALM_STR)) {
chal->realm = unquoted_value;
} else if (!pj_stricmp(&name, &pjsip_DOMAIN_STR)) {
chal->domain = unquoted_value;
} else if (!pj_stricmp(&name, &pjsip_NONCE_STR)) {
chal->nonce = unquoted_value;
} else if (!pj_stricmp(&name, &pjsip_OPAQUE_STR)) {
chal->opaque = unquoted_value;
} else if (!pj_stricmp(&name, &pjsip_STALE_STR)) {
if (!pj_stricmp(&value, &pjsip_TRUE_STR) ||
!pj_stricmp(&value, &pjsip_QUOTED_TRUE_STR))
{
chal->stale = 1;
}
} else if (!pj_stricmp(&name, &pjsip_ALGORITHM_STR)) {
chal->algorithm = unquoted_value;
} else if (!pj_stricmp(&name, &pjsip_QOP_STR)) {
chal->qop = unquoted_value;
} else {
pjsip_param *p = PJ_POOL_ALLOC_T(pool, pjsip_param);
p->name = name;
p->value = value;
pj_list_insert_before(&chal->other_param, p);
}
/* Eat comma */
if (!pj_scan_is_eof(scanner) && *scanner->curptr == ',')
pj_scan_get_char(scanner);
else
break;
}
}
static void parse_pgp_challenge( pj_scanner *scanner, pj_pool_t *pool,
pjsip_pgp_challenge *chal)
{
PJ_UNUSED_ARG(scanner);
PJ_UNUSED_ARG(pool);
PJ_UNUSED_ARG(chal);
PJ_THROW(PJSIP_SYN_ERR_EXCEPTION);
}
static void int_parse_hdr_authorization( pj_scanner *scanner, pj_pool_t *pool,
pjsip_authorization_hdr *hdr)
{
const pjsip_parser_const_t *pc = pjsip_parser_const();
if (*scanner->curptr == '"') {
pj_scan_get_quote(scanner, '"', '"', &hdr->scheme);
hdr->scheme.ptr++;
hdr->scheme.slen -= 2;
} else {
pj_scan_get(scanner, &pc->pjsip_TOKEN_SPEC, &hdr->scheme);
}
if (!pj_stricmp(&hdr->scheme, &pjsip_DIGEST_STR)) {
parse_digest_credential(scanner, pool, &hdr->credential.digest);
} else if (!pj_stricmp(&hdr->scheme, &pjsip_PGP_STR)) {
parse_pgp_credential( scanner, pool, &hdr->credential.pgp);
} else {
PJ_THROW(PJSIP_SYN_ERR_EXCEPTION);
}
pjsip_parse_end_hdr_imp( scanner );
}
static void int_parse_hdr_authenticate( pj_scanner *scanner, pj_pool_t *pool,
pjsip_www_authenticate_hdr *hdr)
{
const pjsip_parser_const_t *pc = pjsip_parser_const();
if (*scanner->curptr == '"') {
pj_scan_get_quote(scanner, '"', '"', &hdr->scheme);
hdr->scheme.ptr++;
hdr->scheme.slen -= 2;
} else {
pj_scan_get(scanner, &pc->pjsip_TOKEN_SPEC, &hdr->scheme);
}
if (!pj_stricmp(&hdr->scheme, &pjsip_DIGEST_STR)) {
parse_digest_challenge(scanner, pool, &hdr->challenge.digest);
} else if (!pj_stricmp(&hdr->scheme, &pjsip_PGP_STR)) {
parse_pgp_challenge(scanner, pool, &hdr->challenge.pgp);
} else {
PJ_THROW(PJSIP_SYN_ERR_EXCEPTION);
}
pjsip_parse_end_hdr_imp( scanner );
}
static pjsip_hdr* parse_hdr_authorization( pjsip_parse_ctx *ctx )
{
pjsip_authorization_hdr *hdr = pjsip_authorization_hdr_create(ctx->pool);
int_parse_hdr_authorization(ctx->scanner, ctx->pool, hdr);
return (pjsip_hdr*)hdr;
}
static pjsip_hdr* parse_hdr_proxy_authorization( pjsip_parse_ctx *ctx )
{
pjsip_proxy_authorization_hdr *hdr =
pjsip_proxy_authorization_hdr_create(ctx->pool);
int_parse_hdr_authorization(ctx->scanner, ctx->pool, hdr);
return (pjsip_hdr*)hdr;
}
static pjsip_hdr* parse_hdr_www_authenticate( pjsip_parse_ctx *ctx )
{
pjsip_www_authenticate_hdr *hdr =
pjsip_www_authenticate_hdr_create(ctx->pool);
int_parse_hdr_authenticate(ctx->scanner, ctx->pool, hdr);
return (pjsip_hdr*)hdr;
}
static pjsip_hdr* parse_hdr_proxy_authenticate( pjsip_parse_ctx *ctx )
{
pjsip_proxy_authenticate_hdr *hdr =
pjsip_proxy_authenticate_hdr_create(ctx->pool);
int_parse_hdr_authenticate(ctx->scanner, ctx->pool, hdr);
return (pjsip_hdr*)hdr;
}
PJ_DEF(pj_status_t) pjsip_auth_init_parser()
{
pj_status_t status;
status = pjsip_register_hdr_parser( "Authorization", NULL,
&parse_hdr_authorization);
PJ_ASSERT_RETURN(status==PJ_SUCCESS, status);
status = pjsip_register_hdr_parser( "Proxy-Authorization", NULL,
&parse_hdr_proxy_authorization);
PJ_ASSERT_RETURN(status==PJ_SUCCESS, status);
status = pjsip_register_hdr_parser( "WWW-Authenticate", NULL,
&parse_hdr_www_authenticate);
PJ_ASSERT_RETURN(status==PJ_SUCCESS, status);
status = pjsip_register_hdr_parser( "Proxy-Authenticate", NULL,
&parse_hdr_proxy_authenticate);
PJ_ASSERT_RETURN(status==PJ_SUCCESS, status);
return PJ_SUCCESS;
}
PJ_DEF(void) pjsip_auth_deinit_parser()
{
}
@@ -0,0 +1,24 @@
/* $Id$ */
/*
* Copyright (C) 2009-2011 Teluu Inc. (http://www.teluu.com)
*
* 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
*/
/*
* This file is a C++ wrapper, see ticket #886 for details.
*/
#include "sip_auth_parser.c"
@@ -0,0 +1,260 @@
/* $Id$ */
/*
* Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
* Copyright (C) 2003-2008 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/sip_auth.h>
#include <pjsip/sip_auth_parser.h> /* just to get pjsip_DIGEST_STR */
#include <pjsip/sip_auth_msg.h>
#include <pjsip/sip_errno.h>
#include <pjsip/sip_transport.h>
#include <pj/string.h>
#include <pj/assert.h>
/*
* Initialize server authorization session data structure to serve the
* specified realm and to use lookup_func function to look for the credential
* info.
*/
PJ_DEF(pj_status_t) pjsip_auth_srv_init( pj_pool_t *pool,
pjsip_auth_srv *auth_srv,
const pj_str_t *realm,
pjsip_auth_lookup_cred *lookup,
unsigned options )
{
PJ_ASSERT_RETURN(pool && auth_srv && realm && lookup, PJ_EINVAL);
pj_bzero(auth_srv, sizeof(*auth_srv));
pj_strdup( pool, &auth_srv->realm, realm);
auth_srv->lookup = lookup;
auth_srv->is_proxy = (options & PJSIP_AUTH_SRV_IS_PROXY);
return PJ_SUCCESS;
}
/*
* Initialize server authorization session data structure to serve the
* specified realm and to use lookup_func function to look for the credential
* info.
*/
PJ_DEF(pj_status_t) pjsip_auth_srv_init2(
pj_pool_t *pool,
pjsip_auth_srv *auth_srv,
const pjsip_auth_srv_init_param *param)
{
PJ_ASSERT_RETURN(pool && auth_srv && param, PJ_EINVAL);
pj_bzero(auth_srv, sizeof(*auth_srv));
pj_strdup( pool, &auth_srv->realm, param->realm);
auth_srv->lookup2 = param->lookup2;
auth_srv->is_proxy = (param->options & PJSIP_AUTH_SRV_IS_PROXY);
return PJ_SUCCESS;
}
/* Verify incoming Authorization/Proxy-Authorization header against the
* specified credential.
*/
static pj_status_t pjsip_auth_verify( const pjsip_authorization_hdr *hdr,
const pj_str_t *method,
const pjsip_cred_info *cred_info )
{
if (pj_stricmp(&hdr->scheme, &pjsip_DIGEST_STR) == 0) {
char digest_buf[PJSIP_MD5STRLEN];
pj_str_t digest;
const pjsip_digest_credential *dig = &hdr->credential.digest;
/* Check that username and realm match.
* These checks should have been performed before entering this
* function.
*/
PJ_ASSERT_RETURN(pj_strcmp(&dig->username, &cred_info->username) == 0,
PJ_EINVALIDOP);
PJ_ASSERT_RETURN(pj_strcmp(&dig->realm, &cred_info->realm) == 0,
PJ_EINVALIDOP);
/* Prepare for our digest calculation. */
digest.ptr = digest_buf;
digest.slen = PJSIP_MD5STRLEN;
/* Create digest for comparison. */
pjsip_auth_create_digest(&digest,
&hdr->credential.digest.nonce,
&hdr->credential.digest.nc,
&hdr->credential.digest.cnonce,
&hdr->credential.digest.qop,
&hdr->credential.digest.uri,
&cred_info->realm,
cred_info,
method );
/* Compare digest. */
return (pj_stricmp(&digest, &hdr->credential.digest.response) == 0) ?
PJ_SUCCESS : PJSIP_EAUTHINVALIDDIGEST;
} else {
pj_assert(!"Unsupported authentication scheme");
return PJSIP_EINVALIDAUTHSCHEME;
}
}
/*
* Request the authorization server framework to verify the authorization
* information in the specified request in rdata.
*/
PJ_DEF(pj_status_t) pjsip_auth_srv_verify( pjsip_auth_srv *auth_srv,
pjsip_rx_data *rdata,
int *status_code)
{
pjsip_authorization_hdr *h_auth;
pjsip_msg *msg = rdata->msg_info.msg;
pjsip_hdr_e htype;
pj_str_t acc_name;
pjsip_cred_info cred_info;
pj_status_t status;
PJ_ASSERT_RETURN(auth_srv && rdata, PJ_EINVAL);
PJ_ASSERT_RETURN(msg->type == PJSIP_REQUEST_MSG, PJSIP_ENOTREQUESTMSG);
htype = auth_srv->is_proxy ? PJSIP_H_PROXY_AUTHORIZATION :
PJSIP_H_AUTHORIZATION;
/* Initialize status with 200. */
*status_code = 200;
/* Find authorization header for our realm. */
h_auth = (pjsip_authorization_hdr*) pjsip_msg_find_hdr(msg, htype, NULL);
while (h_auth) {
if (!pj_stricmp(&h_auth->credential.common.realm, &auth_srv->realm))
break;
h_auth = h_auth->next;
if (h_auth == (void*) &msg->hdr) {
h_auth = NULL;
break;
}
h_auth=(pjsip_authorization_hdr*)pjsip_msg_find_hdr(msg,htype,h_auth);
}
if (!h_auth) {
*status_code = auth_srv->is_proxy ? 407 : 401;
return PJSIP_EAUTHNOAUTH;
}
/* Check authorization scheme. */
if (pj_stricmp(&h_auth->scheme, &pjsip_DIGEST_STR) == 0)
acc_name = h_auth->credential.digest.username;
else {
*status_code = auth_srv->is_proxy ? 407 : 401;
return PJSIP_EINVALIDAUTHSCHEME;
}
/* Find the credential information for the account. */
if (auth_srv->lookup2) {
pjsip_auth_lookup_cred_param param;
pj_bzero(&param, sizeof(param));
param.realm = auth_srv->realm;
param.acc_name = acc_name;
param.rdata = rdata;
status = (*auth_srv->lookup2)(rdata->tp_info.pool, &param, &cred_info);
if (status != PJ_SUCCESS) {
*status_code = PJSIP_SC_FORBIDDEN;
return status;
}
} else {
status = (*auth_srv->lookup)(rdata->tp_info.pool, &auth_srv->realm,
&acc_name, &cred_info);
if (status != PJ_SUCCESS) {
*status_code = PJSIP_SC_FORBIDDEN;
return status;
}
}
/* Authenticate with the specified credential. */
status = pjsip_auth_verify(h_auth, &msg->line.req.method.name,
&cred_info);
if (status != PJ_SUCCESS) {
*status_code = PJSIP_SC_FORBIDDEN;
}
return status;
}
/*
* Add authentication challenge headers to the outgoing response in tdata.
* Application may specify its customized nonce and opaque for the challenge,
* or can leave the value to NULL to make the function fills them in with
* random characters.
*/
PJ_DEF(pj_status_t) pjsip_auth_srv_challenge( pjsip_auth_srv *auth_srv,
const pj_str_t *qop,
const pj_str_t *nonce,
const pj_str_t *opaque,
pj_bool_t stale,
pjsip_tx_data *tdata)
{
pjsip_www_authenticate_hdr *hdr;
char nonce_buf[16];
pj_str_t random;
PJ_ASSERT_RETURN( auth_srv && tdata, PJ_EINVAL );
random.ptr = nonce_buf;
random.slen = sizeof(nonce_buf);
/* Create the header. */
if (auth_srv->is_proxy)
hdr = pjsip_proxy_authenticate_hdr_create(tdata->pool);
else
hdr = pjsip_www_authenticate_hdr_create(tdata->pool);
/* Initialize header.
* Note: only support digest authentication now.
*/
hdr->scheme = pjsip_DIGEST_STR;
hdr->challenge.digest.algorithm = pjsip_MD5_STR;
if (nonce) {
pj_strdup(tdata->pool, &hdr->challenge.digest.nonce, nonce);
} else {
pj_create_random_string(nonce_buf, sizeof(nonce_buf));
pj_strdup(tdata->pool, &hdr->challenge.digest.nonce, &random);
}
if (opaque) {
pj_strdup(tdata->pool, &hdr->challenge.digest.opaque, opaque);
} else {
pj_create_random_string(nonce_buf, sizeof(nonce_buf));
pj_strdup(tdata->pool, &hdr->challenge.digest.opaque, &random);
}
if (qop) {
pj_strdup(tdata->pool, &hdr->challenge.digest.qop, qop);
} else {
hdr->challenge.digest.qop.slen = 0;
}
pj_strdup(tdata->pool, &hdr->challenge.digest.realm, &auth_srv->realm);
hdr->challenge.digest.stale = stale;
pjsip_msg_add_hdr(tdata->msg, (pjsip_hdr*)hdr);
return PJ_SUCCESS;
}
@@ -0,0 +1,73 @@
/* $Id$ */
/*
* Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
* Copyright (C) 2003-2008 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/sip_config.h>
/* pjsip configuration instance, initialized with default values */
pjsip_cfg_t pjsip_sip_cfg_var =
{
/* Global settings */
{
PJSIP_ALLOW_PORT_IN_FROMTO_HDR,
PJSIP_ACCEPT_REPLACE_IN_EARLY_STATE,
0,
0,
PJSIP_DONT_SWITCH_TO_TCP,
PJSIP_DONT_SWITCH_TO_TLS,
PJSIP_FOLLOW_EARLY_MEDIA_FORK,
PJSIP_REQ_HAS_VIA_ALIAS,
PJSIP_RESOLVE_HOSTNAME_TO_GET_INTERFACE,
0,
PJSIP_ENCODE_SHORT_HNAME,
PJSIP_ACCEPT_MULTIPLE_SDP_ANSWERS
},
/* Transaction settings */
{
PJSIP_MAX_TSX_COUNT,
PJSIP_T1_TIMEOUT,
PJSIP_T2_TIMEOUT,
PJSIP_T4_TIMEOUT,
PJSIP_TD_TIMEOUT
},
/* Client registration client */
{
PJSIP_REGISTER_CLIENT_CHECK_CONTACT
},
/* TCP transport settings */
{
PJSIP_TCP_KEEP_ALIVE_INTERVAL
},
/* TLS transport settings */
{
PJSIP_TLS_KEEP_ALIVE_INTERVAL
}
};
#ifdef PJ_DLL
PJ_DEF(pjsip_cfg_t*) pjsip_cfg(void)
{
return &pjsip_sip_cfg_var;
}
#endif
File diff suppressed because it is too large. Load diff
@@ -0,0 +1,24 @@
/* $Id$ */
/*
* Copyright (C) 2009-2011 Teluu Inc. (http://www.teluu.com)
*
* 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
*/
/*
* This file is a C++ wrapper, see ticket #886 for details.
*/
#include "sip_dialog.c"
File diff suppressed because it is too large. Load diff
@@ -0,0 +1,24 @@
/* $Id$ */
/*
* Copyright (C) 2009-2011 Teluu Inc. (http://www.teluu.com)
*
* 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
*/
/*
* This file is a C++ wrapper, see ticket #886 for details.
*/
#include "sip_endpoint.c"
@@ -0,0 +1,212 @@
/* $Id$ */
/*
* Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
* Copyright (C) 2003-2008 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/sip_errno.h>
#include <pjsip/sip_msg.h>
#include <pj/string.h>
#include <pj/errno.h>
/* PJSIP's own error codes/messages
* MUST KEEP THIS ARRAY SORTED!!
*/
#if defined(PJ_HAS_ERROR_STRING) && (PJ_HAS_ERROR_STRING != 0)
static const struct
{
int code;
const char *msg;
} err_str[] =
{
/* Generic SIP errors */
PJ_BUILD_ERR( PJSIP_EBUSY, "Object is busy" ),
PJ_BUILD_ERR( PJSIP_ETYPEEXISTS , "Object with the same type exists" ),
PJ_BUILD_ERR( PJSIP_ESHUTDOWN, "SIP stack shutting down" ),
PJ_BUILD_ERR( PJSIP_ENOTINITIALIZED,"SIP object is not initialized." ),
PJ_BUILD_ERR( PJSIP_ENOROUTESET, "Missing route set (for tel: URI)" ),
/* Messaging errors */
PJ_BUILD_ERR( PJSIP_EINVALIDMSG, "Invalid message/syntax error" ),
PJ_BUILD_ERR( PJSIP_ENOTREQUESTMSG, "Expecting request message"),
PJ_BUILD_ERR( PJSIP_ENOTRESPONSEMSG,"Expecting response message"),
PJ_BUILD_ERR( PJSIP_EMSGTOOLONG, "Message too long" ),
PJ_BUILD_ERR( PJSIP_EPARTIALMSG, "Partial message" ),
PJ_BUILD_ERR( PJSIP_EINVALIDSTATUS, "Invalid/unexpected SIP status code"),
PJ_BUILD_ERR( PJSIP_EINVALIDURI, "Invalid URI" ),
PJ_BUILD_ERR( PJSIP_EINVALIDSCHEME, "Invalid URI scheme" ),
PJ_BUILD_ERR( PJSIP_EMISSINGREQURI, "Missing Request-URI" ),
PJ_BUILD_ERR( PJSIP_EINVALIDREQURI, "Invalid Request URI" ),
PJ_BUILD_ERR( PJSIP_EURITOOLONG, "URI is too long" ),
PJ_BUILD_ERR( PJSIP_EMISSINGHDR, "Missing required header(s)" ),
PJ_BUILD_ERR( PJSIP_EINVALIDHDR, "Invalid header field"),
PJ_BUILD_ERR( PJSIP_EINVALIDVIA, "Invalid Via header" ),
PJ_BUILD_ERR( PJSIP_EMULTIPLEVIA, "Multiple Via headers in response" ),
PJ_BUILD_ERR( PJSIP_EMISSINGBODY, "Missing message body" ),
PJ_BUILD_ERR( PJSIP_EINVALIDMETHOD, "Invalid/unexpected method" ),
/* Transport errors */
PJ_BUILD_ERR( PJSIP_EUNSUPTRANSPORT,"Unsupported transport"),
PJ_BUILD_ERR( PJSIP_EPENDINGTX, "Transmit buffer already pending"),
PJ_BUILD_ERR( PJSIP_ERXOVERFLOW, "Rx buffer overflow"),
PJ_BUILD_ERR( PJSIP_EBUFDESTROYED, "Buffer destroyed"),
PJ_BUILD_ERR( PJSIP_ETPNOTSUITABLE, "Unsuitable transport selected"),
PJ_BUILD_ERR( PJSIP_ETPNOTAVAIL, "Transport not available for use"),
/* Transaction errors */
PJ_BUILD_ERR( PJSIP_ETSXDESTROYED, "Transaction has been destroyed"),
PJ_BUILD_ERR( PJSIP_ENOTSX, "No transaction is associated with the object "
"(expecting stateful processing)" ),
/* URI comparison status */
PJ_BUILD_ERR( PJSIP_ECMPSCHEME, "URI scheme mismatch" ),
PJ_BUILD_ERR( PJSIP_ECMPUSER, "URI user part mismatch" ),
PJ_BUILD_ERR( PJSIP_ECMPPASSWD, "URI password part mismatch" ),
PJ_BUILD_ERR( PJSIP_ECMPHOST, "URI host part mismatch" ),
PJ_BUILD_ERR( PJSIP_ECMPPORT, "URI port mismatch" ),
PJ_BUILD_ERR( PJSIP_ECMPTRANSPORTPRM,"URI transport param mismatch" ),
PJ_BUILD_ERR( PJSIP_ECMPTTLPARAM, "URI ttl param mismatch" ),
PJ_BUILD_ERR( PJSIP_ECMPUSERPARAM, "URI user param mismatch" ),
PJ_BUILD_ERR( PJSIP_ECMPMETHODPARAM,"URI method param mismatch" ),
PJ_BUILD_ERR( PJSIP_ECMPMADDRPARAM, "URI maddr param mismatch" ),
PJ_BUILD_ERR( PJSIP_ECMPOTHERPARAM, "URI other param mismatch" ),
PJ_BUILD_ERR( PJSIP_ECMPHEADERPARAM,"URI header parameter mismatch" ),
/* Authentication. */
PJ_BUILD_ERR( PJSIP_EFAILEDCREDENTIAL, "Credential failed to authenticate"),
PJ_BUILD_ERR( PJSIP_ENOCREDENTIAL, "No suitable credential"),
PJ_BUILD_ERR( PJSIP_EINVALIDALGORITHM, "Invalid/unsupported digest algorithm" ),
PJ_BUILD_ERR( PJSIP_EINVALIDQOP, "Invalid/unsupported digest qop" ),
PJ_BUILD_ERR( PJSIP_EINVALIDAUTHSCHEME,"Unsupported authentication scheme" ),
PJ_BUILD_ERR( PJSIP_EAUTHNOPREVCHAL, "No previous challenge" ),
PJ_BUILD_ERR( PJSIP_EAUTHNOAUTH, "No suitable authorization header" ),
PJ_BUILD_ERR( PJSIP_EAUTHACCNOTFOUND, "Account or credential not found" ),
PJ_BUILD_ERR( PJSIP_EAUTHACCDISABLED, "Account or credential is disabled" ),
PJ_BUILD_ERR( PJSIP_EAUTHINVALIDREALM, "Invalid authorization realm"),
PJ_BUILD_ERR( PJSIP_EAUTHINVALIDDIGEST,"Invalid authorization digest" ),
PJ_BUILD_ERR( PJSIP_EAUTHSTALECOUNT, "Maximum number of stale retries exceeded"),
PJ_BUILD_ERR( PJSIP_EAUTHINNONCE, "Invalid nonce value in authentication challenge"),
PJ_BUILD_ERR( PJSIP_EAUTHINAKACRED, "Invalid AKA credential"),
PJ_BUILD_ERR( PJSIP_EAUTHNOCHAL, "No challenge is found"),
/* UA/dialog layer. */
PJ_BUILD_ERR( PJSIP_EMISSINGTAG, "Missing From/To tag parameter" ),
PJ_BUILD_ERR( PJSIP_ENOTREFER, "Expecting REFER request") ,
PJ_BUILD_ERR( PJSIP_ENOREFERSESSION,"Not associated with REFER subscription"),
/* Invite session. */
PJ_BUILD_ERR( PJSIP_ESESSIONTERMINATED, "INVITE session already terminated" ),
PJ_BUILD_ERR( PJSIP_ESESSIONSTATE, "Invalid INVITE session state" ),
PJ_BUILD_ERR( PJSIP_ESESSIONINSECURE, "Require secure session/transport"),
/* SSL errors */
PJ_BUILD_ERR( PJSIP_TLS_EUNKNOWN, "Unknown TLS error" ),
PJ_BUILD_ERR( PJSIP_TLS_EINVMETHOD, "Invalid SSL protocol method" ),
PJ_BUILD_ERR( PJSIP_TLS_ECACERT, "Error loading/verifying SSL CA list file"),
PJ_BUILD_ERR( PJSIP_TLS_ECERTFILE, "Error loading SSL certificate chain file"),
PJ_BUILD_ERR( PJSIP_TLS_EKEYFILE, "Error adding private key from SSL certificate file"),
PJ_BUILD_ERR( PJSIP_TLS_ECIPHER, "Error setting SSL cipher list"),
PJ_BUILD_ERR( PJSIP_TLS_ECTX, "Error creating SSL context"),
PJ_BUILD_ERR( PJSIP_TLS_ESSLCONN, "Error creating SSL connection object"),
PJ_BUILD_ERR( PJSIP_TLS_ECONNECT, "Unknown error when performing SSL connect()"),
PJ_BUILD_ERR( PJSIP_TLS_EACCEPT, "Unknown error when performing SSL accept()"),
PJ_BUILD_ERR( PJSIP_TLS_ESEND, "Unknown error when sending SSL data"),
PJ_BUILD_ERR( PJSIP_TLS_EREAD, "Unknown error when reading SSL data"),
PJ_BUILD_ERR( PJSIP_TLS_ETIMEDOUT, "SSL negotiation has timed out"),
PJ_BUILD_ERR( PJSIP_TLS_ECERTVERIF, "SSL certificate verification error"),
};
#endif /* PJ_HAS_ERROR_STRING */
/*
* pjsip_strerror()
*/
PJ_DEF(pj_str_t) pjsip_strerror( pj_status_t statcode,
char *buf, pj_size_t bufsize )
{
pj_str_t errstr;
#if defined(PJ_HAS_ERROR_STRING) && (PJ_HAS_ERROR_STRING != 0)
if (statcode >= PJSIP_ERRNO_START && statcode < PJSIP_ERRNO_START+800)
{
/* Status code. */
const pj_str_t *status_text =
pjsip_get_status_text(PJSIP_ERRNO_TO_SIP_STATUS(statcode));
errstr.ptr = buf;
pj_strncpy_with_null(&errstr, status_text, bufsize);
return errstr;
}
else if (statcode >= PJSIP_ERRNO_START_PJSIP &&
statcode < PJSIP_ERRNO_START_PJSIP + 1000)
{
/* Find the error in the table.
* Use binary search!
*/
int first = 0;
int n = PJ_ARRAY_SIZE(err_str);
while (n > 0) {
int half = n/2;
int mid = first + half;
if (err_str[mid].code < statcode) {
first = mid+1;
n -= (half+1);
} else if (err_str[mid].code > statcode) {
n = half;
} else {
first = mid;
break;
}
}
if (PJ_ARRAY_SIZE(err_str) && err_str[first].code == statcode) {
pj_str_t msg;
msg.ptr = (char*)err_str[first].msg;
msg.slen = pj_ansi_strlen(err_str[first].msg);
errstr.ptr = buf;
pj_strncpy_with_null(&errstr, &msg, bufsize);
return errstr;
}
}
#endif /* PJ_HAS_ERROR_STRING */
/* Error not found. */
errstr.ptr = buf;
errstr.slen = pj_ansi_snprintf(buf, bufsize,
"Unknown pjsip error %d",
statcode);
if (errstr.slen < 1 || errstr.slen >= (pj_ssize_t)bufsize)
errstr.slen = bufsize - 1;
return errstr;
}
File diff suppressed because it is too large. Load diff
@@ -0,0 +1,702 @@
/* $Id$ */
/*
* Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
*
* 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/sip_multipart.h>
#include <pjsip/sip_parser.h>
#include <pjlib-util/scanner.h>
#include <pj/assert.h>
#include <pj/ctype.h>
#include <pj/errno.h>
#include <pj/except.h>
#include <pj/guid.h>
#include <pj/log.h>
#include <pj/pool.h>
#include <pj/string.h>
#define THIS_FILE "sip_multipart.c"
#define IS_SPACE(c) ((c)==' ' || (c)=='\t')
#if 0
# define TRACE_(x) PJ_LOG(4,x)
#else
# define TRACE_(x)
#endif
/* Type of "data" in multipart pjsip_msg_body */
struct multipart_data
{
pj_str_t boundary;
pjsip_multipart_part part_head;
pj_str_t raw_data;
};
static int multipart_print_body(struct pjsip_msg_body *msg_body,
char *buf, pj_size_t size)
{
const struct multipart_data *m_data;
pj_str_t clen_hdr = { "Content-Length: ", 16};
pjsip_multipart_part *part;
char *p = buf, *end = buf+size;
#define SIZE_LEFT() (end-p)
m_data = (const struct multipart_data*)msg_body->data;
PJ_ASSERT_RETURN(m_data && !pj_list_empty(&m_data->part_head), PJ_EINVAL);
part = m_data->part_head.next;
while (part != &m_data->part_head) {
enum { CLEN_SPACE = 5 };
char *clen_pos;
const pjsip_hdr *hdr;
pj_bool_t ctype_printed = PJ_FALSE;
clen_pos = NULL;
/* Print delimiter */
if (SIZE_LEFT() <= (m_data->boundary.slen+8) << 1)
return -1;
*p++ = 13; *p++ = 10; *p++ = '-'; *p++ = '-';
pj_memcpy(p, m_data->boundary.ptr, m_data->boundary.slen);
p += m_data->boundary.slen;
*p++ = 13; *p++ = 10;
/* Print optional headers */
hdr = part->hdr.next;
while (hdr != &part->hdr) {
int printed = pjsip_hdr_print_on((pjsip_hdr*)hdr, p,
SIZE_LEFT()-2);
if (printed < 0)
return -1;
p += printed;
*p++ = '\r';
*p++ = '\n';
if (!ctype_printed && hdr->type == PJSIP_H_CONTENT_TYPE)
ctype_printed = PJ_TRUE;
hdr = hdr->next;
}
/* Automaticly adds Content-Type and Content-Length headers, only
* if content_type is set in the message body and haven't been printed.
*/
if (part->body && part->body->content_type.type.slen && !ctype_printed)
{
pj_str_t ctype_hdr = { "Content-Type: ", 14};
const pjsip_media_type *media = &part->body->content_type;
if (pjsip_cfg()->endpt.use_compact_form) {
ctype_hdr.ptr = "c: ";
ctype_hdr.slen = 3;
}
/* Add Content-Type header. */
if ( (end-p) < 24 + media->type.slen + media->subtype.slen) {
return -1;
}
pj_memcpy(p, ctype_hdr.ptr, ctype_hdr.slen);
p += ctype_hdr.slen;
p += pjsip_media_type_print(p, (unsigned)(end-p), media);
*p++ = '\r';
*p++ = '\n';
/* Add Content-Length header. */
if ((end-p) < clen_hdr.slen + 12 + 2) {
return -1;
}
pj_memcpy(p, clen_hdr.ptr, clen_hdr.slen);
p += clen_hdr.slen;
/* Print blanks after "Content-Length:", this is where we'll put
* the content length value after we know the length of the
* body.
*/
pj_memset(p, ' ', CLEN_SPACE);
clen_pos = p;
p += CLEN_SPACE;
*p++ = '\r';
*p++ = '\n';
}
/* Empty newline */
*p++ = 13; *p++ = 10;
/* Print the body */
pj_assert(part->body != NULL);
if (part->body) {
int printed = part->body->print_body(part->body, p, SIZE_LEFT());
if (printed < 0)
return -1;
p += printed;
/* Now that we have the length of the body, print this to the
* Content-Length header.
*/
if (clen_pos) {
char tmp[16];
int len;
len = pj_utoa(printed, tmp);
if (len > CLEN_SPACE) len = CLEN_SPACE;
pj_memcpy(clen_pos+CLEN_SPACE-len, tmp, len);
}
}
part = part->next;
}
/* Print closing delimiter */
if (SIZE_LEFT() < m_data->boundary.slen+8)
return -1;
*p++ = 13; *p++ = 10; *p++ = '-'; *p++ = '-';
pj_memcpy(p, m_data->boundary.ptr, m_data->boundary.slen);
p += m_data->boundary.slen;
*p++ = '-'; *p++ = '-'; *p++ = 13; *p++ = 10;
#undef SIZE_LEFT
return (int)(p - buf);
}
static void* multipart_clone_data(pj_pool_t *pool, const void *data,
unsigned len)
{
const struct multipart_data *src;
struct multipart_data *dst;
const pjsip_multipart_part *src_part;
PJ_UNUSED_ARG(len);
src = (const struct multipart_data*) data;
dst = PJ_POOL_ALLOC_T(pool, struct multipart_data);
pj_list_init(&dst->part_head);
pj_strdup(pool, &dst->boundary, &src->boundary);
src_part = src->part_head.next;
while (src_part != &src->part_head) {
pjsip_multipart_part *dst_part;
const pjsip_hdr *src_hdr;
dst_part = pjsip_multipart_create_part(pool);
src_hdr = src_part->hdr.next;
while (src_hdr != &src_part->hdr) {
pjsip_hdr *dst_hdr = (pjsip_hdr*)pjsip_hdr_clone(pool, src_hdr);
pj_list_push_back(&dst_part->hdr, dst_hdr);
src_hdr = src_hdr->next;
}
dst_part->body = pjsip_msg_body_clone(pool, src_part->body);
pj_list_push_back(&dst->part_head, dst_part);
src_part = src_part->next;
}
return (void*)dst;
}
/*
* Create an empty multipart body.
*/
PJ_DEF(pjsip_msg_body*) pjsip_multipart_create( pj_pool_t *pool,
const pjsip_media_type *ctype,
const pj_str_t *boundary)
{
pjsip_msg_body *body;
pjsip_param *ctype_param;
struct multipart_data *mp_data;
pj_str_t STR_BOUNDARY = { "boundary", 8 };
PJ_ASSERT_RETURN(pool, NULL);
body = PJ_POOL_ZALLOC_T(pool, pjsip_msg_body);
/* content-type */
if (ctype && ctype->type.slen) {
pjsip_media_type_cp(pool, &body->content_type, ctype);
} else {
pj_str_t STR_MULTIPART = {"multipart", 9};
pj_str_t STR_MIXED = { "mixed", 5 };
pjsip_media_type_init(&body->content_type,
&STR_MULTIPART, &STR_MIXED);
}
/* multipart data */
mp_data = PJ_POOL_ZALLOC_T(pool, struct multipart_data);
pj_list_init(&mp_data->part_head);
if (boundary) {
pj_strdup(pool, &mp_data->boundary, boundary);
} else {
pj_create_unique_string(pool, &mp_data->boundary);
}
body->data = mp_data;
/* Add ";boundary" parameter to content_type parameter. */
ctype_param = pjsip_param_find(&body->content_type.param, &STR_BOUNDARY);
if (!ctype_param) {
ctype_param = PJ_POOL_ALLOC_T(pool, pjsip_param);
ctype_param->name = STR_BOUNDARY;
pj_list_push_back(&body->content_type.param, ctype_param);
}
ctype_param->value = mp_data->boundary;
/* function pointers */
body->print_body = &multipart_print_body;
body->clone_data = &multipart_clone_data;
return body;
}
/*
* Create an empty multipart part.
*/
PJ_DEF(pjsip_multipart_part*) pjsip_multipart_create_part(pj_pool_t *pool)
{
pjsip_multipart_part *mp;
mp = PJ_POOL_ZALLOC_T(pool, pjsip_multipart_part);
pj_list_init(&mp->hdr);
return mp;
}
/*
* Deep clone.
*/
PJ_DEF(pjsip_multipart_part*)
pjsip_multipart_clone_part(pj_pool_t *pool,
const pjsip_multipart_part *src)
{
pjsip_multipart_part *dst;
const pjsip_hdr *hdr;
dst = pjsip_multipart_create_part(pool);
hdr = src->hdr.next;
while (hdr != &src->hdr) {
pj_list_push_back(&dst->hdr, pjsip_hdr_clone(pool, hdr));
hdr = hdr->next;
}
dst->body = pjsip_msg_body_clone(pool, src->body);
return dst;
}
/*
* Add a part into multipart bodies.
*/
PJ_DEF(pj_status_t) pjsip_multipart_add_part( pj_pool_t *pool,
pjsip_msg_body *mp,
pjsip_multipart_part *part)
{
struct multipart_data *m_data;
/* All params must be specified */
PJ_ASSERT_RETURN(pool && mp && part, PJ_EINVAL);
/* mp must really point to an actual multipart msg body */
PJ_ASSERT_RETURN(mp->print_body==&multipart_print_body, PJ_EINVAL);
/* The multipart part must contain a valid message body */
PJ_ASSERT_RETURN(part->body && part->body->print_body, PJ_EINVAL);
m_data = (struct multipart_data*)mp->data;
pj_list_push_back(&m_data->part_head, part);
PJ_UNUSED_ARG(pool);
return PJ_SUCCESS;
}
/*
* Get the first part of multipart bodies.
*/
PJ_DEF(pjsip_multipart_part*)
pjsip_multipart_get_first_part(const pjsip_msg_body *mp)
{
struct multipart_data *m_data;
/* Must specify mandatory params */
PJ_ASSERT_RETURN(mp, NULL);
/* mp must really point to an actual multipart msg body */
PJ_ASSERT_RETURN(mp->print_body==&multipart_print_body, NULL);
m_data = (struct multipart_data*)mp->data;
if (pj_list_empty(&m_data->part_head))
return NULL;
return m_data->part_head.next;
}
/*
* Get the next part after the specified part.
*/
PJ_DEF(pjsip_multipart_part*)
pjsip_multipart_get_next_part(const pjsip_msg_body *mp,
pjsip_multipart_part *part)
{
struct multipart_data *m_data;
/* Must specify mandatory params */
PJ_ASSERT_RETURN(mp && part, NULL);
/* mp must really point to an actual multipart msg body */
PJ_ASSERT_RETURN(mp->print_body==&multipart_print_body, NULL);
m_data = (struct multipart_data*)mp->data;
/* the part parameter must be really member of the list */
PJ_ASSERT_RETURN(pj_list_find_node(&m_data->part_head, part) != NULL,
NULL);
if (part->next == &m_data->part_head)
return NULL;
return part->next;
}
/*
* Find a body inside multipart bodies which has the specified content type.
*/
PJ_DEF(pjsip_multipart_part*)
pjsip_multipart_find_part( const pjsip_msg_body *mp,
const pjsip_media_type *content_type,
const pjsip_multipart_part *start)
{
struct multipart_data *m_data;
pjsip_multipart_part *part;
/* Must specify mandatory params */
PJ_ASSERT_RETURN(mp && content_type, NULL);
/* mp must really point to an actual multipart msg body */
PJ_ASSERT_RETURN(mp->print_body==&multipart_print_body, NULL);
m_data = (struct multipart_data*)mp->data;
if (start)
part = start->next;
else
part = m_data->part_head.next;
while (part != &m_data->part_head) {
if (pjsip_media_type_cmp(&part->body->content_type,
content_type, 0)==0)
{
return part;
}
part = part->next;
}
return NULL;
}
/* Parse a multipart part. "pct" is parent content-type */
static pjsip_multipart_part *parse_multipart_part(pj_pool_t *pool,
char *start,
pj_size_t len,
const pjsip_media_type *pct)
{
pjsip_multipart_part *part = pjsip_multipart_create_part(pool);
char *p = start, *end = start+len, *end_hdr = NULL, *start_body = NULL;
pjsip_ctype_hdr *ctype_hdr = NULL;
TRACE_((THIS_FILE, "Parsing part: begin--\n%.*s\n--end",
(int)len, start));
/* Find the end of header area, by looking at an empty line */
for (;;) {
while (p!=end && *p!='\n') ++p;
if (p==end) {
start_body = end;
break;
}
if ((p==start) || (p==start+1 && *(p-1)=='\r')) {
/* Empty header section */
end_hdr = start;
start_body = ++p;
break;
} else if (p==end-1) {
/* Empty body section */
end_hdr = end;
start_body = ++p;
} else if ((p>=start+1 && *(p-1)=='\n') ||
(p>=start+2 && *(p-1)=='\r' && *(p-2)=='\n'))
{
/* Found it */
end_hdr = (*(p-1)=='\r') ? (p-1) : p;
start_body = ++p;
break;
} else {
++p;
}
}
/* Parse the headers */
if (end_hdr-start > 0) {
pjsip_hdr *hdr;
pj_status_t status;
status = pjsip_parse_headers(pool, start, end_hdr-start,
&part->hdr, 0);
if (status != PJ_SUCCESS) {
PJ_PERROR(2,(THIS_FILE, status, "Warning: error parsing multipart"
" header"));
}
/* Find Content-Type header */
hdr = part->hdr.next;
while (hdr != &part->hdr) {
TRACE_((THIS_FILE, "Header parsed: %.*s", (int)hdr->name.slen,
hdr->name.ptr));
if (hdr->type == PJSIP_H_CONTENT_TYPE) {
ctype_hdr = (pjsip_ctype_hdr*)hdr;
}
hdr = hdr->next;
}
}
/* Assign the body */
part->body = PJ_POOL_ZALLOC_T(pool, pjsip_msg_body);
if (ctype_hdr) {
pjsip_media_type_cp(pool, &part->body->content_type, &ctype_hdr->media);
} else if (pct && pj_stricmp2(&pct->subtype, "digest")==0) {
part->body->content_type.type = pj_str("message");
part->body->content_type.subtype = pj_str("rfc822");
} else {
part->body->content_type.type = pj_str("text");
part->body->content_type.subtype = pj_str("plain");
}
if (start_body < end) {
part->body->data = start_body;
part->body->len = (unsigned)(end - start_body);
} else {
part->body->data = (void*)"";
part->body->len = 0;
}
TRACE_((THIS_FILE, "Body parsed: \"%.*s\"", (int)part->body->len,
part->body->data));
part->body->print_body = &pjsip_print_text_body;
part->body->clone_data = &pjsip_clone_text_data;
return part;
}
/* Public function to parse multipart message bodies into its parts */
PJ_DEF(pjsip_msg_body*) pjsip_multipart_parse(pj_pool_t *pool,
char *buf, pj_size_t len,
const pjsip_media_type *ctype,
unsigned options)
{
pj_str_t boundary, delim;
char *curptr, *endptr;
const pjsip_param *ctype_param;
const pj_str_t STR_BOUNDARY = { "boundary", 8 };
pjsip_msg_body *body = NULL;
PJ_ASSERT_RETURN(pool && buf && len && ctype && !options, NULL);
TRACE_((THIS_FILE, "Started parsing multipart body"));
/* Get the boundary value in the ctype */
boundary.ptr = NULL;
boundary.slen = 0;
ctype_param = pjsip_param_find(&ctype->param, &STR_BOUNDARY);
if (ctype_param) {
boundary = ctype_param->value;
if (boundary.slen>2 && *boundary.ptr=='"') {
/* Remove quote */
boundary.ptr++;
boundary.slen -= 2;
}
TRACE_((THIS_FILE, "Boundary is specified: '%.*s'", (int)boundary.slen,
boundary.ptr));
}
if (!boundary.slen) {
/* Boundary not found or not specified. Try to be clever, get
* the boundary from the body.
*/
char *p=buf, *end=buf+len;
PJ_LOG(4,(THIS_FILE, "Warning: boundary parameter not found or "
"not specified when parsing multipart body"));
/* Find the first "--". This "--" must be right after a CRLF, unless
* it really appears at the start of the buffer.
*/
for (;;) {
while (p!=end && *p!='-') ++p;
if (p == end)
break;
if ((p+1<end) && *(p+1)=='-' &&
((p>buf && *(p-1)=='\n') || (p==buf)))
{
p+=2;
break;
} else {
++p;
}
}
if (p==end) {
/* Unable to determine boundary. Maybe this is not a multipart
* message?
*/
PJ_LOG(4,(THIS_FILE, "Error: multipart boundary not specified and"
" unable to calculate from the body"));
return NULL;
}
boundary.ptr = p;
while (p!=end && !pj_isspace(*p)) ++p;
boundary.slen = p - boundary.ptr;
TRACE_((THIS_FILE, "Boundary is calculated: '%.*s'",
(int)boundary.slen, boundary.ptr));
}
/* Build the delimiter:
* delimiter = "--" boundary
*/
delim.slen = boundary.slen+2;
delim.ptr = (char*)pj_pool_alloc(pool, (int)delim.slen);
delim.ptr[0] = '-';
delim.ptr[1] = '-';
pj_memcpy(delim.ptr+2, boundary.ptr, boundary.slen);
/* Start parsing the body, skip until the first delimiter. */
curptr = buf;
endptr = buf + len;
{
pj_str_t strbody;
strbody.ptr = buf; strbody.slen = len;
curptr = pj_strstr(&strbody, &delim);
if (!curptr)
return NULL;
}
body = pjsip_multipart_create(pool, ctype, &boundary);
/* Save full raw body */
{
struct multipart_data *mp = (struct multipart_data*)body->data;
pj_strset(&mp->raw_data, buf, len);
}
for (;;) {
char *start_body, *end_body;
pjsip_multipart_part *part;
/* Eat the boundary */
curptr += delim.slen;
if (*curptr=='-' && curptr<endptr-1 && *(curptr+1)=='-') {
/* Found the closing delimiter */
curptr += 2;
break;
}
/* Optional whitespace after delimiter */
while (curptr!=endptr && IS_SPACE(*curptr)) ++curptr;
/* Mandatory CRLF */
if (*curptr=='\r') ++curptr;
if (*curptr!='\n') {
/* Expecting a newline here */
return NULL;
}
++curptr;
/* We now in the start of the body */
start_body = curptr;
/* Find the next delimiter */
{
pj_str_t subbody;
subbody.ptr = curptr; subbody.slen = endptr - curptr;
curptr = pj_strstr(&subbody, &delim);
if (!curptr) {
/* We're really expecting end delimiter to be found. */
return NULL;
}
}
end_body = curptr;
/* Note that when body is empty, end_body will be equal
* to start_body.
*/
if (end_body > start_body) {
/* The newline preceeding the delimiter is conceptually part of
* the delimiter, so trim it from the body.
*/
if (*(end_body-1) == '\n')
--end_body;
if (end_body > start_body && *(end_body-1) == '\r')
--end_body;
}
/* Now that we have determined the part's boundary, parse it
* to get the header and body part of the part.
*/
part = parse_multipart_part(pool, start_body, end_body - start_body,
ctype);
if (part) {
pjsip_multipart_add_part(pool, body, part);
}
}
return body;
}
PJ_DEF(pj_status_t) pjsip_multipart_get_raw( pjsip_msg_body *mp,
pj_str_t *boundary,
pj_str_t *raw_data)
{
struct multipart_data *m_data;
/* Must specify mandatory params */
PJ_ASSERT_RETURN(mp, PJ_EINVAL);
/* mp must really point to an actual multipart msg body */
PJ_ASSERT_RETURN(mp->print_body==&multipart_print_body, PJ_EINVAL);
m_data = (struct multipart_data*)mp->data;
if (boundary)
*boundary = m_data->boundary;
if (raw_data)
*raw_data = m_data->raw_data;
return PJ_SUCCESS;
}
File diff suppressed because it is too large. Load diff
@@ -0,0 +1,24 @@
/* $Id$ */
/*
* Copyright (C) 2009-2011 Teluu Inc. (http://www.teluu.com)
*
* 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
*/
/*
* This file is a C++ wrapper, see ticket #886 for details.
*/
#include "sip_parser.c"
@@ -0,0 +1,696 @@
/* $Id$ */
/*
* Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
* Copyright (C) 2003-2008 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/sip_resolve.h>
#include <pjsip/sip_transport.h>
#include <pjsip/sip_errno.h>
#include <pjlib-util/errno.h>
#include <pjlib-util/srv_resolver.h>
#include <pj/addr_resolv.h>
#include <pj/array.h>
#include <pj/assert.h>
#include <pj/ctype.h>
#include <pj/log.h>
#include <pj/pool.h>
#include <pj/rand.h>
#include <pj/string.h>
#define THIS_FILE "sip_resolve.c"
struct naptr_target
{
pj_str_t res_type; /**< e.g. "_sip._udp" */
pj_str_t name; /**< Domain name. */
pjsip_transport_type_e type; /**< Transport type. */
unsigned order; /**< Order */
unsigned pref; /**< Preference. */
};
struct query
{
char *objname;
pj_dns_type query_type;
void *token;
pjsip_resolver_callback *cb;
pj_dns_async_query *object;
pj_dns_async_query *object6;
pj_status_t last_error;
/* Original request: */
struct {
pjsip_host_info target;
unsigned def_port;
} req;
/* NAPTR records: */
unsigned naptr_cnt;
struct naptr_target naptr[8];
/* Query result */
pjsip_server_addresses server;
};
struct pjsip_resolver_t
{
pj_dns_resolver *res;
pjsip_ext_resolver *ext_res;
};
static void srv_resolver_cb(void *user_data,
pj_status_t status,
const pj_dns_srv_record *rec);
static void dns_a_callback(void *user_data,
pj_status_t status,
pj_dns_parsed_packet *response);
static void dns_aaaa_callback(void *user_data,
pj_status_t status,
pj_dns_parsed_packet *response);
/*
* Public API to create the resolver.
*/
PJ_DEF(pj_status_t) pjsip_resolver_create( pj_pool_t *pool,
pjsip_resolver_t **p_res)
{
pjsip_resolver_t *resolver;
PJ_ASSERT_RETURN(pool && p_res, PJ_EINVAL);
resolver = PJ_POOL_ZALLOC_T(pool, pjsip_resolver_t);
*p_res = resolver;
return PJ_SUCCESS;
}
/*
* Public API to set the DNS resolver instance for the SIP resolver.
*/
PJ_DEF(pj_status_t) pjsip_resolver_set_resolver(pjsip_resolver_t *res,
pj_dns_resolver *dns_res)
{
#if PJSIP_HAS_RESOLVER
res->res = dns_res;
return PJ_SUCCESS;
#else
PJ_UNUSED_ARG(res);
PJ_UNUSED_ARG(dns_res);
pj_assert(!"Resolver is disabled (PJSIP_HAS_RESOLVER==0)");
return PJ_EINVALIDOP;
#endif
}
/*
* Public API to set the DNS external resolver implementation for the SIP
* resolver.
*/
PJ_DEF(pj_status_t) pjsip_resolver_set_ext_resolver(pjsip_resolver_t *res,
pjsip_ext_resolver *ext_res)
{
if (ext_res && !ext_res->resolve)
return PJ_EINVAL;
if (ext_res && res->res) {
#if PJSIP_HAS_RESOLVER
pj_dns_resolver_destroy(res->res, PJ_FALSE);
#endif
res->res = NULL;
}
res->ext_res = ext_res;
return PJ_SUCCESS;
}
/*
* Public API to get the internal DNS resolver.
*/
PJ_DEF(pj_dns_resolver*) pjsip_resolver_get_resolver(pjsip_resolver_t *res)
{
return res->res;
}
/*
* Public API to create destroy the resolver
*/
PJ_DEF(void) pjsip_resolver_destroy(pjsip_resolver_t *resolver)
{
if (resolver->res) {
#if PJSIP_HAS_RESOLVER
pj_dns_resolver_destroy(resolver->res, PJ_FALSE);
#endif
resolver->res = NULL;
}
}
/*
* Internal:
* determine if an address is a valid IP address, and if it is,
* return the IP version (4 or 6).
*/
static int get_ip_addr_ver(const pj_str_t *host)
{
pj_in_addr dummy;
pj_in6_addr dummy6;
/* First check if this is an IPv4 address */
if (pj_inet_pton(pj_AF_INET(), host, &dummy) == PJ_SUCCESS)
return 4;
/* Then check if this is an IPv6 address */
if (pj_inet_pton(pj_AF_INET6(), host, &dummy6) == PJ_SUCCESS)
return 6;
/* Not an IP address */
return 0;
}
/*
* This is the main function for performing server resolution.
*/
PJ_DEF(void) pjsip_resolve( pjsip_resolver_t *resolver,
pj_pool_t *pool,
const pjsip_host_info *target,
void *token,
pjsip_resolver_callback *cb)
{
pjsip_server_addresses svr_addr;
pj_status_t status = PJ_SUCCESS;
int ip_addr_ver;
struct query *query;
pjsip_transport_type_e type = target->type;
int af = pj_AF_UNSPEC();
/* If an external implementation has been provided use it instead */
if (resolver->ext_res) {
(*resolver->ext_res->resolve)(resolver, pool, target, token, cb);
return;
}
/* Is it IP address or hostname? And if it's an IP, which version? */
ip_addr_ver = get_ip_addr_ver(&target->addr.host);
/* Initialize address family type. Unfortunately, target type doesn't
* really tell the address family type, except when IPv6 flag is
* explicitly set.
*/
#if defined(PJ_HAS_IPV6) && PJ_HAS_IPV6==1
if ((ip_addr_ver == 6) || (type & PJSIP_TRANSPORT_IPV6))
af = pj_AF_INET6();
else if (ip_addr_ver == 4)
af = pj_AF_INET();
#else
/* IPv6 is disabled, will resolving IPv6 address be useful? */
af = pj_AF_INET();
#endif
/* Set the transport type if not explicitly specified.
* RFC 3263 section 4.1 specify rules to set up this.
*/
if (type == PJSIP_TRANSPORT_UNSPECIFIED) {
if (ip_addr_ver || (target->addr.port != 0)) {
#if PJ_HAS_TCP
if (target->flag & PJSIP_TRANSPORT_SECURE)
{
type = PJSIP_TRANSPORT_TLS;
} else if (target->flag & PJSIP_TRANSPORT_RELIABLE)
{
type = PJSIP_TRANSPORT_TCP;
} else
#endif
{
type = PJSIP_TRANSPORT_UDP;
}
} else {
/* No type or explicit port is specified, and the address is
* not IP address.
* In this case, full NAPTR resolution must be performed.
* But we don't support it (yet).
*/
#if PJ_HAS_TCP
if (target->flag & PJSIP_TRANSPORT_SECURE)
{
type = PJSIP_TRANSPORT_TLS;
} else if (target->flag & PJSIP_TRANSPORT_RELIABLE)
{
type = PJSIP_TRANSPORT_TCP;
} else
#endif
{
type = PJSIP_TRANSPORT_UDP;
}
}
}
/* If target is an IP address, or if resolver is not configured,
* we can just finish the resolution now using pj_gethostbyname()
*/
if (ip_addr_ver || resolver->res == NULL) {
char addr_str[PJ_INET6_ADDRSTRLEN+10];
pj_uint16_t srv_port;
unsigned i;
if (ip_addr_ver != 0) {
/* Target is an IP address, no need to resolve */
svr_addr.count = 1;
if (ip_addr_ver == 4) {
if (af == pj_AF_INET6()) {
/* Generate a synthesized IPv6 address, if possible. */
unsigned int count = 1;
pj_addrinfo ai[1];
pj_status_t status2;
status2 = pj_getaddrinfo(pj_AF_INET6(),
&target->addr.host, &count, ai);
if (status2 == PJ_SUCCESS && count > 0 &&
ai[0].ai_addr.addr.sa_family == pj_AF_INET6())
{
pj_sockaddr_init(pj_AF_INET6(),
&svr_addr.entry[0].addr,
NULL, 0);
svr_addr.entry[0].addr.ipv6.sin6_addr =
ai[0].ai_addr.ipv6.sin6_addr;
} else {
pj_sockaddr_init(pj_AF_INET(),
&svr_addr.entry[0].addr, NULL, 0);
pj_inet_pton(pj_AF_INET(), &target->addr.host,
&svr_addr.entry[0].addr.ipv4.sin_addr);
}
} else {
pj_sockaddr_init(pj_AF_INET(), &svr_addr.entry[0].addr,
NULL, 0);
pj_inet_pton(pj_AF_INET(), &target->addr.host,
&svr_addr.entry[0].addr.ipv4.sin_addr);
}
} else {
pj_sockaddr_init(pj_AF_INET6(), &svr_addr.entry[0].addr,
NULL, 0);
pj_inet_pton(pj_AF_INET6(), &target->addr.host,
&svr_addr.entry[0].addr.ipv6.sin6_addr);
}
} else {
pj_addrinfo ai[PJSIP_MAX_RESOLVED_ADDRESSES];
unsigned count;
PJ_LOG(5,(THIS_FILE,
"DNS resolver not available, target '%.*s:%d' type=%s "
"will be resolved with getaddrinfo()",
target->addr.host.slen,
target->addr.host.ptr,
target->addr.port,
pjsip_transport_get_type_name(target->type)));
/* Resolve */
count = PJSIP_MAX_RESOLVED_ADDRESSES;
status = pj_getaddrinfo(af, &target->addr.host, &count, ai);
if (status != PJ_SUCCESS) {
/* "Normalize" error to PJ_ERESOLVE. This is a special error
* because it will be translated to SIP status 502 by
* sip_transaction.c
*/
status = PJ_ERESOLVE;
goto on_error;
}
svr_addr.count = count;
for (i = 0; i < count; i++) {
pj_sockaddr_cp(&svr_addr.entry[i].addr, &ai[i].ai_addr);
}
}
for (i = 0; i < svr_addr.count; i++) {
/* After address resolution, update IPv6 bitflag in
* transport type.
*/
af = svr_addr.entry[i].addr.addr.sa_family;
if (af == pj_AF_INET6()) {
type |= PJSIP_TRANSPORT_IPV6;
} else {
type &= ~PJSIP_TRANSPORT_IPV6;
}
/* Set the port number */
if (target->addr.port == 0) {
srv_port = (pj_uint16_t)
pjsip_transport_get_default_port_for_type(type);
} else {
srv_port = (pj_uint16_t)target->addr.port;
}
pj_sockaddr_set_port(&svr_addr.entry[i].addr, srv_port);
PJ_LOG(5,(THIS_FILE,
"Target '%.*s:%d' type=%s resolved to "
"'%s' type=%s (%s)",
(int)target->addr.host.slen,
target->addr.host.ptr,
target->addr.port,
pjsip_transport_get_type_name(target->type),
pj_sockaddr_print(&svr_addr.entry[i].addr, addr_str,
sizeof(addr_str), 3),
pjsip_transport_get_type_name(type),
pjsip_transport_get_type_desc(type)));
svr_addr.entry[i].priority = 0;
svr_addr.entry[i].weight = 0;
svr_addr.entry[i].type = type;
svr_addr.entry[i].addr_len =
pj_sockaddr_get_len(&svr_addr.entry[i].addr);
}
/* Call the callback. */
(*cb)(status, token, &svr_addr);
/* Done. */
return;
}
/* Target is not an IP address so we need to resolve it. */
#if PJSIP_HAS_RESOLVER
/* Build the query state */
query = PJ_POOL_ZALLOC_T(pool, struct query);
query->objname = THIS_FILE;
query->token = token;
query->cb = cb;
query->req.target = *target;
pj_strdup(pool, &query->req.target.addr.host, &target->addr.host);
/* If port is not specified, start with SRV resolution
* (should be with NAPTR, but we'll do that later)
*/
PJ_TODO(SUPPORT_DNS_NAPTR);
/* Build dummy NAPTR entry */
query->naptr_cnt = 1;
pj_bzero(&query->naptr[0], sizeof(query->naptr[0]));
query->naptr[0].order = 0;
query->naptr[0].pref = 0;
query->naptr[0].type = type;
pj_strdup(pool, &query->naptr[0].name, &target->addr.host);
/* Start DNS SRV or A resolution, depending on whether port is specified */
if (target->addr.port == 0) {
query->query_type = PJ_DNS_TYPE_SRV;
query->req.def_port = 5060;
if (type == PJSIP_TRANSPORT_TLS || type == PJSIP_TRANSPORT_TLS6) {
query->naptr[0].res_type = pj_str("_sips._tcp.");
query->req.def_port = 5061;
} else if (type == PJSIP_TRANSPORT_TCP || type == PJSIP_TRANSPORT_TCP6)
query->naptr[0].res_type = pj_str("_sip._tcp.");
else if (type == PJSIP_TRANSPORT_UDP || type == PJSIP_TRANSPORT_UDP6)
query->naptr[0].res_type = pj_str("_sip._udp.");
else {
pj_assert(!"Unknown transport type");
query->naptr[0].res_type = pj_str("_sip._udp.");
}
} else {
/* Otherwise if port is specified, start with A (or AAAA) host
* resolution
*/
query->query_type = PJ_DNS_TYPE_A;
query->naptr[0].res_type.slen = 0;
query->req.def_port = target->addr.port;
}
/* Start the asynchronous query */
PJ_LOG(5, (query->objname,
"Starting async DNS %s query: target=%.*s%.*s, transport=%s, "
"port=%d",
pj_dns_get_type_name(query->query_type),
(int)query->naptr[0].res_type.slen,
query->naptr[0].res_type.ptr,
(int)query->naptr[0].name.slen, query->naptr[0].name.ptr,
pjsip_transport_get_type_name(target->type),
target->addr.port));
if (query->query_type == PJ_DNS_TYPE_SRV) {
int opt = 0;
if (af == pj_AF_UNSPEC())
opt = PJ_DNS_SRV_FALLBACK_A | PJ_DNS_SRV_FALLBACK_AAAA |
PJ_DNS_SRV_RESOLVE_AAAA;
else if (af == pj_AF_INET6())
opt = PJ_DNS_SRV_FALLBACK_AAAA | PJ_DNS_SRV_RESOLVE_AAAA_ONLY;
else /* af == pj_AF_INET() */
opt = PJ_DNS_SRV_FALLBACK_A;
status = pj_dns_srv_resolve(&query->naptr[0].name,
&query->naptr[0].res_type,
query->req.def_port, pool, resolver->res,
opt, query, &srv_resolver_cb, NULL);
} else if (query->query_type == PJ_DNS_TYPE_A) {
/* Resolve DNS A record if address family is not fixed to IPv6 */
if (af != pj_AF_INET6()) {
/* If there will be DNS AAAA query too, let's setup a dummy one
* here, otherwise app callback may be called immediately (before
* DNS AAAA query is sent) when DNS A record is available in the
* cache.
*/
if (af == pj_AF_UNSPEC())
query->object6 = (pj_dns_async_query*)0x1;
status = pj_dns_resolver_start_query(resolver->res,
&query->naptr[0].name,
PJ_DNS_TYPE_A, 0,
&dns_a_callback,
query, &query->object);
}
/* Resolve DNS AAAA record if address family is not fixed to IPv4 */
if (af != pj_AF_INET() && status == PJ_SUCCESS) {
status = pj_dns_resolver_start_query(resolver->res,
&query->naptr[0].name,
PJ_DNS_TYPE_AAAA, 0,
&dns_aaaa_callback,
query, &query->object6);
}
} else {
pj_assert(!"Unexpected");
status = PJ_EBUG;
}
if (status != PJ_SUCCESS)
goto on_error;
return;
#else /* PJSIP_HAS_RESOLVER */
PJ_UNUSED_ARG(pool);
PJ_UNUSED_ARG(query);
#endif /* PJSIP_HAS_RESOLVER */
on_error:
if (status != PJ_SUCCESS) {
PJ_PERROR(4,(THIS_FILE, status,
"Failed to resolve '%.*s'",
(int)target->addr.host.slen,
target->addr.host.ptr));
(*cb)(status, token, NULL);
return;
}
}
#if PJSIP_HAS_RESOLVER
/*
* This callback is called when target is resolved with DNS A query.
*/
static void dns_a_callback(void *user_data,
pj_status_t status,
pj_dns_parsed_packet *pkt)
{
struct query *query = (struct query*) user_data;
pjsip_server_addresses *srv = &query->server;
/* Reset outstanding job */
query->object = NULL;
if (status == PJ_SUCCESS) {
pj_dns_addr_record rec;
unsigned i;
/* Parse the response */
rec.addr_count = 0;
status = pj_dns_parse_addr_response(pkt, &rec);
/* Build server addresses and call callback */
for (i = 0; i < rec.addr_count &&
srv->count < PJSIP_MAX_RESOLVED_ADDRESSES; ++i)
{
/* Should not happen, just in case */
if (rec.addr[i].af != pj_AF_INET())
continue;
srv->entry[srv->count].type = query->naptr[0].type;
srv->entry[srv->count].priority = 0;
srv->entry[srv->count].weight = 0;
srv->entry[srv->count].addr_len = sizeof(pj_sockaddr_in);
pj_sockaddr_in_init(&srv->entry[srv->count].addr.ipv4,
0, (pj_uint16_t)query->req.def_port);
srv->entry[srv->count].addr.ipv4.sin_addr = rec.addr[i].ip.v4;
++srv->count;
}
}
if (status != PJ_SUCCESS) {
PJ_PERROR(4,(query->objname, status,
"DNS A record resolution failed"));
query->last_error = status;
}
/* Call the callback if all DNS queries have been completed */
if (query->object == NULL && query->object6 == NULL) {
if (srv->count > 0)
(*query->cb)(PJ_SUCCESS, query->token, &query->server);
else
(*query->cb)(query->last_error, query->token, NULL);
}
}
/*
* This callback is called when target is resolved with DNS AAAA query.
*/
static void dns_aaaa_callback(void *user_data,
pj_status_t status,
pj_dns_parsed_packet *pkt)
{
struct query *query = (struct query*) user_data;
pjsip_server_addresses *srv = &query->server;
/* Reset outstanding job */
query->object6 = NULL;
if (status == PJ_SUCCESS) {
pj_dns_addr_record rec;
unsigned i;
/* Parse the response */
rec.addr_count = 0;
status = pj_dns_parse_addr_response(pkt, &rec);
/* Build server addresses and call callback */
for (i = 0; i < rec.addr_count &&
srv->count < PJSIP_MAX_RESOLVED_ADDRESSES; ++i)
{
/* Should not happen, just in case */
if (rec.addr[i].af != pj_AF_INET6())
continue;
srv->entry[srv->count].type = query->naptr[0].type |
PJSIP_TRANSPORT_IPV6;
srv->entry[srv->count].priority = 0;
srv->entry[srv->count].weight = 0;
srv->entry[srv->count].addr_len = sizeof(pj_sockaddr_in6);
pj_sockaddr_init(pj_AF_INET6(), &srv->entry[srv->count].addr,
0, (pj_uint16_t)query->req.def_port);
srv->entry[srv->count].addr.ipv6.sin6_addr = rec.addr[i].ip.v6;
++srv->count;
}
}
if (status != PJ_SUCCESS) {
PJ_PERROR(4,(query->objname, status,
"DNS AAAA record resolution failed"));
query->last_error = status;
}
/* Call the callback if all DNS queries have been completed */
if (query->object == NULL && query->object6 == NULL) {
if (srv->count > 0)
(*query->cb)(PJ_SUCCESS, query->token, &query->server);
else
(*query->cb)(query->last_error, query->token, NULL);
}
}
/* Callback to be called by DNS SRV resolution */
static void srv_resolver_cb(void *user_data,
pj_status_t status,
const pj_dns_srv_record *rec)
{
struct query *query = (struct query*) user_data;
pjsip_server_addresses srv;
unsigned i;
if (status != PJ_SUCCESS) {
PJ_PERROR(4,(query->objname, status,
"DNS A/AAAA record resolution failed"));
/* Call the callback */
(*query->cb)(status, query->token, NULL);
return;
}
/* Build server addresses and call callback */
srv.count = 0;
for (i=0; i<rec->count; ++i) {
const pj_dns_addr_record *s = &rec->entry[i].server;
unsigned j;
for (j = 0; j < s->addr_count &&
srv.count < PJSIP_MAX_RESOLVED_ADDRESSES; ++j)
{
srv.entry[srv.count].type = query->naptr[0].type;
srv.entry[srv.count].priority = rec->entry[i].priority;
srv.entry[srv.count].weight = rec->entry[i].weight;
pj_sockaddr_init(s->addr[j].af,
&srv.entry[srv.count].addr,
0, (pj_uint16_t)rec->entry[i].port);
if (s->addr[j].af == pj_AF_INET6())
srv.entry[srv.count].addr.ipv6.sin6_addr = s->addr[j].ip.v6;
else
srv.entry[srv.count].addr.ipv4.sin_addr = s->addr[j].ip.v4;
srv.entry[srv.count].addr_len =
pj_sockaddr_get_len(&srv.entry[srv.count].addr);
/* Update transport type if this is IPv6 */
if (s->addr[j].af == pj_AF_INET6())
srv.entry[srv.count].type |= PJSIP_TRANSPORT_IPV6;
++srv.count;
}
}
/* Call the callback */
(*query->cb)(PJ_SUCCESS, query->token, &srv);
}
#endif /* PJSIP_HAS_RESOLVER */
@@ -0,0 +1,449 @@
/* $Id$ */
/*
* Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
* Copyright (C) 2003-2008 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/sip_tel_uri.h>
#include <pjsip/sip_msg.h>
#include <pjsip/sip_parser.h>
#include <pjsip/print_util.h>
#include <pj/pool.h>
#include <pj/assert.h>
#include <pj/string.h>
#include <pj/ctype.h>
#include <pj/except.h>
#include <pjlib-util/string.h>
#include <pjlib-util/scanner.h>
#define ALPHA
#define DIGITS "0123456789"
#define HEX "aAbBcCdDeEfF"
#define HEX_DIGITS DIGITS HEX
#define VISUAL_SEP "-.()"
#define PHONE_DIGITS DIGITS VISUAL_SEP
#define GLOBAL_DIGITS "+" PHONE_DIGITS
#define LOCAL_DIGITS HEX_DIGITS "*#" VISUAL_SEP
#define NUMBER_SPEC LOCAL_DIGITS GLOBAL_DIGITS
#define PHONE_CONTEXT ALPHA GLOBAL_DIGITS
//#define RESERVED ";/?:@&=+$,"
#define RESERVED "/:@&$,+"
#define MARK "-_.!~*'()"
#define UNRESERVED ALPHA DIGITS MARK
#define ESCAPED "%"
#define URIC RESERVED UNRESERVED ESCAPED "[]+"
#define PARAM_UNRESERVED "[]/:&+$"
#define PARAM_CHAR PARAM_UNRESERVED UNRESERVED ESCAPED
static pj_cis_buf_t cis_buf;
static pj_cis_t pjsip_TEL_NUMBER_SPEC;
static pj_cis_t pjsip_TEL_EXT_VALUE_SPEC;
static pj_cis_t pjsip_TEL_PHONE_CONTEXT_SPEC;
static pj_cis_t pjsip_TEL_URIC_SPEC;
static pj_cis_t pjsip_TEL_VISUAL_SEP_SPEC;
static pj_cis_t pjsip_TEL_PNAME_SPEC;
static pj_cis_t pjsip_TEL_PVALUE_SPEC;
static pj_cis_t pjsip_TEL_PVALUE_SPEC_ESC;
static pj_cis_t pjsip_TEL_PARSING_PVALUE_SPEC;
static pj_cis_t pjsip_TEL_PARSING_PVALUE_SPEC_ESC;
static pj_str_t pjsip_ISUB_STR = { "isub", 4 };
static pj_str_t pjsip_EXT_STR = { "ext", 3 };
static pj_str_t pjsip_PH_CTX_STR = { "phone-context", 13 };
static const pj_str_t *tel_uri_get_scheme( const pjsip_tel_uri* );
static void *tel_uri_get_uri( pjsip_tel_uri* );
static pj_ssize_t tel_uri_print( pjsip_uri_context_e context,
const pjsip_tel_uri *url,
char *buf, pj_size_t size);
static int tel_uri_cmp( pjsip_uri_context_e context,
const pjsip_tel_uri *url1, const pjsip_tel_uri *url2);
static pjsip_tel_uri* tel_uri_clone(pj_pool_t *pool, const pjsip_tel_uri *rhs);
static void* tel_uri_parse( pj_scanner *scanner, pj_pool_t *pool,
pj_bool_t parse_params);
typedef const pj_str_t* (*P_GET_SCHEME)(const void*);
typedef void* (*P_GET_URI)(void*);
typedef pj_ssize_t (*P_PRINT_URI)(pjsip_uri_context_e,const void *,
char*,pj_size_t);
typedef int (*P_CMP_URI)(pjsip_uri_context_e, const void*,
const void*);
typedef void* (*P_CLONE)(pj_pool_t*, const void*);
static pjsip_uri_vptr tel_uri_vptr =
{
(P_GET_SCHEME) &tel_uri_get_scheme,
(P_GET_URI) &tel_uri_get_uri,
(P_PRINT_URI) &tel_uri_print,
(P_CMP_URI) &tel_uri_cmp,
(P_CLONE) &tel_uri_clone
};
PJ_DEF(pjsip_tel_uri*) pjsip_tel_uri_create(pj_pool_t *pool)
{
pjsip_tel_uri *uri = PJ_POOL_ZALLOC_T(pool, pjsip_tel_uri);
uri->vptr = &tel_uri_vptr;
pj_list_init(&uri->other_param);
return uri;
}
static const pj_str_t *tel_uri_get_scheme( const pjsip_tel_uri *uri )
{
PJ_UNUSED_ARG(uri);
return &pjsip_parser_const()->pjsip_TEL_STR;
}
static void *tel_uri_get_uri( pjsip_tel_uri *uri )
{
return uri;
}
pj_status_t pjsip_tel_uri_subsys_init(void)
{
pj_status_t status;
pj_cis_buf_init(&cis_buf);
status = pj_cis_init(&cis_buf, &pjsip_TEL_EXT_VALUE_SPEC);
PJ_ASSERT_RETURN(status==PJ_SUCCESS, status);
pj_cis_add_str(&pjsip_TEL_EXT_VALUE_SPEC, PHONE_DIGITS);
status = pj_cis_init(&cis_buf, &pjsip_TEL_NUMBER_SPEC);
PJ_ASSERT_RETURN(status==PJ_SUCCESS, status);
pj_cis_add_str(&pjsip_TEL_NUMBER_SPEC, NUMBER_SPEC);
status = pj_cis_init(&cis_buf, &pjsip_TEL_VISUAL_SEP_SPEC);
PJ_ASSERT_RETURN(status==PJ_SUCCESS, status);
pj_cis_add_str(&pjsip_TEL_VISUAL_SEP_SPEC, VISUAL_SEP);
status = pj_cis_init(&cis_buf, &pjsip_TEL_PHONE_CONTEXT_SPEC);
PJ_ASSERT_RETURN(status==PJ_SUCCESS, status);
pj_cis_add_alpha(&pjsip_TEL_PHONE_CONTEXT_SPEC);
pj_cis_add_num(&pjsip_TEL_PHONE_CONTEXT_SPEC);
pj_cis_add_str(&pjsip_TEL_PHONE_CONTEXT_SPEC, PHONE_CONTEXT);
status = pj_cis_init(&cis_buf, &pjsip_TEL_URIC_SPEC);
PJ_ASSERT_RETURN(status==PJ_SUCCESS, status);
pj_cis_add_alpha(&pjsip_TEL_URIC_SPEC);
pj_cis_add_num(&pjsip_TEL_URIC_SPEC);
pj_cis_add_str(&pjsip_TEL_URIC_SPEC, URIC);
status = pj_cis_init(&cis_buf, &pjsip_TEL_PNAME_SPEC);
PJ_ASSERT_RETURN(status==PJ_SUCCESS, status);
pj_cis_add_alpha(&pjsip_TEL_PNAME_SPEC);
pj_cis_add_num(&pjsip_TEL_PNAME_SPEC);
pj_cis_add_str(&pjsip_TEL_PNAME_SPEC, "-");
status = pj_cis_init(&cis_buf, &pjsip_TEL_PVALUE_SPEC);
PJ_ASSERT_RETURN(status==PJ_SUCCESS, status);
pj_cis_add_alpha(&pjsip_TEL_PVALUE_SPEC);
pj_cis_add_num(&pjsip_TEL_PVALUE_SPEC);
pj_cis_add_str(&pjsip_TEL_PVALUE_SPEC, PARAM_CHAR);
status = pj_cis_dup(&pjsip_TEL_PVALUE_SPEC_ESC, &pjsip_TEL_PVALUE_SPEC);
pj_cis_del_str(&pjsip_TEL_PVALUE_SPEC_ESC, "%");
status = pj_cis_dup(&pjsip_TEL_PARSING_PVALUE_SPEC, &pjsip_TEL_URIC_SPEC);
PJ_ASSERT_RETURN(status==PJ_SUCCESS, status);
pj_cis_add_cis(&pjsip_TEL_PARSING_PVALUE_SPEC, &pjsip_TEL_PVALUE_SPEC);
pj_cis_add_str(&pjsip_TEL_PARSING_PVALUE_SPEC, "=");
status = pj_cis_dup(&pjsip_TEL_PARSING_PVALUE_SPEC_ESC,
&pjsip_TEL_PARSING_PVALUE_SPEC);
pj_cis_del_str(&pjsip_TEL_PARSING_PVALUE_SPEC_ESC, "%");
status = pjsip_register_uri_parser("tel", &tel_uri_parse);
PJ_ASSERT_RETURN(status==PJ_SUCCESS, status);
return PJ_SUCCESS;
}
/* Print tel: URI */
static pj_ssize_t tel_uri_print( pjsip_uri_context_e context,
const pjsip_tel_uri *uri,
char *buf, pj_size_t size)
{
int printed;
char *startbuf = buf;
char *endbuf = buf+size-1;
const pjsip_parser_const_t *pc = pjsip_parser_const();
PJ_UNUSED_ARG(context);
/* Print scheme. */
copy_advance(buf, pc->pjsip_TEL_STR);
*buf++ = ':';
/* Print number. */
copy_advance_escape(buf, uri->number, pjsip_TEL_NUMBER_SPEC);
/* ISDN sub-address or extension must appear first. */
/* Extension param. */
copy_advance_pair_escape(buf, ";ext=", 5, uri->ext_param,
pjsip_TEL_EXT_VALUE_SPEC);
/* ISDN sub-address. */
copy_advance_pair_escape(buf, ";isub=", 6, uri->isub_param,
pjsip_TEL_URIC_SPEC);
/* Followed by phone context, if present. */
copy_advance_pair_escape(buf, ";phone-context=", 15, uri->context,
pjsip_TEL_PHONE_CONTEXT_SPEC);
/* Print other parameters. */
printed = (int)pjsip_param_print_on(&uri->other_param, buf, (endbuf-buf),
&pjsip_TEL_PNAME_SPEC,
&pjsip_TEL_PVALUE_SPEC, ';');
if (printed < 0)
return -1;
buf += printed;
*buf = '\0';
return (buf-startbuf);
}
/* Compare two numbers, according to RFC 3966:
* - both must be either local or global numbers.
* - The 'global-number-digits' and the 'local-number-digits' must be
* equal, after removing all visual separators.
*/
PJ_DEF(int) pjsip_tel_nb_cmp(const pj_str_t *number1, const pj_str_t *number2)
{
const char *s1 = number1->ptr,
*e1 = number1->ptr + number1->slen,
*s2 = number2->ptr,
*e2 = number2->ptr + number2->slen;
/* Compare each number, ignoreing visual separators. */
while (s1!=e1 && s2!=e2) {
int diff;
if (pj_cis_match(&pjsip_TEL_VISUAL_SEP_SPEC, *s1)) {
++s1;
continue;
}
if (pj_cis_match(&pjsip_TEL_VISUAL_SEP_SPEC, *s2)) {
++s2;
continue;
}
diff = pj_tolower(*s1) - pj_tolower(*s2);
if (!diff) {
++s1, ++s2;
continue;
} else
return diff;
}
/* Exhaust remaining visual separators. */
while (s1!=e1 && pj_cis_match(&pjsip_TEL_VISUAL_SEP_SPEC, *s1))
++s1;
while (s2!=e2 && pj_cis_match(&pjsip_TEL_VISUAL_SEP_SPEC, *s2))
++s2;
if (s1==e1 && s2==e2)
return 0;
else if (s1==e1)
return -1;
else
return 1;
}
/* Compare two tel: URI */
static int tel_uri_cmp( pjsip_uri_context_e context,
const pjsip_tel_uri *url1, const pjsip_tel_uri *url2)
{
int result;
PJ_UNUSED_ARG(context);
/* Scheme must match. */
if (url1->vptr != url2->vptr)
return -1;
/* Compare number. */
result = pjsip_tel_nb_cmp(&url1->number, &url2->number);
if (result != 0)
return result;
/* Compare phone-context as hostname or as as global nb. */
if (url1->context.slen) {
if (*url1->context.ptr != '+')
result = pj_stricmp(&url1->context, &url2->context);
else
result = pjsip_tel_nb_cmp(&url1->context, &url2->context);
if (result != 0)
return result;
} else if (url2->context.slen)
return -1;
/* Compare extension. */
if (url1->ext_param.slen) {
result = pjsip_tel_nb_cmp(&url1->ext_param, &url2->ext_param);
if (result != 0)
return result;
}
/* Compare isub bytes by bytes. */
if (url1->isub_param.slen) {
result = pj_stricmp(&url1->isub_param, &url2->isub_param);
if (result != 0)
return result;
}
/* Other parameters are compared regardless of the order.
* If one URI has parameter not found in the other URI, the URIs are
* not equal.
*/
if (url1->other_param.next != &url1->other_param) {
const pjsip_param *p1, *p2;
int cnt1 = 0, cnt2 = 0;
p1 = url1->other_param.next;
while (p1 != &url1->other_param) {
p2 = pjsip_param_cfind(&url2->other_param, &p1->name);
if (!p2 )
return 1;
result = pj_stricmp(&p1->value, &p2->value);
if (result != 0)
return result;
p1 = p1->next;
++cnt1;
}
p2 = url2->other_param.next;
while (p2 != &url2->other_param)
++cnt2, p2 = p2->next;
if (cnt1 < cnt2)
return -1;
else if (cnt1 > cnt2)
return 1;
} else if (url2->other_param.next != &url2->other_param)
return -1;
/* Equal. */
return 0;
}
/* Clone tel: URI */
static pjsip_tel_uri* tel_uri_clone(pj_pool_t *pool, const pjsip_tel_uri *rhs)
{
pjsip_tel_uri *uri = pjsip_tel_uri_create(pool);
pj_strdup(pool, &uri->number, &rhs->number);
pj_strdup(pool, &uri->context, &rhs->context);
pj_strdup(pool, &uri->ext_param, &rhs->ext_param);
pj_strdup(pool, &uri->isub_param, &rhs->isub_param);
pjsip_param_clone(pool, &uri->other_param, &rhs->other_param);
return uri;
}
/* Parse tel: URI
* THis actually returns (pjsip_tel_uri *) type.
*/
static void* tel_uri_parse( pj_scanner *scanner, pj_pool_t *pool,
pj_bool_t parse_params)
{
pjsip_tel_uri *uri;
pj_str_t token;
int skip_ws = scanner->skip_ws;
const pjsip_parser_const_t *pc = pjsip_parser_const();
scanner->skip_ws = 0;
/* Parse scheme. */
pj_scan_get(scanner, &pc->pjsip_TOKEN_SPEC, &token);
if (pj_scan_get_char(scanner) != ':')
PJ_THROW(PJSIP_SYN_ERR_EXCEPTION);
if (pj_stricmp_alnum(&token, &pc->pjsip_TEL_STR) != 0)
PJ_THROW(PJSIP_SYN_ERR_EXCEPTION);
/* Create URI */
uri = pjsip_tel_uri_create(pool);
/* Get the phone number. */
#if defined(PJSIP_UNESCAPE_IN_PLACE) && PJSIP_UNESCAPE_IN_PLACE!=0
pj_scan_get_unescape(scanner, &pjsip_TEL_NUMBER_SPEC, &uri->number);
#else
pj_scan_get(scanner, &pjsip_TEL_NUMBER_SPEC, &uri->number);
uri->number = pj_str_unescape(pool, &uri->number);
#endif
/* Get all parameters. */
if (parse_params && *scanner->curptr==';') {
pj_str_t pname, pvalue;
do {
/* Eat the ';' separator. */
pj_scan_get_char(scanner);
/* Get pname. */
pj_scan_get(scanner, &pc->pjsip_PARAM_CHAR_SPEC, &pname);
if (*scanner->curptr == '=') {
pj_scan_get_char(scanner);
# if defined(PJSIP_UNESCAPE_IN_PLACE) && PJSIP_UNESCAPE_IN_PLACE!=0
pj_scan_get_unescape(scanner,
&pjsip_TEL_PARSING_PVALUE_SPEC_ESC,
&pvalue);
# else
pj_scan_get(scanner, &pjsip_TEL_PARSING_PVALUE_SPEC,
&pvalue);
pvalue = pj_str_unescape(pool, &pvalue);
# endif
} else {
pvalue.slen = 0;
pvalue.ptr = NULL;
}
/* Save the parameters. */
if (pj_stricmp_alnum(&pname, &pjsip_ISUB_STR)==0) {
uri->isub_param = pvalue;
} else if (pj_stricmp_alnum(&pname, &pjsip_EXT_STR)==0) {
uri->ext_param = pvalue;
} else if (pj_stricmp_alnum(&pname, &pjsip_PH_CTX_STR)==0) {
uri->context = pvalue;
} else {
pjsip_param *param = PJ_POOL_ALLOC_T(pool, pjsip_param);
param->name = pname;
param->value = pvalue;
pj_list_insert_before(&uri->other_param, param);
}
} while (*scanner->curptr==';');
}
scanner->skip_ws = skip_ws;
pj_scan_skip_whitespace(scanner);
return uri;
}
@@ -0,0 +1,24 @@
/* $Id$ */
/*
* Copyright (C) 2009-2011 Teluu Inc. (http://www.teluu.com)
*
* 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
*/
/*
* This file is a C++ wrapper, see ticket #886 for details.
*/
#include "sip_tel_uri.c"
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
@@ -0,0 +1,513 @@
/* $Id$ */
/*
* Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
* Copyright (C) 2003-2008 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/sip_transport_loop.h>
#include <pjsip/sip_endpoint.h>
#include <pjsip/sip_errno.h>
#include <pj/pool.h>
#include <pj/os.h>
#include <pj/string.h>
#include <pj/lock.h>
#include <pj/assert.h>
#include <pj/compat/socket.h>
#define ADDR_LOOP "128.0.0.1"
#define ADDR_LOOP_DGRAM "129.0.0.1"
/** This structure describes incoming packet. */
struct recv_list
{
PJ_DECL_LIST_MEMBER(struct recv_list);
pjsip_rx_data rdata;
};
/** This structure is used to keep delayed send failure. */
struct send_list
{
PJ_DECL_LIST_MEMBER(struct send_list);
pj_time_val sent_time;
pj_ssize_t sent;
pjsip_tx_data *tdata;
void *token;
void (*callback)(pjsip_transport*, void*, pj_ssize_t);
};
/** This structure describes the loop transport. */
struct loop_transport
{
pjsip_transport base;
pj_pool_t *pool;
pj_thread_t *thread;
pj_bool_t thread_quit_flag;
pj_bool_t discard;
int fail_mode;
unsigned recv_delay;
unsigned send_delay;
struct recv_list recv_list;
struct send_list send_list;
};
/* Helper function to create "incoming" packet */
static struct recv_list *create_incoming_packet( struct loop_transport *loop,
pjsip_tx_data *tdata )
{
pj_pool_t *pool;
struct recv_list *pkt;
pool = pjsip_endpt_create_pool(loop->base.endpt, "rdata",
PJSIP_POOL_RDATA_LEN,
PJSIP_POOL_RDATA_INC+5);
if (!pool)
return NULL;
pkt = PJ_POOL_ZALLOC_T(pool, struct recv_list);
/* Initialize rdata. */
pkt->rdata.tp_info.pool = pool;
pkt->rdata.tp_info.transport = &loop->base;
/* Copy the packet. */
pj_memcpy(pkt->rdata.pkt_info.packet, tdata->buf.start,
tdata->buf.cur - tdata->buf.start);
pkt->rdata.pkt_info.len = tdata->buf.cur - tdata->buf.start;
/* the source address */
pkt->rdata.pkt_info.src_addr.addr.sa_family = pj_AF_INET();
/* "Source address" info. */
pkt->rdata.pkt_info.src_addr_len = sizeof(pj_sockaddr_in);
if (loop->base.key.type == PJSIP_TRANSPORT_LOOP) {
pj_ansi_strcpy(pkt->rdata.pkt_info.src_name, ADDR_LOOP);
} else {
pj_ansi_strcpy(pkt->rdata.pkt_info.src_name, ADDR_LOOP_DGRAM);
}
pkt->rdata.pkt_info.src_port = loop->base.local_name.port;
/* When do we need to "deliver" this packet. */
pj_gettimeofday(&pkt->rdata.pkt_info.timestamp);
pkt->rdata.pkt_info.timestamp.msec += loop->recv_delay;
pj_time_val_normalize(&pkt->rdata.pkt_info.timestamp);
/* Done. */
return pkt;
}
/* Helper function to add pending notification callback. */
static pj_status_t add_notification( struct loop_transport *loop,
pjsip_tx_data *tdata,
pj_ssize_t sent,
void *token,
void (*callback)(pjsip_transport*,
void*, pj_ssize_t))
{
struct send_list *sent_status;
pjsip_tx_data_add_ref(tdata);
pj_lock_acquire(tdata->lock);
sent_status = PJ_POOL_ALLOC_T(tdata->pool, struct send_list);
pj_lock_release(tdata->lock);
sent_status->sent = sent;
sent_status->tdata = tdata;
sent_status->token = token;
sent_status->callback = callback;
pj_gettimeofday(&sent_status->sent_time);
sent_status->sent_time.msec += loop->send_delay;
pj_time_val_normalize(&sent_status->sent_time);
pj_lock_acquire(loop->base.lock);
pj_list_push_back(&loop->send_list, sent_status);
pj_lock_release(loop->base.lock);
return PJ_SUCCESS;
}
/* Handler for sending outgoing message; called by transport manager. */
static pj_status_t loop_send_msg( pjsip_transport *tp,
pjsip_tx_data *tdata,
const pj_sockaddr_t *rem_addr,
int addr_len,
void *token,
pjsip_transport_callback cb)
{
struct loop_transport *loop = (struct loop_transport*)tp;
struct recv_list *recv_pkt;
PJ_ASSERT_RETURN(tp && (tp->key.type == PJSIP_TRANSPORT_LOOP ||
tp->key.type == PJSIP_TRANSPORT_LOOP_DGRAM), PJ_EINVAL);
PJ_UNUSED_ARG(rem_addr);
PJ_UNUSED_ARG(addr_len);
/* Need to send failure? */
if (loop->fail_mode) {
if (loop->send_delay == 0) {
return PJ_STATUS_FROM_OS(OSERR_ECONNRESET);
} else {
add_notification(loop, tdata, -PJ_STATUS_FROM_OS(OSERR_ECONNRESET),
token, cb);
return PJ_EPENDING;
}
}
/* Discard any packets? */
if (loop->discard)
return PJ_SUCCESS;
/* Create rdata for the "incoming" packet. */
recv_pkt = create_incoming_packet(loop, tdata);
if (!recv_pkt)
return PJ_ENOMEM;
/* If delay is not configured, deliver this packet now! */
if (loop->recv_delay == 0) {
pj_ssize_t size_eaten;
size_eaten = pjsip_tpmgr_receive_packet( loop->base.tpmgr,
&recv_pkt->rdata);
pj_assert(size_eaten == recv_pkt->rdata.pkt_info.len);
PJ_UNUSED_ARG(size_eaten);
pjsip_endpt_release_pool(loop->base.endpt,
recv_pkt->rdata.tp_info.pool);
} else {
/* Otherwise if delay is configured, add the "packet" to the
* receive list to be processed by worker thread.
*/
pj_lock_acquire(loop->base.lock);
pj_list_push_back(&loop->recv_list, recv_pkt);
pj_lock_release(loop->base.lock);
}
if (loop->send_delay != 0) {
add_notification(loop, tdata, tdata->buf.cur - tdata->buf.start,
token, cb);
return PJ_EPENDING;
} else {
return PJ_SUCCESS;
}
}
/* Handler to destroy the transport; called by transport manager */
static pj_status_t loop_destroy(pjsip_transport *tp)
{
struct loop_transport *loop = (struct loop_transport*)tp;
PJ_ASSERT_RETURN(tp && (tp->key.type == PJSIP_TRANSPORT_LOOP ||
tp->key.type == PJSIP_TRANSPORT_LOOP_DGRAM), PJ_EINVAL);
loop->thread_quit_flag = 1;
/* Unlock transport mutex before joining thread. */
/// This raised assertion failed "mutex->owner == pj_thread_this()",
/// where mutex->owner==NULL
//pj_lock_release(tp->lock);
pj_thread_join(loop->thread);
pj_thread_destroy(loop->thread);
/* Clear pending send notifications. */
while (!pj_list_empty(&loop->send_list)) {
struct send_list *node = loop->send_list.next;
/* Notify callback. */
if (node->callback) {
(*node->callback)(&loop->base, node->token, -PJSIP_ESHUTDOWN);
}
pj_list_erase(node);
pjsip_tx_data_dec_ref(node->tdata);
}
/* Clear "incoming" packets in the queue. */
while (!pj_list_empty(&loop->recv_list)) {
struct recv_list *node = loop->recv_list.next;
pj_list_erase(node);
pjsip_endpt_release_pool(loop->base.endpt,
node->rdata.tp_info.pool);
}
/* Self destruct.. heheh.. */
pj_lock_destroy(loop->base.lock);
pj_atomic_destroy(loop->base.ref_cnt);
pjsip_endpt_release_pool(loop->base.endpt, loop->base.pool);
return PJ_SUCCESS;
}
/* Worker thread for loop transport. */
static int loop_transport_worker_thread(void *arg)
{
struct loop_transport *loop = (struct loop_transport*) arg;
struct recv_list r;
struct send_list s;
pj_list_init(&r);
pj_list_init(&s);
while (!loop->thread_quit_flag) {
pj_time_val now;
pj_thread_sleep(1);
pj_gettimeofday(&now);
pj_lock_acquire(loop->base.lock);
/* Move expired send notification to local list. */
while (!pj_list_empty(&loop->send_list)) {
struct send_list *node = loop->send_list.next;
/* Break when next node time is greater than now. */
if (PJ_TIME_VAL_GTE(node->sent_time, now))
break;
/* Delete this from the list. */
pj_list_erase(node);
/* Add to local list. */
pj_list_push_back(&s, node);
}
/* Move expired "incoming" packet to local list. */
while (!pj_list_empty(&loop->recv_list)) {
struct recv_list *node = loop->recv_list.next;
/* Break when next node time is greater than now. */
if (PJ_TIME_VAL_GTE(node->rdata.pkt_info.timestamp, now))
break;
/* Delete this from the list. */
pj_list_erase(node);
/* Add to local list. */
pj_list_push_back(&r, node);
}
pj_lock_release(loop->base.lock);
/* Process send notification and incoming packet notification
* without holding down the loop's mutex.
*/
while (!pj_list_empty(&s)) {
struct send_list *node = s.next;
pj_list_erase(node);
/* Notify callback. */
if (node->callback) {
(*node->callback)(&loop->base, node->token, node->sent);
}
/* Decrement tdata reference counter. */
pjsip_tx_data_dec_ref(node->tdata);
}
/* Process "incoming" packet. */
while (!pj_list_empty(&r)) {
struct recv_list *node = r.next;
pj_ssize_t size_eaten;
pj_list_erase(node);
/* Notify transport manager about the "incoming packet" */
size_eaten = pjsip_tpmgr_receive_packet(loop->base.tpmgr,
&node->rdata);
/* Must "eat" all the packets. */
pj_assert(size_eaten == node->rdata.pkt_info.len);
PJ_UNUSED_ARG(size_eaten);
/* Done. */
pjsip_endpt_release_pool(loop->base.endpt,
node->rdata.tp_info.pool);
}
}
return 0;
}
/* Start loop transport. */
PJ_DEF(pj_status_t) pjsip_loop_start( pjsip_endpoint *endpt,
pjsip_transport **transport)
{
pj_pool_t *pool;
struct loop_transport *loop;
pj_status_t status;
/* Create pool. */
pool = pjsip_endpt_create_pool(endpt, "loop", 4000, 4000);
if (!pool)
return PJ_ENOMEM;
/* Create the loop structure. */
loop = PJ_POOL_ZALLOC_T(pool, struct loop_transport);
/* Initialize transport properties. */
pj_ansi_snprintf(loop->base.obj_name, sizeof(loop->base.obj_name),
"loop%p", loop);
loop->base.pool = pool;
status = pj_atomic_create(pool, 0, &loop->base.ref_cnt);
if (status != PJ_SUCCESS)
goto on_error;
status = pj_lock_create_recursive_mutex(pool, "loop", &loop->base.lock);
if (status != PJ_SUCCESS)
goto on_error;
loop->base.key.type = PJSIP_TRANSPORT_LOOP_DGRAM;
//loop->base.key.rem_addr.addr.sa_family = pj_AF_INET();
loop->base.type_name = "LOOP-DGRAM";
loop->base.info = "LOOP-DGRAM";
loop->base.flag = PJSIP_TRANSPORT_DATAGRAM;
loop->base.local_name.host = pj_str(ADDR_LOOP_DGRAM);
loop->base.local_name.port =
pjsip_transport_get_default_port_for_type((pjsip_transport_type_e)
loop->base.key.type);
loop->base.addr_len = sizeof(pj_sockaddr_in);
loop->base.dir = PJSIP_TP_DIR_NONE;
loop->base.endpt = endpt;
loop->base.tpmgr = pjsip_endpt_get_tpmgr(endpt);
loop->base.send_msg = &loop_send_msg;
loop->base.destroy = &loop_destroy;
pj_list_init(&loop->recv_list);
pj_list_init(&loop->send_list);
/* Create worker thread. */
status = pj_thread_create(pool, "loop",
&loop_transport_worker_thread, loop, 0,
PJ_THREAD_SUSPENDED, &loop->thread);
if (status != PJ_SUCCESS)
goto on_error;
/* Register to transport manager. */
status = pjsip_transport_register( loop->base.tpmgr, &loop->base);
if (status != PJ_SUCCESS)
goto on_error;
/* Start the thread. */
status = pj_thread_resume(loop->thread);
if (status != PJ_SUCCESS)
goto on_error;
/*
* Done.
*/
if (transport)
*transport = &loop->base;
return PJ_SUCCESS;
on_error:
if (loop->base.lock)
pj_lock_destroy(loop->base.lock);
if (loop->thread)
pj_thread_destroy(loop->thread);
if (loop->base.ref_cnt)
pj_atomic_destroy(loop->base.ref_cnt);
pjsip_endpt_release_pool(endpt, loop->pool);
return status;
}
PJ_DEF(pj_status_t) pjsip_loop_set_discard( pjsip_transport *tp,
pj_bool_t discard,
pj_bool_t *prev_value )
{
struct loop_transport *loop = (struct loop_transport*)tp;
PJ_ASSERT_RETURN(tp && (tp->key.type == PJSIP_TRANSPORT_LOOP ||
tp->key.type == PJSIP_TRANSPORT_LOOP_DGRAM), PJ_EINVAL);
if (prev_value)
*prev_value = loop->discard;
loop->discard = discard;
return PJ_SUCCESS;
}
PJ_DEF(pj_status_t) pjsip_loop_set_failure( pjsip_transport *tp,
int fail_flag,
int *prev_value )
{
struct loop_transport *loop = (struct loop_transport*)tp;
PJ_ASSERT_RETURN(tp && (tp->key.type == PJSIP_TRANSPORT_LOOP ||
tp->key.type == PJSIP_TRANSPORT_LOOP_DGRAM), PJ_EINVAL);
if (prev_value)
*prev_value = loop->fail_mode;
loop->fail_mode = fail_flag;
return PJ_SUCCESS;
}
PJ_DEF(pj_status_t) pjsip_loop_set_recv_delay( pjsip_transport *tp,
unsigned delay,
unsigned *prev_value)
{
struct loop_transport *loop = (struct loop_transport*)tp;
PJ_ASSERT_RETURN(tp && (tp->key.type == PJSIP_TRANSPORT_LOOP ||
tp->key.type == PJSIP_TRANSPORT_LOOP_DGRAM), PJ_EINVAL);
if (prev_value)
*prev_value = loop->recv_delay;
loop->recv_delay = delay;
return PJ_SUCCESS;
}
PJ_DEF(pj_status_t) pjsip_loop_set_send_callback_delay( pjsip_transport *tp,
unsigned delay,
unsigned *prev_value)
{
struct loop_transport *loop = (struct loop_transport*)tp;
PJ_ASSERT_RETURN(tp && (tp->key.type == PJSIP_TRANSPORT_LOOP ||
tp->key.type == PJSIP_TRANSPORT_LOOP_DGRAM), PJ_EINVAL);
if (prev_value)
*prev_value = loop->send_delay;
loop->send_delay = delay;
return PJ_SUCCESS;
}
PJ_DEF(pj_status_t) pjsip_loop_set_delay( pjsip_transport *tp, unsigned delay )
{
struct loop_transport *loop = (struct loop_transport*)tp;
PJ_ASSERT_RETURN(tp && (tp->key.type == PJSIP_TRANSPORT_LOOP ||
tp->key.type == PJSIP_TRANSPORT_LOOP_DGRAM), PJ_EINVAL);
loop->recv_delay = delay;
loop->send_delay = delay;
return PJ_SUCCESS;
}
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
@@ -0,0 +1,24 @@
/* $Id$ */
/*
* Copyright (C) 2009-2011 Teluu Inc. (http://www.teluu.com)
*
* 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
*/
/*
* This file is a C++ wrapper, see ticket #886 for details.
*/
#include "sip_transport.c"
@@ -0,0 +1,995 @@
/* $Id$ */
/*
* Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
* Copyright (C) 2003-2008 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/sip_ua_layer.h>
#include <pjsip/sip_module.h>
#include <pjsip/sip_dialog.h>
#include <pjsip/sip_endpoint.h>
#include <pjsip/sip_errno.h>
#include <pjsip/sip_transaction.h>
#include <pj/os.h>
#include <pj/hash.h>
#include <pj/assert.h>
#include <pj/string.h>
#include <pj/pool.h>
#include <pj/log.h>
#define THIS_FILE "sip_ua_layer.c"
/*
* Static prototypes.
*/
static pj_status_t mod_ua_load(pjsip_endpoint *endpt);
static pj_status_t mod_ua_unload(void);
static pj_bool_t mod_ua_on_rx_request(pjsip_rx_data *rdata);
static pj_bool_t mod_ua_on_rx_response(pjsip_rx_data *rdata);
static void mod_ua_on_tsx_state(pjsip_transaction*, pjsip_event*);
extern long pjsip_dlg_lock_tls_id; /* defined in sip_dialog.c */
/* This struct is used to represent list of dialog inside a dialog set.
* We don't want to use pjsip_dialog for this purpose, to save some
* memory (about 100 bytes per dialog set).
*/
struct dlg_set_head
{
PJ_DECL_LIST_MEMBER(pjsip_dialog);
};
/* This struct represents a dialog set.
* This is the value that will be put in the UA's hash table.
*/
struct dlg_set
{
/* To put this node in free dlg_set nodes in UA. */
PJ_DECL_LIST_MEMBER(struct dlg_set);
/* This is the buffer to store this entry in the hash table. */
pj_hash_entry_buf ht_entry;
/* List of dialog in this dialog set. */
struct dlg_set_head dlg_list;
};
/*
* Module interface.
*/
static struct user_agent
{
pjsip_module mod;
pj_pool_t *pool;
pjsip_endpoint *endpt;
pj_mutex_t *mutex;
pj_hash_table_t *dlg_table;
pjsip_ua_init_param param;
struct dlg_set free_dlgset_nodes;
} mod_ua =
{
{
NULL, NULL, /* prev, next. */
{ "mod-ua", 6 }, /* Name. */
-1, /* Id */
PJSIP_MOD_PRIORITY_UA_PROXY_LAYER, /* Priority */
&mod_ua_load, /* load() */
NULL, /* start() */
NULL, /* stop() */
&mod_ua_unload, /* unload() */
&mod_ua_on_rx_request, /* on_rx_request() */
&mod_ua_on_rx_response, /* on_rx_response() */
NULL, /* on_tx_request. */
NULL, /* on_tx_response() */
&mod_ua_on_tsx_state, /* on_tsx_state() */
}
};
/*
* mod_ua_load()
*
* Called when module is being loaded by endpoint.
*/
static pj_status_t mod_ua_load(pjsip_endpoint *endpt)
{
pj_status_t status;
/* Initialize the user agent. */
mod_ua.endpt = endpt;
mod_ua.pool = pjsip_endpt_create_pool( endpt, "ua%p", PJSIP_POOL_LEN_UA,
PJSIP_POOL_INC_UA);
if (mod_ua.pool == NULL)
return PJ_ENOMEM;
status = pj_mutex_create_recursive(mod_ua.pool, " ua%p", &mod_ua.mutex);
if (status != PJ_SUCCESS)
return status;
mod_ua.dlg_table = pj_hash_create(mod_ua.pool, PJSIP_MAX_DIALOG_COUNT);
if (mod_ua.dlg_table == NULL)
return PJ_ENOMEM;
pj_list_init(&mod_ua.free_dlgset_nodes);
/* Initialize dialog lock. */
status = pj_thread_local_alloc(&pjsip_dlg_lock_tls_id);
if (status != PJ_SUCCESS)
return status;
pj_thread_local_set(pjsip_dlg_lock_tls_id, NULL);
return PJ_SUCCESS;
}
/*
* mod_ua_unload()
*
* Called when module is being unloaded.
*/
static pj_status_t mod_ua_unload(void)
{
pj_thread_local_free(pjsip_dlg_lock_tls_id);
pj_mutex_destroy(mod_ua.mutex);
/* Release pool */
if (mod_ua.pool) {
pjsip_endpt_release_pool( mod_ua.endpt, mod_ua.pool );
}
return PJ_SUCCESS;
}
/*
* mod_ua_on_tsx_stats()
*
* Called on changed on transaction state.
*/
static void mod_ua_on_tsx_state( pjsip_transaction *tsx, pjsip_event *e)
{
pjsip_dialog *dlg;
/* Get the dialog where this transaction belongs. */
dlg = (pjsip_dialog*) tsx->mod_data[mod_ua.mod.id];
/* If dialog instance has gone, it could mean that the dialog
* may has been destroyed.
*/
if (dlg == NULL)
return;
/* Hand over the event to the dialog. */
pjsip_dlg_on_tsx_state(dlg, tsx, e);
}
/*
* Init user agent module and register it to the endpoint.
*/
PJ_DEF(pj_status_t) pjsip_ua_init_module( pjsip_endpoint *endpt,
const pjsip_ua_init_param *prm)
{
pj_status_t status;
/* Check if module already registered. */
PJ_ASSERT_RETURN(mod_ua.mod.id == -1, PJ_EINVALIDOP);
/* Copy param, if exists. */
if (prm)
pj_memcpy(&mod_ua.param, prm, sizeof(pjsip_ua_init_param));
/* Register the module. */
status = pjsip_endpt_register_module(endpt, &mod_ua.mod);
return status;
}
/*
* Get the instance of the user agent.
*
*/
PJ_DEF(pjsip_user_agent*) pjsip_ua_instance(void)
{
return &mod_ua.mod;
}
/*
* Get the endpoint where this UA is currently registered.
*/
PJ_DEF(pjsip_endpoint*) pjsip_ua_get_endpt(pjsip_user_agent *ua)
{
PJ_UNUSED_ARG(ua);
pj_assert(ua == &mod_ua.mod);
return mod_ua.endpt;
}
/*
* Destroy the user agent layer.
*/
PJ_DEF(pj_status_t) pjsip_ua_destroy(void)
{
/* Check if module already destroyed. */
PJ_ASSERT_RETURN(mod_ua.mod.id != -1, PJ_EINVALIDOP);
return pjsip_endpt_unregister_module(mod_ua.endpt, &mod_ua.mod);
}
/*
* Create key to identify dialog set.
*/
/*
PJ_DEF(void) pjsip_ua_create_dlg_set_key( pj_pool_t *pool,
pj_str_t *set_key,
const pj_str_t *call_id,
const pj_str_t *local_tag)
{
PJ_ASSERT_ON_FAIL(pool && set_key && call_id && local_tag, return;);
set_key->slen = call_id->slen + local_tag->slen + 1;
set_key->ptr = (char*) pj_pool_alloc(pool, set_key->slen);
pj_assert(set_key->ptr != NULL);
pj_memcpy(set_key->ptr, call_id->ptr, call_id->slen);
set_key->ptr[call_id->slen] = '$';
pj_memcpy(set_key->ptr + call_id->slen + 1,
local_tag->ptr, local_tag->slen);
}
*/
/*
* Acquire one dlg_set node to be put in the hash table.
* This will first look in the free nodes list, then allocate
* a new one from UA's pool when one is not available.
*/
static struct dlg_set *alloc_dlgset_node(void)
{
struct dlg_set *set;
if (!pj_list_empty(&mod_ua.free_dlgset_nodes)) {
set = mod_ua.free_dlgset_nodes.next;
pj_list_erase(set);
return set;
} else {
set = PJ_POOL_ALLOC_T(mod_ua.pool, struct dlg_set);
return set;
}
}
/*
* Register new dialog. Called by pjsip_dlg_create_uac() and
* pjsip_dlg_create_uas_and_inc_lock();
*/
PJ_DEF(pj_status_t) pjsip_ua_register_dlg( pjsip_user_agent *ua,
pjsip_dialog *dlg )
{
/* Sanity check. */
PJ_ASSERT_RETURN(ua && dlg, PJ_EINVAL);
/* For all dialogs, local tag (inc hash) must has been initialized. */
PJ_ASSERT_RETURN(dlg->local.info && dlg->local.info->tag.slen &&
dlg->local.tag_hval != 0, PJ_EBUG);
/* For UAS dialog, remote tag (inc hash) must have been initialized. */
//PJ_ASSERT_RETURN(dlg->role==PJSIP_ROLE_UAC ||
// (dlg->role==PJSIP_ROLE_UAS && dlg->remote.info->tag.slen
// && dlg->remote.tag_hval != 0), PJ_EBUG);
/* Lock the user agent. */
pj_mutex_lock(mod_ua.mutex);
/* For UAC, check if there is existing dialog in the same set. */
if (dlg->role == PJSIP_ROLE_UAC) {
struct dlg_set *dlg_set;
dlg_set = (struct dlg_set*)
pj_hash_get_lower( mod_ua.dlg_table,
dlg->local.info->tag.ptr,
(unsigned)dlg->local.info->tag.slen,
&dlg->local.tag_hval);
if (dlg_set) {
/* This is NOT the first dialog in the dialog set.
* Just add this dialog in the list.
*/
pj_assert(dlg_set->dlg_list.next != (void*)&dlg_set->dlg_list);
pj_list_push_back(&dlg_set->dlg_list, dlg);
dlg->dlg_set = dlg_set;
} else {
/* This is the first dialog in the dialog set.
* Create the dialog set and add this dialog to it.
*/
dlg_set = alloc_dlgset_node();
pj_list_init(&dlg_set->dlg_list);
pj_list_push_back(&dlg_set->dlg_list, dlg);
dlg->dlg_set = dlg_set;
/* Register the dialog set in the hash table. */
pj_hash_set_np_lower(mod_ua.dlg_table,
dlg->local.info->tag.ptr,
(unsigned)dlg->local.info->tag.slen,
dlg->local.tag_hval, dlg_set->ht_entry,
dlg_set);
}
} else {
/* For UAS, create the dialog set with a single dialog as member. */
struct dlg_set *dlg_set;
dlg_set = alloc_dlgset_node();
pj_list_init(&dlg_set->dlg_list);
pj_list_push_back(&dlg_set->dlg_list, dlg);
dlg->dlg_set = dlg_set;
pj_hash_set_np_lower(mod_ua.dlg_table,
dlg->local.info->tag.ptr,
(unsigned)dlg->local.info->tag.slen,
dlg->local.tag_hval, dlg_set->ht_entry, dlg_set);
}
/* Unlock user agent. */
pj_mutex_unlock(mod_ua.mutex);
/* Done. */
return PJ_SUCCESS;
}
PJ_DEF(pj_status_t) pjsip_ua_unregister_dlg( pjsip_user_agent *ua,
pjsip_dialog *dlg )
{
struct dlg_set *dlg_set;
pjsip_dialog *d;
/* Sanity-check arguments. */
PJ_ASSERT_RETURN(ua && dlg, PJ_EINVAL);
/* Check that dialog has been registered. */
PJ_ASSERT_RETURN(dlg->dlg_set, PJ_EINVALIDOP);
/* Lock user agent. */
pj_mutex_lock(mod_ua.mutex);
/* Find this dialog from the dialog set. */
dlg_set = (struct dlg_set*) dlg->dlg_set;
d = dlg_set->dlg_list.next;
while (d != (pjsip_dialog*)&dlg_set->dlg_list && d != dlg) {
d = d->next;
}
if (d != dlg) {
pj_assert(!"Dialog is not registered!");
pj_mutex_unlock(mod_ua.mutex);
return PJ_EINVALIDOP;
}
/* Remove this dialog from the list. */
pj_list_erase(dlg);
/* If dialog list is empty, remove the dialog set from the hash table. */
if (pj_list_empty(&dlg_set->dlg_list)) {
pj_hash_set_lower(NULL, mod_ua.dlg_table, dlg->local.info->tag.ptr,
(unsigned)dlg->local.info->tag.slen,
dlg->local.tag_hval, NULL);
/* Return dlg_set to free nodes. */
pj_list_push_back(&mod_ua.free_dlgset_nodes, dlg_set);
}
/* Unlock user agent. */
pj_mutex_unlock(mod_ua.mutex);
/* Done. */
return PJ_SUCCESS;
}
PJ_DEF(pjsip_dialog*) pjsip_rdata_get_dlg( pjsip_rx_data *rdata )
{
return (pjsip_dialog*) rdata->endpt_info.mod_data[mod_ua.mod.id];
}
PJ_DEF(pjsip_dialog*) pjsip_tdata_get_dlg( pjsip_tx_data *tdata )
{
return (pjsip_dialog*) tdata->mod_data[mod_ua.mod.id];
}
PJ_DEF(pjsip_dialog*) pjsip_tsx_get_dlg( pjsip_transaction *tsx )
{
return (pjsip_dialog*) tsx->mod_data[mod_ua.mod.id];
}
/*
* Retrieve the current number of dialog-set currently registered
* in the hash table.
*/
PJ_DEF(unsigned) pjsip_ua_get_dlg_set_count(void)
{
unsigned count;
PJ_ASSERT_RETURN(mod_ua.endpt, 0);
pj_mutex_lock(mod_ua.mutex);
count = pj_hash_count(mod_ua.dlg_table);
pj_mutex_unlock(mod_ua.mutex);
return count;
}
/*
* Find a dialog.
*/
PJ_DEF(pjsip_dialog*) pjsip_ua_find_dialog(const pj_str_t *call_id,
const pj_str_t *local_tag,
const pj_str_t *remote_tag,
pj_bool_t lock_dialog)
{
struct dlg_set *dlg_set;
pjsip_dialog *dlg;
PJ_ASSERT_RETURN(call_id && local_tag && remote_tag, NULL);
/* Lock user agent. */
pj_mutex_lock(mod_ua.mutex);
/* Lookup the dialog set. */
dlg_set = (struct dlg_set*)
pj_hash_get_lower(mod_ua.dlg_table, local_tag->ptr,
(unsigned)local_tag->slen, NULL);
if (dlg_set == NULL) {
/* Not found */
pj_mutex_unlock(mod_ua.mutex);
return NULL;
}
/* Dialog set is found, now find the matching dialog based on the
* remote tag.
*/
dlg = dlg_set->dlg_list.next;
while (dlg != (pjsip_dialog*)&dlg_set->dlg_list) {
if (pj_stricmp(&dlg->remote.info->tag, remote_tag) == 0)
break;
dlg = dlg->next;
}
if (dlg == (pjsip_dialog*)&dlg_set->dlg_list) {
/* Not found */
pj_mutex_unlock(mod_ua.mutex);
return NULL;
}
/* Dialog has been found. It SHOULD have the right Call-ID!! */
if (pj_strcmp(&dlg->call_id->id, call_id)!=0) {
PJ_LOG(6, (THIS_FILE, "Dialog not found: local and remote tags "
"matched but not call id"));
pj_mutex_unlock(mod_ua.mutex);
return NULL;
}
if (lock_dialog) {
if (pjsip_dlg_try_inc_lock(dlg) != PJ_SUCCESS) {
/*
* Unable to acquire dialog's lock while holding the user
* agent's mutex. Release the UA mutex before retrying once
* more.
*
* THIS MAY CAUSE RACE CONDITION!
*/
/* Unlock user agent. */
pj_mutex_unlock(mod_ua.mutex);
/* Lock dialog */
pjsip_dlg_inc_lock(dlg);
} else {
/* Unlock user agent. */
pj_mutex_unlock(mod_ua.mutex);
}
} else {
/* Unlock user agent. */
pj_mutex_unlock(mod_ua.mutex);
}
return dlg;
}
/*
* Find the first dialog in dialog set in hash table for an incoming message.
*/
static struct dlg_set *find_dlg_set_for_msg( pjsip_rx_data *rdata )
{
/* CANCEL message doesn't have To tag, so we must lookup the dialog
* by finding the INVITE UAS transaction being cancelled.
*/
if (rdata->msg_info.cseq->method.id == PJSIP_CANCEL_METHOD) {
pjsip_dialog *dlg;
/* Create key for the rdata, but this time, use INVITE as the
* method.
*/
pj_str_t key;
pjsip_role_e role;
pjsip_transaction *tsx;
if (rdata->msg_info.msg->type == PJSIP_REQUEST_MSG)
role = PJSIP_ROLE_UAS;
else
role = PJSIP_ROLE_UAC;
pjsip_tsx_create_key(rdata->tp_info.pool, &key, role,
pjsip_get_invite_method(), rdata);
/* Lookup the INVITE transaction */
tsx = pjsip_tsx_layer_find_tsx2(&key, PJ_TRUE);
/* We should find the dialog attached to the INVITE transaction */
if (tsx) {
dlg = (pjsip_dialog*) tsx->mod_data[mod_ua.mod.id];
pj_grp_lock_dec_ref(tsx->grp_lock);
/* Dlg may be NULL on some extreme condition
* (e.g. during debugging where initially there is a dialog)
*/
return dlg ? (struct dlg_set*) dlg->dlg_set : NULL;
} else {
return NULL;
}
} else {
pj_str_t *tag;
struct dlg_set *dlg_set;
if (rdata->msg_info.msg->type == PJSIP_REQUEST_MSG)
tag = &rdata->msg_info.to->tag;
else
tag = &rdata->msg_info.from->tag;
/* Lookup the dialog set. */
dlg_set = (struct dlg_set*)
pj_hash_get_lower(mod_ua.dlg_table, tag->ptr,
(unsigned)tag->slen, NULL);
return dlg_set;
}
}
/* On received requests. */
static pj_bool_t mod_ua_on_rx_request(pjsip_rx_data *rdata)
{
struct dlg_set *dlg_set;
pj_str_t *from_tag;
pjsip_dialog *dlg;
pj_status_t status;
/* Optimized path: bail out early if request is not CANCEL and it doesn't
* have To tag
*/
if (rdata->msg_info.to->tag.slen == 0 &&
rdata->msg_info.msg->line.req.method.id != PJSIP_CANCEL_METHOD)
{
return PJ_FALSE;
}
/* Incoming REGISTER may have tags in it */
if (rdata->msg_info.msg->line.req.method.id == PJSIP_REGISTER_METHOD)
return PJ_FALSE;
retry_on_deadlock:
/* Lock user agent before looking up the dialog hash table. */
pj_mutex_lock(mod_ua.mutex);
/* Lookup the dialog set, based on the To tag header. */
dlg_set = find_dlg_set_for_msg(rdata);
/* If dialog is not found, respond with 481 (Call/Transaction
* Does Not Exist).
*/
if (dlg_set == NULL) {
/* Unable to find dialog. */
pj_mutex_unlock(mod_ua.mutex);
if (rdata->msg_info.msg->line.req.method.id != PJSIP_ACK_METHOD) {
PJ_LOG(5,(THIS_FILE,
"Unable to find dialogset for %s, answering with 481",
pjsip_rx_data_get_info(rdata)));
/* Respond with 481 . */
pjsip_endpt_respond_stateless( mod_ua.endpt, rdata, 481, NULL,
NULL, NULL );
}
return PJ_TRUE;
}
/* Dialog set has been found.
* Find the dialog in the dialog set based on the content of the remote
* tag.
*/
from_tag = &rdata->msg_info.from->tag;
dlg = dlg_set->dlg_list.next;
while (dlg != (pjsip_dialog*)&dlg_set->dlg_list) {
if (pj_stricmp(&dlg->remote.info->tag, from_tag) == 0)
break;
dlg = dlg->next;
}
/* Dialog may not be found, e.g. in this case:
* - UAC sends SUBSCRIBE, then UAS sends NOTIFY before answering
* SUBSCRIBE request with 2xx.
*
* In this case, we can accept the request ONLY when the original
* dialog still has empty To tag.
*/
if (dlg == (pjsip_dialog*)&dlg_set->dlg_list) {
pjsip_dialog *first_dlg = dlg_set->dlg_list.next;
if (first_dlg->remote.info->tag.slen != 0) {
/* Not found. Mulfunction UAC? */
pj_mutex_unlock(mod_ua.mutex);
if (rdata->msg_info.msg->line.req.method.id != PJSIP_ACK_METHOD) {
PJ_LOG(5,(THIS_FILE,
"Unable to find dialog for %s, answering with 481",
pjsip_rx_data_get_info(rdata)));
pjsip_endpt_respond_stateless(mod_ua.endpt, rdata,
PJSIP_SC_CALL_TSX_DOES_NOT_EXIST,
NULL, NULL, NULL);
} else {
PJ_LOG(5,(THIS_FILE,
"Unable to find dialog for %s",
pjsip_rx_data_get_info(rdata)));
}
return PJ_TRUE;
}
dlg = first_dlg;
}
/* Mark the dialog id of the request. */
rdata->endpt_info.mod_data[mod_ua.mod.id] = dlg;
/* Try to lock the dialog */
PJ_LOG(6,(dlg->obj_name, "UA layer acquiring dialog lock for request"));
status = pjsip_dlg_try_inc_lock(dlg);
if (status != PJ_SUCCESS) {
/* Failed to acquire dialog mutex immediately, this could be
* because of deadlock. Release UA mutex, yield, and retry
* the whole thing once again.
*/
pj_mutex_unlock(mod_ua.mutex);
pj_thread_sleep(0);
goto retry_on_deadlock;
}
/* Done with processing in UA layer, release lock */
pj_mutex_unlock(mod_ua.mutex);
/* Pass to dialog. */
pjsip_dlg_on_rx_request(dlg, rdata);
/* Unlock the dialog. This may destroy the dialog */
pjsip_dlg_dec_lock(dlg);
/* Report as handled. */
return PJ_TRUE;
}
/* On rx response notification.
*/
static pj_bool_t mod_ua_on_rx_response(pjsip_rx_data *rdata)
{
pjsip_transaction *tsx;
struct dlg_set *dlg_set;
pjsip_dialog *dlg;
pj_status_t status;
/*
* Find the dialog instance for the response.
* All outgoing dialog requests are sent statefully, which means
* there will be an UAC transaction associated with this response,
* and the dialog instance will be recorded in that transaction.
*
* But even when transaction is found, there is possibility that
* the response is a forked response.
*/
retry_on_deadlock:
dlg = NULL;
/* Lock user agent dlg table before we're doing anything. */
pj_mutex_lock(mod_ua.mutex);
/* Check if transaction is present. */
tsx = pjsip_rdata_get_tsx(rdata);
if (tsx) {
/* Check if dialog is present in the transaction. */
dlg = pjsip_tsx_get_dlg(tsx);
if (!dlg) {
/* Unlock dialog hash table. */
pj_mutex_unlock(mod_ua.mutex);
return PJ_FALSE;
}
/* Get the dialog set. */
dlg_set = (struct dlg_set*) dlg->dlg_set;
/* Even if transaction is found and (candidate) dialog has been
* identified, it's possible that the request has forked.
*/
} else {
/* Transaction is not present.
* Check if this is a 2xx/OK response to INVITE, which in this
* case the response will be handled directly by the
* dialog.
*/
pjsip_cseq_hdr *cseq_hdr = rdata->msg_info.cseq;
if (cseq_hdr->method.id != PJSIP_INVITE_METHOD ||
rdata->msg_info.msg->line.status.code / 100 != 2)
{
/* Not a 2xx response to INVITE.
* This must be some stateless response sent by other modules,
* or a very late response.
*/
/* Unlock dialog hash table. */
pj_mutex_unlock(mod_ua.mutex);
return PJ_FALSE;
}
/* Get the dialog set. */
dlg_set = (struct dlg_set*)
pj_hash_get_lower(mod_ua.dlg_table,
rdata->msg_info.from->tag.ptr,
(unsigned)rdata->msg_info.from->tag.slen,
NULL);
if (!dlg_set) {
/* Unlock dialog hash table. */
pj_mutex_unlock(mod_ua.mutex);
/* Strayed 2xx response!! */
PJ_LOG(4,(THIS_FILE,
"Received strayed 2xx response (no dialog is found)"
" from %s:%d: %s",
rdata->pkt_info.src_name, rdata->pkt_info.src_port,
pjsip_rx_data_get_info(rdata)));
return PJ_TRUE;
}
}
/* At this point, we must have the dialog set, and the dialog set
* must have a dialog in the list.
*/
pj_assert(dlg_set && !pj_list_empty(&dlg_set->dlg_list));
/* Check for forked response.
* Request will fork only for the initial INVITE request.
*/
//This doesn't work when there is authentication challenge, since
//first_cseq evaluation will yield false.
//if (rdata->msg_info.cseq->method.id == PJSIP_INVITE_METHOD &&
// rdata->msg_info.cseq->cseq == dlg_set->dlg_list.next->local.first_cseq)
if (rdata->msg_info.cseq->method.id == PJSIP_INVITE_METHOD) {
int st_code = rdata->msg_info.msg->line.status.code;
pj_str_t *to_tag = &rdata->msg_info.to->tag;
dlg = dlg_set->dlg_list.next;
while (dlg != (pjsip_dialog*)&dlg_set->dlg_list) {
/* If there is dialog with no remote tag (i.e. dialog has not
* been established yet), then send this response to that
* dialog.
*/
if (dlg->remote.info->tag.slen == 0)
break;
/* Otherwise find the one with matching To tag. */
if (pj_stricmp(to_tag, &dlg->remote.info->tag) == 0)
break;
dlg = dlg->next;
}
/* If no dialog with matching remote tag is found, this must be
* a forked response. Respond to this ONLY when response is non-100
* provisional response OR a 2xx response.
*/
if (dlg == (pjsip_dialog*)&dlg_set->dlg_list &&
((st_code/100==1 && st_code!=100) || st_code/100==2))
{
PJ_LOG(5,(THIS_FILE,
"Received forked %s for existing dialog %s",
pjsip_rx_data_get_info(rdata),
dlg_set->dlg_list.next->obj_name));
/* Report to application about forked condition.
* Application can either create a dialog or ignore the response.
*/
if (mod_ua.param.on_dlg_forked) {
dlg = (*mod_ua.param.on_dlg_forked)(dlg_set->dlg_list.next,
rdata);
if (dlg == NULL) {
pj_mutex_unlock(mod_ua.mutex);
return PJ_TRUE;
}
} else {
dlg = dlg_set->dlg_list.next;
PJ_LOG(4,(THIS_FILE,
"Unhandled forked %s from %s:%d, response will be "
"handed over to the first dialog",
pjsip_rx_data_get_info(rdata),
rdata->pkt_info.src_name, rdata->pkt_info.src_port));
}
} else if (dlg == (pjsip_dialog*)&dlg_set->dlg_list) {
/* For 100 or non-2xx response which has different To tag,
* pass the response to the first dialog.
*/
dlg = dlg_set->dlg_list.next;
}
} else {
/* Either this is a non-INVITE response, or subsequent INVITE
* within dialog. The dialog should have been identified when
* the transaction was found.
*/
pj_assert(tsx != NULL);
pj_assert(dlg != NULL);
}
/* The dialog must have been found. */
pj_assert(dlg != NULL);
/* Put the dialog instance in the rdata. */
rdata->endpt_info.mod_data[mod_ua.mod.id] = dlg;
/* Attempt to acquire lock to the dialog. */
PJ_LOG(6,(dlg->obj_name, "UA layer acquiring dialog lock for response"));
status = pjsip_dlg_try_inc_lock(dlg);
if (status != PJ_SUCCESS) {
/* Failed to acquire dialog mutex. This could indicate a deadlock
* situation, and for safety, try to avoid deadlock by releasing
* UA mutex, yield, and retry the whole processing once again.
*/
pj_mutex_unlock(mod_ua.mutex);
pj_thread_sleep(0);
goto retry_on_deadlock;
}
/* We're done with processing in the UA layer, we can release the mutex */
pj_mutex_unlock(mod_ua.mutex);
/* Pass the response to the dialog. */
pjsip_dlg_on_rx_response(dlg, rdata);
/* Unlock the dialog. This may destroy the dialog. */
pjsip_dlg_dec_lock(dlg);
/* Done. */
return PJ_TRUE;
}
#if PJ_LOG_MAX_LEVEL >= 3
static void print_dialog( const char *title,
pjsip_dialog *dlg, char *buf, pj_size_t size)
{
int len;
char userinfo[PJSIP_MAX_URL_SIZE];
len = pjsip_hdr_print_on(dlg->remote.info, userinfo, sizeof(userinfo));
if (len < 0)
pj_ansi_strcpy(userinfo, "<--uri too long-->");
else
userinfo[len] = '\0';
len = pj_ansi_snprintf(buf, size, "%s[%s] %s",
title,
(dlg->state==PJSIP_DIALOG_STATE_NULL ? " - " :
"est"),
userinfo);
if (len < 1 || len >= (int)size) {
pj_ansi_strcpy(buf, "<--uri too long-->");
} else
buf[len] = '\0';
}
#endif
/*
* Dump user agent contents (e.g. all dialogs).
*/
PJ_DEF(void) pjsip_ua_dump(pj_bool_t detail)
{
#if PJ_LOG_MAX_LEVEL >= 3
pj_hash_iterator_t itbuf, *it;
char dlginfo[128];
pj_mutex_lock(mod_ua.mutex);
PJ_LOG(3, (THIS_FILE, "Number of dialog sets: %u",
pj_hash_count(mod_ua.dlg_table)));
if (detail && pj_hash_count(mod_ua.dlg_table)) {
PJ_LOG(3, (THIS_FILE, "Dumping dialog sets:"));
it = pj_hash_first(mod_ua.dlg_table, &itbuf);
for (; it != NULL; it = pj_hash_next(mod_ua.dlg_table, it)) {
struct dlg_set *dlg_set;
pjsip_dialog *dlg;
const char *title;
dlg_set = (struct dlg_set*) pj_hash_this(mod_ua.dlg_table, it);
if (!dlg_set || pj_list_empty(&dlg_set->dlg_list)) continue;
/* First dialog in dialog set. */
dlg = dlg_set->dlg_list.next;
if (dlg->role == PJSIP_ROLE_UAC)
title = " [out] ";
else
title = " [in] ";
print_dialog(title, dlg, dlginfo, sizeof(dlginfo));
PJ_LOG(3,(THIS_FILE, "%s", dlginfo));
/* Next dialog in dialog set (forked) */
dlg = dlg->next;
while (dlg != (pjsip_dialog*) &dlg_set->dlg_list) {
print_dialog(" [forked] ", dlg, dlginfo, sizeof(dlginfo));
dlg = dlg->next;
}
}
}
pj_mutex_unlock(mod_ua.mutex);
#endif
}
+732
View File
@@ -0,0 +1,732 @@
/* $Id$ */
/*
* Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
* Copyright (C) 2003-2008 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/sip_uri.h>
#include <pjsip/sip_msg.h>
#include <pjsip/sip_parser.h>
#include <pjsip/print_util.h>
#include <pjsip/sip_errno.h>
#include <pjlib-util/string.h>
#include <pj/string.h>
#include <pj/pool.h>
#include <pj/assert.h>
/*
* Generic parameter manipulation.
*/
PJ_DEF(pjsip_param*) pjsip_param_find( const pjsip_param *param_list,
const pj_str_t *name )
{
pjsip_param *p = (pjsip_param*)param_list->next;
while (p != param_list) {
if (pj_stricmp(&p->name, name)==0)
return p;
p = p->next;
}
return NULL;
}
PJ_DEF(int) pjsip_param_cmp( const pjsip_param *param_list1,
const pjsip_param *param_list2,
pj_bool_t ig_nf)
{
const pjsip_param *p1;
if ((ig_nf & 1)==0 && pj_list_size(param_list1)!=pj_list_size(param_list2))
return 1;
p1 = param_list1->next;
while (p1 != param_list1) {
const pjsip_param *p2;
p2 = pjsip_param_find(param_list2, &p1->name);
if (p2 ) {
int rc = pj_stricmp(&p1->value, &p2->value);
if (rc != 0)
return rc;
} else if ((ig_nf & 1)==0)
return 1;
p1 = p1->next;
}
return 0;
}
PJ_DEF(void) pjsip_param_clone( pj_pool_t *pool, pjsip_param *dst_list,
const pjsip_param *src_list)
{
const pjsip_param *p = src_list->next;
pj_list_init(dst_list);
while (p && p != src_list) {
pjsip_param *new_param = PJ_POOL_ALLOC_T(pool, pjsip_param);
pj_strdup(pool, &new_param->name, &p->name);
pj_strdup(pool, &new_param->value, &p->value);
pj_list_insert_before(dst_list, new_param);
p = p->next;
}
}
PJ_DEF(void) pjsip_param_shallow_clone( pj_pool_t *pool,
pjsip_param *dst_list,
const pjsip_param *src_list)
{
const pjsip_param *p = src_list->next;
pj_list_init(dst_list);
while (p != src_list) {
pjsip_param *new_param = PJ_POOL_ALLOC_T(pool, pjsip_param);
new_param->name = p->name;
new_param->value = p->value;
pj_list_insert_before(dst_list, new_param);
p = p->next;
}
}
PJ_DEF(pj_ssize_t) pjsip_param_print_on( const pjsip_param *param_list,
char *buf, pj_size_t size,
const pj_cis_t *pname_spec,
const pj_cis_t *pvalue_spec,
int sep)
{
const pjsip_param *p;
char *startbuf;
char *endbuf;
int printed;
p = param_list->next;
if (p == NULL || p == param_list)
return 0;
startbuf = buf;
endbuf = buf + size;
PJ_UNUSED_ARG(pname_spec);
do {
copy_advance_char_check(buf, (char)sep);
copy_advance_escape(buf, p->name, (*pname_spec));
if (p->value.slen) {
copy_advance_char_check(buf, '=');
if (*p->value.ptr == '"')
copy_advance(buf, p->value);
else
copy_advance_escape(buf, p->value, (*pvalue_spec));
}
p = p->next;
if (sep == '?') sep = '&';
} while (p != param_list);
return buf-startbuf;
}
/*
* URI stuffs
*/
#define IS_SIPS(url) ((url)->vptr==&sips_url_vptr)
static const pj_str_t *pjsip_url_get_scheme( const pjsip_sip_uri* );
static const pj_str_t *pjsips_url_get_scheme( const pjsip_sip_uri* );
static const pj_str_t *pjsip_name_addr_get_scheme( const pjsip_name_addr * );
static void *pjsip_get_uri( pjsip_uri *uri );
static void *pjsip_name_addr_get_uri( pjsip_name_addr *name );
static pj_str_t sip_str = { "sip", 3 };
static pj_str_t sips_str = { "sips", 4 };
static pjsip_name_addr* pjsip_name_addr_clone( pj_pool_t *pool,
const pjsip_name_addr *rhs);
static pj_ssize_t pjsip_name_addr_print(pjsip_uri_context_e context,
const pjsip_name_addr *name,
char *buf, pj_size_t size);
static int pjsip_name_addr_compare( pjsip_uri_context_e context,
const pjsip_name_addr *naddr1,
const pjsip_name_addr *naddr2);
static pj_ssize_t pjsip_url_print( pjsip_uri_context_e context,
const pjsip_sip_uri *url,
char *buf, pj_size_t size);
static int pjsip_url_compare( pjsip_uri_context_e context,
const pjsip_sip_uri *url1,
const pjsip_sip_uri *url2);
static pjsip_sip_uri* pjsip_url_clone(pj_pool_t *pool,
const pjsip_sip_uri *rhs);
typedef const pj_str_t* (*P_GET_SCHEME)(const void*);
typedef void* (*P_GET_URI)(void*);
typedef pj_ssize_t (*P_PRINT_URI)(pjsip_uri_context_e,const void *,
char*,pj_size_t);
typedef int (*P_CMP_URI)(pjsip_uri_context_e, const void*,
const void*);
typedef void* (*P_CLONE)(pj_pool_t*, const void*);
static pjsip_uri_vptr sip_url_vptr =
{
(P_GET_SCHEME) &pjsip_url_get_scheme,
(P_GET_URI) &pjsip_get_uri,
(P_PRINT_URI) &pjsip_url_print,
(P_CMP_URI) &pjsip_url_compare,
(P_CLONE) &pjsip_url_clone
};
static pjsip_uri_vptr sips_url_vptr =
{
(P_GET_SCHEME) &pjsips_url_get_scheme,
(P_GET_URI) &pjsip_get_uri,
(P_PRINT_URI) &pjsip_url_print,
(P_CMP_URI) &pjsip_url_compare,
(P_CLONE) &pjsip_url_clone
};
static pjsip_uri_vptr name_addr_vptr =
{
(P_GET_SCHEME) &pjsip_name_addr_get_scheme,
(P_GET_URI) &pjsip_name_addr_get_uri,
(P_PRINT_URI) &pjsip_name_addr_print,
(P_CMP_URI) &pjsip_name_addr_compare,
(P_CLONE) &pjsip_name_addr_clone
};
static const pj_str_t *pjsip_url_get_scheme(const pjsip_sip_uri *url)
{
PJ_UNUSED_ARG(url);
return &sip_str;
}
static const pj_str_t *pjsips_url_get_scheme(const pjsip_sip_uri *url)
{
PJ_UNUSED_ARG(url);
return &sips_str;
}
static void *pjsip_get_uri( pjsip_uri *uri )
{
return uri;
}
static void *pjsip_name_addr_get_uri( pjsip_name_addr *name )
{
return pjsip_uri_get_uri(name->uri);
}
PJ_DEF(void) pjsip_sip_uri_set_secure( pjsip_sip_uri *url,
pj_bool_t secure )
{
url->vptr = secure ? &sips_url_vptr : &sip_url_vptr;
}
PJ_DEF(void) pjsip_sip_uri_init(pjsip_sip_uri *url, pj_bool_t secure)
{
pj_bzero(url, sizeof(*url));
url->ttl_param = -1;
pjsip_sip_uri_set_secure(url, secure);
pj_list_init(&url->other_param);
pj_list_init(&url->header_param);
}
PJ_DEF(pjsip_sip_uri*) pjsip_sip_uri_create( pj_pool_t *pool,
pj_bool_t secure )
{
pjsip_sip_uri *url = PJ_POOL_ALLOC_T(pool, pjsip_sip_uri);
pjsip_sip_uri_init(url, secure);
return url;
}
static pj_ssize_t pjsip_url_print( pjsip_uri_context_e context,
const pjsip_sip_uri *url,
char *buf, pj_size_t size)
{
int printed;
char *startbuf = buf;
char *endbuf = buf+size;
const pj_str_t *scheme;
const pjsip_parser_const_t *pc = pjsip_parser_const();
*buf = '\0';
/* Print scheme ("sip:" or "sips:") */
scheme = pjsip_uri_get_scheme(url);
copy_advance_check(buf, *scheme);
copy_advance_char_check(buf, ':');
/* Print "user:password@", if any. */
if (url->user.slen) {
const pj_cis_t *spec = pjsip_cfg()->endpt.allow_tx_hash_in_uri ?
&pc->pjsip_USER_SPEC_LENIENT :
&pc->pjsip_USER_SPEC;
copy_advance_escape(buf, url->user, *spec);
if (url->passwd.slen) {
copy_advance_char_check(buf, ':');
copy_advance_escape(buf, url->passwd, pc->pjsip_PASSWD_SPEC);
}
copy_advance_char_check(buf, '@');
}
/* Print host. */
pj_assert(url->host.slen != 0);
/* Detect IPv6 IP address */
if (pj_memchr(url->host.ptr, ':', url->host.slen)) {
copy_advance_pair_quote_cond(buf, "", 0, url->host, '[', ']');
} else {
copy_advance_check(buf, url->host);
}
/* Only print port if it is explicitly specified.
* Port is not allowed in To and From header, see Table 1 in
* RFC 3261 Section 19.1.1
*/
/* Note: ticket #1141 adds run-time setting to allow port number to
* appear in From/To header. Default is still false.
*/
if (url->port &&
(context != PJSIP_URI_IN_FROMTO_HDR ||
pjsip_cfg()->endpt.allow_port_in_fromto_hdr))
{
if (endbuf - buf < 10)
return -1;
copy_advance_char_check(buf, ':');
printed = pj_utoa(url->port, buf);
buf += printed;
}
/* User param is allowed in all contexes */
copy_advance_pair_check(buf, ";user=", 6, url->user_param);
/* Method param is only allowed in external/other context. */
if (context == PJSIP_URI_IN_OTHER) {
copy_advance_pair_escape(buf, ";method=", 8, url->method_param,
pc->pjsip_PARAM_CHAR_SPEC);
}
/* Transport is not allowed in From/To header. */
if (context != PJSIP_URI_IN_FROMTO_HDR) {
copy_advance_pair_escape(buf, ";transport=", 11, url->transport_param,
pc->pjsip_PARAM_CHAR_SPEC);
}
/* TTL param is not allowed in From, To, Route, and Record-Route header. */
if (url->ttl_param >= 0 && context != PJSIP_URI_IN_FROMTO_HDR &&
context != PJSIP_URI_IN_ROUTING_HDR)
{
if (endbuf - buf < 15)
return -1;
pj_memcpy(buf, ";ttl=", 5);
printed = pj_utoa(url->ttl_param, buf+5);
buf += printed + 5;
}
/* maddr param is not allowed in From and To header. */
if (context != PJSIP_URI_IN_FROMTO_HDR && url->maddr_param.slen) {
/* Detect IPv6 IP address */
if (pj_memchr(url->maddr_param.ptr, ':', url->maddr_param.slen)) {
copy_advance_pair_quote_cond(buf, ";maddr=", 7, url->maddr_param,
'[', ']');
} else {
copy_advance_pair_escape(buf, ";maddr=", 7, url->maddr_param,
pc->pjsip_PARAM_CHAR_SPEC);
}
}
/* lr param is not allowed in From, To, and Contact header. */
if (url->lr_param && context != PJSIP_URI_IN_FROMTO_HDR &&
context != PJSIP_URI_IN_CONTACT_HDR)
{
pj_str_t lr = { ";lr", 3 };
if (endbuf - buf < 3)
return -1;
copy_advance_check(buf, lr);
}
/* Other param. */
printed = (int)pjsip_param_print_on(&url->other_param, buf, endbuf-buf,
&pc->pjsip_PARAM_CHAR_SPEC,
&pc->pjsip_PARAM_CHAR_SPEC, ';');
if (printed < 0)
return -1;
buf += printed;
/* Header param.
* Header param is only allowed in these contexts:
* - PJSIP_URI_IN_CONTACT_HDR
* - PJSIP_URI_IN_OTHER
*/
if (context == PJSIP_URI_IN_CONTACT_HDR || context == PJSIP_URI_IN_OTHER) {
printed = (int)pjsip_param_print_on(&url->header_param, buf, endbuf-buf,
&pc->pjsip_HDR_CHAR_SPEC,
&pc->pjsip_HDR_CHAR_SPEC, '?');
if (printed < 0)
return -1;
buf += printed;
}
*buf = '\0';
return buf-startbuf;
}
static pj_status_t pjsip_url_compare( pjsip_uri_context_e context,
const pjsip_sip_uri *url1,
const pjsip_sip_uri *url2)
{
const pjsip_param *p1;
/*
* Compare two SIP URL's according to Section 19.1.4 of RFC 3261.
*/
/* SIP and SIPS URI are never equivalent.
* Note: just compare the vptr to avoid string comparison.
* Pretty neat huh!!
*/
if (url1->vptr != url2->vptr)
return PJSIP_ECMPSCHEME;
/* Comparison of the userinfo of SIP and SIPS URIs is case-sensitive.
* This includes userinfo containing passwords or formatted as
* telephone-subscribers.
*/
if (pj_strcmp(&url1->user, &url2->user) != 0)
return PJSIP_ECMPUSER;
if (pj_strcmp(&url1->passwd, &url2->passwd) != 0)
return PJSIP_ECMPPASSWD;
/* Comparison of all other components of the URI is
* case-insensitive unless explicitly defined otherwise.
*/
/* The ordering of parameters and header fields is not significant
* in comparing SIP and SIPS URIs.
*/
/* Characters other than those in the reserved set (see RFC 2396 [5])
* are equivalent to their encoding.
*/
/* An IP address that is the result of a DNS lookup of a host name
* does not match that host name.
*/
if (pj_stricmp(&url1->host, &url2->host) != 0)
return PJSIP_ECMPHOST;
/* A URI omitting any component with a default value will not match a URI
* explicitly containing that component with its default value.
* For instance, a URI omitting the optional port component will not match
* a URI explicitly declaring port 5060.
* The same is true for the transport-parameter, ttl-parameter,
* user-parameter, and method components.
*/
/* Port is not allowed in To and From header.
*/
if (context != PJSIP_URI_IN_FROMTO_HDR) {
if (url1->port != url2->port)
return PJSIP_ECMPPORT;
}
/* Transport is not allowed in From/To header. */
if (context != PJSIP_URI_IN_FROMTO_HDR) {
if (pj_stricmp(&url1->transport_param, &url2->transport_param) != 0)
return PJSIP_ECMPTRANSPORTPRM;
}
/* TTL param is not allowed in From, To, Route, and Record-Route header. */
if (context != PJSIP_URI_IN_FROMTO_HDR &&
context != PJSIP_URI_IN_ROUTING_HDR)
{
if (url1->ttl_param != url2->ttl_param)
return PJSIP_ECMPTTLPARAM;
}
/* User param is allowed in all contexes */
if (pj_stricmp(&url1->user_param, &url2->user_param) != 0)
return PJSIP_ECMPUSERPARAM;
/* Method param is only allowed in external/other context. */
if (context == PJSIP_URI_IN_OTHER) {
if (pj_stricmp(&url1->method_param, &url2->method_param) != 0)
return PJSIP_ECMPMETHODPARAM;
}
/* maddr param is not allowed in From and To header. */
if (context != PJSIP_URI_IN_FROMTO_HDR) {
if (pj_stricmp(&url1->maddr_param, &url2->maddr_param) != 0)
return PJSIP_ECMPMADDRPARAM;
}
/* lr parameter is ignored (?) */
/* lr param is not allowed in From, To, and Contact header. */
/* All other uri-parameters appearing in only one URI are ignored when
* comparing the URIs.
*/
if (pjsip_param_cmp(&url1->other_param, &url2->other_param, 1)!=0)
return PJSIP_ECMPOTHERPARAM;
/* URI header components are never ignored. Any present header component
* MUST be present in both URIs and match for the URIs to match.
* The matching rules are defined for each header field in Section 20.
*/
p1 = url1->header_param.next;
while (p1 != &url1->header_param) {
const pjsip_param *p2;
p2 = pjsip_param_find(&url2->header_param, &p1->name);
if (p2) {
/* It seems too much to compare two header params according to
* the rule of each header. We'll just compare them string to
* string..
*/
if (pj_stricmp(&p1->value, &p2->value) != 0)
return PJSIP_ECMPHEADERPARAM;
} else {
return PJSIP_ECMPHEADERPARAM;
}
p1 = p1->next;
}
/* Equal!! Pheuww.. */
return PJ_SUCCESS;
}
PJ_DEF(void) pjsip_sip_uri_assign(pj_pool_t *pool, pjsip_sip_uri *url,
const pjsip_sip_uri *rhs)
{
pj_strdup( pool, &url->user, &rhs->user);
pj_strdup( pool, &url->passwd, &rhs->passwd);
pj_strdup( pool, &url->host, &rhs->host);
url->port = rhs->port;
pj_strdup( pool, &url->user_param, &rhs->user_param);
pj_strdup( pool, &url->method_param, &rhs->method_param);
pj_strdup( pool, &url->transport_param, &rhs->transport_param);
url->ttl_param = rhs->ttl_param;
pj_strdup( pool, &url->maddr_param, &rhs->maddr_param);
pjsip_param_clone(pool, &url->other_param, &rhs->other_param);
pjsip_param_clone(pool, &url->header_param, &rhs->header_param);
url->lr_param = rhs->lr_param;
}
static pjsip_sip_uri* pjsip_url_clone(pj_pool_t *pool, const pjsip_sip_uri *rhs)
{
pjsip_sip_uri *url = PJ_POOL_ALLOC_T(pool, pjsip_sip_uri);
if (!url)
return NULL;
pjsip_sip_uri_init(url, IS_SIPS(rhs));
pjsip_sip_uri_assign(pool, url, rhs);
return url;
}
static const pj_str_t *pjsip_name_addr_get_scheme(const pjsip_name_addr *name)
{
pj_assert(name->uri != NULL);
return pjsip_uri_get_scheme(name->uri);
}
PJ_DEF(void) pjsip_name_addr_init(pjsip_name_addr *name)
{
name->vptr = &name_addr_vptr;
name->uri = NULL;
name->display.slen = 0;
name->display.ptr = NULL;
}
PJ_DEF(pjsip_name_addr*) pjsip_name_addr_create(pj_pool_t *pool)
{
pjsip_name_addr *name_addr = PJ_POOL_ALLOC_T(pool, pjsip_name_addr);
pjsip_name_addr_init(name_addr);
return name_addr;
}
static pj_ssize_t pjsip_name_addr_print(pjsip_uri_context_e context,
const pjsip_name_addr *name,
char *buf, pj_size_t size)
{
int printed;
char *startbuf = buf;
char *endbuf = buf + size;
pjsip_uri *uri;
uri = (pjsip_uri*) pjsip_uri_get_uri(name->uri);
pj_assert(uri != NULL);
if (context != PJSIP_URI_IN_REQ_URI) {
if (name->display.slen) {
if (endbuf-buf < name->display.slen + 3) return -1;
copy_advance_char_check(buf, '"');
copy_advance(buf, name->display);
copy_advance_char_check(buf, '"');
copy_advance_char_check(buf, ' ');
}
copy_advance_char_check(buf, '<');;
}
printed = pjsip_uri_print(context,uri, buf, size-(buf-startbuf));
if (printed < 1)
return -1;
buf += printed;
if (context != PJSIP_URI_IN_REQ_URI) {
copy_advance_char_check(buf, '>');
}
*buf = '\0';
return buf-startbuf;
}
PJ_DEF(void) pjsip_name_addr_assign(pj_pool_t *pool, pjsip_name_addr *dst,
const pjsip_name_addr *src)
{
pj_strdup( pool, &dst->display, &src->display);
dst->uri = (pjsip_uri*) pjsip_uri_clone(pool, src->uri);
}
static pjsip_name_addr* pjsip_name_addr_clone( pj_pool_t *pool,
const pjsip_name_addr *rhs)
{
pjsip_name_addr *addr = PJ_POOL_ALLOC_T(pool, pjsip_name_addr);
if (!addr)
return NULL;
pjsip_name_addr_init(addr);
pjsip_name_addr_assign(pool, addr, rhs);
return addr;
}
static int pjsip_name_addr_compare( pjsip_uri_context_e context,
const pjsip_name_addr *naddr1,
const pjsip_name_addr *naddr2)
{
int d;
/* Check that naddr2 is also a name_addr */
if (naddr1->vptr != naddr2->vptr)
return -1;
/* I'm not sure whether display name is included in the comparison. */
if (pj_strcmp(&naddr1->display, &naddr2->display) != 0) {
return -1;
}
pj_assert( naddr1->uri != NULL );
pj_assert( naddr2->uri != NULL );
/* Compare name-addr as URL */
d = pjsip_uri_cmp( context, naddr1->uri, naddr2->uri);
if (d)
return d;
return 0;
}
///////////////////////////////////////////////////////////////////////////////
static const pj_str_t *other_uri_get_scheme( const pjsip_other_uri*);
static void *other_uri_get_uri( pjsip_other_uri*);
static pj_ssize_t other_uri_print( pjsip_uri_context_e context,
const pjsip_other_uri *url,
char *buf, pj_size_t size);
static int other_uri_cmp( pjsip_uri_context_e context,
const pjsip_other_uri *url1,
const pjsip_other_uri *url2);
static pjsip_other_uri* other_uri_clone( pj_pool_t *pool,
const pjsip_other_uri *rhs);
static pjsip_uri_vptr other_uri_vptr =
{
(P_GET_SCHEME) &other_uri_get_scheme,
(P_GET_URI) &other_uri_get_uri,
(P_PRINT_URI) &other_uri_print,
(P_CMP_URI) &other_uri_cmp,
(P_CLONE) &other_uri_clone
};
PJ_DEF(pjsip_other_uri*) pjsip_other_uri_create(pj_pool_t *pool)
{
pjsip_other_uri *uri = PJ_POOL_ZALLOC_T(pool, pjsip_other_uri);
uri->vptr = &other_uri_vptr;
return uri;
}
static const pj_str_t *other_uri_get_scheme( const pjsip_other_uri *uri )
{
return &uri->scheme;
}
static void *other_uri_get_uri( pjsip_other_uri *uri )
{
return uri;
}
static pj_ssize_t other_uri_print(pjsip_uri_context_e context,
const pjsip_other_uri *uri,
char *buf, pj_size_t size)
{
char *startbuf = buf;
char *endbuf = buf + size;
PJ_UNUSED_ARG(context);
if (uri->scheme.slen + uri->content.slen + 1 > (int)size)
return -1;
/* Print scheme. */
copy_advance(buf, uri->scheme);
*buf++ = ':';
/* Print content. */
copy_advance(buf, uri->content);
return (buf - startbuf);
}
static int other_uri_cmp(pjsip_uri_context_e context,
const pjsip_other_uri *uri1,
const pjsip_other_uri *uri2)
{
PJ_UNUSED_ARG(context);
/* Check that uri2 is also an other_uri */
if (uri1->vptr != uri2->vptr)
return -1;
/* Scheme must match. */
if (pj_stricmp(&uri1->scheme, &uri2->scheme) != 0) {
return PJSIP_ECMPSCHEME;
}
/* Content must match. */
if(pj_stricmp(&uri1->content, &uri2->content) != 0) {
return -1;
}
/* Equal. */
return 0;
}
/* Clone *: URI */
static pjsip_other_uri* other_uri_clone(pj_pool_t *pool,
const pjsip_other_uri *rhs)
{
pjsip_other_uri *uri = pjsip_other_uri_create(pool);
pj_strdup(pool, &uri->scheme, &rhs->scheme);
pj_strdup(pool, &uri->content, &rhs->content);
return uri;
}
File diff suppressed because it is too large. Load diff
@@ -0,0 +1,389 @@
/* $Id$ */
/*
* Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
* Copyright (C) 2003-2008 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/sip_util.h>
#include <pjsip/sip_endpoint.h>
#include <pjsip/sip_errno.h>
#include <pjsip/sip_msg.h>
#include <pj/assert.h>
#include <pj/ctype.h>
#include <pj/except.h>
#include <pj/guid.h>
#include <pj/pool.h>
#include <pj/string.h>
#include <pjlib-util/md5.h>
/**
* Clone the incoming SIP request or response message. A forwarding proxy
* typically would need to clone the incoming SIP message before processing
* the message.
*
* Once a transmit data is created, the reference counter is initialized to 1.
*
* @param endpt The endpoint instance.
* @param rdata The incoming SIP message.
* @param p_tdata Pointer to receive the transmit data containing
* the duplicated message.
*
* @return PJ_SUCCESS on success.
*/
/*
PJ_DEF(pj_status_t) pjsip_endpt_clone_msg( pjsip_endpoint *endpt,
const pjsip_rx_data *rdata,
pjsip_tx_data **p_tdata)
{
pjsip_tx_data *tdata;
pj_status_t status;
status = pjsip_endpt_create_tdata(endpt, &tdata);
if (status != PJ_SUCCESS)
return status;
tdata->msg = pjsip_msg_clone(tdata->pool, rdata->msg_info.msg);
pjsip_tx_data_add_ref(tdata);
*p_tdata = tdata;
return PJ_SUCCESS;
}
*/
/*
* Create new request message to be forwarded upstream to new destination URI
* in uri.
*/
PJ_DEF(pj_status_t) pjsip_endpt_create_request_fwd(pjsip_endpoint *endpt,
pjsip_rx_data *rdata,
const pjsip_uri *uri,
const pj_str_t *branch,
unsigned options,
pjsip_tx_data **p_tdata)
{
pjsip_tx_data *tdata;
pj_status_t status;
PJ_USE_EXCEPTION;
PJ_ASSERT_RETURN(endpt && rdata && p_tdata, PJ_EINVAL);
PJ_ASSERT_RETURN(rdata->msg_info.msg->type == PJSIP_REQUEST_MSG,
PJSIP_ENOTREQUESTMSG);
PJ_UNUSED_ARG(options);
/* Request forwarding rule in RFC 3261 section 16.6:
*
* For each target, the proxy forwards the request following these
* steps:
*
* 1. Make a copy of the received request
* 2. Update the Request-URI
* 3. Update the Max-Forwards header field
* 4. Optionally add a Record-route header field value
* 5. Optionally add additional header fields
* 6. Postprocess routing information
* 7. Determine the next-hop address, port, and transport
* 8. Add a Via header field value
* 9. Add a Content-Length header field if necessary
* 10. Forward the new request
*
* Of these steps, we only do step 1-3, since the later will be
* done by application.
*/
status = pjsip_endpt_create_tdata(endpt, &tdata);
if (status != PJ_SUCCESS)
return status;
/* Always increment ref counter to 1 */
pjsip_tx_data_add_ref(tdata);
/* Duplicate the request */
PJ_TRY {
pjsip_msg *dst;
const pjsip_msg *src = rdata->msg_info.msg;
const pjsip_hdr *hsrc;
/* Create the request */
tdata->msg = dst = pjsip_msg_create(tdata->pool, PJSIP_REQUEST_MSG);
/* Duplicate request method */
pjsip_method_copy(tdata->pool, &tdata->msg->line.req.method,
&src->line.req.method);
/* Set request URI */
if (uri) {
dst->line.req.uri = (pjsip_uri*)
pjsip_uri_clone(tdata->pool, uri);
} else {
dst->line.req.uri= (pjsip_uri*)
pjsip_uri_clone(tdata->pool, src->line.req.uri);
}
/* Clone ALL headers */
hsrc = src->hdr.next;
while (hsrc != &src->hdr) {
pjsip_hdr *hdst;
/* If this is the top-most Via header, insert our own before
* cloning the header.
*/
if (hsrc == (pjsip_hdr*)rdata->msg_info.via) {
pjsip_via_hdr *hvia;
hvia = pjsip_via_hdr_create(tdata->pool);
if (branch)
pj_strdup(tdata->pool, &hvia->branch_param, branch);
else {
pj_str_t new_branch = pjsip_calculate_branch_id(rdata);
pj_strdup(tdata->pool, &hvia->branch_param, &new_branch);
}
pjsip_msg_add_hdr(dst, (pjsip_hdr*)hvia);
}
/* Skip Content-Type and Content-Length as these would be
* generated when the the message is printed.
*/
else if (hsrc->type == PJSIP_H_CONTENT_LENGTH ||
hsrc->type == PJSIP_H_CONTENT_TYPE) {
hsrc = hsrc->next;
continue;
}
#if 0
/* If this is the top-most Route header and it indicates loose
* route, remove the header.
*/
else if (hsrc == (pjsip_hdr*)rdata->msg_info.route) {
const pjsip_route_hdr *hroute = (const pjsip_route_hdr*) hsrc;
const pjsip_sip_uri *sip_uri;
if (!PJSIP_URI_SCHEME_IS_SIP(hroute->name_addr.uri) &&
!PJSIP_URI_SCHEME_IS_SIPS(hroute->name_addr.uri))
{
/* This is a bad request! */
status = PJSIP_EINVALIDHDR;
goto on_error;
}
sip_uri = (pjsip_sip_uri*) hroute->name_addr.uri;
if (sip_uri->lr_param) {
/* Yes lr param is present, skip this Route header */
hsrc = hsrc->next;
continue;
}
}
#endif
/* Clone the header */
hdst = (pjsip_hdr*) pjsip_hdr_clone(tdata->pool, hsrc);
/* If this is Max-Forward header, decrement the value */
if (hdst->type == PJSIP_H_MAX_FORWARDS) {
pjsip_max_fwd_hdr *hmaxfwd = (pjsip_max_fwd_hdr*)hdst;
--hmaxfwd->ivalue;
}
/* Append header to new request */
pjsip_msg_add_hdr(dst, hdst);
hsrc = hsrc->next;
}
/* 16.6.3:
* If the copy does not contain a Max-Forwards header field, the
* proxy MUST add one with a field value, which SHOULD be 70.
*/
if (rdata->msg_info.max_fwd == NULL) {
pjsip_max_fwd_hdr *hmaxfwd =
pjsip_max_fwd_hdr_create(tdata->pool, 70);
pjsip_msg_add_hdr(tdata->msg, (pjsip_hdr*)hmaxfwd);
}
/* Clone request body */
if (src->body) {
dst->body = pjsip_msg_body_clone(tdata->pool, src->body);
}
}
PJ_CATCH_ANY {
status = PJ_ENOMEM;
goto on_error;
}
PJ_END
/* Done */
*p_tdata = tdata;
return PJ_SUCCESS;
on_error:
pjsip_tx_data_dec_ref(tdata);
return status;
}
PJ_DEF(pj_status_t) pjsip_endpt_create_response_fwd( pjsip_endpoint *endpt,
pjsip_rx_data *rdata,
unsigned options,
pjsip_tx_data **p_tdata)
{
pjsip_tx_data *tdata;
pj_status_t status;
PJ_USE_EXCEPTION;
PJ_UNUSED_ARG(options);
status = pjsip_endpt_create_tdata(endpt, &tdata);
if (status != PJ_SUCCESS)
return status;
pjsip_tx_data_add_ref(tdata);
PJ_TRY {
pjsip_msg *dst;
const pjsip_msg *src = rdata->msg_info.msg;
const pjsip_hdr *hsrc;
/* Create the request */
tdata->msg = dst = pjsip_msg_create(tdata->pool, PJSIP_RESPONSE_MSG);
/* Clone the status line */
dst->line.status.code = src->line.status.code;
pj_strdup(tdata->pool, &dst->line.status.reason,
&src->line.status.reason);
/* Duplicate all headers */
hsrc = src->hdr.next;
while (hsrc != &src->hdr) {
/* Skip Content-Type and Content-Length as these would be
* generated when the the message is printed.
*/
if (hsrc->type == PJSIP_H_CONTENT_LENGTH ||
hsrc->type == PJSIP_H_CONTENT_TYPE) {
hsrc = hsrc->next;
continue;
}
/* Remove the first Via header */
else if (hsrc == (pjsip_hdr*) rdata->msg_info.via) {
hsrc = hsrc->next;
continue;
}
pjsip_msg_add_hdr(dst,
(pjsip_hdr*)pjsip_hdr_clone(tdata->pool, hsrc));
hsrc = hsrc->next;
}
/* Clone message body */
if (src->body)
dst->body = pjsip_msg_body_clone(tdata->pool, src->body);
}
PJ_CATCH_ANY {
status = PJ_ENOMEM;
goto on_error;
}
PJ_END;
*p_tdata = tdata;
return PJ_SUCCESS;
on_error:
pjsip_tx_data_dec_ref(tdata);
return status;
}
static void digest2str(const unsigned char digest[], char *output)
{
int i;
for (i = 0; i<16; ++i) {
pj_val_to_hex_digit(digest[i], output);
output += 2;
}
}
PJ_DEF(pj_str_t) pjsip_calculate_branch_id( pjsip_rx_data *rdata )
{
pj_md5_context ctx;
pj_uint8_t digest[16];
pj_str_t branch;
pj_str_t rfc3261_branch = {PJSIP_RFC3261_BRANCH_ID,
PJSIP_RFC3261_BRANCH_LEN};
/* If incoming request does not have RFC 3261 branch value, create
* a branch value from GUID .
*/
if (pj_strnicmp(&rdata->msg_info.via->branch_param,
&rfc3261_branch, PJSIP_RFC3261_BRANCH_LEN) != 0 )
{
pj_str_t tmp;
branch.ptr = (char*)
pj_pool_alloc(rdata->tp_info.pool, PJSIP_MAX_BRANCH_LEN);
branch.slen = PJSIP_RFC3261_BRANCH_LEN;
pj_memcpy(branch.ptr, PJSIP_RFC3261_BRANCH_ID,
PJSIP_RFC3261_BRANCH_LEN);
tmp.ptr = branch.ptr + PJSIP_RFC3261_BRANCH_LEN + 2;
*(tmp.ptr-2) = (pj_int8_t)(branch.slen+73);
*(tmp.ptr-1) = (pj_int8_t)(branch.slen+99);
pj_generate_unique_string( &tmp );
branch.slen = PJSIP_MAX_BRANCH_LEN;
return branch;
}
/* Create branch ID for new request by calculating MD5 hash
* of the branch parameter in top-most Via header.
*/
pj_md5_init(&ctx);
pj_md5_update(&ctx, (pj_uint8_t*)rdata->msg_info.via->branch_param.ptr,
(unsigned)rdata->msg_info.via->branch_param.slen);
pj_md5_final(&ctx, digest);
branch.ptr = (char*)
pj_pool_alloc(rdata->tp_info.pool,
34 + PJSIP_RFC3261_BRANCH_LEN);
pj_memcpy(branch.ptr, PJSIP_RFC3261_BRANCH_ID, PJSIP_RFC3261_BRANCH_LEN);
branch.slen = PJSIP_RFC3261_BRANCH_LEN;
*(branch.ptr+PJSIP_RFC3261_BRANCH_LEN) = (pj_int8_t)(branch.slen+73);
*(branch.ptr+PJSIP_RFC3261_BRANCH_LEN+1) = (pj_int8_t)(branch.slen+99);
digest2str(digest, branch.ptr+PJSIP_RFC3261_BRANCH_LEN+2);
branch.slen = 34 + PJSIP_RFC3261_BRANCH_LEN;
return branch;
}
@@ -0,0 +1,24 @@
/* $Id$ */
/*
* Copyright (C) 2009-2011 Teluu Inc. (http://www.teluu.com)
*
* 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
*/
/*
* This file is a C++ wrapper, see ticket #886 for details.
*/
#include "sip_util_proxy.c"
@@ -0,0 +1,192 @@
/* $Id$ */
/*
* Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
* Copyright (C) 2003-2008 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/sip_util.h>
#include <pjsip/sip_module.h>
#include <pjsip/sip_endpoint.h>
#include <pjsip/sip_transaction.h>
#include <pjsip/sip_event.h>
#include <pjsip/sip_errno.h>
#include <pj/assert.h>
#include <pj/log.h>
#include <pj/pool.h>
#include <pj/string.h>
struct tsx_data
{
void *token;
void (*cb)(void*, pjsip_event*);
};
static void mod_util_on_tsx_state(pjsip_transaction*, pjsip_event*);
/* This module will be registered in pjsip_endpt.c */
pjsip_module mod_stateful_util =
{
NULL, NULL, /* prev, next. */
{ "mod-stateful-util", 17 }, /* Name. */
-1, /* Id */
PJSIP_MOD_PRIORITY_APPLICATION, /* Priority */
NULL, /* load() */
NULL, /* start() */
NULL, /* stop() */
NULL, /* unload() */
NULL, /* on_rx_request() */
NULL, /* on_rx_response() */
NULL, /* on_tx_request. */
NULL, /* on_tx_response() */
&mod_util_on_tsx_state, /* on_tsx_state() */
};
static void mod_util_on_tsx_state(pjsip_transaction *tsx, pjsip_event *event)
{
struct tsx_data *tsx_data;
/* Check if the module has been unregistered (see ticket #1535) and also
* verify the event type.
*/
if (mod_stateful_util.id < 0 || event->type != PJSIP_EVENT_TSX_STATE)
return;
tsx_data = (struct tsx_data*) tsx->mod_data[mod_stateful_util.id];
if (tsx_data == NULL)
return;
if (tsx->status_code < 200)
return;
/* Call the callback, if any, and prevent the callback to be called again
* by clearing the transaction's module_data.
*/
tsx->mod_data[mod_stateful_util.id] = NULL;
if (tsx_data->cb) {
(*tsx_data->cb)(tsx_data->token, event);
}
}
PJ_DEF(pj_status_t) pjsip_endpt_send_request( pjsip_endpoint *endpt,
pjsip_tx_data *tdata,
pj_int32_t timeout,
void *token,
pjsip_endpt_send_callback cb)
{
pjsip_transaction *tsx;
struct tsx_data *tsx_data;
pj_status_t status;
PJ_ASSERT_RETURN(endpt && tdata && (timeout==-1 || timeout>0), PJ_EINVAL);
/* Check that transaction layer module is registered to endpoint */
PJ_ASSERT_RETURN(mod_stateful_util.id != -1, PJ_EINVALIDOP);
PJ_UNUSED_ARG(timeout);
status = pjsip_tsx_create_uac(&mod_stateful_util, tdata, &tsx);
if (status != PJ_SUCCESS) {
pjsip_tx_data_dec_ref(tdata);
return status;
}
pjsip_tsx_set_transport(tsx, &tdata->tp_sel);
tsx_data = PJ_POOL_ALLOC_T(tsx->pool, struct tsx_data);
tsx_data->token = token;
tsx_data->cb = cb;
tsx->mod_data[mod_stateful_util.id] = tsx_data;
status = pjsip_tsx_send_msg(tsx, NULL);
if (status != PJ_SUCCESS)
pjsip_tx_data_dec_ref(tdata);
return status;
}
/*
* Send response statefully.
*/
PJ_DEF(pj_status_t) pjsip_endpt_respond( pjsip_endpoint *endpt,
pjsip_module *tsx_user,
pjsip_rx_data *rdata,
int st_code,
const pj_str_t *st_text,
const pjsip_hdr *hdr_list,
const pjsip_msg_body *body,
pjsip_transaction **p_tsx )
{
pj_status_t status;
pjsip_tx_data *tdata;
pjsip_transaction *tsx;
/* Validate arguments. */
PJ_ASSERT_RETURN(endpt && rdata, PJ_EINVAL);
if (p_tsx) *p_tsx = NULL;
/* Create response message */
status = pjsip_endpt_create_response( endpt, rdata, st_code, st_text,
&tdata);
if (status != PJ_SUCCESS)
return status;
/* Add the message headers, if any */
if (hdr_list) {
const pjsip_hdr *hdr = hdr_list->next;
while (hdr != hdr_list) {
pjsip_msg_add_hdr(tdata->msg, (pjsip_hdr*)
pjsip_hdr_clone(tdata->pool, hdr) );
hdr = hdr->next;
}
}
/* Add the message body, if any. */
if (body) {
tdata->msg->body = pjsip_msg_body_clone( tdata->pool, body );
if (tdata->msg->body == NULL) {
pjsip_tx_data_dec_ref(tdata);
return status;
}
}
/* Create UAS transaction. */
status = pjsip_tsx_create_uas(tsx_user, rdata, &tsx);
if (status != PJ_SUCCESS) {
pjsip_tx_data_dec_ref(tdata);
return status;
}
/* Feed the request to the transaction. */
pjsip_tsx_recv_msg(tsx, rdata);
/* Send the message. */
status = pjsip_tsx_send_msg(tsx, tdata);
if (status != PJ_SUCCESS) {
pjsip_tx_data_dec_ref(tdata);
} else if (p_tsx) {
*p_tsx = tsx;
}
return status;
}
@@ -0,0 +1,24 @@
/* $Id$ */
/*
* Copyright (C) 2009-2011 Teluu Inc. (http://www.teluu.com)
*
* 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
*/
/*
* This file is a C++ wrapper, see ticket #886 for details.
*/
#include "sip_util.c"
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
@@ -0,0 +1,789 @@
/* $Id$ */
/*
* Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
* Copyright (C) 2003-2008 Benny Prijono <benny@prijono.org>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#include <pjsua-lib/pjsua.h>
#include <pjsua-lib/pjsua_internal.h>
#define THIS_FILE "pjsua_im.h"
/* 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_method_e) 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 };
/* Check if content type is acceptable */
#if 0
static pj_bool_t acceptable_message(const pjsip_media_type *mime)
{
const pj_str_t STR_MIME_TEXT = { "text", 4 };
const pj_str_t STR_MIME_PLAIN = { "plain", 5 };
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);
}
#endif
/**
* 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,
pjsip_accept_hdr **p_accept_hdr)
{
/* Some UA sends text/html, so this check will break */
#if 0
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_ctype_hdr*)
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;
}
#elif 0
pjsip_msg *msg;
msg = rdata->msg_info.msg;
if (msg->body == NULL) {
/* Create Accept header. */
if (p_accept_hdr)
*p_accept_hdr = pjsua_im_create_accept(rdata->tp_info.pool);
return PJ_FALSE;
}
#else
/* Ticket #693: allow incoming MESSAGE without message body */
PJ_UNUSED_ARG(rdata);
PJ_UNUSED_ARG(p_accept_hdr);
#endif
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_id, const pj_str_t *from,
const pj_str_t *to, pjsip_rx_data *rdata)
{
pjsip_contact_hdr *contact_hdr;
pj_str_t contact;
pjsip_msg_body *body = rdata->msg_info.msg->body;
#if 0
/* Ticket #693: allow incoming MESSAGE without message body */
/* Body MUST have been checked before */
pj_assert(body != NULL);
#endif
/* Build remote contact */
contact_hdr = (pjsip_contact_hdr*)
pjsip_msg_find_hdr(rdata->msg_info.msg, PJSIP_H_CONTACT,
NULL);
if (contact_hdr && contact_hdr->uri) {
contact.ptr = (char*) pj_pool_alloc(rdata->tp_info.pool,
PJSIP_MAX_URL_SIZE);
contact.slen = pjsip_uri_print(PJSIP_URI_IN_CONTACT_HDR,
contact_hdr->uri, contact.ptr,
PJSIP_MAX_URL_SIZE);
} else {
contact.slen = 0;
}
if (body && pj_stricmp(&body->content_type.type, &STR_MIME_APP)==0 &&
pj_stricmp(&body->content_type.subtype, &STR_MIME_ISCOMPOSING)==0)
{
/* Expecting typing indication */
pj_status_t status;
pj_bool_t is_typing;
status = pjsip_iscomposing_parse(rdata->tp_info.pool, (char*)body->data,
body->len, &is_typing, NULL, NULL,
NULL );
if (status != PJ_SUCCESS) {
pjsua_perror(THIS_FILE, "Invalid MESSAGE body", status);
return;
}
if (pjsua_var.ua_cfg.cb.on_typing) {
(*pjsua_var.ua_cfg.cb.on_typing)(call_id, from, to, &contact,
is_typing);
}
if (pjsua_var.ua_cfg.cb.on_typing2) {
pjsua_acc_id acc_id;
if (call_id == PJSUA_INVALID_ID) {
acc_id = pjsua_acc_find_for_incoming(rdata);
} else {
pjsua_call *call = &pjsua_var.calls[call_id];
acc_id = call->acc_id;
}
if (acc_id != PJSUA_INVALID_ID) {
(*pjsua_var.ua_cfg.cb.on_typing2)(call_id, from, to, &contact,
is_typing, rdata, acc_id);
}
}
} else {
pj_str_t mime_type;
char buf[256];
pjsip_media_type *m;
pj_str_t text_body;
/* Save text body */
if (body) {
text_body.ptr = (char*)rdata->msg_info.msg->body->data;
text_body.slen = rdata->msg_info.msg->body->len;
/* Get mime type */
m = &rdata->msg_info.msg->body->content_type;
mime_type.ptr = buf;
mime_type.slen = pj_ansi_snprintf(buf, sizeof(buf),
"%.*s/%.*s",
(int)m->type.slen,
m->type.ptr,
(int)m->subtype.slen,
m->subtype.ptr);
if (mime_type.slen < 1)
mime_type.slen = 0;
} else {
text_body.ptr = mime_type.ptr = "";
text_body.slen = mime_type.slen = 0;
}
if (pjsua_var.ua_cfg.cb.on_pager) {
(*pjsua_var.ua_cfg.cb.on_pager)(call_id, from, to, &contact,
&mime_type, &text_body);
}
if (pjsua_var.ua_cfg.cb.on_pager2) {
pjsua_acc_id acc_id;
if (call_id == PJSUA_INVALID_ID) {
acc_id = pjsua_acc_find_for_incoming(rdata);
} else {
pjsua_call *call = &pjsua_var.calls[call_id];
acc_id = call->acc_id;
}
if (acc_id != PJSUA_INVALID_ID) {
(*pjsua_var.ua_cfg.cb.on_pager2)(call_id, from, to, &contact,
&mime_type, &text_body, rdata,
acc_id);
}
}
}
}
/*
* 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;
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_var.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.
*/
pjsip_endpt_respond( pjsua_var.endpt, NULL, rdata, 200, NULL,
NULL, NULL, NULL);
/* For the source URI, we use Contact header if present, since
* Contact header contains the port number information. If this is
* not available, then use From header.
*/
from.ptr = (char*)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 = (char*) 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)
{
pjsua_im_data *im_data = (pjsua_im_data*) token;
if (e->type == PJSIP_EVENT_TSX_STATE) {
pjsip_transaction *tsx = e->body.tsx_state.tsx;
/* Ignore provisional response, if any */
if (tsx->status_code < 200)
return;
/* Handle authentication challenges */
if (e->body.tsx_state.type == PJSIP_EVENT_RX_MSG &&
(tsx->status_code == 401 || tsx->status_code == 407))
{
pjsip_rx_data *rdata = e->body.tsx_state.src.rdata;
pjsip_tx_data *tdata;
pjsip_auth_clt_sess auth;
pj_status_t status;
PJ_LOG(4,(THIS_FILE, "Resending IM with authentication"));
/* Create temporary authentication session */
pjsip_auth_clt_init(&auth,pjsua_var.endpt,rdata->tp_info.pool, 0);
pjsip_auth_clt_set_credentials(&auth,
pjsua_var.acc[im_data->acc_id].cred_cnt,
pjsua_var.acc[im_data->acc_id].cred);
pjsip_auth_clt_set_prefs(&auth,
&pjsua_var.acc[im_data->acc_id].cfg.auth_pref);
status = pjsip_auth_clt_reinit_req(&auth, rdata, tsx->last_tx,
&tdata);
if (status == PJ_SUCCESS) {
pjsua_im_data *im_data2;
/* Must duplicate im_data */
im_data2 = pjsua_im_data_dup(tdata->pool, im_data);
/* Increment CSeq */
PJSIP_MSG_CSEQ_HDR(tdata->msg)->cseq++;
/* Re-send request */
status = pjsip_endpt_send_request( pjsua_var.endpt, tdata, -1,
im_data2, &im_callback);
if (status == PJ_SUCCESS) {
/* Done */
pjsip_auth_clt_deinit(&auth);
return;
}
pjsip_auth_clt_deinit(&auth);
/* Don't invoke callback if another callback (with auth,
* different tsx) has been called.
*/
if (im_data2->acc_id == PJSUA_INVALID_ID)
return;
}
}
if (tsx->status_code/100 == 2) {
PJ_LOG(4,(THIS_FILE,
"Message \'%s\' delivered successfully",
im_data->body.ptr));
} else {
PJ_LOG(3,(THIS_FILE,
"Failed to deliver message \'%s\': %d/%.*s",
im_data->body.ptr,
tsx->status_code,
(int)tsx->status_text.slen,
tsx->status_text.ptr));
}
if (pjsua_var.ua_cfg.cb.on_pager_status) {
pj_str_t im_body = im_data->body;
if (im_body.slen==0) {
pjsip_msg_body *body = tsx->last_tx->msg->body;
pj_strset(&im_body, body->data, body->len);
}
pjsua_var.ua_cfg.cb.on_pager_status(im_data->call_id,
&im_data->to,
&im_body,
im_data->user_data,
(pjsip_status_code)
tsx->status_code,
&tsx->status_text);
}
if (pjsua_var.ua_cfg.cb.on_pager_status2) {
pjsip_rx_data *rdata;
pj_str_t im_body = im_data->body;
if (e->body.tsx_state.type == PJSIP_EVENT_RX_MSG)
rdata = e->body.tsx_state.src.rdata;
else
rdata = NULL;
if (im_body.slen==0) {
pjsip_msg_body *body = tsx->last_tx->msg->body;
pj_strset(&im_body, body->data, body->len);
}
pjsua_var.ua_cfg.cb.on_pager_status2(im_data->call_id,
&im_data->to,
&im_body,
im_data->user_data,
(pjsip_status_code)
tsx->status_code,
&tsx->status_text,
tsx->last_tx,
rdata, im_data->acc_id);
}
/* Reset acc_id after invoking callback */
im_data->acc_id = PJSUA_INVALID_ID;
}
}
/* Outgoing typing indication callback.
* (used to reauthenticate request)
*/
static void typing_callback(void *token, pjsip_event *e)
{
pjsua_im_data *im_data = (pjsua_im_data*) token;
if (e->type == PJSIP_EVENT_TSX_STATE) {
pjsip_transaction *tsx = e->body.tsx_state.tsx;
/* Ignore provisional response, if any */
if (tsx->status_code < 200)
return;
/* Handle authentication challenges */
if (e->body.tsx_state.type == PJSIP_EVENT_RX_MSG &&
(tsx->status_code == 401 || tsx->status_code == 407))
{
pjsip_rx_data *rdata = e->body.tsx_state.src.rdata;
pjsip_tx_data *tdata;
pjsip_auth_clt_sess auth;
pj_status_t status;
PJ_LOG(4,(THIS_FILE, "Resending IM with authentication"));
/* Create temporary authentication session */
pjsip_auth_clt_init(&auth,pjsua_var.endpt,rdata->tp_info.pool, 0);
pjsip_auth_clt_set_credentials(&auth,
pjsua_var.acc[im_data->acc_id].cred_cnt,
pjsua_var.acc[im_data->acc_id].cred);
pjsip_auth_clt_set_prefs(&auth,
&pjsua_var.acc[im_data->acc_id].cfg.auth_pref);
status = pjsip_auth_clt_reinit_req(&auth, rdata, tsx->last_tx,
&tdata);
if (status == PJ_SUCCESS) {
pjsua_im_data *im_data2;
/* Must duplicate im_data */
im_data2 = pjsua_im_data_dup(tdata->pool, im_data);
/* Increment CSeq */
PJSIP_MSG_CSEQ_HDR(tdata->msg)->cseq++;
/* Re-send request */
status = pjsip_endpt_send_request( pjsua_var.endpt, tdata, -1,
im_data2, &typing_callback);
if (status == PJ_SUCCESS) {
/* Done */
pjsip_auth_clt_deinit(&auth);
return;
}
pjsip_auth_clt_deinit(&auth);
}
}
}
}
/*
* Send instant messaging outside dialog, using the specified account for
* route set and authentication.
*/
PJ_DEF(pj_status_t) pjsua_im_send( pjsua_acc_id acc_id,
const pj_str_t *to,
const pj_str_t *mime_type,
const pj_str_t *content,
const pjsua_msg_data *msg_data,
void *user_data)
{
pjsip_tx_data *tdata;
const pj_str_t mime_text_plain = pj_str("text/plain");
pjsip_media_type media_type;
pjsua_im_data *im_data;
pjsua_acc *acc;
pj_bool_t content_in_msg_data;
pj_status_t status;
content_in_msg_data = msg_data && (msg_data->msg_body.slen ||
msg_data->multipart_ctype.type.slen);
/* To and message body must be specified. */
PJ_ASSERT_RETURN(to && (content || content_in_msg_data), PJ_EINVAL);
acc = &pjsua_var.acc[acc_id];
/* Create request. */
status = pjsip_endpt_create_request(pjsua_var.endpt,
&pjsip_message_method,
(msg_data && msg_data->target_uri.slen?
&msg_data->target_uri: to),
&acc->cfg.id,
to, NULL, NULL, -1, NULL, &tdata);
if (status != PJ_SUCCESS) {
pjsua_perror(THIS_FILE, "Unable to create request", status);
return status;
}
/* If account is locked to specific transport, then set transport to
* the request.
*/
if (acc->cfg.transport_id != PJSUA_INVALID_ID) {
pjsip_tpselector tp_sel;
pjsua_init_tpselector(acc->cfg.transport_id, &tp_sel);
pjsip_tx_data_set_transport(tdata, &tp_sel);
}
/* Add accept header. */
pjsip_msg_add_hdr( tdata->msg,
(pjsip_hdr*)pjsua_im_create_accept(tdata->pool));
/* Create suitable Contact header unless a Contact header has been
* set in the account.
*/
/* Ticket #1632: According to RFC 3428:
* MESSAGE requests do not initiate dialogs.
* User Agents MUST NOT insert Contact header fields into MESSAGE requests
*/
/*
if (acc->contact.slen) {
contact = acc->contact;
} else {
status = pjsua_acc_create_uac_contact(tdata->pool, &contact, acc_id, to);
if (status != PJ_SUCCESS) {
pjsua_perror(THIS_FILE, "Unable to generate Contact header", status);
pjsip_tx_data_dec_ref(tdata);
return status;
}
}
pjsip_msg_add_hdr( tdata->msg, (pjsip_hdr*)
pjsip_generic_string_hdr_create(tdata->pool,
&STR_CONTACT, &contact));
*/
/* Create IM data to keep message details and give it back to
* application on the callback
*/
im_data = PJ_POOL_ZALLOC_T(tdata->pool, pjsua_im_data);
im_data->acc_id = acc_id;
im_data->call_id = PJSUA_INVALID_ID;
pj_strdup_with_null(tdata->pool, &im_data->to, to);
im_data->user_data = user_data;
/* Add message body, if content is set */
if (content) {
pj_strdup_with_null(tdata->pool, &im_data->body, content);
/* Set default media type if none is specified */
if (mime_type == NULL) {
mime_type = &mime_text_plain;
}
/* Parse MIME type */
pjsua_parse_media_type(tdata->pool, mime_type, &media_type);
/* Add message body */
tdata->msg->body = pjsip_msg_body_create( tdata->pool, &media_type.type,
&media_type.subtype,
&im_data->body);
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;
}
}
/* Add additional headers etc. */
pjsua_process_msg_data(tdata, msg_data);
/* Add route set */
pjsua_set_msg_route_set(tdata, &acc->route_set);
/* If via_addr is set, use this address for the Via header. */
if (acc->cfg.allow_via_rewrite && acc->via_addr.host.slen > 0) {
tdata->via_addr = acc->via_addr;
tdata->via_tp = acc->via_tp;
}
/* Send request (statefully) */
status = pjsip_endpt_send_request( pjsua_var.endpt, tdata, -1,
im_data, &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_DEF(pj_status_t) pjsua_im_typing( pjsua_acc_id acc_id,
const pj_str_t *to,
pj_bool_t is_typing,
const pjsua_msg_data *msg_data)
{
pjsua_im_data *im_data;
pjsip_tx_data *tdata;
pjsua_acc *acc;
pj_status_t status;
acc = &pjsua_var.acc[acc_id];
/* Create request. */
status = pjsip_endpt_create_request( pjsua_var.endpt, &pjsip_message_method,
to, &acc->cfg.id,
to, NULL, NULL, -1, NULL, &tdata);
if (status != PJ_SUCCESS) {
pjsua_perror(THIS_FILE, "Unable to create request", status);
return status;
}
/* If account is locked to specific transport, then set transport to
* the request.
*/
if (acc->cfg.transport_id != PJSUA_INVALID_ID) {
pjsip_tpselector tp_sel;
pjsua_init_tpselector(acc->cfg.transport_id, &tp_sel);
pjsip_tx_data_set_transport(tdata, &tp_sel);
}
/* Add accept header. */
pjsip_msg_add_hdr( tdata->msg,
(pjsip_hdr*)pjsua_im_create_accept(tdata->pool));
/* Create suitable Contact header unless a Contact header has been
* set in the account.
*/
/* Ticket #1632: According to RFC 3428:
* MESSAGE requests do not initiate dialogs.
* User Agents MUST NOT insert Contact header fields into MESSAGE requests
*/
/*
if (acc->contact.slen) {
contact = acc->contact;
} else {
status = pjsua_acc_create_uac_contact(tdata->pool, &contact, acc_id, to);
if (status != PJ_SUCCESS) {
pjsua_perror(THIS_FILE, "Unable to generate Contact header", status);
pjsip_tx_data_dec_ref(tdata);
return status;
}
}
pjsip_msg_add_hdr( tdata->msg, (pjsip_hdr*)
pjsip_generic_string_hdr_create(tdata->pool,
&STR_CONTACT, &contact));
*/
/* Create "application/im-iscomposing+xml" msg body. */
tdata->msg->body = pjsip_iscomposing_create_body( tdata->pool, is_typing,
NULL, NULL, -1);
/* Add additional headers etc. */
pjsua_process_msg_data(tdata, msg_data);
/* Add route set */
pjsua_set_msg_route_set(tdata, &acc->route_set);
/* If via_addr is set, use this address for the Via header. */
if (acc->cfg.allow_via_rewrite && acc->via_addr.host.slen > 0) {
tdata->via_addr = acc->via_addr;
tdata->via_tp = acc->via_tp;
}
/* Create data to reauthenticate */
im_data = PJ_POOL_ZALLOC_T(tdata->pool, pjsua_im_data);
im_data->acc_id = acc_id;
/* Send request (statefully) */
status = pjsip_endpt_send_request( pjsua_var.endpt, tdata, -1,
im_data, &typing_callback);
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 };
const pj_str_t STR_MIME_TEXT_PLAIN = { "text/plain", 10 };
const pj_str_t STR_MIME_APP_ISCOMPOSING =
{ "application/im-iscomposing+xml", 30 };
pj_status_t status;
/* Register module */
status = pjsip_endpt_register_module(pjsua_var.endpt, &mod_pjsua_im);
if (status != PJ_SUCCESS)
return status;
/* Register support for MESSAGE method. */
pjsip_endpt_add_capability( pjsua_var.endpt, &mod_pjsua_im, PJSIP_H_ALLOW,
NULL, 1, &msg_tag);
/* Register support for "application/im-iscomposing+xml" content */
pjsip_endpt_add_capability( pjsua_var.endpt, &mod_pjsua_im, PJSIP_H_ACCEPT,
NULL, 1, &STR_MIME_APP_ISCOMPOSING);
/* Register support for "text/plain" content */
pjsip_endpt_add_capability( pjsua_var.endpt, &mod_pjsua_im, PJSIP_H_ACCEPT,
NULL, 1, &STR_MIME_TEXT_PLAIN);
return PJ_SUCCESS;
}
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
@@ -0,0 +1,39 @@
/* $Id$ */
/*
* Copyright (C) 2012 Teluu Inc. (http://www.teluu.com)
*
* 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 <pjsua2/endpoint.hpp>
using namespace pj;
extern "C"
int main(int argc, char *argv[])
{
Endpoint ep;
EpConfig epCfg;
epCfg.uaConfig.userAgent = "pjsua++-test";
ep.libCreate();
ep.libInit(epCfg);
ep.libStart();
ep.libDestroy();
return 0;
}
File diff suppressed because it is too large. Load diff
+975
View File
@@ -0,0 +1,975 @@
/* $Id$ */
/*
* Copyright (C) 2012-2013 Teluu Inc. (http://www.teluu.com)
*
* 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 <pjsua2/account.hpp>
#include <pjsua2/call.hpp>
#include <pjsua2/endpoint.hpp>
#include <pj/ctype.h>
#include "util.hpp"
using namespace pj;
using namespace std;
#include <pjsua-lib/pjsua_internal.h>
#define THIS_FILE "call.cpp"
///////////////////////////////////////////////////////////////////////////////
/* Avoid conflict with predefined standard macros. */
#undef max
#undef min
MathStat::MathStat()
: n(0), max(0), min(0), last(0), mean(0)
{
}
void MathStat::fromPj(const pj_math_stat &prm)
{
this->n = prm.n;
this->max = prm.max;
this->min = prm.min;
this->last = prm.last;
this->mean = prm.mean;
}
void RtcpStreamStat::fromPj(const pjmedia_rtcp_stream_stat &prm)
{
this->update.fromPj(prm.update);
this->updateCount = prm.update_cnt;
this->pkt = (unsigned)prm.pkt;
this->bytes = (unsigned)prm.bytes;
this->discard = prm.discard;
this->loss = prm.loss;
this->reorder = prm.loss;
this->dup = prm.dup;
this->lossPeriodUsec.fromPj(prm.loss_period);
this->lossType.burst = prm.loss_type.burst;
this->lossType.random = prm.loss_type.random;
this->jitterUsec.fromPj(prm.jitter);
}
void RtcpSdes::fromPj(const pjmedia_rtcp_sdes &prm)
{
this->cname = pj2Str(prm.cname);
this->name = pj2Str(prm.name);
this->email = pj2Str(prm.email);
this->phone = pj2Str(prm.phone);
this->loc = pj2Str(prm.loc);
this->tool = pj2Str(prm.tool);
this->note = pj2Str(prm.note);
}
void RtcpStat::fromPj(const pjmedia_rtcp_stat &prm)
{
this->start.fromPj(prm.start);
this->txStat.fromPj(prm.tx);
this->rxStat.fromPj(prm.rx);
this->rttUsec.fromPj(prm.rtt);
this->rtpTxLastTs = prm.rtp_tx_last_ts;
this->rtpTxLastSeq = prm.rtp_tx_last_seq;
#if defined(PJMEDIA_RTCP_STAT_HAS_IPDV) && PJMEDIA_RTCP_STAT_HAS_IPDV!=0
this->rxIpdvUsec.fromPj(prm.rx_ipdv);
#endif
#if defined(PJMEDIA_RTCP_STAT_HAS_RAW_JITTER) && \
PJMEDIA_RTCP_STAT_HAS_RAW_JITTER!=0
this->rxRawJitterUsec.fromPj(prm.rx_raw_jitter);
#endif
this->peerSdes.fromPj(prm.peer_sdes);
}
void JbufState::fromPj(const pjmedia_jb_state &prm)
{
this->frameSize = prm.frame_size;
this->minPrefetch = prm.min_prefetch;
this->maxPrefetch = prm.max_prefetch;
this->burst = prm.burst;
this->prefetch = prm.prefetch;
this->size = prm.size;
this->avgDelayMsec = prm.avg_delay;
this->minDelayMsec = prm.min_delay;
this->maxDelayMsec = prm.max_delay;
this->devDelayMsec = prm.dev_delay;
this->avgBurst = prm.avg_burst;
this->lost = prm.lost;
this->discard = prm.discard;
this->empty = prm.empty;
}
void SdpSession::fromPj(const pjmedia_sdp_session &sdp)
{
#if PJSUA2_MAX_SDP_BUF_LEN
char buf[PJSUA2_MAX_SDP_BUF_LEN];
int len;
len = pjmedia_sdp_print(&sdp, buf, sizeof(buf));
wholeSdp = (len > -1? string(buf, len): "");
#else
wholeSdp = "";
#endif
pjSdpSession = (void *)&sdp;
}
void MediaTransportInfo::fromPj(const pjmedia_transport_info &info)
{
char straddr[PJ_INET6_ADDRSTRLEN+10];
localRtpName = localRtcpName = srcRtpName = srcRtcpName = "";
if (pj_sockaddr_has_addr(&info.sock_info.rtp_addr_name)) {
pj_sockaddr_print(&info.sock_info.rtp_addr_name, straddr,
sizeof(straddr), 3);
localRtpName = straddr;
}
if (pj_sockaddr_has_addr(&info.sock_info.rtcp_addr_name)) {
pj_sockaddr_print(&info.sock_info.rtcp_addr_name, straddr,
sizeof(straddr), 3);
localRtcpName = straddr;
}
if (pj_sockaddr_has_addr(&info.src_rtp_name)) {
pj_sockaddr_print(&info.src_rtp_name, straddr, sizeof(straddr), 3);
srcRtpName = straddr;
}
if (pj_sockaddr_has_addr(&info.src_rtcp_name)) {
pj_sockaddr_print(&info.src_rtcp_name, straddr, sizeof(straddr), 3);
srcRtcpName = straddr;
}
}
//////////////////////////////////////////////////////////////////////////////
CallOpParam::CallOpParam(bool useDefaultCallSetting)
: statusCode(pjsip_status_code(0)), reason(""), options(0)
{
sdp.wholeSdp = "";
if (useDefaultCallSetting)
opt = CallSetting(true);
}
CallSendRequestParam::CallSendRequestParam()
: method("")
{
}
CallVidSetStreamParam::CallVidSetStreamParam()
{
#if PJSUA_HAS_VIDEO
pjsua_call_vid_strm_op_param prm;
pjsua_call_vid_strm_op_param_default(&prm);
this->medIdx = prm.med_idx;
this->dir = prm.dir;
this->capDev = prm.cap_dev;
#endif
}
CallSendDtmfParam::CallSendDtmfParam()
{
pjsua_call_send_dtmf_param param;
pjsua_call_send_dtmf_param_default(&param);
fromPj(param);
}
pjsua_call_send_dtmf_param CallSendDtmfParam::toPj() const
{
pjsua_call_send_dtmf_param param;
pjsua_call_send_dtmf_param_default(&param);
param.method = this->method;
param.duration = this->duration;
param.digits = str2Pj(this->digits);
return param;
}
void CallSendDtmfParam::fromPj(const pjsua_call_send_dtmf_param &param)
{
this->method = param.method;
this->duration = param.duration;
this->digits = pj2Str(param.digits);
}
CallSetting::CallSetting(bool useDefaultValues)
{
if (useDefaultValues) {
pjsua_call_setting setting;
pjsua_call_setting_default(&setting);
fromPj(setting);
} else {
flag = 0;
reqKeyframeMethod = 0;
audioCount = 0;
videoCount = 0;
}
}
bool CallSetting::isEmpty() const
{
return (flag == 0 && reqKeyframeMethod == 0 && audioCount == 0 &&
videoCount == 0);
}
void CallSetting::fromPj(const pjsua_call_setting &prm)
{
this->flag = prm.flag;
this->reqKeyframeMethod = prm.req_keyframe_method;
this->audioCount = prm.aud_cnt;
this->videoCount = prm.vid_cnt;
}
pjsua_call_setting CallSetting::toPj() const
{
pjsua_call_setting setting;
setting.flag = this->flag;
setting.req_keyframe_method = this->reqKeyframeMethod;
setting.aud_cnt = this->audioCount;
setting.vid_cnt = this->videoCount;
return setting;
}
CallMediaInfo::CallMediaInfo()
: type(PJMEDIA_TYPE_NONE),
dir(PJMEDIA_DIR_NONE),
status(PJSUA_CALL_MEDIA_NONE),
audioConfSlot(PJSUA_INVALID_ID),
videoIncomingWindowId(PJSUA_INVALID_ID),
videoWindow(PJSUA_INVALID_ID),
videoCapDev(PJMEDIA_VID_INVALID_DEV)
{
}
void CallMediaInfo::fromPj(const pjsua_call_media_info &prm)
{
this->index = prm.index;
this->type = prm.type;
this->dir = prm.dir;
this->status = prm.status;
if (this->type == PJMEDIA_TYPE_AUDIO) {
this->audioConfSlot = (int)prm.stream.aud.conf_slot;
} else if (this->type == PJMEDIA_TYPE_VIDEO) {
this->videoIncomingWindowId = prm.stream.vid.win_in;
this->videoWindow = VideoWindow(prm.stream.vid.win_in);
this->videoCapDev = prm.stream.vid.cap_dev;
}
}
void CallInfo::fromPj(const pjsua_call_info &pci)
{
unsigned mi;
id = pci.id;
role = pci.role;
accId = pci.acc_id;
localUri = pj2Str(pci.local_info);
localContact = pj2Str(pci.local_contact);
remoteUri = pj2Str(pci.remote_info);
remoteContact = pj2Str(pci.remote_contact);
callIdString = pj2Str(pci.call_id);
setting.fromPj(pci.setting);
state = pci.state;
stateText = pj2Str(pci.state_text);
lastStatusCode = pci.last_status;
lastReason = pj2Str(pci.last_status_text);
connectDuration.fromPj(pci.connect_duration);
totalDuration.fromPj(pci.total_duration);
remOfferer = PJ2BOOL(pci.rem_offerer);
remAudioCount = pci.rem_aud_cnt;
remVideoCount = pci.rem_vid_cnt;
for (mi = 0; mi < pci.media_cnt; mi++) {
CallMediaInfo med;
med.fromPj(pci.media[mi]);
media.push_back(med);
}
for (mi = 0; mi < pci.prov_media_cnt; mi++) {
CallMediaInfo med;
med.fromPj(pci.prov_media[mi]);
provMedia.push_back(med);
}
}
void StreamInfo::fromPj(const pjsua_stream_info &info)
{
char straddr[PJ_INET6_ADDRSTRLEN+10];
type = info.type;
if (type == PJMEDIA_TYPE_AUDIO) {
proto = info.info.aud.proto;
dir = info.info.aud.dir;
pj_sockaddr_print(&info.info.aud.rem_addr, straddr, sizeof(straddr), 3);
remoteRtpAddress = straddr;
pj_sockaddr_print(&info.info.aud.rem_rtcp, straddr, sizeof(straddr), 3);
remoteRtcpAddress = straddr;
txPt = info.info.aud.tx_pt;
rxPt = info.info.aud.rx_pt;
codecName = pj2Str(info.info.aud.fmt.encoding_name);
codecClockRate = info.info.aud.fmt.clock_rate;
audCodecParam.fromPj(*info.info.aud.param);
jbInit = info.info.aud.jb_init;
jbMinPre = info.info.aud.jb_min_pre;
jbMaxPre = info.info.aud.jb_max_pre;
jbMax = info.info.aud.jb_max;
jbDiscardAlgo = info.info.aud.jb_discard_algo;
#if defined(PJMEDIA_STREAM_ENABLE_KA) && (PJMEDIA_STREAM_ENABLE_KA != 0)
useKa = PJ2BOOL(info.info.aud.use_ka);
#endif
rtcpSdesByeDisabled = PJ2BOOL(info.info.aud.rtcp_sdes_bye_disabled);
} else if (type == PJMEDIA_TYPE_VIDEO) {
proto = info.info.vid.proto;
dir = info.info.vid.dir;
pj_sockaddr_print(&info.info.vid.rem_addr, straddr, sizeof(straddr), 3);
remoteRtpAddress = straddr;
pj_sockaddr_print(&info.info.vid.rem_rtcp, straddr, sizeof(straddr), 3);
remoteRtcpAddress = straddr;
txPt = info.info.vid.tx_pt;
rxPt = info.info.vid.rx_pt;
codecName = pj2Str(info.info.vid.codec_info.encoding_name);
codecClockRate = info.info.vid.codec_info.clock_rate;
vidCodecParam.fromPj(*info.info.vid.codec_param);
jbInit = info.info.vid.jb_init;
jbMinPre = info.info.vid.jb_min_pre;
jbMaxPre = info.info.vid.jb_max_pre;
jbMax = info.info.vid.jb_max;
jbDiscardAlgo = PJMEDIA_JB_DISCARD_NONE;
#if defined(PJMEDIA_STREAM_ENABLE_KA) && (PJMEDIA_STREAM_ENABLE_KA != 0)
useKa = PJ2BOOL(info.info.vid.use_ka);
#endif
rtcpSdesByeDisabled = PJ2BOOL(info.info.vid.rtcp_sdes_bye_disabled);
}
}
void StreamStat::fromPj(const pjsua_stream_stat &prm)
{
rtcp.fromPj(prm.rtcp);
jbuf.fromPj(prm.jbuf);
}
///////////////////////////////////////////////////////////////////////////////
struct call_param
{
pjsua_msg_data msg_data;
pjsua_msg_data *p_msg_data;
pjsua_call_setting opt;
pjsua_call_setting *p_opt;
pj_str_t reason;
pj_str_t *p_reason;
pjmedia_sdp_session *sdp;
public:
/**
* Default constructors with specified parameters.
*/
call_param(const SipTxOption &tx_option);
call_param(const SipTxOption &tx_option, const CallSetting &setting,
const string &reason_str, pj_pool_t *pool = NULL,
const string &sdp_str = "");
};
call_param::call_param(const SipTxOption &tx_option)
{
if (tx_option.isEmpty()) {
p_msg_data = NULL;
} else {
tx_option.toPj(msg_data);
p_msg_data = &msg_data;
}
p_opt = NULL;
p_reason = NULL;
sdp = NULL;
}
call_param::call_param(const SipTxOption &tx_option, const CallSetting &setting,
const string &reason_str, pj_pool_t *pool,
const string &sdp_str)
{
if (tx_option.isEmpty()) {
p_msg_data = NULL;
} else {
tx_option.toPj(msg_data);
p_msg_data = &msg_data;
}
if (setting.isEmpty()) {
p_opt = NULL;
} else {
opt = setting.toPj();
p_opt = &opt;
}
reason = str2Pj(reason_str);
p_reason = (reason.slen == 0? NULL: &reason);
sdp = NULL;
if (sdp_str != "") {
pj_str_t dup_pj_sdp;
pj_str_t pj_sdp_str = {(char*)sdp_str.c_str(),
(pj_ssize_t)sdp_str.size()};
pj_status_t status;
pj_strdup(pool, &dup_pj_sdp, &pj_sdp_str);
status = pjmedia_sdp_parse(pool, dup_pj_sdp.ptr,
dup_pj_sdp.slen, &sdp);
if (status != PJ_SUCCESS) {
PJ_PERROR(4,(THIS_FILE, status,
"Failed to parse SDP for call param"));
}
}
}
Call::Call(Account& account, int call_id)
: acc(account), id(call_id), userData(NULL), sdp_pool(NULL), child(NULL)
{
if (call_id != PJSUA_INVALID_ID)
pjsua_call_set_user_data(call_id, this);
}
Call::~Call()
{
/* Remove reference to this instance from PJSUA library */
if (id != PJSUA_INVALID_ID)
pjsua_call_set_user_data(id, NULL);
/*
* If this instance is deleted, also hangup the corresponding call in
* PJSUA library.
*/
if (pjsua_get_state() < PJSUA_STATE_CLOSING && isActive()) {
try {
CallOpParam prm;
hangup(prm);
} catch (Error &err) {
// Ignore
PJ_UNUSED_ARG(err);
}
}
}
CallInfo Call::getInfo() const PJSUA2_THROW(Error)
{
pjsua_call_info pj_ci;
CallInfo ci;
PJSUA2_CHECK_EXPR( pjsua_call_get_info(id, &pj_ci) );
ci.fromPj(pj_ci);
return ci;
}
bool Call::isActive() const
{
if (id == PJSUA_INVALID_ID)
return false;
return (pjsua_call_is_active(id) != 0);
}
int Call::getId() const
{
return id;
}
Call *Call::lookup(int call_id)
{
Call *call = (Call*)pjsua_call_get_user_data(call_id);
if (call && call_id != call->id) {
if (call->child && call->child->id == PJSUA_INVALID_ID) {
/* This must be a new call from call transfer */
call = call->child;
pjsua_call_set_user_data(call_id, call);
}
call->id = call_id;
}
return call;
}
bool Call::hasMedia() const
{
return (pjsua_call_has_media(id) != 0);
}
Media *Call::getMedia(unsigned med_idx) const
{
/* Check if the media index is valid and if the media has a valid port ID */
if (med_idx >= medias.size() ||
(medias[med_idx] && medias[med_idx]->getType() == PJMEDIA_TYPE_AUDIO &&
((AudioMedia *)medias[med_idx])->getPortId() == PJSUA_INVALID_ID))
{
return NULL;
}
return medias[med_idx];
}
AudioMedia Call::getAudioMedia(int med_idx) const PJSUA2_THROW(Error)
{
pjsua_call_info pj_ci;
pjsua_call_get_info(id, &pj_ci);
if (med_idx < 0) {
for (unsigned i = 0; i < pj_ci.media_cnt; ++i) {
if (pj_ci.media[i].type == PJMEDIA_TYPE_AUDIO &&
pj_ci.media[i].stream.aud.conf_slot != PJSUA_INVALID_ID)
{
med_idx = i;
break;
}
}
if (med_idx < 0) {
PJSUA2_RAISE_ERROR3(PJ_ENOTFOUND, "getAudioMedia()",
"no active audio media");
}
}
if (med_idx >= (int)pj_ci.media_cnt) {
PJSUA2_RAISE_ERROR3(PJ_EINVAL, "getAudioMedia()",
"invalid media index");
}
if (pj_ci.media[med_idx].type != PJMEDIA_TYPE_AUDIO) {
PJSUA2_RAISE_ERROR3(PJ_EINVAL, "getAudioMedia()",
"media is not audio");
}
if (pj_ci.media[med_idx].stream.aud.conf_slot == PJSUA_INVALID_ID) {
PJSUA2_RAISE_ERROR3(PJ_EINVAL, "getAudioMedia()",
"no audio slot (inactive?)");
}
AudioMediaHelper am;
am.setPortId(pj_ci.media[med_idx].stream.aud.conf_slot);
return am;
}
VideoMedia Call::getEncodingVideoMedia(int med_idx) const PJSUA2_THROW(Error)
{
pjsua_call_info pj_ci;
pjsua_call_get_info(id, &pj_ci);
if (med_idx < 0) {
for (unsigned i = 0; i < pj_ci.media_cnt; ++i) {
if (pj_ci.media[i].type == PJMEDIA_TYPE_VIDEO &&
pj_ci.media[i].stream.vid.enc_slot != PJSUA_INVALID_ID)
{
med_idx = i;
break;
}
}
if (med_idx < 0) {
PJSUA2_RAISE_ERROR3(PJ_ENOTFOUND, "getEncodingVideoMedia()",
"no active encoding video media");
}
}
if (med_idx >= (int)pj_ci.media_cnt) {
PJSUA2_RAISE_ERROR3(PJ_EINVAL, "getEncodingVideoMedia()",
"invalid media index");
}
if (pj_ci.media[med_idx].type != PJMEDIA_TYPE_VIDEO) {
PJSUA2_RAISE_ERROR3(PJ_EINVAL, "getEncodingVideoMedia()",
"media is not video");
}
if (pj_ci.media[med_idx].stream.vid.enc_slot == PJSUA_INVALID_ID) {
PJSUA2_RAISE_ERROR3(PJ_EINVAL, "getEncodingVideoMedia()",
"no encoding slot (recvonly?)");
}
VideoMediaHelper vm;
vm.setPortId(pj_ci.media[med_idx].stream.vid.enc_slot);
return vm;
}
VideoMedia Call::getDecodingVideoMedia(int med_idx) const PJSUA2_THROW(Error)
{
pjsua_call_info pj_ci;
pjsua_call_get_info(id, &pj_ci);
if (med_idx < 0) {
for (unsigned i = 0; i < pj_ci.media_cnt; ++i) {
if (pj_ci.media[i].type == PJMEDIA_TYPE_VIDEO &&
pj_ci.media[i].stream.vid.dec_slot != PJSUA_INVALID_ID)
{
med_idx = i;
break;
}
}
if (med_idx < 0) {
PJSUA2_RAISE_ERROR3(PJ_ENOTFOUND, "getDecodingVideoMedia()",
"no active decoding video media");
}
}
if (med_idx >= (int)pj_ci.media_cnt) {
PJSUA2_RAISE_ERROR3(PJ_EINVAL, "getDecodingVideoMedia()",
"invalid media index");
}
if (pj_ci.media[med_idx].type != PJMEDIA_TYPE_VIDEO) {
PJSUA2_RAISE_ERROR3(PJ_EINVAL, "getDecodingVideoMedia()",
"media is not video");
}
if (pj_ci.media[med_idx].stream.vid.dec_slot == PJSUA_INVALID_ID) {
PJSUA2_RAISE_ERROR3(PJ_EINVAL, "getDecodingVideoMedia()",
"no decoding slot (sendonly?)");
}
VideoMediaHelper vm;
vm.setPortId(pj_ci.media[med_idx].stream.vid.dec_slot);
return vm;
}
pjsip_dialog_cap_status Call::remoteHasCap(int htype,
const string &hname,
const string &token) const
{
pj_str_t pj_hname = str2Pj(hname);
pj_str_t pj_token = str2Pj(token);
return pjsua_call_remote_has_cap(id, htype,
(htype == PJSIP_H_OTHER)? &pj_hname: NULL,
&pj_token);
}
void Call::setUserData(Token user_data)
{
userData = user_data;
}
Token Call::getUserData() const
{
return userData;
}
pj_stun_nat_type Call::getRemNatType() PJSUA2_THROW(Error)
{
pj_stun_nat_type nat;
PJSUA2_CHECK_EXPR( pjsua_call_get_rem_nat_type(id, &nat) );
return nat;
}
void Call::makeCall(const string &dst_uri, const CallOpParam &prm)
PJSUA2_THROW(Error)
{
pj_str_t pj_dst_uri = str2Pj(dst_uri);
call_param param(prm.txOption, prm.opt, prm.reason);
PJSUA2_CHECK_EXPR( pjsua_call_make_call(acc.getId(), &pj_dst_uri,
param.p_opt, this,
param.p_msg_data, &id) );
}
void Call::answer(const CallOpParam &prm) PJSUA2_THROW(Error)
{
call_param param(prm.txOption, prm.opt, prm.reason,
sdp_pool, prm.sdp.wholeSdp);
if (param.sdp) {
PJSUA2_CHECK_EXPR( pjsua_call_answer_with_sdp(id, param.sdp,
param.p_opt,
prm.statusCode,
param.p_reason,
param.p_msg_data) );
} else {
PJSUA2_CHECK_EXPR( pjsua_call_answer2(id, param.p_opt, prm.statusCode,
param.p_reason,
param.p_msg_data) );
}
}
void Call::hangup(const CallOpParam &prm) PJSUA2_THROW(Error)
{
call_param param(prm.txOption, prm.opt, prm.reason);
PJSUA2_CHECK_EXPR( pjsua_call_hangup(id, prm.statusCode, param.p_reason,
param.p_msg_data) );
}
void Call::setHold(const CallOpParam &prm) PJSUA2_THROW(Error)
{
call_param param(prm.txOption, prm.opt, prm.reason);
PJSUA2_CHECK_EXPR( pjsua_call_set_hold2(id, prm.options,
param.p_msg_data) );
}
void Call::reinvite(const CallOpParam &prm) PJSUA2_THROW(Error)
{
call_param param(prm.txOption, prm.opt, prm.reason);
PJSUA2_CHECK_EXPR( pjsua_call_reinvite2(id, param.p_opt,
param.p_msg_data) );
}
void Call::update(const CallOpParam &prm) PJSUA2_THROW(Error)
{
call_param param(prm.txOption, prm.opt, prm.reason);
PJSUA2_CHECK_EXPR( pjsua_call_update2(id, param.p_opt,
param.p_msg_data) );
}
void Call::xfer(const string &dest, const CallOpParam &prm)
PJSUA2_THROW(Error)
{
call_param param(prm.txOption);
pj_str_t pj_dest = str2Pj(dest);
PJSUA2_CHECK_EXPR( pjsua_call_xfer(id, &pj_dest, param.p_msg_data) );
}
void Call::xferReplaces(const Call& dest_call,
const CallOpParam &prm) PJSUA2_THROW(Error)
{
call_param param(prm.txOption);
PJSUA2_CHECK_EXPR(pjsua_call_xfer_replaces(id, dest_call.getId(),
prm.options,
param.p_msg_data) );
}
void Call::processRedirect(pjsip_redirect_op cmd) PJSUA2_THROW(Error)
{
PJSUA2_CHECK_EXPR(pjsua_call_process_redirect(id, cmd));
}
void Call::dialDtmf(const string &digits) PJSUA2_THROW(Error)
{
pj_str_t pj_digits = str2Pj(digits);
PJSUA2_CHECK_EXPR(pjsua_call_dial_dtmf(id, &pj_digits));
}
void Call::sendDtmf(const CallSendDtmfParam &param) PJSUA2_THROW(Error)
{
pjsua_call_send_dtmf_param pj_param = param.toPj();
PJSUA2_CHECK_EXPR(pjsua_call_send_dtmf(id, &pj_param));
}
void Call::sendInstantMessage(const SendInstantMessageParam& prm)
PJSUA2_THROW(Error)
{
pj_str_t mime_type = str2Pj(prm.contentType);
pj_str_t content = str2Pj(prm.content);
call_param param(prm.txOption);
PJSUA2_CHECK_EXPR(pjsua_call_send_im(id, &mime_type, &content,
param.p_msg_data, prm.userData) );
}
void Call::sendTypingIndication(const SendTypingIndicationParam &prm)
PJSUA2_THROW(Error)
{
call_param param(prm.txOption);
PJSUA2_CHECK_EXPR(pjsua_call_send_typing_ind(id,
(prm.isTyping?
PJ_TRUE: PJ_FALSE),
param.p_msg_data) );
}
void Call::sendRequest(const CallSendRequestParam &prm) PJSUA2_THROW(Error)
{
pj_str_t method = str2Pj(prm.method);
call_param param(prm.txOption);
PJSUA2_CHECK_EXPR(pjsua_call_send_request(id, &method,
param.p_msg_data) );
}
string Call::dump(bool with_media, const string indent) PJSUA2_THROW(Error)
{
#if defined(PJMEDIA_HAS_RTCP_XR) && (PJMEDIA_HAS_RTCP_XR != 0)
char buffer[1024 * 10];
#else
char buffer[1024 * 3];
#endif
PJSUA2_CHECK_EXPR(pjsua_call_dump(id, (with_media? PJ_TRUE: PJ_FALSE),
buffer, sizeof(buffer),
indent.c_str()));
return buffer;
}
int Call::vidGetStreamIdx() const
{
#if PJSUA_HAS_VIDEO
return pjsua_call_get_vid_stream_idx(id);
#else
return PJSUA_INVALID_ID;
#endif
}
bool Call::vidStreamIsRunning(int med_idx, pjmedia_dir dir) const
{
#if PJSUA_HAS_VIDEO
return (pjsua_call_vid_stream_is_running(id, med_idx, dir) == PJ_TRUE);
#else
PJ_UNUSED_ARG(med_idx);
PJ_UNUSED_ARG(dir);
return false;
#endif
}
void Call::vidSetStream(pjsua_call_vid_strm_op op,
const CallVidSetStreamParam &param)
PJSUA2_THROW(Error)
{
#if PJSUA_HAS_VIDEO
pjsua_call_vid_strm_op_param prm;
prm.med_idx = param.medIdx;
prm.dir = param.dir;
prm.cap_dev = param.capDev;
PJSUA2_CHECK_EXPR( pjsua_call_set_vid_strm(id, op, &prm) );
#else
PJ_UNUSED_ARG(op);
PJ_UNUSED_ARG(param);
PJSUA2_RAISE_ERROR(PJ_EINVALIDOP);
#endif
}
StreamInfo Call::getStreamInfo(unsigned med_idx) const PJSUA2_THROW(Error)
{
pjsua_stream_info pj_si;
StreamInfo si;
PJSUA2_CHECK_EXPR( pjsua_call_get_stream_info(id, med_idx, &pj_si) );
si.fromPj(pj_si);
return si;
}
StreamStat Call::getStreamStat(unsigned med_idx) const PJSUA2_THROW(Error)
{
pjsua_stream_stat pj_ss;
StreamStat ss;
PJSUA2_CHECK_EXPR( pjsua_call_get_stream_stat(id, med_idx, &pj_ss) );
ss.fromPj(pj_ss);
return ss;
}
MediaTransportInfo Call::getMedTransportInfo(unsigned med_idx) const
PJSUA2_THROW(Error)
{
pjmedia_transport_info pj_mti;
MediaTransportInfo mti;
PJSUA2_CHECK_EXPR( pjsua_call_get_med_transport_info(id, med_idx,
&pj_mti) );
mti.fromPj(pj_mti);
return mti;
}
void Call::processMediaUpdate(OnCallMediaStateParam &prm)
{
pjsua_call_info pj_ci;
unsigned mi;
if (pjsua_call_get_info(id, &pj_ci) == PJ_SUCCESS) {
if (medias.size()) {
/* Clear medias. */
for (mi = 0; mi < medias.size(); mi++) {
if (medias[mi]) {
Endpoint::instance().mediaRemove((AudioMedia&)*medias[mi]);
delete medias[mi];
}
}
medias.clear();
}
for (mi = 0; mi < pj_ci.media_cnt; mi++) {
if (mi >= medias.size()) {
if (pj_ci.media[mi].type == PJMEDIA_TYPE_AUDIO) {
medias.push_back(new AudioMediaHelper);
} else {
medias.push_back(NULL);
}
}
if (pj_ci.media[mi].type == PJMEDIA_TYPE_AUDIO) {
AudioMediaHelper *aud_med = (AudioMediaHelper*)medias[mi];
aud_med->setPortId(pj_ci.media[mi].stream.aud.conf_slot);
/* Add media if the conference slot ID is valid. */
if (pj_ci.media[mi].stream.aud.conf_slot != PJSUA_INVALID_ID)
{
Endpoint::instance().mediaAdd((AudioMedia &)*aud_med);
} else {
Endpoint::instance().mediaRemove((AudioMedia &)*aud_med);
}
}
}
}
/* Call media state callback. */
onCallMediaState(prm);
}
void Call::processStateChange(OnCallStateParam &prm)
{
pjsua_call_info pj_ci;
unsigned mi;
if (pjsua_call_get_info(id, &pj_ci) == PJ_SUCCESS &&
pj_ci.state == PJSIP_INV_STATE_DISCONNECTED)
{
pjsua_call *call = &pjsua_var.calls[id];
/* We are going to remove the Call object association below,
* so we need to call onStreamDestroyed() callback here.
*/
for (mi = 0; mi < call->med_cnt; ++mi) {
pjsua_call_media *call_med = &call->media[mi];
if (call_med->type == PJMEDIA_TYPE_AUDIO &&
call_med->strm.a.stream)
{
OnStreamDestroyedParam strm_prm;
strm_prm.stream = call_med->strm.a.stream;
strm_prm.streamIdx = mi;
onStreamDestroyed(strm_prm);
}
}
/* Clear medias. */
for (mi = 0; mi < medias.size(); mi++) {
if (medias[mi]) {
Endpoint::instance().mediaRemove((AudioMedia &)*medias[mi]);
delete medias[mi];
}
}
medias.clear();
/* Remove this Call object association */
pjsua_call_set_user_data(id, NULL);
}
onCallState(prm);
/* If the state is DISCONNECTED, this call may have already been deleted
* by the application in the callback, so do not access it anymore here.
*/
}
File diff suppressed because it is too large. Load diff
+543
View File
@@ -0,0 +1,543 @@
/* $Id$ */
/*
* Copyright (C) 2013 Teluu Inc. (http://www.teluu.com)
*
* 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 <pjsua2/json.hpp>
#include <pjlib-util/errno.h>
#include <pj/file_io.h>
#include "util.hpp"
#define THIS_FILE "json.cpp"
using namespace pj;
using namespace std;
/* Json node operations */
static bool jsonNode_hasUnread(const ContainerNode*);
static string jsonNode_unreadName(const ContainerNode*n)
PJSUA2_THROW(Error);
static float jsonNode_readNumber(const ContainerNode*,
const string&)
PJSUA2_THROW(Error);
static bool jsonNode_readBool(const ContainerNode*,
const string&)
PJSUA2_THROW(Error);
static string jsonNode_readString(const ContainerNode*,
const string&)
PJSUA2_THROW(Error);
static StringVector jsonNode_readStringVector(const ContainerNode*,
const string&)
PJSUA2_THROW(Error);
static ContainerNode jsonNode_readContainer(const ContainerNode*,
const string &)
PJSUA2_THROW(Error);
static ContainerNode jsonNode_readArray(const ContainerNode*,
const string &)
PJSUA2_THROW(Error);
static void jsonNode_writeNumber(ContainerNode*,
const string &name,
float num)
PJSUA2_THROW(Error);
static void jsonNode_writeBool(ContainerNode*,
const string &name,
bool value)
PJSUA2_THROW(Error);
static void jsonNode_writeString(ContainerNode*,
const string &name,
const string &value)
PJSUA2_THROW(Error);
static void jsonNode_writeStringVector(ContainerNode*,
const string &name,
const StringVector &value)
PJSUA2_THROW(Error);
static ContainerNode jsonNode_writeNewContainer(ContainerNode*,
const string &name)
PJSUA2_THROW(Error);
static ContainerNode jsonNode_writeNewArray(ContainerNode*,
const string &name)
PJSUA2_THROW(Error);
static container_node_op json_op =
{
&jsonNode_hasUnread,
&jsonNode_unreadName,
&jsonNode_readNumber,
&jsonNode_readBool,
&jsonNode_readString,
&jsonNode_readStringVector,
&jsonNode_readContainer,
&jsonNode_readArray,
&jsonNode_writeNumber,
&jsonNode_writeBool,
&jsonNode_writeString,
&jsonNode_writeStringVector,
&jsonNode_writeNewContainer,
&jsonNode_writeNewArray
};
///////////////////////////////////////////////////////////////////////////////
JsonDocument::JsonDocument()
: root(NULL)
{
pj_caching_pool_init(&cp, NULL, 0);
pool = pj_pool_create(&cp.factory, "jsondoc", 512, 512, NULL);
if (!pool)
PJSUA2_RAISE_ERROR(PJ_ENOMEM);
}
JsonDocument::~JsonDocument()
{
if (pool)
pj_pool_release(pool);
pj_caching_pool_destroy(&cp);
}
void JsonDocument::initRoot() const
{
rootNode.op = &json_op;
rootNode.data.doc = (void*)this;
rootNode.data.data1 = (void*)root;
rootNode.data.data2 = root->value.children.next;
}
void JsonDocument::loadFile(const string &filename) PJSUA2_THROW(Error)
{
if (root)
PJSUA2_RAISE_ERROR3(PJ_EINVALIDOP, "JsonDocument.loadString()",
"Document already initialized");
if (!pj_file_exists(filename.c_str()))
PJSUA2_RAISE_ERROR(PJ_ENOTFOUND);
pj_ssize_t size = (pj_ssize_t)pj_file_size(filename.c_str());
pj_status_t status;
char *buffer = (char*)pj_pool_alloc(pool, size+1);
pj_oshandle_t fd = 0;
unsigned parse_size;
char err_msg[120];
pj_json_err_info err_info;
err_msg[0] = '\0';
status = pj_file_open(pool, filename.c_str(), PJ_O_RDONLY, &fd);
if (status != PJ_SUCCESS)
goto on_error;
status = pj_file_read(fd, buffer, &size);
if (status != PJ_SUCCESS)
goto on_error;
pj_file_close(fd);
fd = NULL;
if (size <= 0) {
status = PJ_EEOF;
goto on_error;
}
parse_size = (unsigned)size;
root = pj_json_parse(pool, buffer, &parse_size, &err_info);
if (root == NULL) {
pj_ansi_snprintf(err_msg, sizeof(err_msg),
"JSON parsing failed: syntax error in file '%s' at "
"line %d column %d",
filename.c_str(), err_info.line, err_info.col);
PJ_LOG(1,(THIS_FILE, err_msg));
status = PJLIB_UTIL_EINJSON;
goto on_error;
}
initRoot();
return;
on_error:
if (fd)
pj_file_close(fd);
if (err_msg[0])
PJSUA2_RAISE_ERROR3(status, "loadFile()", err_msg);
else
PJSUA2_RAISE_ERROR(status);
}
void JsonDocument::loadString(const string &input) PJSUA2_THROW(Error)
{
if (root)
PJSUA2_RAISE_ERROR3(PJ_EINVALIDOP, "JsonDocument.loadString()",
"Document already initialized");
unsigned size = (unsigned)input.size();
char *buffer = (char*)pj_pool_alloc(pool, size+1);
unsigned parse_size = (unsigned)size;
pj_json_err_info err_info;
pj_memcpy(buffer, input.c_str(), size);
root = pj_json_parse(pool, buffer, &parse_size, &err_info);
if (root == NULL) {
char err_msg[80];
pj_ansi_snprintf(err_msg, sizeof(err_msg),
"JSON parsing failed at line %d column %d",
err_info.line, err_info.col);
PJ_LOG(1,(THIS_FILE, err_msg));
PJSUA2_RAISE_ERROR3(PJLIB_UTIL_EINJSON, "loadString()", err_msg);
}
initRoot();
}
struct save_file_data
{
pj_oshandle_t fd;
};
static pj_status_t json_file_writer(const char *s,
unsigned size,
void *user_data)
{
save_file_data *sd = (save_file_data*)user_data;
pj_ssize_t ssize = (pj_ssize_t)size;
return pj_file_write(sd->fd, s, &ssize);
}
void JsonDocument::saveFile(const string &filename) PJSUA2_THROW(Error)
{
struct save_file_data sd;
pj_status_t status;
/* Make sure root container has been created */
getRootContainer();
status = pj_file_open(pool, filename.c_str(), PJ_O_WRONLY, &sd.fd);
if (status != PJ_SUCCESS)
PJSUA2_RAISE_ERROR(status);
status = pj_json_writef(root, &json_file_writer, &sd.fd);
pj_file_close(sd.fd);
if (status != PJ_SUCCESS)
PJSUA2_RAISE_ERROR(status);
}
struct save_string_data
{
string output;
};
static pj_status_t json_string_writer(const char *s,
unsigned size,
void *user_data)
{
save_string_data *sd = (save_string_data*)user_data;
sd->output.append(s, size);
return PJ_SUCCESS;
}
string JsonDocument::saveString() PJSUA2_THROW(Error)
{
struct save_string_data sd;
pj_status_t status;
/* Make sure root container has been created */
getRootContainer();
status = pj_json_writef(root, &json_string_writer, &sd);
if (status != PJ_SUCCESS)
PJSUA2_RAISE_ERROR(status);
return sd.output;
}
ContainerNode & JsonDocument::getRootContainer() const
{
if (!root) {
root = allocElement();
pj_json_elem_obj(root, NULL);
initRoot();
}
return rootNode;
}
pj_json_elem* JsonDocument::allocElement() const
{
return (pj_json_elem*)pj_pool_alloc(pool, sizeof(pj_json_elem));
}
pj_pool_t *JsonDocument::getPool()
{
return pool;
}
///////////////////////////////////////////////////////////////////////////////
struct json_node_data
{
JsonDocument *doc;
pj_json_elem *jnode;
pj_json_elem *childPtr;
};
static bool jsonNode_hasUnread(const ContainerNode *node)
{
json_node_data *jdat = (json_node_data*)&node->data;
return jdat->childPtr != (pj_json_elem*)&jdat->jnode->value.children;
}
static void json_verify(struct json_node_data *jdat,
const char *op,
const string &name,
pj_json_val_type type)
{
if (jdat->childPtr == (pj_json_elem*)&jdat->jnode->value.children)
PJSUA2_RAISE_ERROR3(PJ_EEOF, op, "No unread element");
/* If name is specified, then check if the names match, except
* when the node name itself is empty and the parent node is
* an array, then ignore the checking (JSON doesn't allow array
* elements to have name).
*/
if (jdat->jnode->type != PJ_JSON_VAL_ARRAY &&
name.size() && jdat->childPtr->name.slen &&
name.compare(0, name.size(), jdat->childPtr->name.ptr,
jdat->childPtr->name.slen))
{
char err_msg[80];
pj_ansi_snprintf(err_msg, sizeof(err_msg),
"Name mismatch: expecting '%s' got '%.*s'",
name.c_str(), (int)jdat->childPtr->name.slen,
jdat->childPtr->name.ptr);
PJSUA2_RAISE_ERROR3(PJLIB_UTIL_EINJSON, op, err_msg);
}
if (type != PJ_JSON_VAL_NULL && jdat->childPtr->type != type) {
char err_msg[80];
pj_ansi_snprintf(err_msg, sizeof(err_msg),
"Type mismatch: expecting %d got %d",
type, jdat->childPtr->type);
PJSUA2_RAISE_ERROR3(PJLIB_UTIL_EINJSON, op, err_msg);
}
}
static string jsonNode_unreadName(const ContainerNode *node)
PJSUA2_THROW(Error)
{
json_node_data *jdat = (json_node_data*)&node->data;
json_verify(jdat, "unreadName()", "", PJ_JSON_VAL_NULL);
return pj2Str(jdat->childPtr->name);
}
static float jsonNode_readNumber(const ContainerNode *node,
const string &name)
PJSUA2_THROW(Error)
{
json_node_data *jdat = (json_node_data*)&node->data;
json_verify(jdat, "readNumber()", name, PJ_JSON_VAL_NUMBER);
jdat->childPtr = jdat->childPtr->next;
return jdat->childPtr->prev->value.num;
}
static bool jsonNode_readBool(const ContainerNode *node,
const string &name)
PJSUA2_THROW(Error)
{
json_node_data *jdat = (json_node_data*)&node->data;
json_verify(jdat, "readBool()", name, PJ_JSON_VAL_BOOL);
jdat->childPtr = jdat->childPtr->next;
return PJ2BOOL(jdat->childPtr->prev->value.is_true);
}
static string jsonNode_readString(const ContainerNode *node,
const string &name)
PJSUA2_THROW(Error)
{
json_node_data *jdat = (json_node_data*)&node->data;
json_verify(jdat, "readString()", name, PJ_JSON_VAL_STRING);
jdat->childPtr = jdat->childPtr->next;
return pj2Str(jdat->childPtr->prev->value.str);
}
static StringVector jsonNode_readStringVector(const ContainerNode *node,
const string &name)
PJSUA2_THROW(Error)
{
json_node_data *jdat = (json_node_data*)&node->data;
json_verify(jdat, "readStringVector()", name, PJ_JSON_VAL_ARRAY);
StringVector result;
pj_json_elem *child = jdat->childPtr->value.children.next;
while (child != (pj_json_elem*)&jdat->childPtr->value.children) {
if (child->type != PJ_JSON_VAL_STRING) {
char err_msg[80];
pj_ansi_snprintf(err_msg, sizeof(err_msg),
"Elements not string but type %d",
child->type);
PJSUA2_RAISE_ERROR3(PJLIB_UTIL_EINJSON, "readStringVector()",
err_msg);
}
result.push_back(pj2Str(child->value.str));
child = child->next;
}
jdat->childPtr = jdat->childPtr->next;
return result;
}
static ContainerNode jsonNode_readContainer(const ContainerNode *node,
const string &name)
PJSUA2_THROW(Error)
{
json_node_data *jdat = (json_node_data*)&node->data;
json_verify(jdat, "readContainer()", name, PJ_JSON_VAL_OBJ);
ContainerNode json_node;
json_node.op = &json_op;
json_node.data.doc = (void*)jdat->doc;
json_node.data.data1 = (void*)jdat->childPtr;
json_node.data.data2 = (void*)jdat->childPtr->value.children.next;
jdat->childPtr = jdat->childPtr->next;
return json_node;
}
static ContainerNode jsonNode_readArray(const ContainerNode *node,
const string &name)
PJSUA2_THROW(Error)
{
json_node_data *jdat = (json_node_data*)&node->data;
json_verify(jdat, "readArray()", name, PJ_JSON_VAL_ARRAY);
ContainerNode json_node;
json_node.op = &json_op;
json_node.data.doc = (void*)jdat->doc;
json_node.data.data1 = (void*)jdat->childPtr;
json_node.data.data2 = (void*)jdat->childPtr->value.children.next;
jdat->childPtr = jdat->childPtr->next;
return json_node;
}
static pj_str_t alloc_name(JsonDocument *doc, const string &name)
{
pj_str_t new_name;
pj_strdup2(doc->getPool(), &new_name, name.c_str());
return new_name;
}
static void jsonNode_writeNumber(ContainerNode *node,
const string &name,
float num)
PJSUA2_THROW(Error)
{
json_node_data *jdat = (json_node_data*)&node->data;
pj_json_elem *el = jdat->doc->allocElement();
pj_str_t nm = alloc_name(jdat->doc, name);
pj_json_elem_number(el, &nm, num);
pj_json_elem_add(jdat->jnode, el);
}
static void jsonNode_writeBool(ContainerNode *node,
const string &name,
bool value)
PJSUA2_THROW(Error)
{
json_node_data *jdat = (json_node_data*)&node->data;
pj_json_elem *el = jdat->doc->allocElement();
pj_str_t nm = alloc_name(jdat->doc, name);
pj_json_elem_bool(el, &nm, value);
pj_json_elem_add(jdat->jnode, el);
}
static void jsonNode_writeString(ContainerNode *node,
const string &name,
const string &value)
PJSUA2_THROW(Error)
{
json_node_data *jdat = (json_node_data*)&node->data;
pj_json_elem *el = jdat->doc->allocElement();
pj_str_t nm = alloc_name(jdat->doc, name);
pj_str_t new_val;
pj_strdup2(jdat->doc->getPool(), &new_val, value.c_str());
pj_json_elem_string(el, &nm, &new_val);
pj_json_elem_add(jdat->jnode, el);
}
static void jsonNode_writeStringVector(ContainerNode *node,
const string &name,
const StringVector &value)
PJSUA2_THROW(Error)
{
json_node_data *jdat = (json_node_data*)&node->data;
pj_json_elem *el = jdat->doc->allocElement();
pj_str_t nm = alloc_name(jdat->doc, name);
pj_json_elem_array(el, &nm);
for (unsigned i=0; i<value.size(); ++i) {
pj_str_t new_val;
pj_strdup2(jdat->doc->getPool(), &new_val, value[i].c_str());
pj_json_elem *child = jdat->doc->allocElement();
pj_json_elem_string(child, NULL, &new_val);
pj_json_elem_add(el, child);
}
pj_json_elem_add(jdat->jnode, el);
}
static ContainerNode jsonNode_writeNewContainer(ContainerNode *node,
const string &name)
PJSUA2_THROW(Error)
{
json_node_data *jdat = (json_node_data*)&node->data;
pj_json_elem *el = jdat->doc->allocElement();
pj_str_t nm = alloc_name(jdat->doc, name);
pj_json_elem_obj(el, &nm);
pj_json_elem_add(jdat->jnode, el);
ContainerNode json_node;
json_node.op = &json_op;
json_node.data.doc = (void*)jdat->doc;
json_node.data.data1 = (void*)el;
json_node.data.data2 = (void*)el->value.children.next;
return json_node;
}
static ContainerNode jsonNode_writeNewArray(ContainerNode *node,
const string &name)
PJSUA2_THROW(Error)
{
json_node_data *jdat = (json_node_data*)&node->data;
pj_json_elem *el = jdat->doc->allocElement();
pj_str_t nm = alloc_name(jdat->doc, name);
pj_json_elem_array(el, &nm);
pj_json_elem_add(jdat->jnode, el);
ContainerNode json_node;
json_node.op = &json_op;
json_node.data.doc = (void*)jdat->doc;
json_node.data.data1 = (void*)el;
json_node.data.data2 = (void*)el->value.children.next;
return json_node;
}
File diff suppressed because it is too large. Load diff
@@ -0,0 +1,234 @@
/* $Id$ */
/*
* Copyright (C) 2013 Teluu Inc. (http://www.teluu.com)
*
* 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 <pjsua2/persistent.hpp>
using namespace pj;
using namespace std;
bool PersistentDocument::hasUnread() const
{
return getRootContainer().hasUnread();
}
string PersistentDocument::unreadName() const PJSUA2_THROW(Error)
{
return getRootContainer().unreadName();
}
int PersistentDocument::readInt(const string &name) const PJSUA2_THROW(Error)
{
return (int)getRootContainer().readNumber(name);
}
float PersistentDocument::readNumber(const string &name) const
PJSUA2_THROW(Error)
{
return getRootContainer().readNumber(name);
}
bool PersistentDocument::readBool(const string &name) const
PJSUA2_THROW(Error)
{
return getRootContainer().readBool(name);
}
string PersistentDocument::readString(const string &name) const
PJSUA2_THROW(Error)
{
return getRootContainer().readString(name);
}
StringVector PersistentDocument::readStringVector(const string &name) const
PJSUA2_THROW(Error)
{
return getRootContainer().readStringVector(name);
}
void PersistentDocument::readObject(PersistentObject &obj) const
PJSUA2_THROW(Error)
{
getRootContainer().readObject(obj);
}
ContainerNode PersistentDocument::readContainer(const string &name) const
PJSUA2_THROW(Error)
{
return getRootContainer().readContainer(name);
}
ContainerNode PersistentDocument::readArray(const string &name) const
PJSUA2_THROW(Error)
{
return getRootContainer().readArray(name);
}
void PersistentDocument::writeNumber(const string &name,
float num) PJSUA2_THROW(Error)
{
getRootContainer().writeNumber(name, num);
}
void PersistentDocument::writeInt(const string &name,
int num) PJSUA2_THROW(Error)
{
getRootContainer().writeNumber(name, (float)num);
}
void PersistentDocument::writeBool(const string &name,
bool value) PJSUA2_THROW(Error)
{
getRootContainer().writeBool(name, value);
}
void PersistentDocument::writeString(const string &name,
const string &value) PJSUA2_THROW(Error)
{
getRootContainer().writeString(name, value);
}
void PersistentDocument::writeStringVector(const string &name,
const StringVector &value)
PJSUA2_THROW(Error)
{
getRootContainer().writeStringVector(name, value);
}
void PersistentDocument::writeObject(const PersistentObject &obj)
PJSUA2_THROW(Error)
{
getRootContainer().writeObject(obj);
}
ContainerNode PersistentDocument::writeNewContainer(const string &name)
PJSUA2_THROW(Error)
{
return getRootContainer().writeNewContainer(name);
}
ContainerNode PersistentDocument::writeNewArray(const string &name)
PJSUA2_THROW(Error)
{
return getRootContainer().writeNewArray(name);
}
///////////////////////////////////////////////////////////////////////////////
bool ContainerNode::hasUnread() const
{
return op->hasUnread(this);
}
string ContainerNode::unreadName() const PJSUA2_THROW(Error)
{
return op->unreadName(this);
}
int ContainerNode::readInt(const string &name) const PJSUA2_THROW(Error)
{
return (int)op->readNumber(this, name);
}
float ContainerNode::readNumber(const string &name) const PJSUA2_THROW(Error)
{
return op->readNumber(this, name);
}
bool ContainerNode::readBool(const string &name) const PJSUA2_THROW(Error)
{
return op->readBool(this, name);
}
string ContainerNode::readString(const string &name) const PJSUA2_THROW(Error)
{
return op->readString(this, name);
}
StringVector ContainerNode::readStringVector(const string &name) const
PJSUA2_THROW(Error)
{
return op->readStringVector(this, name);
}
void ContainerNode::readObject(PersistentObject &obj) const
PJSUA2_THROW(Error)
{
obj.readObject(*this);
}
ContainerNode ContainerNode::readContainer(const string &name) const
PJSUA2_THROW(Error)
{
return op->readContainer(this, name);
}
ContainerNode ContainerNode::readArray(const string &name) const
PJSUA2_THROW(Error)
{
return op->readArray(this, name);
}
void ContainerNode::writeNumber(const string &name,
float num) PJSUA2_THROW(Error)
{
return op->writeNumber(this, name, num);
}
void ContainerNode::writeInt(const string &name,
int num) PJSUA2_THROW(Error)
{
return op->writeNumber(this, name, (float)num);
}
void ContainerNode::writeBool(const string &name,
bool value) PJSUA2_THROW(Error)
{
return op->writeBool(this, name, value);
}
void ContainerNode::writeString(const string &name,
const string &value) PJSUA2_THROW(Error)
{
return op->writeString(this, name, value);
}
void ContainerNode::writeStringVector(const string &name,
const StringVector &value)
PJSUA2_THROW(Error)
{
return op->writeStringVector(this, name, value);
}
void ContainerNode::writeObject(const PersistentObject &obj)
PJSUA2_THROW(Error)
{
obj.writeObject(*this);
}
ContainerNode ContainerNode::writeNewContainer(const string &name)
PJSUA2_THROW(Error)
{
return op->writeNewContainer(this, name);
}
ContainerNode ContainerNode::writeNewArray(const string &name)
PJSUA2_THROW(Error)
{
return op->writeNewArray(this, name);
}
@@ -0,0 +1,233 @@
/* $Id$ */
/*
* Copyright (C) 2013 Teluu Inc. (http://www.teluu.com)
*
* 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 <pjsua2/presence.hpp>
#include <pjsua2/account.hpp>
#include "util.hpp"
using namespace pj;
using namespace std;
#define THIS_FILE "presence.cpp"
///////////////////////////////////////////////////////////////////////////////
PresenceStatus::PresenceStatus()
: status(PJSUA_BUDDY_STATUS_UNKNOWN), activity(PJRPID_ACTIVITY_UNKNOWN)
{
}
///////////////////////////////////////////////////////////////////////////////
void BuddyConfig::readObject(const ContainerNode &node) PJSUA2_THROW(Error)
{
ContainerNode this_node = node.readContainer("BuddyConfig");
NODE_READ_STRING ( this_node, uri);
NODE_READ_BOOL ( this_node, subscribe);
}
void BuddyConfig::writeObject(ContainerNode &node) const PJSUA2_THROW(Error)
{
ContainerNode this_node = node.writeNewContainer("BuddyConfig");
NODE_WRITE_STRING ( this_node, uri);
NODE_WRITE_BOOL ( this_node, subscribe);
}
//////////////////////////////////////////////////////////////////////////////
void BuddyInfo::fromPj(const pjsua_buddy_info &pbi)
{
uri = pj2Str(pbi.uri);
contact = pj2Str(pbi.contact);
presMonitorEnabled = PJ2BOOL(pbi.monitor_pres);
subState = pbi.sub_state;
subStateName = string(pbi.sub_state_name);
subTermCode = (pjsip_status_code)pbi.sub_term_code;
subTermReason = pj2Str(pbi.sub_term_reason);
/* Presence status */
presStatus.status = pbi.status;
presStatus.statusText = pj2Str(pbi.status_text);
presStatus.activity = pbi.rpid.activity;
presStatus.note = pj2Str(pbi.rpid.note);
presStatus.rpidId = pj2Str(pbi.rpid.id);
}
//////////////////////////////////////////////////////////////////////////////
typedef struct BuddyUserData
{
Buddy *self;
Account *acc;
} BuddyUserData;
/*
* Constructor.
*/
Buddy::Buddy()
: id(PJSUA_INVALID_ID)
{
}
Buddy::Buddy(pjsua_buddy_id buddy_id)
: id(buddy_id)
{
}
Buddy *Buddy::getOriginalInstance()
{
BuddyUserData *bud = (BuddyUserData*)pjsua_buddy_get_user_data(id);
return (bud? bud->self : NULL);
}
/*
* Destructor.
*/
Buddy::~Buddy()
{
if (isValid() && getOriginalInstance()==this) {
Account *acc = NULL;
BuddyUserData *bud = (BuddyUserData*)pjsua_buddy_get_user_data(id);
if (bud) {
acc = bud->acc;
delete bud;
}
pjsua_buddy_set_user_data(id, NULL);
pjsua_buddy_del(id);
/* Remove from account buddy list */
if (acc)
acc->removeBuddy(this);
}
}
/*
* Create buddy and register the buddy to PJSUA-LIB.
*/
void Buddy::create(Account &account, const BuddyConfig &cfg)
PJSUA2_THROW(Error)
{
pjsua_buddy_config pj_cfg;
pjsua_buddy_config_default(&pj_cfg);
if (!account.isValid())
PJSUA2_RAISE_ERROR3(PJ_EINVAL, "Buddy::create()", "Invalid account");
BuddyUserData *bud = new BuddyUserData();
bud->self = this;
bud->acc = &account;
pj_cfg.uri = str2Pj(cfg.uri);
pj_cfg.subscribe = cfg.subscribe;
pj_cfg.user_data = (void*)bud;
PJSUA2_CHECK_EXPR( pjsua_buddy_add(&pj_cfg, &id) );
account.addBuddy(this);
}
/*
* Check if this buddy is valid.
*/
bool Buddy::isValid() const
{
return PJ2BOOL( pjsua_buddy_is_valid(id) );
}
/*
* Get detailed buddy info.
*/
BuddyInfo Buddy::getInfo() const PJSUA2_THROW(Error)
{
pjsua_buddy_info pj_bi;
BuddyInfo bi;
PJSUA2_CHECK_EXPR( pjsua_buddy_get_info(id, &pj_bi) );
bi.fromPj(pj_bi);
return bi;
}
/*
* Enable/disable buddy's presence monitoring.
*/
void Buddy::subscribePresence(bool subscribe) PJSUA2_THROW(Error)
{
PJSUA2_CHECK_EXPR( pjsua_buddy_subscribe_pres(id, subscribe) );
}
/*
* Update the presence information for the buddy.
*/
void Buddy::updatePresence(void) PJSUA2_THROW(Error)
{
PJSUA2_CHECK_EXPR( pjsua_buddy_update_pres(id) );
}
/*
* Send instant messaging outside dialog.
*/
void Buddy::sendInstantMessage(const SendInstantMessageParam &prm)
PJSUA2_THROW(Error)
{
BuddyInfo bi = getInfo();
BuddyUserData *bud = (BuddyUserData*)pjsua_buddy_get_user_data(id);
Account *acc = bud? bud->acc : NULL;
if (!bud || !acc || !acc->isValid()) {
PJSUA2_RAISE_ERROR3(PJ_EINVAL, "sendInstantMessage()",
"Invalid Buddy");
}
pj_str_t to = str2Pj(bi.contact.empty()? bi.uri : bi.contact);
pj_str_t mime_type = str2Pj(prm.contentType);
pj_str_t content = str2Pj(prm.content);
void *user_data = (void*)prm.userData;
pjsua_msg_data msg_data;
prm.txOption.toPj(msg_data);
PJSUA2_CHECK_EXPR( pjsua_im_send(acc->getId(), &to, &mime_type, &content,
&msg_data, user_data) );
}
/*
* Send typing indication outside dialog.
*/
void Buddy::sendTypingIndication(const SendTypingIndicationParam &prm)
PJSUA2_THROW(Error)
{
BuddyInfo bi = getInfo();
BuddyUserData *bud = (BuddyUserData*)pjsua_buddy_get_user_data(id);
Account *acc = bud? bud->acc : NULL;
if (!bud || !acc || !acc->isValid()) {
PJSUA2_RAISE_ERROR3(PJ_EINVAL, "sendInstantMessage()",
"Invalid Buddy");
}
pj_str_t to = str2Pj(bi.contact.empty()? bi.uri : bi.contact);
pjsua_msg_data msg_data;
prm.txOption.toPj(msg_data);
PJSUA2_CHECK_EXPR( pjsua_im_typing(acc->getId(), &to, prm.isTyping,
&msg_data) );
}
@@ -0,0 +1,643 @@
/* $Id$ */
/*
* Copyright (C) 2013 Teluu Inc. (http://www.teluu.com)
*
* 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 <pjsua2/types.hpp>
#include <pjsua2/siptypes.hpp>
#include "util.hpp"
using namespace pj;
using namespace std;
#define THIS_FILE "siptypes.cpp"
///////////////////////////////////////////////////////////////////////////////
namespace pj
{
void readIntVector( ContainerNode &node,
const string &array_name,
IntVector &v) PJSUA2_THROW(Error)
{
ContainerNode array_node = node.readArray(array_name);
v.resize(0);
while (array_node.hasUnread()) {
v.push_back((int)array_node.readNumber());
}
}
void writeIntVector(ContainerNode &node,
const string &array_name,
const IntVector &v) PJSUA2_THROW(Error)
{
ContainerNode array_node = node.writeNewArray(array_name);
for (unsigned i=0; i<v.size(); ++i) {
array_node.writeNumber("", (float)v[i]);
}
}
void readQosParams( ContainerNode &node,
pj_qos_params &qos) PJSUA2_THROW(Error)
{
ContainerNode this_node = node.readContainer("qosParams");
NODE_READ_NUM_T( this_node, pj_uint8_t, qos.flags);
NODE_READ_NUM_T( this_node, pj_uint8_t, qos.dscp_val);
NODE_READ_NUM_T( this_node, pj_uint8_t, qos.so_prio);
NODE_READ_NUM_T( this_node, pj_qos_wmm_prio, qos.wmm_prio);
}
void writeQosParams( ContainerNode &node,
const pj_qos_params &qos) PJSUA2_THROW(Error)
{
ContainerNode this_node = node.writeNewContainer("qosParams");
NODE_WRITE_NUM_T( this_node, pj_uint8_t, qos.flags);
NODE_WRITE_NUM_T( this_node, pj_uint8_t, qos.dscp_val);
NODE_WRITE_NUM_T( this_node, pj_uint8_t, qos.so_prio);
NODE_WRITE_NUM_T( this_node, pj_qos_wmm_prio, qos.wmm_prio);
}
void readSipHeaders( const ContainerNode &node,
const string &array_name,
SipHeaderVector &headers) PJSUA2_THROW(Error)
{
ContainerNode headers_node = node.readArray(array_name);
headers.resize(0);
while (headers_node.hasUnread()) {
SipHeader hdr;
ContainerNode header_node = headers_node.readContainer("header");
hdr.hName = header_node.readString("hname");
hdr.hValue = header_node.readString("hvalue");
headers.push_back(hdr);
}
}
void writeSipHeaders(ContainerNode &node,
const string &array_name,
const SipHeaderVector &headers) PJSUA2_THROW(Error)
{
ContainerNode headers_node = node.writeNewArray(array_name);
for (unsigned i=0; i<headers.size(); ++i) {
ContainerNode header_node = headers_node.writeNewContainer("header");
header_node.writeString("hname", headers[i].hName);
header_node.writeString("hvalue", headers[i].hValue);
}
}
} // namespace
///////////////////////////////////////////////////////////////////////////////
AuthCredInfo::AuthCredInfo()
: scheme("digest"), realm("*"), dataType(0)
{
}
AuthCredInfo::AuthCredInfo(const string &param_scheme,
const string &param_realm,
const string &param_user_name,
const int param_data_type,
const string param_data)
: scheme(param_scheme), realm(param_realm), username(param_user_name),
dataType(param_data_type), data(param_data)
{
}
void AuthCredInfo::readObject(const ContainerNode &node) PJSUA2_THROW(Error)
{
ContainerNode this_node = node.readContainer("AuthCredInfo");
NODE_READ_STRING( this_node, scheme);
NODE_READ_STRING( this_node, realm);
NODE_READ_STRING( this_node, username);
NODE_READ_INT ( this_node, dataType);
NODE_READ_STRING( this_node, data);
NODE_READ_STRING( this_node, akaK);
NODE_READ_STRING( this_node, akaOp);
NODE_READ_STRING( this_node, akaAmf);
}
void AuthCredInfo::writeObject(ContainerNode &node) const PJSUA2_THROW(Error)
{
ContainerNode this_node = node.writeNewContainer("AuthCredInfo");
NODE_WRITE_STRING( this_node, scheme);
NODE_WRITE_STRING( this_node, realm);
NODE_WRITE_STRING( this_node, username);
NODE_WRITE_INT ( this_node, dataType);
NODE_WRITE_STRING( this_node, data);
NODE_WRITE_STRING( this_node, akaK);
NODE_WRITE_STRING( this_node, akaOp);
NODE_WRITE_STRING( this_node, akaAmf);
}
///////////////////////////////////////////////////////////////////////////////
TlsConfig::TlsConfig() : method(PJSIP_SSL_UNSPECIFIED_METHOD),
qosType(PJ_QOS_TYPE_BEST_EFFORT)
{
pjsip_tls_setting ts;
pjsip_tls_setting_default(&ts);
this->fromPj(ts);
}
pjsip_tls_setting TlsConfig::toPj() const
{
pjsip_tls_setting ts;
pjsip_tls_setting_default(&ts);
ts.ca_list_file = str2Pj(this->CaListFile);
ts.cert_file = str2Pj(this->certFile);
ts.privkey_file = str2Pj(this->privKeyFile);
ts.password = str2Pj(this->password);
ts.ca_buf = str2Pj(this->CaBuf);
ts.cert_buf = str2Pj(this->certBuf);
ts.privkey_buf = str2Pj(this->privKeyBuf);
ts.method = this->method;
ts.ciphers_num = (unsigned)this->ciphers.size();
ts.proto = this->proto;
// The following will only work if sizeof(enum)==sizeof(int)
pj_assert(sizeof(ts.ciphers[0]) == sizeof(int));
ts.ciphers = ts.ciphers_num?
(pj_ssl_cipher*)&this->ciphers[0] : NULL;
ts.verify_server = this->verifyServer;
ts.verify_client = this->verifyClient;
ts.require_client_cert = this->requireClientCert;
ts.timeout.sec = this->msecTimeout / 1000;
ts.timeout.msec = this->msecTimeout % 1000;
ts.qos_type = this->qosType;
ts.qos_params = this->qosParams;
ts.qos_ignore_error = this->qosIgnoreError;
return ts;
}
void TlsConfig::fromPj(const pjsip_tls_setting &prm)
{
this->CaListFile = pj2Str(prm.ca_list_file);
this->certFile = pj2Str(prm.cert_file);
this->privKeyFile = pj2Str(prm.privkey_file);
this->password = pj2Str(prm.password);
this->CaBuf = pj2Str(prm.ca_buf);
this->certBuf = pj2Str(prm.cert_buf);
this->privKeyBuf = pj2Str(prm.privkey_buf);
this->method = (pjsip_ssl_method)prm.method;
this->proto = prm.proto;
// The following will only work if sizeof(enum)==sizeof(int)
pj_assert(sizeof(prm.ciphers[0]) == sizeof(int));
this->ciphers = IntVector(prm.ciphers, prm.ciphers+prm.ciphers_num);
this->verifyServer = PJ2BOOL(prm.verify_server);
this->verifyClient = PJ2BOOL(prm.verify_client);
this->requireClientCert = PJ2BOOL(prm.require_client_cert);
this->msecTimeout = PJ_TIME_VAL_MSEC(prm.timeout);
this->qosType = prm.qos_type;
this->qosParams = prm.qos_params;
this->qosIgnoreError = PJ2BOOL(prm.qos_ignore_error);
}
void TlsConfig::readObject(const ContainerNode &node) PJSUA2_THROW(Error)
{
ContainerNode this_node = node.readContainer("TlsConfig");
NODE_READ_STRING ( this_node, CaListFile);
NODE_READ_STRING ( this_node, certFile);
NODE_READ_STRING ( this_node, privKeyFile);
NODE_READ_STRING ( this_node, password);
NODE_READ_STRING ( this_node, CaBuf);
NODE_READ_STRING ( this_node, certBuf);
NODE_READ_STRING ( this_node, privKeyBuf);
NODE_READ_NUM_T ( this_node, pjsip_ssl_method, method);
readIntVector ( this_node, "ciphers", ciphers);
NODE_READ_BOOL ( this_node, verifyServer);
NODE_READ_BOOL ( this_node, verifyClient);
NODE_READ_BOOL ( this_node, requireClientCert);
NODE_READ_UNSIGNED( this_node, msecTimeout);
NODE_READ_NUM_T ( this_node, pj_qos_type, qosType);
readQosParams ( this_node, qosParams);
NODE_READ_BOOL ( this_node, qosIgnoreError);
}
void TlsConfig::writeObject(ContainerNode &node) const PJSUA2_THROW(Error)
{
ContainerNode this_node = node.writeNewContainer("TlsConfig");
NODE_WRITE_STRING ( this_node, CaListFile);
NODE_WRITE_STRING ( this_node, certFile);
NODE_WRITE_STRING ( this_node, privKeyFile);
NODE_WRITE_STRING ( this_node, password);
NODE_WRITE_STRING ( this_node, CaBuf);
NODE_WRITE_STRING ( this_node, certBuf);
NODE_WRITE_STRING ( this_node, privKeyBuf);
NODE_WRITE_NUM_T ( this_node, pjsip_ssl_method, method);
writeIntVector ( this_node, "ciphers", ciphers);
NODE_WRITE_BOOL ( this_node, verifyServer);
NODE_WRITE_BOOL ( this_node, verifyClient);
NODE_WRITE_BOOL ( this_node, requireClientCert);
NODE_WRITE_UNSIGNED( this_node, msecTimeout);
NODE_WRITE_NUM_T ( this_node, pj_qos_type, qosType);
writeQosParams ( this_node, qosParams);
NODE_WRITE_BOOL ( this_node, qosIgnoreError);
}
///////////////////////////////////////////////////////////////////////////////
TransportConfig::TransportConfig() : qosType(PJ_QOS_TYPE_BEST_EFFORT)
{
pjsua_transport_config tc;
pjsua_transport_config_default(&tc);
this->fromPj(tc);
}
void TransportConfig::fromPj(const pjsua_transport_config &prm)
{
this->port = prm.port;
this->portRange = prm.port_range;
this->publicAddress = pj2Str(prm.public_addr);
this->boundAddress = pj2Str(prm.bound_addr);
this->tlsConfig.fromPj(prm.tls_setting);
this->qosType = prm.qos_type;
this->qosParams = prm.qos_params;
}
pjsua_transport_config TransportConfig::toPj() const
{
pjsua_transport_config tc;
pjsua_transport_config_default(&tc);
tc.port = this->port;
tc.port_range = this->portRange;
tc.public_addr = str2Pj(this->publicAddress);
tc.bound_addr = str2Pj(this->boundAddress);
tc.tls_setting = this->tlsConfig.toPj();
tc.qos_type = this->qosType;
tc.qos_params = this->qosParams;
return tc;
}
void TransportConfig::readObject(const ContainerNode &node) PJSUA2_THROW(Error)
{
ContainerNode this_node = node.readContainer("TransportConfig");
NODE_READ_UNSIGNED ( this_node, port);
NODE_READ_UNSIGNED ( this_node, portRange);
NODE_READ_STRING ( this_node, publicAddress);
NODE_READ_STRING ( this_node, boundAddress);
NODE_READ_NUM_T ( this_node, pj_qos_type, qosType);
readQosParams ( this_node, qosParams);
NODE_READ_OBJ ( this_node, tlsConfig);
}
void TransportConfig::writeObject(ContainerNode &node) const
PJSUA2_THROW(Error)
{
ContainerNode this_node = node.writeNewContainer("TransportConfig");
NODE_WRITE_UNSIGNED ( this_node, port);
NODE_WRITE_UNSIGNED ( this_node, portRange);
NODE_WRITE_STRING ( this_node, publicAddress);
NODE_WRITE_STRING ( this_node, boundAddress);
NODE_WRITE_NUM_T ( this_node, pj_qos_type, qosType);
writeQosParams ( this_node, qosParams);
NODE_WRITE_OBJ ( this_node, tlsConfig);
}
///////////////////////////////////////////////////////////////////////////////
TransportInfo::TransportInfo()
: id(), type(PJSIP_TRANSPORT_UNSPECIFIED), flags(), usageCount()
{
}
void TransportInfo::fromPj(const pjsua_transport_info &tinfo)
{
this->id = tinfo.id;
this->type = tinfo.type;
this->typeName = pj2Str(tinfo.type_name);
this->info = pj2Str(tinfo.info);
this->flags = tinfo.flag;
char straddr[PJ_INET6_ADDRSTRLEN+10];
pj_sockaddr_print(&tinfo.local_addr, straddr, sizeof(straddr), 3);
this->localAddress = straddr;
pj_ansi_snprintf(straddr, sizeof(straddr), "%.*s:%d",
(int)tinfo.local_name.host.slen,
tinfo.local_name.host.ptr,
tinfo.local_name.port);
this->localName = straddr;
this->usageCount = tinfo.usage_count;
}
///////////////////////////////////////////////////////////////////////////////
SipRxData::SipRxData()
: pjRxData(NULL)
{
}
void SipRxData::fromPj(pjsip_rx_data &rdata)
{
char straddr[PJ_INET6_ADDRSTRLEN+10];
info = pjsip_rx_data_get_info(&rdata);
wholeMsg = string(rdata.msg_info.msg_buf, rdata.msg_info.len);
pj_sockaddr_print(&rdata.pkt_info.src_addr, straddr, sizeof(straddr), 3);
srcAddress = straddr;
pjRxData = (void *)&rdata;
}
///////////////////////////////////////////////////////////////////////////////
void SipMediaType::fromPj(const pjsip_media_type &prm)
{
type = pj2Str(prm.type);
subType = pj2Str(prm.subtype);
}
pjsip_media_type SipMediaType::toPj() const
{
pjsip_media_type pj_mt;
pj_bzero(&pj_mt, sizeof(pj_mt));
pj_mt.type = str2Pj(type);
pj_mt.subtype = str2Pj(subType);
return pj_mt;
}
///////////////////////////////////////////////////////////////////////////////
void SipHeader::fromPj(const pjsip_hdr *hdr) PJSUA2_THROW(Error)
{
char *buf = NULL;
int len = 0;
unsigned buf_size = 256>>1;
/* Print header to a 256 bytes buffer first.
* If buffer is not sufficient, try 512, 1024, soon
* until > PJSIP_MAX_PKT_LEN
*/
do {
buf_size <<= 1;
buf = (char*)malloc(buf_size);
if (!buf)
PJSUA2_RAISE_ERROR(PJ_ENOMEM);
len = pjsip_hdr_print_on((void*)hdr, buf, buf_size-1);
if (len < 0)
free(buf);
} while ((buf_size < PJSIP_MAX_PKT_LEN) && (len < 0));
if (len < 0)
PJSUA2_RAISE_ERROR(PJ_ETOOSMALL);
buf[len] = '\0';
char *pos = strchr(buf, ':');
if (!pos) {
free(buf);
PJSUA2_RAISE_ERROR(PJSIP_EINVALIDHDR);
}
// Trim white space after header name
char *end_name = pos;
while (end_name>buf && pj_isspace(*(end_name-1))) --end_name;
// Trim whitespaces after colon
char *start_val = pos+1;
while (*start_val && pj_isspace(*start_val)) ++start_val;
hName = string(buf, end_name);
hValue = string(start_val);
free(buf);
}
pjsip_generic_string_hdr &SipHeader::toPj() const
{
pj_str_t hname = str2Pj(hName);
pj_str_t hvalue = str2Pj(hValue);
pjsip_generic_string_hdr_init2(&pjHdr, &hname, &hvalue);
return pjHdr;
}
///////////////////////////////////////////////////////////////////////////////
void SipMultipartPart::fromPj(const pjsip_multipart_part &prm)
PJSUA2_THROW(Error)
{
headers.clear();
pjsip_hdr* pj_hdr = prm.hdr.next;
while (pj_hdr != &prm.hdr) {
SipHeader sh;
sh.fromPj(pj_hdr);
headers.push_back(sh);
pj_hdr = pj_hdr->next;
}
if (!prm.body)
PJSUA2_RAISE_ERROR(PJ_EINVAL);
contentType.fromPj(prm.body->content_type);
body = string((char*)prm.body->data, prm.body->len);
}
pjsip_multipart_part& SipMultipartPart::toPj() const
{
pj_list_init(&pjMpp.hdr);
for (unsigned i = 0; i < headers.size(); i++) {
pjsip_generic_string_hdr& pj_hdr = headers[i].toPj();
pj_list_push_back(&pjMpp.hdr, &pj_hdr);
}
pj_bzero(&pjMsgBody, sizeof(pjMsgBody));
pjMsgBody.content_type = contentType.toPj();
pjMsgBody.print_body = &pjsip_print_text_body;
pjMsgBody.clone_data = &pjsip_clone_text_data;
pjMsgBody.data = (void*)body.c_str();
pjMsgBody.len = (unsigned)body.size();
pjMpp.body = &pjMsgBody;
return pjMpp;
}
///////////////////////////////////////////////////////////////////////////////
SipEvent::SipEvent()
: type(PJSIP_EVENT_UNKNOWN), pjEvent(NULL)
{
}
void SipEvent::fromPj(const pjsip_event &ev)
{
type = ev.type;
if (type == PJSIP_EVENT_TIMER) {
body.timer.entry = ev.body.timer.entry;
} else if (type == PJSIP_EVENT_TSX_STATE) {
body.tsxState.prevState = (pjsip_tsx_state_e)
ev.body.tsx_state.prev_state;
body.tsxState.tsx.fromPj(*ev.body.tsx_state.tsx);
body.tsxState.type = ev.body.tsx_state.type;
if (body.tsxState.type == PJSIP_EVENT_TX_MSG) {
if (ev.body.tsx_state.src.tdata)
body.tsxState.src.tdata.fromPj(*ev.body.tsx_state.src.tdata);
} else if (body.tsxState.type == PJSIP_EVENT_RX_MSG) {
if (ev.body.tsx_state.src.rdata)
body.tsxState.src.rdata.fromPj(*ev.body.tsx_state.src.rdata);
} else if (body.tsxState.type == PJSIP_EVENT_TRANSPORT_ERROR) {
body.tsxState.src.status = ev.body.tsx_state.src.status;
} else if (body.tsxState.type == PJSIP_EVENT_TIMER) {
body.tsxState.src.timer = ev.body.tsx_state.src.timer;
} else if (body.tsxState.type == PJSIP_EVENT_USER) {
body.tsxState.src.data = ev.body.tsx_state.src.data;
}
} else if (type == PJSIP_EVENT_TX_MSG) {
if (ev.body.tx_msg.tdata)
body.txMsg.tdata.fromPj(*ev.body.tx_msg.tdata);
} else if (type == PJSIP_EVENT_RX_MSG) {
if (ev.body.rx_msg.rdata)
body.rxMsg.rdata.fromPj(*ev.body.rx_msg.rdata);
} else if (type == PJSIP_EVENT_TRANSPORT_ERROR) {
if (ev.body.tx_error.tdata)
body.txError.tdata.fromPj(*ev.body.tx_error.tdata);
if (ev.body.tx_error.tsx)
body.txError.tsx.fromPj(*ev.body.tx_error.tsx);
} else if (type == PJSIP_EVENT_USER) {
body.user.user1 = ev.body.user.user1;
body.user.user2 = ev.body.user.user2;
body.user.user3 = ev.body.user.user3;
body.user.user4 = ev.body.user.user4;
}
pjEvent = (void *)&ev;
}
SipTxData::SipTxData()
: pjTxData(NULL)
{
}
void SipTxData::fromPj(pjsip_tx_data &tdata)
{
char straddr[PJ_INET6_ADDRSTRLEN+10];
info = pjsip_tx_data_get_info(&tdata);
pjsip_tx_data_encode(&tdata);
wholeMsg = string(tdata.buf.start, tdata.buf.cur - tdata.buf.start);
if (pj_sockaddr_has_addr(&tdata.tp_info.dst_addr)) {
pj_sockaddr_print(&tdata.tp_info.dst_addr, straddr, sizeof(straddr), 3);
dstAddress = straddr;
} else {
dstAddress = "";
}
pjTxData = (void *)&tdata;
}
SipTransaction::SipTransaction()
: role(PJSIP_ROLE_UAC), statusCode(PJSIP_SC_NULL),
state(PJSIP_TSX_STATE_NULL), pjTransaction(NULL)
{
}
void SipTransaction::fromPj(pjsip_transaction &tsx)
{
this->role = tsx.role;
this->method = pj2Str(tsx.method.name);
this->statusCode = tsx.status_code;
this->statusText = pj2Str(tsx.status_text);
this->state = tsx.state;
if (tsx.last_tx)
this->lastTx.fromPj(*tsx.last_tx);
else
this->lastTx.pjTxData = NULL;
this->pjTransaction = (void *)&tsx;
}
TsxStateEvent::TsxStateEvent()
: prevState(PJSIP_TSX_STATE_NULL), type(PJSIP_EVENT_UNKNOWN)
{
}
bool SipTxOption::isEmpty() const
{
return (targetUri == "" && headers.size() == 0 && contentType == "" &&
msgBody == "" && multipartContentType.type == "" &&
multipartContentType.subType == "" && multipartParts.size() == 0);
}
void SipTxOption::fromPj(const pjsua_msg_data &prm) PJSUA2_THROW(Error)
{
targetUri = pj2Str(prm.target_uri);
headers.clear();
pjsip_hdr* pj_hdr = prm.hdr_list.next;
while (pj_hdr != &prm.hdr_list) {
SipHeader sh;
sh.fromPj(pj_hdr);
headers.push_back(sh);
pj_hdr = pj_hdr->next;
}
contentType = pj2Str(prm.content_type);
msgBody = pj2Str(prm.msg_body);
multipartContentType.fromPj(prm.multipart_ctype);
multipartParts.clear();
pjsip_multipart_part* pj_mp = prm.multipart_parts.next;
while (pj_mp != &prm.multipart_parts) {
SipMultipartPart smp;
smp.fromPj(*pj_mp);
multipartParts.push_back(smp);
pj_mp = pj_mp->next;
}
}
void SipTxOption::toPj(pjsua_msg_data &msg_data) const
{
unsigned i;
pjsua_msg_data_init(&msg_data);
msg_data.target_uri = str2Pj(targetUri);
pj_list_init(&msg_data.hdr_list);
for (i = 0; i < headers.size(); i++) {
pjsip_generic_string_hdr& pj_hdr = headers[i].toPj();
pj_list_push_back(&msg_data.hdr_list, &pj_hdr);
}
msg_data.content_type = str2Pj(contentType);
msg_data.msg_body = str2Pj(msgBody);
msg_data.multipart_ctype = multipartContentType.toPj();
pj_list_init(&msg_data.multipart_parts);
for (i = 0; i < multipartParts.size(); i++) {
pjsip_multipart_part& pj_part = multipartParts[i].toPj();
pj_list_push_back(&msg_data.multipart_parts, &pj_part);
}
}
//////////////////////////////////////////////////////////////////////////////
SendInstantMessageParam::SendInstantMessageParam()
: contentType("text/plain"), content(""), userData(NULL)
{
}
SendTypingIndicationParam::SendTypingIndicationParam()
: isTyping(false)
{
}
@@ -0,0 +1,95 @@
/* $Id$ */
/*
* Copyright (C) 2013 Teluu Inc. (http://www.teluu.com)
*
* 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 <pjsua2/types.hpp>
#include "util.hpp"
using namespace pj;
using namespace std;
#define THIS_FILE "types.cpp"
///////////////////////////////////////////////////////////////////////////////
Error::Error()
: status(PJ_SUCCESS), srcLine(0)
{
}
Error::Error( pj_status_t prm_status,
const string &prm_title,
const string &prm_reason,
const string &prm_src_file,
int prm_src_line)
: status(prm_status), title(prm_title), reason(prm_reason),
srcFile(prm_src_file), srcLine(prm_src_line)
{
if (this->status != PJ_SUCCESS && prm_reason.empty()) {
char errmsg[PJ_ERR_MSG_SIZE];
pj_strerror(this->status, errmsg, sizeof(errmsg));
this->reason = errmsg;
}
}
string Error::info(bool multi_line) const
{
string output;
if (status==PJ_SUCCESS) {
output = "No error";
} else if (!multi_line) {
char temp[80];
if (!title.empty()) {
output += title + " error: ";
}
snprintf(temp, sizeof(temp), " (status=%d)", status);
output += reason + temp;
if (!srcFile.empty()) {
output += " [";
output += srcFile;
snprintf(temp, sizeof(temp), ":%d]", srcLine);
output += temp;
}
} else {
char temp[80];
if (!title.empty()) {
output += string("Title: ") + title + "\n";
}
snprintf(temp, sizeof(temp), "%d\n", status);
output += string("Code: ") + temp;
output += string("Description: ") + reason + "\n";
if (!srcFile.empty()) {
snprintf(temp, sizeof(temp), ":%d\n", srcLine);
output += string("Location: ") + srcFile + temp;
}
}
return output;
}
///////////////////////////////////////////////////////////////////////////////
void TimeVal::fromPj(const pj_time_val &prm)
{
this->sec = prm.sec;
this->msec = prm.msec;
}
@@ -0,0 +1,57 @@
/* $Id$ */
/*
* Copyright (C) 2013 Teluu Inc. (http://www.teluu.com)
*
* 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 <pjsua2/types.hpp>
#include <pjsua2/media.hpp>
#include <string>
#define PJ2BOOL(var) ((var) != PJ_FALSE)
namespace pj
{
using std::string;
inline pj_str_t str2Pj(const string &input_str)
{
pj_str_t output_str;
output_str.ptr = (char*)input_str.c_str();
output_str.slen = input_str.size();
return output_str;
}
inline string pj2Str(const pj_str_t &input_str)
{
if (input_str.ptr && input_str.slen>0)
return string(input_str.ptr, input_str.slen);
return string();
}
class AudioMediaHelper : public AudioMedia
{
public:
void setPortId(int port_id) { id = port_id; }
};
class VideoMediaHelper : public VideoMedia
{
public:
void setPortId(int port_id) { id = port_id; }
};
} // namespace pj
@@ -0,0 +1,23 @@
/* $Id$ */
/*
* Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
* Copyright (C) 2003-2008 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 "test.h"
#include <pjsip.h>
+646
View File
@@ -0,0 +1,646 @@
/* $Id$ */
/*
* Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
* Copyright (C) 2003-2008 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 "test.h"
#include <pjsip.h>
#include <pjlib.h>
#include <pjlib-util.h>
/* For logging purpose. */
#define THIS_FILE "dns_test.c"
struct result
{
pj_status_t status;
pjsip_server_addresses servers;
};
static void cb(pj_status_t status,
void *token,
const struct pjsip_server_addresses *addr)
{
struct result *result = (struct result*) token;
result->status = status;
if (status == PJ_SUCCESS)
pj_memcpy(&result->servers, addr, sizeof(*addr));
}
static void add_dns_entries(pj_dns_resolver *resv)
{
/* Inject DNS SRV entry */
pj_dns_parsed_packet pkt;
pj_dns_parsed_query q;
pj_dns_parsed_rr ans[4];
pj_dns_parsed_rr ar[10];
pj_str_t tmp;
unsigned i;
/*
* This is answer to SRV query to "example.com" domain, and
* the answer contains full reference to the A records of
* the server. The full DNS records is :
_sip._udp.example.com 3600 IN SRV 0 0 5060 sip01.example.com.
_sip._udp.example.com 3600 IN SRV 0 20 5060 sip02.example.com.
_sip._udp.example.com 3600 IN SRV 0 10 5060 sip03.example.com.
_sip._udp.example.com 3600 IN SRV 1 0 5060 sip04.example.com.
sip01.example.com. 3600 IN A 1.1.1.1
sip02.example.com. 3600 IN A 2.2.2.2
sip03.example.com. 3600 IN A 3.3.3.3
sip04.example.com. 3600 IN A 4.4.4.4
; Additionally, add A record for "example.com"
example.com. 3600 IN A 5.5.5.5
; Additionally, add corresponding empty AAAA records for all A records
*/
pj_bzero(&pkt, sizeof(pkt));
pj_bzero(ans, sizeof(ans));
pj_bzero(ar, sizeof(ar));
pkt.hdr.flags = PJ_DNS_SET_QR(1);
pkt.hdr.anscount = PJ_ARRAY_SIZE(ans);
pkt.hdr.arcount = 0;
pkt.ans = ans;
pkt.arr = ar;
ans[0].name = pj_str("_sip._udp.example.com");
ans[0].type = PJ_DNS_TYPE_SRV;
ans[0].dnsclass = PJ_DNS_CLASS_IN;
ans[0].ttl = 3600;
ans[0].rdata.srv.prio = 0;
ans[0].rdata.srv.weight = 0;
ans[0].rdata.srv.port = 5060;
ans[0].rdata.srv.target = pj_str("sip01.example.com");
ans[1].name = pj_str("_sip._udp.example.com");
ans[1].type = PJ_DNS_TYPE_SRV;
ans[1].dnsclass = PJ_DNS_CLASS_IN;
ans[1].ttl = 3600;
ans[1].rdata.srv.prio = 0;
ans[1].rdata.srv.weight = 20;
ans[1].rdata.srv.port = 5060;
ans[1].rdata.srv.target = pj_str("sip02.example.com");
ans[2].name = pj_str("_sip._udp.example.com");
ans[2].type = PJ_DNS_TYPE_SRV;
ans[2].dnsclass = PJ_DNS_CLASS_IN;
ans[2].ttl = 3600;
ans[2].rdata.srv.prio = 0;
ans[2].rdata.srv.weight = 10;
ans[2].rdata.srv.port = 5060;
ans[2].rdata.srv.target = pj_str("sip03.example.com");
ans[3].name = pj_str("_sip._udp.example.com");
ans[3].type = PJ_DNS_TYPE_SRV;
ans[3].dnsclass = PJ_DNS_CLASS_IN;
ans[3].ttl = 3600;
ans[3].rdata.srv.prio = 1;
ans[3].rdata.srv.weight = 0;
ans[3].rdata.srv.port = 5060;
ans[3].rdata.srv.target = pj_str("sip04.example.com");
pj_dns_resolver_add_entry( resv, &pkt, PJ_FALSE);
ar[0].name = pj_str("sip01.example.com");
ar[0].type = PJ_DNS_TYPE_A;
ar[0].dnsclass = PJ_DNS_CLASS_IN;
ar[0].ttl = 3600;
ar[0].rdata.a.ip_addr = pj_inet_addr(pj_cstr(&tmp, "1.1.1.1"));
ar[1].name = pj_str("sip02.example.com");
ar[1].type = PJ_DNS_TYPE_A;
ar[1].dnsclass = PJ_DNS_CLASS_IN;
ar[1].ttl = 3600;
ar[1].rdata.a.ip_addr = pj_inet_addr(pj_cstr(&tmp, "2.2.2.2"));
ar[2].name = pj_str("sip03.example.com");
ar[2].type = PJ_DNS_TYPE_A;
ar[2].dnsclass = PJ_DNS_CLASS_IN;
ar[2].ttl = 3600;
ar[2].rdata.a.ip_addr = pj_inet_addr(pj_cstr(&tmp, "3.3.3.3"));
ar[3].name = pj_str("sip04.example.com");
ar[3].type = PJ_DNS_TYPE_A;
ar[3].dnsclass = PJ_DNS_CLASS_IN;
ar[3].ttl = 3600;
ar[3].rdata.a.ip_addr = pj_inet_addr(pj_cstr(&tmp, "4.4.4.4"));
ar[4].name = pj_str("example.com");
ar[4].type = PJ_DNS_TYPE_A;
ar[4].dnsclass = PJ_DNS_CLASS_IN;
ar[4].ttl = 3600;
ar[4].rdata.a.ip_addr = pj_inet_addr(pj_cstr(&tmp, "5.5.5.5"));
/* Add corresponding AAAA records for A records above */
for (i = 0; i < 5; ++i) {
ar[5+i].name = ar[i].name;
ar[5+i].type = PJ_DNS_TYPE_AAAA;
ar[5+i].dnsclass = PJ_DNS_CLASS_IN;
ar[5+i].ttl = 3600;
}
/*
* Create individual A records for all hosts in "example.com" domain.
*/
for (i=0; i<PJ_ARRAY_SIZE(ar); ++i) {
pj_bzero(&pkt, sizeof(pkt));
pkt.hdr.flags = PJ_DNS_SET_QR(1);
pkt.hdr.qdcount = 1;
pkt.q = &q;
q.name = ar[i].name;
q.type = ar[i].type;
q.dnsclass = PJ_DNS_CLASS_IN;
/* For now, AAAA always contains empty record */
if (ar[i].type != PJ_DNS_TYPE_AAAA) {
pkt.hdr.anscount = 1;
pkt.ans = &ar[i];
}
pj_dns_resolver_add_entry( resv, &pkt, PJ_FALSE);
}
/*
* Simulate DNS error response by creating these answers.
* Sample of invalid SRV records: _sip._udp.sip01.example.com.
*/
for (i=0; i<PJ_ARRAY_SIZE(ans); ++i) {
pj_dns_parsed_query q2;
char buf[128];
char *services[] = { "_sip._udp.", "_sip._tcp.", "_sips._tcp."};
unsigned j;
for (j=0; j<PJ_ARRAY_SIZE(services); ++j) {
q2.dnsclass = PJ_DNS_CLASS_IN;
q2.type = PJ_DNS_TYPE_SRV;
q2.name.ptr = buf;
pj_bzero(buf, sizeof(buf));
pj_strcpy2(&q2.name, services[j]);
pj_strcat(&q2.name, &ans[i].rdata.srv.target);
pj_bzero(&pkt, sizeof(pkt));
pkt.hdr.qdcount = 1;
pkt.hdr.flags = PJ_DNS_SET_QR(1) |
PJ_DNS_SET_RCODE(PJ_DNS_RCODE_NXDOMAIN);
pkt.q = &q2;
pj_dns_resolver_add_entry( resv, &pkt, PJ_FALSE);
}
}
/*
* ANOTHER DOMAIN.
*
* This time we let SRV and A get answered in different DNS
* query.
*/
/* The "domain.com" DNS records (note the different the port):
_sip._tcp.domain.com 3600 IN SRV 1 0 50060 sip06.domain.com.
_sip._tcp.domain.com 3600 IN SRV 2 0 50060 sip07.domain.com.
sip06.domain.com. 3600 IN A 6.6.6.6
sip07.domain.com. 3600 IN A 7.7.7.7
; Additionally, add corresponding empty AAAA records for all A records
*/
pj_bzero(&pkt, sizeof(pkt));
pj_bzero(&ans, sizeof(ans));
pkt.hdr.flags = PJ_DNS_SET_QR(1);
pkt.hdr.anscount = 2;
pkt.ans = ans;
/* Add the SRV records, with reverse priority (to test that sorting
* works.
*/
ans[0].name = pj_str("_sip._tcp.domain.com");
ans[0].type = PJ_DNS_TYPE_SRV;
ans[0].dnsclass = PJ_DNS_CLASS_IN;
ans[0].ttl = 3600;
ans[0].rdata.srv.prio = 2;
ans[0].rdata.srv.weight = 0;
ans[0].rdata.srv.port = 50060;
ans[0].rdata.srv.target = pj_str("SIP07.DOMAIN.COM");
ans[1].name = pj_str("_sip._tcp.domain.com");
ans[1].type = PJ_DNS_TYPE_SRV;
ans[1].dnsclass = PJ_DNS_CLASS_IN;
ans[1].ttl = 3600;
ans[1].rdata.srv.prio = 1;
ans[1].rdata.srv.weight = 0;
ans[1].rdata.srv.port = 50060;
ans[1].rdata.srv.target = pj_str("SIP06.DOMAIN.COM");
pj_dns_resolver_add_entry( resv, &pkt, PJ_FALSE);
/* From herein there is only one answer */
pkt.hdr.anscount = 1;
/* Add a single SRV for UDP */
ans[0].name = pj_str("_sip._udp.domain.com");
ans[0].type = PJ_DNS_TYPE_SRV;
ans[0].dnsclass = PJ_DNS_CLASS_IN;
ans[0].ttl = 3600;
ans[0].rdata.srv.prio = 0;
ans[0].rdata.srv.weight = 0;
ans[0].rdata.srv.port = 50060;
ans[0].rdata.srv.target = pj_str("SIP06.DOMAIN.COM");
pj_dns_resolver_add_entry( resv, &pkt, PJ_FALSE);
/* Add the A record for sip06.domain.com */
ans[0].name = pj_str("sip06.domain.com");
ans[0].type = PJ_DNS_TYPE_A;
ans[0].dnsclass = PJ_DNS_CLASS_IN;
ans[0].ttl = 3600;
ans[0].rdata.a.ip_addr = pj_inet_addr(pj_cstr(&tmp, "6.6.6.6"));
pkt.hdr.qdcount = 1;
pkt.hdr.anscount = 1;
pkt.q = &q;
q.name = ans[0].name;
q.type = ans[0].type;
q.dnsclass = ans[0].dnsclass;
pj_dns_resolver_add_entry( resv, &pkt, PJ_FALSE);
/* Add corresponding AAAA record */
q.type = PJ_DNS_TYPE_AAAA;
pkt.hdr.anscount = 0;
pj_dns_resolver_add_entry( resv, &pkt, PJ_FALSE);
/* Add the A record for sip07.domain.com */
ans[0].name = pj_str("sip07.domain.com");
ans[0].type = PJ_DNS_TYPE_A;
ans[0].dnsclass = PJ_DNS_CLASS_IN;
ans[0].ttl = 3600;
ans[0].rdata.a.ip_addr = pj_inet_addr(pj_cstr(&tmp, "7.7.7.7"));
pkt.hdr.qdcount = 1;
pkt.hdr.anscount = 1;
pkt.q = &q;
q.name = ans[0].name;
q.type = ans[0].type;
q.dnsclass = ans[0].dnsclass;
pj_dns_resolver_add_entry( resv, &pkt, PJ_FALSE);
/* Add corresponding AAAA record */
q.type = PJ_DNS_TYPE_AAAA;
pkt.hdr.anscount = 0;
pj_dns_resolver_add_entry( resv, &pkt, PJ_FALSE);
pkt.hdr.qdcount = 0;
}
/*
* Perform server resolution where the results are expected to
* come in strict order.
*/
static int test_resolve(const char *title,
pj_pool_t *pool,
pjsip_transport_type_e type,
char *name,
int port,
pjsip_server_addresses *ref)
{
pjsip_host_info dest;
struct result result;
PJ_LOG(3,(THIS_FILE, " test_resolve(): %s", title));
dest.type = type;
dest.flag = pjsip_transport_get_flag_from_type(type);
dest.addr.host = pj_str(name);
dest.addr.port = port;
result.status = 0x12345678;
pjsip_endpt_resolve(endpt, pool, &dest, &result, &cb);
while (result.status == 0x12345678) {
int i = 0;
pj_time_val timeout = { 1, 0 };
pjsip_endpt_handle_events(endpt, &timeout);
if (i == 1)
pj_dns_resolver_dump(pjsip_endpt_get_resolver(endpt), PJ_TRUE);
}
if (result.status != PJ_SUCCESS) {
app_perror(" pjsip_endpt_resolve() error", result.status);
return result.status;
}
if (ref) {
unsigned i;
if (ref->count != result.servers.count) {
PJ_LOG(3,(THIS_FILE, " test_resolve() error 10: result count mismatch"));
return 10;
}
for (i=0; i<ref->count; ++i) {
pj_sockaddr_in *ra = (pj_sockaddr_in *)&ref->entry[i].addr;
pj_sockaddr_in *rb = (pj_sockaddr_in *)&result.servers.entry[i].addr;
if (ra->sin_addr.s_addr != rb->sin_addr.s_addr) {
PJ_LOG(3,(THIS_FILE, " test_resolve() error 20: IP address mismatch"));
return 20;
}
if (ra->sin_port != rb->sin_port) {
PJ_LOG(3,(THIS_FILE, " test_resolve() error 30: port mismatch"));
return 30;
}
if (ref->entry[i].addr_len != result.servers.entry[i].addr_len) {
PJ_LOG(3,(THIS_FILE, " test_resolve() error 40: addr_len mismatch"));
return 40;
}
if (ref->entry[i].type != result.servers.entry[i].type) {
PJ_LOG(3,(THIS_FILE, " test_resolve() error 50: transport type mismatch"));
return 50;
}
}
}
return PJ_SUCCESS;
}
/*
* Perform round-robin/load balance test.
*/
static int round_robin_test(pj_pool_t *pool)
{
enum { COUNT = 400, PCT_ALLOWANCE = 10 };
unsigned i;
struct server_hit
{
char *ip_addr;
unsigned percent;
unsigned hits;
} server_hit[] =
{
{ "1.1.1.1", 3, 0 },
{ "2.2.2.2", 65, 0 },
{ "3.3.3.3", 32, 0 },
{ "4.4.4.4", 0, 0 }
};
PJ_LOG(3,(THIS_FILE, " Performing round-robin/load-balance test.."));
/* Do multiple resolve request to "example.com".
* The resolver should select the server based on the weight proportion
* the the servers in the SRV entry.
*/
for (i=0; i<COUNT; ++i) {
pjsip_host_info dest;
struct result result;
unsigned j;
dest.type = PJSIP_TRANSPORT_UDP;
dest.flag = pjsip_transport_get_flag_from_type(PJSIP_TRANSPORT_UDP);
dest.addr.host = pj_str("example.com");
dest.addr.port = 0;
result.status = 0x12345678;
pjsip_endpt_resolve(endpt, pool, &dest, &result, &cb);
while (result.status == 0x12345678) {
int ii = 0;
pj_time_val timeout = { 1, 0 };
pjsip_endpt_handle_events(endpt, &timeout);
if (ii == 1)
pj_dns_resolver_dump(pjsip_endpt_get_resolver(endpt), PJ_TRUE);
}
/* Find which server was "hit" */
for (j=0; j<PJ_ARRAY_SIZE(server_hit); ++j) {
pj_str_t tmp;
pj_in_addr a1;
pj_sockaddr_in *a2;
tmp = pj_str(server_hit[j].ip_addr);
a1 = pj_inet_addr(&tmp);
a2 = (pj_sockaddr_in*) &result.servers.entry[0].addr;
if (a1.s_addr == a2->sin_addr.s_addr) {
server_hit[j].hits++;
break;
}
}
if (j == PJ_ARRAY_SIZE(server_hit)) {
PJ_LOG(1,(THIS_FILE, "..round_robin_test() error 10: returned address mismatch"));
return 10;
}
}
/* Print the actual hit rate */
for (i=0; i<PJ_ARRAY_SIZE(server_hit); ++i) {
PJ_LOG(3,(THIS_FILE, " ..Server %s: weight=%d%%, hit %d%% times",
server_hit[i].ip_addr, server_hit[i].percent,
(server_hit[i].hits * 100) / COUNT));
}
/* Compare the actual hit with the weight proportion */
for (i=0; i<PJ_ARRAY_SIZE(server_hit); ++i) {
int actual_pct = (server_hit[i].hits * 100) / COUNT;
if (actual_pct + PCT_ALLOWANCE < (int)server_hit[i].percent ||
actual_pct - PCT_ALLOWANCE > (int)server_hit[i].percent)
{
PJ_LOG(1,(THIS_FILE,
"..round_robin_test() error 20: "
"hit rate difference for server %s (%d%%) is more than "
"tolerable allowance (%d%%)",
server_hit[i].ip_addr,
actual_pct - server_hit[i].percent,
PCT_ALLOWANCE));
return 20;
}
}
PJ_LOG(3,(THIS_FILE,
" Load balance test success, hit-rate is "
"within %d%% allowance", PCT_ALLOWANCE));
return PJ_SUCCESS;
}
#define C(expr) status = expr; \
if (status != PJ_SUCCESS) app_perror(THIS_FILE, "Error", status);
static void add_ref(pjsip_server_addresses *r,
pjsip_transport_type_e type,
char *addr,
int port)
{
pj_sockaddr_in *a;
pj_str_t tmp;
r->entry[r->count].type = type;
r->entry[r->count].priority = 0;
r->entry[r->count].weight = 0;
r->entry[r->count].addr_len = sizeof(pj_sockaddr_in);
a = (pj_sockaddr_in *)&r->entry[r->count].addr;
a->sin_family = pj_AF_INET();
tmp = pj_str(addr);
a->sin_addr = pj_inet_addr(&tmp);
a->sin_port = pj_htons((pj_uint16_t)port);
r->count++;
}
static void create_ref(pjsip_server_addresses *r,
pjsip_transport_type_e type,
char *addr,
int port)
{
r->count = 0;
add_ref(r, type, addr, port);
}
/*
* Main test entry.
*/
int resolve_test(void)
{
pj_pool_t *pool;
pj_dns_resolver *resv;
pj_str_t nameserver;
pj_uint16_t port = 5353;
pj_status_t status;
pool = pjsip_endpt_create_pool(endpt, NULL, 4000, 4000);
status = pjsip_endpt_create_resolver(endpt, &resv);
nameserver = pj_str("192.168.0.106");
pj_dns_resolver_set_ns(resv, 1, &nameserver, &port);
pjsip_endpt_set_resolver(endpt, resv);
add_dns_entries(resv);
/* These all should be resolved as IP addresses (DNS A query) */
{
pjsip_server_addresses ref;
create_ref(&ref, PJSIP_TRANSPORT_UDP, "1.1.1.1", 5060);
status = test_resolve("IP address without transport and port", pool, PJSIP_TRANSPORT_UNSPECIFIED, "1.1.1.1", 0, &ref);
if (status != PJ_SUCCESS)
return -100;
}
{
pjsip_server_addresses ref;
create_ref(&ref, PJSIP_TRANSPORT_UDP, "1.1.1.1", 5060);
status = test_resolve("IP address with explicit port", pool, PJSIP_TRANSPORT_UNSPECIFIED, "1.1.1.1", 5060, &ref);
if (status != PJ_SUCCESS)
return -110;
}
{
pjsip_server_addresses ref;
create_ref(&ref, PJSIP_TRANSPORT_TCP, "1.1.1.1", 5060);
status = test_resolve("IP address without port (TCP)", pool, PJSIP_TRANSPORT_TCP,"1.1.1.1", 0, &ref);
if (status != PJ_SUCCESS)
return -120;
}
{
pjsip_server_addresses ref;
create_ref(&ref, PJSIP_TRANSPORT_TLS, "1.1.1.1", 5061);
status = test_resolve("IP address without port (TLS)", pool, PJSIP_TRANSPORT_TLS, "1.1.1.1", 0, &ref);
if (status != PJ_SUCCESS)
return -130;
}
/* This should be resolved as DNS A record (because port is present) */
{
pjsip_server_addresses ref;
create_ref(&ref, PJSIP_TRANSPORT_UDP, "5.5.5.5", 5060);
status = test_resolve("domain name with port should resolve to A record", pool, PJSIP_TRANSPORT_UNSPECIFIED, "example.com", 5060, &ref);
if (status != PJ_SUCCESS)
return -140;
}
/* This will fail to be resolved as SRV, resolver should fallback to
* resolving to A record.
*/
{
pjsip_server_addresses ref;
create_ref(&ref, PJSIP_TRANSPORT_UDP, "2.2.2.2", 5060);
status = test_resolve("failure with SRV fallback to A record", pool, PJSIP_TRANSPORT_UNSPECIFIED, "sip02.example.com", 0, &ref);
if (status != PJ_SUCCESS)
return -150;
}
/* Same as above, but explicitly for TLS. */
{
pjsip_server_addresses ref;
create_ref(&ref, PJSIP_TRANSPORT_TLS, "2.2.2.2", 5061);
status = test_resolve("failure with SRV fallback to A record (for TLS)", pool, PJSIP_TRANSPORT_TLS, "sip02.example.com", 0, &ref);
if (status != PJ_SUCCESS)
return -150;
}
/* Standard DNS SRV followed by A recolution */
{
pjsip_server_addresses ref;
create_ref(&ref, PJSIP_TRANSPORT_UDP, "6.6.6.6", 50060);
status = test_resolve("standard SRV resolution", pool, PJSIP_TRANSPORT_UNSPECIFIED, "domain.com", 0, &ref);
if (status != PJ_SUCCESS)
return -155;
}
/* Standard DNS SRV followed by A recolution (explicit transport) */
{
pjsip_server_addresses ref;
create_ref(&ref, PJSIP_TRANSPORT_TCP, "6.6.6.6", 50060);
add_ref(&ref, PJSIP_TRANSPORT_TCP, "7.7.7.7", 50060);
status = test_resolve("standard SRV resolution with explicit transport (TCP)", pool, PJSIP_TRANSPORT_TCP, "domain.com", 0, &ref);
if (status != PJ_SUCCESS)
return -160;
}
/* Round robin/load balance test */
if (round_robin_test(pool) != 0)
return -170;
/* Timeout test */
{
status = test_resolve("timeout test", pool, PJSIP_TRANSPORT_UNSPECIFIED, "an.invalid.address", 0, NULL);
if (status == PJ_SUCCESS)
return -150;
}
return 0;
}
@@ -0,0 +1,695 @@
/* $Id$ */
/*
* Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
* Copyright (C) 2003-2008 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 "test.h"
#include <pjsip_ua.h>
#include <pjsip.h>
#include <pjlib.h>
#define THIS_FILE "inv_offer_answer_test.c"
#define PORT 5068
#define CONTACT "sip:127.0.0.1:5068"
#define TRACE_(x) PJ_LOG(3,x)
static struct oa_sdp_t
{
const char *offer;
const char *answer;
unsigned pt_result;
} oa_sdp[] =
{
{
/* Offer: */
"v=0\r\n"
"o=alice 1 1 IN IP4 host.anywhere.com\r\n"
"s= \r\n"
"c=IN IP4 host.anywhere.com\r\n"
"t=0 0\r\n"
"m=audio 49170 RTP/AVP 0\r\n"
"a=rtpmap:0 PCMU/8000\r\n",
/* Answer: */
"v=0\r\n"
"o=bob 1 1 IN IP4 host.example.com\r\n"
"s= \r\n"
"c=IN IP4 host.example.com\r\n"
"t=0 0\r\n"
"m=audio 49920 RTP/AVP 0\r\n"
"a=rtpmap:0 PCMU/8000\r\n"
"m=video 0 RTP/AVP 31\r\n",
0
},
{
/* Offer: */
"v=0\r\n"
"o=alice 2 2 IN IP4 host.anywhere.com\r\n"
"s= \r\n"
"c=IN IP4 host.anywhere.com\r\n"
"t=0 0\r\n"
"m=audio 49170 RTP/AVP 8\r\n"
"a=rtpmap:0 PCMA/8000\r\n",
/* Answer: */
"v=0\r\n"
"o=bob 2 2 IN IP4 host.example.com\r\n"
"s= \r\n"
"c=IN IP4 host.example.com\r\n"
"t=0 0\r\n"
"m=audio 49920 RTP/AVP 8\r\n"
"a=rtpmap:0 PCMA/8000\r\n",
8
},
{
/* Offer: */
"v=0\r\n"
"o=alice 3 3 IN IP4 host.anywhere.com\r\n"
"s= \r\n"
"c=IN IP4 host.anywhere.com\r\n"
"t=0 0\r\n"
"m=audio 49170 RTP/AVP 3\r\n",
/* Answer: */
"v=0\r\n"
"o=bob 3 3 IN IP4 host.example.com\r\n"
"s= \r\n"
"c=IN IP4 host.example.com\r\n"
"t=0 0\r\n"
"m=audio 49920 RTP/AVP 3\r\n",
3
},
{
/* Offer: */
"v=0\r\n"
"o=alice 4 4 IN IP4 host.anywhere.com\r\n"
"s= \r\n"
"c=IN IP4 host.anywhere.com\r\n"
"t=0 0\r\n"
"m=audio 49170 RTP/AVP 4\r\n",
/* Answer: */
"v=0\r\n"
"o=bob 4 4 IN IP4 host.example.com\r\n"
"s= \r\n"
"c=IN IP4 host.example.com\r\n"
"t=0 0\r\n"
"m=audio 49920 RTP/AVP 4\r\n",
4
}
};
typedef enum oa_t
{
OFFERER_NONE,
OFFERER_UAC,
OFFERER_UAS
} oa_t;
typedef struct inv_test_param_t
{
char *title;
unsigned inv_option;
pj_bool_t need_established;
unsigned count;
oa_t oa[4];
} inv_test_param_t;
typedef struct inv_test_t
{
inv_test_param_t param;
pjsip_inv_session *uac;
pjsip_inv_session *uas;
pj_bool_t complete;
pj_bool_t uas_complete,
uac_complete;
unsigned oa_index;
unsigned uac_update_cnt,
uas_update_cnt;
} inv_test_t;
/**************** GLOBALS ******************/
static inv_test_t inv_test;
static unsigned job_cnt;
typedef enum job_type
{
SEND_OFFER,
ESTABLISH_CALL
} job_type;
typedef struct job_t
{
job_type type;
pjsip_role_e who;
} job_t;
static job_t jobs[128];
/**************** UTILS ******************/
static pjmedia_sdp_session *create_sdp(pj_pool_t *pool, const char *body)
{
pjmedia_sdp_session *sdp;
pj_str_t dup;
pj_status_t status;
pj_strdup2_with_null(pool, &dup, body);
status = pjmedia_sdp_parse(pool, dup.ptr, dup.slen, &sdp);
pj_assert(status == PJ_SUCCESS);
PJ_UNUSED_ARG(status);
return sdp;
}
/**************** INVITE SESSION CALLBACKS ******************/
static void on_rx_offer(pjsip_inv_session *inv,
const pjmedia_sdp_session *offer)
{
pjmedia_sdp_session *sdp;
PJ_UNUSED_ARG(offer);
sdp = create_sdp(inv->dlg->pool, oa_sdp[inv_test.oa_index].answer);
pjsip_inv_set_sdp_answer(inv, sdp);
if (inv_test.oa_index == inv_test.param.count-1 &&
inv_test.param.need_established)
{
jobs[job_cnt].type = ESTABLISH_CALL;
jobs[job_cnt].who = PJSIP_ROLE_UAS;
job_cnt++;
}
}
static void on_create_offer(pjsip_inv_session *inv,
pjmedia_sdp_session **p_offer)
{
PJ_UNUSED_ARG(inv);
PJ_UNUSED_ARG(p_offer);
pj_assert(!"Should not happen");
}
static void on_media_update(pjsip_inv_session *inv_ses,
pj_status_t status)
{
PJ_UNUSED_ARG(status);
if (inv_ses == inv_test.uas) {
inv_test.uas_update_cnt++;
pj_assert(inv_test.uas_update_cnt - inv_test.uac_update_cnt <= 1);
TRACE_((THIS_FILE, " Callee media is established"));
} else if (inv_ses == inv_test.uac) {
inv_test.uac_update_cnt++;
pj_assert(inv_test.uac_update_cnt - inv_test.uas_update_cnt <= 1);
TRACE_((THIS_FILE, " Caller media is established"));
} else {
pj_assert(!"Unknown session!");
}
if (inv_test.uac_update_cnt == inv_test.uas_update_cnt) {
inv_test.oa_index++;
if (inv_test.oa_index < inv_test.param.count) {
switch (inv_test.param.oa[inv_test.oa_index]) {
case OFFERER_UAC:
jobs[job_cnt].type = SEND_OFFER;
jobs[job_cnt].who = PJSIP_ROLE_UAC;
job_cnt++;
break;
case OFFERER_UAS:
jobs[job_cnt].type = SEND_OFFER;
jobs[job_cnt].who = PJSIP_ROLE_UAS;
job_cnt++;
break;
default:
pj_assert(!"Invalid oa");
}
}
pj_assert(job_cnt <= PJ_ARRAY_SIZE(jobs));
}
}
static void on_state_changed(pjsip_inv_session *inv, pjsip_event *e)
{
const char *who = NULL;
PJ_UNUSED_ARG(e);
if (inv->state == PJSIP_INV_STATE_DISCONNECTED) {
TRACE_((THIS_FILE, " %s call disconnected",
(inv==inv_test.uas ? "Callee" : "Caller")));
return;
}
if (inv->state != PJSIP_INV_STATE_CONFIRMED)
return;
if (inv == inv_test.uas) {
inv_test.uas_complete = PJ_TRUE;
who = "Callee";
} else if (inv == inv_test.uac) {
inv_test.uac_complete = PJ_TRUE;
who = "Caller";
} else
pj_assert(!"No session");
TRACE_((THIS_FILE, " %s call is confirmed", who));
if (inv_test.uac_complete && inv_test.uas_complete)
inv_test.complete = PJ_TRUE;
}
/**************** MODULE TO RECEIVE INITIAL INVITE ******************/
static pj_bool_t on_rx_request(pjsip_rx_data *rdata)
{
if (rdata->msg_info.msg->type == PJSIP_REQUEST_MSG &&
rdata->msg_info.msg->line.req.method.id == PJSIP_INVITE_METHOD)
{
pjsip_dialog *dlg;
pjmedia_sdp_session *sdp = NULL;
pj_str_t uri;
pjsip_tx_data *tdata;
pj_status_t status;
/*
* Create UAS
*/
uri = pj_str(CONTACT);
status = pjsip_dlg_create_uas_and_inc_lock(pjsip_ua_instance(), rdata,
&uri, &dlg);
pj_assert(status == PJ_SUCCESS);
if (inv_test.param.oa[0] == OFFERER_UAC)
sdp = create_sdp(rdata->tp_info.pool, oa_sdp[0].answer);
else if (inv_test.param.oa[0] == OFFERER_UAS)
sdp = create_sdp(rdata->tp_info.pool, oa_sdp[0].offer);
else
pj_assert(!"Invalid offerer type");
status = pjsip_inv_create_uas(dlg, rdata, sdp, inv_test.param.inv_option, &inv_test.uas);
pj_assert(status == PJ_SUCCESS);
pjsip_dlg_dec_lock(dlg);
TRACE_((THIS_FILE, " Sending 183 with SDP"));
/*
* Answer with 183
*/
status = pjsip_inv_initial_answer(inv_test.uas, rdata, 183, NULL,
NULL, &tdata);
pj_assert(status == PJ_SUCCESS);
status = pjsip_inv_send_msg(inv_test.uas, tdata);
pj_assert(status == PJ_SUCCESS);
return (status == PJ_SUCCESS);
}
return PJ_FALSE;
}
static pjsip_module mod_inv_oa_test =
{
NULL, NULL, /* prev, next. */
{ "mod-inv-oa-test", 15 }, /* Name. */
-1, /* Id */
PJSIP_MOD_PRIORITY_APPLICATION, /* Priority */
NULL, /* load() */
NULL, /* start() */
NULL, /* stop() */
NULL, /* unload() */
&on_rx_request, /* on_rx_request() */
NULL, /* on_rx_response() */
NULL, /* on_tx_request. */
NULL, /* on_tx_response() */
NULL, /* on_tsx_state() */
};
/**************** THE TEST ******************/
static void run_job(job_t *j)
{
pjsip_inv_session *inv;
pjsip_tx_data *tdata;
pjmedia_sdp_session *sdp;
pj_status_t status;
if (j->who == PJSIP_ROLE_UAC)
inv = inv_test.uac;
else
inv = inv_test.uas;
switch (j->type) {
case SEND_OFFER:
sdp = create_sdp(inv->dlg->pool, oa_sdp[inv_test.oa_index].offer);
TRACE_((THIS_FILE, " Sending UPDATE with offer"));
status = pjsip_inv_update(inv, NULL, sdp, &tdata);
pj_assert(status == PJ_SUCCESS);
status = pjsip_inv_send_msg(inv, tdata);
pj_assert(status == PJ_SUCCESS);
break;
case ESTABLISH_CALL:
TRACE_((THIS_FILE, " Sending 200/OK"));
status = pjsip_inv_answer(inv, 200, NULL, NULL, &tdata);
pj_assert(status == PJ_SUCCESS);
status = pjsip_inv_send_msg(inv, tdata);
pj_assert(status == PJ_SUCCESS);
break;
}
PJ_UNUSED_ARG(status);
}
static int perform_test(inv_test_param_t *param)
{
pj_str_t uri;
pjsip_dialog *dlg;
pjmedia_sdp_session *sdp;
pjsip_tx_data *tdata;
pj_status_t status;
PJ_LOG(3,(THIS_FILE, " %s", param->title));
pj_bzero(&inv_test, sizeof(inv_test));
pj_memcpy(&inv_test.param, param, sizeof(*param));
job_cnt = 0;
uri = pj_str(CONTACT);
/*
* Create UAC
*/
status = pjsip_dlg_create_uac(pjsip_ua_instance(),
&uri, &uri, &uri, &uri, &dlg);
PJ_ASSERT_RETURN(status==PJ_SUCCESS, -10);
if (inv_test.param.oa[0] == OFFERER_UAC)
sdp = create_sdp(dlg->pool, oa_sdp[0].offer);
else
sdp = NULL;
status = pjsip_inv_create_uac(dlg, sdp, inv_test.param.inv_option, &inv_test.uac);
PJ_ASSERT_RETURN(status==PJ_SUCCESS, -20);
TRACE_((THIS_FILE, " Sending INVITE %s offer", (sdp ? "with" : "without")));
/*
* Make call!
*/
status = pjsip_inv_invite(inv_test.uac, &tdata);
PJ_ASSERT_RETURN(status==PJ_SUCCESS, -30);
status = pjsip_inv_send_msg(inv_test.uac, tdata);
PJ_ASSERT_RETURN(status==PJ_SUCCESS, -30);
/*
* Wait until test completes
*/
while (!inv_test.complete) {
pj_time_val delay = {0, 20};
pjsip_endpt_handle_events(endpt, &delay);
while (job_cnt) {
job_t j;
j = jobs[0];
pj_array_erase(jobs, sizeof(jobs[0]), job_cnt, 0);
--job_cnt;
run_job(&j);
}
}
flush_events(100);
/*
* Hangup
*/
TRACE_((THIS_FILE, " Disconnecting call"));
status = pjsip_inv_end_session(inv_test.uas, PJSIP_SC_DECLINE, 0, &tdata);
pj_assert(status == PJ_SUCCESS);
status = pjsip_inv_send_msg(inv_test.uas, tdata);
pj_assert(status == PJ_SUCCESS);
flush_events(500);
return 0;
}
static pj_bool_t log_on_rx_msg(pjsip_rx_data *rdata)
{
pjsip_msg *msg = rdata->msg_info.msg;
char info[80];
if (msg->type == PJSIP_REQUEST_MSG)
pj_ansi_snprintf(info, sizeof(info), "%.*s",
(int)msg->line.req.method.name.slen,
msg->line.req.method.name.ptr);
else
pj_ansi_snprintf(info, sizeof(info), "%d/%.*s",
msg->line.status.code,
(int)rdata->msg_info.cseq->method.name.slen,
rdata->msg_info.cseq->method.name.ptr);
TRACE_((THIS_FILE, " Received %s %s sdp", info,
(msg->body ? "with" : "without")));
return PJ_FALSE;
}
/* Message logger module. */
static pjsip_module mod_msg_logger =
{
NULL, NULL, /* prev and next */
{ "mod-msg-loggee", 14}, /* Name. */
-1, /* Id */
PJSIP_MOD_PRIORITY_TRANSPORT_LAYER-1,/* Priority */
NULL, /* load() */
NULL, /* start() */
NULL, /* stop() */
NULL, /* unload() */
&log_on_rx_msg, /* on_rx_request() */
&log_on_rx_msg, /* on_rx_response() */
NULL, /* on_tx_request() */
NULL, /* on_tx_response() */
NULL, /* on_tsx_state() */
};
static inv_test_param_t test_params[] =
{
/* Normal scenario:
UAC UAS
INVITE (offer) -->
200/INVITE (answer) <--
ACK -->
*/
#if 0
{
"Standard INVITE with offer",
0,
PJ_TRUE,
1,
{ OFFERER_UAC }
},
{
"Standard INVITE with offer, with 100rel",
PJSIP_INV_REQUIRE_100REL,
PJ_TRUE,
1,
{ OFFERER_UAC }
},
#endif
/* Delayed offer:
UAC UAS
INVITE (no SDP) -->
200/INVITE (offer) <--
ACK (answer) -->
*/
#if 1
{
"INVITE with no offer",
0,
PJ_TRUE,
1,
{ OFFERER_UAS }
},
{
"INVITE with no offer, with 100rel",
PJSIP_INV_REQUIRE_100REL,
PJ_TRUE,
1,
{ OFFERER_UAS }
},
#endif
/* Subsequent UAC offer with UPDATE:
UAC UAS
INVITE (offer) -->
180/rel (answer) <--
UPDATE (offer) --> inv_update() on_rx_offer()
set_sdp_answer()
200/UPDATE (answer) <--
200/INVITE <--
ACK -->
*/
#if 1
{
"INVITE and UPDATE by UAC",
0,
PJ_TRUE,
2,
{ OFFERER_UAC, OFFERER_UAC }
},
{
"INVITE and UPDATE by UAC, with 100rel",
PJSIP_INV_REQUIRE_100REL,
PJ_TRUE,
2,
{ OFFERER_UAC, OFFERER_UAC }
},
#endif
/* Subsequent UAS offer with UPDATE:
INVITE (offer -->
180/rel (answer) <--
UPDATE (offer) <-- inv_update()
on_rx_offer()
set_sdp_answer()
200/UPDATE (answer) -->
UPDATE (offer) --> on_rx_offer()
set_sdp_answer()
200/UPDATE (answer) <--
200/INVITE <--
ACK -->
*/
{
"INVITE and many UPDATE by UAC and UAS",
0,
PJ_TRUE,
4,
{ OFFERER_UAC, OFFERER_UAS, OFFERER_UAC, OFFERER_UAS }
},
};
static pjsip_dialog* on_dlg_forked(pjsip_dialog *first_set, pjsip_rx_data *res)
{
PJ_UNUSED_ARG(first_set);
PJ_UNUSED_ARG(res);
return NULL;
}
static void on_new_session(pjsip_inv_session *inv, pjsip_event *e)
{
PJ_UNUSED_ARG(inv);
PJ_UNUSED_ARG(e);
}
int inv_offer_answer_test(void)
{
unsigned i;
int rc = 0;
/* Init UA layer */
if (pjsip_ua_instance()->id == -1) {
pjsip_ua_init_param ua_param;
pj_bzero(&ua_param, sizeof(ua_param));
ua_param.on_dlg_forked = &on_dlg_forked;
pjsip_ua_init_module(endpt, &ua_param);
}
/* Init inv-usage */
if (pjsip_inv_usage_instance()->id == -1) {
pjsip_inv_callback inv_cb;
pj_bzero(&inv_cb, sizeof(inv_cb));
inv_cb.on_media_update = &on_media_update;
inv_cb.on_rx_offer = &on_rx_offer;
inv_cb.on_create_offer = &on_create_offer;
inv_cb.on_state_changed = &on_state_changed;
inv_cb.on_new_session = &on_new_session;
pjsip_inv_usage_init(endpt, &inv_cb);
}
/* 100rel module */
pjsip_100rel_init_module(endpt);
/* Our module */
pjsip_endpt_register_module(endpt, &mod_inv_oa_test);
pjsip_endpt_register_module(endpt, &mod_msg_logger);
/* Create SIP UDP transport */
{
pj_sockaddr_in addr;
pjsip_transport *tp;
pj_status_t status;
pj_sockaddr_in_init(&addr, NULL, PORT);
status = pjsip_udp_transport_start(endpt, &addr, NULL, 1, &tp);
pj_assert(status == PJ_SUCCESS);
PJ_UNUSED_ARG(status);
}
/* Do tests */
for (i=0; i<PJ_ARRAY_SIZE(test_params); ++i) {
rc = perform_test(&test_params[i]);
if (rc != 0)
goto on_return;
}
on_return:
return rc;
}
+122
View File
@@ -0,0 +1,122 @@
/* $Id$ */
/*
* Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
* Copyright (C) 2003-2008 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 "test.h"
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
extern const char *system_name;
static void usage()
{
puts("Usage: test-pjsip");
puts("Options:");
puts(" -i,--interractive Key input at the end.");
puts(" -h,--help Show this screen");
puts(" -l,--log-level N Set log level (0-6)");
}
#if PJ_LINUX || PJ_DARWINOS
#include <execinfo.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
static void print_stack(int sig)
{
void *array[16];
size_t size;
size = backtrace(array, 16);
fprintf(stderr, "Error: signal %d:\n", sig);
backtrace_symbols_fd(array, size, STDERR_FILENO);
exit(1);
}
static void init_signals()
{
signal(SIGSEGV, &print_stack);
signal(SIGABRT, &print_stack);
}
#else
#define init_signals()
#endif
int main(int argc, char *argv[])
{
int interractive = 0;
int retval;
char **opt_arg;
PJ_UNUSED_ARG(argc);
init_signals();
/* Parse arguments. */
opt_arg = argv+1;
while (*opt_arg) {
if (strcmp(*opt_arg, "-i") == 0 ||
strcmp(*opt_arg, "--interractive") == 0)
{
interractive = 1;
} else if (strcmp(*opt_arg, "-h") == 0 ||
strcmp(*opt_arg, "--help") == 0)
{
usage();
return 1;
} else if (strcmp(*opt_arg, "-l") == 0 ||
strcmp(*opt_arg, "--log-level") == 0)
{
++opt_arg;
if (!opt_arg) {
usage();
return 1;
}
log_level = atoi(*opt_arg);
} else if (strcmp(*opt_arg, "-s") == 0 ||
strcmp(*opt_arg, "--system") == 0)
{
++opt_arg;
if (!opt_arg) {
usage();
return 1;
}
system_name = *opt_arg;
} else {
usage();
return 1;
}
++opt_arg;
}
retval = test_main();
if (interractive) {
char s[10];
printf("<Press ENTER to quit>\n"); fflush(stdout);
if (fgets(s, sizeof(s), stdin) == NULL)
return retval;
}
return retval;
}
@@ -0,0 +1,12 @@
/*
* !! OIY OIY !!
*
* The purpose of this file is only to get pjsip-test linked. I haven't
* actually tried to run pjsip-test on RTEMS!!
*
*/
#include "../../pjlib/src/pjlib-test/main_rtems.c"
@@ -0,0 +1 @@
#include "../../pjlib/src/pjlib-test/main_win32.c"
@@ -0,0 +1,103 @@
/* $Id$ */
/*
* Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
* Copyright (C) 2003-2008 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 "test.h"
#include <pjsip.h>
#include <pjlib.h>
#define THIS_FILE "msg_err_test.c"
static pj_bool_t verify_success(pjsip_msg *msg,
pjsip_parser_err_report *err_list)
{
PJ_UNUSED_ARG(msg);
PJ_UNUSED_ARG(err_list);
return PJ_TRUE;
}
static struct test_entry
{
char msg[1024];
pj_bool_t (*verify)(pjsip_msg *msg,
pjsip_parser_err_report *err_list);
} test_entries[] =
{
/* Syntax error in status line */
{
"SIP/2.0 200\r\n"
"H-Name: H-Value\r\n"
"\r\n",
&verify_success
},
/* Syntax error in header */
{
"SIP/2.0 200 OK\r\n"
"Via: SIP/2.0\r\n"
"H-Name: H-Value\r\n"
"\r\n",
&verify_success
},
/* Multiple syntax errors in headers */
{
"SIP/2.0 200 OK\r\n"
"Via: SIP/2.0\r\n"
"H-Name: H-Value\r\n"
"Via: SIP/2.0\r\n"
"\r\n",
&verify_success
}
};
int msg_err_test(void)
{
pj_pool_t *pool;
unsigned i;
PJ_LOG(3,(THIS_FILE, "Testing parsing error"));
pool = pjsip_endpt_create_pool(endpt, "msgerrtest", 4000, 4000);
for (i=0; i<PJ_ARRAY_SIZE(test_entries); ++i) {
pjsip_parser_err_report err_list, *e;
PJ_LOG(3,(THIS_FILE, " Parsing msg %d", i));
pj_list_init(&err_list);
pjsip_parse_msg(pool, test_entries[i].msg,
strlen(test_entries[i].msg), &err_list);
e = err_list.next;
while (e != &err_list) {
PJ_LOG(3,(THIS_FILE,
" reported syntax error at line %d col %d for %.*s",
e->line, e->col,
(int)e->hname.slen,
e->hname.ptr));
e = e->next;
}
}
pj_pool_release(pool);
return 0;
}
@@ -0,0 +1,104 @@
/* $Id$ */
/*
* Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
* Copyright (C) 2003-2008 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 "test.h"
#include <pjsip.h>
#include <pjlib.h>
#define THIS_FILE "msg_logger.c"
static pj_bool_t msg_log_enabled;
static pj_bool_t on_rx_msg(pjsip_rx_data *rdata)
{
if (msg_log_enabled) {
PJ_LOG(4,(THIS_FILE, "RX %d bytes %s from %s:%s:%d:\n"
"%.*s\n"
"--end msg--",
rdata->msg_info.len,
pjsip_rx_data_get_info(rdata),
rdata->tp_info.transport->type_name,
rdata->pkt_info.src_name,
rdata->pkt_info.src_port,
rdata->msg_info.len,
rdata->msg_info.msg_buf));
}
return PJ_FALSE;
}
static pj_status_t on_tx_msg(pjsip_tx_data *tdata)
{
if (msg_log_enabled) {
PJ_LOG(4,(THIS_FILE, "TX %d bytes %s to %s:%s:%d:\n"
"%.*s\n"
"--end msg--",
(tdata->buf.cur - tdata->buf.start),
pjsip_tx_data_get_info(tdata),
tdata->tp_info.transport->type_name,
tdata->tp_info.dst_name,
tdata->tp_info.dst_port,
(tdata->buf.cur - tdata->buf.start),
tdata->buf.start));
}
return PJ_SUCCESS;
}
/* Message logger module. */
static pjsip_module mod_msg_logger =
{
NULL, NULL, /* prev and next */
{ "mod-msg-logger", 14}, /* Name. */
-1, /* Id */
PJSIP_MOD_PRIORITY_TRANSPORT_LAYER-1,/* Priority */
NULL, /* load() */
NULL, /* start() */
NULL, /* stop() */
NULL, /* unload() */
&on_rx_msg, /* on_rx_request() */
&on_rx_msg, /* on_rx_response() */
&on_tx_msg, /* on_tx_request() */
&on_tx_msg, /* on_tx_response() */
NULL, /* on_tsx_state() */
};
int init_msg_logger(void)
{
pj_status_t status;
if (mod_msg_logger.id != -1)
return 0;
status = pjsip_endpt_register_module(endpt, &mod_msg_logger);
if (status != PJ_SUCCESS) {
app_perror(" error registering module", status);
return -10;
}
return 0;
}
int msg_logger_set_enabled(pj_bool_t enabled)
{
int val = msg_log_enabled;
msg_log_enabled = enabled;
return val;
}
File diff suppressed because it is too large. Load diff
@@ -0,0 +1,266 @@
/* $Id$ */
/*
* Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
*
* 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 "test.h"
#include <pjsip.h>
#include <pjlib.h>
#define THIS_FILE ""
/*
* multipart tests
*/
typedef pj_status_t (*verify_ptr)(pj_pool_t*,pjsip_msg_body*);
static pj_status_t verify1(pj_pool_t *pool, pjsip_msg_body *body);
static struct test_t
{
char *ctype;
char *csubtype;
char *boundary;
const char *msg;
verify_ptr verify;
} p_tests[] =
{
{
/* Content-type */
"multipart", "mixed", "12345",
/* Body: */
"This is the prolog, which should be ignored.\r\n"
"--12345\r\n"
"Content-Type: my/text\r\n"
"\r\n"
"Header and body\r\n"
"--12345 \t\r\n"
"Content-Type: hello/world\r\n"
"Content-Length: 0\r\n"
"\r\n"
"--12345\r\n"
"\r\n"
"Body only\r\n"
"--12345\r\n"
"Content-Type: multipart/mixed;boundary=6789\r\n"
"\r\n"
"Prolog of the subbody, should be ignored\r\n"
"--6789\r\n"
"\r\n"
"Subbody\r\n"
"--6789--\r\n"
"Epilogue of the subbody, should be ignored\r\n"
"--12345--\r\n"
"This is epilogue, which should be ignored too",
&verify1
}
};
static void init_media_type(pjsip_media_type *mt,
char *type, char *subtype, char *boundary)
{
static pjsip_param prm;
pjsip_media_type_init(mt, NULL, NULL);
if (type) mt->type = pj_str(type);
if (subtype) mt->subtype = pj_str(subtype);
if (boundary) {
pj_list_init(&prm);
prm.name = pj_str("boundary");
prm.value = pj_str(boundary);
pj_list_push_back(&mt->param, &prm);
}
}
static int verify_part(pjsip_multipart_part *part,
char *h_content_type,
char *h_content_subtype,
char *boundary,
int h_content_length,
const char *body)
{
pjsip_ctype_hdr *ctype_hdr = NULL;
pjsip_clen_hdr *clen_hdr = NULL;
pjsip_hdr *hdr;
pj_str_t the_body;
hdr = part->hdr.next;
while (hdr != &part->hdr) {
if (hdr->type == PJSIP_H_CONTENT_TYPE)
ctype_hdr = (pjsip_ctype_hdr*)hdr;
else if (hdr->type == PJSIP_H_CONTENT_LENGTH)
clen_hdr = (pjsip_clen_hdr*)hdr;
hdr = hdr->next;
}
if (h_content_type) {
pjsip_media_type mt;
if (ctype_hdr == NULL)
return -10;
init_media_type(&mt, h_content_type, h_content_subtype, boundary);
if (pjsip_media_type_cmp(&ctype_hdr->media, &mt, 2) != 0)
return -20;
} else {
if (ctype_hdr)
return -30;
}
if (h_content_length >= 0) {
if (clen_hdr == NULL)
return -50;
if (clen_hdr->len != h_content_length)
return -60;
} else {
if (clen_hdr)
return -70;
}
the_body.ptr = (char*)part->body->data;
the_body.slen = part->body->len;
if (pj_strcmp2(&the_body, body) != 0)
return -90;
return 0;
}
static pj_status_t verify1(pj_pool_t *pool, pjsip_msg_body *body)
{
pjsip_media_type mt;
pjsip_multipart_part *part;
int rc;
PJ_UNUSED_ARG(pool);
/* Check content-type: "multipart/mixed;boundary=12345" */
init_media_type(&mt, "multipart", "mixed", "12345");
if (pjsip_media_type_cmp(&body->content_type, &mt, 2) != 0)
return -200;
/* First part:
"Content-Type: my/text\r\n"
"\r\n"
"Header and body\r\n"
*/
part = pjsip_multipart_get_first_part(body);
if (!part)
return -210;
if (verify_part(part, "my", "text", NULL, -1, "Header and body"))
return -220;
/* Next part:
"Content-Type: hello/world\r\n"
"Content-Length: 0\r\n"
"\r\n"
*/
part = pjsip_multipart_get_next_part(body, part);
if (!part)
return -230;
if ((rc=verify_part(part, "hello", "world", NULL, 0, ""))!=0) {
PJ_LOG(3,(THIS_FILE, " err: verify_part rc=%d", rc));
return -240;
}
/* Next part:
"\r\n"
"Body only\r\n"
*/
part = pjsip_multipart_get_next_part(body, part);
if (!part)
return -260;
if (verify_part(part, NULL, NULL, NULL, -1, "Body only"))
return -270;
/* Next part:
"Content-Type: multipart/mixed;boundary=6789\r\n"
"\r\n"
"Prolog of the subbody, should be ignored\r\n"
"--6789\r\n"
"\r\n"
"Subbody\r\n"
"--6789--\r\n"
"Epilogue of the subbody, should be ignored\r\n"
*/
part = pjsip_multipart_get_next_part(body, part);
if (!part)
return -280;
if ((rc=verify_part(part, "multipart", "mixed", "6789", -1,
"Prolog of the subbody, should be ignored\r\n"
"--6789\r\n"
"\r\n"
"Subbody\r\n"
"--6789--\r\n"
"Epilogue of the subbody, should be ignored"))!=0) {
PJ_LOG(3,(THIS_FILE, " err: verify_part rc=%d", rc));
return -290;
}
return 0;
}
static int parse_test(void)
{
unsigned i;
for (i=0; i<PJ_ARRAY_SIZE(p_tests); ++i) {
pj_pool_t *pool;
pjsip_media_type ctype;
pjsip_msg_body *body;
pj_str_t str;
int rc;
pool = pjsip_endpt_create_pool(endpt, NULL, 512, 512);
init_media_type(&ctype, p_tests[i].ctype, p_tests[i].csubtype,
p_tests[i].boundary);
pj_strdup2_with_null(pool, &str, p_tests[i].msg);
body = pjsip_multipart_parse(pool, str.ptr, str.slen, &ctype, 0);
if (!body)
return -100;
if (p_tests[i].verify) {
rc = p_tests[i].verify(pool, body);
} else {
rc = 0;
}
pj_pool_release(pool);
if (rc)
return rc;
}
return 0;
}
int multipart_test(void)
{
int rc;
rc = parse_test();
if (rc)
return rc;
return rc;
}
File diff suppressed because it is too large. Load diff
+407
View File
@@ -0,0 +1,407 @@
/* $Id$ */
/*
* Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
* Copyright (C) 2003-2008 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 "test.h"
#include <pjlib.h>
#include <pjlib-util.h>
#include <pjsip.h>
#define THIS_FILE "test.c"
#define DO_TEST(test) do { \
PJ_LOG(3, (THIS_FILE, "Running %s...", #test)); \
rc = test; \
PJ_LOG(3, (THIS_FILE, \
"%s(%d)", \
(rc ? "..ERROR" : "..success"), rc)); \
if (rc!=0) goto on_return; \
} while (0)
#define DO_TSX_TEST(test, param) \
do { \
PJ_LOG(3, (THIS_FILE, "Running %s(%s)...", #test, (param)->tp_type)); \
rc = test(param); \
PJ_LOG(3, (THIS_FILE, \
"%s(%d)", \
(rc ? "..ERROR" : "..success"), rc)); \
if (rc!=0) goto on_return; \
} while (0)
pjsip_endpoint *endpt;
pj_caching_pool caching_pool;
int log_level = 3;
int param_log_decor = PJ_LOG_HAS_NEWLINE | PJ_LOG_HAS_TIME | PJ_LOG_HAS_SENDER |
PJ_LOG_HAS_MICRO_SEC | PJ_LOG_HAS_INDENT;
static pj_oshandle_t fd_report;
const char *system_name = "Unknown";
static char buf[1024];
void app_perror(const char *msg, pj_status_t rc)
{
char errbuf[256];
PJ_CHECK_STACK();
pj_strerror(rc, errbuf, sizeof(errbuf));
PJ_LOG(3,(THIS_FILE, "%s: [pj_status_t=%d] %s", msg, rc, errbuf));
}
void flush_events(unsigned duration)
{
pj_time_val stop_time;
pj_gettimeofday(&stop_time);
stop_time.msec += duration;
pj_time_val_normalize(&stop_time);
/* Process all events for the specified duration. */
for (;;) {
pj_time_val timeout = {0, 1}, now;
pjsip_endpt_handle_events(endpt, &timeout);
pj_gettimeofday(&now);
if (PJ_TIME_VAL_GTE(now, stop_time))
break;
}
}
pj_status_t register_static_modules(pj_size_t *count, pjsip_module **modules)
{
PJ_UNUSED_ARG(modules);
*count = 0;
return PJ_SUCCESS;
}
static pj_status_t init_report(void)
{
char tmp[80];
pj_time_val timestamp;
pj_parsed_time date_time;
pj_ssize_t len;
pj_status_t status;
pj_ansi_sprintf(tmp, "pjsip-static-bench-%s-%s.htm", PJ_OS_NAME, PJ_CC_NAME);
status = pj_file_open(NULL, tmp, PJ_O_WRONLY, &fd_report);
if (status != PJ_SUCCESS)
return status;
/* Title */
len = pj_ansi_sprintf(buf, "<HTML>\n"
" <HEAD>\n"
" <TITLE>PJSIP %s (%s) - Static Benchmark</TITLE>\n"
" </HEAD>\n"
"<BODY>\n"
"\n",
PJ_VERSION,
(PJ_DEBUG ? "Debug" : "Release"));
pj_file_write(fd_report, buf, &len);
/* Title */
len = pj_ansi_sprintf(buf, "<H1>PJSIP %s (%s) - Static Benchmark</H1>\n",
PJ_VERSION,
(PJ_DEBUG ? "Debug" : "Release"));
pj_file_write(fd_report, buf, &len);
len = pj_ansi_sprintf(buf, "<P>Below is the benchmark result generated "
"by <b>test-pjsip</b> program. The program "
"is single-threaded only.</P>\n");
pj_file_write(fd_report, buf, &len);
/* Write table heading */
len = pj_ansi_sprintf(buf, "<TABLE border=\"1\" cellpadding=\"4\">\n"
" <TR><TD bgColor=\"aqua\" align=\"center\">Variable</TD>\n"
" <TD bgColor=\"aqua\" align=\"center\">Value</TD>\n"
" <TD bgColor=\"aqua\" align=\"center\">Description</TD>\n"
" </TR>\n");
pj_file_write(fd_report, buf, &len);
/* Write version */
report_sval("version", PJ_VERSION, "", "PJLIB/PJSIP version");
/* Debug or release */
report_sval("build-type", (PJ_DEBUG ? "Debug" : "Release"), "", "Build type");
/* Write timestamp */
pj_gettimeofday(&timestamp);
report_ival("timestamp", timestamp.sec, "", "System timestamp of the test");
/* Write time of day */
pj_time_decode(&timestamp, &date_time);
len = pj_ansi_sprintf(tmp, "%04d-%02d-%02d %02d:%02d:%02d",
date_time.year, date_time.mon+1, date_time.day,
date_time.hour, date_time.min, date_time.sec);
report_sval("date-time", tmp, "", "Date/time of the test");
/* Write System */
report_sval("system", system_name, "", "System description");
/* Write OS type */
report_sval("os-family", PJ_OS_NAME, "", "Operating system family");
/* Write CC name */
len = pj_ansi_sprintf(tmp, "%s-%d.%d.%d", PJ_CC_NAME,
PJ_CC_VER_1, PJ_CC_VER_2, PJ_CC_VER_2);
report_sval("cc-name", tmp, "", "Compiler name and version");
return PJ_SUCCESS;
}
void report_sval(const char *name, const char* value, const char *valname,
const char *desc)
{
pj_ssize_t len;
len = pj_ansi_sprintf(buf, " <TR><TD><TT>%s</TT></TD>\n"
" <TD align=\"right\"><B>%s %s</B></TD>\n"
" <TD>%s</TD>\n"
" </TR>\n",
name, value, valname, desc);
pj_file_write(fd_report, buf, &len);
}
void report_ival(const char *name, int value, const char *valname,
const char *desc)
{
pj_ssize_t len;
len = pj_ansi_sprintf(buf, " <TR><TD><TT>%s</TT></TD>\n"
" <TD align=\"right\"><B>%d %s</B></TD>\n"
" <TD>%s</TD>\n"
" </TR>\n",
name, value, valname, desc);
pj_file_write(fd_report, buf, &len);
}
static void close_report(void)
{
pj_ssize_t len;
if (fd_report) {
len = pj_ansi_sprintf(buf, "</TABLE>\n</BODY>\n</HTML>\n");
pj_file_write(fd_report, buf, &len);
pj_file_close(fd_report);
}
}
int test_main(void)
{
pj_status_t rc;
const char *filename;
unsigned tsx_test_cnt=0;
struct tsx_test_param tsx_test[10];
pj_status_t status;
#if INCLUDE_TSX_TEST
unsigned i;
pjsip_transport *tp;
#if PJ_HAS_TCP
pjsip_tpfactory *tpfactory;
#endif /* PJ_HAS_TCP */
#endif /* INCLUDE_TSX_TEST */
int line;
pj_log_set_level(log_level);
pj_log_set_decor(param_log_decor);
if ((rc=pj_init()) != PJ_SUCCESS) {
app_perror("pj_init", rc);
return rc;
}
if ((rc=pjlib_util_init()) != PJ_SUCCESS) {
app_perror("pj_init", rc);
return rc;
}
status = init_report();
if (status != PJ_SUCCESS)
return status;
pj_dump_config();
pj_caching_pool_init( &caching_pool, &pj_pool_factory_default_policy,
PJSIP_TEST_MEM_SIZE );
rc = pjsip_endpt_create(&caching_pool.factory, "endpt", &endpt);
if (rc != PJ_SUCCESS) {
app_perror("pjsip_endpt_create", rc);
pj_caching_pool_destroy(&caching_pool);
return rc;
}
PJ_LOG(3,(THIS_FILE,""));
/* Init logger module. */
init_msg_logger();
msg_logger_set_enabled(1);
/* Start transaction layer module. */
rc = pjsip_tsx_layer_init_module(endpt);
if (rc != PJ_SUCCESS) {
app_perror(" Error initializing transaction module", rc);
goto on_return;
}
/* Create loop transport. */
rc = pjsip_loop_start(endpt, NULL);
if (rc != PJ_SUCCESS) {
app_perror(" error: unable to create datagram loop transport",
rc);
goto on_return;
}
tsx_test[tsx_test_cnt].port = 5060;
tsx_test[tsx_test_cnt].tp_type = "loop-dgram";
tsx_test[tsx_test_cnt].type = PJSIP_TRANSPORT_LOOP_DGRAM;
++tsx_test_cnt;
#if INCLUDE_URI_TEST
DO_TEST(uri_test());
#endif
#if INCLUDE_MSG_TEST
DO_TEST(msg_test());
DO_TEST(msg_err_test());
#endif
#if INCLUDE_MULTIPART_TEST
DO_TEST(multipart_test());
#endif
#if INCLUDE_TXDATA_TEST
DO_TEST(txdata_test());
#endif
#if INCLUDE_TSX_BENCH
DO_TEST(tsx_bench());
#endif
#if INCLUDE_UDP_TEST
DO_TEST(transport_udp_test());
#endif
#if INCLUDE_LOOP_TEST
DO_TEST(transport_loop_test());
#endif
#if INCLUDE_TCP_TEST
DO_TEST(transport_tcp_test());
#endif
#if INCLUDE_RESOLVE_TEST
DO_TEST(resolve_test());
#endif
#if INCLUDE_TSX_TEST
status = pjsip_udp_transport_start(endpt, NULL, NULL, 1, &tp);
if (status == PJ_SUCCESS) {
tsx_test[tsx_test_cnt].port = tp->local_name.port;
tsx_test[tsx_test_cnt].tp_type = "udp";
tsx_test[tsx_test_cnt].type = PJSIP_TRANSPORT_UDP;
++tsx_test_cnt;
}
#if PJ_HAS_TCP
status = pjsip_tcp_transport_start(endpt, NULL, 1, &tpfactory);
if (status == PJ_SUCCESS) {
tsx_test[tsx_test_cnt].port = tpfactory->addr_name.port;
tsx_test[tsx_test_cnt].tp_type = "tcp";
tsx_test[tsx_test_cnt].type = PJSIP_TRANSPORT_TCP;
++tsx_test_cnt;
} else {
app_perror("Unable to create TCP", status);
rc = -4;
goto on_return;
}
#endif
for (i=0; i<tsx_test_cnt; ++i) {
DO_TSX_TEST(tsx_basic_test, &tsx_test[i]);
DO_TSX_TEST(tsx_uac_test, &tsx_test[i]);
DO_TSX_TEST(tsx_uas_test, &tsx_test[i]);
}
#endif
#if INCLUDE_INV_OA_TEST
DO_TEST(inv_offer_answer_test());
#endif
#if INCLUDE_REGC_TEST
DO_TEST(regc_test());
#endif
/*
* Better be last because it recreates the endpt
*/
#if INCLUDE_TSX_DESTROY_TEST
DO_TEST(tsx_destroy_test());
#endif
on_return:
flush_events(500);
/* Show additional info on the log. e.g: not released memory pool. */
pj_log_set_level(4);
/* Dumping memory pool usage */
PJ_LOG(3,(THIS_FILE, "Peak memory size=%u MB",
caching_pool.peak_used_size / 1000000));
pjsip_endpt_destroy(endpt);
pj_caching_pool_destroy(&caching_pool);
PJ_LOG(3,(THIS_FILE, ""));
pj_thread_get_stack_info(pj_thread_this(), &filename, &line);
PJ_LOG(3,(THIS_FILE, "Stack max usage: %u, deepest: %s:%u",
pj_thread_get_stack_max_usage(pj_thread_this()),
filename, line));
if (rc == 0)
PJ_LOG(3,(THIS_FILE, "Looks like everything is okay!.."));
else
PJ_LOG(3,(THIS_FILE, "Test completed with error(s)"));
report_ival("test-status", rc, "", "Overall test status/result (0==success)");
close_report();
return rc;
}
+134
View File
@@ -0,0 +1,134 @@
/* $Id$ */
/*
* Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
* Copyright (C) 2003-2008 Benny Prijono <benny@prijono.org>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#ifndef __TEST_H__
#define __TEST_H__
#include <pjsip/sip_types.h>
extern pjsip_endpoint *endpt;
extern pj_caching_pool caching_pool;
#define TEST_UDP_PORT 15060
#define TEST_UDP_PORT_STR "15060"
/**
* Memory size to use in caching pool.
* Default: 2MB
*/
#ifndef PJSIP_TEST_MEM_SIZE
# define PJSIP_TEST_MEM_SIZE (2*1024*1024)
#endif
#define INCLUDE_MESSAGING_GROUP 1
#define INCLUDE_TRANSPORT_GROUP 1
#define INCLUDE_TSX_GROUP 1
#define INCLUDE_INV_GROUP 1
#define INCLUDE_REGC_GROUP 1
#define INCLUDE_BENCHMARKS 1
/*
* Include tests that normally would fail under certain gcc
* optimization levels.
*/
#ifndef INCLUDE_GCC_TEST
# define INCLUDE_GCC_TEST 0
#endif
#if defined(PJ_EXCLUDE_BENCHMARK_TESTS) && (PJ_EXCLUDE_BENCHMARK_TESTS==1)
# define WITH_BENCHMARK 0
#else
# define WITH_BENCHMARK 1
#endif
#define INCLUDE_URI_TEST INCLUDE_MESSAGING_GROUP
#define INCLUDE_MSG_TEST INCLUDE_MESSAGING_GROUP
#define INCLUDE_MULTIPART_TEST INCLUDE_MESSAGING_GROUP
#define INCLUDE_TXDATA_TEST INCLUDE_MESSAGING_GROUP
#define INCLUDE_TSX_BENCH (INCLUDE_MESSAGING_GROUP && WITH_BENCHMARK)
#define INCLUDE_UDP_TEST INCLUDE_TRANSPORT_GROUP
#define INCLUDE_LOOP_TEST INCLUDE_TRANSPORT_GROUP
#define INCLUDE_TCP_TEST INCLUDE_TRANSPORT_GROUP
#define INCLUDE_RESOLVE_TEST INCLUDE_TRANSPORT_GROUP
#define INCLUDE_TSX_TEST INCLUDE_TSX_GROUP
#define INCLUDE_TSX_DESTROY_TEST INCLUDE_TSX_GROUP
#define INCLUDE_INV_OA_TEST INCLUDE_INV_GROUP
#define INCLUDE_REGC_TEST INCLUDE_REGC_GROUP
/* The tests */
int uri_test(void);
int msg_test(void);
int msg_err_test(void);
int multipart_test(void);
int txdata_test(void);
int tsx_bench(void);
int tsx_destroy_test(void);
int transport_udp_test(void);
int transport_loop_test(void);
int transport_tcp_test(void);
int resolve_test(void);
int regc_test(void);
struct tsx_test_param
{
int type;
int port;
char *tp_type;
};
int tsx_basic_test(struct tsx_test_param *param);
int tsx_uac_test(struct tsx_test_param *param);
int tsx_uas_test(struct tsx_test_param *param);
/* Transport test helpers (transport_test.c). */
int generic_transport_test(pjsip_transport *tp);
int transport_send_recv_test( pjsip_transport_type_e tp_type,
pjsip_transport *ref_tp,
char *target_url,
int *p_usec_rtt);
int transport_rt_test( pjsip_transport_type_e tp_type,
pjsip_transport *ref_tp,
char *target_url,
int *pkt_lost);
int transport_load_test(char *target_url);
/* Invite session */
int inv_offer_answer_test(void);
/* Test main entry */
int test_main(void);
/* Test utilities. */
void app_perror(const char *msg, pj_status_t status);
int init_msg_logger(void);
int msg_logger_set_enabled(pj_bool_t enabled);
void flush_events(unsigned duration);
void report_ival(const char *name, int value, const char *valname, const char *desc);
void report_sval(const char *name, const char* value, const char *valname, const char *desc);
/* Settings. */
extern int log_level;
#endif /* __TEST_H__ */
@@ -0,0 +1,128 @@
/* $Id$ */
/*
* Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
* Copyright (C) 2003-2008 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 "test.h"
#include <pjsip.h>
#include <pjlib.h>
#define THIS_FILE "transport_loop_test.c"
static int datagram_loop_test()
{
enum { LOOP = 8 };
pjsip_transport *loop;
int i, pkt_lost;
pj_sockaddr_in addr;
pj_status_t status;
long ref_cnt;
int rtt[LOOP], min_rtt;
PJ_LOG(3,(THIS_FILE, "testing datagram loop transport"));
pj_sockaddr_in_init(&addr, NULL, 0);
/* Test acquire transport. */
status = pjsip_endpt_acquire_transport( endpt, PJSIP_TRANSPORT_LOOP_DGRAM,
&addr, sizeof(addr), NULL, &loop);
if (status != PJ_SUCCESS) {
app_perror(" error: loop transport is not configured", status);
return -20;
}
/* Get initial reference counter */
ref_cnt = pj_atomic_get(loop->ref_cnt);
/* Test basic transport attributes */
status = generic_transport_test(loop);
if (status != PJ_SUCCESS)
return status;
/* Basic transport's send/receive loopback test. */
for (i=0; i<LOOP; ++i) {
status = transport_send_recv_test(PJSIP_TRANSPORT_LOOP_DGRAM, loop,
"sip:bob@130.0.0.1;transport=loop-dgram",
&rtt[i]);
if (status != 0)
return status;
}
min_rtt = 0xFFFFFFF;
for (i=0; i<LOOP; ++i)
if (rtt[i] < min_rtt) min_rtt = rtt[i];
report_ival("loop-rtt-usec", min_rtt, "usec",
"Best Loopback transport round trip time, in microseconds "
"(time from sending request until response is received. "
"Tests were performed on local machine only)");
/* Multi-threaded round-trip test. */
status = transport_rt_test(PJSIP_TRANSPORT_LOOP_DGRAM, loop,
"sip:bob@130.0.0.1;transport=loop-dgram",
&pkt_lost);
if (status != 0)
return status;
if (pkt_lost != 0) {
PJ_LOG(3,(THIS_FILE, " error: %d packet(s) was lost", pkt_lost));
return -40;
}
/* Put delay. */
PJ_LOG(3,(THIS_FILE," setting network delay to 10 ms"));
pjsip_loop_set_delay(loop, 10);
/* Multi-threaded round-trip test. */
status = transport_rt_test(PJSIP_TRANSPORT_LOOP_DGRAM, loop,
"sip:bob@130.0.0.1;transport=loop-dgram",
&pkt_lost);
if (status != 0)
return status;
if (pkt_lost != 0) {
PJ_LOG(3,(THIS_FILE, " error: %d packet(s) was lost", pkt_lost));
return -50;
}
/* Restore delay. */
pjsip_loop_set_delay(loop, 0);
/* Check reference counter. */
if (pj_atomic_get(loop->ref_cnt) != ref_cnt) {
PJ_LOG(3,(THIS_FILE, " error: ref counter is not %d (%d)",
ref_cnt, pj_atomic_get(loop->ref_cnt)));
return -51;
}
/* Decrement reference. */
pjsip_transport_dec_ref(loop);
return 0;
}
int transport_loop_test(void)
{
int status;
status = datagram_loop_test();
if (status != 0)
return status;
return 0;
}
@@ -0,0 +1,258 @@
/* $Id$ */
/*
* Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
* Copyright (C) 2003-2008 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 "test.h"
#include <pjsip.h>
#include <pjlib.h>
#define THIS_FILE "transport_tcp_test.c"
/*
* TCP transport test.
*/
#if PJ_HAS_TCP
static pj_status_t multi_listener_test(pjsip_tpfactory *factory[],
unsigned num_factory,
pjsip_transport *tp[],
unsigned *num_tp)
{
pj_status_t status;
unsigned i = 0;
pj_str_t s;
pjsip_transport *tcp;
pjsip_tpfactory *tpfactory = NULL;
pj_sockaddr_in rem_addr;
pjsip_tpselector tp_sel;
unsigned ntp = 0;
for (;i<num_factory;++i)
{
/* Start TCP listener on arbitrary port. */
status = pjsip_tcp_transport_start(endpt, NULL, 1, &tpfactory);
if (status != PJ_SUCCESS) {
app_perror(" Error: unable to start TCP transport", status);
return -10;
}
factory[i] = tpfactory;
}
/* Get the last listener address */
status = pj_sockaddr_in_init(&rem_addr, &tpfactory->addr_name.host,
(pj_uint16_t)tpfactory->addr_name.port);
if (status != PJ_SUCCESS) {
app_perror(" Error: possibly invalid TCP address name", status);
return -11;
}
/* Acquire transport without selector. */
status = pjsip_endpt_acquire_transport(endpt, PJSIP_TRANSPORT_TCP,
&rem_addr, sizeof(rem_addr),
NULL, &tcp);
if (status != PJ_SUCCESS || tcp == NULL) {
app_perror(" Error: unable to acquire TCP transport", status);
return -12;
}
tp[ntp++] = tcp;
/* After pjsip_endpt_acquire_transport, TCP transport must have
* reference counter 1.
*/
if (pj_atomic_get(tcp->ref_cnt) != 1)
return -13;
/* Acquire with the same remote address, should return the same tp. */
status = pjsip_endpt_acquire_transport(endpt, PJSIP_TRANSPORT_TCP,
&rem_addr, sizeof(rem_addr),
NULL, &tcp);
if (status != PJ_SUCCESS || tcp == NULL) {
app_perror(" Error: unable to acquire TCP transport", status);
return -14;
}
/* Should return existing transport. */
if (tp[ntp-1] != tcp) {
return -15;
}
/* Using the same TCP transport, it must have reference counter 2.
*/
if (pj_atomic_get(tcp->ref_cnt) != 2)
return -16;
/* Decrease the reference. */
pjsip_transport_dec_ref(tcp);
/* Test basic transport attributes */
status = generic_transport_test(tcp);
if (status != PJ_SUCCESS)
return status;
/* Check again that reference counter is 1. */
if (pj_atomic_get(tcp->ref_cnt) != 1)
return -17;
/* Acquire transport test with selector. */
pj_bzero(&tp_sel, sizeof(tp_sel));
tp_sel.type = PJSIP_TPSELECTOR_LISTENER;
tp_sel.u.listener = factory[num_factory/2];
pj_sockaddr_in_init(&rem_addr, pj_cstr(&s, "1.1.1.1"), 80);
status = pjsip_endpt_acquire_transport(endpt, PJSIP_TRANSPORT_TCP,
&rem_addr, sizeof(rem_addr),
&tp_sel, &tcp);
if (status != PJ_SUCCESS) {
app_perror(" Error: unable to acquire TCP transport", status);
return -18;
}
/* The transport should have the same factory set on the selector. */
if (tcp->factory != factory[num_factory/2])
return -19;
/* The transport should be newly created. */
for (i = 0; i < ntp; ++i) {
if (tp[i] == tcp) {
break;
}
}
if (i != ntp)
return -20;
tp[ntp++] = tcp;
for (i = 0; i<ntp; ++i) {
if (pj_atomic_get(tp[i]->ref_cnt) != 1)
return -21;
}
*num_tp = ntp;
return PJ_SUCCESS;
}
int transport_tcp_test(void)
{
enum { SEND_RECV_LOOP = 8 };
enum { NUM_LISTENER = 4 };
enum { NUM_TP = 8 };
pjsip_tpfactory *tpfactory[NUM_LISTENER];
pjsip_transport *tcp[NUM_TP];
pj_sockaddr_in rem_addr;
pj_status_t status;
char url[PJSIP_MAX_URL_SIZE];
char addr[PJ_INET_ADDRSTRLEN];
int rtt[SEND_RECV_LOOP], min_rtt;
int pkt_lost;
unsigned i;
unsigned num_listener = NUM_LISTENER;
unsigned num_tp = NUM_TP;
status = multi_listener_test(tpfactory, num_listener, tcp, &num_tp);
if (status != PJ_SUCCESS)
return status;
/* Get the last listener address */
status = pj_sockaddr_in_init(&rem_addr, &tpfactory[0]->addr_name.host,
(pj_uint16_t)tpfactory[0]->addr_name.port);
pj_ansi_sprintf(url, "sip:alice@%s:%d;transport=tcp",
pj_inet_ntop2(pj_AF_INET(), &rem_addr.sin_addr, addr,
sizeof(addr)),
pj_ntohs(rem_addr.sin_port));
/* Load test */
if (transport_load_test(url) != 0)
return -60;
/* Basic transport's send/receive loopback test. */
for (i=0; i<SEND_RECV_LOOP; ++i) {
status = transport_send_recv_test(PJSIP_TRANSPORT_TCP, tcp[0], url,
&rtt[i]);
if (status != 0) {
for (i = 0; i < num_tp ; ++i) {
pjsip_transport_dec_ref(tcp[i]);
}
flush_events(500);
return -72;
}
}
min_rtt = 0xFFFFFFF;
for (i=0; i<SEND_RECV_LOOP; ++i)
if (rtt[i] < min_rtt) min_rtt = rtt[i];
report_ival("tcp-rtt-usec", min_rtt, "usec",
"Best TCP transport round trip time, in microseconds "
"(time from sending request until response is received. "
"Tests were performed on local machine only, and after "
"TCP socket has been established by previous test)");
/* Multi-threaded round-trip test. */
status = transport_rt_test(PJSIP_TRANSPORT_TCP, tcp[0], url, &pkt_lost);
if (status != 0) {
for (i = 0; i < num_tp ; ++i) {
pjsip_transport_dec_ref(tcp[i]);
}
return status;
}
if (pkt_lost != 0)
PJ_LOG(3,(THIS_FILE, " note: %d packet(s) was lost", pkt_lost));
/* Check again that reference counter is still 1. */
for (i = 0; i < num_tp; ++i) {
if (pj_atomic_get(tcp[i]->ref_cnt) != 1)
return -80;
}
for (i = 0; i < num_tp; ++i) {
/* Destroy this transport. */
pjsip_transport_dec_ref(tcp[i]);
/* Force destroy this transport. */
status = pjsip_transport_destroy(tcp[i]);
if (status != PJ_SUCCESS)
return -90;
}
for (i = 0; i < num_listener; ++i) {
/* Unregister factory */
status = pjsip_tpmgr_unregister_tpfactory(pjsip_endpt_get_tpmgr(endpt),
tpfactory[i]);
if (status != PJ_SUCCESS)
return -95;
}
/* Flush events. */
PJ_LOG(3,(THIS_FILE, " Flushing events, 1 second..."));
flush_events(1000);
/* Done */
return 0;
}
#else /* PJ_HAS_TCP */
int transport_tcp_test(void)
{
return 0;
}
#endif /* PJ_HAS_TCP */
@@ -0,0 +1,773 @@
/* $Id$ */
/*
* Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
* Copyright (C) 2003-2008 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 "test.h"
#include <pjsip.h>
#include <pjlib.h>
#define THIS_FILE "transport_test.c"
///////////////////////////////////////////////////////////////////////////////
/*
* Generic testing for transport, to make sure that basic
* attributes have been initialized properly.
*/
int generic_transport_test(pjsip_transport *tp)
{
PJ_LOG(3,(THIS_FILE, " structure test..."));
/* Check that local address name is valid. */
{
pj_in_addr addr;
if (pj_inet_pton(pj_AF_INET(), &tp->local_name.host,
&addr) == PJ_SUCCESS)
{
if (addr.s_addr==PJ_INADDR_ANY || addr.s_addr==PJ_INADDR_NONE) {
PJ_LOG(3,(THIS_FILE, " Error: invalid address name"));
return -420;
}
} else {
/* It's okay. local_name.host may be a hostname instead of
* IP address.
*/
}
}
/* Check that port is valid. */
if (tp->local_name.port <= 0) {
return -430;
}
/* Check length of address (for now we only check against sockaddr_in). */
if (tp->addr_len != sizeof(pj_sockaddr_in))
return -440;
/* Check type. */
if (tp->key.type == PJSIP_TRANSPORT_UNSPECIFIED)
return -450;
/* That's it. */
return PJ_SUCCESS;
}
///////////////////////////////////////////////////////////////////////////////
/*
* Send/receive test.
*
* This test sends a request to loopback address; as soon as request is
* received, response will be sent, and time is recorded.
*
* The main purpose is to test that the basic transport functionalities works,
* before we continue with more complicated tests.
*/
#define FROM_HDR "Bob <sip:bob@example.com>"
#define CONTACT_HDR "Bob <sip:bob@127.0.0.1>"
#define CALL_ID_HDR "SendRecv-Test"
#define CSEQ_VALUE 100
#define BODY "Hello World!"
static pj_bool_t my_on_rx_request(pjsip_rx_data *rdata);
static pj_bool_t my_on_rx_response(pjsip_rx_data *rdata);
/* Flag to indicate message has been received
* (or failed to send)
*/
#define NO_STATUS -2
static int send_status = NO_STATUS;
static int recv_status = NO_STATUS;
static pj_timestamp my_send_time, my_recv_time;
/* Module to receive messages for this test. */
static pjsip_module my_module =
{
NULL, NULL, /* prev and next */
{ "Transport-Test", 14}, /* Name. */
-1, /* Id */
PJSIP_MOD_PRIORITY_TSX_LAYER-1, /* Priority */
NULL, /* load() */
NULL, /* start() */
NULL, /* stop() */
NULL, /* unload() */
&my_on_rx_request, /* on_rx_request() */
&my_on_rx_response, /* on_rx_response() */
NULL, /* on_tsx_state() */
};
static pj_bool_t my_on_rx_request(pjsip_rx_data *rdata)
{
/* Check that this is our request. */
if (pj_strcmp2(&rdata->msg_info.cid->id, CALL_ID_HDR) == 0) {
/* It is! */
/* Send response. */
pjsip_tx_data *tdata;
pjsip_response_addr res_addr;
pj_status_t status;
status = pjsip_endpt_create_response( endpt, rdata, 200, NULL, &tdata);
if (status != PJ_SUCCESS) {
recv_status = status;
return PJ_TRUE;
}
status = pjsip_get_response_addr( tdata->pool, rdata, &res_addr);
if (status != PJ_SUCCESS) {
recv_status = status;
pjsip_tx_data_dec_ref(tdata);
return PJ_TRUE;
}
status = pjsip_endpt_send_response( endpt, &res_addr, tdata, NULL, NULL);
if (status != PJ_SUCCESS) {
recv_status = status;
pjsip_tx_data_dec_ref(tdata);
return PJ_TRUE;
}
return PJ_TRUE;
}
/* Not ours. */
return PJ_FALSE;
}
static pj_bool_t my_on_rx_response(pjsip_rx_data *rdata)
{
if (pj_strcmp2(&rdata->msg_info.cid->id, CALL_ID_HDR) == 0) {
pj_get_timestamp(&my_recv_time);
recv_status = PJ_SUCCESS;
return PJ_TRUE;
}
return PJ_FALSE;
}
/* Transport callback. */
static void send_msg_callback(pjsip_send_state *stateless_data,
pj_ssize_t sent, pj_bool_t *cont)
{
PJ_UNUSED_ARG(stateless_data);
if (sent < 1) {
/* Obtain the error code. */
send_status = (int)-sent;
} else {
send_status = PJ_SUCCESS;
}
/* Don't want to continue. */
*cont = PJ_FALSE;
}
/* Test that we receive loopback message. */
int transport_send_recv_test( pjsip_transport_type_e tp_type,
pjsip_transport *ref_tp,
char *target_url,
int *p_usec_rtt)
{
pj_bool_t msg_log_enabled;
pj_status_t status;
pj_str_t target, from, to, contact, call_id, body;
pjsip_method method;
pjsip_tx_data *tdata;
pj_time_val timeout;
PJ_UNUSED_ARG(tp_type);
PJ_UNUSED_ARG(ref_tp);
PJ_LOG(3,(THIS_FILE, " single message round-trip test..."));
/* Register out test module to receive the message (if necessary). */
if (my_module.id == -1) {
status = pjsip_endpt_register_module( endpt, &my_module );
if (status != PJ_SUCCESS) {
app_perror(" error: unable to register module", status);
return -500;
}
}
/* Disable message logging. */
msg_log_enabled = msg_logger_set_enabled(0);
/* Create a request message. */
target = pj_str(target_url);
from = pj_str(FROM_HDR);
to = pj_str(target_url);
contact = pj_str(CONTACT_HDR);
call_id = pj_str(CALL_ID_HDR);
body = pj_str(BODY);
pjsip_method_set(&method, PJSIP_OPTIONS_METHOD);
status = pjsip_endpt_create_request( endpt, &method, &target, &from, &to,
&contact, &call_id, CSEQ_VALUE,
&body, &tdata );
if (status != PJ_SUCCESS) {
app_perror(" error: unable to create request", status);
return -510;
}
/* Reset statuses */
send_status = recv_status = NO_STATUS;
/* Start time. */
pj_get_timestamp(&my_send_time);
/* Send the message (statelessly). */
PJ_LOG(5,(THIS_FILE, "Sending request to %.*s",
(int)target.slen, target.ptr));
status = pjsip_endpt_send_request_stateless( endpt, tdata, NULL,
&send_msg_callback);
if (status != PJ_SUCCESS) {
/* Immediate error! */
pjsip_tx_data_dec_ref(tdata);
send_status = status;
}
/* Set the timeout (2 seconds from now) */
pj_gettimeofday(&timeout);
timeout.sec += 2;
/* Loop handling events until we get status */
do {
pj_time_val now;
pj_time_val poll_interval = { 0, 10 };
pj_gettimeofday(&now);
if (PJ_TIME_VAL_GTE(now, timeout)) {
PJ_LOG(3,(THIS_FILE, " error: timeout in send/recv test"));
status = -540;
goto on_return;
}
if (send_status!=NO_STATUS && send_status!=PJ_SUCCESS) {
app_perror(" error sending message", send_status);
status = -550;
goto on_return;
}
if (recv_status!=NO_STATUS && recv_status!=PJ_SUCCESS) {
app_perror(" error receiving message", recv_status);
status = -560;
goto on_return;
}
if (send_status!=NO_STATUS && recv_status!=NO_STATUS) {
/* Success! */
break;
}
pjsip_endpt_handle_events(endpt, &poll_interval);
} while (1);
if (status == PJ_SUCCESS) {
unsigned usec_rt;
usec_rt = pj_elapsed_usec(&my_send_time, &my_recv_time);
PJ_LOG(3,(THIS_FILE, " round-trip = %d usec", usec_rt));
*p_usec_rtt = usec_rt;
}
/* Restore message logging. */
msg_logger_set_enabled(msg_log_enabled);
status = PJ_SUCCESS;
on_return:
return status;
}
///////////////////////////////////////////////////////////////////////////////
/*
* Multithreaded round-trip test
*
* This test will spawn multiple threads, each of them send a request. As soon
* as request is received, response will be sent, and time is recorded.
*
* The main purpose of this test is to ensure there's no crash when multiple
* threads are sending/receiving messages.
*
*/
static pj_bool_t rt_on_rx_request(pjsip_rx_data *rdata);
static pj_bool_t rt_on_rx_response(pjsip_rx_data *rdata);
static pjsip_module rt_module =
{
NULL, NULL, /* prev and next */
{ "Transport-RT-Test", 17}, /* Name. */
-1, /* Id */
PJSIP_MOD_PRIORITY_TSX_LAYER-1, /* Priority */
NULL, /* load() */
NULL, /* start() */
NULL, /* stop() */
NULL, /* unload() */
&rt_on_rx_request, /* on_rx_request() */
&rt_on_rx_response, /* on_rx_response() */
NULL, /* tsx_handler() */
};
static struct
{
pj_thread_t *thread;
pj_timestamp send_time;
pj_timestamp total_rt_time;
int sent_request_count, recv_response_count;
pj_str_t call_id;
pj_timer_entry timeout_timer;
pj_timer_entry tx_timer;
pj_mutex_t *mutex;
} rt_test_data[16];
static char rt_target_uri[64];
static pj_bool_t rt_stop;
static pj_str_t rt_call_id;
static pj_bool_t rt_on_rx_request(pjsip_rx_data *rdata)
{
if (!pj_strncmp(&rdata->msg_info.cid->id, &rt_call_id, rt_call_id.slen)) {
pjsip_tx_data *tdata;
pjsip_response_addr res_addr;
pj_status_t status;
status = pjsip_endpt_create_response( endpt, rdata, 200, NULL, &tdata);
if (status != PJ_SUCCESS) {
app_perror(" error creating response", status);
return PJ_TRUE;
}
status = pjsip_get_response_addr( tdata->pool, rdata, &res_addr);
if (status != PJ_SUCCESS) {
app_perror(" error in get response address", status);
pjsip_tx_data_dec_ref(tdata);
return PJ_TRUE;
}
status = pjsip_endpt_send_response( endpt, &res_addr, tdata, NULL, NULL);
if (status != PJ_SUCCESS) {
app_perror(" error sending response", status);
pjsip_tx_data_dec_ref(tdata);
return PJ_TRUE;
}
return PJ_TRUE;
}
return PJ_FALSE;
}
static pj_status_t rt_send_request(int thread_id)
{
pj_status_t status;
pj_str_t target, from, to, contact, call_id;
pjsip_tx_data *tdata;
pj_time_val timeout_delay;
pj_mutex_lock(rt_test_data[thread_id].mutex);
/* Create a request message. */
target = pj_str(rt_target_uri);
from = pj_str(FROM_HDR);
to = pj_str(rt_target_uri);
contact = pj_str(CONTACT_HDR);
call_id = rt_test_data[thread_id].call_id;
status = pjsip_endpt_create_request( endpt, &pjsip_options_method,
&target, &from, &to,
&contact, &call_id, -1,
NULL, &tdata );
if (status != PJ_SUCCESS) {
app_perror(" error: unable to create request", status);
pj_mutex_unlock(rt_test_data[thread_id].mutex);
return -610;
}
/* Start time. */
pj_get_timestamp(&rt_test_data[thread_id].send_time);
/* Send the message (statelessly). */
status = pjsip_endpt_send_request_stateless( endpt, tdata, NULL, NULL);
if (status != PJ_SUCCESS) {
/* Immediate error! */
app_perror(" error: send request", status);
pjsip_tx_data_dec_ref(tdata);
pj_mutex_unlock(rt_test_data[thread_id].mutex);
return -620;
}
/* Update counter. */
rt_test_data[thread_id].sent_request_count++;
/* Set timeout timer. */
if (rt_test_data[thread_id].timeout_timer.user_data != NULL) {
pjsip_endpt_cancel_timer(endpt, &rt_test_data[thread_id].timeout_timer);
}
timeout_delay.sec = 100; timeout_delay.msec = 0;
rt_test_data[thread_id].timeout_timer.user_data = (void*)(pj_ssize_t)1;
pjsip_endpt_schedule_timer(endpt, &rt_test_data[thread_id].timeout_timer,
&timeout_delay);
pj_mutex_unlock(rt_test_data[thread_id].mutex);
return PJ_SUCCESS;
}
static pj_bool_t rt_on_rx_response(pjsip_rx_data *rdata)
{
if (!pj_strncmp(&rdata->msg_info.cid->id, &rt_call_id, rt_call_id.slen)) {
char *pos = pj_strchr(&rdata->msg_info.cid->id, '/')+1;
int thread_id = (*pos - '0');
pj_timestamp recv_time;
pj_mutex_lock(rt_test_data[thread_id].mutex);
/* Stop timer. */
pjsip_endpt_cancel_timer(endpt, &rt_test_data[thread_id].timeout_timer);
/* Update counter and end-time. */
rt_test_data[thread_id].recv_response_count++;
pj_get_timestamp(&recv_time);
pj_sub_timestamp(&recv_time, &rt_test_data[thread_id].send_time);
pj_add_timestamp(&rt_test_data[thread_id].total_rt_time, &recv_time);
if (!rt_stop) {
pj_time_val tx_delay = { 0, 0 };
pj_assert(rt_test_data[thread_id].tx_timer.user_data == NULL);
rt_test_data[thread_id].tx_timer.user_data = (void*)(pj_ssize_t)1;
pjsip_endpt_schedule_timer(endpt, &rt_test_data[thread_id].tx_timer,
&tx_delay);
}
pj_mutex_unlock(rt_test_data[thread_id].mutex);
return PJ_TRUE;
}
return PJ_FALSE;
}
static void rt_timeout_timer( pj_timer_heap_t *timer_heap,
struct pj_timer_entry *entry )
{
pj_mutex_lock(rt_test_data[entry->id].mutex);
PJ_UNUSED_ARG(timer_heap);
PJ_LOG(3,(THIS_FILE, " timeout waiting for response"));
rt_test_data[entry->id].timeout_timer.user_data = NULL;
if (rt_test_data[entry->id].tx_timer.user_data == NULL) {
pj_time_val delay = { 0, 0 };
rt_test_data[entry->id].tx_timer.user_data = (void*)(pj_ssize_t)1;
pjsip_endpt_schedule_timer(endpt, &rt_test_data[entry->id].tx_timer,
&delay);
}
pj_mutex_unlock(rt_test_data[entry->id].mutex);
}
static void rt_tx_timer( pj_timer_heap_t *timer_heap,
struct pj_timer_entry *entry )
{
pj_mutex_lock(rt_test_data[entry->id].mutex);
PJ_UNUSED_ARG(timer_heap);
pj_assert(rt_test_data[entry->id].tx_timer.user_data != NULL);
rt_test_data[entry->id].tx_timer.user_data = NULL;
rt_send_request(entry->id);
pj_mutex_unlock(rt_test_data[entry->id].mutex);
}
static int rt_worker_thread(void *arg)
{
int i;
pj_time_val poll_delay = { 0, 10 };
PJ_UNUSED_ARG(arg);
/* Sleep to allow main threads to run. */
pj_thread_sleep(10);
while (!rt_stop) {
pjsip_endpt_handle_events(endpt, &poll_delay);
}
/* Exhaust responses. */
for (i=0; i<100; ++i)
pjsip_endpt_handle_events(endpt, &poll_delay);
return 0;
}
int transport_rt_test( pjsip_transport_type_e tp_type,
pjsip_transport *ref_tp,
char *target_url,
int *lost)
{
enum { THREADS = 4, INTERVAL = 10 };
int i;
pj_status_t status;
pj_pool_t *pool;
pj_bool_t logger_enabled;
pj_timestamp zero_time, total_time;
unsigned usec_rt;
unsigned total_sent;
unsigned total_recv;
PJ_UNUSED_ARG(tp_type);
PJ_UNUSED_ARG(ref_tp);
PJ_LOG(3,(THIS_FILE, " multithreaded round-trip test (%d threads)...",
THREADS));
PJ_LOG(3,(THIS_FILE, " this will take approx %d seconds, please wait..",
INTERVAL));
/* Make sure msg logger is disabled. */
logger_enabled = msg_logger_set_enabled(0);
/* Register module (if not yet registered) */
if (rt_module.id == -1) {
status = pjsip_endpt_register_module( endpt, &rt_module );
if (status != PJ_SUCCESS) {
app_perror(" error: unable to register module", status);
return -600;
}
}
/* Create pool for this test. */
pool = pjsip_endpt_create_pool(endpt, NULL, 4000, 4000);
if (!pool)
return -610;
/* Initialize static test data. */
pj_ansi_strcpy(rt_target_uri, target_url);
rt_call_id = pj_str("RT-Call-Id/");
rt_stop = PJ_FALSE;
/* Initialize thread data. */
for (i=0; i<THREADS; ++i) {
char buf[1];
pj_str_t str_id;
pj_strset(&str_id, buf, 1);
pj_bzero(&rt_test_data[i], sizeof(rt_test_data[i]));
/* Init timer entry */
rt_test_data[i].tx_timer.id = i;
rt_test_data[i].tx_timer.cb = &rt_tx_timer;
rt_test_data[i].timeout_timer.id = i;
rt_test_data[i].timeout_timer.cb = &rt_timeout_timer;
/* Generate Call-ID for each thread. */
rt_test_data[i].call_id.ptr = (char*) pj_pool_alloc(pool, rt_call_id.slen+1);
pj_strcpy(&rt_test_data[i].call_id, &rt_call_id);
buf[0] = '0' + (char)i;
pj_strcat(&rt_test_data[i].call_id, &str_id);
/* Init mutex. */
status = pj_mutex_create_recursive(pool, "rt", &rt_test_data[i].mutex);
if (status != PJ_SUCCESS) {
app_perror(" error: unable to create mutex", status);
return -615;
}
/* Create thread, suspended. */
status = pj_thread_create(pool, "rttest%p", &rt_worker_thread,
(void*)(pj_ssize_t)i, 0,
PJ_THREAD_SUSPENDED, &rt_test_data[i].thread);
if (status != PJ_SUCCESS) {
app_perror(" error: unable to create thread", status);
return -620;
}
}
/* Start threads! */
for (i=0; i<THREADS; ++i) {
pj_time_val delay = {0,0};
pj_thread_resume(rt_test_data[i].thread);
/* Schedule first message transmissions. */
rt_test_data[i].tx_timer.user_data = (void*)(pj_ssize_t)1;
pjsip_endpt_schedule_timer(endpt, &rt_test_data[i].tx_timer, &delay);
}
/* Sleep for some time. */
pj_thread_sleep(INTERVAL * 1000);
/* Signal thread to stop. */
rt_stop = PJ_TRUE;
/* Wait threads to complete. */
for (i=0; i<THREADS; ++i) {
pj_thread_join(rt_test_data[i].thread);
pj_thread_destroy(rt_test_data[i].thread);
}
/* Destroy rt_test_data */
for (i=0; i<THREADS; ++i) {
pj_mutex_destroy(rt_test_data[i].mutex);
pjsip_endpt_cancel_timer(endpt, &rt_test_data[i].timeout_timer);
}
/* Gather statistics. */
pj_bzero(&total_time, sizeof(total_time));
pj_bzero(&zero_time, sizeof(zero_time));
usec_rt = total_sent = total_recv = 0;
for (i=0; i<THREADS; ++i) {
total_sent += rt_test_data[i].sent_request_count;
total_recv += rt_test_data[i].recv_response_count;
pj_add_timestamp(&total_time, &rt_test_data[i].total_rt_time);
}
/* Display statistics. */
if (total_recv)
total_time.u64 = total_time.u64/total_recv;
else
total_time.u64 = 0;
usec_rt = pj_elapsed_usec(&zero_time, &total_time);
PJ_LOG(3,(THIS_FILE, " done."));
PJ_LOG(3,(THIS_FILE, " total %d messages sent", total_sent));
PJ_LOG(3,(THIS_FILE, " average round-trip=%d usec", usec_rt));
pjsip_endpt_release_pool(endpt, pool);
*lost = total_sent-total_recv;
/* Flush events. */
flush_events(500);
/* Restore msg logger. */
msg_logger_set_enabled(logger_enabled);
return 0;
}
///////////////////////////////////////////////////////////////////////////////
/*
* Transport load testing
*/
static pj_bool_t load_on_rx_request(pjsip_rx_data *rdata);
static struct mod_load_test
{
pjsip_module mod;
pj_int32_t next_seq;
pj_bool_t err;
} mod_load =
{
{
NULL, NULL, /* prev and next */
{ "mod-load-test", 13}, /* Name. */
-1, /* Id */
PJSIP_MOD_PRIORITY_TSX_LAYER-1, /* Priority */
NULL, /* load() */
NULL, /* start() */
NULL, /* stop() */
NULL, /* unload() */
&load_on_rx_request, /* on_rx_request() */
NULL, /* on_rx_response() */
NULL, /* tsx_handler() */
}
};
static pj_bool_t load_on_rx_request(pjsip_rx_data *rdata)
{
if (rdata->msg_info.cseq->cseq != mod_load.next_seq) {
PJ_LOG(1,("THIS_FILE", " err: expecting cseq %u, got %u",
mod_load.next_seq, rdata->msg_info.cseq->cseq));
mod_load.err = PJ_TRUE;
mod_load.next_seq = rdata->msg_info.cseq->cseq + 1;
} else
mod_load.next_seq++;
return PJ_TRUE;
}
int transport_load_test(char *target_url)
{
enum { COUNT = 2000 };
unsigned i;
pj_status_t status = PJ_SUCCESS;
/* exhaust packets */
do {
pj_time_val delay = {1, 0};
i = 0;
pjsip_endpt_handle_events2(endpt, &delay, &i);
} while (i != 0);
PJ_LOG(3,(THIS_FILE, " transport load test..."));
if (mod_load.mod.id == -1) {
status = pjsip_endpt_register_module( endpt, &mod_load.mod);
if (status != PJ_SUCCESS) {
app_perror("error registering module", status);
return -1;
}
}
mod_load.err = PJ_FALSE;
mod_load.next_seq = 0;
for (i=0; i<COUNT && !mod_load.err; ++i) {
pj_str_t target, from, call_id;
pjsip_tx_data *tdata;
target = pj_str(target_url);
from = pj_str("<sip:user@host>");
call_id = pj_str("thecallid");
status = pjsip_endpt_create_request(endpt, &pjsip_invite_method,
&target, &from,
&target, &from, &call_id,
i, NULL, &tdata );
if (status != PJ_SUCCESS) {
app_perror("error creating request", status);
goto on_return;
}
status = pjsip_endpt_send_request_stateless(endpt, tdata, NULL, NULL);
if (status != PJ_SUCCESS) {
app_perror("error sending request", status);
goto on_return;
}
}
do {
pj_time_val delay = {1, 0};
i = 0;
pjsip_endpt_handle_events2(endpt, &delay, &i);
} while (i != 0);
if (mod_load.next_seq != COUNT) {
PJ_LOG(1,("THIS_FILE", " err: expecting %u msg, got only %u",
COUNT, mod_load.next_seq));
status = -2;
goto on_return;
}
on_return:
if (mod_load.mod.id != -1) {
pjsip_endpt_unregister_module( endpt, &mod_load.mod);
mod_load.mod.id = -1;
}
if (status != PJ_SUCCESS || mod_load.err) {
return -2;
}
PJ_LOG(3,(THIS_FILE, " success"));
return 0;
}
@@ -0,0 +1,178 @@
/* $Id$ */
/*
* Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
* Copyright (C) 2003-2008 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 "test.h"
#include <pjsip.h>
#include <pjlib.h>
#define THIS_FILE "transport_udp_test.c"
static pj_status_t multi_transport_test(pjsip_transport *tp[], unsigned num_tp)
{
pj_status_t status;
unsigned i = 0;
pj_str_t s;
pjsip_transport *udp_tp;
pj_sockaddr_in rem_addr;
pjsip_tpselector tp_sel;
for (;i<num_tp;++i)
{
pj_sockaddr_in addr;
pj_sockaddr_in_init(&addr, NULL, (pj_uint16_t)(TEST_UDP_PORT+i));
/* Start UDP transport. */
status = pjsip_udp_transport_start( endpt, &addr, NULL, 1, &udp_tp);
if (status != PJ_SUCCESS) {
app_perror(" Error: unable to start UDP transport", status);
return -110;
}
/* UDP transport must have initial reference counter set to 1. */
if (pj_atomic_get(udp_tp->ref_cnt) != 1)
return -120;
/* Test basic transport attributes */
status = generic_transport_test(udp_tp);
if (status != PJ_SUCCESS)
return status;
tp[i] = udp_tp;
}
for (i = 0; i < num_tp; ++i) {
udp_tp = tp[i];
if (pj_atomic_get(udp_tp->ref_cnt) != 1)
return -130;
}
/* Acquire transport test without selector. */
pj_sockaddr_in_init(&rem_addr, pj_cstr(&s, "1.1.1.1"), 80);
status = pjsip_endpt_acquire_transport(endpt, PJSIP_TRANSPORT_UDP,
&rem_addr, sizeof(rem_addr),
NULL, &udp_tp);
if (status != PJ_SUCCESS)
return -140;
for (i = 0; i < num_tp; ++i) {
if (udp_tp == tp[i]) {
break;
}
}
if (i == num_tp)
return -150;
pjsip_transport_dec_ref(udp_tp);
if (pj_atomic_get(udp_tp->ref_cnt) != 1)
return -160;
/* Acquire transport test with selector. */
pj_bzero(&tp_sel, sizeof(tp_sel));
tp_sel.type = PJSIP_TPSELECTOR_TRANSPORT;
tp_sel.u.transport = tp[num_tp-1];
pj_sockaddr_in_init(&rem_addr, pj_cstr(&s, "1.1.1.1"), 80);
status = pjsip_endpt_acquire_transport(endpt, PJSIP_TRANSPORT_UDP,
&rem_addr, sizeof(rem_addr),
&tp_sel, &udp_tp);
if (status != PJ_SUCCESS)
return -170;
if (udp_tp != tp[num_tp-1])
return -180;
pjsip_transport_dec_ref(udp_tp);
if (pj_atomic_get(udp_tp->ref_cnt) != 1)
return -190;
return PJ_SUCCESS;
}
/*
* UDP transport test.
*/
int transport_udp_test(void)
{
enum { SEND_RECV_LOOP = 8 };
enum { NUM_TP = 4 };
pjsip_transport *tp[NUM_TP], *udp_tp;
pj_sockaddr_in rem_addr;
pj_str_t s;
pj_status_t status;
int rtt[SEND_RECV_LOOP], min_rtt;
int i, pkt_lost;
status = multi_transport_test(&tp[0], NUM_TP);
if (status != PJ_SUCCESS)
return status;
/* Basic transport's send/receive loopback test. */
pj_sockaddr_in_init(&rem_addr, pj_cstr(&s, "127.0.0.1"), TEST_UDP_PORT);
for (i=0; i<SEND_RECV_LOOP; ++i) {
status = transport_send_recv_test(PJSIP_TRANSPORT_UDP, tp[0],
"sip:alice@127.0.0.1:"TEST_UDP_PORT_STR,
&rtt[i]);
if (status != 0)
return status;
}
min_rtt = 0xFFFFFFF;
for (i=0; i<SEND_RECV_LOOP; ++i)
if (rtt[i] < min_rtt) min_rtt = rtt[i];
report_ival("udp-rtt-usec", min_rtt, "usec",
"Best UDP transport round trip time, in microseconds "
"(time from sending request until response is received. "
"Tests were performed on local machine only)");
/* Multi-threaded round-trip test. */
status = transport_rt_test(PJSIP_TRANSPORT_UDP, tp[0],
"sip:alice@127.0.0.1:"TEST_UDP_PORT_STR,
&pkt_lost);
if (status != 0)
return status;
if (pkt_lost != 0)
PJ_LOG(3,(THIS_FILE, " note: %d packet(s) was lost", pkt_lost));
for (i = 0; i < NUM_TP; ++i) {
udp_tp = tp[i];
/* Check again that reference counter is 1. */
if (pj_atomic_get(udp_tp->ref_cnt) != 1)
return -80;
/* Destroy this transport. */
pjsip_transport_dec_ref(udp_tp);
status = pjsip_transport_destroy(udp_tp);
if (status != PJ_SUCCESS)
return -90;
}
/* Flush events. */
PJ_LOG(3,(THIS_FILE, " Flushing events, 1 second..."));
flush_events(1000);
/* Done */
return 0;
}
@@ -0,0 +1,342 @@
/* $Id$ */
/*
* Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
* Copyright (C) 2003-2008 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 "test.h"
#include <pjsip.h>
#include <pjlib.h>
#define THIS_FILE "tsx_basic_test.c"
static char TARGET_URI[PJSIP_MAX_URL_SIZE];
static char FROM_URI[PJSIP_MAX_URL_SIZE];
/* Test transaction layer. */
static int tsx_layer_test(void)
{
pj_str_t target, from, tsx_key;
pjsip_tx_data *tdata;
pjsip_transaction *tsx, *found;
pj_status_t status;
PJ_LOG(3,(THIS_FILE, " transaction layer test"));
target = pj_str(TARGET_URI);
from = pj_str(FROM_URI);
status = pjsip_endpt_create_request(endpt, &pjsip_invite_method, &target,
&from, &target, NULL, NULL, -1, NULL,
&tdata);
if (status != PJ_SUCCESS) {
app_perror(" error: unable to create request", status);
return -110;
}
status = pjsip_tsx_create_uac(NULL, tdata, &tsx);
if (status != PJ_SUCCESS) {
app_perror(" error: unable to create transaction", status);
return -120;
}
pj_strdup(tdata->pool, &tsx_key, &tsx->transaction_key);
found = pjsip_tsx_layer_find_tsx(&tsx_key, PJ_FALSE);
if (found != tsx) {
return -130;
}
pjsip_tsx_terminate(tsx, PJSIP_SC_REQUEST_TERMINATED);
flush_events(500);
if (pjsip_tx_data_dec_ref(tdata) != PJSIP_EBUFDESTROYED) {
return -140;
}
return 0;
}
/* Double terminate test. */
static int double_terminate(void)
{
pj_str_t target, from, tsx_key;
pjsip_tx_data *tdata;
pjsip_transaction *tsx;
pj_status_t status;
PJ_LOG(3,(THIS_FILE, " double terminate test"));
target = pj_str(TARGET_URI);
from = pj_str(FROM_URI);
/* Create request. */
status = pjsip_endpt_create_request(endpt, &pjsip_invite_method, &target,
&from, &target, NULL, NULL, -1, NULL,
&tdata);
if (status != PJ_SUCCESS) {
app_perror(" error: unable to create request", status);
return -10;
}
/* Create transaction. */
status = pjsip_tsx_create_uac(NULL, tdata, &tsx);
if (status != PJ_SUCCESS) {
app_perror(" error: unable to create transaction", status);
return -20;
}
/* Save transaction key for later. */
pj_strdup_with_null(tdata->pool, &tsx_key, &tsx->transaction_key);
/* Add reference to transmit buffer (tsx_send_msg() will dec txdata). */
pjsip_tx_data_add_ref(tdata);
/* Send message to start timeout timer. */
status = pjsip_tsx_send_msg(tsx, NULL);
/* Terminate transaction. */
status = pjsip_tsx_terminate(tsx, PJSIP_SC_REQUEST_TERMINATED);
if (status != PJ_SUCCESS) {
app_perror(" error: unable to terminate transaction", status);
return -30;
}
tsx = pjsip_tsx_layer_find_tsx(&tsx_key, PJ_TRUE);
if (tsx) {
/* Terminate transaction again. */
pjsip_tsx_terminate(tsx, PJSIP_SC_REQUEST_TERMINATED);
if (status != PJ_SUCCESS) {
app_perror(" error: unable to terminate transaction", status);
return -40;
}
pj_grp_lock_release(tsx->grp_lock);
}
flush_events(500);
if (pjsip_tx_data_dec_ref(tdata) != PJSIP_EBUFDESTROYED) {
return -50;
}
return PJ_SUCCESS;
}
int tsx_basic_test(struct tsx_test_param *param)
{
int status;
pj_ansi_sprintf(TARGET_URI, "sip:bob@127.0.0.1:%d;transport=%s",
param->port, param->tp_type);
pj_ansi_sprintf(FROM_URI, "sip:alice@127.0.0.1:%d;transport=%s",
param->port, param->tp_type);
status = tsx_layer_test();
if (status != 0)
return status;
status = double_terminate();
if (status != 0)
return status;
return 0;
}
/**************************************************************************/
struct tsx_test_state
{
pj_size_t pool_cnt;
};
static void save_tsx_test_state(struct tsx_test_state *st)
{
st->pool_cnt = caching_pool.used_count;
}
static pj_status_t check_tsx_test_state(struct tsx_test_state *st)
{
if (caching_pool.used_count > st->pool_cnt)
return -1;
return 0;
}
static void destroy_endpt()
{
pjsip_endpt_destroy(endpt);
endpt = NULL;
}
static pj_status_t init_endpt()
{
pj_str_t ns = { "10.187.27.172", 13}; /* just a random, unreachable IP */
pj_dns_resolver *resolver;
pj_status_t rc;
rc = pjsip_endpt_create(&caching_pool.factory, "endpt", &endpt);
if (rc != PJ_SUCCESS) {
app_perror("pjsip_endpt_create", rc);
return rc;
}
/* Start transaction layer module. */
rc = pjsip_tsx_layer_init_module(endpt);
if (rc != PJ_SUCCESS) {
app_perror("tsx_layer_init", rc);
return rc;
}
rc = pjsip_udp_transport_start(endpt, NULL, NULL, 1, NULL);
if (rc != PJ_SUCCESS) {
app_perror("udp init", rc);
return rc;
}
rc = pjsip_tcp_transport_start(endpt, NULL, 1, NULL);
if (rc != PJ_SUCCESS) {
app_perror("tcp init", rc);
return rc;
}
rc = pjsip_endpt_create_resolver(endpt, &resolver);
if (rc != PJ_SUCCESS) {
app_perror("create resolver", rc);
return rc;
}
pj_dns_resolver_set_ns(resolver, 1, &ns, NULL);
rc = pjsip_endpt_set_resolver(endpt, resolver);
if (rc != PJ_SUCCESS) {
app_perror("set resolver", rc);
return rc;
}
return PJ_SUCCESS;
}
static int tsx_create_and_send_req(void *arg)
{
pj_str_t dst_uri = pj_str((char*)arg);
pj_str_t from_uri = pj_str((char*)"<sip:user@host>");
pjsip_tx_data *tdata;
pj_status_t status;
status = pjsip_endpt_create_request(endpt, &pjsip_options_method,
&dst_uri, &from_uri, &dst_uri,
NULL, NULL, -1, NULL,
&tdata);
if (status != PJ_SUCCESS)
return status;
status = pjsip_endpt_send_request(endpt, tdata, -1, NULL, NULL);
if (status != PJ_SUCCESS)
return status;
return PJ_SUCCESS;
}
int tsx_destroy_test()
{
struct tsx_test_state state;
struct test_desc
{
const char *title;
int (*func)(void*);
void *arg;
int sleep_before_unload;
int sleep_after_unload;
} test_entries[] =
{
{
"normal unable to resolve",
&tsx_create_and_send_req,
"sip:user@somehost",
20000,
1
},
{
"resolve and destroy, wait",
&tsx_create_and_send_req,
"sip:user@somehost",
1,
20000
},
{
"tcp connect and destroy",
&tsx_create_and_send_req,
"sip:user@10.125.36.63:58517;transport=tcp",
60000,
1000
},
{
"tcp connect and destroy 2",
&tsx_create_and_send_req,
"sip:user@10.125.36.63:58517;transport=tcp",
1,
60000
},
};
int rc;
unsigned i;
const int INDENT = 2;
pj_log_add_indent(INDENT);
destroy_endpt();
for (i=0; i<PJ_ARRAY_SIZE(test_entries); ++i) {
struct test_desc *td = &test_entries[i];
PJ_LOG(3,(THIS_FILE, "%s", td->title));
pj_log_add_indent(INDENT);
save_tsx_test_state(&state);
rc = init_endpt();
if (rc != PJ_SUCCESS) {
pj_log_add_indent(-INDENT*2);
return -10;
}
rc = td->func(td->arg);
if (rc != PJ_SUCCESS) {
pj_log_add_indent(-INDENT*2);
return -20;
}
flush_events(td->sleep_before_unload);
pjsip_tsx_layer_destroy();
flush_events(td->sleep_after_unload);
destroy_endpt();
rc = check_tsx_test_state(&state);
if (rc != PJ_SUCCESS) {
init_endpt();
pj_log_add_indent(-INDENT*2);
return -30;
}
pj_log_add_indent(-INDENT);
}
init_endpt();
pj_log_add_indent(-INDENT);
return 0;
}
+295
View File
@@ -0,0 +1,295 @@
/* $Id$ */
/*
* Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
* Copyright (C) 2003-2008 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 "test.h"
#include <pjsip.h>
#include <pjlib.h>
#define THIS_FILE "tsx_uas_test.c"
static pjsip_module mod_tsx_user;
static int uac_tsx_bench(unsigned working_set, pj_timestamp *p_elapsed)
{
unsigned i;
pjsip_tx_data *request;
pjsip_transaction **tsx;
pj_timestamp t1, t2, elapsed;
pjsip_via_hdr *via;
pj_status_t status;
/* Create the request first. */
pj_str_t str_target = pj_str("sip:someuser@someprovider.com");
pj_str_t str_from = pj_str("\"Local User\" <sip:localuser@serviceprovider.com>");
pj_str_t str_to = pj_str("\"Remote User\" <sip:remoteuser@serviceprovider.com>");
pj_str_t str_contact = str_from;
status = pjsip_endpt_create_request(endpt, &pjsip_invite_method,
&str_target, &str_from, &str_to,
&str_contact, NULL, -1, NULL,
&request);
if (status != PJ_SUCCESS) {
app_perror(" error: unable to create request", status);
return status;
}
via = (pjsip_via_hdr*) pjsip_msg_find_hdr(request->msg, PJSIP_H_VIA,
NULL);
/* Create transaction array */
tsx = (pjsip_transaction**) pj_pool_zalloc(request->pool, working_set * sizeof(pj_pool_t*));
pj_bzero(&mod_tsx_user, sizeof(mod_tsx_user));
mod_tsx_user.id = -1;
/* Benchmark */
elapsed.u64 = 0;
pj_get_timestamp(&t1);
for (i=0; i<working_set; ++i) {
status = pjsip_tsx_create_uac(&mod_tsx_user, request, &tsx[i]);
if (status != PJ_SUCCESS)
goto on_error;
/* Reset branch param */
via->branch_param.slen = 0;
}
pj_get_timestamp(&t2);
pj_sub_timestamp(&t2, &t1);
pj_add_timestamp(&elapsed, &t2);
p_elapsed->u64 = elapsed.u64;
status = PJ_SUCCESS;
on_error:
for (i=0; i<working_set; ++i) {
if (tsx[i]) {
pj_timer_heap_t *th;
pjsip_tsx_terminate(tsx[i], 601);
tsx[i] = NULL;
th = pjsip_endpt_get_timer_heap(endpt);
pj_timer_heap_poll(th, NULL);
}
}
pjsip_tx_data_dec_ref(request);
flush_events(2000);
return status;
}
static int uas_tsx_bench(unsigned working_set, pj_timestamp *p_elapsed)
{
unsigned i;
pjsip_tx_data *request;
pjsip_via_hdr *via;
pjsip_rx_data rdata;
pj_sockaddr_in remote;
pjsip_transaction **tsx;
pj_timestamp t1, t2, elapsed;
char branch_buf[80] = PJSIP_RFC3261_BRANCH_ID "0000000000";
pj_status_t status;
/* Create the request first. */
pj_str_t str_target = pj_str("sip:someuser@someprovider.com");
pj_str_t str_from = pj_str("\"Local User\" <sip:localuser@serviceprovider.com>");
pj_str_t str_to = pj_str("\"Remote User\" <sip:remoteuser@serviceprovider.com>");
pj_str_t str_contact = str_from;
status = pjsip_endpt_create_request(endpt, &pjsip_invite_method,
&str_target, &str_from, &str_to,
&str_contact, NULL, -1, NULL,
&request);
if (status != PJ_SUCCESS) {
app_perror(" error: unable to create request", status);
return status;
}
/* Create Via */
via = pjsip_via_hdr_create(request->pool);
via->sent_by.host = pj_str("192.168.0.7");
via->sent_by.port = 5061;
via->transport = pj_str("udp");
via->rport_param = 1;
via->recvd_param = pj_str("192.168.0.7");
pjsip_msg_insert_first_hdr(request->msg, (pjsip_hdr*)via);
/* Create "dummy" rdata from the tdata */
pj_bzero(&rdata, sizeof(pjsip_rx_data));
rdata.tp_info.pool = request->pool;
rdata.msg_info.msg = request->msg;
rdata.msg_info.from = (pjsip_from_hdr*) pjsip_msg_find_hdr(request->msg, PJSIP_H_FROM, NULL);
rdata.msg_info.to = (pjsip_to_hdr*) pjsip_msg_find_hdr(request->msg, PJSIP_H_TO, NULL);
rdata.msg_info.cseq = (pjsip_cseq_hdr*) pjsip_msg_find_hdr(request->msg, PJSIP_H_CSEQ, NULL);
rdata.msg_info.cid = (pjsip_cid_hdr*) pjsip_msg_find_hdr(request->msg, PJSIP_H_FROM, NULL);
rdata.msg_info.via = via;
pj_sockaddr_in_init(&remote, 0, 0);
status = pjsip_endpt_acquire_transport(endpt, PJSIP_TRANSPORT_LOOP_DGRAM,
&remote, sizeof(pj_sockaddr_in),
NULL, &rdata.tp_info.transport);
if (status != PJ_SUCCESS) {
app_perror(" error: unable to get loop transport", status);
return status;
}
/* Create transaction array */
tsx = (pjsip_transaction**) pj_pool_zalloc(request->pool, working_set * sizeof(pj_pool_t*));
pj_bzero(&mod_tsx_user, sizeof(mod_tsx_user));
mod_tsx_user.id = -1;
/* Benchmark */
elapsed.u64 = 0;
pj_get_timestamp(&t1);
for (i=0; i<working_set; ++i) {
via->branch_param.ptr = branch_buf;
via->branch_param.slen = PJSIP_RFC3261_BRANCH_LEN +
pj_ansi_sprintf(branch_buf+PJSIP_RFC3261_BRANCH_LEN,
"-%d", i);
status = pjsip_tsx_create_uas(&mod_tsx_user, &rdata, &tsx[i]);
if (status != PJ_SUCCESS)
goto on_error;
}
pj_get_timestamp(&t2);
pj_sub_timestamp(&t2, &t1);
pj_add_timestamp(&elapsed, &t2);
p_elapsed->u64 = elapsed.u64;
status = PJ_SUCCESS;
on_error:
for (i=0; i<working_set; ++i) {
if (tsx[i]) {
pj_timer_heap_t *th;
pjsip_tsx_terminate(tsx[i], 601);
tsx[i] = NULL;
th = pjsip_endpt_get_timer_heap(endpt);
pj_timer_heap_poll(th, NULL);
}
}
pjsip_tx_data_dec_ref(request);
flush_events(2000);
return status;
}
int tsx_bench(void)
{
enum { WORKING_SET=10000, REPEAT = 4 };
unsigned i, speed;
pj_timestamp usec[REPEAT], min, freq;
char desc[250];
int status;
status = pj_get_timestamp_freq(&freq);
if (status != PJ_SUCCESS)
return status;
/*
* Benchmark UAC
*/
PJ_LOG(3,(THIS_FILE, " benchmarking UAC transaction creation:"));
for (i=0; i<REPEAT; ++i) {
PJ_LOG(3,(THIS_FILE, " test %d of %d..",
i+1, REPEAT));
PJ_LOG(3,(THIS_FILE, " number of current tsx: %d",
pjsip_tsx_layer_get_tsx_count()));
status = uac_tsx_bench(WORKING_SET, &usec[i]);
if (status != PJ_SUCCESS)
return status;
}
min.u64 = PJ_UINT64(0xFFFFFFFFFFFFFFF);
for (i=0; i<REPEAT; ++i) {
if (usec[i].u64 < min.u64) min.u64 = usec[i].u64;
}
/* Report time */
pj_ansi_sprintf(desc, "Time to create %d UAC transactions, in miliseconds",
WORKING_SET);
report_ival("create-uac-time", (unsigned)(min.u64 * 1000 / freq.u64), "msec", desc);
/* Write speed */
speed = (unsigned)(freq.u64 * WORKING_SET / min.u64);
PJ_LOG(3,(THIS_FILE, " UAC created at %d tsx/sec", speed));
pj_ansi_sprintf(desc, "Number of UAC transactions that potentially can be created per second "
"with <tt>pjsip_tsx_create_uac()</tt>, based on the time "
"to create %d simultaneous transactions above.",
WORKING_SET);
report_ival("create-uac-tsx-per-sec",
speed, "tsx/sec", desc);
/*
* Benchmark UAS
*/
PJ_LOG(3,(THIS_FILE, " benchmarking UAS transaction creation:"));
for (i=0; i<REPEAT; ++i) {
PJ_LOG(3,(THIS_FILE, " test %d of %d..",
i+1, REPEAT));
PJ_LOG(3,(THIS_FILE, " number of current tsx: %d",
pjsip_tsx_layer_get_tsx_count()));
status = uas_tsx_bench(WORKING_SET, &usec[i]);
if (status != PJ_SUCCESS)
return status;
}
min.u64 = PJ_UINT64(0xFFFFFFFFFFFFFFF);
for (i=0; i<REPEAT; ++i) {
if (usec[i].u64 < min.u64) min.u64 = usec[i].u64;
}
/* Report time */
pj_ansi_sprintf(desc, "Time to create %d UAS transactions, in miliseconds",
WORKING_SET);
report_ival("create-uas-time", (unsigned)(min.u64 * 1000 / freq.u64), "msec", desc);
/* Write speed */
speed = (unsigned)(freq.u64 * WORKING_SET / min.u64);
PJ_LOG(3,(THIS_FILE, " UAS created at %d tsx/sec", speed));
pj_ansi_sprintf(desc, "Number of UAS transactions that potentially can be created per second "
"with <tt>pjsip_tsx_create_uas()</tt>, based on the time "
"to create %d simultaneous transactions above.",
WORKING_SET);
report_ival("create-uas-tsx-per-sec",
speed, "tsx/sec", desc);
return PJ_SUCCESS;
}
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
@@ -0,0 +1,856 @@
/* $Id$ */
/*
* Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
* Copyright (C) 2003-2008 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 "test.h"
#include <pjsip.h>
#include <pjlib.h>
#define THIS_FILE "txdata_test.c"
#define HFIND(msg,h,H) ((pjsip_##h##_hdr*) pjsip_msg_find_hdr(msg, PJSIP_H_##H, NULL))
#if defined(PJ_DEBUG) && PJ_DEBUG!=0
# define LOOP 10000
#else
# define LOOP 100000
#endif
/*
* This tests various core message creation functions.
*/
static int core_txdata_test(void)
{
pj_status_t status;
pj_str_t target, from, to, contact, body;
pjsip_rx_data dummy_rdata;
pjsip_tx_data *invite, *invite2, *cancel, *response, *ack;
PJ_LOG(3,(THIS_FILE, " core transmit data test"));
/* Create INVITE request. */
target = pj_str("tel:+1");
from = pj_str("tel:+0");
to = pj_str("tel:+1");
contact = pj_str("Bob <sip:+0@example.com;user=phone>");
body = pj_str("Hello world!");
status = pjsip_endpt_create_request( endpt, &pjsip_invite_method, &target,
&from, &to, &contact, NULL, 10, &body,
&invite);
if (status != PJ_SUCCESS) {
app_perror(" error: unable to create request", status);
return -10;
}
/* Buffer must be invalid. */
if (pjsip_tx_data_is_valid(invite) != 0) {
PJ_LOG(3,(THIS_FILE, " error: buffer must be invalid"));
return -14;
}
/* Reference counter must be set to 1. */
if (pj_atomic_get(invite->ref_cnt) != 1) {
PJ_LOG(3,(THIS_FILE, " error: invalid reference counter"));
return -15;
}
/* Check message type. */
if (invite->msg->type != PJSIP_REQUEST_MSG)
return -16;
/* Check method. */
if (invite->msg->line.req.method.id != PJSIP_INVITE_METHOD)
return -17;
/* Check that mandatory headers are present. */
if (HFIND(invite->msg, from, FROM) == 0)
return -20;
if (HFIND(invite->msg, to, TO) == 0)
return -21;
if (HFIND(invite->msg, contact, CONTACT) == 0)
return -22;
if (HFIND(invite->msg, cid, CALL_ID) == 0)
return -23;
if (HFIND(invite->msg, cseq, CSEQ) == 0)
return -24;
do {
pjsip_via_hdr *via = HFIND(invite->msg, via, VIA);
if (via == NULL)
return -25;
/* Branch param must be empty. */
if (via->branch_param.slen != 0)
return -26;
} while (0);
if (invite->msg->body == NULL)
return -28;
/* Create another INVITE request from first request. */
status = pjsip_endpt_create_request_from_hdr( endpt, &pjsip_invite_method,
invite->msg->line.req.uri,
HFIND(invite->msg,from,FROM),
HFIND(invite->msg,to,TO),
HFIND(invite->msg,contact,CONTACT),
HFIND(invite->msg,cid,CALL_ID),
10, &body, &invite2);
if (status != PJ_SUCCESS) {
app_perror(" error: create second request failed", status);
return -30;
}
/* Buffer must be invalid. */
if (pjsip_tx_data_is_valid(invite2) != 0) {
PJ_LOG(3,(THIS_FILE, " error: buffer must be invalid"));
return -34;
}
/* Reference counter must be set to 1. */
if (pj_atomic_get(invite2->ref_cnt) != 1) {
PJ_LOG(3,(THIS_FILE, " error: invalid reference counter"));
return -35;
}
/* Check message type. */
if (invite2->msg->type != PJSIP_REQUEST_MSG)
return -36;
/* Check method. */
if (invite2->msg->line.req.method.id != PJSIP_INVITE_METHOD)
return -37;
/* Check that mandatory headers are again present. */
if (HFIND(invite2->msg, from, FROM) == 0)
return -40;
if (HFIND(invite2->msg, to, TO) == 0)
return -41;
if (HFIND(invite2->msg, contact, CONTACT) == 0)
return -42;
if (HFIND(invite2->msg, cid, CALL_ID) == 0)
return -43;
if (HFIND(invite2->msg, cseq, CSEQ) == 0)
return -44;
if (HFIND(invite2->msg, via, VIA) == 0)
return -45;
/*
if (HFIND(invite2->msg, ctype, CONTENT_TYPE) == 0)
return -46;
if (HFIND(invite2->msg, clen, CONTENT_LENGTH) == 0)
return -47;
*/
if (invite2->msg->body == NULL)
return -48;
/* Done checking invite2. We can delete this. */
if (pjsip_tx_data_dec_ref(invite2) != PJSIP_EBUFDESTROYED) {
PJ_LOG(3,(THIS_FILE, " error: request buffer not destroyed!"));
return -49;
}
/* Initialize dummy rdata (to simulate receiving a request)
* We should never do this in real application, as there are many
* many more fields need to be initialized!!
*/
dummy_rdata.msg_info.cid = HFIND(invite->msg, cid, CALL_ID);
dummy_rdata.msg_info.clen = NULL;
dummy_rdata.msg_info.cseq = HFIND(invite->msg, cseq, CSEQ);
dummy_rdata.msg_info.ctype = NULL;
dummy_rdata.msg_info.from = HFIND(invite->msg, from, FROM);
dummy_rdata.msg_info.max_fwd = NULL;
dummy_rdata.msg_info.msg = invite->msg;
dummy_rdata.msg_info.record_route = NULL;
dummy_rdata.msg_info.require = NULL;
dummy_rdata.msg_info.route = NULL;
dummy_rdata.msg_info.to = HFIND(invite->msg, to, TO);
dummy_rdata.msg_info.via = HFIND(invite->msg, via, VIA);
/* Create a response message for the request. */
status = pjsip_endpt_create_response( endpt, &dummy_rdata, 301, NULL,
&response);
if (status != PJ_SUCCESS) {
app_perror(" error: unable to create response", status);
return -50;
}
/* Buffer must be invalid. */
if (pjsip_tx_data_is_valid(response) != 0) {
PJ_LOG(3,(THIS_FILE, " error: buffer must be invalid"));
return -54;
}
/* Check reference counter. */
if (pj_atomic_get(response->ref_cnt) != 1) {
PJ_LOG(3,(THIS_FILE, " error: invalid ref count in response"));
return -55;
}
/* Check message type. */
if (response->msg->type != PJSIP_RESPONSE_MSG)
return -56;
/* Check correct status is set. */
if (response->msg->line.status.code != 301)
return -57;
/* Check that mandatory headers are again present. */
if (HFIND(response->msg, from, FROM) == 0)
return -60;
if (HFIND(response->msg, to, TO) == 0)
return -61;
/*
if (HFIND(response->msg, contact, CONTACT) == 0)
return -62;
*/
if (HFIND(response->msg, cid, CALL_ID) == 0)
return -63;
if (HFIND(response->msg, cseq, CSEQ) == 0)
return -64;
if (HFIND(response->msg, via, VIA) == 0)
return -65;
/* This response message will be used later when creating ACK */
/* Create CANCEL request for the original request. */
status = pjsip_endpt_create_cancel( endpt, invite, &cancel);
if (status != PJ_SUCCESS) {
app_perror(" error: unable to create CANCEL request", status);
return -80;
}
/* Buffer must be invalid. */
if (pjsip_tx_data_is_valid(cancel) != 0) {
PJ_LOG(3,(THIS_FILE, " error: buffer must be invalid"));
return -84;
}
/* Check reference counter. */
if (pj_atomic_get(cancel->ref_cnt) != 1) {
PJ_LOG(3,(THIS_FILE, " error: invalid ref count in CANCEL request"));
return -85;
}
/* Check message type. */
if (cancel->msg->type != PJSIP_REQUEST_MSG)
return -86;
/* Check method. */
if (cancel->msg->line.req.method.id != PJSIP_CANCEL_METHOD)
return -87;
/* Check that mandatory headers are again present. */
if (HFIND(cancel->msg, from, FROM) == 0)
return -90;
if (HFIND(cancel->msg, to, TO) == 0)
return -91;
/*
if (HFIND(cancel->msg, contact, CONTACT) == 0)
return -92;
*/
if (HFIND(cancel->msg, cid, CALL_ID) == 0)
return -93;
if (HFIND(cancel->msg, cseq, CSEQ) == 0)
return -94;
if (HFIND(cancel->msg, via, VIA) == 0)
return -95;
/* Done checking CANCEL request. */
if (pjsip_tx_data_dec_ref(cancel) != PJSIP_EBUFDESTROYED) {
PJ_LOG(3,(THIS_FILE, " error: response buffer not destroyed!"));
return -99;
}
/* Modify dummy_rdata to simulate receiving response. */
pj_bzero(&dummy_rdata, sizeof(dummy_rdata));
dummy_rdata.msg_info.msg = response->msg;
dummy_rdata.msg_info.to = HFIND(response->msg, to, TO);
/* Create ACK request */
status = pjsip_endpt_create_ack( endpt, invite, &dummy_rdata, &ack );
if (status != PJ_SUCCESS) {
PJ_LOG(3,(THIS_FILE, " error: unable to create ACK"));
return -100;
}
/* Buffer must be invalid. */
if (pjsip_tx_data_is_valid(ack) != 0) {
PJ_LOG(3,(THIS_FILE, " error: buffer must be invalid"));
return -104;
}
/* Check reference counter. */
if (pj_atomic_get(ack->ref_cnt) != 1) {
PJ_LOG(3,(THIS_FILE, " error: invalid ref count in ACK request"));
return -105;
}
/* Check message type. */
if (ack->msg->type != PJSIP_REQUEST_MSG)
return -106;
/* Check method. */
if (ack->msg->line.req.method.id != PJSIP_ACK_METHOD)
return -107;
/* Check Request-URI is present. */
if (ack->msg->line.req.uri == NULL)
return -108;
/* Check that mandatory headers are again present. */
if (HFIND(ack->msg, from, FROM) == 0)
return -110;
if (HFIND(ack->msg, to, TO) == 0)
return -111;
if (HFIND(ack->msg, cid, CALL_ID) == 0)
return -112;
if (HFIND(ack->msg, cseq, CSEQ) == 0)
return -113;
if (HFIND(ack->msg, via, VIA) == 0)
return -114;
if (ack->msg->body != NULL)
return -115;
/* Done checking invite message. */
if (pjsip_tx_data_dec_ref(invite) != PJSIP_EBUFDESTROYED) {
PJ_LOG(3,(THIS_FILE, " error: response buffer not destroyed!"));
return -120;
}
/* Done checking response message. */
if (pjsip_tx_data_dec_ref(response) != PJSIP_EBUFDESTROYED) {
PJ_LOG(3,(THIS_FILE, " error: response buffer not destroyed!"));
return -130;
}
/* Done checking ack message. */
if (pjsip_tx_data_dec_ref(ack) != PJSIP_EBUFDESTROYED) {
PJ_LOG(3,(THIS_FILE, " error: response buffer not destroyed!"));
return -140;
}
/* Done. */
return 0;
}
/*
* This test demonstrate the bug as reported in:
* http://bugzilla.pjproject.net/show_bug.cgi?id=49
*/
#if INCLUDE_GCC_TEST
static int gcc_test()
{
char msgbuf[512];
pj_str_t target = pj_str("sip:alice@wonderland:5061;x-param=param%201"
"?X-Hdr-1=Header%201"
"&X-Empty-Hdr=");
pjsip_tx_data *tdata;
pjsip_parser_err_report err_list;
pjsip_msg *msg;
int len;
pj_status_t status;
PJ_LOG(3,(THIS_FILE, " header param in URI to create request"));
/* Create request with header param in target URI. */
status = pjsip_endpt_create_request(endpt, &pjsip_invite_method, &target,
&target, &target, &target, NULL, -1,
NULL, &tdata);
if (status != 0) {
app_perror(" error: Unable to create request", status);
return -200;
}
/* Print and parse the request.
* We'll check that header params are not present in
*/
len = pjsip_msg_print(tdata->msg, msgbuf, sizeof(msgbuf));
if (len < 1) {
PJ_LOG(3,(THIS_FILE, " error: printing message"));
pjsip_tx_data_dec_ref(tdata);
return -250;
}
msgbuf[len] = '\0';
PJ_LOG(5,(THIS_FILE, "%d bytes request created:--begin-msg--\n"
"%s\n"
"--end-msg--", len, msgbuf));
/* Now parse the message. */
pj_list_init(&err_list);
msg = pjsip_parse_msg( tdata->pool, msgbuf, len, &err_list);
if (msg == NULL) {
pjsip_parser_err_report *e;
PJ_LOG(3,(THIS_FILE, " error: parsing message message"));
e = err_list.next;
while (e != &err_list) {
PJ_LOG(3,(THIS_FILE, " %s in line %d col %d hname=%.*s",
pj_exception_id_name(e->except_code),
e->line, e->col+1,
(int)e->hname.slen,
e->hname.ptr));
e = e->next;
}
pjsip_tx_data_dec_ref(tdata);
return -255;
}
pjsip_tx_data_dec_ref(tdata);
return 0;
}
#endif
/* This tests the request creating functions against the following
* requirements:
* - header params in URI creates header in the request.
* - method and headers params are correctly shown or hidden in
* request URI, From, To, and Contact header.
*/
static int txdata_test_uri_params(void)
{
char msgbuf[512];
pj_str_t target = pj_str("sip:alice@wonderland:5061;x-param=param%201"
"?X-Hdr-1=Header%201"
"&X-Empty-Hdr=");
pj_str_t contact;
pj_str_t pname = pj_str("x-param");
pj_str_t hname = pj_str("X-Hdr-1");
pj_str_t hemptyname = pj_str("X-Empty-Hdr");
pjsip_from_hdr *from_hdr;
pjsip_to_hdr *to_hdr;
pjsip_contact_hdr *contact_hdr;
pjsip_generic_string_hdr *hdr;
pjsip_tx_data *tdata;
pjsip_sip_uri *uri;
pjsip_param *param;
pjsip_via_hdr *via;
pjsip_parser_err_report err_list;
pjsip_msg *msg;
pj_ssize_t len;
pj_status_t status;
PJ_LOG(3,(THIS_FILE, " header param in URI to create request"));
/* Due to #930, contact argument is now parsed as Contact header, so
* must enclose it with <> to make it be parsed as URI.
*/
pj_ansi_snprintf(msgbuf, sizeof(msgbuf), "<%.*s>",
(int)target.slen, target.ptr);
contact.ptr = msgbuf;
contact.slen = strlen(msgbuf);
/* Create request with header param in target URI. */
status = pjsip_endpt_create_request(endpt, &pjsip_invite_method, &target,
&target, &target, &contact, NULL, -1,
NULL, &tdata);
if (status != 0) {
app_perror(" error: Unable to create request", status);
return -200;
}
/* Fill up the Via header to prevent syntax error on parsing */
via = (pjsip_via_hdr*) pjsip_msg_find_hdr(tdata->msg, PJSIP_H_VIA, NULL);
via->transport = pj_str("TCP");
via->sent_by.host = pj_str("127.0.0.1");
/* Print and parse the request.
* We'll check that header params are not present in
*/
len = pjsip_msg_print(tdata->msg, msgbuf, sizeof(msgbuf));
if (len < 1) {
PJ_LOG(3,(THIS_FILE, " error: printing message"));
pjsip_tx_data_dec_ref(tdata);
return -250;
}
msgbuf[len] = '\0';
PJ_LOG(5,(THIS_FILE, "%d bytes request created:--begin-msg--\n"
"%s\n"
"--end-msg--", len, msgbuf));
/* Now parse the message. */
pj_list_init(&err_list);
msg = pjsip_parse_msg( tdata->pool, msgbuf, len, &err_list);
if (msg == NULL) {
pjsip_parser_err_report *e;
PJ_LOG(3,(THIS_FILE, " error: parsing message message"));
e = err_list.next;
while (e != &err_list) {
PJ_LOG(3,(THIS_FILE, " %s in line %d col %d hname=%.*s",
pj_exception_id_name(e->except_code),
e->line, e->col+1,
(int)e->hname.slen,
e->hname.ptr));
e = e->next;
}
pjsip_tx_data_dec_ref(tdata);
return -256;
}
/* Check the existence of port, other_param, and header param.
* Port is now allowed in To and From header.
*/
/* Port in request URI. */
uri = (pjsip_sip_uri*) pjsip_uri_get_uri(msg->line.req.uri);
if (uri->port != 5061) {
PJ_LOG(3,(THIS_FILE, " error: port not present in request URI"));
pjsip_tx_data_dec_ref(tdata);
return -260;
}
/* other_param in request_uri */
param = pjsip_param_find(&uri->other_param, &pname);
if (param == NULL || pj_strcmp2(&param->value, "param 1") != 0) {
PJ_LOG(3,(THIS_FILE, " error: x-param not present in request URI"));
pjsip_tx_data_dec_ref(tdata);
return -261;
}
/* header param in request uri. */
if (!pj_list_empty(&uri->header_param)) {
PJ_LOG(3,(THIS_FILE, " error: hparam in request URI"));
pjsip_tx_data_dec_ref(tdata);
return -262;
}
/* Port in From header. */
from_hdr = (pjsip_from_hdr*) pjsip_msg_find_hdr(msg, PJSIP_H_FROM, NULL);
uri = (pjsip_sip_uri*) pjsip_uri_get_uri(from_hdr->uri);
if (uri->port != 0) {
PJ_LOG(3,(THIS_FILE, " error: port most not exist in From header"));
pjsip_tx_data_dec_ref(tdata);
return -270;
}
/* other_param in From header */
param = pjsip_param_find(&uri->other_param, &pname);
if (param == NULL || pj_strcmp2(&param->value, "param 1") != 0) {
PJ_LOG(3,(THIS_FILE, " error: x-param not present in From header"));
pjsip_tx_data_dec_ref(tdata);
return -271;
}
/* header param in From header. */
if (!pj_list_empty(&uri->header_param)) {
PJ_LOG(3,(THIS_FILE, " error: hparam in From header"));
pjsip_tx_data_dec_ref(tdata);
return -272;
}
/* Port in To header. */
to_hdr = (pjsip_to_hdr*) pjsip_msg_find_hdr(msg, PJSIP_H_TO, NULL);
uri = (pjsip_sip_uri*) pjsip_uri_get_uri(to_hdr->uri);
if (uri->port != 0) {
PJ_LOG(3,(THIS_FILE, " error: port most not exist in To header"));
pjsip_tx_data_dec_ref(tdata);
return -280;
}
/* other_param in To header */
param = pjsip_param_find(&uri->other_param, &pname);
if (param == NULL || pj_strcmp2(&param->value, "param 1") != 0) {
PJ_LOG(3,(THIS_FILE, " error: x-param not present in To header"));
pjsip_tx_data_dec_ref(tdata);
return -281;
}
/* header param in From header. */
if (!pj_list_empty(&uri->header_param)) {
PJ_LOG(3,(THIS_FILE, " error: hparam in To header"));
pjsip_tx_data_dec_ref(tdata);
return -282;
}
/* Port in Contact header. */
contact_hdr = (pjsip_contact_hdr*) pjsip_msg_find_hdr(msg, PJSIP_H_CONTACT, NULL);
uri = (pjsip_sip_uri*) pjsip_uri_get_uri(contact_hdr->uri);
if (uri->port != 5061) {
PJ_LOG(3,(THIS_FILE, " error: port not present in Contact header"));
pjsip_tx_data_dec_ref(tdata);
return -290;
}
/* other_param in Contact header */
param = pjsip_param_find(&uri->other_param, &pname);
if (param == NULL || pj_strcmp2(&param->value, "param 1") != 0) {
PJ_LOG(3,(THIS_FILE, " error: x-param not present in Contact header"));
pjsip_tx_data_dec_ref(tdata);
return -291;
}
/* header param in Contact header. */
if (pj_list_empty(&uri->header_param)) {
PJ_LOG(3,(THIS_FILE, " error: hparam is missing in Contact header"));
pjsip_tx_data_dec_ref(tdata);
return -292;
}
/* Check for X-Hdr-1 */
param = pjsip_param_find(&uri->header_param, &hname);
if (param == NULL || pj_strcmp2(&param->value, "Header 1")!=0) {
PJ_LOG(3,(THIS_FILE, " error: hparam is missing in Contact header"));
pjsip_tx_data_dec_ref(tdata);
return -293;
}
/* Check for X-Empty-Hdr */
param = pjsip_param_find(&uri->header_param, &hemptyname);
if (param == NULL || pj_strcmp2(&param->value, "")!=0) {
PJ_LOG(3,(THIS_FILE, " error: hparam is missing in Contact header"));
pjsip_tx_data_dec_ref(tdata);
return -294;
}
/* Check that headers are present in the request. */
hdr = (pjsip_generic_string_hdr*)
pjsip_msg_find_hdr_by_name(msg, &hname, NULL);
if (hdr == NULL || pj_strcmp2(&hdr->hvalue, "Header 1")!=0) {
PJ_LOG(3,(THIS_FILE, " error: header X-Hdr-1 not created"));
pjsip_tx_data_dec_ref(tdata);
return -300;
}
hdr = (pjsip_generic_string_hdr*)
pjsip_msg_find_hdr_by_name(msg, &hemptyname, NULL);
if (hdr == NULL || pj_strcmp2(&param->value, "")!=0) {
PJ_LOG(3,(THIS_FILE, " error: header X-Empty-Hdr not created"));
pjsip_tx_data_dec_ref(tdata);
return -330;
}
pjsip_tx_data_dec_ref(tdata);
return 0;
}
/*
* create request benchmark
*/
static int create_request_bench(pj_timestamp *p_elapsed)
{
enum { COUNT = 100 };
unsigned i, j;
pjsip_tx_data *tdata[COUNT];
pj_timestamp t1, t2, elapsed;
pj_status_t status;
pj_str_t str_target = pj_str("sip:someuser@someprovider.com");
pj_str_t str_from = pj_str("\"Local User\" <sip:localuser@serviceprovider.com>");
pj_str_t str_to = pj_str("\"Remote User\" <sip:remoteuser@serviceprovider.com>");
pj_str_t str_contact = str_from;
elapsed.u64 = 0;
for (i=0; i<LOOP; i+=COUNT) {
pj_bzero(tdata, sizeof(tdata));
pj_get_timestamp(&t1);
for (j=0; j<COUNT; ++j) {
status = pjsip_endpt_create_request(endpt, &pjsip_invite_method,
&str_target, &str_from, &str_to,
&str_contact, NULL, -1, NULL,
&tdata[j]);
if (status != PJ_SUCCESS) {
app_perror(" error: unable to create request", status);
goto on_error;
}
}
pj_get_timestamp(&t2);
pj_sub_timestamp(&t2, &t1);
pj_add_timestamp(&elapsed, &t2);
for (j=0; j<COUNT; ++j)
pjsip_tx_data_dec_ref(tdata[j]);
}
p_elapsed->u64 = elapsed.u64;
return PJ_SUCCESS;
on_error:
for (i=0; i<COUNT; ++i) {
if (tdata[i])
pjsip_tx_data_dec_ref(tdata[i]);
}
return -400;
}
/*
* create response benchmark
*/
static int create_response_bench(pj_timestamp *p_elapsed)
{
enum { COUNT = 100 };
unsigned i, j;
pjsip_via_hdr *via;
pjsip_rx_data rdata;
pjsip_tx_data *request;
pjsip_tx_data *tdata[COUNT];
pj_timestamp t1, t2, elapsed;
pj_status_t status;
/* Create the request first. */
pj_str_t str_target = pj_str("sip:someuser@someprovider.com");
pj_str_t str_from = pj_str("\"Local User\" <sip:localuser@serviceprovider.com>");
pj_str_t str_to = pj_str("\"Remote User\" <sip:remoteuser@serviceprovider.com>");
pj_str_t str_contact = str_from;
status = pjsip_endpt_create_request(endpt, &pjsip_invite_method,
&str_target, &str_from, &str_to,
&str_contact, NULL, -1, NULL,
&request);
if (status != PJ_SUCCESS) {
app_perror(" error: unable to create request", status);
return status;
}
/* Create several Via headers */
via = pjsip_via_hdr_create(request->pool);
via->sent_by.host = pj_str("192.168.0.7");
via->sent_by.port = 5061;
via->transport = pj_str("udp");
via->rport_param = 0;
via->branch_param = pj_str("012345678901234567890123456789");
via->recvd_param = pj_str("192.168.0.7");
pjsip_msg_insert_first_hdr(request->msg, (pjsip_hdr*) pjsip_hdr_clone(request->pool, via));
pjsip_msg_insert_first_hdr(request->msg, (pjsip_hdr*) pjsip_hdr_clone(request->pool, via));
pjsip_msg_insert_first_hdr(request->msg, (pjsip_hdr*)via);
/* Create "dummy" rdata from the tdata */
pj_bzero(&rdata, sizeof(pjsip_rx_data));
rdata.tp_info.pool = request->pool;
rdata.msg_info.msg = request->msg;
rdata.msg_info.from = (pjsip_from_hdr*) pjsip_msg_find_hdr(request->msg, PJSIP_H_FROM, NULL);
rdata.msg_info.to = (pjsip_to_hdr*) pjsip_msg_find_hdr(request->msg, PJSIP_H_TO, NULL);
rdata.msg_info.cseq = (pjsip_cseq_hdr*) pjsip_msg_find_hdr(request->msg, PJSIP_H_CSEQ, NULL);
rdata.msg_info.cid = (pjsip_cid_hdr*) pjsip_msg_find_hdr(request->msg, PJSIP_H_FROM, NULL);
rdata.msg_info.via = via;
/*
* Now benchmark create_response
*/
elapsed.u64 = 0;
for (i=0; i<LOOP; i+=COUNT) {
pj_bzero(tdata, sizeof(tdata));
pj_get_timestamp(&t1);
for (j=0; j<COUNT; ++j) {
status = pjsip_endpt_create_response(endpt, &rdata, 200, NULL, &tdata[j]);
if (status != PJ_SUCCESS) {
app_perror(" error: unable to create request", status);
goto on_error;
}
}
pj_get_timestamp(&t2);
pj_sub_timestamp(&t2, &t1);
pj_add_timestamp(&elapsed, &t2);
for (j=0; j<COUNT; ++j)
pjsip_tx_data_dec_ref(tdata[j]);
}
p_elapsed->u64 = elapsed.u64;
pjsip_tx_data_dec_ref(request);
return PJ_SUCCESS;
on_error:
for (i=0; i<COUNT; ++i) {
if (tdata[i])
pjsip_tx_data_dec_ref(tdata[i]);
}
return -400;
}
int txdata_test(void)
{
enum { REPEAT = 4 };
unsigned i, msgs;
pj_timestamp usec[REPEAT], min, freq;
int status;
status = pj_get_timestamp_freq(&freq);
if (status != PJ_SUCCESS)
return status;
status = core_txdata_test();
if (status != 0)
return status;
#if INCLUDE_GCC_TEST
status = gcc_test();
if (status != 0)
return status;
#endif
status = txdata_test_uri_params();
if (status != 0)
return status;
/*
* Benchmark create_request()
*/
PJ_LOG(3,(THIS_FILE, " benchmarking request creation:"));
for (i=0; i<REPEAT; ++i) {
PJ_LOG(3,(THIS_FILE, " test %d of %d..",
i+1, REPEAT));
status = create_request_bench(&usec[i]);
if (status != PJ_SUCCESS)
return status;
}
min.u64 = PJ_UINT64(0xFFFFFFFFFFFFFFF);
for (i=0; i<REPEAT; ++i) {
if (usec[i].u64 < min.u64) min.u64 = usec[i].u64;
}
msgs = (unsigned)(freq.u64 * LOOP / min.u64);
PJ_LOG(3,(THIS_FILE, " Requests created at %d requests/sec", msgs));
report_ival("create-request-per-sec",
msgs, "msg/sec",
"Number of typical request messages that can be created "
"per second with <tt>pjsip_endpt_create_request()</tt>");
/*
* Benchmark create_response()
*/
PJ_LOG(3,(THIS_FILE, " benchmarking response creation:"));
for (i=0; i<REPEAT; ++i) {
PJ_LOG(3,(THIS_FILE, " test %d of %d..",
i+1, REPEAT));
status = create_response_bench(&usec[i]);
if (status != PJ_SUCCESS)
return status;
}
min.u64 = PJ_UINT64(0xFFFFFFFFFFFFFFF);
for (i=0; i<REPEAT; ++i) {
if (usec[i].u64 < min.u64) min.u64 = usec[i].u64;
}
msgs = (unsigned)(freq.u64 * LOOP / min.u64);
PJ_LOG(3,(THIS_FILE, " Responses created at %d responses/sec", msgs));
report_ival("create-response-per-sec",
msgs, "msg/sec",
"Number of typical response messages that can be created "
"per second with <tt>pjsip_endpt_create_response()</tt>");
return 0;
}
File diff suppressed because it is too large. Load diff