diff options
Diffstat (limited to 'pjsip/src/pjsip-ua/sip_replaces.c')
-rw-r--r-- | pjsip/src/pjsip-ua/sip_replaces.c | 355 |
1 files changed, 355 insertions, 0 deletions
diff --git a/pjsip/src/pjsip-ua/sip_replaces.c b/pjsip/src/pjsip-ua/sip_replaces.c new file mode 100644 index 00000000..acf8181b --- /dev/null +++ b/pjsip/src/pjsip-ua/sip_replaces.c @@ -0,0 +1,355 @@ +/* $Id$ */ +/* + * Copyright (C) 2003-2006 Benny Prijono <benny@prijono.org> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include <pjsip-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/pool.h> +#include <pj/string.h> + + +/* + * 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; + +PJ_DEF(pjsip_replaces_hdr*) pjsip_replaces_hdr_create(pj_pool_t *pool) +{ + pjsip_replaces_hdr *hdr = pj_pool_zalloc(pool, sizeof(*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; + int printed; + + 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, + &pjsip_TOKEN_SPEC, + &pjsip_TOKEN_SPEC, ';'); + if (printed < 0) + return printed; + + p += printed; + return 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(pool, sizeof(*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); + + 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(ctx->pool, sizeof(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; +} + +/* + * 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 }; + static pj_bool_t is_initialized; + + 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); + + 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) { + 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_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; +} + + + |