From 3112361512e913a6ce28a9253a6d45434b975505 Mon Sep 17 00:00:00 2001 From: Benny Prijono Date: Wed, 3 Oct 2007 18:28:49 +0000 Subject: Ticket 5: Support for SIP UPDATE (RFC 3311) and fix the offer/answer negotiation git-svn-id: http://svn.pjsip.org/repos/pjproject/trunk@1469 74dad513-b988-da41-8d7b-12977e46ad98 --- pjsip/src/test-pjsip/inv_offer_answer_test.c | 676 +++++++++++++++++++++++++++ pjsip/src/test-pjsip/test.c | 4 + pjsip/src/test-pjsip/test.h | 6 +- 3 files changed, 685 insertions(+), 1 deletion(-) create mode 100644 pjsip/src/test-pjsip/inv_offer_answer_test.c (limited to 'pjsip/src/test-pjsip') diff --git a/pjsip/src/test-pjsip/inv_offer_answer_test.c b/pjsip/src/test-pjsip/inv_offer_answer_test.c new file mode 100644 index 00000000..9da8d250 --- /dev/null +++ b/pjsip/src/test-pjsip/inv_offer_answer_test.c @@ -0,0 +1,676 @@ +/* $Id$ */ +/* + * Copyright (C) 2003-2007 Benny Prijono + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "test.h" +#include +#include +#include + +#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); + + return sdp; +} + +/**************** INVITE SESSION CALLBACKS ******************/ +static void on_rx_offer(pjsip_inv_session *inv, + const pjmedia_sdp_session *offer) +{ + pjmedia_sdp_session *sdp; + + 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_assert(!"Should not happen"); +} + +static void on_media_update(pjsip_inv_session *inv_ses, + pj_status_t 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; + + 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; + pj_str_t uri; + pjsip_tx_data *tdata; + pj_status_t status; + + /* + * Create UAS + */ + uri = pj_str(CONTACT); + status = pjsip_dlg_create_uas(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); + + 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 PJ_TRUE; + } + + 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; + } +} + + +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) +{ + return NULL; +} + + +static void on_new_session(pjsip_inv_session *inv, pjsip_event *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); + } + + /* Do tests */ + for (i=0; i