| /* $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); |
| |
| 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(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) |
| { |
| 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); |
| } |
| |
| /* 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; |
| } |
| |