Author: mikej Date: Wed May 14 15:10:54 2008 New Revision: 8406 Added: freeswitch/trunk/libs/sofia-sip/libsofia-sip-ua/nua/check_nua.c freeswitch/trunk/libs/sofia-sip/libsofia-sip-ua/nua/check_nua.h freeswitch/trunk/libs/sofia-sip/libsofia-sip-ua/nua/check_register.c freeswitch/trunk/libs/sofia-sip/libsofia-sip-ua/nua/check_session.c freeswitch/trunk/libs/sofia-sip/libsofia-sip-ua/nua/test_s2.c freeswitch/trunk/libs/sofia-sip/libsofia-sip-ua/nua/test_s2.h freeswitch/trunk/libs/sofia-sip/tests/test_100rel.c freeswitch/trunk/libs/sofia-sip/tests/test_basic_call.c freeswitch/trunk/libs/sofia-sip/tests/test_call_hold.c freeswitch/trunk/libs/sofia-sip/tests/test_call_reject.c freeswitch/trunk/libs/sofia-sip/tests/test_cancel_bye.c freeswitch/trunk/libs/sofia-sip/tests/test_extension.c freeswitch/trunk/libs/sofia-sip/tests/test_init.c freeswitch/trunk/libs/sofia-sip/tests/test_nat.c freeswitch/trunk/libs/sofia-sip/tests/test_nat.h freeswitch/trunk/libs/sofia-sip/tests/test_nat_tags.c freeswitch/trunk/libs/sofia-sip/tests/test_nua.c freeswitch/trunk/libs/sofia-sip/tests/test_nua.h freeswitch/trunk/libs/sofia-sip/tests/test_nua_api.c freeswitch/trunk/libs/sofia-sip/tests/test_nua_params.c freeswitch/trunk/libs/sofia-sip/tests/test_offer_answer.c freeswitch/trunk/libs/sofia-sip/tests/test_ops.c freeswitch/trunk/libs/sofia-sip/tests/test_proxy.c freeswitch/trunk/libs/sofia-sip/tests/test_proxy.h freeswitch/trunk/libs/sofia-sip/tests/test_refer.c freeswitch/trunk/libs/sofia-sip/tests/test_register.c freeswitch/trunk/libs/sofia-sip/tests/test_session_timer.c freeswitch/trunk/libs/sofia-sip/tests/test_simple.c freeswitch/trunk/libs/sofia-sip/tests/test_sip_events.c Removed: freeswitch/trunk/libs/sofia-sip/libsofia-sip-ua/nua/test_100rel.c freeswitch/trunk/libs/sofia-sip/libsofia-sip-ua/nua/test_basic_call.c freeswitch/trunk/libs/sofia-sip/libsofia-sip-ua/nua/test_call_hold.c freeswitch/trunk/libs/sofia-sip/libsofia-sip-ua/nua/test_call_reject.c freeswitch/trunk/libs/sofia-sip/libsofia-sip-ua/nua/test_cancel_bye.c freeswitch/trunk/libs/sofia-sip/libsofia-sip-ua/nua/test_extension.c freeswitch/trunk/libs/sofia-sip/libsofia-sip-ua/nua/test_init.c freeswitch/trunk/libs/sofia-sip/libsofia-sip-ua/nua/test_nat.c freeswitch/trunk/libs/sofia-sip/libsofia-sip-ua/nua/test_nat.h freeswitch/trunk/libs/sofia-sip/libsofia-sip-ua/nua/test_nat_tags.c freeswitch/trunk/libs/sofia-sip/libsofia-sip-ua/nua/test_nua.c freeswitch/trunk/libs/sofia-sip/libsofia-sip-ua/nua/test_nua.h freeswitch/trunk/libs/sofia-sip/libsofia-sip-ua/nua/test_nua_api.c freeswitch/trunk/libs/sofia-sip/libsofia-sip-ua/nua/test_nua_params.c freeswitch/trunk/libs/sofia-sip/libsofia-sip-ua/nua/test_offer_answer.c freeswitch/trunk/libs/sofia-sip/libsofia-sip-ua/nua/test_ops.c freeswitch/trunk/libs/sofia-sip/libsofia-sip-ua/nua/test_proxy.c freeswitch/trunk/libs/sofia-sip/libsofia-sip-ua/nua/test_proxy.h freeswitch/trunk/libs/sofia-sip/libsofia-sip-ua/nua/test_refer.c freeswitch/trunk/libs/sofia-sip/libsofia-sip-ua/nua/test_register.c freeswitch/trunk/libs/sofia-sip/libsofia-sip-ua/nua/test_session_timer.c freeswitch/trunk/libs/sofia-sip/libsofia-sip-ua/nua/test_simple.c freeswitch/trunk/libs/sofia-sip/libsofia-sip-ua/nua/test_sip_events.c Log: complete r8400 commit Added: freeswitch/trunk/libs/sofia-sip/libsofia-sip-ua/nua/check_nua.c ============================================================================== --- (empty file) +++ freeswitch/trunk/libs/sofia-sip/libsofia-sip-ua/nua/check_nua.c Wed May 14 15:10:54 2008 @@ -0,0 +1,120 @@ +/* + * This file is part of the Sofia-SIP package + * + * Copyright (C) 2007 Nokia Corporation. + * + * Contact: Pekka Pessi <pekka.pessi@nokia.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public License + * as published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + */ + +/**@CFILE check2_sofia.c + * + * @brief Check-driven tester for Sofia SIP User Agent library + * + * @author Pekka Pessi <Pekka.Pessi@nokia.com> + * + * @copyright (C) 2007 Nokia Corporation. + */ + +#include "config.h" + +#include "check_nua.h" + +#include <stdlib.h> +#include <string.h> +#include <stdio.h> + +#if HAVE_FNMATCH_H +#include <fnmatch.h> +#endif + +static char const * const default_patterns[] = { "*", NULL }; +static char const * const *test_patterns = default_patterns; + +void check_nua_tcase_add_test(TCase *tc, TFun tf, char const *name) +{ + char const * const *patterns; + +#if HAVE_FNMATCH_H + for (patterns = test_patterns; *patterns; patterns++) { + if (!fnmatch(*patterns, name, 0)) { + if (strcmp(*patterns, "*")) { + printf("%s: match with %s\n", name, *patterns); + } + _tcase_add_test(tc, tf, name, 0, 0, 1); + return; + } + } +#else + for (patterns = test_patterns; *patterns; patterns++) { + if (!strcmp(*patterns, name) || !strcmp(*patterns, "*")) { + if (strcmp(*patterns, "*")) { + printf("%s: match with %s\n", name, *patterns); + } + _tcase_add_test(tc, tf, name, 0, 0, 1); + return; + } + } +#endif + printf("%s: no match\n", name); +} + +int main(int argc, char *argv[]) +{ + int failed = 0; + + Suite *suite = suite_create("Unit tests for Sofia-SIP UA Engine"); + SRunner *runner; + + if (getenv("CHECK_NUA_CASES")) { + size_t i; + char *s, **patterns; + char *cases = strdup(getenv("CHECK_NUA_CASES")); + + /* Count commas */ + for (i = 2, s = cases; (s = strchr(s, ',')); s++, i++); + + patterns = calloc(i, sizeof *patterns); + + for (i = 0, s = cases;; i++) { + patterns[i] = s; + if (s == NULL) + break; + s = strchr(s, ','); + if (s) + *s++ = '\0'; + } + + test_patterns = (char const * const *)patterns; + } + + check_register_cases(suite); + check_session_cases(suite); + + runner = srunner_create(suite); + + if (argv[1]) { + srunner_set_xml(runner, argv[1]); + } + srunner_run_all(runner, CK_ENV); + + failed = srunner_ntests_failed(runner); + srunner_free(runner); + + exit(failed ? EXIT_FAILURE : EXIT_SUCCESS); +} Added: freeswitch/trunk/libs/sofia-sip/libsofia-sip-ua/nua/check_nua.h ============================================================================== --- (empty file) +++ freeswitch/trunk/libs/sofia-sip/libsofia-sip-ua/nua/check_nua.h Wed May 14 15:10:54 2008 @@ -0,0 +1,15 @@ +#ifndef CHECK_NUA_H + +#include <check.h> + +#undef tcase_add_test +#define tcase_add_test(tc, tf) \ + check_nua_tcase_add_test(tc, tf, "" #tf "") + +void check_nua_tcase_add_test(TCase *, TFun, char const *name); + +void check_session_cases(Suite *suite); +void check_register_cases(Suite *suite); + +#endif + Added: freeswitch/trunk/libs/sofia-sip/libsofia-sip-ua/nua/check_register.c ============================================================================== --- (empty file) +++ freeswitch/trunk/libs/sofia-sip/libsofia-sip-ua/nua/check_register.c Wed May 14 15:10:54 2008 @@ -0,0 +1,741 @@ +/* + * This file is part of the Sofia-SIP package + * + * Copyright (C) 2008 Nokia Corporation. + * + * Contact: Pekka Pessi <pekka.pessi@nokia.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public License + * as published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + */ + +/**@CFILE check_register.c + * + * @brief Check-driven tester for Sofia SIP User Agent library + * + * @author Pekka Pessi <Pekka.Pessi@nokia.com> + * + * @copyright (C) 2008 Nokia Corporation. + */ + +#include "config.h" + +#include "check_nua.h" + +#include "test_s2.h" + +#include <sofia-sip/sip_status.h> +#include <sofia-sip/sip_header.h> +#include <sofia-sip/soa.h> +#include <sofia-sip/su_tagarg.h> +#include <sofia-sip/su_tag_io.h> +#include <sofia-sip/su_log.h> + +#include <stdlib.h> +#include <string.h> +#include <assert.h> + +SOFIAPUBVAR su_log_t tport_log[]; + +static nua_t *nua; + +static void register_setup(void) +{ + nua = s2_nua_setup(TAG_END()); +} + +static void register_pingpong_setup(void) +{ + nua = s2_nua_setup(TPTAG_PINGPONG(20000), + TPTAG_KEEPALIVE(10000), + TAG_END()); + tport_set_params(s2->tcp.tport, TPTAG_PONG2PING(1), TAG_END()); +} + + +static void register_teardown(void) +{ + nua_shutdown(nua); + fail_unless(s2_check_event(nua_r_shutdown, 200)); + s2_nua_teardown(); +} + + +/* ---------------------------------------------------------------------- */ + +START_TEST(register_1_0_1) +{ + nua_handle_t *nh = nua_handle(nua, NULL, TAG_END()); + struct message *m; + + s2_case("1.0.1", "Failed Register", "REGISTER returned 403 response"); + + nua_register(nh, TAG_END()); + + fail_unless((m = s2_wait_for_request(SIP_METHOD_REGISTER)) != NULL, NULL); + + s2_respond_to(m, NULL, + SIP_403_FORBIDDEN, + TAG_END()); + s2_free_message(m); + + nua_handle_destroy(nh); + +} END_TEST + + +START_TEST(register_1_1_1) +{ + s2_case("1.1.1", "Basic Register", "REGISTER returning 200 OK"); + + s2_register_setup(); + + s2_register_teardown(); + +} END_TEST + + +START_TEST(register_1_1_2) +{ + nua_handle_t *nh; + struct message *m; + + s2_case("1.1.2", "Register with dual authentication", + "Register, authenticate"); + + nh = nua_handle(nua, NULL, TAG_END()); + + nua_register(nh, TAG_END()); + + m = s2_wait_for_request(SIP_METHOD_REGISTER); fail_if(!m); + s2_respond_to(m, NULL, + SIP_407_PROXY_AUTH_REQUIRED, + SIPTAG_PROXY_AUTHENTICATE_STR(s2_auth_digest_str), + TAG_END()); + s2_free_message(m); + s2_check_event(nua_r_register, 407); + + nua_authenticate(nh, NUTAG_AUTH(s2_auth_credentials), TAG_END()); + + m = s2_wait_for_request(SIP_METHOD_REGISTER); fail_if(!m); + s2_respond_to(m, NULL, + SIP_401_UNAUTHORIZED, + SIPTAG_WWW_AUTHENTICATE_STR(s2_auth2_digest_str), + SIPTAG_PROXY_AUTHENTICATE_STR(s2_auth_digest_str), + TAG_END()); + s2_free_message(m); + s2_check_event(nua_r_register, 401); + + nua_authenticate(nh, NUTAG_AUTH(s2_auth2_credentials), TAG_END()); + + m = s2_wait_for_request(SIP_METHOD_REGISTER); + fail_if(!m); + fail_if(!m->sip->sip_authorization); + fail_if(!m->sip->sip_proxy_authorization); + s2_save_register(m); + + s2_respond_to(m, NULL, + SIP_200_OK, + SIPTAG_CONTACT(s2->registration->contact), + TAG_END()); + s2_free_message(m); + + assert(s2->registration->contact != NULL); + s2_check_event(nua_r_register, 200); + + s2->registration->nh = nh; + + s2_register_teardown(); + +} END_TEST + +/* ---------------------------------------------------------------------- */ + +static char const *receive_natted = "received=4.255.255.9"; + +/* Return Via that looks very natted */ +static sip_via_t *natted_via(struct message *m) +{ + su_home_t *h; + sip_via_t *via; + + h = msg_home(m->msg); + via = sip_via_dup(h, m->sip->sip_via); + msg_header_replace_param(h, via->v_common, receive_natted); + + if (via->v_protocol == sip_transport_udp) + msg_header_replace_param(h, via->v_common, "rport=9"); + + if (via->v_protocol == sip_transport_tcp && via->v_rport) { + tp_name_t const *tpn = tport_name(m->tport); + char *rport = su_sprintf(h, "rport=%s", tpn->tpn_port); + msg_header_replace_param(h, via->v_common, rport); + } + + return via; +} + +/* ---------------------------------------------------------------------- */ + +START_TEST(register_1_2_1) { + nua_handle_t *nh; + struct message *m; + + s2_case("1.2.1", "Register behind NAT", + "Register through NAT, detect NAT, re-REGISTER"); + + nh = nua_handle(nua, NULL, TAG_END()); + + nua_register(nh, TAG_END()); + + m = s2_wait_for_request(SIP_METHOD_REGISTER); + fail_if(!m); + fail_if(!m->sip->sip_contact || m->sip->sip_contact->m_next); + s2_save_register(m); + + s2_respond_to(m, NULL, + SIP_200_OK, + SIPTAG_CONTACT(s2->registration->contact), + SIPTAG_VIA(natted_via(m)), + TAG_END()); + s2_free_message(m); + + assert(s2->registration->contact != NULL); + s2_check_event(nua_r_register, 100); + + m = s2_wait_for_request(SIP_METHOD_REGISTER); + fail_if(!m); + fail_if(!m->sip->sip_contact || !m->sip->sip_contact->m_next); + s2_save_register(m); + + s2_respond_to(m, NULL, + SIP_200_OK, + SIPTAG_CONTACT(s2->registration->contact), + SIPTAG_VIA(natted_via(m)), + TAG_END()); + s2_free_message(m); + + fail_unless(s2->registration->contact != NULL); + fail_if(s2->registration->contact->m_next != NULL); + s2_check_event(nua_r_register, 200); + + s2->registration->nh = nh; + + s2_register_teardown(); + +} END_TEST + + +static nua_handle_t *make_auth_natted_register( + nua_handle_t *nh, + tag_type_t tag, tag_value_t value, ...) +{ + struct message *m; + + ta_list ta; + ta_start(ta, tag, value); + nua_register(nh, ta_tags(ta)); + ta_end(ta); + + m = s2_wait_for_request(SIP_METHOD_REGISTER); fail_if(!m); + s2_respond_to(m, NULL, + SIP_401_UNAUTHORIZED, + SIPTAG_WWW_AUTHENTICATE_STR(s2_auth_digest_str), + SIPTAG_VIA(natted_via(m)), + TAG_END()); + s2_free_message(m); + + s2_check_event(nua_r_register, 401); + + nua_authenticate(nh, NUTAG_AUTH(s2_auth_credentials), TAG_END()); + + m = s2_wait_for_request(SIP_METHOD_REGISTER); + fail_if(!m); + fail_if(!m->sip->sip_authorization); + s2_save_register(m); + + s2_respond_to(m, NULL, + SIP_200_OK, + SIPTAG_CONTACT(s2->registration->contact), + SIPTAG_VIA(natted_via(m)), + TAG_END()); + s2_free_message(m); + + assert(s2->registration->contact != NULL); + s2_check_event(nua_r_register, 200); + + return nh; +} + +START_TEST(register_1_2_2_1) +{ + nua_handle_t *nh = nua_handle(nua, NULL, TAG_END()); + + s2_case("1.2.2.1", "Register behind NAT", + "Authenticate, outbound activated"); + + mark_point(); + make_auth_natted_register(nh, TAG_END()); + s2->registration->nh = nh; + s2_register_teardown(); +} +END_TEST + +START_TEST(register_1_2_2_2) +{ + nua_handle_t *nh = nua_handle(nua, NULL, TAG_END()); + struct message *m; + + s2_case("1.2.2.2", "Register behind NAT", + "Authenticate, outbound activated, " + "authenticate OPTIONS probe, " + "NAT binding change"); + + mark_point(); + make_auth_natted_register(nh, TAG_END()); + s2->registration->nh = nh; + + mark_point(); + + m = s2_wait_for_request(SIP_METHOD_OPTIONS); + fail_if(!m); + s2_respond_to(m, NULL, + SIP_407_PROXY_AUTH_REQUIRED, + SIPTAG_VIA(natted_via(m)), + SIPTAG_PROXY_AUTHENTICATE_STR(s2_auth_digest_str), + TAG_END()); + s2_free_message(m); + mark_point(); + + m = s2_wait_for_request(SIP_METHOD_OPTIONS); + fail_if(!m); fail_if(!m->sip->sip_proxy_authorization); + s2_respond_to(m, NULL, + SIP_200_OK, + SIPTAG_VIA(natted_via(m)), + TAG_END()); + s2_free_message(m); + + su_root_step(s2->root, 20); su_root_step(s2->root, 20); + s2_fast_forward(120); /* Default keepalive interval */ + mark_point(); + + m = s2_wait_for_request(SIP_METHOD_OPTIONS); + s2_respond_to(m, NULL, + SIP_200_OK, + SIPTAG_VIA(natted_via(m)), + TAG_END()); + s2_free_message(m); + + su_root_step(s2->root, 20); su_root_step(s2->root, 20); + s2_fast_forward(120); /* Default keepalive interval */ + mark_point(); + + receive_natted = "received=4.255.255.10"; + + m = s2_wait_for_request(SIP_METHOD_OPTIONS); + s2_respond_to(m, NULL, + SIP_200_OK, + SIPTAG_VIA(natted_via(m)), + TAG_END()); + s2_free_message(m); + + s2_check_event(nua_i_outbound, 0); + + m = s2_wait_for_request(SIP_METHOD_REGISTER); + fail_if(!m); fail_if(!m->sip->sip_authorization); + fail_if(!m->sip->sip_contact || !m->sip->sip_contact->m_next); + s2_save_register(m); + + s2_respond_to(m, NULL, + SIP_200_OK, + SIPTAG_CONTACT(s2->registration->contact), + SIPTAG_VIA(natted_via(m)), + TAG_END()); + s2_free_message(m); + + s2_check_event(nua_r_register, 200); + + fail_unless(s2->registration->contact != NULL); + fail_if(s2->registration->contact->m_next != NULL); + + s2_register_teardown(); + +} END_TEST + + +START_TEST(register_1_2_2_3) +{ + nua_handle_t *nh = nua_handle(nua, NULL, TAG_END()); + struct message *m; + + s2_case("1.2.2.3", "Register behind NAT", + "Authenticate, outbound activated, " + "detect NAT binding change when re-REGISTERing"); + + mark_point(); + + make_auth_natted_register(nh, + NUTAG_OUTBOUND("no-options-keepalive"), + TAG_END()); + s2->registration->nh = nh; + + receive_natted = "received=4.255.255.10"; + + s2_fast_forward(3600); + mark_point(); + + m = s2_wait_for_request(SIP_METHOD_REGISTER); + fail_if(!m); fail_if(!m->sip->sip_authorization); + s2_save_register(m); + + s2_respond_to(m, NULL, + SIP_200_OK, + SIPTAG_CONTACT(s2->registration->contact), + SIPTAG_VIA(natted_via(m)), + TAG_END()); + s2_free_message(m); + + s2_check_event(nua_r_register, 100); + + m = s2_wait_for_request(SIP_METHOD_REGISTER); + fail_if(!m); fail_if(!m->sip->sip_authorization); + fail_if(!m->sip->sip_contact || !m->sip->sip_contact->m_next); + s2_save_register(m); + + s2_respond_to(m, NULL, + SIP_200_OK, + SIPTAG_CONTACT(s2->registration->contact), + SIPTAG_VIA(natted_via(m)), + TAG_END()); + s2_free_message(m); + + fail_unless(s2->registration->contact != NULL); + fail_if(s2->registration->contact->m_next != NULL); + + s2_check_event(nua_r_register, 200); + + s2_register_teardown(); + +} END_TEST + + +START_TEST(register_1_2_3) { + nua_handle_t *nh; + struct message *m; + + s2_case("1.2.3", "Register behind NAT", + "Outbound activated by error response"); + + nh = nua_handle(nua, NULL, TAG_END()); + nua_register(nh, TAG_END()); + + mark_point(); + + m = s2_wait_for_request(SIP_METHOD_REGISTER); + fail_if(!m); + fail_if(!m->sip->sip_contact || m->sip->sip_contact->m_next); + + s2_respond_to(m, NULL, + 400, "Bad Contact", + SIPTAG_VIA(natted_via(m)), + TAG_END()); + s2_free_message(m); + + s2_check_event(nua_r_register, 100); + + m = s2_wait_for_request(SIP_METHOD_REGISTER); + fail_if(!m); + s2_save_register(m); + + s2_respond_to(m, NULL, + SIP_200_OK, + SIPTAG_CONTACT(s2->registration->contact), + SIPTAG_VIA(natted_via(m)), + TAG_END()); + s2_free_message(m); + + fail_unless(s2->registration->contact != NULL); + fail_if(s2->registration->contact->m_next != NULL); + s2_check_event(nua_r_register, 200); + + s2->registration->nh = nh; + + s2_register_teardown(); + +} END_TEST + + +/* ---------------------------------------------------------------------- */ + +START_TEST(register_1_3_1) +{ + nua_handle_t *nh; + struct message *m; + + s2_case("1.3.1", "Register over TCP via NAT", + "REGISTER via TCP, detect NTA, re-REGISTER"); + + nh = nua_handle(nua, NULL, TAG_END()); + + nua_register(nh, NUTAG_PROXY(s2->tcp.contact->m_url), TAG_END()); + + m = s2_wait_for_request(SIP_METHOD_REGISTER); + fail_if(!m); fail_if(!m->sip->sip_contact || m->sip->sip_contact->m_next); + fail_if(!tport_is_tcp(m->tport)); + s2_save_register(m); + + s2_respond_to(m, NULL, + SIP_200_OK, + SIPTAG_CONTACT(s2->registration->contact), + SIPTAG_VIA(natted_via(m)), + TAG_END()); + s2_free_message(m); + + assert(s2->registration->contact != NULL); + s2_check_event(nua_r_register, 100); + + m = s2_wait_for_request(SIP_METHOD_REGISTER); + fail_if(!m); + fail_if(!m->sip->sip_contact || !m->sip->sip_contact->m_next); + s2_save_register(m); + + s2_respond_to(m, NULL, + SIP_200_OK, + SIPTAG_CONTACT(s2->registration->contact), + SIPTAG_VIA(natted_via(m)), + TAG_END()); + s2_free_message(m); + + fail_unless(s2->registration->contact != NULL); + fail_if(s2->registration->contact->m_next != NULL); + fail_unless( + url_has_param(s2->registration->contact->m_url, "transport=tcp")); + s2_check_event(nua_r_register, 200); + + s2->registration->nh = nh; + + s2_register_teardown(); + +} END_TEST + + +START_TEST(register_1_3_2_1) +{ + nua_handle_t *nh = nua_handle(nua, NULL, TAG_END()); + + s2_case("1.3.2.1", "Register behind NAT", + "Authenticate, outbound activated"); + + mark_point(); + s2->registration->nh = nh; + make_auth_natted_register(nh, NUTAG_PROXY(s2->tcp.contact->m_url), TAG_END()); + fail_if(!tport_is_tcp(s2->registration->tport)); + s2_register_teardown(); +} +END_TEST + + +START_TEST(register_1_3_2_2) +{ + nua_handle_t *nh = nua_handle(nua, NULL, TAG_END()); + struct message *m; + + s2_case("1.3.2.2", "Register behind NAT with TCP", + "Detect NAT over TCP using rport. " + "Authenticate, detect NAT, " + "close TCP at server, wait for re-REGISTERs."); + + nua_set_params(nua, NTATAG_TCP_RPORT(1), TAG_END()); + s2_check_event(nua_r_set_params, 200); + + mark_point(); + s2->registration->nh = nh; + make_auth_natted_register( + nh, NUTAG_PROXY(s2->tcp.contact->m_url), + NUTAG_OUTBOUND("no-options-keepalive, no-validate"), + TAG_END()); + fail_if(!tport_is_tcp(s2->registration->tport)); + tport_shutdown(s2->registration->tport, 2); + + m = s2_wait_for_request(SIP_METHOD_REGISTER); + fail_if(!m); fail_if(!m->sip->sip_authorization); + s2_save_register(m); + + s2_respond_to(m, NULL, + SIP_200_OK, + SIPTAG_CONTACT(s2->registration->contact), + SIPTAG_VIA(natted_via(m)), + TAG_END()); + s2_free_message(m); + + /* The "NAT binding" changed when new TCP connection is established */ + /* => NUA re-REGISTERs with newly detected contact */ + s2_check_event(nua_r_register, 100); + + m = s2_wait_for_request(SIP_METHOD_REGISTER); + fail_if(!m); fail_if(!m->sip->sip_authorization); + fail_if(!m->sip->sip_contact || !m->sip->sip_contact->m_next); + s2_save_register(m); + + s2_respond_to(m, NULL, + SIP_200_OK, + SIPTAG_CONTACT(s2->registration->contact), + SIPTAG_VIA(natted_via(m)), + TAG_END()); + s2_free_message(m); + + s2_check_event(nua_r_register, 200); + + fail_unless(s2->registration->contact != NULL); + fail_if(s2->registration->contact->m_next != NULL); + + s2_register_teardown(); +} +END_TEST + +START_TEST(register_1_3_3_1) +{ + nua_handle_t *nh = nua_handle(nua, NULL, TAG_END()); + struct message *m; + tport_t *tcp; + + s2_case("1.3.3.1", "Register behind NAT with UDP and TCP", + "Register with UDP, UDP time-outing, then w/ TCP using rport. "); + + nua_set_params(nua, NTATAG_TCP_RPORT(1), TAG_END()); + s2_check_event(nua_r_set_params, 200); + + mark_point(); + s2->registration->nh = nh; + + nua_register(nh, + NUTAG_OUTBOUND("no-options-keepalive, no-validate"), + TAG_END()); + + /* NTA tries with UDP, we drop them */ + for (;;) { + m = s2_wait_for_request(SIP_METHOD_REGISTER); fail_if(!m); + if (!tport_is_udp(m->tport)) /* Drop UDP */ + break; + s2_free_message(m); + s2_fast_forward(4); + } + + tcp = tport_ref(m->tport); + + /* Respond to request over TCP */ + s2_respond_to(m, NULL, + SIP_401_UNAUTHORIZED, + SIPTAG_WWW_AUTHENTICATE_STR(s2_auth_digest_str), + SIPTAG_VIA(natted_via(m)), + TAG_END()); + s2_free_message(m); + s2_check_event(nua_r_register, 401); + nua_authenticate(nh, NUTAG_AUTH(s2_auth_credentials), TAG_END()); + + /* Turn off pong */ + tport_set_params(tcp, TPTAG_PONG2PING(0), TAG_END()); + + /* Now request over UDP ... registering TCP contact! */ + m = s2_wait_for_request(SIP_METHOD_REGISTER); + fail_if(!m); fail_if(!m->sip->sip_authorization); + s2_save_register(m); + fail_unless( + url_has_param(s2->registration->contact->m_url, "transport=tcp")); + s2_respond_to(m, NULL, + SIP_200_OK, + SIPTAG_CONTACT(s2->registration->contact), + SIPTAG_VIA(natted_via(m)), + TAG_END()); + s2_free_message(m); + + /* NUA detects oops... re-registers UDP */ + s2_check_event(nua_r_register, 100); + + m = s2_wait_for_request(SIP_METHOD_REGISTER); + fail_if(!m); fail_if(!m->sip->sip_authorization); + fail_if(!m->sip->sip_contact || !m->sip->sip_contact->m_next); + s2_save_register(m); + + s2_respond_to(m, NULL, + SIP_200_OK, + SIPTAG_CONTACT(s2->registration->contact), + SIPTAG_VIA(natted_via(m)), + TAG_END()); + s2_free_message(m); + + s2_check_event(nua_r_register, 200); + + fail_unless(s2->registration->contact != NULL); + fail_if(s2->registration->contact->m_next != NULL); + + /* Wait until ping-pong failure closes the TCP connection */ + { + int i; + for (i = 0; i < 5; i++) { + su_root_step(s2->root, 5); + su_root_step(s2->root, 5); + su_root_step(s2->root, 5); + s2_fast_forward(5); + } + } + + s2_register_teardown(); +} +END_TEST + +/* ---------------------------------------------------------------------- */ + +TCase *register_tcase(void) +{ + TCase *tc = tcase_create("1 - REGISTER"); + /* Each testcase is run in different process */ + tcase_add_checked_fixture(tc, register_setup, register_teardown); + { + tcase_add_test(tc, register_1_0_1); + tcase_add_test(tc, register_1_1_1); + tcase_add_test(tc, register_1_1_2); + tcase_add_test(tc, register_1_2_1); + tcase_add_test(tc, register_1_2_2_1); + tcase_add_test(tc, register_1_2_2_2); + tcase_add_test(tc, register_1_2_2_3); + tcase_add_test(tc, register_1_2_3); + tcase_add_test(tc, register_1_3_1); + tcase_add_test(tc, register_1_3_2_1); + tcase_add_test(tc, register_1_3_2_2); + } + tcase_set_timeout(tc, 5); + return tc; +} + +TCase *pingpong_tcase(void) +{ + TCase *tc = tcase_create("1 - REGISTER with PingPong"); + /* Each testcase is run in different process */ + tcase_add_checked_fixture(tc, register_pingpong_setup, register_teardown); + { + tcase_add_test(tc, register_1_3_3_1); + } + tcase_set_timeout(tc, 5); + return tc; +} + +void check_register_cases(Suite *suite) +{ + suite_add_tcase(suite, register_tcase()); + suite_add_tcase(suite, pingpong_tcase()); +} + Added: freeswitch/trunk/libs/sofia-sip/libsofia-sip-ua/nua/check_session.c ============================================================================== --- (empty file) +++ freeswitch/trunk/libs/sofia-sip/libsofia-sip-ua/nua/check_session.c Wed May 14 15:10:54 2008 @@ -0,0 +1,1564 @@ +/* + * This file is part of the Sofia-SIP package + * + * Copyright (C) 2008 Nokia Corporation. + * + * Contact: Pekka Pessi <pekka.pessi@nokia.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public License + * as published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + */ + +/**@CFILE check_session.c + * + * @brief NUA module tests for SIP session handling + * + * @author Pekka Pessi <Pekka.Pessi@nokia.com> + * + * @copyright (C) 2008 Nokia Corporation. + */ + +#include "config.h" + +#include "check_nua.h" + +#include "test_s2.h" + +#include <sofia-sip/sip_status.h> +#include <sofia-sip/sip_header.h> +#include <sofia-sip/soa.h> +#include <sofia-sip/su_tagarg.h> +#include <sofia-sip/su_tag_io.h> + +#include <stdlib.h> +#include <string.h> +#include <assert.h> + +/* ====================================================================== */ +/* Call cases */ + +static nua_t *nua; +static soa_session_t *soa = NULL; +static struct dialog *dialog = NULL; + +#define CRLF "\r\n" + +static void call_setup(void) +{ + s2_case("0.1.1", "Setup for Call Tests", ""); + + nua = s2_nua_setup(TAG_END()); + + soa = soa_create(NULL, s2->root, NULL); + + fail_if(!soa); + + soa_set_params(soa, + SOATAG_USER_SDP_STR("m=audio 5008 RTP/AVP 8 0" CRLF + "m=video 5010 RTP/AVP 34" CRLF), + TAG_END()); + + dialog = su_home_new(sizeof *dialog); fail_if(!dialog); + + s2_register_setup(); +} + +static void call_teardown(void) +{ + s2_case("0.1.2", "Teardown Call Test Setup", ""); + + mark_point(); + + s2_register_teardown(); + + nua_shutdown(nua); + fail_unless(s2_check_event(nua_r_shutdown, 200)); + + s2_nua_teardown(); +} + +static void save_sdp_to_soa(struct message *message) +{ + sip_payload_t *pl; + char const *body; + isize_t bodylen; + + fail_if(!message); + + fail_if(!message->sip->sip_content_length); + fail_if(!message->sip->sip_content_type); + fail_if(strcmp(message->sip->sip_content_type->c_type, + "application/sdp")); + + fail_if(!message->sip->sip_payload); + pl = message->sip->sip_payload; + body = pl->pl_data, bodylen = pl->pl_len; + + fail_if(soa_set_remote_sdp(soa, NULL, body, (issize_t)bodylen) < 0); +} + +static void process_offer(struct message *message) +{ + save_sdp_to_soa(message); + fail_if(soa_generate_answer(soa, NULL) < 0); +} + +static void process_answer(struct message *message) +{ + save_sdp_to_soa(message); + fail_if(soa_process_answer(soa, NULL) < 0); +} + +static void +respond_with_sdp(struct message *request, + struct dialog *dialog, + int status, char const *phrase, + tag_type_t tag, tag_value_t value, ...) +{ + ta_list ta; + + char const *body; + isize_t bodylen; + + fail_if(soa_get_local_sdp(soa, NULL, &body, &bodylen) != 1); + + ta_start(ta, tag, value); + s2_respond_to(request, dialog, status, phrase, + SIPTAG_CONTENT_TYPE_STR("application/sdp"), + SIPTAG_PAYLOAD_STR(body), + SIPTAG_CONTENT_DISPOSITION_STR("session"), + ta_tags(ta)); + ta_end(ta); +} + +static void +request_with_sdp(struct dialog *dialog, + sip_method_t method, char const *name, + tport_t *tport, + tag_type_t tag, tag_value_t value, ...) +{ + ta_list ta; + + char const *body; + isize_t bodylen; + + fail_if(soa_get_local_sdp(soa, NULL, &body, &bodylen) != 1); + + ta_start(ta, tag, value); + fail_if( + s2_request_to(dialog, method, name, tport, + SIPTAG_CONTENT_TYPE_STR("application/sdp"), + SIPTAG_PAYLOAD_STR(body), + ta_tags(ta))); + ta_end(ta); +} + +static struct message * +invite_sent_by_nua(nua_handle_t *nh, + tag_type_t tag, tag_value_t value, ...) +{ + ta_list ta; + ta_start(ta, tag, value); + nua_invite(nh, SOATAG_USER_SDP_STR("m=audio 5004 RTP/AVP 0 8"), + ta_tags(ta)); + ta_end(ta); + + fail_unless(s2_check_callstate(nua_callstate_calling)); + + return s2_wait_for_request(SIP_METHOD_INVITE); +} + +static struct message * +respond_with_100rel(struct message *invite, + struct dialog *d, + int sdp, + int status, char const *phrase, + tag_type_t tag, tag_value_t value, ...) +{ + struct message *prack; + ta_list ta; + static uint32_t rseq; + sip_rseq_t rs[1]; + + assert(100 < status && status < 200); + + sip_rseq_init(rs); + rs->rs_response = ++rseq; + + ta_start(ta, tag, value); + + if (sdp) { + respond_with_sdp( + invite, dialog, status, phrase, + SIPTAG_REQUIRE_STR("100rel"), + SIPTAG_RSEQ(rs), + ta_tags(ta)); + } + else { + s2_respond_to( + invite, dialog, status, phrase, + SIPTAG_REQUIRE_STR("100rel"), + SIPTAG_RSEQ(rs), + ta_tags(ta)); + } + ta_end(ta); + + fail_unless(s2_check_event(nua_r_invite, status)); + + prack = s2_wait_for_request(SIP_METHOD_PRACK); + /* Assumes auto-prack, so there is no offer in prack */ + s2_respond_to(prack, dialog, SIP_200_OK, TAG_END()); + + return prack; +} + +static void +invite_by_nua(nua_handle_t *nh, + tag_type_t tag, tag_value_t value, ...) +{ + struct message *invite; + ta_list ta; + + ta_start(ta, tag, value); + invite = invite_sent_by_nua( + nh, SOATAG_USER_SDP_STR("m=audio 5004 RTP/AVP 0 8"), + ta_tags(ta)); + ta_end(ta); + + process_offer(invite); + respond_with_sdp( + invite, dialog, SIP_180_RINGING, + SIPTAG_CONTENT_DISPOSITION_STR("session;handling=optional"), + TAG_END()); + + fail_unless(s2_check_event(nua_r_invite, 180)); + fail_unless(s2_check_callstate(nua_callstate_proceeding)); + + respond_with_sdp(invite, dialog, SIP_200_OK, TAG_END()); + s2_free_message(invite); + fail_unless(s2_check_event(nua_r_invite, 200)); + fail_unless(s2_check_callstate(nua_callstate_ready)); + fail_unless(s2_check_request(SIP_METHOD_ACK)); +} + +static nua_handle_t * +invite_to_nua(tag_type_t tag, tag_value_t value, ...) +{ + ta_list ta; + struct event *invite; + struct message *response; + nua_handle_t *nh; + sip_cseq_t cseq[1]; + + soa_generate_offer(soa, 1, NULL); + + ta_start(ta, tag, value); + request_with_sdp(dialog, SIP_METHOD_INVITE, NULL, ta_tags(ta)); + ta_end(ta); + + invite = s2_wait_for_event(nua_i_invite, 100); fail_unless(invite != NULL); + fail_unless(s2_check_callstate(nua_callstate_received)); + + nh = invite->nh; + fail_if(!nh); + + sip_cseq_init(cseq); + cseq->cs_method = sip_method_ack; + cseq->cs_method_name = "ACK"; + cseq->cs_seq = sip_object(invite->data->e_msg)->sip_cseq->cs_seq; + + s2_free_event(invite); + + response = s2_wait_for_response(100, SIP_METHOD_INVITE); + fail_if(!response); + + nua_respond(nh, SIP_180_RINGING, + SOATAG_USER_SDP_STR("m=audio 5004 RTP/AVP 0 8"), + TAG_END()); + fail_unless(s2_check_callstate(nua_callstate_early)); + + response = s2_wait_for_response(180, SIP_METHOD_INVITE); + fail_if(!response); + s2_update_dialog(dialog, response); + process_answer(response); + s2_free_message(response); + + nua_respond(nh, SIP_200_OK, TAG_END()); + + fail_unless(s2_check_callstate(nua_callstate_completed)); + + response = s2_wait_for_response(200, SIP_METHOD_INVITE); + + fail_if(!response); + s2_update_dialog(dialog, response); + s2_free_message(response); + + fail_if(s2_request_to(dialog, SIP_METHOD_ACK, NULL, + SIPTAG_CSEQ(cseq), TAG_END())); + + fail_unless(s2_check_event(nua_i_ack, 200)); + fail_unless(s2_check_callstate(nua_callstate_ready)); + + return nh; +} + +static void +bye_by_nua(nua_handle_t *nh, + tag_type_t tag, tag_value_t value, ...) +{ + ta_list ta; + struct message *bye; + + ta_start(ta, tag, value); + nua_bye(nh, ta_tags(ta)); + ta_end(ta); + + bye = s2_wait_for_request(SIP_METHOD_BYE); + fail_if(!bye); + s2_respond_to(bye, dialog, SIP_200_OK, TAG_END()); + s2_free_message(bye); + fail_unless(s2_check_event(nua_r_bye, 200)); + fail_unless(s2_check_callstate(nua_callstate_terminated)); +} + +static void +bye_by_nua_challenged(nua_handle_t *nh, + tag_type_t tag, tag_value_t value, ...) +{ + ta_list ta; + struct message *bye; + + s2_flush_events(); + + ta_start(ta, tag, value); + nua_bye(nh, ta_tags(ta)); + ta_end(ta); + + bye = s2_wait_for_request(SIP_METHOD_BYE); + fail_if(!bye); + s2_respond_to(bye, dialog, SIP_407_PROXY_AUTH_REQUIRED, + SIPTAG_PROXY_AUTHENTICATE_STR(s2_auth_digest_str), + TAG_END()); + s2_free_message(bye); + fail_unless(s2_check_event(nua_r_bye, 407)); + + nua_authenticate(nh, NUTAG_AUTH("Digest:\"s2test\":abc:abc"), TAG_END()); + bye = s2_wait_for_request(SIP_METHOD_BYE); + fail_if(!bye); + s2_respond_to(bye, dialog, SIP_200_OK, TAG_END()); + s2_free_message(bye); + fail_unless(s2_check_event(nua_r_bye, 200)); + fail_unless(s2_check_callstate(nua_callstate_terminated)); + fail_if(s2->events); +} + + +static void +cancel_by_nua(nua_handle_t *nh, + struct message *invite, + struct dialog *dialog, + tag_type_t tag, tag_value_t value, ...) +{ + ta_list ta; + struct message *cancel; + + ta_start(ta, tag, value); + nua_cancel(nh, ta_tags(ta)); + ta_end(ta); + + cancel = s2_wait_for_request(SIP_METHOD_CANCEL); + fail_if(!cancel); + s2_respond_to(cancel, dialog, SIP_200_OK, TAG_END()); + s2_free_message(cancel); + fail_unless(s2_check_event(nua_r_cancel, 200)); + + s2_respond_to(invite, dialog, SIP_487_REQUEST_CANCELLED, TAG_END()); + fail_unless(s2_check_request(SIP_METHOD_ACK)); + + fail_unless(s2_check_event(nua_r_invite, 487)); +} + +static void +bye_to_nua(nua_handle_t *nh, + tag_type_t tag, tag_value_t value, ...) +{ + ta_list ta; + + ta_start(ta, tag, value); + fail_if(s2_request_to(dialog, SIP_METHOD_BYE, NULL, ta_tags(ta))); + ta_end(ta); + + fail_unless(s2_check_event(nua_i_bye, 200)); + fail_unless(s2_check_callstate(nua_callstate_terminated)); + fail_unless(s2_check_response(200, SIP_METHOD_BYE)); +} + +/* ====================================================================== */ +/* 2 - Call cases */ + +/* 2.1 - Basic call cases */ + +START_TEST(basic_call_with_bye_by_nua) +{ + nua_handle_t *nh; + + s2_case("2.1.1", "Basic call", + "NUA sends INVITE, NUA sends BYE"); + + nh = nua_handle(nua, NULL, SIPTAG_TO(s2->local), TAG_END()); + + invite_by_nua(nh, TAG_END()); + + bye_by_nua(nh, TAG_END()); + + nua_handle_destroy(nh); +} +END_TEST + + +START_TEST(basic_call_with_bye_to_nua) +{ + nua_handle_t *nh; + + s2_case("2.1.2", "Basic call", + "NUA sends INVITE, NUA receives BYE"); + + nh = nua_handle(nua, NULL, SIPTAG_TO(s2->local), TAG_END()); + + invite_by_nua(nh, TAG_END()); + + bye_to_nua(nh, TAG_END()); + + nua_handle_destroy(nh); +} +END_TEST + + +START_TEST(call_to_nua_with_bye_to_nua) +{ + nua_handle_t *nh; + + s2_case("2.1.3", "Incoming call", + "NUA receives INVITE and BYE"); + + nh = invite_to_nua(TAG_END()); + + bye_to_nua(nh, TAG_END()); + + nua_handle_destroy(nh); +} +END_TEST + + +START_TEST(call_to_nua_with_bye_by_nua) +{ + nua_handle_t *nh; + + s2_case("2.1.4", "Incoming call", + "NUA receives INVITE and sends BYE"); + + nh = invite_to_nua(TAG_END()); + + bye_by_nua(nh, TAG_END()); + + nua_handle_destroy(nh); +} +END_TEST + + +START_TEST(call_to_nua_with_bye_by_nua_challenged) +{ + nua_handle_t *nh; + + s2_case("2.1.5", "Incoming call", + "NUA receives INVITE and sends BYE, BYE is challenged"); + + nh = invite_to_nua(TAG_END()); + + bye_by_nua_challenged(nh, TAG_END()); + + nua_handle_destroy(nh); +} +END_TEST + + +START_TEST(call_2_1_6) +{ + nua_handle_t *nh; + struct message *bye; + struct event *invite; + struct message *response; + sip_cseq_t cseq[1]; + + s2_case("2.1.6", "Basic call", + "NUA received INVITE, " + "NUA responds (and saves proxy for dialog), " + "NUA sends BYE"); + + soa_generate_offer(soa, 1, NULL); + + request_with_sdp(dialog, SIP_METHOD_INVITE, NULL, TAG_END()); + + invite = s2_wait_for_event(nua_i_invite, 100); fail_unless(invite != NULL); + fail_unless(s2_check_callstate(nua_callstate_received)); + + nh = invite->nh; + fail_if(!nh); + + sip_cseq_init(cseq); + cseq->cs_method = sip_method_ack; + cseq->cs_method_name = "ACK"; + cseq->cs_seq = sip_object(invite->data->e_msg)->sip_cseq->cs_seq; + + s2_free_event(invite); + + response = s2_wait_for_response(100, SIP_METHOD_INVITE); + fail_if(!response); + + nua_respond(nh, SIP_180_RINGING, + /* Dialog-specific proxy is saved */ + NUTAG_PROXY(s2->tcp.contact->m_url), + SOATAG_USER_SDP_STR("m=audio 5004 RTP/AVP 0 8"), + TAG_END()); + fail_unless(s2_check_callstate(nua_callstate_early)); + + response = s2_wait_for_response(180, SIP_METHOD_INVITE); + fail_if(!response); + s2_update_dialog(dialog, response); + process_answer(response); + s2_free_message(response); + + nua_respond(nh, SIP_200_OK, TAG_END()); + + fail_unless(s2_check_callstate(nua_callstate_completed)); + + response = s2_wait_for_response(200, SIP_METHOD_INVITE); + + fail_if(!response); + s2_update_dialog(dialog, response); + s2_free_message(response); + + fail_if(s2_request_to(dialog, SIP_METHOD_ACK, NULL, + SIPTAG_CSEQ(cseq), TAG_END())); + + fail_unless(s2_check_event(nua_i_ack, 200)); + fail_unless(s2_check_callstate(nua_callstate_ready)); + + nua_bye(nh, TAG_END()); + bye = s2_wait_for_request(SIP_METHOD_BYE); + fail_if(!bye); + /* Check that NUA used dialog-specific proxy with BYE */ + fail_unless(tport_is_tcp(bye->tport)); + s2_respond_to(bye, dialog, SIP_200_OK, TAG_END()); + s2_free_message(bye); + fail_unless(s2_check_event(nua_r_bye, 200)); + fail_unless(s2_check_callstate(nua_callstate_terminated)); + + nua_handle_destroy(nh); +} +END_TEST + + +TCase *invite_tcase(void) +{ + TCase *tc = tcase_create("2.1 - Basic INVITE"); + tcase_add_checked_fixture(tc, call_setup, call_teardown); + { + tcase_add_test(tc, basic_call_with_bye_by_nua); + tcase_add_test(tc, basic_call_with_bye_to_nua); + tcase_add_test(tc, call_to_nua_with_bye_to_nua); + tcase_add_test(tc, call_to_nua_with_bye_by_nua); + tcase_add_test(tc, call_to_nua_with_bye_by_nua_challenged); + tcase_add_test(tc, call_2_1_6); + } + return tc; +} + +/* ---------------------------------------------------------------------- */ +/* 2.2 - Call CANCEL cases */ + +START_TEST(cancel_outgoing) +{ + nua_handle_t *nh; + struct message *invite, *cancel; + + s2_case("2.2.1", "Cancel call", + "NUA is callee, NUA sends CANCEL immediately"); + + nh = nua_handle(nua, NULL, SIPTAG_TO(s2->local), TAG_END()); + + nua_invite(nh, SOATAG_USER_SDP_STR("m=audio 5004 RTP/AVP 0 8"), + TAG_END()); + fail_unless(s2_check_callstate(nua_callstate_calling)); + nua_cancel(nh, TAG_END()); + + invite = s2_wait_for_request(SIP_METHOD_INVITE); + fail_if(!invite); + fail_if(s2->received != NULL); + s2_respond_to(invite, dialog, SIP_100_TRYING, TAG_END()); + cancel = s2_wait_for_request(SIP_METHOD_CANCEL); + fail_if(!cancel); + s2_respond_to(invite, dialog, SIP_487_REQUEST_CANCELLED, TAG_END()); + s2_respond_to(cancel, dialog, SIP_200_OK, TAG_END()); + + fail_unless(s2_check_request(SIP_METHOD_ACK)); + + fail_unless(s2_check_event(nua_r_invite, 487)); + fail_unless(s2_check_callstate(nua_callstate_terminated)); + fail_unless(s2_check_event(nua_r_cancel, 200)); + fail_if(s2->events != NULL); + + nua_handle_destroy(nh); +} +END_TEST + + +START_TEST(cancel_outgoing_after_100) +{ + nua_handle_t *nh; + struct message *invite; + + s2_case("2.2.2", "Canceled call", + "NUA is callee, NUA sends CANCEL after receiving 100"); + + nh = nua_handle(nua, NULL, SIPTAG_TO(s2->local), TAG_END()); + + nua_invite(nh, SOATAG_USER_SDP_STR("m=audio 5004 RTP/AVP 0 8"), + TAG_END()); + fail_unless(s2_check_callstate(nua_callstate_calling)); + + invite = s2_wait_for_request(SIP_METHOD_INVITE); + process_offer(invite); + s2_respond_to(invite, dialog, SIP_100_TRYING, TAG_END()); + + cancel_by_nua(nh, invite, dialog, TAG_END()); + + fail_unless(s2_check_callstate(nua_callstate_terminated)); + + nua_handle_destroy(nh); +} +END_TEST + + +START_TEST(cancel_outgoing_after_180) +{ + nua_handle_t *nh; + struct message *invite; + + s2_case("2.2.3", "Canceled call", + "NUA is callee, NUA sends CANCEL after receiving 180"); + + nh = nua_handle(nua, NULL, SIPTAG_TO(s2->local), TAG_END()); + + nua_invite(nh, SOATAG_USER_SDP_STR("m=audio 5004 RTP/AVP 0 8"), + TAG_END()); + fail_unless(s2_check_callstate(nua_callstate_calling)); + + invite = s2_wait_for_request(SIP_METHOD_INVITE); + process_offer(invite); + respond_with_sdp( + invite, dialog, SIP_180_RINGING, + SIPTAG_CONTENT_DISPOSITION_STR("session;handling=optional"), + TAG_END()); + fail_unless(s2_check_event(nua_r_invite, 180)); + fail_unless(s2_check_callstate(nua_callstate_proceeding)); + + cancel_by_nua(nh, invite, dialog, TAG_END()); + fail_unless(s2_check_callstate(nua_callstate_terminated)); + + nua_handle_destroy(nh); +} +END_TEST + + +START_TEST(cancel_outgoing_glare) +{ + nua_handle_t *nh; + struct message *invite, *cancel; + + s2_case("2.2.4", "Cancel and 200 OK glare", + "NUA is callee, NUA sends CANCEL after receiving 180 " + "but UAS already sent 200 OK."); + + nh = nua_handle(nua, NULL, SIPTAG_TO(s2->local), TAG_END()); + + nua_invite(nh, SOATAG_USER_SDP_STR("m=audio 5004 RTP/AVP 0 8"), + TAG_END()); + fail_unless(s2_check_callstate(nua_callstate_calling)); + + invite = s2_wait_for_request(SIP_METHOD_INVITE); + process_offer(invite); + respond_with_sdp( + invite, dialog, SIP_180_RINGING, + SIPTAG_CONTENT_DISPOSITION_STR("session;handling=optional"), + TAG_END()); + fail_unless(s2_check_event(nua_r_invite, 180)); + fail_unless(s2_check_callstate(nua_callstate_proceeding)); + + nua_cancel(nh, TAG_END()); + cancel = s2_wait_for_request(SIP_METHOD_CANCEL); + fail_if(!cancel); + + respond_with_sdp(invite, dialog, SIP_200_OK, TAG_END()); + + s2_respond_to(cancel, dialog, SIP_481_NO_TRANSACTION, TAG_END()); + s2_free_message(cancel); + fail_unless(s2_check_event(nua_r_cancel, 481)); + + fail_unless(s2_check_request(SIP_METHOD_ACK)); + + fail_unless(s2_check_callstate(nua_callstate_ready)); + + bye_by_nua(nh, TAG_END()); + + nua_handle_destroy(nh); +} +END_TEST + + +TCase *cancel_tcase(void) +{ + TCase *tc = tcase_create("2.2 - CANCEL"); + tcase_add_checked_fixture(tc, call_setup, call_teardown); + + tcase_add_test(tc, cancel_outgoing); + tcase_add_test(tc, cancel_outgoing_after_100); + tcase_add_test(tc, cancel_outgoing_after_180); + tcase_add_test(tc, cancel_outgoing_glare); + + return tc; +} + + +/* ---------------------------------------------------------------------- */ +/* 2.3 - Session timers */ + +static void invite_timer_round(nua_handle_t *nh, + char const *session_expires) +{ + struct message *invite, *ack; + + fail_unless(s2_check_callstate(nua_callstate_calling)); + invite = s2_wait_for_request(SIP_METHOD_INVITE); + process_offer(invite); + respond_with_sdp( + invite, dialog, SIP_200_OK, + SIPTAG_SESSION_EXPIRES_STR(session_expires), + SIPTAG_REQUIRE_STR("timer"), + TAG_END()); + s2_free_message(invite); + fail_unless(s2_check_event(nua_r_invite, 200)); + fail_unless(s2_check_callstate(nua_callstate_ready)); + ack = s2_wait_for_request(SIP_METHOD_ACK); + s2_free_message(ack); +} + +START_TEST(call_to_nua_with_timer) +{ + nua_handle_t *nh; + + s2_case("2.3.1", "Incoming call with call timers", + "NUA receives INVITE, " + "activates call timers, " + "sends re-INVITE twice, " + "sends BYE."); + + nh = invite_to_nua( + SIPTAG_SESSION_EXPIRES_STR("300;refresher=uas"), + SIPTAG_REQUIRE_STR("timer"), + TAG_END()); + + s2_fast_forward(300); + invite_timer_round(nh, "300;refresher=uac"); + s2_fast_forward(300); + invite_timer_round(nh, "300;refresher=uac"); + + bye_by_nua(nh, TAG_END()); + + nua_handle_destroy(nh); +} +END_TEST + +START_TEST(call_to_nua_with_timer_2) +{ + nua_handle_t *nh; + + s2_case("2.3.2", "Incoming call with call timers", + "NUA receives INVITE, " + "activates call timers, " + "sends re-INVITE, " + "sends BYE."); + + nh = invite_to_nua( + SIPTAG_SESSION_EXPIRES_STR("300;refresher=uas"), + SIPTAG_REQUIRE_STR("timer"), + TAG_END()); + + s2_fast_forward(300); + invite_timer_round(nh, "300"); + s2_fast_forward(300); + invite_timer_round(nh, "300"); + + bye_by_nua(nh, TAG_END()); + + nua_handle_destroy(nh); +} +END_TEST + + +TCase *session_timer_tcase(void) +{ + TCase *tc = tcase_create("2.3 - Session timers"); + tcase_add_checked_fixture(tc, call_setup, call_teardown); + { + tcase_add_test(tc, call_to_nua_with_timer); + tcase_add_test(tc, call_to_nua_with_timer_2); + } + return tc; +} + +/* ====================================================================== */ +/* 2.4 - 100rel */ + +START_TEST(call_with_prack_by_nua) +{ + nua_handle_t *nh; + struct message *invite, *prack; + + s2_case("2.4.1", "Call with 100rel", + "NUA sends INVITE, " + "receives 183, sends PRACK, receives 200 for it, " + "receives 180, sends PRACK, receives 200 for it, " + "receives 200, send ACK."); + + nh = nua_handle(nua, NULL, SIPTAG_TO(s2->local), TAG_END()); + + invite = invite_sent_by_nua( + nh, SOATAG_USER_SDP_STR("m=audio 5004 RTP/AVP 0 8"), + TAG_END()); + process_offer(invite); + + prack = respond_with_100rel(invite, dialog, 1, + SIP_183_SESSION_PROGRESS, + TAG_END()); + s2_free_message(prack), prack = NULL; + fail_unless(s2_check_callstate(nua_callstate_proceeding)); + fail_unless(s2_check_event(nua_r_prack, 200)); + + prack = respond_with_100rel(invite, dialog, 0, + SIP_180_RINGING, + TAG_END()); + s2_free_message(prack), prack = NULL; + fail_unless(s2_check_callstate(nua_callstate_proceeding)); + fail_unless(s2_check_event(nua_r_prack, 200)); + + s2_respond_to(invite, dialog, SIP_200_OK, TAG_END()); + s2_free_message(invite); + fail_unless(s2_check_event(nua_r_invite, 200)); + fail_unless(s2_check_callstate(nua_callstate_ready)); + fail_unless(s2_check_request(SIP_METHOD_ACK)); + + bye_to_nua(nh, TAG_END()); + + nua_handle_destroy(nh); +} +END_TEST + +START_TEST(call_with_prack_sans_soa) +{ + nua_handle_t *nh; + struct message *invite, *prack; + + s2_case("2.4.1", "Call with 100rel", + "NUA sends INVITE, " + "receives 183, sends PRACK, receives 200 for it, " + "receives 180, sends PRACK, receives 200 for it, " + "receives 200, send ACK."); + + nh = nua_handle(nua, NULL, SIPTAG_TO(s2->local), TAG_END()); + + invite = invite_sent_by_nua( + nh, + NUTAG_MEDIA_ENABLE(0), + SIPTAG_CONTENT_TYPE_STR("application/sdp"), + SIPTAG_PAYLOAD_STR( + "v=0" CRLF + "o=- 6805647540234172778 5821668777690722690 IN IP4 127.0.0.1" CRLF + "s=-" CRLF + "c=IN IP4 127.0.0.1" CRLF + "m=audio 5004 RTP/AVP 0 8" CRLF), + TAG_END()); + + prack = respond_with_100rel(invite, dialog, 0, + SIP_183_SESSION_PROGRESS, + TAG_END()); + s2_free_message(prack), prack = NULL; + fail_unless(s2_check_callstate(nua_callstate_proceeding)); + fail_unless(s2_check_event(nua_r_prack, 200)); + + prack = respond_with_100rel(invite, dialog, 0, + SIP_180_RINGING, + TAG_END()); + s2_free_message(prack), prack = NULL; + fail_unless(s2_check_callstate(nua_callstate_proceeding)); + fail_unless(s2_check_event(nua_r_prack, 200)); + + s2_respond_to(invite, dialog, SIP_200_OK, TAG_END()); + s2_free_message(invite); + fail_unless(s2_check_event(nua_r_invite, 200)); + fail_unless(s2_check_callstate(nua_callstate_ready)); + fail_unless(s2_check_request(SIP_METHOD_ACK)); + + bye_to_nua(nh, TAG_END()); + + nua_handle_destroy(nh); +} +END_TEST + +TCase *invite_100rel_tcase(void) +{ + TCase *tc = tcase_create("2.4 - INVITE with 100rel"); + tcase_add_checked_fixture(tc, call_setup, call_teardown); + { + tcase_add_test(tc, call_with_prack_by_nua); + tcase_add_test(tc, call_with_prack_sans_soa); + } + return tc; +} + +/* ====================================================================== */ +/* 3.1 - Call error cases */ + +START_TEST(call_forbidden) +{ + nua_handle_t *nh; + struct message *invite; + + s2_case("3.1.1", "Call failure", "Call fails with 403 response"); + nh = nua_handle(nua, NULL, SIPTAG_TO(s2->local), + TAG_END()); + + nua_invite(nh, SOATAG_USER_SDP_STR("m=audio 5004 RTP/AVP 0 8"), + TAG_END()); + + fail_unless(s2_check_callstate(nua_callstate_calling)); + + invite = s2_wait_for_request(SIP_METHOD_INVITE); + fail_if(!invite); + s2_respond_to(invite, NULL, SIP_403_FORBIDDEN, TAG_END()); + s2_free_message(invite); + + fail_unless(s2_check_request(SIP_METHOD_ACK)); + fail_unless(s2_check_event(nua_r_invite, 403)); + fail_unless(s2_check_callstate(nua_callstate_terminated)); + + nua_handle_destroy(nh); +} +END_TEST + + +START_TEST(too_many_retrys) +{ + nua_handle_t *nh; + struct message *invite; + int i; + + s2_case("3.1.2", "Call fails after too many retries", + "Call fails after 4 times 500 Retry-After"); + + nh = nua_handle(nua, NULL, SIPTAG_TO(s2->local), + NUTAG_RETRY_COUNT(3), + TAG_END()); + + nua_invite(nh, SOATAG_USER_SDP_STR("m=audio 5004 RTP/AVP 0 8"), + TAG_END()); + + for (i = 0;; i++) { + fail_unless(s2_check_callstate(nua_callstate_calling)); + invite = s2_wait_for_request(SIP_METHOD_INVITE); + fail_if(!invite); + s2_respond_to(invite, NULL, SIP_500_INTERNAL_SERVER_ERROR, + SIPTAG_RETRY_AFTER_STR("5"), + TAG_END()); + s2_free_message(invite); + fail_unless(s2_check_request(SIP_METHOD_ACK)); + if (i == 3) + break; + fail_unless(s2_check_event(nua_r_invite, 100)); + s2_fast_forward(5); + } + + fail_unless(s2_check_event(nua_r_invite, 500)); + fail_unless(s2_check_callstate(nua_callstate_terminated)); + + nua_handle_destroy(nh); +} +END_TEST + +START_TEST(reinvite_forbidden) +{ + nua_handle_t *nh; + struct message *invite; + + s2_case("3.2.1", "Re-INVITE failure", "Re-INVITE fails with 403 response"); + nh = nua_handle(nua, NULL, SIPTAG_TO(s2->local), + TAG_END()); + + invite_by_nua(nh, TAG_END()); + + nua_invite(nh, TAG_END()); + + fail_unless(s2_check_callstate(nua_callstate_calling)); + + invite = s2_wait_for_request(SIP_METHOD_INVITE); + fail_if(!invite); + s2_respond_to(invite, NULL, SIP_403_FORBIDDEN, TAG_END()); + s2_free_message(invite); + + fail_unless(s2_check_request(SIP_METHOD_ACK)); + fail_unless(s2_check_event(nua_r_invite, 403)); + /* Return to previous state */ + fail_unless(s2_check_callstate(nua_callstate_ready)); + + bye_by_nua(nh, TAG_END()); +} +END_TEST + + +START_TEST(reinvite_too_many_retrys) +{ + nua_handle_t *nh; + struct message *invite, *bye; + int i; + + s2_case("3.2.2", "Re-INVITE fails after too many retries", + "Call fails after 4 times 500 Retry-After"); + + nh = nua_handle(nua, NULL, SIPTAG_TO(s2->local), + NUTAG_RETRY_COUNT(3), + TAG_END()); + + invite_by_nua(nh, TAG_END()); + + nua_invite(nh, SOATAG_USER_SDP_STR("m=audio 5004 RTP/AVP 0 8"), + TAG_END()); + + for (i = 0;; i++) { + fail_unless(s2_check_callstate(nua_callstate_calling)); + invite = s2_wait_for_request(SIP_METHOD_INVITE); + fail_if(!invite); + s2_respond_to(invite, NULL, SIP_500_INTERNAL_SERVER_ERROR, + SIPTAG_RETRY_AFTER_STR("5"), + TAG_END()); + s2_free_message(invite); + fail_unless(s2_check_request(SIP_METHOD_ACK)); + if (i == 3) + break; + fail_unless(s2_check_event(nua_r_invite, 100)); + s2_fast_forward(5); + } + + fail_unless(s2_check_event(nua_r_invite, 500)); + /* Graceful termination */ + fail_unless(s2_check_callstate(nua_callstate_terminating)); + bye = s2_wait_for_request(SIP_METHOD_BYE); + fail_if(!bye); + s2_respond_to(bye, dialog, SIP_200_OK, TAG_END()); + s2_free_message(bye); + fail_unless(s2_check_event(nua_r_bye, 200)); + fail_unless(s2_check_callstate(nua_callstate_terminated)); + + nua_handle_destroy(nh); +} +END_TEST + + +TCase *invite_error_tcase(void) +{ + TCase *tc = tcase_create("3 - Call Errors"); + tcase_add_checked_fixture(tc, call_setup, call_teardown); + { + tcase_add_test(tc, call_forbidden); + tcase_add_test(tc, too_many_retrys); + tcase_add_test(tc, reinvite_forbidden); + tcase_add_test(tc, reinvite_too_many_retrys); + tcase_set_timeout(tc, 5); + } + return tc; +} + + +/* ====================================================================== */ +/* Weird call termination cases */ + +START_TEST(terminating_re_invite) +{ + nua_handle_t *nh; + struct message *bye, *r481; + + s2_case("4.1.1", "Re-INVITE while terminating", + "NUA sends BYE, " + "BYE is challenged, " + "and NUA is re-INVITEd at the same time."); + + nh = invite_to_nua(TAG_END()); + + s2_flush_events(); + + nua_bye(nh, TAG_END()); + + bye = s2_wait_for_request(SIP_METHOD_BYE); + fail_if(!bye); + s2_respond_to(bye, dialog, SIP_407_PROXY_AUTH_REQUIRED, + SIPTAG_PROXY_AUTHENTICATE_STR(s2_auth_digest_str), + TAG_END()); + s2_free_message(bye); + fail_unless(s2_check_event(nua_r_bye, 407)); + + soa_generate_offer(soa, 1, NULL); + + request_with_sdp(dialog, SIP_METHOD_INVITE, NULL, TAG_END()); + + do { + r481 = s2_wait_for_response(0, SIP_METHOD_INVITE); + } + while (r481->sip->sip_status->st_status < 200); + + s2_update_dialog(dialog, r481); /* send ACK */ + + fail_unless(s2_check_callstate(nua_callstate_terminated)); + + nua_handle_destroy(nh); +} +END_TEST + + +START_TEST(bye_invite_glare) +{ + nua_handle_t *nh; + struct message *bye, *r481; + + s2_case("4.1.2", "Re-INVITE while terminating", + "NUA sends BYE, and gets re-INVITEd at same time"); + + nh = invite_to_nua(TAG_END()); + + s2_flush_events(); + + nua_bye(nh, TAG_END()); + bye = s2_wait_for_request(SIP_METHOD_BYE); + fail_if(!bye); + + request_with_sdp(dialog, SIP_METHOD_INVITE, NULL, TAG_END()); + do { + r481 = s2_wait_for_response(0, SIP_METHOD_INVITE); + } + while (r481->sip->sip_status->st_status < 200); + + s2_update_dialog(dialog, r481); /* send ACK */ + + fail_unless(s2_check_callstate(nua_callstate_terminated)); + + s2_respond_to(bye, dialog, SIP_200_OK, + TAG_END()); + s2_free_message(bye); + fail_unless(s2_check_event(nua_r_bye, 200)); + + nua_handle_destroy(nh); +} +END_TEST + +START_TEST(call_4_1_3) +{ + nua_handle_t *nh; + struct message *bye; + struct event *i_bye; + + s2_case("4.1.3", "BYE while terminating", + "NUA sends BYE and receives BYE"); + + nh = invite_to_nua(TAG_END()); + + mark_point(); + + nua_set_hparams(nh, NUTAG_APPL_METHOD("BYE"), TAG_END()); + fail_unless(s2_check_event(nua_r_set_params, 200)); + + s2_flush_events(); + + nua_bye(nh, TAG_END()); + bye = s2_wait_for_request(SIP_METHOD_BYE); + fail_if(!bye); + + s2_request_to(dialog, SIP_METHOD_BYE, NULL, TAG_END()); + i_bye = s2_wait_for_event(nua_i_bye, 100); + fail_if(!i_bye); + + nua_respond(nh, 200, "OKOK", NUTAG_WITH(i_bye->data->e_msg), TAG_END()); + + s2_respond_to(bye, dialog, SIP_200_OK, TAG_END()); + s2_free_message(bye); + + fail_unless(s2_check_event(nua_r_bye, 200)); + fail_unless(s2_check_callstate(nua_callstate_terminated)); + + fail_unless(s2_check_response(200, SIP_METHOD_BYE)); + + nua_handle_destroy(nh); +} +END_TEST + + +START_TEST(call_4_1_4) +{ + nua_handle_t *nh; + struct message *bye; + struct event *i_bye; + + s2_case("4.1.4", "Send BYE after BYE has been received", + "NUA receives BYE, tries to send BYE at same time"); + + nh = invite_to_nua(TAG_END()); + + mark_point(); + nua_set_hparams(nh, NUTAG_APPL_METHOD("BYE"), TAG_END()); + fail_unless(s2_check_event(nua_r_set_params, 200)); + s2_flush_events(); + + s2_request_to(dialog, SIP_METHOD_BYE, NULL, TAG_END()); + i_bye = s2_wait_for_event(nua_i_bye, 100); + fail_if(!i_bye); + + nua_bye(nh, TAG_END()); + + bye = s2_wait_for_request(SIP_METHOD_BYE); + fail_if(!bye); + + s2_respond_to(bye, dialog, SIP_200_OK, TAG_END()); + s2_free_message(bye); + + fail_unless(s2_check_event(nua_r_bye, 200)); + fail_unless(s2_check_callstate(nua_callstate_terminated)); + + nua_respond(nh, 200, "OKOK", NUTAG_WITH(i_bye->data->e_msg), TAG_END()); + fail_unless(s2_check_response(200, SIP_METHOD_BYE)); + + nua_handle_destroy(nh); +} +END_TEST + + +START_TEST(call_4_1_5) +{ + nua_handle_t *nh; + struct message *bye; + struct event *i_bye; + + s2_case("4.1.5", "Send BYE after BYE has been received", + "NUA receives BYE, tries to send BYE at same time"); + + nh = invite_to_nua(TAG_END()); + + mark_point(); + nua_set_hparams(nh, NUTAG_APPL_METHOD("BYE"), TAG_END()); + fail_unless(s2_check_event(nua_r_set_params, 200)); + s2_flush_events(); + + s2_request_to(dialog, SIP_METHOD_BYE, NULL, TAG_END()); + i_bye = s2_wait_for_event(nua_i_bye, 100); + fail_if(!i_bye); + + nua_bye(nh, TAG_END()); + + bye = s2_wait_for_request(SIP_METHOD_BYE); + fail_if(!bye); + + s2_respond_to(bye, dialog, SIP_200_OK, TAG_END()); + s2_free_message(bye); + + fail_unless(s2_check_event(nua_r_bye, 200)); + fail_unless(s2_check_callstate(nua_callstate_terminated)); + + nua_handle_destroy(nh); + fail_unless(s2_check_response(500, SIP_METHOD_BYE)); +} +END_TEST + + +START_TEST(bye_invite_glare2) +{ + nua_handle_t *nh; + struct message *bye, *r486; + + s2_case("4.1.6", "Send BYE after INVITE has been received", + "NUA receives INVITE, sends BYE at same time"); + + nh = invite_to_nua(TAG_END()); + + nua_set_hparams(nh, NUTAG_AUTOANSWER(0), TAG_END()); + fail_unless(s2_check_event(nua_r_set_params, 200)); + + s2_flush_events(); + + request_with_sdp(dialog, SIP_METHOD_INVITE, NULL, TAG_END()); + fail_unless(s2_check_response(100, SIP_METHOD_INVITE)); + nua_bye(nh, TAG_END()); + fail_unless(s2_check_event(nua_i_invite, 100)); + fail_unless(s2_check_callstate(nua_callstate_received)); + + do { + r486 = s2_wait_for_response(0, SIP_METHOD_INVITE); + } + while (r486->sip->sip_status->st_status < 200); + s2_update_dialog(dialog, r486); /* send ACK */ + fail_unless(r486->sip->sip_status->st_status == 486); + + bye = s2_wait_for_request(SIP_METHOD_BYE); + fail_if(!bye); + s2_respond_to(bye, dialog, SIP_200_OK, TAG_END()); + s2_free_message(bye); + + nua_handle_destroy(nh); +} +END_TEST + + +START_TEST(bye_invite_glare3) +{ + nua_handle_t *nh; + struct message *bye, *r486; + + s2_case("4.1.7", "Send BYE after INVITE has been received", + "NUA receives INVITE, sends BYE at same time"); + + nh = invite_to_nua(TAG_END()); + + nua_set_hparams(nh, NUTAG_AUTOANSWER(0), TAG_END()); + fail_unless(s2_check_event(nua_r_set_params, 200)); + + s2_flush_events(); + + request_with_sdp(dialog, SIP_METHOD_INVITE, NULL, TAG_END()); + fail_unless(s2_check_response(100, SIP_METHOD_INVITE)); + nua_bye(nh, TAG_END()); + fail_unless(s2_check_event(nua_i_invite, 100)); + fail_unless(s2_check_callstate(nua_callstate_received)); + + bye = s2_wait_for_request(SIP_METHOD_BYE); + fail_if(!bye); + s2_respond_to(bye, dialog, SIP_200_OK, TAG_END()); + s2_free_message(bye); + + do { + r486 = s2_wait_for_response(0, SIP_METHOD_INVITE); + } + while (r486->sip->sip_status->st_status < 200); + s2_update_dialog(dialog, r486); /* send ACK */ + fail_unless(r486->sip->sip_status->st_status == 486); + + nua_handle_destroy(nh); +} +END_TEST + +START_TEST(bye_then_respond) +{ + nua_handle_t *nh; + struct message *bye, *r486; + + s2_case("4.1.8", "BYE followed by response to INVITE", + "NUA receives INVITE, sends BYE at same time"); + + nh = nua_handle(nua, NULL, SIPTAG_TO(s2->local), TAG_END()); + + invite_by_nua(nh, NUTAG_AUTOANSWER(0), TAG_END()); + + s2_flush_events(); + + request_with_sdp(dialog, SIP_METHOD_INVITE, NULL, TAG_END()); + fail_unless(s2_check_response(100, SIP_METHOD_INVITE)); + nua_bye(nh, TAG_END()); + fail_unless(s2_check_event(nua_i_invite, 100)); + fail_unless(s2_check_callstate(nua_callstate_received)); + + nua_respond(nh, SIP_486_BUSY_HERE, TAG_END()); + + do { + r486 = s2_wait_for_response(0, SIP_METHOD_INVITE); + } + while (r486->sip->sip_status->st_status < 200); + s2_update_dialog(dialog, r486); /* send ACK */ + fail_unless(r486->sip->sip_status->st_status == 486); + + bye = s2_wait_for_request(SIP_METHOD_BYE); + fail_if(!bye); + s2_respond_to(bye, dialog, SIP_200_OK, TAG_END()); + s2_free_message(bye); + + nua_handle_destroy(nh); +} +END_TEST + + +START_TEST(bye_with_timer) +{ + nua_handle_t *nh; + struct message *bye; + + s2_case("4.2.1", "BYE in progress while call timer expires", + "NUA receives INVITE, " + "activates call timers, " + "sends BYE, BYE challenged, " + "waits until session expires."); + + nh = invite_to_nua( + SIPTAG_SESSION_EXPIRES_STR("300;refresher=uas"), + SIPTAG_REQUIRE_STR("timer"), + TAG_END()); + + s2_fast_forward(300); + invite_timer_round(nh, "300"); + + nua_bye(nh, TAG_END()); + + bye = s2_wait_for_request(SIP_METHOD_BYE); + fail_if(!bye); + s2_respond_to(bye, dialog, SIP_407_PROXY_AUTH_REQUIRED, + SIPTAG_PROXY_AUTHENTICATE_STR(s2_auth_digest_str), + TAG_END()); + s2_free_message(bye); + fail_unless(s2_check_event(nua_r_bye, 407)); + + s2_fast_forward(300); + + nua_authenticate(nh, NUTAG_AUTH("Digest:\"s2test\":abc:abc"), TAG_END()); + bye = s2_wait_for_request(SIP_METHOD_BYE); + fail_if(!bye); + s2_respond_to(bye, dialog, SIP_200_OK, TAG_END()); + s2_free_message(bye); + fail_unless(s2_check_event(nua_r_bye, 200)); + fail_unless(s2_check_callstate(nua_callstate_terminated)); + fail_if(s2->events); + + nua_handle_destroy(nh); +} +END_TEST + +START_TEST(bye_with_timer2) +{ + nua_handle_t *nh; + struct message *bye; + + s2_case("4.2.1", "BYE in progress while call timer expires", + "NUA receives INVITE, " + "activates call timers, " + "sends BYE, BYE challenged, " + "waits until session expires."); + + nh = invite_to_nua( + SIPTAG_SESSION_EXPIRES_STR("300;refresher=uas"), + SIPTAG_REQUIRE_STR("timer"), + TAG_END()); + + s2_fast_forward(300); + invite_timer_round(nh, "300"); + + s2_fast_forward(300); + + nua_bye(nh, TAG_END()); + + bye = s2_wait_for_request(SIP_METHOD_BYE); + fail_if(!bye); + s2_respond_to(bye, dialog, SIP_407_PROXY_AUTH_REQUIRED, + SIPTAG_PROXY_AUTHENTICATE_STR(s2_auth_digest_str), + TAG_END()); + s2_free_message(bye); + fail_unless(s2_check_event(nua_r_bye, 407)); + + s2_fast_forward(300); + + nua_authenticate(nh, NUTAG_AUTH(s2_auth_credentials), TAG_END()); + bye = s2_wait_for_request(SIP_METHOD_BYE); + fail_if(!bye); + s2_respond_to(bye, dialog, SIP_200_OK, TAG_END()); + s2_free_message(bye); + fail_unless(s2_check_event(nua_r_bye, 200)); + fail_unless(s2_check_callstate(nua_callstate_terminated)); + fail_if(s2->events); + + nua_handle_destroy(nh); +} +END_TEST + +TCase *termination_tcase(void) +{ + TCase *tc = tcase_create("4 - Call Termination"); + tcase_add_checked_fixture(tc, call_setup, call_teardown); + { + tcase_add_test(tc, terminating_re_invite); + tcase_add_test(tc, bye_invite_glare); + tcase_add_test(tc, call_4_1_3); + tcase_add_test(tc, call_4_1_4); + tcase_add_test(tc, call_4_1_5); + tcase_add_test(tc, bye_invite_glare2); + tcase_add_test(tc, bye_invite_glare3); + tcase_add_test(tc, bye_with_timer); + tcase_add_test(tc, bye_with_timer2); + tcase_add_test(tc, bye_then_respond); + tcase_set_timeout(tc, 5); + } + return tc; +} + +/* ====================================================================== */ + +/* Test case template */ + +START_TEST(empty) +{ + s2_case("0.0.0", "Empty test case", + "Detailed explanation for empty test case."); + + tport_set_params(s2->master, TPTAG_LOG(1), TAG_END()); + s2_setup_logs(7); + s2_setup_logs(0); + tport_set_params(s2->master, TPTAG_LOG(0), TAG_END()); +} + +END_TEST + +TCase *empty_tcase(void) +{ + TCase *tc = tcase_create("0 - Empty"); + tcase_add_checked_fixture(tc, call_setup, call_teardown); + tcase_add_test(tc, empty); + + return tc; +} + +/* ====================================================================== */ + +void check_session_cases(Suite *suite) +{ + suite_add_tcase(suite, invite_tcase()); + suite_add_tcase(suite, cancel_tcase()); + suite_add_tcase(suite, session_timer_tcase()); + suite_add_tcase(suite, invite_100rel_tcase()); + suite_add_tcase(suite, invite_error_tcase()); + suite_add_tcase(suite, termination_tcase()); + + if (0) /* Template */ + suite_add_tcase(suite, empty_tcase()); +} Added: freeswitch/trunk/libs/sofia-sip/libsofia-sip-ua/nua/test_s2.c ============================================================================== --- (empty file) +++ freeswitch/trunk/libs/sofia-sip/libsofia-sip-ua/nua/test_s2.c Wed May 14 15:10:54 2008 @@ -0,0 +1,1589 @@ +/* + * This file is part of the Sofia-SIP package + * + * Copyright (C) 2008 Nokia Corporation. + * + * Contact: Pekka Pessi <pekka.pessi@nokia.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public License + * as published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + */ + +/**@CFILE s2tester.c + * @brief 2nd test Suite for Sofia SIP User Agent Engine + * + * @author Pekka Pessi <Pekka.Pessi@nokia.com> + * + * @date Created: Wed Apr 30 12:48:27 EEST 2008 ppessi + */ + +#include "config.h" + +#undef NDEBUG + +#define TP_MAGIC_T struct tp_magic_s + +#include "test_s2.h" + +#include <sofia-sip/sip_header.h> +#include <sofia-sip/sip_status.h> +#include <sofia-sip/msg_addr.h> +#include <sofia-sip/su_log.h> +#include <sofia-sip/su_tagarg.h> +#include <sofia-sip/su_alloc.h> + +#include <stdlib.h> +#include <string.h> +#include <assert.h> +#include <limits.h> +#include <time.h> + +/* -- Module types ------------------------------------------------------ */ + +struct tp_magic_s +{ + sip_via_t *via; + sip_contact_t *contact; +}; + +/* -- Module prototypes ------------------------------------------------- */ + +static msg_t *s2_msg(int flags); +static int s2_complete_response(msg_t *response, + int status, char const *phrase, + msg_t *request); +static char *s2_generate_tag(su_home_t *home); + +/* -- Module globals ---------------------------------------------------- */ + +struct tester *s2; + +static char const *_s2case = "0.0"; +static unsigned s2_tag_generator = 0; + +/* -- Globals ----------------------------------------------------------- */ + +unsigned s2_default_registration_duration = 3600; + +char const s2_auth_digest_str[] = + "Digest realm=\"s2test\", " + "nonce=\"dcd98b7102dd2f0e8b11d0f600bfb0c093\", " + "qop=\"auth\", " + "algorithm=\"MD5\""; + +char const s2_auth_credentials[] = "Digest:\"s2test\":abc:abc"; + +char const s2_auth2_digest_str[] = + "Digest realm=\"s2test2\", " + "nonce=\"fb0c093dcd98b7102dd2f0e8b11d0f600b\", " + "qop=\"auth\", " + "algorithm=\"MD5\""; + +char const s2_auth2_credentials[] = "Digest:\"s2test2\":abc:abc"; + +char const s2_auth3_digest_str[] = + "Digest realm=\"s2test3\", " + "nonce=\"e8b11d0f600bfb0c093dcd98b7102dd2f0\", " + "qop=\"auth-int\", " + "algorithm=\"MD5-sess\""; + +char const s2_auth3_credentials[] = "Digest:\"s2test3\":abc:abc"; + +/* -- Delay scenarios --------------------------------------------------- */ + +static unsigned long time_offset; + +extern void (*_su_time)(su_time_t *tv); + +static void _su_time_fast_forwarder(su_time_t *tv) +{ + tv->tv_sec += time_offset; +} + +void s2_fast_forward(unsigned long seconds) +{ + if (_su_time == NULL) + _su_time = _su_time_fast_forwarder; + + time_offset += seconds; +} + +/* -- NUA events -------------------------------------------------------- */ + +struct event *s2_remove_event(struct event *e) +{ + if ((*e->prev = e->next)) + e->next->prev = e->prev; + + e->prev = NULL, e->next = NULL; + + return e; +} + +void s2_free_event(struct event *e) +{ + if (e) { + if (e->prev) { + if ((*e->prev = e->next)) + e->next->prev = e->prev; + } + nua_destroy_event(e->event); + nua_handle_unref(e->nh); + free(e); + } +} + +void s2_flush_events(void) +{ + while (s2->events) { + s2_free_event(s2->events); + } +} + +struct event *s2_next_event(void) +{ + for (;;) { + if (s2->events) + return s2_remove_event(s2->events); + + su_root_step(s2->root, 100); + } +} + +struct event *s2_wait_for_event(nua_event_t event, int status) +{ + struct event *e; + + for (;;) { + for (e = s2->events; e; e = e->next) { + if (event != nua_i_none && event != e->data->e_event) + continue; + if (status && e->data->e_status != status) + continue; + return s2_remove_event(e); + } + + su_root_step(s2->root, 100); + } +} + +int s2_check_event(nua_event_t event, int status) +{ + struct event *e = s2_wait_for_event(event, status); + s2_free_event(e); + return e != NULL; +} + +int s2_check_callstate(enum nua_callstate state) +{ + int retval = 0; + tagi_t const *tagi; + struct event *e; + + e = s2_wait_for_event(nua_i_state, 0); + if (e) { + tagi = tl_find(e->data->e_tags, nutag_callstate); + if (tagi) { + retval = (tag_value_t)state == tagi->t_value; + } + } + s2_free_event(e); + return retval; +} + +static void +s2_nua_callback(nua_event_t event, + int status, char const *phrase, + nua_t *nua, nua_magic_t *_t, + nua_handle_t *nh, nua_hmagic_t *hmagic, + sip_t const *sip, + tagi_t tags[]) +{ + struct event *e, **prev; + + if (event == nua_i_active || event == nua_i_terminated) + return; + + e = calloc(1, sizeof *e); + nua_save_event(nua, e->event); + e->nh = nua_handle_ref(nh); + e->data = nua_event_data(e->event); + + for (prev = &s2->events; *prev; prev = &(*prev)->next) + ; + + *prev = e, e->prev = prev; +} + + +struct message * +s2_remove_message(struct message *m) +{ + if ((*m->prev = m->next)) + m->next->prev = m->prev; + + m->prev = NULL, m->next = NULL; + + return m; +} + +void +s2_free_message(struct message *m) +{ + if (m) { + if (m->prev) { + if ((*m->prev = m->next)) + m->next->prev = m->prev; + } + msg_destroy(m->msg); + tport_unref(m->tport); + free(m); + } +} + +void s2_flush_messages(void) +{ + while (s2->received) { + s2_free_message(s2->received); + } +} + +struct message * +s2_next_response(void) +{ + struct message *m; + + for (;;) { + for (m = s2->received; m; m = m->next) { + if (m->sip->sip_status) + return s2_remove_message(m); + } + su_root_step(s2->root, 100); + } +} + +struct message * +s2_wait_for_response(int status, sip_method_t method, char const *name) +{ + struct message *m; + + for (;;) { + for (m = s2->received; m; m = m->next) { + if (!m->sip->sip_status) + continue; + + if (status != 0 && m->sip->sip_status->st_status != status) + continue; + + if (method == sip_method_unknown && name == NULL) + break; + + if (m->sip->sip_cseq == NULL) + continue; + + if (m->sip->sip_cseq->cs_method != method) + continue; + if (name == NULL) + break; + if (strcmp(m->sip->sip_cseq->cs_method_name, name) == 0) + break; + } + + if (m) + return s2_remove_message(m); + + su_root_step(s2->root, 100); + } +} + +int +s2_check_response(int status, sip_method_t method, char const *name) +{ + struct message *m = s2_wait_for_response(status, method, name); + s2_free_message(m); + return m != NULL; +} + + +struct message * +s2_next_request(void) +{ + struct message *m; + + for (;;) { + for (m = s2->received; m; m = m->next) { + if (m->sip->sip_request) + return s2_remove_message(m); + } + + su_root_step(s2->root, 100); + } + + return NULL; +} + +struct message * +s2_wait_for_request(sip_method_t method, char const *name) +{ + struct message *m; + + for (;;) { + for (m = s2->received; m; m = m->next) { + if (m->sip->sip_request) { + if (method == sip_method_unknown && name == NULL) + return s2_remove_message(m); + + if (m->sip->sip_request->rq_method == method && + strcmp(m->sip->sip_request->rq_method_name, name) == 0) + return s2_remove_message(m); + } + } + + su_root_step(s2->root, 100); + } + + return NULL; +} + +int +s2_check_request(sip_method_t method, char const *name) +{ + struct message *m = s2_wait_for_request(method, name); + s2_free_message(m); + return m != NULL; +} + +struct message * +s2_respond_to(struct message *m, struct dialog *d, + int status, char const *phrase, + tag_type_t tag, tag_value_t value, ...) +{ + ta_list ta; + msg_t *reply; + sip_t *sip; + su_home_t *home; + tp_name_t tpn[1]; + char *rport; + + assert(m); assert(m->msg); assert(m->tport); + assert(100 <= status && status < 700); + + ta_start(ta, tag, value); + + reply = s2_msg(0); sip = sip_object(reply); home = msg_home(reply); + + assert(reply && home && sip); + + if (sip_add_tl(reply, sip, ta_tags(ta)) < 0) { + abort(); + } + + s2_complete_response(reply, status, phrase, m->msg); + + if (sip->sip_status && sip->sip_status->st_status > 100 && + sip->sip_to && !sip->sip_to->a_tag && + sip->sip_cseq && sip->sip_cseq->cs_method != sip_method_cancel) { + char const *ltag = NULL; + + if (d && d->local) + ltag = d->local->a_tag; + + if (ltag == NULL) + ltag = s2_generate_tag(home); + + if (sip_to_tag(msg_home(reply), sip->sip_to, ltag) < 0) { + assert(!"add To tag"); + } + } + + if (d && !d->local) { + d->local = sip_from_dup(d->home, sip->sip_to); + d->remote = sip_to_dup(d->home, sip->sip_from); + d->call_id = sip_call_id_dup(d->home, sip->sip_call_id); + d->rseq = sip->sip_cseq->cs_seq; + /* d->route = sip_route_dup(d->home, sip->sip_record_route); */ + d->target = sip_contact_dup(d->home, m->sip->sip_contact); + d->contact = sip_contact_dup(d->home, sip->sip_contact); + } + + *tpn = *tport_name(m->tport); + + rport = su_sprintf(home, "rport=%u", + ntohs(((su_sockaddr_t *) + msg_addrinfo(m->msg)->ai_addr)->su_port)); + + if (s2->server_uses_rport && + sip->sip_via->v_rport && + sip->sip_via->v_rport[0] == '\0') { + msg_header_add_param(home, sip->sip_via->v_common, rport); + } + + tpn->tpn_port = rport + strlen("rport="); + + tport_tsend(m->tport, reply, tpn, TPTAG_MTU(INT_MAX), ta_tags(ta)); + msg_destroy(reply); + + ta_end(ta); + + return m; +} + +/** Add headers from the request to the response message. */ +static int +s2_complete_response(msg_t *response, + int status, char const *phrase, + msg_t *request) +{ + su_home_t *home = msg_home(response); + sip_t *response_sip = sip_object(response); + sip_t const *request_sip = sip_object(request); + + int incomplete = 0; + + if (!response_sip || !request_sip || !request_sip->sip_request) + return -1; + + if (!response_sip->sip_status) + response_sip->sip_status = sip_status_create(home, status, phrase, NULL); + if (!response_sip->sip_via) + response_sip->sip_via = sip_via_dup(home, request_sip->sip_via); + if (!response_sip->sip_from) + response_sip->sip_from = sip_from_dup(home, request_sip->sip_from); + if (!response_sip->sip_to) + response_sip->sip_to = sip_to_dup(home, request_sip->sip_to); + if (!response_sip->sip_call_id) + response_sip->sip_call_id = + sip_call_id_dup(home, request_sip->sip_call_id); + if (!response_sip->sip_cseq) + response_sip->sip_cseq = sip_cseq_dup(home, request_sip->sip_cseq); + + if (!response_sip->sip_record_route && request_sip->sip_record_route) + sip_add_dup(response, response_sip, (void*)request_sip->sip_record_route); + + incomplete = sip_complete_message(response) < 0; + + msg_serialize(response, (msg_pub_t *)response_sip); + + if (incomplete || + !response_sip->sip_status || + !response_sip->sip_via || + !response_sip->sip_from || + !response_sip->sip_to || + !response_sip->sip_call_id || + !response_sip->sip_cseq || + !response_sip->sip_content_length || + !response_sip->sip_separator || + (request_sip->sip_record_route && !response_sip->sip_record_route)) + return -1; + + return 0; +} + +/* Send request (updating dialog). + * + * Return zero upon success, nonzero upon failure. + */ +int +s2_request_to(struct dialog *d, + sip_method_t method, char const *name, + tport_t *tport, + tag_type_t tag, tag_value_t value, ...) +{ + ta_list ta; + tagi_t const *tags; + + msg_t *msg = s2_msg(0); + sip_t *sip = sip_object(msg); + url_string_t *target = NULL; + sip_cseq_t cseq[1]; + sip_via_t via[1]; char const *v_params[8]; + sip_content_length_t l[1]; + tp_name_t tpn[1]; + tp_magic_t *magic; + + ta_start(ta, tag, value); + tags = ta_args(ta); + + if (sip_add_tagis(msg, sip, &tags) < 0) + goto error; + + if (!sip->sip_request) { + sip_request_t *rq; + + if (d->target) + target = (url_string_t *)d->target->m_url; + else if (s2->registration->contact) + target = (url_string_t *)s2->registration->contact->m_url; + else + target = NULL; + + if (target == NULL) + goto error; + + rq = sip_request_create(msg_home(msg), method, name, target, NULL); + sip_header_insert(msg, sip, (sip_header_t *)rq); + } + + if (!d->local && sip->sip_from) + d->local = sip_from_dup(d->home, sip->sip_from); + if (!d->contact && sip->sip_contact) + d->contact = sip_contact_dup(d->home, sip->sip_contact); + if (!d->remote && sip->sip_to) + d->remote = sip_to_dup(d->home, sip->sip_to); + if (!d->target && sip->sip_request) + d->target = sip_contact_create(d->home, + (url_string_t *)sip->sip_request->rq_url, + NULL); + if (!d->call_id && sip->sip_call_id) + d->call_id = sip_call_id_dup(d->home, sip->sip_call_id); + if (!d->lseq && sip->sip_cseq) + d->lseq = sip->sip_cseq->cs_seq; + + if (!d->local) + d->local = sip_from_dup(d->home, s2->local); + if (!d->contact) + d->contact = sip_contact_dup(d->home, s2->contact); + if (!d->remote) + d->remote = sip_to_dup(d->home, s2->registration->aor); + if (!d->call_id) + d->call_id = sip_call_id_create(d->home, NULL); + assert(d->local && d->contact); + assert(d->remote && d->target); + assert(d->call_id); + + if (tport == NULL) + tport = d->tport; + + if (tport == NULL) + tport = s2->registration->tport; + + if (tport == NULL && d->target->m_url->url_type == url_sips) + tport = s2->tls.tport; + + if (tport == NULL) + tport = s2->udp.tport; + else if (tport == NULL) + tport = s2->tcp.tport; + else if (tport == NULL) + tport = s2->tls.tport; + + assert(tport); + + *tpn = *tport_name(tport); + tpn->tpn_host = d->target->m_url->url_host; + tpn->tpn_port = d->target->m_url->url_port; + + magic = tport_magic(tport); + assert(magic != NULL); + + sip_cseq_init(cseq); + cseq->cs_method = method; + cseq->cs_method_name = name; + + if (d->invite && (method == sip_method_ack || method == sip_method_cancel)) { + cseq->cs_seq = sip_object(d->invite)->sip_cseq->cs_seq; + } + else { + cseq->cs_seq = ++d->lseq; + } + + if (d->invite && method == sip_method_cancel) { + *via = *sip_object(d->invite)->sip_via; + } + else { + *via = *magic->via; + via->v_params = v_params; + v_params[0] = su_sprintf(msg_home(msg), "branch=z9hG4bK%lx", ++s2->tid); + v_params[1] = NULL; + } + + sip_content_length_init(l); + if (sip->sip_payload) + l->l_length = sip->sip_payload->pl_len; + + sip_add_tl(msg, sip, + TAG_IF(!sip->sip_from, SIPTAG_FROM(d->local)), + TAG_IF(!sip->sip_contact, SIPTAG_CONTACT(d->contact)), + TAG_IF(!sip->sip_to, SIPTAG_TO(d->remote)), + TAG_IF(!sip->sip_call_id, SIPTAG_CALL_ID(d->call_id)), + TAG_IF(!sip->sip_cseq, SIPTAG_CSEQ(cseq)), + SIPTAG_VIA(via), + TAG_IF(!sip->sip_content_length, SIPTAG_CONTENT_LENGTH(l)), + TAG_IF(!sip->sip_separator, SIPTAG_SEPARATOR_STR("\r\n")), + TAG_END()); + + msg_serialize(msg, NULL); + + if (method == sip_method_invite) { + msg_destroy(d->invite); + d->invite = msg_ref_create(msg); + } + + tport = tport_tsend(tport, msg, tpn, ta_tags(ta)); + ta_end(ta); + + if (d->tport != tport) { + tport_unref(d->tport); + d->tport = tport_ref(tport); + } + + return tport ? 0 : -1; + + error: + ta_end(ta); + return -1; +} + +/** Save information from response. + * + * Send ACK for error messages to INVITE. + */ +int s2_update_dialog(struct dialog *d, struct message *m) +{ + int status = 0; + + if (m->sip->sip_status) + status = m->sip->sip_status->st_status; + + if (100 < status && status < 300) { + d->remote = sip_to_dup(d->home, m->sip->sip_to); + if (m->sip->sip_contact) + d->contact = sip_contact_dup(d->home, m->sip->sip_contact); + } + + if (300 <= status && m->sip->sip_cseq && + m->sip->sip_cseq->cs_method == sip_method_invite && + d->invite) { + msg_t *ack = s2_msg(0); + sip_t *sip = sip_object(ack); + sip_t *invite = sip_object(d->invite); + sip_request_t rq[1]; + sip_cseq_t cseq[1]; + tp_name_t tpn[1]; + + *rq = *invite->sip_request; + rq->rq_method = sip_method_ack, rq->rq_method_name = "ACK"; + *cseq = *invite->sip_cseq; + cseq->cs_method = sip_method_ack, cseq->cs_method_name = "ACK"; + + sip_add_tl(ack, sip, + SIPTAG_REQUEST(rq), + SIPTAG_VIA(invite->sip_via), + SIPTAG_FROM(invite->sip_from), + SIPTAG_TO(invite->sip_to), + SIPTAG_CALL_ID(invite->sip_call_id), + SIPTAG_CSEQ(cseq), + SIPTAG_CONTENT_LENGTH_STR("0"), + SIPTAG_SEPARATOR_STR("\r\n"), + TAG_END()); + + *tpn = *tport_name(d->tport); + if (!tport_is_secondary(d->tport) || + !tport_is_clear_to_send(d->tport)) { + tpn->tpn_host = rq->rq_url->url_host; + tpn->tpn_port = rq->rq_url->url_port; + } + + msg_serialize(ack, NULL); + tport_tsend(d->tport, ack, tpn, TAG_END()); + } + + return 0; +} + +/* ---------------------------------------------------------------------- */ + +int +s2_save_register(struct message *rm) +{ + sip_contact_t *contact, *m, **m_prev; + sip_expires_t const *ex; + sip_date_t const *date; + sip_time_t now = rm->when.tv_sec, expires; + + msg_header_free_all(s2->home, (msg_header_t *)s2->registration->aor); + msg_header_free_all(s2->home, (msg_header_t *)s2->registration->contact); + tport_unref(s2->registration->tport); + + s2->registration->aor = NULL; + s2->registration->contact = NULL; + s2->registration->tport = NULL; + + if (rm == NULL) + return 0; + + assert(rm && rm->sip && rm->sip->sip_request); + assert(rm->sip->sip_request->rq_method == sip_method_register); + + ex = rm->sip->sip_expires; + date = rm->sip->sip_date; + + contact = sip_contact_dup(s2->home, rm->sip->sip_contact); + + for (m_prev = &contact; *m_prev;) { + m = *m_prev; + + expires = sip_contact_expires(m, ex, date, + s2_default_registration_duration, + now); + if (expires) { + char *p = su_sprintf(s2->home, "expires=%lu", (unsigned long)expires); + msg_header_add_param(s2->home, m->m_common, p); + m_prev = &m->m_next; + } + else { + *m_prev = m->m_next; + m->m_next = NULL; + msg_header_free(s2->home, (msg_header_t *)m); + } + } + + if (contact == NULL) + return 0; + + s2->registration->aor = sip_to_dup(s2->home, rm->sip->sip_to); + s2->registration->contact = contact; + s2->registration->tport = tport_ref(rm->tport); + + return 0; +} + +/* ---------------------------------------------------------------------- */ + +static char * +s2_generate_tag(su_home_t *home) +{ + s2_tag_generator += 1; + + return su_sprintf(home, "tag=N2-%s/%u", _s2case, s2_tag_generator); +} + +void s2_case(char const *number, + char const *title, + char const *desciption) +{ + _s2case = number; +} + + +/* ---------------------------------------------------------------------- */ +/* tport interface */ +static void +s2_stack_recv(struct tester *s2, + tport_t *tp, + msg_t *msg, + tp_magic_t *magic, + su_time_t now) +{ + struct message *next = calloc(1, sizeof *next), **prev; + + next->msg = msg; + next->sip = sip_object(msg); + next->when = now; + next->tport = tport_ref(tp); + +#if 0 + if (next->sip->sip_request) + printf("nua sent: %s\n", next->sip->sip_request->rq_method_name); + else + printf("nua sent: SIP/2.0 %u %s\n", + next->sip->sip_status->st_status, + next->sip->sip_status->st_phrase); +#endif + + for (prev = &s2->received; *prev; prev = &(*prev)->next) + ; + + next->prev = prev, *prev = next; +} + +static void +s2_stack_error(struct tester *s2, + tport_t *tp, + int errcode, + char const *remote) +{ + fprintf(stderr, "%s(%p): error %d (%s) from %s\n", + "nua_tester_error", + (void *)tp, errcode, su_strerror(errcode), + remote ? remote : "<unknown destination>"); +} + +static msg_t * +s2_stack_alloc(struct tester *s2, int flags, + char const data[], usize_t size, + tport_t const *tport, + tp_client_t *tpc) +{ + return msg_create(s2->mclass, flags | s2->flags); +} + +static msg_t * +s2_msg(int flags) +{ + return msg_create(s2->mclass, flags | s2->flags); +} + +tp_stack_class_t const s2_stack[1] = + {{ + /* tpac_size */ (sizeof s2_stack), + /* tpac_recv */ s2_stack_recv, + /* tpac_error */ s2_stack_error, + /* tpac_alloc */ s2_stack_alloc, + }}; + +/** Basic setup for test cases */ +void s2_setup_base(char const *hostname) +{ + assert(s2 == NULL); + + su_init(); + + s2 = su_home_new(sizeof *s2); + + assert(s2 != NULL); + + s2->root = su_root_create(s2); + + assert(s2->root != NULL); + + su_root_threading(s2->root, 0); /* disable multithreading */ + + s2->local = sip_from_format(s2->home, "Bob <sip:bob@%s>", + hostname ? hostname : "example.net"); + + if (hostname == NULL) + hostname = "127.0.0.1"; + + s2->hostname = hostname; + s2->tid = (unsigned long)time(NULL) * 510633671UL; +} + +SOFIAPUBVAR su_log_t nua_log[]; +SOFIAPUBVAR su_log_t soa_log[]; +SOFIAPUBVAR su_log_t nea_log[]; +SOFIAPUBVAR su_log_t nta_log[]; +SOFIAPUBVAR su_log_t tport_log[]; +SOFIAPUBVAR su_log_t su_log_default[]; + +void +s2_setup_logs(int level) +{ + assert(s2); + + su_log_soft_set_level(nua_log, level); + su_log_soft_set_level(soa_log, level); + su_log_soft_set_level(su_log_default, level); + su_log_soft_set_level(nea_log, level); + su_log_soft_set_level(nta_log, level); + su_log_soft_set_level(tport_log, level); +} + +static char const * default_protocols[] = { "udp", "tcp", NULL }; + +void +s2_setup_tport(char const * const *protocols, + tag_type_t tag, tag_value_t value, ...) +{ + ta_list ta; + tp_name_t tpn[1]; + int bound; + tport_t *tp; + + assert(s2 != NULL); + + ta_start(ta, tag, value); + + if (s2->master == NULL) { + s2->master = tport_tcreate(s2, s2_stack, s2->root, ta_tags(ta)); + + if (s2->master == NULL) { + assert(s2->master); + } + s2->mclass = sip_default_mclass(); + s2->flags = 0; + } + + memset(tpn, 0, (sizeof tpn)); + tpn->tpn_proto = "*"; + tpn->tpn_host = s2->hostname; + tpn->tpn_port = "*"; + + if (protocols == NULL) + protocols = default_protocols; + + bound = tport_tbind(s2->master, tpn, protocols, + TPTAG_SERVER(1), + ta_tags(ta)); + assert(bound != -1); + + tp = tport_primaries(s2->master); + + if (protocols == default_protocols && s2->contact == NULL) { + *tpn = *tport_name(tp); + s2->contact = sip_contact_format(s2->home, "<sip:%s:%s>", + tpn->tpn_host, + tpn->tpn_port); + } + + for (;tp; tp = tport_next(tp)) { + sip_via_t *v; + sip_contact_t *m; + tp_magic_t *magic; + + if (tport_magic(tp)) + continue; + + *tpn = *tport_name(tp); + + v = sip_via_format(s2->home, "SIP/2.0/%s %s:%s", + tpn->tpn_proto, + tpn->tpn_host, + tpn->tpn_port); + assert(v != NULL); + if (strncasecmp(tpn->tpn_proto, "tls", 3)) { + m = sip_contact_format(s2->home, "<sip:%s:%s;transport=%s>", + tpn->tpn_host, + tpn->tpn_port, + tpn->tpn_proto); + if (s2->udp.contact == NULL && strcasecmp(tpn->tpn_proto, "udp") == 0) { + s2->udp.tport = tport_ref(tp); + s2->udp.contact = m; + } + if (s2->tcp.contact == NULL && strcasecmp(tpn->tpn_proto, "tcp") == 0) { + s2->tcp.tport = tport_ref(tp); + s2->tcp.contact = m; + } + } + else if (strcasecmp(tpn->tpn_proto, "tls")) { + m = sip_contact_format(s2->home, "<sips:%s:%s;transport=%s>", + tpn->tpn_host, + tpn->tpn_port, + tpn->tpn_proto); + } + else { + m = sip_contact_format(s2->home, "<sips:%s:%s>", + tpn->tpn_host, + tpn->tpn_port); + if (s2->tls.contact == NULL) { + s2->tls.tport = tport_ref(tp); + s2->tls.contact = m; + } + } + assert(m != NULL); + + magic = su_zalloc(s2->home, (sizeof *magic)); + magic->via = v, magic->contact = m; + + if (s2->contact == NULL) + s2->contact = m; + + tport_set_magic(tp, magic); + } +} + +/* ---------------------------------------------------------------------- */ +/* S2 DNS server */ + +#include <sofia-resolv/sres_record.h> + +extern uint16_t _sres_default_port; + +static int s2_dns_query(struct tester *s2, + su_wait_t *w, + su_wakeup_arg_t *arg); + +void s2_setup_dns(void) +{ + int n; + su_socket_t socket; + su_wait_t *wait; + su_sockaddr_t su[1]; + socklen_t sulen = sizeof su->su_sin; + + assert(s2->nua == NULL); assert(s2->root != NULL); + + memset(su, 0, sulen); + su->su_len = sulen; + su->su_family = AF_INET; + + /* su->su_port = htons(1053); */ + + socket = su_socket(su->su_family, SOCK_DGRAM, 0); + + n = bind(socket, &su->su_sa, sulen); assert(n == 0); + n = getsockname(socket, &su->su_sa, &sulen); assert(n == 0); + + _sres_default_port = ntohs(su->su_port); + + wait = s2->dns.wait; + n = su_wait_create(wait, socket, SU_WAIT_IN); assert(n == 0); + s2->dns.reg = su_root_register(s2->root, wait, s2_dns_query, NULL, 0); + assert(s2->dns.reg > 0); + s2->dns.socket = socket; +} + +static +struct s2_dns_response { + struct s2_dns_response *next; + uint16_t qlen, dlen; + struct m_header { + /* Header defined in RFC 1035 section 4.1.1 (page 26) */ + uint16_t mh_id; /* Query ID */ + uint16_t mh_flags; /* Flags */ + uint16_t mh_qdcount; /* Question record count */ + uint16_t mh_ancount; /* Answer record count */ + uint16_t mh_nscount; /* Authority records count */ + uint16_t mh_arcount; /* Additional records count */ + } header[1]; + uint8_t data[1500]; +} *zonedata; + +enum { + FLAGS_QR = (1 << 15), + FLAGS_QUERY = (0 << 11), + FLAGS_IQUERY = (1 << 11), + FLAGS_STATUS = (2 << 11), + FLAGS_OPCODE = (15 << 11), /* mask */ + FLAGS_AA = (1 << 10), /* */ + FLAGS_TC = (1 << 9), + FLAGS_RD = (1 << 8), + FLAGS_RA = (1 << 7), + + FLAGS_RCODE = (15 << 0), /* mask of return code */ + + FLAGS_OK = 0, /* No error condition. */ + FLAGS_FORMAT_ERR = 1, /* Server could not interpret query. */ + FLAGS_SERVER_ERR = 2, /* Server error. */ + FLAGS_NAME_ERR = 3, /* No domain name. */ + FLAGS_UNIMPL_ERR = 4, /* Not implemented. */ + FLAGS_AUTH_ERR = 5, /* Refused */ +}; + +uint32_t s2_dns_ttl = 3600; + +static int +s2_dns_query(struct tester *s2, + su_wait_t *w, + su_wakeup_arg_t *arg) +{ + union { + struct m_header header[1]; + uint8_t buffer[1500]; + } request; + ssize_t len; + + su_socket_t socket; + su_sockaddr_t su[1]; + socklen_t sulen = sizeof su; + uint16_t flags; + struct s2_dns_response *r; + size_t const hlen = sizeof r->header; + + (void)arg; + + socket = s2->dns.socket; + + len = su_recvfrom(socket, request.buffer, sizeof request.buffer, 0, + &su->su_sa, &sulen); + + flags = ntohs(request.header->mh_flags); + + if (len < (ssize_t)hlen) + return 0; + if ((flags & FLAGS_QR) == FLAGS_QR) + return 0; + if ((flags & FLAGS_RCODE) != FLAGS_OK) + return 0; + + if ((flags & FLAGS_OPCODE) != FLAGS_QUERY + || ntohs(request.header->mh_qdcount) != 1) { + flags |= FLAGS_QR | FLAGS_UNIMPL_ERR; + request.header->mh_flags = htons(flags); + su_sendto(socket, request.buffer, len, 0, &su->su_sa, sulen); + return 0; + } + + for (r = zonedata; r; r = r->next) { + if (memcmp(r->data, request.buffer + hlen, r->qlen) == 0) + break; + } + + if (r) { + flags |= FLAGS_QR | FLAGS_AA | FLAGS_OK; + request.header->mh_flags = htons(flags); + request.header->mh_ancount = htons(r->header->mh_ancount); + request.header->mh_nscount = htons(r->header->mh_nscount); + request.header->mh_arcount = htons(r->header->mh_arcount); + memcpy(request.buffer + hlen + r->qlen, + r->data + r->qlen, + r->dlen - r->qlen); + len = hlen + r->dlen; + } + else { + flags |= FLAGS_QR | FLAGS_AA | FLAGS_NAME_ERR; + } + + request.header->mh_flags = htons(flags); + su_sendto(socket, request.buffer, len, 0, &su->su_sa, sulen); + return 0; +} + +static void put_uint16(struct s2_dns_response *m, uint16_t h) +{ + uint8_t *p = m->data + m->dlen; + + assert(m->dlen + (sizeof h) < sizeof m->data); + p[0] = h >> 8; p[1] = h; + m->dlen += (sizeof h); +} + +static void put_uint32(struct s2_dns_response *m, uint32_t w) +{ + uint8_t *p = m->data + m->dlen; + + assert(m->dlen + (sizeof w) < sizeof m->data); + p[0] = w >> 24; p[1] = w >> 16; p[2] = w >> 8; p[3] = w; + m->dlen += (sizeof w); +} + +static void put_domain(struct s2_dns_response *m, char const *domain) +{ + char const *label; + size_t llen; + + /* Copy domain into query label at a time */ + for (label = domain; label && label[0]; label += llen) { + assert(!(label[0] == '.' && label[1] != '\0')); + llen = strcspn(label, "."); + assert(llen < 64); + assert(m->dlen + llen + 1 < sizeof m->data); + m->data[m->dlen++] = (uint8_t)llen; + if (llen == 0) + return; + + memcpy(m->data + m->dlen, label, llen); + m->dlen += (uint16_t)llen; + + if (label[llen] == '\0') + break; + if (label[llen + 1]) + llen++; + } + + assert(m->dlen < sizeof m->data); + m->data[m->dlen++] = '\0'; +} + +static void put_string(struct s2_dns_response *m, char const *string) +{ + uint8_t *p = m->data + m->dlen; + size_t len = strlen(string); + + assert(len <= 255); + assert(m->dlen + len + 1 < sizeof m->data); + + *p++ = (uint8_t)len; + memcpy(p, string, len); + m->dlen += len + 1; +} + +static uint16_t put_len_at(struct s2_dns_response *m) +{ + uint16_t at = m->dlen; + assert(m->dlen + sizeof(at) < sizeof m->data); + memset(m->data + m->dlen, 0, sizeof(at)); + m->dlen += sizeof(at); + return at; +} + +static void put_len(struct s2_dns_response *m, uint16_t start) +{ + uint8_t *p = m->data + start; + uint16_t len = m->dlen - (start + 2); + p[0] = len >> 8; p[1] = len; +} + +static void put_data(struct s2_dns_response *m, void *data, uint16_t len) +{ + assert(m->dlen + len < sizeof m->data); + memcpy(m->data + m->dlen, data, len); + m->dlen += len; +} + +static void put_query(struct s2_dns_response *m, char const *domain, + uint16_t qtype) +{ + assert(m->header->mh_qdcount == 0); + put_domain(m, domain), put_uint16(m, qtype), put_uint16(m, sres_class_in); + m->header->mh_qdcount++; + m->qlen = m->dlen; +} + +static void put_a_record(struct s2_dns_response *m, + char const *domain, + struct in_addr addr) +{ + uint16_t start; + + put_domain(m, domain); + put_uint16(m, sres_type_a); + put_uint16(m, sres_class_in); + put_uint32(m, s2_dns_ttl); + start = put_len_at(m); + + put_data(m, &addr, sizeof addr); + put_len(m, start); +} + +static void put_srv_record(struct s2_dns_response *m, + char const *domain, + uint16_t prio, uint16_t weight, + uint16_t port, char const *target) +{ + uint16_t start; + put_domain(m, domain); + put_uint16(m, sres_type_srv); + put_uint16(m, sres_class_in); + put_uint32(m, s2_dns_ttl); + start = put_len_at(m); + + put_uint16(m, prio); + put_uint16(m, weight); + put_uint16(m, port); + put_domain(m, target); + put_len(m, start); +} + +static void put_naptr_record(struct s2_dns_response *m, + char const *domain, + uint16_t order, uint16_t preference, + char const *flags, + char const *services, + char const *regexp, + char const *replace) +{ + uint16_t start; + put_domain(m, domain); + put_uint16(m, sres_type_naptr); + put_uint16(m, sres_class_in); + put_uint32(m, s2_dns_ttl); + start = put_len_at(m); + + put_uint16(m, order); + put_uint16(m, preference); + put_string(m, flags); + put_string(m, services); + put_string(m, regexp); + put_domain(m, replace); + put_len(m, start); +} + +static void put_srv_record_from_uri(struct s2_dns_response *m, + char const *base, + uint16_t prio, uint16_t weight, + url_t const *uri, char const *server) +{ + char domain[1024] = "none"; + char const *service = url_port(uri); + uint16_t port; + + if (uri->url_type == url_sips) { + strcpy(domain, "_sips._tcp."); + } + else if (uri->url_type == url_sip) { + if (url_has_param(uri, "transport=udp")) { + strcpy(domain, "_sip._udp."); + } + else if (url_has_param(uri, "transport=tcp")) { + strcpy(domain, "_sip._tcp."); + } + } + + assert(strcmp(domain, "none")); + + strcat(domain, base); + + if (m->header->mh_qdcount == 0) + put_query(m, domain, sres_type_srv); + + port = (uint16_t)strtoul(service, NULL, 10); + + put_srv_record(m, domain, prio, weight, port, server); +} + +static +void s2_add_to_zone(struct s2_dns_response *_r) +{ + size_t size = offsetof(struct s2_dns_response, data[_r->dlen]); + struct s2_dns_response *r = malloc(size); assert(r); + + memcpy(r, _r, size); + r->next = zonedata; + zonedata = r; +} + + +static void make_server(char *server, char const *prefix, char const *domain) +{ + strcpy(server, prefix); + + if (strlen(server) == 0 || server[strlen(server) - 1] != '.') { + strcat(server, "."); + strcat(server, domain); + } +} + +/** Set up DNS domain */ +void s2_dns_domain(char const *domain, int use_naptr, + /* char *prefix, int priority, url_t const *uri, */ + ...) +{ + struct s2_dns_response m[1]; + + char server[1024], target[1024]; + + va_list va0, va; + char const *prefix; int priority; url_t const *uri; + struct in_addr localhost; + + assert(s2->dns.reg != 0); + + inet_pton(AF_INET, "127.0.0.1", &localhost); + + va_start(va0, use_naptr); + + if (use_naptr) { + memset(m, 0, sizeof m); + put_query(m, domain, sres_type_naptr); + + va_copy(va, va0); + + for (;(prefix = va_arg(va, char *));) { + char *services = NULL; + + priority = va_arg(va, int); + uri = va_arg(va, url_t *); assert(uri); + + if (uri->url_type == url_sips) { + services = "SIPS+D2T"; + strcpy(target, "_sips._tcp."); + } + else if (uri->url_type == url_sip) { + if (url_has_param(uri, "transport=udp")) { + services = "SIP+D2U"; + strcpy(target, "_sip._udp."); + } + else if (url_has_param(uri, "transport=tcp")) { + services = "SIP+D2T"; + strcpy(target, "_sip._tcp."); + } + } + + strcat(target, domain); + assert(services); + put_naptr_record(m, domain, 1, priority, "s", services, "", target); + m->header->mh_ancount++; + } + + va_end(va); + va_copy(va, va0); + + for (;(prefix = va_arg(va, char *));) { + priority = va_arg(va, int); + uri = va_arg(va, url_t *); assert(uri); + + make_server(server, prefix, domain); + + put_srv_record_from_uri(m, domain, priority, 10, uri, server); + m->header->mh_arcount++; + + put_a_record(m, server, localhost); + m->header->mh_arcount++; + } + va_end(va); + + s2_add_to_zone(m); + } + + /* Add SRV records */ + va_copy(va, va0); + for (;(prefix = va_arg(va, char *));) { + priority = va_arg(va, int); + uri = va_arg(va, url_t *); assert(uri); + + make_server(server, prefix, domain); + + memset(m, 0, sizeof m); + put_srv_record_from_uri(m, domain, priority, 10, uri, server); + m->header->mh_ancount++; + + strcpy(server, prefix); strcat(server, domain); + + put_a_record(m, server, localhost); + m->header->mh_arcount++; + + s2_add_to_zone(m); + } + va_end(va); + + /* Add A records */ + va_copy(va, va0); + for (;(prefix = va_arg(va, char *));) { + (void)va_arg(va, int); + (void)va_arg(va, url_t *); + + memset(m, 0, sizeof m); + make_server(server, prefix, domain); + + put_query(m, server, sres_type_a); + put_a_record(m, server, localhost); + m->header->mh_ancount++; + + s2_add_to_zone(m); + } + va_end(va); + + va_end(va0); +} + +void +s2_teardown(void) +{ + s2 = NULL; + su_deinit(); +} + +/* ====================================================================== */ + +#include <sofia-sip/sresolv.h> + +nua_t *s2_nua_setup(tag_type_t tag, tag_value_t value, ...) +{ + ta_list ta; + + s2_setup_base(NULL); + s2_setup_dns(); + + s2_setup_logs(0); + s2_setup_tport(NULL, TPTAG_LOG(0), TAG_END()); + assert(s2->contact); + + s2_dns_domain("example.org", 1, + "s2", 1, s2->udp.contact->m_url, + "s2", 1, s2->tcp.contact->m_url, + NULL); + + ta_start(ta, tag, value); + s2->nua = + nua_create(s2->root, + s2_nua_callback, + s2, + SIPTAG_FROM_STR("Alice <sip:alice@example.org>"), + /* NUTAG_PROXY((url_string_t *)s2->contact->m_url), */ + /* Use internal DNS server */ + NUTAG_PROXY("sip:example.org"), +#if HAVE_WIN32 + SRESTAG_RESOLV_CONF("NUL"), +#else + SRESTAG_RESOLV_CONF("/dev/null"), +#endif + ta_tags(ta)); + ta_end(ta); + + return s2->nua; +} + +void s2_nua_teardown(void) +{ + nua_destroy(s2->nua); + s2->nua = NULL; + s2_teardown(); +} + +/* ====================================================================== */ + +/** Register NUA user. + * + * <pre> + * A B + * |-----REGISTER---->| + * |<-----200 OK------| + * | | + * </pre> + */ +void s2_register_setup(void) +{ + nua_handle_t *nh; + struct message *m; + + assert(s2 && s2->nua); + assert(!s2->registration->nh); + + nh = nua_handle(s2->nua, NULL, TAG_END()); + + nua_register(nh, TAG_END()); + + m = s2_wait_for_request(SIP_METHOD_REGISTER); + assert(m); + s2_save_register(m); + + s2_respond_to(m, NULL, + SIP_200_OK, + SIPTAG_CONTACT(s2->registration->contact), + TAG_END()); + s2_free_message(m); + + assert(s2->registration->contact != NULL); + s2_check_event(nua_r_register, 200); + + s2->registration->nh = nh; +} + +/** Un-register NUA user. + * + * <pre> + * A B + * |-----REGISTER---->| + * |<-----200 OK------| + * | | + * </pre> + */ +void s2_register_teardown(void) +{ + if (s2 && s2->registration->nh) { + nua_handle_t *nh = s2->registration->nh; + struct message *m; + + nua_unregister(nh, TAG_END()); + + m = s2_wait_for_request(SIP_METHOD_REGISTER); assert(m); + s2_save_register(m); + s2_respond_to(m, NULL, + SIP_200_OK, + SIPTAG_CONTACT(s2->registration->contact), + TAG_END()); + assert(s2->registration->contact == NULL); + + s2_free_message(m); + + s2_check_event(nua_r_unregister, 200); + + nua_handle_destroy(nh); + s2->registration->nh = NULL; + } +} + Added: freeswitch/trunk/libs/sofia-sip/libsofia-sip-ua/nua/test_s2.h ============================================================================== --- (empty file) +++ freeswitch/trunk/libs/sofia-sip/libsofia-sip-ua/nua/test_s2.h Wed May 14 15:10:54 2008 @@ -0,0 +1,174 @@ +/* + * This file is part of the Sofia-SIP package + * + * Copyright (C) 2008 Nokia Corporation. + * + * Contact: Pekka Pessi <pekka.pessi@nokia.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public License + * as published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + */ + +#ifndef S2TESTER_H +#define S2TESTER_H + +#define TP_STACK_T struct tester +#define SU_ROOT_MAGIC_T struct tester + +#include <sofia-sip/su_wait.h> +#include <sofia-sip/sip.h> +#include <sofia-sip/tport.h> +#include <sofia-sip/nua.h> + +struct tester +{ + su_home_t home[1]; + + su_root_t *root; + msg_mclass_t const *mclass; + int flags; + + char const *hostname; + tport_t *master; + + sip_to_t *local; + sip_contact_t *contact; + struct { + sip_contact_t *contact; + tport_t *tport; + } udp, tcp, tls; + + struct message { + struct message *next, **prev; + msg_t *msg; + sip_t *sip; + tport_t *tport; + su_time_t when; + } *received; + + struct { + su_socket_t socket; + su_wait_t wait[1]; + int reg; + } dns; + + nua_t *nua; + + struct event { + struct event *next, **prev; + nua_saved_event_t event[1]; + nua_handle_t *nh; + nua_event_data_t const *data; + su_time_t when; + } *events; + + struct { + nua_handle_t *nh; + sip_to_t *aor; + sip_contact_t *contact; + tport_t *tport; + } registration[1]; + + unsigned long tid; + + /* Settings */ + int server_uses_rport; +}; + +struct dialog +{ + su_home_t home[1]; + sip_from_t *local; + sip_to_t *remote; + sip_call_id_t *call_id; + uint32_t lseq, rseq; + sip_contact_t *target; + sip_route_t *route; + sip_contact_t *contact; + + tport_t *tport; + msg_t *invite; /* latest invite sent */ +}; + +extern struct tester *s2; +extern tp_stack_class_t const s2_stack[1]; + +extern unsigned s2_default_registration_duration; +extern char const s2_auth_digest_str[]; +extern char const s2_auth_credentials[]; + +extern char const s2_auth2_digest_str[]; +extern char const s2_auth2_credentials[]; + +extern char const s2_auth3_digest_str[]; +extern char const s2_auth3_credentials[]; + +void s2_fast_forward(unsigned long seconds); + +void s2_case(char const *tag, + char const *title, + char const *description); + +struct event *s2_remove_event(struct event *); +void s2_free_event(struct event *); +void s2_flush_events(void); + +struct event *s2_next_event(void); +struct event *s2_wait_for_event(nua_event_t event, int status); +int s2_check_event(nua_event_t event, int status); +int s2_check_callstate(enum nua_callstate state); + +struct message *s2_remove_message(struct message *m); +void s2_free_message(struct message *m); +void s2_flush_messages(void); + +struct message *s2_next_response(void); +struct message *s2_wait_for_response(int status, sip_method_t , char const *); +int s2_check_response(int status, sip_method_t method, char const *name); + +struct message *s2_next_request(void); +struct message *s2_wait_for_request(sip_method_t method, char const *name); +int s2_check_request(sip_method_t method, char const *name); + +#define SIP_METHOD_UNKNOWN sip_method_unknown, NULL + +struct message *s2_respond_to(struct message *m, struct dialog *d, + int status, char const *phrase, + tag_type_t tag, tag_value_t value, ...); + +int s2_request_to(struct dialog *d, + sip_method_t method, char const *name, + tport_t *tport, + tag_type_t tag, tag_value_t value, ...); + +int s2_update_dialog(struct dialog *d, struct message *response); + +int s2_save_register(struct message *m); + +void s2_flush_all(void); + +void s2_setup_base(char const *hostname); +void s2_setup_logs(int level); +void s2_setup_tport(char const * const *protocols, + tag_type_t tag, tag_value_t value, ...); +void s2_teardown(void); + +nua_t *s2_nua_setup(tag_type_t tag, tag_value_t value, ...); +void s2_nua_teardown(void); + +void s2_register_setup(void); +void s2_register_teardown(void); + +#endif Added: freeswitch/trunk/libs/sofia-sip/tests/test_100rel.c ============================================================================== --- (empty file) +++ freeswitch/trunk/libs/sofia-sip/tests/test_100rel.c Wed May 14 15:10:54 2008 @@ -0,0 +1,2624 @@ +/* + * This file is part of the Sofia-SIP package + * + * Copyright (C) 2005 Nokia Corporation. + * + * Contact: Pekka Pessi <pekka.pessi@nokia.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public License + * as published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + */ + +/**@CFILE test_nua_100rel.c + * @brief NUA-10 tests: early session, PRACK, UPDATE, precondition. + * + * @author Pekka Pessi <Pekka.Pessi@nokia.com> + * @author Martti Mela <Martti.Mela@nokia.com> + * + * @date Created: Wed Aug 17 12:12:12 EEST 2005 ppessi + */ + +#include "config.h" + +#include "test_nua.h" +#include <sofia-sip/auth_common.h> +#include <sofia-sip/su_tag_class.h> + +#if HAVE_FUNC +#elif HAVE_FUNCTION +#define __func__ __FUNCTION__ +#else +#define __func__ "test_call_hold" +#endif + +/* ======================================================================== */ + +/* + X accept_pracked ep + |-------INVITE------>| + | (sdp) | + | | + |<----100 Trying-----| + | | + |<-------180---------| + | (sdp) | + |-------PRACK------->| + |<-------200---------| + | | + |<------200 OK-------| + |--------ACK-------->| + | | +*/ +int accept_pracked(CONDITION_PARAMS) +{ + if (!(check_handle(ep, call, nh, SIP_500_INTERNAL_SERVER_ERROR))) + return 0; + + save_event_in_list(ctx, event, ep, call); + + switch (event) { + case nua_i_prack: + if (200 <= status && status < 300) { + RESPOND(ep, call, nh, SIP_200_OK, TAG_END()); + ep->next_condition = until_ready; + } + default: + break; + } + + switch (callstate(tags)) { + case nua_callstate_received: + RESPOND(ep, call, nh, SIP_180_RINGING, + TAG_IF(call->sdp, SOATAG_USER_SDP_STR(call->sdp)), + TAG_END()); + return 0; + case nua_callstate_terminated: + if (call) + nua_handle_destroy(call->nh), call->nh = NULL; + return 1; + default: + return 0; + } +} + +int accept_pracked2(CONDITION_PARAMS) +{ + if (!(check_handle(ep, call, nh, SIP_500_INTERNAL_SERVER_ERROR))) + return 0; + + save_event_in_list(ctx, event, ep, call); + + switch (callstate(tags)) { + case nua_callstate_received: + RESPOND(ep, call, nh, SIP_180_RINGING, + TAG_IF(call->sdp, SOATAG_USER_SDP_STR(call->sdp)), + TAG_END()); + RESPOND(ep, call, nh, SIP_200_OK, + NUTAG_INCLUDE_EXTRA_SDP(1), + TAG_END()); + return 0; + case nua_callstate_ready: + return 1; + case nua_callstate_terminated: + if (call) + nua_handle_destroy(call->nh), call->nh = NULL; + return 1; + default: + return 0; + } +} + + +int test_180rel(struct context *ctx) +{ + BEGIN(); + + struct endpoint *a = &ctx->a, *b = &ctx->b; + struct call *a_call = a->call, *b_call = b->call; + struct event *e, *ep, *ei; + sip_t *sip; + + if (print_headings) + printf("TEST NUA-10.1.1: Call with 100rel and 180\n"); + +/* Test for 100rel: + + A B + |-------INVITE------>| + |<----100 Trying-----| + | | + |<-------180---------| + |-------PRACK------->| + |<-------200---------| + | | + |<------200 OK-------| + |--------ACK-------->| + | | + |<-------BYE---------| + |-------200 OK-------| + | | + +*/ + + a_call->sdp = "m=audio 5008 RTP/AVP 8"; + b_call->sdp = "m=audio 5010 RTP/AVP 0 8"; + + nua_set_params(ctx->a.nua, + NUTAG_EARLY_MEDIA(1), + TAG_END()); + run_a_until(ctx, nua_r_set_params, until_final_response); + + nua_set_params(ctx->b.nua, + NUTAG_EARLY_MEDIA(1), + NUTAG_SESSION_TIMER(180), + TAG_END()); + run_b_until(ctx, nua_r_set_params, until_final_response); + + TEST_1(a_call->nh = nua_handle(a->nua, a_call, SIPTAG_TO(b->to), TAG_END())); + + INVITE(a, a_call, a_call->nh, + TAG_IF(!ctx->proxy_tests, NUTAG_URL(b->contact->m_url)), + SOATAG_USER_SDP_STR(a_call->sdp), + TAG_END()); + + run_ab_until(ctx, -1, until_ready, -1, accept_pracked2); + + /* Client transitions: + INIT -(C1)-> CALLING: nua_invite(), nua_i_state + CALLING -(C2)-> PROCEEDING: nua_r_invite, nua_i_state, nua_r_prack + PROCEEDING -(C3+C4)-> READY: nua_r_invite, nua_i_state + */ + TEST_1(e = a->events->head); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_calling); /* CALLING */ + TEST_1(is_offer_sent(e->data->e_tags)); + + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_r_invite); + TEST(e->data->e_status, 180); + + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_proceeding); + TEST_1(is_answer_recv(e->data->e_tags)); + TEST_1(!is_offer_sent(e->data->e_tags)); + + ei = event_by_type(e->next, nua_r_invite); + ep = event_by_type(e->next, nua_r_prack); + if (!ep) { + run_a_until(ctx, -1, save_until_final_response); + ep = event_by_type(e->next, nua_r_prack); + } + + TEST_1(e = ep); TEST_E(e->data->e_event, nua_r_prack); + TEST(e->data->e_status, 200); + + TEST_1(e = ei); TEST_E(e->data->e_event, nua_r_invite); + TEST(e->data->e_status, 200); + TEST_1(sip = sip_object(e->data->e_msg)); + TEST_1(sip->sip_content_type); + TEST_S(sip->sip_content_type->c_type, "application/sdp"); + TEST_1(sip->sip_payload); + + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_ready); /* READY */ + TEST_1(!is_offer_answer_done(e->data->e_tags)); + + TEST_1(!e->next || !ep->next); + free_events_in_list(ctx, a->events); + + /* + Server transitions: + INIT -(S1)-> RECEIVED: nua_i_invite, nua_i_state + RECEIVED -(S2a)-> EARLY: nua_respond(), nua_i_state + EARLY -(S3b)-> COMPLETED: nua_respond(), nua_i_state + COMPLETED -(S4)-> READY: nua_i_ack, nua_i_state + */ + TEST_1(e = b->events->head); TEST_E(e->data->e_event, nua_i_invite); + TEST(e->data->e_status, 100); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_received); /* RECEIVED */ + TEST_1(is_offer_recv(e->data->e_tags)); + + /* Responded with 180 Ringing */ + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_early); /* EARLY */ + TEST_1(is_answer_sent(e->data->e_tags)); + + /* 180 is PRACKed */ + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_prack); + /* Does not have effect on call state */ + + /* Respond with 200 OK */ + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_completed); /* COMPLETED */ + TEST_1(!is_offer_answer_done(e->data->e_tags)); + + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_ack); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_ready); /* READY */ + TEST_1(!is_offer_answer_done(e->data->e_tags)); + TEST_1(!e->next); + free_events_in_list(ctx, b->events); + + if (print_headings) + printf("TEST NUA-10.1.1: PASSED\n"); + + if (print_headings) + printf("TEST NUA-10.1.2: terminate call\n"); + + BYE(b, b_call, b_call->nh, TAG_END()); + run_ab_until(ctx, -1, until_terminated, -1, until_terminated); + + /* B transitions: + READY --(T2)--> TERMINATING: nua_bye() + TERMINATING --(T3)--> TERMINATED: nua_r_bye, nua_i_state + */ + TEST_1(e = b->events->head); TEST_E(e->data->e_event, nua_r_bye); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_terminated); /* TERMINATED */ + TEST_1(!e->next); + free_events_in_list(ctx, b->events); + + /* A: READY -(T1)-> TERMINATED: nua_i_bye, nua_i_state */ + TEST_1(e = a->events->head); TEST_E(e->data->e_event, nua_i_bye); + TEST(e->data->e_status, 200); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_terminated); /* TERMINATED */ + TEST_1(!e->next); + free_events_in_list(ctx, a->events); + + nua_handle_destroy(a_call->nh), a_call->nh = NULL; + nua_handle_destroy(b_call->nh), b_call->nh = NULL; + + if (print_headings) + printf("TEST NUA-10.1.2: PASSED\n"); + + END(); +} + +/* + X INVITE + | | + |-------INVITE------>| + |<--------200--------| + |---------ACK------->| +*/ +int authenticate_until_ready(CONDITION_PARAMS) +{ + if (!(check_handle(ep, call, nh, SIP_500_INTERNAL_SERVER_ERROR))) + return 0; + + save_event_in_list(ctx, event, ep, call); + + if (status == 401 || status == 407) { + AUTHENTICATE(ep, call, nh, + NUTAG_AUTH("Digest:\"test-proxy\":charlie:secret"), + TAG_END()); + } + + switch (callstate(tags)) { + case nua_callstate_ready: + return 1; + case nua_callstate_terminated: + if (call) + nua_handle_destroy(call->nh), call->nh = NULL; + return 1; + default: + return 0; + } +} + +/** Test authentication for PRACK */ +int test_prack_auth(struct context *ctx) +{ + if (!ctx->proxy_tests) + return 0; + + BEGIN(); + + struct endpoint *c = &ctx->c, *b = &ctx->b; + struct call *c_call = c->call, *b_call = b->call; + struct event *e, *ep, *ei; + sip_t *sip; + sip_proxy_authenticate_t *au; + + if (print_headings) + printf("TEST NUA-10.1.3: Call with 100rel, PRACK is challenged\n"); + +/* Test for authentication during 100rel + + C B + |-------INVITE--\ | + |<-------407----/ | + | | + |-------INVITE------>| + |<----100 Trying-----| + | | + |<-------180---------| + |-------PRACK---\ | + |<-------407----/ | + |-------PRACK------->| + |<-------200---------| + | | + |<------200 OK-------| + |--------ACK-------->| + | | + |<-------BYE---------| + |-------200 OK-------| + | | + +*/ + + c_call->sdp = "m=audio 5008 RTP/AVP 8"; + b_call->sdp = "m=audio 5010 RTP/AVP 0 8"; + + nua_set_params(ctx->c.nua, + NUTAG_EARLY_MEDIA(1), + TAG_END()); + run_c_until(ctx, nua_r_set_params, until_final_response); + + nua_set_params(ctx->b.nua, + NUTAG_EARLY_MEDIA(1), + TAG_END()); + run_b_until(ctx, nua_r_set_params, until_final_response); + + TEST_1(c_call->nh = nua_handle(c->nua, c_call, SIPTAG_TO(b->to), TAG_END())); + + INVITE(c, c_call, c_call->nh, + TAG_IF(!ctx->proxy_tests, NUTAG_URL(b->contact->m_url)), + SIPTAG_FROM(c->to), + SOATAG_USER_SDP_STR(c_call->sdp), + TAG_END()); + + run_bc_until(ctx, -1, accept_pracked, -1, authenticate_until_ready); + + /* Client transitions: + INIT -(C1)-> CALLING: nua_invite(), nua_i_state + CALLING -(C2)-> PROCEEDING: nua_r_invite, nua_i_state, nua_r_prack + PROCEEDING -(C3+C4)-> READY: nua_r_invite, nua_i_state + */ + TEST_1(e = c->events->head); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_calling); /* CALLING */ + TEST_1(is_offer_sent(e->data->e_tags)); + + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_r_invite); + TEST(e->data->e_status, 407); + TEST_1(sip = sip_object(e->data->e_msg)); + TEST_1(au = sip->sip_proxy_authenticate); + + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_calling); /* CALLING */ + TEST_1(is_offer_sent(e->data->e_tags)); + + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_r_invite); + TEST(e->data->e_status, 180); + + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_proceeding); + TEST_1(is_answer_recv(e->data->e_tags)); + TEST_1(!is_offer_sent(e->data->e_tags)); + + ei = event_by_type(e->next, nua_r_invite); + ep = event_by_type(e->next, nua_r_prack); + + TEST_1(e = ep); TEST_E(e->data->e_event, nua_r_prack); + if (e->data->e_status == 100 || e->data->e_status == 407) { + /* The final response to PRACK may be received after ACK is sent */ + if (!event_by_type(e->next, nua_r_prack)) + run_bc_until(ctx, -1, save_events, -1, save_until_final_response); + TEST_1(e = ep = event_by_type(e->next, nua_r_prack)); + } + TEST_E(e->data->e_event, nua_r_prack); + TEST(e->data->e_status, 200); + TEST_1(sip = sip_object(e->data->e_msg)); + TEST_1(sip->sip_from->a_url->url_user); + + TEST_1(e = ei); TEST_E(e->data->e_event, nua_r_invite); + TEST(e->data->e_status, 200); + + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_ready); /* READY */ + TEST_1(!is_offer_answer_done(e->data->e_tags)); + TEST_1(!e->next || !ep->next); + free_events_in_list(ctx, c->events); + + /* + Server transitions: + INIT -(S1)-> RECEIVED: nua_i_invite, nua_i_state + RECEIVED -(S2a)-> EARLY: nua_respond(), nua_i_state + EARLY -(S3b)-> COMPLETED: nua_respond(), nua_i_state + COMPLETED -(S4)-> READY: nua_i_ack, nua_i_state + */ + TEST_1(e = b->events->head); TEST_E(e->data->e_event, nua_i_invite); + TEST(e->data->e_status, 100); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_received); /* RECEIVED */ + TEST_1(is_offer_recv(e->data->e_tags)); + + /* Responded with 180 Ringing */ + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_early); /* EARLY */ + TEST_1(is_answer_sent(e->data->e_tags)); + + /* 180 is PRACKed */ + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_prack); + /* Does not have effect on call state */ + + /* Respond with 200 OK */ + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_completed); /* COMPLETED */ + TEST_1(!is_offer_answer_done(e->data->e_tags)); + + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_ack); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_ready); /* READY */ + TEST_1(!is_offer_answer_done(e->data->e_tags)); + TEST_1(!e->next); + free_events_in_list(ctx, b->events); + + if (print_headings) + printf("TEST NUA-10.1.3: PASSED\n"); + + if (print_headings) + printf("TEST NUA-10.1.4: terminate call\n"); + + BYE(b, b_call, b_call->nh, TAG_END()); + run_bc_until(ctx, -1, until_terminated, -1, until_terminated); + + /* B transitions: + READY --(T2)--> TERMINATING: nua_bye() + TERMINATING --(T3)--> TERMINATED: nua_r_bye, nua_i_state + */ + TEST_1(e = b->events->head); TEST_E(e->data->e_event, nua_r_bye); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_terminated); /* TERMINATED */ + TEST_1(!e->next); + free_events_in_list(ctx, b->events); + + /* C: READY -(T1)-> TERMINATED: nua_i_bye, nua_i_state */ + TEST_1(e = c->events->head); TEST_E(e->data->e_event, nua_i_bye); + TEST(e->data->e_status, 200); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_terminated); /* TERMINATED */ + TEST_1(!e->next); + free_events_in_list(ctx, c->events); + + nua_handle_destroy(c_call->nh), c_call->nh = NULL; + nua_handle_destroy(b_call->nh), b_call->nh = NULL; + + if (print_headings) + printf("TEST NUA-10.1.4: PASSED\n"); + + END(); +} + +/* + X ringing_pracked ep + |-------INVITE------>| + |<----100 Trying-----| + | | + |<-------183---------| + |-------PRACK------->| + |<-------200---------| + | | + |<-------180---------| + |-------PRACK------->| + |<-------200---------| + | | + |<------200 OK-------| + |--------ACK-------->| + | | +*/ +int ringing_pracked(CONDITION_PARAMS) +{ + if (!(check_handle(ep, call, nh, SIP_500_INTERNAL_SERVER_ERROR))) + return 0; + + save_event_in_list(ctx, event, ep, call); + + switch (event) { + case nua_i_prack: + if (200 <= status && status < 300) { + RESPOND(ep, call, nh, SIP_180_RINGING, TAG_END()); + ep->next_condition = accept_pracked; + } + default: + break; + } + + switch (callstate(tags)) { + case nua_callstate_received: + RESPOND(ep, call, nh, SIP_183_SESSION_PROGRESS, + TAG_IF(call->sdp, SOATAG_USER_SDP_STR(call->sdp)), + TAG_END()); + return 0; + case nua_callstate_terminated: + if (call) + nua_handle_destroy(call->nh), call->nh = NULL; + return 1; + default: + return 0; + } +} + +int respond_483_to_prack(CONDITION_PARAMS); +static int prack_100rel(CONDITION_PARAMS); + +int test_183rel(struct context *ctx) +{ + BEGIN(); + + struct endpoint *a = &ctx->a, *b = &ctx->b; + struct call *a_call = a->call, *b_call = b->call; + struct event *e, *ei, *ep; + + if (print_headings) + printf("TEST NUA-10.2.1: Call with 100rel, 183 and 180\n"); + + TEST_1(a_call->nh = nua_handle(a->nua, a_call, SIPTAG_TO(b->to), TAG_END())); + + INVITE(a, a_call, a_call->nh, + TAG_IF(!ctx->proxy_tests, NUTAG_URL(b->contact->m_url)), + SOATAG_USER_SDP_STR(a_call->sdp), + TAG_END()); + + run_ab_until(ctx, -1, until_ready, -1, ringing_pracked); + + /* Client transitions: + INIT -(C1)-> CALLING: nua_invite(), nua_i_state + CALLING -(C2)-> PROCEEDING: nua_r_invite, nua_i_state, nua_r_prack + PROCEEDING -(C2)-> PROCEEDING: nua_r_invite, nua_i_state, nua_r_prack + PROCEEDING -(C3+C4)-> READY: nua_r_invite, nua_i_state + */ + TEST_1(e = a->events->head); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_calling); /* CALLING */ + TEST_1(is_offer_sent(e->data->e_tags)); + + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_r_invite); + TEST(e->data->e_status, 183); + + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_proceeding); + TEST_1(is_answer_recv(e->data->e_tags)); + TEST_1(!is_offer_sent(e->data->e_tags)); + + TEST_1(e = e->next); + + ep = e->data->e_event == nua_r_prack ? e : NULL; + + if (ep) { + TEST(ep->data->e_status, 200); TEST_1(e = e->next); + } + + TEST_E(e->data->e_event, nua_r_invite); + TEST(e->data->e_status, 180); + + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_proceeding); + TEST_1(!is_offer_answer_done(e->data->e_tags)); + + if (!ep) { + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_r_prack); + TEST(e->data->e_status, 200); + } + + ei = event_by_type(e->next, nua_r_invite); + ep = event_by_type(e->next, nua_r_prack); + if (!ep) { + run_a_until(ctx, -1, save_until_final_response); + ep = event_by_type(e->next, nua_r_prack); + } + + TEST_1(e = ep); TEST_E(e->data->e_event, nua_r_prack); + TEST(e->data->e_status, 200); + + TEST_1(e = ei); TEST_E(e->data->e_event, nua_r_invite); + TEST(e->data->e_status, 200); + + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_ready); /* READY */ + TEST_1(!is_offer_answer_done(e->data->e_tags)); + TEST_1(!e->next || !ep->next); + free_events_in_list(ctx, a->events); + + /* + Server transitions: + INIT -(S1)-> RECEIVED: nua_i_invite, nua_i_state + RECEIVED -(S2a)-> EARLY: nua_respond(), nua_i_state + EARLY -(S3b)-> COMPLETED: nua_respond(), nua_i_state + COMPLETED -(S4)-> READY: nua_i_ack, nua_i_state + */ + TEST_1(e = b->events->head); TEST_E(e->data->e_event, nua_i_invite); + TEST(e->data->e_status, 100); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_received); /* RECEIVED */ + TEST_1(is_offer_recv(e->data->e_tags)); + + /* Responded with 183 Session Progress */ + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(e->data->e_status, 183); + TEST(callstate(e->data->e_tags), nua_callstate_early); /* EARLY */ + TEST_1(is_answer_sent(e->data->e_tags)); + + /* 183 is PRACKed */ + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_prack); + /* Does not have effect on call state */ + + /* Responded with 180 Ringing */ + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(e->data->e_status, 180); + TEST(callstate(e->data->e_tags), nua_callstate_early); /* EARLY */ + TEST_1(!is_offer_answer_done(e->data->e_tags)); + + /* 180 is PRACKed */ + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_prack); + /* Does not have effect on call state */ + + /* Respond with 200 OK */ + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(e->data->e_status, 200); + TEST(callstate(e->data->e_tags), nua_callstate_completed); /* COMPLETED */ + TEST_1(!is_offer_answer_done(e->data->e_tags)); + + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_ack); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_ready); /* READY */ + TEST_1(!is_offer_answer_done(e->data->e_tags)); + TEST_1(!e->next); + free_events_in_list(ctx, b->events); + + if (print_headings) + printf("TEST NUA-10.2.1: PASSED\n"); + + /* Test for graceful termination by client because 483 sent to PRACK */ + if (print_headings) + printf("TEST NUA-10.2.2: graceful termination because PRACK fails\n"); + + nua_set_hparams(a_call->nh, NUTAG_APPL_METHOD("PRACK"), TAG_END()); + nua_set_hparams(b_call->nh, NUTAG_APPL_METHOD("PRACK"), + NUTAG_AUTOANSWER(0), TAG_END()); + run_ab_until(ctx, nua_r_set_params, NULL, nua_r_set_params, NULL); + + INVITE(a, a_call, a_call->nh, TAG_END()); + + run_ab_until(ctx, -1, prack_100rel, -1, respond_483_to_prack); + + /* Client transitions: + INIT -(C1)-> CALLING: nua_invite(), nua_i_state + CALLING -(C2)-> TERMINATING: nua_r_invite, nua_i_state, + nua_r_prack, nua_i_state + TERMINATING -(T1)-> TERMINATED: nua_r_invite, nua_i_state + */ + TEST_1(e = a->events->head); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_calling); /* CALLING */ + TEST_1(is_offer_sent(e->data->e_tags)); + + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_r_invite); + TEST(e->data->e_status, 183); + + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_proceeding); + TEST_1(is_answer_recv(e->data->e_tags)); + TEST_1(!is_offer_sent(e->data->e_tags)); + + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_r_prack); + TEST(e->data->e_status, 483); + + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_terminating); + + { + int bye = 1, cancel = 1, invite = 1; + + while (bye || cancel || invite) { + TEST_1(e = e->next); + if (e->data->e_event == nua_r_bye) { + TEST_E(e->data->e_event, nua_r_bye); + TEST(e->data->e_status, 200); + bye = 0; + break; + } + else if (e->data->e_event == nua_r_invite) { + TEST_E(e->data->e_event, nua_r_invite); + TEST(e->data->e_status, 487); + invite = 0; + } + else if (e->data->e_event == nua_r_cancel) { + TEST_E(e->data->e_event, nua_r_cancel); + TEST_1(e->data->e_status == 200 || e->data->e_status == 481); + cancel = 0; + } + } + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_terminated); + } + + TEST_1(!e->next); + + free_events_in_list(ctx, a->events); + + /* + Server transitions: + INIT -(S1)-> RECEIVED: nua_i_invite, nua_i_state + RECEIVED -(S2a)-> EARLY: nua_respond(), nua_i_state, nua_respond(to PRACK) + EARLY -(S3b)-> TERMINATED: nua_i_bye, nua_i_state + */ + TEST_1(e = b->events->head); TEST_E(e->data->e_event, nua_i_invite); + TEST(e->data->e_status, 100); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_received); /* RECEIVED */ + TEST_1(is_offer_recv(e->data->e_tags)); + + /* Responded with 183 Session Progress */ + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(e->data->e_status, 183); + TEST(callstate(e->data->e_tags), nua_callstate_early); /* EARLY */ + TEST_1(is_answer_sent(e->data->e_tags)); + + /* 183 is PRACKed, PRACK is responded with 483 */ + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_prack); + + /* Client terminates the call + - we may (it is received before BYE) or may not (received after BYE) + get CANCEL request + */ + TEST_1(e = e->next); + if (e->data->e_event == nua_i_cancel) { + TEST_E(e->data->e_event, nua_i_cancel); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_ready); + TEST_1(e = e->next); + } + + TEST_E(e->data->e_event, nua_i_bye); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_terminated); + TEST_1(!e->next); + + free_events_in_list(ctx, b->events); + + nua_handle_destroy(a_call->nh), a_call->nh = NULL; + nua_handle_destroy(b_call->nh), b_call->nh = NULL; + + if (print_headings) + printf("TEST NUA-10.2.2: PASSED\n"); + + END(); +} + +static int prack_100rel(CONDITION_PARAMS) +{ + if (!check_handle(ep, call, nh, SIP_500_INTERNAL_SERVER_ERROR)) + return 0; + + save_event_in_list(ctx, event, ep, call); + + if (event == nua_r_invite && 100 < status && status < 200 && + sip_has_feature(sip->sip_require, "100rel")) { + sip_rack_t rack[1]; + + sip_rack_init(rack); + rack->ra_response = sip->sip_rseq->rs_response; + rack->ra_cseq = sip->sip_cseq->cs_seq; + rack->ra_method = sip->sip_cseq->cs_method; + rack->ra_method_name = sip->sip_cseq->cs_method_name; + + nua_prack(nh, SIPTAG_RACK(rack), TAG_END()); + } + + return event == nua_i_state && callstate(tags) == nua_callstate_terminated; +} + +int respond_483_to_prack(CONDITION_PARAMS) +{ + if (!(check_handle(ep, call, nh, SIP_500_INTERNAL_SERVER_ERROR))) + return 0; + + switch (event) { + case nua_i_prack: + if (status <= 200) { + RESPOND(ep, call, nh, 483, "Foo", + NUTAG_WITH_THIS(nua), + TAG_END()); + } + default: + break; + } + + save_event_in_list(ctx, event, ep, call); + + switch (callstate(tags)) { + case nua_callstate_received: + RESPOND(ep, call, nh, SIP_183_SESSION_PROGRESS, + TAG_IF(call->sdp, SOATAG_USER_SDP_STR(call->sdp)), + TAG_END()); + return 0; + case nua_callstate_terminated: + if (call) + nua_handle_destroy(call->nh), call->nh = NULL; + return 1; + default: + return 0; + } +} + +/* + X ringing_updated ep + |-------INVITE------>| + | (sdp) | + |<----100 Trying-----| + | | + |<-------183---------| + | (sdp) | + |-------PRACK------->| + | (sdp) | + |<-------200---------| + | (sdp) | + | | + |-------UPDATE------>| + | (sdp) | + |<-------200---------| + | (sdp) | + | | +<using acccept_pracked> + | | + |<-------180---------| + |-------PRACK------->| + |<-------200---------| + | | + |<------200 OK-------| + |--------ACK-------->| + | | +*/ +int ringing_updated(CONDITION_PARAMS) +{ + if (!(check_handle(ep, call, nh, SIP_500_INTERNAL_SERVER_ERROR))) + return 0; + + save_event_in_list(ctx, event, ep, call); + + switch (event) { + case nua_i_update: + if (200 <= status && status < 300) { + RESPOND(ep, call, nh, SIP_180_RINGING, TAG_END()); + ep->next_condition = accept_pracked; + } + return 0; + default: + break; + } + + switch (callstate(tags)) { + case nua_callstate_received: + RESPOND(ep, call, nh, SIP_183_SESSION_PROGRESS, + SIPTAG_REQUIRE_STR("100rel"), + TAG_IF(call->sdp, SOATAG_USER_SDP_STR(call->sdp)), + TAG_END()); + return 0; + case nua_callstate_early: + return 0; + case nua_callstate_ready: + return 1; + case nua_callstate_terminated: + if (call) + nua_handle_destroy(call->nh), call->nh = NULL; + return 1; + default: + return 0; + } +} + +int test_preconditions(struct context *ctx) +{ + BEGIN(); + + struct endpoint *a = &ctx->a, *b = &ctx->b; + struct call *a_call = a->call, *b_call = b->call; + struct event *e, *ep, *ei; + sip_t *sip; + + if (print_headings) + printf("TEST NUA-10.3.1: Call with 100rel and preconditions\n"); + +/* Test for precondition: + + A B + |-------INVITE------>| + |<----100 Trying-----| + | | + |<-------183---------| + |-------PRACK------->| + |<-------200---------| + | | + |-------UPDATE------>| + |<-------200---------| + | | + |<-------180---------| + |-------PRACK------->| + |<-------200---------| + | | + |<------200 OK-------| + |--------ACK-------->| + | | + |<-------BYE---------| + |-------200 OK-------| + | | + +*/ + + a_call->sdp = "m=audio 5008 RTP/AVP 8"; + b_call->sdp = "m=audio 5010 RTP/AVP 0 8"; + + nua_set_params(ctx->a.nua, + NUTAG_EARLY_MEDIA(1), + SIPTAG_SUPPORTED_STR("100rel, precondition"), + TAG_END()); + run_a_until(ctx, nua_r_set_params, until_final_response); + + nua_set_params(ctx->b.nua, + NUTAG_EARLY_MEDIA(0), + SIPTAG_SUPPORTED_STR("100rel, precondition, timer"), + TAG_END()); + run_b_until(ctx, nua_r_set_params, until_final_response); + + TEST_1(a_call->nh = nua_handle(a->nua, a_call, SIPTAG_TO(b->to), TAG_END())); + + INVITE(a, a_call, a_call->nh, + TAG_IF(!ctx->proxy_tests, NUTAG_URL(b->contact->m_url)), + SOATAG_USER_SDP_STR(a_call->sdp), + SIPTAG_SUPPORTED_STR("100rel"), + SIPTAG_REQUIRE_STR("precondition"), + TAG_END()); + + run_ab_until(ctx, -1, until_ready, -1, ringing_updated); + + /* Client transitions: + INIT -(C1)-> CALLING: nua_invite(), nua_i_state + CALLING -(C2)-> PROCEEDING: nua_r_invite, nua_i_state + PROCEEDING --> PROCEEDING: nua_r_prack, nua_i_state + PROCEEDING --> PROCEEDING: nua_r_update, nua_i_state + PROCEEDING -(C3+C4)-> READY: nua_r_invite, nua_i_state + */ + TEST_1(e = a->events->head); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_calling); /* CALLING */ + TEST_1(is_offer_sent(e->data->e_tags)); + + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_r_invite); + TEST(e->data->e_status, 183); + + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_proceeding); + TEST_1(is_answer_recv(e->data->e_tags)); + TEST_1(is_offer_sent(e->data->e_tags)); + + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_r_prack); + TEST(e->data->e_status, 200); + + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_proceeding); + TEST_1(is_answer_recv(e->data->e_tags)); + TEST_1(!is_offer_sent(e->data->e_tags)); + + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_proceeding); + TEST_1(!is_answer_recv(e->data->e_tags)); + TEST_1(is_offer_sent(e->data->e_tags)); + + ei = event_by_type(e->next, nua_r_invite); + ep = event_by_type(e->next, nua_r_update); + + TEST_1(e = ep); TEST_E(e->data->e_event, nua_r_update); + TEST(e->data->e_status, 200); + TEST_1(sip = sip_object(e->data->e_msg)); + TEST_1(sip->sip_session_expires); + + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_proceeding); + TEST_1(is_answer_recv(e->data->e_tags)); + TEST_1(!is_offer_sent(e->data->e_tags)); + + TEST_1(e = ei); TEST_E(e->data->e_event, nua_r_invite); + TEST(e->data->e_status, 180); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_proceeding); /* PROCEEDING */ + TEST_1(!is_offer_answer_done(e->data->e_tags)); + + if (e == ep) /* invite was responded before update */ + e = ep->next->next; + + ei = event_by_type(e->next, nua_r_invite); + ep = event_by_type(e->next, nua_r_prack); + if (!ep) { + run_a_until(ctx, -1, save_until_final_response); + ep = event_by_type(e->next, nua_r_prack); + } + + TEST_1(e = ep); TEST_E(e->data->e_event, nua_r_prack); + TEST(e->data->e_status, 200); + /* Does not have effect on call state */ + + TEST_1(e = ei); TEST_E(e->data->e_event, nua_r_invite); + TEST(e->data->e_status, 200); + TEST_1(sip = sip_object(e->data->e_msg)); + if (ctx->proxy_tests) { + TEST_1(sip->sip_session_expires); + TEST_S(sip->sip_session_expires->x_refresher, "uas"); + TEST_1(!sip_has_supported(sip->sip_require, "timer")); + } + + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_ready); /* READY */ + TEST_1(!is_offer_answer_done(e->data->e_tags)); + TEST_1(!e->next || !ep->next); + free_events_in_list(ctx, a->events); + + /* + Server transitions: + INIT -(S1)-> RECEIVED: nua_i_invite, nua_i_state + RECEIVED -(S2a)-> EARLY: nua_respond(), nua_i_state + EARLY --> EARLY: nua_i_prack, nua_i_state + EARLY --> EARLY: nua_i_update, nua_i_state + EARLY --> EARLY: nua_r_update, nua_i_state + EARLY -(S3b)-> COMPLETED: nua_respond(), nua_i_state + COMPLETED -(S4)-> READY: nua_i_ack, nua_i_state + */ + TEST_1(e = b->events->head); TEST_E(e->data->e_event, nua_i_invite); + TEST(e->data->e_status, 100); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_received); /* RECEIVED */ + + /* Responded with 183 Session Progress */ + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(e->data->e_status, 183); + TEST(callstate(e->data->e_tags), nua_callstate_early); /* EARLY */ + TEST_1(is_answer_sent(e->data->e_tags)); + + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_prack); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_early); /* EARLY */ + TEST_1(is_offer_recv(e->data->e_tags)); + TEST_1(is_answer_sent(e->data->e_tags)); + + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_update); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_early); /* EARLY */ + TEST_1(!is_offer_sent(e->data->e_tags)); + TEST_1(is_offer_recv(e->data->e_tags)); + TEST_1(is_answer_sent(e->data->e_tags)); + TEST_1(!is_answer_recv(e->data->e_tags)); + + /* Responded with 180 Ringing */ + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(e->data->e_status, 180); + TEST(callstate(e->data->e_tags), nua_callstate_early); /* EARLY */ + TEST_1(!is_offer_answer_done(e->data->e_tags)); + + /* 180 PRACKed */ + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_prack); + /* Does not have effect on call state */ + + /* Responded with 200 OK */ + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(e->data->e_status, 200); + TEST(callstate(e->data->e_tags), nua_callstate_completed); /* COMPLETED */ + TEST_1(!is_offer_answer_done(e->data->e_tags)); + + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_ack); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_ready); /* READY */ + TEST_1(!is_offer_answer_done(e->data->e_tags)); + TEST_1(!e->next); + free_events_in_list(ctx, b->events); + + if (print_headings) + printf("TEST NUA-10.3.1: PASSED\n"); + + if (print_headings) + printf("TEST NUA-10.3.2: terminate call\n"); + + BYE(b, b_call, b_call->nh, TAG_END()); + run_ab_until(ctx, -1, until_terminated, -1, until_terminated); + + /* B transitions: + READY --(T2)--> TERMINATING: nua_bye() + TERMINATING --(T3)--> TERMINATED: nua_r_bye, nua_i_state + */ + TEST_1(e = b->events->head); TEST_E(e->data->e_event, nua_r_bye); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_terminated); /* TERMINATED */ + TEST_1(!e->next); + free_events_in_list(ctx, b->events); + + /* A: READY -(T1)-> TERMINATED: nua_i_bye, nua_i_state */ + TEST_1(e = a->events->head); TEST_E(e->data->e_event, nua_i_bye); + TEST(e->data->e_status, 200); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_terminated); /* TERMINATED */ + TEST_1(!e->next); + free_events_in_list(ctx, a->events); + + nua_handle_destroy(a_call->nh), a_call->nh = NULL; + nua_handle_destroy(b_call->nh), b_call->nh = NULL; + + if (print_headings) + printf("TEST NUA-10.3.2: PASSED\n"); + + END(); +} + +/* + X accept_updated ep + |-------INVITE------>| + | (sdp) | + |<----100 Trying-----| + | | + |<-------183---------| + | (sdp) | + |-------PRACK------->| + | (sdp) | + |<-------200---------| + | (sdp) | + | | + |-------UPDATE------>| + | (sdp) | + |<-------200---------| + | (sdp) | + | | + | | + |<-------180---------| + | | + |<------200 OK-------| + |--------ACK-------->| + | | +*/ +int accept_updated(CONDITION_PARAMS) +{ + if (!(check_handle(ep, call, nh, SIP_500_INTERNAL_SERVER_ERROR))) + return 0; + + save_event_in_list(ctx, event, ep, call); + + switch (event) { + case nua_i_update: + if (200 <= status && status < 300) { + RESPOND(ep, call, nh, SIP_180_RINGING, TAG_END()); + } + return 0; + default: + break; + } + + switch (callstate(tags)) { + case nua_callstate_received: + RESPOND(ep, call, nh, SIP_183_SESSION_PROGRESS, + SIPTAG_REQUIRE_STR("100rel"), + TAG_IF(call->sdp, SOATAG_USER_SDP_STR(call->sdp)), + TAG_END()); + return 0; + case nua_callstate_early: + if (status == 180) + RESPOND(ep, call, nh, SIP_200_OK, TAG_END()); + return 0; + case nua_callstate_ready: + return 1; + case nua_callstate_terminated: + if (call) + nua_handle_destroy(call->nh), call->nh = NULL; + return 1; + default: + return 0; + } +} + + +int test_preconditions2(struct context *ctx) +{ + BEGIN(); + + struct endpoint *a = &ctx->a, *b = &ctx->b; + struct call *a_call = a->call, *b_call = b->call; + struct event *e, *eu, *ei; + enum nua_callstate ustate, istate; + + if (print_headings) + printf("TEST NUA-10.4.1: Call with preconditions and non-100rel 180\n"); + +/* Test 100rel and preconditions with NUTAG_ONLY183_100REL(1): + + A B + |-------INVITE------>| + |<----100 Trying-----| + | | + |<-------183---------| + |-------PRACK------->| + |<-------200---------| + | | + |-------UPDATE------>| + +------------------------+ + | |<-------200---------| | + | | | | + | |<-------180---------| | + | | | | + | |<------200 OK-------| | + +------------------------+ + |--------ACK-------->| + | | + |<-------BYE---------| + |-------200 OK-------| + | | + + Note that the boxed responses above can be re-ordered + (180 or 200 OK to INVITE is received before 200 OK to UPDATE). + ACK, however, is sent only after 200 OK to both UPDATE and INVITE. +*/ + + a_call->sdp = "m=audio 5008 RTP/AVP 8"; + b_call->sdp = "m=audio 5010 RTP/AVP 0 8"; + + nua_set_params(ctx->a.nua, + NUTAG_EARLY_MEDIA(1), + SIPTAG_SUPPORTED_STR("100rel, precondition, timer"), + TAG_END()); + run_a_until(ctx, nua_r_set_params, until_final_response); + + nua_set_params(ctx->b.nua, + NUTAG_EARLY_MEDIA(1), + NUTAG_ONLY183_100REL(1), + SIPTAG_SUPPORTED_STR("100rel, precondition, timer"), + TAG_END()); + run_b_until(ctx, nua_r_set_params, until_final_response); + + TEST_1(a_call->nh = nua_handle(a->nua, a_call, SIPTAG_TO(b->to), TAG_END())); + + INVITE(a, a_call, a_call->nh, + TAG_IF(!ctx->proxy_tests, NUTAG_URL(b->contact->m_url)), + SOATAG_USER_SDP_STR(a_call->sdp), + SIPTAG_SUPPORTED_STR("100rel"), + SIPTAG_REQUIRE_STR("precondition"), + TAG_END()); + + run_ab_until(ctx, -1, until_ready, -1, accept_updated); + + /* Client transitions: + INIT -(C1)-> CALLING: nua_invite(), nua_i_state + CALLING -(C2)-> PROCEEDING: nua_r_invite, nua_i_state + PROCEEDING -(C3+C4)-> READY: nua_r_invite, nua_i_state + */ + TEST_1(e = a->events->head); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_calling); /* CALLING */ + TEST_1(is_offer_sent(e->data->e_tags)); + + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_r_invite); + TEST(e->data->e_status, 183); + + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_proceeding); + TEST_1(is_answer_recv(e->data->e_tags)); + /* Offer is sent in PRACK */ + TEST_1(is_offer_sent(e->data->e_tags)); + + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_r_prack); + TEST(e->data->e_status, 200); + + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_proceeding); + TEST_1(is_answer_recv(e->data->e_tags)); + TEST_1(!is_offer_sent(e->data->e_tags)); + + /* Send UPDATE */ + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_proceeding); + TEST_1(!is_answer_recv(e->data->e_tags)); + TEST_1(is_offer_sent(e->data->e_tags)); + + /* The final response to the UPDATE and INVITE can be received in any order */ + eu = event_by_type(e->next, nua_r_update); + ei = event_by_type(e->next, nua_r_invite); + + TEST_1(e = eu); TEST_E(e->data->e_event, nua_r_update); + TEST(e->data->e_status, 200); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + ustate = callstate(e->data->e_tags); + TEST_1(is_answer_recv(e->data->e_tags)); + TEST_1(!is_offer_sent(e->data->e_tags)); + + TEST_1(e = ei); TEST_E(e->data->e_event, nua_r_invite); + TEST(e->data->e_status, 180); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_proceeding); /* PROCEEDING */ + TEST_1(!is_offer_answer_done(e->data->e_tags)); + + /* Final response to INVITE */ + TEST_1(ei = event_by_type(ei->next, nua_r_invite)); + + TEST_E(ei->data->e_event, nua_r_invite); TEST(ei->data->e_status, 200); + TEST_1(e = ei->next); TEST_E(e->data->e_event, nua_i_state); + istate = callstate(e->data->e_tags); + TEST_1(!is_offer_answer_done(e->data->e_tags)); + + if (eu == e->next) { + /* 200 OK to UPDATE is received after 200 OK to INVITE */ + TEST(ustate, nua_callstate_ready); + TEST(istate, nua_callstate_completing); + } + else { + /* 200 OK to UPDATE is received before 200 OK to INVITE */ + TEST(ustate, nua_callstate_proceeding); + TEST(istate, nua_callstate_ready); + } + + free_events_in_list(ctx, a->events); + + /* + Server transitions: + INIT -(S1)-> RECEIVED: nua_i_invite, nua_i_state + RECEIVED -(S2a)-> EARLY: nua_respond(), nua_i_state + EARLY -(S3b)-> COMPLETED: nua_respond(), nua_i_state + COMPLETED -(S4)-> READY: nua_i_ack, nua_i_state + */ + TEST_1(e = b->events->head); TEST_E(e->data->e_event, nua_i_invite); + TEST(e->data->e_status, 100); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_received); /* RECEIVED */ + + /* Responded with 183 Session Progress */ + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_early); /* EARLY */ + TEST_1(is_answer_sent(e->data->e_tags)); + + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_prack); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_early); /* EARLY */ + TEST_1(is_offer_recv(e->data->e_tags)); + TEST_1(is_answer_sent(e->data->e_tags)); + + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_update); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_early); /* EARLY */ + TEST_1(is_offer_recv(e->data->e_tags)); + TEST_1(is_answer_sent(e->data->e_tags)); + + /* Responded with 180 Ringing */ + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_early); /* EARLY */ + TEST_1(!is_offer_answer_done(e->data->e_tags)); + + /* Responded with 200 OK */ + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_completed); /* COMPLETED */ + TEST_1(!is_offer_answer_done(e->data->e_tags)); + + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_ack); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_ready); /* READY */ + TEST_1(!is_offer_answer_done(e->data->e_tags)); + TEST_1(!e->next); + free_events_in_list(ctx, b->events); + + if (print_headings) + printf("TEST NUA-10.4.1: PASSED\n"); + + if (print_headings) + printf("TEST NUA-10.4.2: terminate call\n"); + + BYE(b, b_call, b_call->nh, TAG_END()); + run_ab_until(ctx, -1, until_terminated, -1, until_terminated); + + /* B transitions: + READY --(T2)--> TERMINATING: nua_bye() + TERMINATING --(T3)--> TERMINATED: nua_r_bye, nua_i_state + */ + TEST_1(e = b->events->head); TEST_E(e->data->e_event, nua_r_bye); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_terminated); /* TERMINATED */ + TEST_1(!e->next); + free_events_in_list(ctx, b->events); + + /* A: READY -(T1)-> TERMINATED: nua_i_bye, nua_i_state */ + TEST_1(e = a->events->head); TEST_E(e->data->e_event, nua_i_bye); + TEST(e->data->e_status, 200); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_terminated); /* TERMINATED */ + TEST_1(!e->next); + free_events_in_list(ctx, a->events); + + nua_handle_destroy(a_call->nh), a_call->nh = NULL; + nua_handle_destroy(b_call->nh), b_call->nh = NULL; + + if (print_headings) + printf("TEST NUA-10.4.2: PASSED\n"); + + END(); +} + +/* + X ringing_updated2 ep + |-------INVITE------>| + | (sdp) | + |<----100 Trying-----| + | | + |<-------183---------| + | (sdp) | + |-------PRACK------->| + | (sdp) | + |<-------200---------| + | (sdp) | + | | + |-------UPDATE------>| + | (sdp) | + |<-------200---------| + | (sdp) | + | | + |<------UPDATE-------| + | (sdp) | + |--------200-------->| + | (sdp) | + | | +<using acccept_pracked> + | | + |<-------180---------| + |-------PRACK------->| + |<-------200---------| + | | + |<------200 OK-------| + |--------ACK-------->| + | | +*/ +int ringing_updated2(CONDITION_PARAMS) +{ + if (!(check_handle(ep, call, nh, SIP_500_INTERNAL_SERVER_ERROR))) + return 0; + + save_event_in_list(ctx, event, ep, call); + + switch (event) { + case nua_i_update: + if (200 <= status && status < 300) { + UPDATE(ep, call, nh, TAG_END()); + } + return 0; + case nua_r_update: + if (200 <= status && status < 300) { + RESPOND(ep, call, nh, SIP_180_RINGING, + SIPTAG_REQUIRE_STR("100rel"), + TAG_END()); + ep->next_condition = accept_pracked; + } + else if (300 <= status) { + RESPOND(ep, call, nh, SIP_500_INTERNAL_SERVER_ERROR, TAG_END()); + } + return 0; + default: + break; + } + + switch (callstate(tags)) { + case nua_callstate_received: + RESPOND(ep, call, nh, SIP_183_SESSION_PROGRESS, + SIPTAG_REQUIRE_STR("100rel"), + TAG_IF(call->sdp, SOATAG_USER_SDP_STR(call->sdp)), + TAG_END()); + return 0; + case nua_callstate_early: + return 0; + case nua_callstate_ready: + return 1; + case nua_callstate_terminated: + if (call) + nua_handle_destroy(call->nh), call->nh = NULL; + return 1; + default: + return 0; + } +} + +int test_update_by_uas(struct context *ctx) +{ + BEGIN(); + + struct endpoint *a = &ctx->a, *b = &ctx->b; + struct call *a_call = a->call, *b_call = b->call; + struct event *e, *ep, *ei; + sip_t *sip; + + /* -------------------------------------------------------------------- */ + + if (print_headings) + printf("TEST NUA-10.5.1: Call with dual UPDATE\n"); + +/* Test for update by UAS. + + A B + |-------INVITE------>| + |<----100 Trying-----| + | | + |<-------183---------| + |-------PRACK------->| + |<-------200---------| + | | + |-------UPDATE------>| + +------------------------+ + | |<-------200---------| | + | | | | + | |<------UPDATE-------| | + +------------------------+ + |--------200-------->| + | | + |<-------180---------| + |-------PRACK------->| + |<-------200---------| + | | + |<------200 OK-------| + |--------ACK-------->| + | | + |<-------BYE---------| + |-------200 OK-------| + | | + + Note that the 200 OK to UPDATE from A and UPDATE from B may be re-ordered + In that case, A will respond with 500/Retry-After and B will retry UPDATE. + See do {} while () loop below. +*/ + + a_call->sdp = "m=audio 5008 RTP/AVP 8"; + b_call->sdp = "m=audio 5010 RTP/AVP 0 8"; + + nua_set_params(ctx->a.nua, + NUTAG_EARLY_MEDIA(1), + SIPTAG_SUPPORTED_STR("100rel, precondition"), + TAG_END()); + run_a_until(ctx, nua_r_set_params, until_final_response); + + nua_set_params(ctx->b.nua, + NUTAG_EARLY_MEDIA(0), + SIPTAG_SUPPORTED_STR("100rel, precondition, timer"), + TAG_END()); + run_b_until(ctx, nua_r_set_params, until_final_response); + + TEST_1(a_call->nh = nua_handle(a->nua, a_call, SIPTAG_TO(b->to), TAG_END())); + + INVITE(a, a_call, a_call->nh, + TAG_IF(!ctx->proxy_tests, NUTAG_URL(b->contact->m_url)), + SOATAG_USER_SDP_STR(a_call->sdp), + SIPTAG_SUPPORTED_STR("100rel"), + SIPTAG_REQUIRE_STR("precondition"), + TAG_END()); + + run_ab_until(ctx, -1, until_ready, -1, ringing_updated2); + + /* Client transitions: + INIT -(C1)-> CALLING: nua_invite(), nua_i_state + CALLING -(C2)-> PROCEEDING: nua_r_invite, nua_i_state + PROCEEDING: nua_r_prack, nua_i_state + PROCEEDING: nua_r_update, nua_i_state + PROCEEDING: nua_i_update, nua_i_state + PROCEEDING -(C3+C4)-> READY: nua_r_invite, nua_i_state + */ + TEST_1(e = a->events->head); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_calling); /* CALLING */ + TEST_1(is_offer_sent(e->data->e_tags)); + + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_r_invite); + TEST(e->data->e_status, 183); + + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_proceeding); + TEST_1(is_answer_recv(e->data->e_tags)); + TEST_1(is_offer_sent(e->data->e_tags)); + + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_r_prack); + TEST(e->data->e_status, 200); + + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_proceeding); + TEST_1(is_answer_recv(e->data->e_tags)); + TEST_1(!is_offer_sent(e->data->e_tags)); + + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_proceeding); + TEST_1(!is_answer_recv(e->data->e_tags)); + TEST_1(is_offer_sent(e->data->e_tags)); + + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_r_update); + TEST(e->data->e_status, 200); + + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_proceeding); + TEST_1(is_answer_recv(e->data->e_tags)); + TEST_1(!is_offer_sent(e->data->e_tags)); + + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_update); + TEST(e->data->e_status, 200); + + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_proceeding); + TEST_1(is_offer_recv(e->data->e_tags)); + TEST_1(is_answer_sent(e->data->e_tags)); + + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_r_invite); + TEST(e->data->e_status, 180); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_proceeding); /* PROCEEDING */ + TEST_1(!is_offer_answer_done(e->data->e_tags)); + + ei = event_by_type(e->next, nua_r_invite); + ep = event_by_type(e->next, nua_r_prack); + if (!ep) { + run_a_until(ctx, -1, save_until_final_response); + ep = event_by_type(e->next, nua_r_prack); + } + + TEST_1(e = ep); TEST_E(e->data->e_event, nua_r_prack); + TEST(e->data->e_status, 200); + /* Does not have effect on call state */ + + TEST_1(e = ei); TEST_E(e->data->e_event, nua_r_invite); + TEST(e->data->e_status, 200); + if (ctx->proxy_tests) { + TEST_1(sip = sip_object(e->data->e_msg)); + TEST_1(sip->sip_session_expires); + TEST_S(sip->sip_session_expires->x_refresher, "uas"); + TEST_1(!sip_has_supported(sip->sip_require, "timer")); + } + + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_ready); /* READY */ + TEST_1(!is_offer_answer_done(e->data->e_tags)); + TEST_1(!e->next || !ep->next); + free_events_in_list(ctx, a->events); + + /* + Server transitions: + INIT -(S1)-> RECEIVED: nua_i_invite, nua_i_state + RECEIVED -(S2a)-> EARLY: nua_respond(183), nua_i_state + EARLY --> EARLY: nua_i_prack, nua_i_state + EARLY --> EARLY: nua_i_update, nua_i_state + EARLY --> EARLY: nua_update(), nua_i_state + EARLY --> EARLY: nua_r_update, nua_i_state + EARLY --> EARLY: nua_respond(180), nua_i_state + EARLY --> EARLY: nua_i_prack, nua_i_state + EARLY -(S3b)-> COMPLETED: nua_respond(200), nua_i_state + COMPLETED -(S4)-> READY: nua_i_ack, nua_i_state + */ + TEST_1(e = b->events->head); TEST_E(e->data->e_event, nua_i_invite); + TEST(e->data->e_status, 100); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_received); /* RECEIVED */ + + /* Responded with 183 Session Progress */ + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(e->data->e_status, 183); + TEST(callstate(e->data->e_tags), nua_callstate_early); /* EARLY */ + TEST_1(is_answer_sent(e->data->e_tags)); + + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_prack); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_early); /* EARLY */ + TEST_1(is_offer_recv(e->data->e_tags)); + TEST_1(is_answer_sent(e->data->e_tags)); + + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_update); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_early); /* EARLY */ + TEST_1(is_offer_recv(e->data->e_tags)); + TEST_1(is_answer_sent(e->data->e_tags)); + + /* sent UPDATE */ + do { + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_early); /* EARLY */ + TEST_1(is_offer_sent(e->data->e_tags)); + TEST_1(!is_offer_recv(e->data->e_tags)); + TEST_1(!is_answer_sent(e->data->e_tags)); + TEST_1(!is_answer_recv(e->data->e_tags)); + + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_r_update); + if (e->data->e_status == 100) { + TEST_1(sip = sip_object(e->data->e_msg)); + TEST(sip->sip_status->st_status, 500); TEST_1(sip->sip_retry_after); + } + } while (e->data->e_status == 100); + TEST(e->data->e_status, 200); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_early); /* EARLY */ + TEST_1(!is_offer_sent(e->data->e_tags)); /* XXX */ + TEST_1(!is_offer_recv(e->data->e_tags)); + TEST_1(!is_answer_sent(e->data->e_tags)); + TEST_1(is_answer_recv(e->data->e_tags)); + + /* Responded with 180 Ringing */ + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(e->data->e_status, 180); + TEST(callstate(e->data->e_tags), nua_callstate_early); /* EARLY */ + TEST_1(!is_offer_answer_done(e->data->e_tags)); + + /* 180 PRACKed */ + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_prack); + /* Does not have effect on call state */ + + /* Responded with 200 OK */ + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(e->data->e_status, 200); + TEST(callstate(e->data->e_tags), nua_callstate_completed); /* COMPLETED */ + TEST_1(!is_offer_answer_done(e->data->e_tags)); + + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_ack); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_ready); /* READY */ + TEST_1(!is_offer_answer_done(e->data->e_tags)); + TEST_1(!e->next); + free_events_in_list(ctx, b->events); + + if (print_headings) + printf("TEST NUA-10.5.1: PASSED\n"); + + if (print_headings) + printf("TEST NUA-10.5.2: terminate call\n"); + + BYE(b, b_call, b_call->nh, TAG_END()); + run_ab_until(ctx, -1, until_terminated, -1, until_terminated); + + /* B transitions: + READY --(T2)--> TERMINATING: nua_bye() + TERMINATING --(T3)--> TERMINATED: nua_r_bye, nua_i_state + */ + TEST_1(e = b->events->head); TEST_E(e->data->e_event, nua_r_bye); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_terminated); /* TERMINATED */ + TEST_1(!e->next); + free_events_in_list(ctx, b->events); + + /* A: READY -(T1)-> TERMINATED: nua_i_bye, nua_i_state */ + TEST_1(e = a->events->head); TEST_E(e->data->e_event, nua_i_bye); + TEST(e->data->e_status, 200); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_terminated); /* TERMINATED */ + TEST_1(!e->next); + free_events_in_list(ctx, a->events); + + nua_handle_destroy(a_call->nh), a_call->nh = NULL; + nua_handle_destroy(b_call->nh), b_call->nh = NULL; + + if (print_headings) + printf("TEST NUA-10.5.2: PASSED\n"); + + END(); +} + +int test_update_failure(struct context *ctx) +{ + BEGIN(); +#if 0 + struct endpoint *a = &ctx->a, *b = &ctx->b; + struct call *a_call = a->call, *b_call = b->call; + struct event *e, *eu, *ei; + enum nua_callstate ustate, istate; + + if (print_headings) + printf("TEST NUA-10.4.1: UPDATE failure terminating session\n"); + +/* Test UPDATE failing: + + A B + |-------INVITE------>| + |<----100 Trying-----| + | | + |<-------183---------| + |-------PRACK------->| + |<-------200---------| + | | + |<-------180---------| + | | + +------------------------+ + |<------200 OK-------| + |-------UPDATE------>| + | | + |<-------481---------| + | | + |--------ACK-------->| + | | + |<-------BYE---------| + |-------200 OK-------| + | | + +*/ + + a_call->sdp = "m=audio 5008 RTP/AVP 8"; + b_call->sdp = "m=audio 5010 RTP/AVP 0 8"; + + nua_set_params(ctx->a.nua, + NUTAG_EARLY_MEDIA(1), + SIPTAG_SUPPORTED_STR("100rel, precondition, timer"), + TAG_END()); + run_a_until(ctx, nua_r_set_params, until_final_response); + + nua_set_params(ctx->b.nua, + NUTAG_EARLY_MEDIA(1), + NUTAG_ONLY183_100REL(1), + SIPTAG_SUPPORTED_STR("100rel, precondition, timer"), + NUTAG_APPL_METHOD("UPDATE"), + TAG_END()); + run_b_until(ctx, nua_r_set_params, until_final_response); + + TEST_1(a_call->nh = nua_handle(a->nua, a_call, SIPTAG_TO(b->to), TAG_END())); + + INVITE(a, a_call, a_call->nh, + TAG_IF(!ctx->proxy_tests, NUTAG_URL(b->contact->m_url)), + SOATAG_USER_SDP_STR(a_call->sdp), + SIPTAG_SUPPORTED_STR("100rel"), + TAG_END()); + + run_ab_until(ctx, -1, save_until_final_response, -1, until_pracked); + + /* Client transitions: + INIT -(C1)-> CALLING: nua_invite(), nua_i_state + CALLING -(C2)-> PROCEEDING: nua_r_invite, nua_i_state + PROCEEDING -(C3+C4)-> READY: nua_r_invite, nua_i_state + */ + TEST_1(e = a->events->head); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_calling); /* CALLING */ + TEST_1(is_offer_sent(e->data->e_tags)); + + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_r_invite); + TEST(e->data->e_status, 183); + + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_proceeding); + TEST_1(is_answer_recv(e->data->e_tags)); + /* Offer is sent in PRACK */ + TEST_1(is_offer_sent(e->data->e_tags)); + + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_r_prack); + TEST(e->data->e_status, 200); + + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_proceeding); + TEST_1(is_answer_recv(e->data->e_tags)); + TEST_1(!is_offer_sent(e->data->e_tags)); + + /* Send UPDATE */ + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_proceeding); + TEST_1(!is_answer_recv(e->data->e_tags)); + TEST_1(is_offer_sent(e->data->e_tags)); + + /* The final response to the UPDATE and INVITE can be received in any order */ + eu = event_by_type(e->next, nua_r_update); + ei = event_by_type(e->next, nua_r_invite); + + TEST_1(e = eu); TEST_E(e->data->e_event, nua_r_update); + TEST(e->data->e_status, 200); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + ustate = callstate(e->data->e_tags); + TEST_1(is_answer_recv(e->data->e_tags)); + TEST_1(!is_offer_sent(e->data->e_tags)); + + TEST_1(e = ei); TEST_E(e->data->e_event, nua_r_invite); + TEST(e->data->e_status, 180); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_proceeding); /* PROCEEDING */ + TEST_1(!is_offer_answer_done(e->data->e_tags)); + + /* Final response to INVITE */ + TEST_1(ei = event_by_type(ei->next, nua_r_invite)); + + TEST_E(ei->data->e_event, nua_r_invite); TEST(ei->data->e_status, 200); + TEST_1(e = ei->next); TEST_E(e->data->e_event, nua_i_state); + istate = callstate(e->data->e_tags); + TEST_1(!is_offer_answer_done(e->data->e_tags)); + + if (eu == e->next) { + /* 200 OK to UPDATE is received after 200 OK to INVITE */ + TEST(ustate, nua_callstate_ready); + TEST(istate, nua_callstate_completing); + } + else { + /* 200 OK to UPDATE is received before 200 OK to INVITE */ + TEST(ustate, nua_callstate_proceeding); + TEST(istate, nua_callstate_ready); + } + + free_events_in_list(ctx, a->events); + + /* + Server transitions: + INIT -(S1)-> RECEIVED: nua_i_invite, nua_i_state + RECEIVED -(S2a)-> EARLY: nua_respond(), nua_i_state + EARLY -(S3b)-> COMPLETED: nua_respond(), nua_i_state + COMPLETED -(S4)-> READY: nua_i_ack, nua_i_state + */ + TEST_1(e = b->events->head); TEST_E(e->data->e_event, nua_i_invite); + TEST(e->data->e_status, 100); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_received); /* RECEIVED */ + + /* Responded with 183 Session Progress */ + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_early); /* EARLY */ + TEST_1(is_answer_sent(e->data->e_tags)); + + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_prack); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_early); /* EARLY */ + TEST_1(is_offer_recv(e->data->e_tags)); + TEST_1(is_answer_sent(e->data->e_tags)); + + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_update); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_early); /* EARLY */ + TEST_1(is_offer_recv(e->data->e_tags)); + TEST_1(is_answer_sent(e->data->e_tags)); + + /* Responded with 180 Ringing */ + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_early); /* EARLY */ + TEST_1(!is_offer_answer_done(e->data->e_tags)); + + /* Responded with 200 OK */ + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_completed); /* COMPLETED */ + TEST_1(!is_offer_answer_done(e->data->e_tags)); + + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_ack); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_ready); /* READY */ + TEST_1(!is_offer_answer_done(e->data->e_tags)); + TEST_1(!e->next); + free_events_in_list(ctx, b->events); + + if (print_headings) + printf("TEST NUA-10.4.1: PASSED\n"); + + if (print_headings) + printf("TEST NUA-10.4.2: terminate call\n"); + + BYE(b, b_call, b_call->nh, TAG_END()); + run_ab_until(ctx, -1, until_terminated, -1, until_terminated); + + /* B transitions: + READY --(T2)--> TERMINATING: nua_bye() + TERMINATING --(T3)--> TERMINATED: nua_r_bye, nua_i_state + */ + TEST_1(e = b->events->head); TEST_E(e->data->e_event, nua_r_bye); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_terminated); /* TERMINATED */ + TEST_1(!e->next); + free_events_in_list(ctx, b->events); + + /* A: READY -(T1)-> TERMINATED: nua_i_bye, nua_i_state */ + TEST_1(e = a->events->head); TEST_E(e->data->e_event, nua_i_bye); + TEST(e->data->e_status, 200); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_terminated); /* TERMINATED */ + TEST_1(!e->next); + free_events_in_list(ctx, a->events); + + nua_handle_destroy(a_call->nh), a_call->nh = NULL; + nua_handle_destroy(b_call->nh), b_call->nh = NULL; + + if (print_headings) + printf("TEST NUA-10.4.2: PASSED\n"); +#endif + END(); +} + +int cancel_when_pracked(CONDITION_PARAMS); +int alert_call(CONDITION_PARAMS); + +int test_180rel_cancel1(struct context *ctx) +{ + BEGIN(); + + struct endpoint *a = &ctx->a, *b = &ctx->b; + struct call *a_call = a->call, *b_call = b->call; + struct event *e; + + if (print_headings) + printf("TEST NUA-10.6: CANCEL after PRACK\n"); + +/* Test for 100rel: + + A B + |-------INVITE------>| + |<----100 Trying-----| + | | + |<-------180---------| + |-------PRACK------->| + |<-------200---------| + | | + |------CANCEL------->| + |<------200 OK-------| + | | + |<-------487---------| + |--------ACK-------->| + | | + +*/ + + a_call->sdp = "m=audio 5008 RTP/AVP 8"; + b_call->sdp = "m=audio 5010 RTP/AVP 0 8"; + + nua_set_params(ctx->a.nua, + NUTAG_EARLY_MEDIA(1), + NUTAG_ONLY183_100REL(0), + TAG_END()); + run_a_until(ctx, nua_r_set_params, until_final_response); + + nua_set_params(ctx->b.nua, + NUTAG_EARLY_MEDIA(1), + NUTAG_ONLY183_100REL(0), + TAG_END()); + run_b_until(ctx, nua_r_set_params, until_final_response); + + TEST_1(a_call->nh = nua_handle(a->nua, a_call, SIPTAG_TO(b->to), TAG_END())); + + INVITE(a, a_call, a_call->nh, + TAG_IF(!ctx->proxy_tests, NUTAG_URL(b->contact->m_url)), + SOATAG_USER_SDP_STR(a_call->sdp), + TAG_END()); + + run_ab_until(ctx, -1, cancel_when_pracked, -1, alert_call); + + /* Client transitions: + INIT -(C1)-> CALLING: nua_invite(), nua_i_state + CALLING -(C2)-> PROCEEDING: nua_r_invite, nua_i_state, nua_r_prack + PROCEEDING -(C3+C4)-> TERMINATED: nua_r_invite, nua_i_state + */ + TEST_1(e = a->events->head); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_calling); /* CALLING */ + TEST_1(is_offer_sent(e->data->e_tags)); + + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_r_invite); + TEST(e->data->e_status, 180); + + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_proceeding); + TEST_1(is_answer_recv(e->data->e_tags)); + TEST_1(!is_offer_sent(e->data->e_tags)); + + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_r_prack); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_r_cancel); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_r_invite); + TEST(e->data->e_status, 487); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_terminated); + TEST_1(!e->next); + + free_events_in_list(ctx, a->events); + + /* + Server transitions: + INIT -(S1)-> RECEIVED: nua_i_invite, nua_i_state + RECEIVED -(S2a)-> EARLY: nua_respond(), nua_i_state + Option A: + EARLY -(S10)-> TERMINATED: nua_i_cancel, nua_i_state + Option B: + EARLY -(S3b)-> COMPLETED: nua_respond(), nua_i_state + COMPLETED -(S4)-> READY: nua_i_ack, nua_i_state + READY -(T1)-> TERMINATED: nua_i_bye, nua_i_state + */ + TEST_1(e = b->events->head); TEST_E(e->data->e_event, nua_i_invite); + TEST(e->data->e_status, 100); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_received); /* RECEIVED */ + TEST_1(is_offer_recv(e->data->e_tags)); + + /* Responded with 180 Ringing */ + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_early); /* EARLY */ + TEST_1(is_answer_sent(e->data->e_tags)); + + /* 180 is PRACKed */ + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_prack); + /* Does not have effect on call state */ + + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_cancel); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_terminated); /* TERMINATED */ + + free_events_in_list(ctx, b->events); + + nua_handle_destroy(a_call->nh), a_call->nh = NULL; + nua_handle_destroy(b_call->nh), b_call->nh = NULL; + + if (print_headings) + printf("TEST NUA-10.6: PASSED\n"); + + END(); +} + +int cancel_when_pracked(CONDITION_PARAMS) +{ + if (!(check_handle(ep, call, nh, SIP_500_INTERNAL_SERVER_ERROR))) + return 0; + + save_event_in_list(ctx, event, ep, call); + + if (event == nua_r_prack) + CANCEL(ep, call, nh, TAG_END()); + + switch (callstate(tags)) { + case nua_callstate_proceeding: + return 0; + case nua_callstate_ready: + return 1; + case nua_callstate_terminated: + return 1; + default: + return 0; + } +} + +int test_180rel_cancel2(struct context *ctx) +{ + BEGIN(); + + struct endpoint *a = &ctx->a, *b = &ctx->b; + struct call *a_call = a->call, *b_call = b->call; + struct event *e, *ep, *ec; + + if (print_headings) + printf("TEST NUA-10.7: CANCEL after 100rel 180\n"); + +/* Test for 100rel: + + A B + |-------INVITE------>| + |<----100 Trying-----| + | | + |<-------180---------| + |-------PRACK------->| + |<-------200---------| + | | + |------CANCEL------->| + |<------200 OK-------| + | | + |<-------487---------| + |--------ACK-------->| + | | + +*/ + + a_call->sdp = "m=audio 5008 RTP/AVP 8"; + b_call->sdp = "m=audio 5010 RTP/AVP 0 8"; + + nua_set_params(ctx->a.nua, + NUTAG_EARLY_MEDIA(1), + NUTAG_ONLY183_100REL(0), + TAG_END()); + run_a_until(ctx, nua_r_set_params, until_final_response); + + nua_set_params(ctx->b.nua, + NUTAG_EARLY_MEDIA(1), + NUTAG_ONLY183_100REL(0), + TAG_END()); + run_b_until(ctx, nua_r_set_params, until_final_response); + + TEST_1(a_call->nh = nua_handle(a->nua, a_call, SIPTAG_TO(b->to), TAG_END())); + + INVITE(a, a_call, a_call->nh, + TAG_IF(!ctx->proxy_tests, NUTAG_URL(b->contact->m_url)), + SOATAG_USER_SDP_STR(a_call->sdp), + TAG_END()); + + run_ab_until(ctx, -1, cancel_when_ringing, -1, accept_pracked2); + + /* Client transitions: + INIT -(C1)-> CALLING: nua_invite(), nua_i_state + CALLING -(C2)-> PROCEEDING: nua_r_invite, nua_i_state, nua_r_prack + PROCEEDING -(C3+C4)-> TERMINATED: nua_r_invite, nua_i_state + */ + TEST_1(e = a->events->head); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_calling); /* CALLING */ + TEST_1(is_offer_sent(e->data->e_tags)); + + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_r_invite); + TEST(e->data->e_status, 180); + + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_proceeding); + TEST_1(is_answer_recv(e->data->e_tags)); + TEST_1(!is_offer_sent(e->data->e_tags)); + +#define NEXT_SKIP(x) \ + do { TEST_1(e = e->next); } \ + while (x); + + NEXT_SKIP(e->data->e_event == nua_r_prack || + e->data->e_event == nua_r_cancel || + e->data->e_event == nua_i_state); + + TEST_E(e->data->e_event, nua_r_invite); + if (e->data->e_status == 487) { + TEST(e->data->e_status, 487); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_terminated); + if (e->next) + NEXT_SKIP(e->data->e_event == nua_r_prack || e->data->e_event == nua_r_cancel); + TEST_1(!e->next); + } + else { + TEST(e->data->e_status, 200); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_ready); + + BYE(a, a_call, a_call->nh, TAG_END()); + run_ab_until(ctx, -1, until_terminated, -1, until_terminated); + + NEXT_SKIP(e->data->e_event == nua_r_prack || e->data->e_event == nua_r_cancel); + TEST_E(e->data->e_event, nua_r_bye); + TEST(e->data->e_status, 200); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_terminated); /* TERMINATED */ + TEST_1(!e->next); + } + + free_events_in_list(ctx, a->events); + + /* + Server transitions: + INIT -(S1)-> RECEIVED: nua_i_invite, nua_i_state + RECEIVED -(S2a)-> EARLY: nua_respond(), nua_i_state + Option A: + EARLY -(S10)-> TERMINATED: nua_i_cancel, nua_i_state + Option B: + EARLY -(S3b)-> COMPLETED: nua_respond(), nua_i_state + COMPLETED -(S4)-> READY: nua_i_ack, nua_i_state + READY -(T1)-> TERMINATED: nua_i_bye, nua_i_state + */ + TEST_1(e = b->events->head); TEST_E(e->data->e_event, nua_i_invite); + TEST(e->data->e_status, 100); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_received); /* RECEIVED */ + TEST_1(is_offer_recv(e->data->e_tags)); + + /* Responded with 180 Ringing */ + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_early); /* EARLY */ + TEST_1(is_answer_sent(e->data->e_tags)); + + ec = event_by_type(e->next, nua_i_cancel); + + if (ec) { + TEST_1(e = ec); TEST_E(e->data->e_event, nua_i_cancel); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_terminated); /* TERMINATED */ + } + else { + /* 180 is PRACKed, PRACK does not have effect on call state */ + ep = event_by_type(e->next, nua_i_prack); + if (ep) e = ep; + /* Responded with 200 OK */ + TEST_1(e = event_by_type(e->next, nua_i_state)); + TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_completed); /* COMPLETED */ + TEST_1(!is_offer_answer_done(e->data->e_tags)); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_ack); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_ready); /* READY */ + TEST_1(!is_offer_answer_done(e->data->e_tags)); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_bye); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_terminated); /* TERMINATED */ + } + + free_events_in_list(ctx, b->events); + + nua_handle_destroy(a_call->nh), a_call->nh = NULL; + nua_handle_destroy(b_call->nh), b_call->nh = NULL; + + if (print_headings) + printf("TEST NUA-10.7: PASSED\n"); + + END(); +} + +int redirect_pracked(CONDITION_PARAMS); + +int test_180rel_redirected(struct context *ctx) +{ + BEGIN(); + + struct endpoint *a = &ctx->a, *b = &ctx->b; + struct call *a_call = a->call, *b_call = b->call; + struct event *e, *ep, *ei; + sip_t *sip; + + if (print_headings) + printf("TEST NUA-10.8.1: Call with 100rel and 180\n"); + +/* Test for 100rel: + + A B + |-------INVITE------>| + |<----100 Trying-----| + | | + |<-------180---------| + |-------PRACK------->| + |<-------200---------| + | | + |<----302 Moved------| + |--------ACK-------->| + | | + |-------INVITE------>| + |<----100 Trying-----| + | | + |<-------180---------| + |-------PRACK------->| + |<-------200---------| + | | + |<------200 OK-------| + |--------ACK-------->| + | | + |<-------BYE---------| + |-------200 OK-------| + | | + +*/ + + a_call->sdp = "m=audio 5008 RTP/AVP 8"; + b_call->sdp = "m=audio 5010 RTP/AVP 0 8"; + + nua_set_params(ctx->a.nua, + NUTAG_EARLY_MEDIA(1), + TAG_END()); + run_a_until(ctx, nua_r_set_params, until_final_response); + + nua_set_params(ctx->b.nua, + NUTAG_EARLY_MEDIA(1), + NUTAG_SESSION_TIMER(180), + TAG_END()); + run_b_until(ctx, nua_r_set_params, until_final_response); + + TEST_1(a_call->nh = nua_handle(a->nua, a_call, SIPTAG_TO(b->to), TAG_END())); + + INVITE(a, a_call, a_call->nh, + TAG_IF(!ctx->proxy_tests, NUTAG_URL(b->contact->m_url)), + SOATAG_USER_SDP_STR(a_call->sdp), + TAG_END()); + + run_ab_until(ctx, -1, until_ready, -1, redirect_pracked); + + /* Client transitions: + INIT -(C1)-> CALLING: nua_invite(), nua_i_state + CALLING -(C2)-> PROCEEDING: nua_r_invite, nua_i_state, nua_r_prack + PROCEEDING->(redirected)->CALLING: nua_r_invite, nua_i_state + CALLING -(C2)-> PROCEEDING: nua_r_invite, nua_i_state, nua_r_prack + PROCEEDING -(C3+C4)-> READY: nua_r_invite, nua_i_state + */ + TEST_1(e = a->events->head); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_calling); /* CALLING */ + TEST_1(is_offer_sent(e->data->e_tags)); + + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_r_invite); + TEST(e->data->e_status, 180); + + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_proceeding); + TEST_1(is_answer_recv(e->data->e_tags)); + TEST_1(!is_offer_sent(e->data->e_tags)); + + ei = event_by_type(e->next, nua_r_invite); /* 302 */ + ep = event_by_type(e->next, nua_r_prack); /* 200 */ + if (!ep) { + run_a_until(ctx, -1, save_until_final_response); + ep = event_by_type(e->next, nua_r_prack); + } + + TEST_1(e = ep); TEST_E(e->data->e_event, nua_r_prack); + TEST(e->data->e_status, 200); + + TEST_1(e = ei); TEST_E(e->data->e_event, nua_r_invite); + TEST(e->data->e_status, 100); + TEST_1(sip = sip_object(e->data->e_msg)); + TEST(sip->sip_status->st_status, 302); + + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_calling); /* CALLING */ + + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_r_invite); + TEST(e->data->e_status, 180); + + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_proceeding); + TEST_1(is_answer_recv(e->data->e_tags)); + TEST_1(!is_offer_sent(e->data->e_tags)); + + ei = event_by_type(e->next, nua_r_invite); + ep = event_by_type(e->next, nua_r_prack); + if (!ep) { + run_a_until(ctx, -1, save_until_final_response); + ep = event_by_type(e->next, nua_r_prack); + } + + TEST_1(e = ep); TEST_E(e->data->e_event, nua_r_prack); + TEST(e->data->e_status, 200); + + TEST_1(e = ei); TEST_E(e->data->e_event, nua_r_invite); + TEST(e->data->e_status, 200); + + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_ready); /* READY */ + TEST_1(!is_offer_answer_done(e->data->e_tags)); + + TEST_1(!e->next || !ep->next); + free_events_in_list(ctx, a->events); + + /* + Server transitions: + INIT -(S1)-> RECEIVED: nua_i_invite, nua_i_state + RECEIVED -(S2a)-> EARLY: nua_respond(), nua_i_state + EARLY -(S3b)-> COMPLETED: nua_respond(), nua_i_state + COMPLETED -(S4)-> READY: nua_i_ack, nua_i_state + */ + TEST_1(e = b->events->head); TEST_E(e->data->e_event, nua_i_invite); + TEST(e->data->e_status, 100); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_received); /* RECEIVED */ + TEST_1(is_offer_recv(e->data->e_tags)); + + /* Responded with 180 Ringing */ + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_early); /* EARLY */ + TEST_1(is_answer_sent(e->data->e_tags)); + + /* 180 is PRACKed */ + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_prack); + /* 302 terminates call */ + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_terminated); /* Terminated */ + + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_invite); + TEST(e->data->e_status, 100); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_received); /* RECEIVED */ + TEST_1(is_offer_recv(e->data->e_tags)); + + /* Responded with 180 Ringing */ + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_early); /* EARLY */ + TEST_1(is_answer_sent(e->data->e_tags)); + + /* 180 is PRACKed */ + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_prack); + /* Does not have effect on call state */ + + /* Respond with 200 OK */ + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_completed); /* COMPLETED */ + TEST_1(!is_offer_answer_done(e->data->e_tags)); + + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_ack); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_ready); /* READY */ + TEST_1(!is_offer_answer_done(e->data->e_tags)); + TEST_1(!e->next); + free_events_in_list(ctx, b->events); + + if (print_headings) + printf("TEST NUA-10.8.1: PASSED\n"); + + if (print_headings) + printf("TEST NUA-10.8.2: terminate call\n"); + + BYE(b, b_call, b_call->nh, TAG_END()); + run_ab_until(ctx, -1, until_terminated, -1, until_terminated); + + /* B transitions: + READY --(T2)--> TERMINATING: nua_bye() + TERMINATING --(T3)--> TERMINATED: nua_r_bye, nua_i_state + */ + TEST_1(e = b->events->head); TEST_E(e->data->e_event, nua_r_bye); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_terminated); /* TERMINATED */ + TEST_1(!e->next); + free_events_in_list(ctx, b->events); + + /* A: READY -(T1)-> TERMINATED: nua_i_bye, nua_i_state */ + TEST_1(e = a->events->head); TEST_E(e->data->e_event, nua_i_bye); + TEST(e->data->e_status, 200); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_terminated); /* TERMINATED */ + TEST_1(!e->next); + free_events_in_list(ctx, a->events); + + nua_handle_destroy(a_call->nh), a_call->nh = NULL; + nua_handle_destroy(b_call->nh), b_call->nh = NULL; + + if (print_headings) + printf("TEST NUA-10.8.2: PASSED\n"); + + END(); +} + +int redirect_pracked(CONDITION_PARAMS) +{ + if (!(check_handle(ep, call, nh, SIP_500_INTERNAL_SERVER_ERROR))) + return 0; + + save_event_in_list(ctx, event, ep, call); + + if (event == nua_i_prack) { + if (!ep->flags.bit0) { + sip_contact_t m[1]; + + ep->flags.bit0 = 1; + + *m = *ep->contact; + m->m_url->url_user = "302"; + RESPOND(ep, call, nh, SIP_302_MOVED_TEMPORARILY, SIPTAG_CONTACT(m), TAG_END()); + return 0; + } + else { + RESPOND(ep, call, nh, SIP_200_OK, + TAG_IF(call->sdp, SOATAG_USER_SDP_STR(call->sdp)), + TAG_END()); + } + } + + + switch (callstate(tags)) { + case nua_callstate_received: + RESPOND(ep, call, nh, SIP_180_RINGING, + TAG_IF(call->sdp, SOATAG_USER_SDP_STR(call->sdp)), + TAG_END()); + return 0; + case nua_callstate_ready: + return 1; + case nua_callstate_terminated: + if (call) + nua_handle_destroy(call->nh), call->nh = NULL; + return 0; + default: + return 0; + } +} + +int test_100rel(struct context *ctx) +{ + int retval = 0; + + retval = test_180rel(ctx); RETURN_ON_SINGLE_FAILURE(retval); + retval = test_prack_auth(ctx); RETURN_ON_SINGLE_FAILURE(retval); + retval = test_183rel(ctx); RETURN_ON_SINGLE_FAILURE(retval); + retval = test_preconditions(ctx); RETURN_ON_SINGLE_FAILURE(retval); + retval = test_preconditions2(ctx); RETURN_ON_SINGLE_FAILURE(retval); + retval = test_update_by_uas(ctx); RETURN_ON_SINGLE_FAILURE(retval); + retval = test_update_failure(ctx); RETURN_ON_SINGLE_FAILURE(retval); + retval = test_180rel_cancel1(ctx); RETURN_ON_SINGLE_FAILURE(retval); + retval = test_180rel_cancel2(ctx); RETURN_ON_SINGLE_FAILURE(retval); + retval = test_180rel_redirected(ctx); RETURN_ON_SINGLE_FAILURE(retval); + + nua_set_params(ctx->a.nua, + NUTAG_EARLY_MEDIA(0), + SIPTAG_SUPPORTED(ctx->a.supported), + TAG_END()); + run_a_until(ctx, nua_r_set_params, until_final_response); + + nua_set_params(ctx->b.nua, + NUTAG_EARLY_MEDIA(0), + NUTAG_ONLY183_100REL(0), + SIPTAG_SUPPORTED(ctx->b.supported), + TAG_END()); + run_b_until(ctx, nua_r_set_params, until_final_response); + + nua_set_params(ctx->c.nua, + NUTAG_EARLY_MEDIA(0), + NUTAG_ONLY183_100REL(0), + SIPTAG_SUPPORTED(ctx->c.supported), + TAG_END()); + run_c_until(ctx, nua_r_set_params, until_final_response); + + return retval; +} Added: freeswitch/trunk/libs/sofia-sip/tests/test_basic_call.c ============================================================================== --- (empty file) +++ freeswitch/trunk/libs/sofia-sip/tests/test_basic_call.c Wed May 14 15:10:54 2008 @@ -0,0 +1,1999 @@ +/* + * This file is part of the Sofia-SIP package + * + * Copyright (C) 2005 Nokia Corporation. + * + * Contact: Pekka Pessi <pekka.pessi@nokia.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public License + * as published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + */ + +/**@CFILE test_nua_basic_call.c + * @brief Test basic call. + * + * @author Pekka Pessi <Pekka.Pessi@nokia.com> + * @author Martti Mela <Martti Mela@nokia.com> + * + * @date Created: Wed Aug 17 12:12:12 EEST 2005 ppessi + */ + +#include "config.h" + +#include "test_nua.h" +#include <sofia-sip/su_tag_class.h> + +#if HAVE_FUNC +#elif HAVE_FUNCTION +#define __func__ __FUNCTION__ +#else +#define __func__ "test_basic_call" +#endif + +/* ======================================================================== */ + +int until_terminated(CONDITION_PARAMS) +{ + if (!check_handle(ep, call, nh, SIP_500_INTERNAL_SERVER_ERROR)) + return 0; + + save_event_in_list(ctx, event, ep, call); + + return event == nua_i_state && callstate(tags) == nua_callstate_terminated; +} + +/* + X accept_call ep + | | + |-------INVITE------>| + |<----100 Trying-----| + | | + |<----180 Ringing----| + | | + |<--------200--------| + |---------ACK------->| +*/ +int accept_call(CONDITION_PARAMS) +{ + if (!(check_handle(ep, call, nh, SIP_500_INTERNAL_SERVER_ERROR))) + return 0; + + save_event_in_list(ctx, event, ep, call); + + switch (callstate(tags)) { + case nua_callstate_received: + RESPOND(ep, call, nh, SIP_180_RINGING, + TAG_END()); + return 0; + case nua_callstate_early: + RESPOND(ep, call, nh, SIP_200_OK, + TAG_IF(call->sdp, SOATAG_USER_SDP_STR(call->sdp)), + TAG_END()); + return 0; + case nua_callstate_ready: + return 1; + case nua_callstate_terminated: + if (call) + nua_handle_destroy(call->nh), call->nh = NULL; + return 1; + default: + return 0; + } +} + + +/* + X accept_call ep + | | + |-------INVITE------>| + |<----100 Trying-----| + | | + |<----180 Ringing----| + | | + |<--------200--------| + |---------ACK------->| +*/ +int accept_call_with_early_sdp(CONDITION_PARAMS) +{ + if (!(check_handle(ep, call, nh, SIP_500_INTERNAL_SERVER_ERROR))) + return 0; + + save_event_in_list(ctx, event, ep, call); + + switch (callstate(tags)) { + case nua_callstate_received: + RESPOND(ep, call, nh, SIP_180_RINGING, + TAG_IF(call->sdp, SOATAG_USER_SDP_STR(call->sdp)), + NUTAG_M_DISPLAY("Bob"), + NUTAG_M_USERNAME("b+b"), + TAG_END()); + return 0; + case nua_callstate_early: + RESPOND(ep, call, nh, SIP_200_OK, + TAG_IF(call->sdp, SOATAG_USER_SDP_STR(call->sdp)), + TAG_END()); + return 0; + case nua_callstate_ready: + return 1; + case nua_callstate_terminated: + if (call) + nua_handle_destroy(call->nh), call->nh = NULL; + return 1; + default: + return 0; + } +} + + +/* + X INVITE + | | + |-------INVITE------>| + |<--------200--------| + |---------ACK------->| +*/ +int until_ready(CONDITION_PARAMS) +{ + if (!(check_handle(ep, call, nh, SIP_500_INTERNAL_SERVER_ERROR))) + return 0; + + save_event_in_list(ctx, event, ep, call); + + switch (callstate(tags)) { + case nua_callstate_ready: + return 1; + case nua_callstate_terminated: + if (call) + nua_handle_destroy(call->nh), call->nh = NULL; + return 1; + default: + return 0; + } +} + +/* ======================================================================== */ + +/* Basic call: + + A B + |-------INVITE------>| + |<----100 Trying-----| + | | + |<----180 Ringing----| + | | + |<------200 OK-------| + |--------ACK-------->| + | | + |<-------BYE---------| + |-------200 OK------>| + | | + + Client transitions: + INIT -(C1)-> CALLING -(C2a)-> PROCEEDING -(C3+C4)-> READY + Server transitions: + INIT -(S1)-> RECEIVED -(S2a)-> EARLY -(S3b)-> COMPLETED -(S4)-> READY + + B sends BYE: + READY -(T2)-> TERMINATING -(T3)-> TERMINATED + A receives BYE: + READY -(T1)-> TERMINATED + + See @page nua_call_model in nua.docs for more information +*/ + +int test_basic_call_1(struct context *ctx) +{ + BEGIN(); + + struct endpoint *a = &ctx->a, *b = &ctx->b; + struct call *a_call = a->call, *b_call = b->call; + struct event *e; + sip_t *sip; + sip_replaces_t *repa, *repb; + nua_handle_t *nh; + + static int once = 0; + sip_time_t se, min_se; + + if (print_headings) + printf("TEST NUA-3.1: Basic call\n"); + + /* Disable session timer from proxy */ + test_proxy_get_session_timer(ctx->p, &se, &min_se); + test_proxy_set_session_timer(ctx->p, 0, 0); + + a_call->sdp = "m=audio 5008 RTP/AVP 8"; + b_call->sdp = "m=audio 5010 RTP/AVP 0 8"; + + TEST_1(a_call->nh = nua_handle(a->nua, a_call, SIPTAG_TO(b->to), TAG_END())); + + TEST_1(!nua_handle_has_active_call(a_call->nh)); + TEST_1(!nua_handle_has_call_on_hold(a_call->nh)); + + INVITE(a, a_call, a_call->nh, + TAG_IF(!ctx->proxy_tests, NUTAG_URL(b->contact->m_url)), + SOATAG_USER_SDP_STR(a_call->sdp), + NUTAG_M_USERNAME("a+a"), + NUTAG_M_DISPLAY("Alice"), + TAG_END()); + + run_ab_until(ctx, -1, until_ready, -1, accept_call_with_early_sdp); + + TEST_1(nua_handle_has_active_call(a_call->nh)); + TEST_1(!nua_handle_has_call_on_hold(a_call->nh)); + + TEST_1(nua_handle_has_active_call(b_call->nh)); + TEST_1(!nua_handle_has_call_on_hold(b_call->nh)); + + TEST_1(repa = nua_handle_make_replaces(a_call->nh, nua_handle_home(a_call->nh), 0)); + TEST_1(repb = nua_handle_make_replaces(b_call->nh, nua_handle_home(b_call->nh), 0)); + + TEST_S(repa->rp_call_id, repb->rp_call_id); + + TEST_1(!nua_handle_by_replaces(a->nua, repa)); + TEST_1(!nua_handle_by_replaces(b->nua, repb)); + + TEST_1(nh = nua_handle_by_replaces(a->nua, repb)); + TEST_P(nh, a_call->nh); + nua_handle_unref(nh); + + TEST_1(nh = nua_handle_by_replaces(b->nua, repa)); + TEST_P(nh, b_call->nh); + nua_handle_unref(nh); + + /* Client transitions: + INIT -(C1)-> CALLING: nua_invite(), nua_i_state + CALLING -(C2)-> PROCEEDING: nua_r_invite, nua_i_state + PROCEEDING -(C3+C4)-> READY: nua_r_invite, nua_i_state + */ + TEST_1(e = a->events->head); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_calling); /* CALLING */ + TEST_1(is_offer_sent(e->data->e_tags)); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_r_invite); + TEST(e->data->e_status, 180); + TEST_1(sip = sip_object(e->data->e_msg)); + TEST_S(sip->sip_call_id->i_id, repb->rp_call_id); + TEST_S(sip->sip_from->a_tag, repb->rp_to_tag); + TEST_S(sip->sip_to->a_tag, repb->rp_from_tag); + TEST_1(sip->sip_payload); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_proceeding); /* PROCEEDING */ + TEST_1(is_answer_recv(e->data->e_tags)); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_r_invite); + TEST(e->data->e_status, 200); + TEST_1(sip = sip_object(e->data->e_msg)); + TEST_1(sip->sip_payload); + TEST_1(sip->sip_contact); + TEST_S(sip->sip_contact->m_display, "Bob"); + TEST_S(sip->sip_contact->m_url->url_user, "b+b"); + if (!once) { + /* The session expiration is not used by default. */ + TEST_1(sip->sip_session_expires == NULL); + } + /* Test that B uses application-specific contact */ + if (ctx->proxy_tests) + TEST_1(sip->sip_contact->m_url->url_user); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_ready); /* READY */ + TEST_1(!e->next); + free_events_in_list(ctx, a->events); + + /* + Server transitions: + INIT -(S1)-> RECEIVED: nua_i_invite, nua_i_state + RECEIVED -(S2a)-> EARLY: nua_respond(), nua_i_state + EARLY -(S3b)-> COMPLETED: nua_respond(), nua_i_state + COMPLETED -(S4)-> READY: nua_i_ack, nua_i_state + */ + TEST_1(e = b->events->head); TEST_E(e->data->e_event, nua_i_invite); + TEST(e->data->e_status, 100); + TEST_1(sip = sip_object(e->data->e_msg)); + TEST_1(sip->sip_contact); + TEST_S(sip->sip_contact->m_display, "Alice"); + TEST_S(sip->sip_contact->m_url->url_user, "a+a"); + if (!once++) { + /* The Session-Expires header is not used by default. */ + TEST_1(sip->sip_session_expires == NULL); + } + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_received); /* RECEIVED */ + TEST_1(is_offer_recv(e->data->e_tags)); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_early); /* EARLY */ + TEST_1(is_answer_sent(e->data->e_tags)); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_completed); /* COMPLETED */ + TEST_1(is_answer_sent(e->data->e_tags)); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_ack); + TEST_1(sip = sip_object(e->data->e_msg)); + TEST_S(sip->sip_call_id->i_id, repa->rp_call_id); + TEST_S(sip->sip_from->a_tag, repa->rp_from_tag); + TEST_S(sip->sip_to->a_tag, repa->rp_to_tag); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_ready); /* READY */ + TEST_1(!e->next); + free_events_in_list(ctx, b->events); + + BYE(b, b_call, b_call->nh, TAG_END()); + run_ab_until(ctx, -1, until_terminated, -1, until_terminated); + + /* B transitions: + READY --(T2)--> TERMINATING: nua_bye() + TERMINATING --(T3)--> TERMINATED: nua_r_bye, nua_i_state + */ + TEST_1(e = b->events->head); TEST_E(e->data->e_event, nua_r_bye); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_terminated); /* TERMINATED */ + TEST_1(!e->next); + free_events_in_list(ctx, b->events); + + TEST_1(!nua_handle_has_active_call(b_call->nh)); + + /* A transitions: + READY -(T1)-> TERMINATED: nua_i_bye, nua_i_state + */ + TEST_1(e = a->events->head); TEST_E(e->data->e_event, nua_i_bye); + TEST(e->data->e_status, 200); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_terminated); /* TERMINATED */ + TEST_1(!e->next); + free_events_in_list(ctx, a->events); + + TEST_1(!nua_handle_has_active_call(a_call->nh)); + + nua_handle_destroy(a_call->nh), a_call->nh = NULL; + nua_handle_destroy(b_call->nh), b_call->nh = NULL; + + test_proxy_set_session_timer(ctx->p, se, min_se); + + if (print_headings) + printf("TEST NUA-3.1: PASSED\n"); + + END(); +} + +/* + accept_early_answer + X ep + | | + |-------INVITE------>| + |<----100 Trying-----| + | | + |<----180 Ringing----| + | | + |<--------200--------| + |---------ACK------->| +*/ +int accept_early_answer(CONDITION_PARAMS) +{ + if (!(check_handle(ep, call, nh, SIP_500_INTERNAL_SERVER_ERROR))) + return 0; + + save_event_in_list(ctx, event, ep, call); + + switch (callstate(tags)) { + case nua_callstate_received: + RESPOND(ep, call, nh, SIP_180_RINGING, + NUTAG_EARLY_ANSWER(1), + TAG_IF(call->sdp, SOATAG_USER_SDP_STR(call->sdp)), + TAG_END()); + return 0; + case nua_callstate_early: + RESPOND(ep, call, nh, SIP_200_OK, + TAG_IF(call->sdp, SOATAG_USER_SDP_STR(call->sdp)), + TAG_END()); + return 0; + case nua_callstate_ready: + return 1; + case nua_callstate_terminated: + if (call) + nua_handle_destroy(call->nh), call->nh = NULL; + return 1; + default: + return 0; + } +} + +int test_basic_call_2(struct context *ctx) +{ + BEGIN(); + + struct endpoint *a = &ctx->a, *b = &ctx->b; + struct call *a_call = a->call, *b_call = b->call; + struct event *e; + sip_t const *sip; + + if (print_headings) + printf("TEST NUA-3.2: Basic call with SDP in 180\n"); + + a_call->sdp = "m=audio 5008 RTP/AVP 8"; + b_call->sdp = "m=audio 5010 RTP/AVP 0 8"; + + TEST_1(a_call->nh = nua_handle(a->nua, a_call, + SIPTAG_TO_STR("<sip:b@x.org>"), + TAG_END())); + + TEST_1(!nua_handle_has_active_call(a_call->nh)); + TEST_1(!nua_handle_has_call_on_hold(a_call->nh)); + + INVITE(a, a_call, a_call->nh, + NUTAG_URL(b->contact->m_url), + SOATAG_USER_SDP_STR(a_call->sdp), + NUTAG_ALLOW("INFO"), + TAG_END()); + + run_ab_until(ctx, -1, until_ready, -1, accept_early_answer); + + /* Client transitions: + INIT -(C1)-> CALLING: nua_invite(), nua_i_state + CALLING -(C2)-> PROCEEDING: nua_r_invite, nua_i_state + PROCEEDING -(C3+C4)-> READY: nua_r_invite, nua_i_state + */ + TEST_1(e = a->events->head); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_calling); /* CALLING */ + TEST_1(is_offer_sent(e->data->e_tags)); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_r_invite); + TEST(e->data->e_status, 180); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_proceeding); /* PROCEEDING */ + TEST_1(is_answer_recv(e->data->e_tags)); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_r_invite); + TEST(e->data->e_status, 200); + TEST_1(sip = sip_object(e->data->e_msg)); + TEST_1(sip->sip_content_type); + TEST_S(sip->sip_content_type->c_type, "application/sdp"); + TEST_1(sip->sip_payload); /* there is sdp in 200 OK */ + TEST_1(sip->sip_contact); +#if nomore + /* Test that B does not use application-specific contact */ + TEST_1(!sip->sip_contact->m_url->url_user); +#else + /* sf.net bug #1816647: Outbound contact does not make it to dialogs */ + /* Now we use first registered contact if aor does not match */ + if (ctx->proxy_tests) + TEST_S(sip->sip_contact->m_url->url_user, "b"); +#endif + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_ready); /* READY */ + TEST_1(!is_answer_recv(e->data->e_tags)); /* but it is ignored */ + TEST_1(!e->next); + free_events_in_list(ctx, a->events); + + TEST_1(nua_handle_has_active_call(a_call->nh)); + TEST_1(!nua_handle_has_call_on_hold(a_call->nh)); + + /* + Server transitions: + INIT -(S1)-> RECEIVED: nua_i_invite, nua_i_state + RECEIVED -(S2a)-> EARLY: nua_respond(), nua_i_state + EARLY -(S3b)-> COMPLETED: nua_respond(), nua_i_state + COMPLETED -(S4)-> READY: nua_i_ack, nua_i_state + */ + TEST_1(e = b->events->head); TEST_E(e->data->e_event, nua_i_invite); + TEST(e->data->e_status, 100); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_received); /* RECEIVED */ + TEST_1(is_offer_recv(e->data->e_tags)); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_early); /* EARLY */ + TEST_1(is_answer_sent(e->data->e_tags)); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_completed); /* COMPLETED */ + TEST_1(is_answer_sent(e->data->e_tags)); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_ack); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_ready); /* READY */ + TEST_1(!e->next); + free_events_in_list(ctx, b->events); + + TEST_1(nua_handle_has_active_call(b_call->nh)); + TEST_1(!nua_handle_has_call_on_hold(b_call->nh)); + + /* Send a NOTIFY from B to A */ + if (print_headings) + printf("TEST NUA-3.2.2: send a NOTIFY within a dialog\n"); + + /* Make A to accept NOTIFY */ + nua_set_params(a->nua, NUTAG_APPL_METHOD("NOTIFY"), TAG_END()); + run_a_until(ctx, nua_r_set_params, until_final_response); + + NOTIFY(b, b_call, b_call->nh, + NUTAG_NEWSUB(1), + SIPTAG_SUBJECT_STR("NUA-3.2.2"), + SIPTAG_EVENT_STR("message-summary"), + SIPTAG_CONTENT_TYPE_STR("application/simple-message-summary"), + SIPTAG_PAYLOAD_STR("Messages-Waiting: no"), + TAG_END()); + + run_ab_until(ctx, -1, accept_notify, -1, save_until_final_response); + + /* Notifier events: nua_r_notify */ + TEST_1(e = b->events->head); TEST_E(e->data->e_event, nua_r_notify); + TEST(e->data->e_status, 200); + TEST_1(tl_find(e->data->e_tags, nutag_substate)); + TEST(tl_find(e->data->e_tags, nutag_substate)->t_value, + nua_substate_terminated); + + /* watcher events: nua_i_notify */ + TEST_1(e = a->events->head); TEST_E(e->data->e_event, nua_i_notify); + TEST_1(sip = sip_object(e->data->e_msg)); + TEST_1(sip->sip_subscription_state); + TEST_S(sip->sip_subscription_state->ss_substate, "terminated"); + TEST_1(tl_find(e->data->e_tags, nutag_substate)); + TEST(tl_find(e->data->e_tags, nutag_substate)->t_value, + nua_substate_terminated); + TEST_1(!e->next); + + free_events_in_list(ctx, a->events); + free_events_in_list(ctx, b->events); + + if (print_headings) + printf("TEST NUA-3.2.2: PASSED\n"); + + INFO(b, b_call, b_call->nh, TAG_END()); + BYE(b, b_call, b_call->nh, TAG_END()); + INFO(b, b_call, b_call->nh, TAG_END()); + + run_ab_until(ctx, -1, until_terminated, -1, until_terminated); + + while (!b->events->head || /* r_info */ + !b->events->head->next || /* r_bye */ + !b->events->head->next->next || /* i_state */ + !b->events->head->next->next->next) /* r_info */ + run_ab_until(ctx, -1, save_events, -1, save_until_final_response); + + /* B transitions: + nua_info() + READY --(T2)--> TERMINATING: nua_bye() + nua_r_info with 200 + TERMINATING --(T3)--> TERMINATED: nua_r_bye, nua_i_state + nua_r_info with 481/900 + */ + TEST_1(e = b->events->head); TEST_E(e->data->e_event, nua_r_info); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_r_bye); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_terminated); /* TERMINATED */ + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_r_info); + TEST_1(e->data->e_status >= 900 || e->data->e_status == 481); + TEST_1(!e->next); + free_events_in_list(ctx, b->events); + + TEST_1(!nua_handle_has_active_call(b_call->nh)); + + /* A transitions: + nua_i_info + READY -(T1)-> TERMINATED: nua_i_bye, nua_i_state + */ + TEST_1(e = a->events->head); TEST_E(e->data->e_event, nua_i_info); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_bye); + TEST(e->data->e_status, 200); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_terminated); /* TERMINATED */ + TEST_1(!e->next); + free_events_in_list(ctx, a->events); + + BYE(a, a_call, a_call->nh, TAG_END()); + run_a_until(ctx, -1, save_until_final_response); + + TEST_1(e = a->events->head); TEST_E(e->data->e_event, nua_r_bye); + TEST_1(e->data->e_status >= 900); + TEST_1(!e->next); + free_events_in_list(ctx, a->events); + + nua_handle_destroy(a_call->nh), a_call->nh = NULL; + nua_handle_destroy(b_call->nh), b_call->nh = NULL; + + if (print_headings) + printf("TEST NUA-3.2: PASSED\n"); + + END(); +} + +/* ====================================================================== */ + +/* Basic calls with soa disabled */ + +int accept_call_no_media(CONDITION_PARAMS) +{ + if (!(check_handle(ep, call, nh, SIP_500_INTERNAL_SERVER_ERROR))) + return 0; + + save_event_in_list(ctx, event, ep, call); + + switch (callstate(tags)) { + case nua_callstate_received: + RESPOND(ep, call, nh, SIP_180_RINGING, + NUTAG_MEDIA_ENABLE(0), + TAG_END()); + return 0; + case nua_callstate_early: + RESPOND(ep, call, nh, SIP_200_OK, + TAG_IF(call->sdp, SIPTAG_CONTENT_TYPE_STR("application/sdp")), + TAG_IF(call->sdp, SIPTAG_PAYLOAD_STR(call->sdp)), + TAG_END()); + return 0; + case nua_callstate_ready: + return 1; + case nua_callstate_terminated: + if (call) + nua_handle_destroy(call->nh), call->nh = NULL; + return 1; + default: + return 0; + } +} + +int test_basic_call_3(struct context *ctx) +{ + BEGIN(); + + struct endpoint *a = &ctx->a, *b = &ctx->b; + struct call *a_call = a->call, *b_call = b->call; + struct event *e; + sip_t const *sip; + + if (print_headings) + printf("TEST NUA-3.3: Basic call with media disabled\n"); + + a_call->sdp = "v=0\r\n" + "o=- 1 1 IN IP4 127.0.0.1\r\n" + "s=-\r\n" + "c=IN IP4 127.0.0.1\r\n" + "t=0 0\r\n" + "m=audio 5008 RTP/AVP 8\r\n"; + + b_call->sdp = + "v=0\r\n" + "o=- 2 1 IN IP4 127.0.0.1\r\n" + "s=-\r\n" + "c=IN IP4 127.0.0.1\r\n" + "t=0 0\r\n" + "m=audio 5010 RTP/AVP 0 8\r\n"; + + TEST_1(a_call->nh = nua_handle(a->nua, a_call, + SIPTAG_TO_STR("<sip:b@x.org>"), + TAG_END())); + + TEST_1(!nua_handle_has_active_call(a_call->nh)); + TEST_1(!nua_handle_has_call_on_hold(a_call->nh)); + + INVITE(a, a_call, a_call->nh, + NUTAG_MEDIA_ENABLE(0), + NUTAG_URL(b->contact->m_url), + SIPTAG_CONTENT_TYPE_STR("application/sdp"), + SIPTAG_PAYLOAD_STR(a_call->sdp), + TAG_END()); + + run_ab_until(ctx, -1, until_ready, -1, accept_call_no_media); + + /* Client transitions: + INIT -(C1)-> CALLING: nua_invite(), nua_i_state + CALLING -(C2)-> PROCEEDING: nua_r_invite, nua_i_state + PROCEEDING -(C3+C4)-> READY: nua_r_invite, nua_i_state + */ + TEST_1(e = a->events->head); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_calling); /* CALLING */ + TEST_1(is_offer_sent(e->data->e_tags)); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_r_invite); + TEST(e->data->e_status, 180); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_proceeding); /* PROCEEDING */ + TEST_1(!is_answer_recv(e->data->e_tags)); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_r_invite); + TEST(e->data->e_status, 200); + TEST_1(sip = sip_object(e->data->e_msg)); + TEST_1(sip->sip_content_type); + TEST_S(sip->sip_content_type->c_type, "application/sdp"); + TEST_1(sip->sip_payload); /* there is sdp in 200 OK */ + TEST_1(sip->sip_contact); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_ready); /* READY */ + TEST_1(is_answer_recv(e->data->e_tags)); + TEST_1(!e->next); + free_events_in_list(ctx, a->events); + + TEST_1(nua_handle_has_active_call(a_call->nh)); + TEST_1(!nua_handle_has_call_on_hold(a_call->nh)); + + /* + Server transitions: + INIT -(S1)-> RECEIVED: nua_i_invite, nua_i_state + RECEIVED -(S2a)-> EARLY: nua_respond(), nua_i_state + EARLY -(S3b)-> COMPLETED: nua_respond(), nua_i_state + COMPLETED -(S4)-> READY: nua_i_ack, nua_i_state + */ + TEST_1(e = b->events->head); TEST_E(e->data->e_event, nua_i_invite); + TEST(e->data->e_status, 100); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_received); /* RECEIVED */ + TEST_1(is_offer_recv(e->data->e_tags)); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_early); /* EARLY */ + TEST_1(!is_answer_sent(e->data->e_tags)); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_completed); /* COMPLETED */ + TEST_1(is_answer_sent(e->data->e_tags)); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_ack); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_ready); /* READY */ + TEST_1(!e->next); + free_events_in_list(ctx, b->events); + + TEST_1(nua_handle_has_active_call(b_call->nh)); + TEST_1(!nua_handle_has_call_on_hold(b_call->nh)); + + BYE(b, b_call, b_call->nh, TAG_END()); + + run_ab_until(ctx, -1, until_terminated, -1, until_terminated); + + /* B transitions: + READY --(T2)--> TERMINATING: nua_bye() + TERMINATING --(T3)--> TERMINATED: nua_r_bye, nua_i_state + */ + TEST_1(e = b->events->head); TEST_E(e->data->e_event, nua_r_bye); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_terminated); /* TERMINATED */ + TEST_1(!e->next); + free_events_in_list(ctx, b->events); + + TEST_1(!nua_handle_has_active_call(b_call->nh)); + + /* A transitions: + nua_i_info + READY -(T1)-> TERMINATED: nua_i_bye, nua_i_state + */ + TEST_1(e = a->events->head); TEST_E(e->data->e_event, nua_i_bye); + TEST(e->data->e_status, 200); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_terminated); /* TERMINATED */ + TEST_1(!e->next); + free_events_in_list(ctx, a->events); + + nua_handle_destroy(a_call->nh), a_call->nh = NULL; + nua_handle_destroy(b_call->nh), b_call->nh = NULL; + + if (print_headings) + printf("TEST NUA-3.3: PASSED\n"); + + END(); +} + +int ack_when_completing_no_media(CONDITION_PARAMS) +{ + if (!(check_handle(ep, call, nh, SIP_500_INTERNAL_SERVER_ERROR))) + return 0; + + save_event_in_list(ctx, event, ep, call); + + switch (callstate(tags)) { + case nua_callstate_completing: + ACK(ep, call, nh, + TAG_IF(call->sdp, SIPTAG_CONTENT_TYPE_STR("application/sdp")), + TAG_IF(call->sdp, SIPTAG_PAYLOAD_STR(call->sdp)), + TAG_END()); + return 0; + case nua_callstate_ready: + return 1; + case nua_callstate_terminated: + if (call) + nua_handle_destroy(call->nh), call->nh = NULL; + return 1; + default: + return 0; + } +} + +int accept_call_no_media2(CONDITION_PARAMS) +{ + if (!(check_handle(ep, call, nh, SIP_500_INTERNAL_SERVER_ERROR))) + return 0; + + save_event_in_list(ctx, event, ep, call); + + switch (callstate(tags)) { + case nua_callstate_received: + RESPOND(ep, call, nh, SIP_180_RINGING, + NUTAG_MEDIA_ENABLE(0), + TAG_IF(call->sdp, SIPTAG_CONTENT_TYPE_STR("application/sdp")), + TAG_IF(call->sdp, SIPTAG_PAYLOAD_STR(call->sdp)), + TAG_END()); + return 0; + case nua_callstate_early: + RESPOND(ep, call, nh, SIP_200_OK, + TAG_IF(call->sdp, SIPTAG_CONTENT_TYPE_STR("application/sdp")), + TAG_IF(call->sdp, SIPTAG_PAYLOAD_STR(call->sdp)), + TAG_END()); + return 0; + case nua_callstate_ready: + return 1; + case nua_callstate_terminated: + if (call) + nua_handle_destroy(call->nh), call->nh = NULL; + return 1; + default: + return 0; + } +} + +/* Media disabled, offer/answer in 200 OK/ACK */ +int test_basic_call_4(struct context *ctx) +{ + BEGIN(); + + struct endpoint *a = &ctx->a, *b = &ctx->b; + struct call *a_call = a->call, *b_call = b->call; + struct event *e; + sip_t const *sip; + + if (print_headings) + printf("TEST NUA-3.4: 3pcc call with media disabled\n"); + + a_call->sdp = "v=0\r\n" + "o=- 1 1 IN IP4 127.0.0.1\r\n" + "s=-\r\n" + "c=IN IP4 127.0.0.1\r\n" + "t=0 0\r\n" + "m=audio 5008 RTP/AVP 8\r\n"; + + b_call->sdp = + "v=0\r\n" + "o=- 2 1 IN IP4 127.0.0.1\r\n" + "s=-\r\n" + "c=IN IP4 127.0.0.1\r\n" + "t=0 0\r\n" + "m=audio 5010 RTP/AVP 0 8\r\n"; + + TEST_1(a_call->nh = nua_handle(a->nua, a_call, + SIPTAG_TO_STR("<sip:b@x.org>"), + TAG_END())); + + TEST_1(!nua_handle_has_active_call(a_call->nh)); + TEST_1(!nua_handle_has_call_on_hold(a_call->nh)); + + INVITE(a, a_call, a_call->nh, + NUTAG_MEDIA_ENABLE(0), + NUTAG_URL(b->contact->m_url), + NUTAG_AUTOACK(0), + TAG_END()); + + run_ab_until(ctx, -1, ack_when_completing_no_media, + -1, accept_call_no_media2); + + /* Client transitions: + INIT -(C1)-> CALLING: nua_invite(), nua_i_state + CALLING -(C2)-> PROCEEDING: nua_r_invite, nua_i_state + PROCEEDING -(C3+C4)-> READY: nua_r_invite, nua_i_state + */ + TEST_1(e = a->events->head); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_calling); /* CALLING */ + TEST_1(!is_offer_sent(e->data->e_tags)); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_r_invite); + TEST(e->data->e_status, 180); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_proceeding); /* PROCEEDING */ + TEST_1(!is_answer_recv(e->data->e_tags)); + TEST_1(is_offer_recv(e->data->e_tags)); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_r_invite); + TEST(e->data->e_status, 200); + TEST_1(sip = sip_object(e->data->e_msg)); + TEST_1(sip->sip_content_type); + TEST_S(sip->sip_content_type->c_type, "application/sdp"); + TEST_1(sip->sip_payload); /* there is sdp in 200 OK */ + TEST_1(sip->sip_contact); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_completing); /* COMPLETING */ + TEST_1(is_offer_recv(e->data->e_tags)); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_ready); /* READY */ + TEST_1(is_answer_sent(e->data->e_tags)); + TEST_1(!e->next); + free_events_in_list(ctx, a->events); + + TEST_1(nua_handle_has_active_call(a_call->nh)); + TEST_1(!nua_handle_has_call_on_hold(a_call->nh)); + + /* + Server transitions: + INIT -(S1)-> RECEIVED: nua_i_invite, nua_i_state + RECEIVED -(S2a)-> EARLY: nua_respond(), nua_i_state + EARLY -(S3b)-> COMPLETED: nua_respond(), nua_i_state + COMPLETED -(S4)-> READY: nua_i_ack, nua_i_state + */ + TEST_1(e = b->events->head); TEST_E(e->data->e_event, nua_i_invite); + TEST(e->data->e_status, 100); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_received); /* RECEIVED */ + TEST_1(!is_offer_recv(e->data->e_tags)); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_early); /* EARLY */ + TEST_1(!is_answer_sent(e->data->e_tags)); + TEST_1(is_offer_sent(e->data->e_tags)); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_completed); /* COMPLETED */ + TEST_1(is_offer_sent(e->data->e_tags)); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_ack); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_ready); /* READY */ + TEST_1(is_answer_recv(e->data->e_tags)); + TEST_1(!e->next); + free_events_in_list(ctx, b->events); + + TEST_1(nua_handle_has_active_call(b_call->nh)); + TEST_1(!nua_handle_has_call_on_hold(b_call->nh)); + + BYE(b, b_call, b_call->nh, TAG_END()); + + run_ab_until(ctx, -1, until_terminated, -1, until_terminated); + + /* B transitions: + READY --(T2)--> TERMINATING: nua_bye() + TERMINATING --(T3)--> TERMINATED: nua_r_bye, nua_i_state + */ + TEST_1(e = b->events->head); TEST_E(e->data->e_event, nua_r_bye); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_terminated); /* TERMINATED */ + TEST_1(!e->next); + free_events_in_list(ctx, b->events); + + TEST_1(!nua_handle_has_active_call(b_call->nh)); + + /* A transitions: + nua_i_info + READY -(T1)-> TERMINATED: nua_i_bye, nua_i_state + */ + TEST_1(e = a->events->head); TEST_E(e->data->e_event, nua_i_bye); + TEST(e->data->e_status, 200); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_terminated); /* TERMINATED */ + TEST_1(!e->next); + free_events_in_list(ctx, a->events); + + nua_handle_destroy(a_call->nh), a_call->nh = NULL; + nua_handle_destroy(b_call->nh), b_call->nh = NULL; + + if (print_headings) + printf("TEST NUA-3.4: PASSED\n"); + + END(); +} + +int change_uri_in_ack(CONDITION_PARAMS) +{ + if (!(check_handle(ep, call, nh, SIP_500_INTERNAL_SERVER_ERROR))) + return 0; + + save_event_in_list(ctx, event, ep, call); + + switch (callstate(tags)) { + case nua_callstate_completing: + ACK(ep, call, nh, + SIPTAG_FROM_STR("sip:anonymous@org.invalid"), + SIPTAG_TO_STR("sip:anonymous@net.invalid"), + TAG_END()); + return 0; + case nua_callstate_ready: + return 1; + case nua_callstate_terminated: + if (call) + nua_handle_destroy(call->nh), call->nh = NULL; + return 1; + default: + return 0; + } +} + +/* Test changing from/to within dialog */ +/* Test that a proper Contact gets selected in response + * regardless of the To URI. + */ +int test_basic_call_5(struct context *ctx) +{ + BEGIN(); + + struct endpoint *a = &ctx->a, *b = &ctx->b; + struct call *a_call = a->call, *b_call = b->call; + struct event *e; + sip_t const *sip; + + if (print_headings) + printf("TEST NUA-3.5: test changing From/To URL in ACK\n"); + + a_call->sdp = "m=audio 5008 RTP/AVP 8"; + b_call->sdp = "m=audio 5010 RTP/AVP 0 8"; + + TEST_1(a_call->nh = nua_handle(a->nua, a_call, + SIPTAG_TO_STR("<sips:b@x.org>"), + TAG_END())); + + TEST_1(!nua_handle_has_active_call(a_call->nh)); + TEST_1(!nua_handle_has_call_on_hold(a_call->nh)); + + INVITE(a, a_call, a_call->nh, + NUTAG_URL(b->contact->m_url), + SOATAG_USER_SDP_STR(a_call->sdp), + NUTAG_AUTOACK(0), + TAG_END()); + + run_ab_until(ctx, -1, change_uri_in_ack, -1, accept_call); + + /* Client transitions: + INIT -(C1)-> CALLING: nua_invite(), nua_i_state + CALLING -(C2)-> PROCEEDING: nua_r_invite, nua_i_state + PROCEEDING -(C3+C4)-> READY: nua_r_invite, nua_i_state + */ + TEST_1(e = a->events->head); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_calling); /* CALLING */ + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_r_invite); + TEST(e->data->e_status, 180); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_proceeding); /* PROCEEDING */ + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_r_invite); + TEST(e->data->e_status, 200); + TEST_1(sip = sip_object(e->data->e_msg)); + TEST_1(sip->sip_contact); + if (ctx->proxy_tests) /* Use Contact from registration? */ + TEST_S(sip->sip_contact->m_url->url_user, "b"); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_completing); /* COMPLETING */ + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_ready); /* READY */ + TEST_1(!e->next); + free_events_in_list(ctx, a->events); + + TEST_1(nua_handle_has_active_call(a_call->nh)); + TEST_1(!nua_handle_has_call_on_hold(a_call->nh)); + + /* + Server transitions: + INIT -(S1)-> RECEIVED: nua_i_invite, nua_i_state + RECEIVED -(S2a)-> EARLY: nua_respond(), nua_i_state + EARLY -(S3b)-> COMPLETED: nua_respond(), nua_i_state + COMPLETED -(S4)-> READY: nua_i_ack, nua_i_state + */ + TEST_1(e = b->events->head); TEST_E(e->data->e_event, nua_i_invite); + TEST(e->data->e_status, 100); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_received); /* RECEIVED */ + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_early); /* EARLY */ + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_completed); /* COMPLETED */ + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_ack); + TEST_1(sip = sip_object(e->data->e_msg)); + TEST_S(sip->sip_to->a_url->url_user, "anonymous"); + TEST_S(sip->sip_from->a_url->url_user, "anonymous"); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_ready); /* READY */ + TEST_1(!e->next); + free_events_in_list(ctx, b->events); + + TEST_1(nua_handle_has_active_call(b_call->nh)); + TEST_1(!nua_handle_has_call_on_hold(b_call->nh)); + + BYE(b, b_call, b_call->nh, TAG_END()); + + run_ab_until(ctx, -1, until_terminated, -1, until_terminated); + + /* B transitions: + READY --(T2)--> TERMINATING: nua_bye() + TERMINATING --(T3)--> TERMINATED: nua_r_bye, nua_i_state + */ + TEST_1(e = b->events->head); TEST_E(e->data->e_event, nua_r_bye); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_terminated); /* TERMINATED */ + TEST_1(!e->next); + free_events_in_list(ctx, b->events); + + TEST_1(!nua_handle_has_active_call(b_call->nh)); + + /* A transitions: + nua_i_info + READY -(T1)-> TERMINATED: nua_i_bye, nua_i_state + */ + TEST_1(e = a->events->head); TEST_E(e->data->e_event, nua_i_bye); + TEST(e->data->e_status, 200); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_terminated); /* TERMINATED */ + TEST_1(!e->next); + free_events_in_list(ctx, a->events); + + nua_handle_destroy(a_call->nh), a_call->nh = NULL; + nua_handle_destroy(b_call->nh), b_call->nh = NULL; + + if (print_headings) + printf("TEST NUA-3.5: PASSED\n"); + + END(); +} + +/* ======================================================================== */ + +/* Call with media upgrade: + + A B + |-------INVITE------>| + | with audio only | + |<----100 Trying-----| + | | + |<----180 Ringing----| + | | + |<------200 OK-------| + |--------ACK-------->| + | | + |<------INVITE-------| + | with video | + |-----100 Trying---->| + | | + |-------200 OK------>| + | with video | + |<-------ACK---------| + | | + |<-------BYE---------| + |-------200 OK------>| + | | + + Client transitions: + INIT -(C1)-> CALLING -(C2a)-> PROCEEDING -(C3+C4)-> READY + Server transitions: + INIT -(S1)-> RECEIVED -(S2a)-> EARLY -(S3b)-> COMPLETED -(S4)-> READY + + B sends BYE: + READY -(T2)-> TERMINATING -(T3)-> TERMINATED + A receives BYE: + READY -(T1)-> TERMINATED + + See @page nua_call_model in nua.docs for more information +*/ +int accept_upgrade(CONDITION_PARAMS); + +int test_video_call_1(struct context *ctx) +{ + BEGIN(); + + struct endpoint *a = &ctx->a, *b = &ctx->b; + struct call *a_call = a->call, *b_call = b->call; + struct event *e; + sip_t *sip; + sdp_session_t *b_sdp; + sdp_media_t *m, b_video[1]; + sdp_rtpmap_t *rm, b_h261[1]; + + sip_time_t se, min_se; + + if (print_headings) + printf("TEST NUA-3.6: Basic call\n"); + + /* Disable session timer from proxy */ + test_proxy_get_session_timer(ctx->p, &se, &min_se); + test_proxy_set_session_timer(ctx->p, 0, 0); + + a_call->sdp = "m=audio 5008 RTP/AVP 8"; + b_call->sdp = "m=audio 5010 RTP/AVP 0 8"; + + TEST_1(a_call->nh = nua_handle(a->nua, a_call, SIPTAG_TO(b->to), TAG_END())); + + TEST_1(!nua_handle_has_active_call(a_call->nh)); + TEST_1(!nua_handle_has_call_on_hold(a_call->nh)); + + INVITE(a, a_call, a_call->nh, + TAG_IF(!ctx->proxy_tests, NUTAG_URL(b->contact->m_url)), + SOATAG_USER_SDP_STR(a_call->sdp), + NUTAG_AUTOANSWER(0), + TAG_END()); + + run_ab_until(ctx, -1, until_ready, -1, accept_call_with_early_sdp); + + TEST_1(nua_handle_has_active_call(a_call->nh)); + TEST_1(!nua_handle_has_call_on_hold(a_call->nh)); + + TEST_1(nua_handle_has_active_call(b_call->nh)); + TEST_1(!nua_handle_has_call_on_hold(b_call->nh)); + + /* Client transitions: + INIT -(C1)-> CALLING: nua_invite(), nua_i_state + CALLING -(C2)-> PROCEEDING: nua_r_invite, nua_i_state + PROCEEDING -(C3+C4)-> READY: nua_r_invite, nua_i_state + */ + TEST_1(e = a->events->head); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_calling); /* CALLING */ + TEST_1(is_offer_sent(e->data->e_tags)); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_r_invite); + TEST(e->data->e_status, 180); + TEST_1(sip = sip_object(e->data->e_msg)); + TEST_1(sip->sip_payload); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_proceeding); /* PROCEEDING */ + TEST_1(is_answer_recv(e->data->e_tags)); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_r_invite); + TEST(e->data->e_status, 200); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_ready); /* READY */ + TEST_1(!e->next); + free_events_in_list(ctx, a->events); + + /* + Server transitions: + INIT -(S1)-> RECEIVED: nua_i_invite, nua_i_state + RECEIVED -(S2a)-> EARLY: nua_respond(), nua_i_state + EARLY -(S3b)-> COMPLETED: nua_respond(), nua_i_state + COMPLETED -(S4)-> READY: nua_i_ack, nua_i_state + */ + TEST_1(e = b->events->head); TEST_E(e->data->e_event, nua_i_invite); + TEST(e->data->e_status, 100); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_received); /* RECEIVED */ + TEST_1(is_offer_recv(e->data->e_tags)); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_early); /* EARLY */ + TEST_1(is_answer_sent(e->data->e_tags)); + TEST_1(tl_find(e->data->e_tags, soatag_local_sdp)); + TEST_1(b_sdp = sdp_session_dup(nua_handle_home(a_call->nh), + (sdp_session_t *) + tl_find(e->data->e_tags, soatag_local_sdp) + ->t_value)); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_completed); /* COMPLETED */ + TEST_1(is_answer_sent(e->data->e_tags)); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_ack); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_ready); /* READY */ + TEST_1(!e->next); + free_events_in_list(ctx, b->events); + + a_call->sdp = + "m=audio 5008 RTP/AVP 8\n" + "m=video 5014 RTP/AVP 34 31\n"; + + m = memset(b_video, 0, sizeof b_video); + m->m_size = sizeof *m; + m->m_session = b_sdp; + m->m_type = sdp_media_video, m->m_type_name = "video"; + m->m_port = 5016; + m->m_proto = sdp_proto_rtp; m->m_proto_name = "RTP/AVP"; + m->m_rtpmaps = memset(rm = b_h261, 0, sizeof b_h261); + rm->rm_size = sizeof *rm; + rm->rm_pt = 31; rm->rm_encoding = "h261"; rm->rm_rate = 90000; + + b_sdp->sdp_media->m_next = m; + + INVITE(b, b_call, b_call->nh, + SOATAG_USER_SDP(b_sdp), + TAG_END()); + run_ab_until(ctx, -1, accept_upgrade, -1, until_ready); + + TEST_1(e = b->events->head); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_calling); /* CALLING */ + TEST_1(tl_find(e->data->e_tags, soatag_local_sdp_str)); + TEST_1(strstr((char *) + tl_find(e->data->e_tags, soatag_local_sdp_str)->t_value, + "m=video")); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_r_invite); + TEST(e->data->e_status, 200); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST_1(is_answer_recv(e->data->e_tags)); + TEST(callstate(e->data->e_tags), nua_callstate_ready); /* READY */ + TEST_1(!e->next); + free_events_in_list(ctx, b->events); + + TEST_1(e = a->events->head); TEST_E(e->data->e_event, nua_i_invite); + TEST(e->data->e_status, 100); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_received); /* RECEIVED */ + TEST_1(is_offer_recv(e->data->e_tags)); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_completed); /* COMPLETED */ + TEST_1(is_answer_sent(e->data->e_tags)); + TEST_1(tl_find(e->data->e_tags, soatag_local_sdp_str)); + TEST_1(strstr((char *) + tl_find(e->data->e_tags, soatag_local_sdp_str)->t_value, + "m=video")); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_ack); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_ready); /* READY */ + TEST_1(!e->next); + free_events_in_list(ctx, a->events); + + BYE(b, b_call, b_call->nh, TAG_END()); + run_ab_until(ctx, -1, until_terminated, -1, until_terminated); + + /* B transitions: + READY --(T2)--> TERMINATING: nua_bye() + TERMINATING --(T3)--> TERMINATED: nua_r_bye, nua_i_state + */ + TEST_1(e = b->events->head); TEST_E(e->data->e_event, nua_r_bye); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_terminated); /* TERMINATED */ + TEST_1(!e->next); + free_events_in_list(ctx, b->events); + + TEST_1(!nua_handle_has_active_call(b_call->nh)); + + /* A transitions: + READY -(T1)-> TERMINATED: nua_i_bye, nua_i_state + */ + TEST_1(e = a->events->head); TEST_E(e->data->e_event, nua_i_bye); + TEST(e->data->e_status, 200); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_terminated); /* TERMINATED */ + TEST_1(!e->next); + free_events_in_list(ctx, a->events); + + TEST_1(!nua_handle_has_active_call(a_call->nh)); + + nua_handle_destroy(a_call->nh), a_call->nh = NULL; + nua_handle_destroy(b_call->nh), b_call->nh = NULL; + + test_proxy_set_session_timer(ctx->p, se, min_se); + + if (print_headings) + printf("TEST NUA-3.6: PASSED\n"); + + END(); +} + +int accept_upgrade(CONDITION_PARAMS) +{ + if (!(check_handle(ep, call, nh, SIP_500_INTERNAL_SERVER_ERROR))) + return 0; + + save_event_in_list(ctx, event, ep, call); + + if (event == nua_i_invite && status < 200) { + RESPOND(ep, call, nh, SIP_200_OK, + TAG_IF(call->sdp, SOATAG_USER_SDP_STR(call->sdp)), + TAG_END()); + return 0; + } + + switch (callstate(tags)) { + case nua_callstate_ready: + return 1; + case nua_callstate_terminated: + if (call) + nua_handle_destroy(call->nh), call->nh = NULL; + return 1; + default: + return 0; + } +} + +/* Basic call and re-INVITE with user-specified Contact: + + A B + |-------INVITE------>| + |<----100 Trying-----| + | | + |<----180 Ringing----| + | | + |<------200 OK-------| + |--------ACK-------->| + | | + |-----re-INVITE----->| + |<------200 OK-------| + |--------ACK-------->| + | | + |<-------BYE---------| + |-------200 OK------>| + | | + + Client transitions: + INIT -(C1)-> CALLING -(C2a)-> PROCEEDING -(C3+C4)-> READY + Server transitions: + INIT -(S1)-> RECEIVED -(S2a)-> EARLY -(S3b)-> COMPLETED -(S4)-> READY + + Both client and server save Contact from nua_invite() and nua_respond(), + respectively. + + INIT -(C1)-> CALLING -(C3a+C4)-> READY + INIT -(S3c)-> COMPLETED -(S4)-> READY + + Both client and server use saved Contact. + + B sends BYE: + READY -(T2)-> TERMINATING -(T3)-> TERMINATED + A receives BYE: + READY -(T1)-> TERMINATED + + See @page nua_call_model in nua.docs for more information +*/ + +static sip_contact_t *contact_for_b; + +int accept_call_with_contact(CONDITION_PARAMS) +{ + if (!(check_handle(ep, call, nh, SIP_500_INTERNAL_SERVER_ERROR))) + return 0; + + save_event_in_list(ctx, event, ep, call); + + switch (callstate(tags)) { + case nua_callstate_received: + RESPOND(ep, call, nh, SIP_180_RINGING, + TAG_IF(call->sdp, SOATAG_USER_SDP_STR(call->sdp)), + SIPTAG_CONTACT(contact_for_b), + TAG_END()); + return 0; + case nua_callstate_early: + RESPOND(ep, call, nh, SIP_200_OK, + TAG_IF(call->sdp, SOATAG_USER_SDP_STR(call->sdp)), + SIPTAG_CONTACT(contact_for_b), + TAG_END()); + return 0; + case nua_callstate_ready: + return 1; + case nua_callstate_terminated: + if (call) + nua_handle_destroy(call->nh), call->nh = NULL; + return 1; + default: + return 0; + } +} + +int test_basic_call_6(struct context *ctx) +{ + BEGIN(); + + struct endpoint *a = &ctx->a, *b = &ctx->b; + struct call *a_call = a->call, *b_call = b->call; + struct event *e; + sip_route_t *r, rb[1]; + sip_t *sip; + + sip_contact_t ma[1], mb[1]; + + if (print_headings) + printf("TEST NUA-3.6: Basic call\n"); + + a_call->sdp = "m=audio 5008 RTP/AVP 8"; + b_call->sdp = "m=audio 5010 RTP/AVP 0 8"; + + TEST_1(a_call->nh = nua_handle(a->nua, a_call, SIPTAG_TO(b->to), TAG_END())); + + TEST_1(!nua_handle_has_active_call(a_call->nh)); + TEST_1(!nua_handle_has_call_on_hold(a_call->nh)); + + *ma = *a->contact; + ma->m_display = "Alice B."; + ma->m_url->url_user = "a++a"; + + *mb = *b->contact; + mb->m_display = "Bob A."; + mb->m_url->url_user = "b++b"; + + contact_for_b = mb; + + sip_route_init(rb)->r_url[0] = b->contact->m_url[0]; + rb->r_url->url_user = "bob+0"; + url_param_add(nua_handle_home(a_call->nh), rb->r_url, "lr"); + + INVITE(a, a_call, a_call->nh, + NUTAG_URL("sip:bob@example.org"), /* Expanded by proxy */ + SIPTAG_ROUTE_STR("B2 <sip:bob+2@example.org>;bar=foo"), /* Last in list */ + NUTAG_INITIAL_ROUTE(ctx->lr), /* Removed by proxy (if any) */ + NUTAG_INITIAL_ROUTE(rb), /* Used to route request to b (not removed) */ + NUTAG_INITIAL_ROUTE_STR("B1 <sip:bob+1@example.org;lr>;foo=bar"), /* Next in list */ + SOATAG_USER_SDP_STR(a_call->sdp), + SIPTAG_CONTACT(ma), + TAG_END()); + + run_ab_until(ctx, -1, until_ready, -1, accept_call_with_contact); + + TEST_1(nua_handle_has_active_call(a_call->nh)); + TEST_1(!nua_handle_has_call_on_hold(a_call->nh)); + + TEST_1(nua_handle_has_active_call(b_call->nh)); + TEST_1(!nua_handle_has_call_on_hold(b_call->nh)); + + /* Client transitions: + INIT -(C1)-> CALLING: nua_invite(), nua_i_state + CALLING -(C2)-> PROCEEDING: nua_r_invite, nua_i_state + PROCEEDING -(C3+C4)-> READY: nua_r_invite, nua_i_state + */ + TEST_1(e = a->events->head); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_calling); /* CALLING */ + TEST_1(is_offer_sent(e->data->e_tags)); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_r_invite); + TEST(e->data->e_status, 180); + TEST_1(sip = sip_object(e->data->e_msg)); + TEST_1(sip->sip_payload); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_proceeding); /* PROCEEDING */ + TEST_1(is_answer_recv(e->data->e_tags)); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_r_invite); + TEST(e->data->e_status, 200); + TEST_1(sip = sip_object(e->data->e_msg)); + TEST_1(sip->sip_payload); + TEST_1(sip->sip_contact); + TEST_S(sip->sip_contact->m_display, "Bob A."); + TEST_S(sip->sip_contact->m_url->url_user, "b++b"); + /* Test that B uses application-specific contact */ + TEST_1(sip->sip_contact->m_url->url_user); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_ready); /* READY */ + TEST_1(!e->next); + free_events_in_list(ctx, a->events); + + /* + Server transitions: + INIT -(S1)-> RECEIVED: nua_i_invite, nua_i_state + RECEIVED -(S2a)-> EARLY: nua_respond(), nua_i_state + EARLY -(S3b)-> COMPLETED: nua_respond(), nua_i_state + COMPLETED -(S4)-> READY: nua_i_ack, nua_i_state + */ + TEST_1(e = b->events->head); TEST_E(e->data->e_event, nua_i_invite); + TEST(e->data->e_status, 100); + TEST_1(sip = sip_object(e->data->e_msg)); + TEST_1(sip->sip_contact); + TEST_S(sip->sip_contact->m_display, "Alice B."); + TEST_S(sip->sip_contact->m_url->url_user, "a++a"); + TEST_1(r = sip->sip_route); + TEST_S(r->r_url->url_user, "bob+0"); + TEST_1(r = r->r_next); + TEST_S(r->r_url->url_user, "bob+1"); + TEST_1(r = r->r_next); + TEST_S(r->r_url->url_user, "bob+2"); + TEST_1(!r->r_next); + + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_received); /* RECEIVED */ + TEST_1(is_offer_recv(e->data->e_tags)); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_early); /* EARLY */ + TEST_1(is_answer_sent(e->data->e_tags)); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_completed); /* COMPLETED */ + TEST_1(is_answer_sent(e->data->e_tags)); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_ack); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_ready); /* READY */ + TEST_1(!e->next); + free_events_in_list(ctx, b->events); + + /* re-INVITE */ + INVITE(a, a_call, a_call->nh, TAG_END()); + run_ab_until(ctx, -1, until_ready, -1, until_ready); + + TEST_1(e = a->events->head); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_calling); /* CALLING */ + TEST_1(is_offer_sent(e->data->e_tags)); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_r_invite); + TEST(e->data->e_status, 200); + TEST_1(sip = sip_object(e->data->e_msg)); + TEST_1(sip->sip_contact); + TEST_S(sip->sip_contact->m_display, "Bob A."); + TEST_S(sip->sip_contact->m_url->url_user, "b++b"); + /* Test that B uses application-specific contact */ + TEST_1(sip->sip_contact->m_url->url_user); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_ready); /* READY */ + TEST_1(!e->next); + free_events_in_list(ctx, a->events); + + TEST_1(e = b->events->head); TEST_E(e->data->e_event, nua_i_invite); + TEST(e->data->e_status, 200); + TEST_1(sip = sip_object(e->data->e_msg)); + TEST_1(sip->sip_contact); + TEST_S(sip->sip_contact->m_display, "Alice B."); + TEST_S(sip->sip_contact->m_url->url_user, "a++a"); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_completed); /* COMPLETED */ + TEST_1(is_answer_sent(e->data->e_tags)); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_ack); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_ready); /* READY */ + TEST_1(!e->next); + free_events_in_list(ctx, b->events); + + BYE(b, b_call, b_call->nh, TAG_END()); + run_ab_until(ctx, -1, until_terminated, -1, until_terminated); + + /* B transitions: + READY --(T2)--> TERMINATING: nua_bye() + TERMINATING --(T3)--> TERMINATED: nua_r_bye, nua_i_state + */ + TEST_1(e = b->events->head); TEST_E(e->data->e_event, nua_r_bye); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_terminated); /* TERMINATED */ + TEST_1(!e->next); + free_events_in_list(ctx, b->events); + + TEST_1(!nua_handle_has_active_call(b_call->nh)); + + /* A transitions: + READY -(T1)-> TERMINATED: nua_i_bye, nua_i_state + */ + TEST_1(e = a->events->head); TEST_E(e->data->e_event, nua_i_bye); + TEST(e->data->e_status, 200); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_terminated); /* TERMINATED */ + TEST_1(!e->next); + free_events_in_list(ctx, a->events); + + TEST_1(!nua_handle_has_active_call(a_call->nh)); + + nua_handle_destroy(a_call->nh), a_call->nh = NULL; + nua_handle_destroy(b_call->nh), b_call->nh = NULL; + + if (print_headings) + printf("TEST NUA-3.6: PASSED\n"); + + END(); +} + +/* Terminate call with 408: + + A B + |-------INVITE------>| + |<----100 Trying-----| + | | + |<----180 Ringing----| + | | + |<------200 OK-------| + |--------ACK-------->| + | | + |--------INFO------->| + |<--------408--------| + | | + |<-------BYE---------| + |--------487-------->| + + Client transitions: + INIT -(C1)-> CALLING -(C2a)-> PROCEEDING -(C3+C4)-> READY + Server transitions: + INIT -(S1)-> RECEIVED -(S2a)-> EARLY -(S3b)-> COMPLETED -(S4)-> READY + + A send INFO: + READY -(T1)-> TERMINATED + + B sends BYE: + READY -(T2)-> TERMINATING -(T3)-> TERMINATED + + See @page nua_call_model in nua.docs for more information +*/ +int reject_method(CONDITION_PARAMS); +int reject_info(CONDITION_PARAMS); + +int test_basic_call_7(struct context *ctx) +{ + BEGIN(); + + struct endpoint *a = &ctx->a, *b = &ctx->b; + struct call *a_call = a->call, *b_call = b->call; + struct event *e; + sip_t *sip; + sip_replaces_t *repa, *repb; + nua_handle_t *nh; + + if (print_headings) + printf("TEST NUA-3.7.1: Release dialog with error response (RFC 5057)\n"); + + a_call->sdp = "m=audio 5008 RTP/AVP 8"; + b_call->sdp = "m=audio 5010 RTP/AVP 0 8"; + + TEST_1(a_call->nh = nua_handle(a->nua, a_call, SIPTAG_TO(b->to), TAG_END())); + + TEST_1(!nua_handle_has_active_call(a_call->nh)); + TEST_1(!nua_handle_has_call_on_hold(a_call->nh)); + + INVITE(a, a_call, a_call->nh, + TAG_IF(!ctx->proxy_tests, NUTAG_URL(b->contact->m_url)), + SOATAG_USER_SDP_STR(a_call->sdp), + TAG_END()); + + run_ab_until(ctx, -1, until_ready, -1, accept_call_with_early_sdp); + + TEST_1(nua_handle_has_active_call(a_call->nh)); + TEST_1(!nua_handle_has_call_on_hold(a_call->nh)); + + TEST_1(nua_handle_has_active_call(b_call->nh)); + TEST_1(!nua_handle_has_call_on_hold(b_call->nh)); + + TEST_1(repa = nua_handle_make_replaces(a_call->nh, nua_handle_home(a_call->nh), 0)); + TEST_1(repb = nua_handle_make_replaces(b_call->nh, nua_handle_home(b_call->nh), 0)); + + TEST_S(repa->rp_call_id, repb->rp_call_id); + + TEST_1(!nua_handle_by_replaces(a->nua, repa)); + TEST_1(!nua_handle_by_replaces(b->nua, repb)); + + TEST_1(nh = nua_handle_by_replaces(a->nua, repb)); + TEST_P(nh, a_call->nh); + nua_handle_unref(nh); + + TEST_1(nh = nua_handle_by_replaces(b->nua, repa)); + TEST_P(nh, b_call->nh); + nua_handle_unref(nh); + + /* Client transitions: + INIT -(C1)-> CALLING: nua_invite(), nua_i_state + CALLING -(C2)-> PROCEEDING: nua_r_invite, nua_i_state + PROCEEDING -(C3+C4)-> READY: nua_r_invite, nua_i_state + */ + TEST_1(e = a->events->head); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_calling); /* CALLING */ + TEST_1(is_offer_sent(e->data->e_tags)); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_r_invite); + TEST(e->data->e_status, 180); + TEST_1(sip = sip_object(e->data->e_msg)); + TEST_S(sip->sip_call_id->i_id, repb->rp_call_id); + TEST_S(sip->sip_from->a_tag, repb->rp_to_tag); + TEST_S(sip->sip_to->a_tag, repb->rp_from_tag); + TEST_1(sip->sip_payload); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_proceeding); /* PROCEEDING */ + TEST_1(is_answer_recv(e->data->e_tags)); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_r_invite); + TEST(e->data->e_status, 200); + TEST_1(sip = sip_object(e->data->e_msg)); + TEST_1(sip->sip_payload); + TEST_1(sip->sip_contact); + TEST_S(sip->sip_contact->m_display, "Bob"); + TEST_S(sip->sip_contact->m_url->url_user, "b+b"); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_ready); /* READY */ + TEST_1(!e->next); + free_events_in_list(ctx, a->events); + + /* + Server transitions: + INIT -(S1)-> RECEIVED: nua_i_invite, nua_i_state + RECEIVED -(S2a)-> EARLY: nua_respond(), nua_i_state + EARLY -(S3b)-> COMPLETED: nua_respond(), nua_i_state + COMPLETED -(S4)-> READY: nua_i_ack, nua_i_state + */ + TEST_1(e = b->events->head); TEST_E(e->data->e_event, nua_i_invite); + TEST(e->data->e_status, 100); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_received); /* RECEIVED */ + TEST_1(is_offer_recv(e->data->e_tags)); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_early); /* EARLY */ + TEST_1(is_answer_sent(e->data->e_tags)); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_completed); /* COMPLETED */ + TEST_1(is_answer_sent(e->data->e_tags)); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_ack); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_ready); /* READY */ + TEST_1(!e->next); + free_events_in_list(ctx, b->events); + + /* Make B to process HUMPPA at application level */ + nua_set_hparams(b_call->nh, NUTAG_APPL_METHOD("HUMPPA"), + NUTAG_ALLOW("HUMPPA"), + TAG_END()); + run_b_until(ctx, nua_r_set_params, until_final_response); + + METHOD(a, a_call, a_call->nh, NUTAG_METHOD("HUMPPA"), TAG_END()); + + run_ab_until(ctx, -1, until_terminated, -1, reject_method); + + TEST_1(e = a->events->head); TEST_E(e->data->e_event, nua_r_method); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_terminated); /* TERMINATED */ + TEST_1(!e->next); + free_events_in_list(ctx, a->events); + + TEST_1(!nua_handle_has_active_call(a_call->nh)); + + TEST_1(e = b->events->head); TEST_E(e->data->e_event, nua_i_method); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_terminated); /* TERMINATED */ + TEST_1(!e->next); + free_events_in_list(ctx, b->events); + + TEST_1(!nua_handle_has_active_call(b_call->nh)); + + nua_handle_destroy(a_call->nh), a_call->nh = NULL; + nua_handle_destroy(b_call->nh), b_call->nh = NULL; + + if (print_headings) + printf("TEST NUA-3.7.1: PASSED\n"); + + if (print_headings) + printf("TEST NUA-3.7.2: Release dialog usage with error response (RFC 5057)\n"); + + TEST_1(a_call->nh = nua_handle(a->nua, a_call, SIPTAG_TO(b->to), TAG_END())); + + TEST_1(!nua_handle_has_active_call(a_call->nh)); + TEST_1(!nua_handle_has_call_on_hold(a_call->nh)); + + INVITE(a, a_call, a_call->nh, + TAG_IF(!ctx->proxy_tests, NUTAG_URL(b->contact->m_url)), + SOATAG_USER_SDP_STR(a_call->sdp), + TAG_END()); + + run_ab_until(ctx, -1, until_ready, -1, accept_call_with_early_sdp); + + TEST_1(nua_handle_has_active_call(a_call->nh)); + TEST_1(!nua_handle_has_call_on_hold(a_call->nh)); + + TEST_1(nua_handle_has_active_call(b_call->nh)); + TEST_1(!nua_handle_has_call_on_hold(b_call->nh)); + + TEST_1(repa = nua_handle_make_replaces(a_call->nh, nua_handle_home(a_call->nh), 0)); + TEST_1(repb = nua_handle_make_replaces(b_call->nh, nua_handle_home(b_call->nh), 0)); + + TEST_S(repa->rp_call_id, repb->rp_call_id); + + TEST_1(!nua_handle_by_replaces(a->nua, repa)); + TEST_1(!nua_handle_by_replaces(b->nua, repb)); + + TEST_1(nh = nua_handle_by_replaces(a->nua, repb)); + TEST_P(nh, a_call->nh); + nua_handle_unref(nh); + + TEST_1(nh = nua_handle_by_replaces(b->nua, repa)); + TEST_P(nh, b_call->nh); + nua_handle_unref(nh); + + /* Client transitions: + INIT -(C1)-> CALLING: nua_invite(), nua_i_state + CALLING -(C2)-> PROCEEDING: nua_r_invite, nua_i_state + PROCEEDING -(C3+C4)-> READY: nua_r_invite, nua_i_state + */ + TEST_1(e = a->events->head); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_calling); /* CALLING */ + TEST_1(is_offer_sent(e->data->e_tags)); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_r_invite); + TEST(e->data->e_status, 180); + TEST_1(sip = sip_object(e->data->e_msg)); + TEST_S(sip->sip_call_id->i_id, repb->rp_call_id); + TEST_S(sip->sip_from->a_tag, repb->rp_to_tag); + TEST_S(sip->sip_to->a_tag, repb->rp_from_tag); + TEST_1(sip->sip_payload); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_proceeding); /* PROCEEDING */ + TEST_1(is_answer_recv(e->data->e_tags)); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_r_invite); + TEST(e->data->e_status, 200); + TEST_1(sip = sip_object(e->data->e_msg)); + TEST_1(sip->sip_payload); + TEST_1(sip->sip_contact); + TEST_S(sip->sip_contact->m_display, "Bob"); + TEST_S(sip->sip_contact->m_url->url_user, "b+b"); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_ready); /* READY */ + TEST_1(!e->next); + free_events_in_list(ctx, a->events); + + /* + Server transitions: + INIT -(S1)-> RECEIVED: nua_i_invite, nua_i_state + RECEIVED -(S2a)-> EARLY: nua_respond(), nua_i_state + EARLY -(S3b)-> COMPLETED: nua_respond(), nua_i_state + COMPLETED -(S4)-> READY: nua_i_ack, nua_i_state + */ + TEST_1(e = b->events->head); TEST_E(e->data->e_event, nua_i_invite); + TEST(e->data->e_status, 100); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_received); /* RECEIVED */ + TEST_1(is_offer_recv(e->data->e_tags)); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_early); /* EARLY */ + TEST_1(is_answer_sent(e->data->e_tags)); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_completed); /* COMPLETED */ + TEST_1(is_answer_sent(e->data->e_tags)); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_ack); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_ready); /* READY */ + TEST_1(!e->next); + free_events_in_list(ctx, b->events); + + /* Let A allow INFO */ + nua_set_params(a->nua, NUTAG_ALLOW("INFO"), TAG_END()); + /* Make B to process INFO at application level */ + nua_set_hparams(b_call->nh, NUTAG_APPL_METHOD("INFO"), + NUTAG_ALLOW("INFO"), + TAG_END()); + run_ab_until(ctx, nua_r_set_params, NULL, nua_r_set_params, NULL); + + INFO(a, a_call, a_call->nh, TAG_END()); + + run_ab_until(ctx, -1, until_terminated, -1, reject_info); + + TEST_1(e = a->events->head); TEST_E(e->data->e_event, nua_r_info); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_terminated); /* TERMINATED */ + TEST_1(!e->next); + free_events_in_list(ctx, a->events); + + TEST_1(!nua_handle_has_active_call(a_call->nh)); + + TEST_1(e = b->events->head); TEST_E(e->data->e_event, nua_i_info); + TEST_1(!e->next); + free_events_in_list(ctx, b->events); + + TEST_1(nua_handle_has_active_call(b_call->nh)); + + INFO(b, b_call, b_call->nh, TAG_END()); + run_b_until(ctx, -1, until_terminated); + + /* B transitions: + READY --(T2)--> TERMINATING: nua_bye() + TERMINATING --(T3)--> TERMINATED: nua_r_bye, nua_i_state + */ + TEST_1(e = b->events->head); TEST_E(e->data->e_event, nua_r_info); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_terminated); /* TERMINATED */ + TEST_1(!e->next); + free_events_in_list(ctx, b->events); + + TEST_1(!nua_handle_has_active_call(b_call->nh)); + + nua_handle_destroy(a_call->nh), a_call->nh = NULL; + nua_handle_destroy(b_call->nh), b_call->nh = NULL; + + if (print_headings) + printf("TEST NUA-3.7.2: PASSED\n"); + + END(); +} + +int reject_method(CONDITION_PARAMS) +{ + msg_t *current = nua_current_request(nua); + + if (!(check_handle(ep, call, nh, SIP_500_INTERNAL_SERVER_ERROR))) + return 0; + + save_event_in_list(ctx, event, ep, call); + + if (event == nua_i_method) { + RESPOND(ep, call, nh, + SIP_604_DOES_NOT_EXIST_ANYWHERE, + NUTAG_WITH(current), + TAG_END()); + return 1; + } + return 0; +} + +int reject_info(CONDITION_PARAMS) +{ + msg_t *current = nua_current_request(nua); + + if (!(check_handle(ep, call, nh, SIP_500_INTERNAL_SERVER_ERROR))) + return 0; + + save_event_in_list(ctx, event, ep, call); + + if (event == nua_i_info) { + RESPOND(ep, call, nh, + SIP_480_TEMPORARILY_UNAVAILABLE, + NUTAG_WITH(current), + TAG_END()); + return 1; + } + return 0; +} + + +int test_basic_call(struct context *ctx) +{ + return 0 + || test_basic_call_1(ctx) + || test_basic_call_2(ctx) + || test_basic_call_3(ctx) + || test_basic_call_4(ctx) + || test_basic_call_5(ctx) + || test_basic_call_6(ctx) + || test_basic_call_7(ctx) + || test_video_call_1(ctx) + ; +} Added: freeswitch/trunk/libs/sofia-sip/tests/test_call_hold.c ============================================================================== --- (empty file) +++ freeswitch/trunk/libs/sofia-sip/tests/test_call_hold.c Wed May 14 15:10:54 2008 @@ -0,0 +1,1170 @@ +/* + * This file is part of the Sofia-SIP package + * + * Copyright (C) 2005 Nokia Corporation. + * + * Contact: Pekka Pessi <pekka.pessi@nokia.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public License + * as published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + */ + +/**@CFILE test_call_hold.c + * @brief Test re-INVITE, call hold, un-hold. + * + * @author Pekka Pessi <Pekka.Pessi@nokia.com> + * @author Martti Mela <Martti Mela@nokia.com> + * + * @date Created: Wed Aug 17 12:12:12 EEST 2005 ppessi + */ + +#include "config.h" + +#include "test_nua.h" +#include <sofia-sip/su_tag_class.h> + +#if HAVE_FUNC +#elif HAVE_FUNCTION +#define __func__ __FUNCTION__ +#else +#define __func__ "test_call_hold" +#endif + +int complete_call(CONDITION_PARAMS); +int until_complete(CONDITION_PARAMS); +int invite_responded(CONDITION_PARAMS); +static size_t remove_first_ack(void *_once, void *message, size_t len); + +/* ======================================================================== */ +/* test_call_hold message sequence looks like this: + + A B + | | + |-------INVITE------>| + |<----100 Trying-----| + | | + |<----180 Ringing----| + | | + |<--------200--------| + |---------ACK------->| + : : + |--INVITE(sendonly)->| + |<---200(recvonly)---| + |---------ACK------->| + : : + |<-INVITE(inactive)--| + |----200(inactive)-->| + |<--------ACK--------| + : : + |--INVITE(recvonly)->| + |<---200(sendonly)---| + |---------ACK------->| + : : + |<-INVITE(sendrecv)--| + |----200(sendrecv)-->| + |<--------ACK--------| + : : + |--------INFO------->| + |<--------200--------| + : : + |---------BYE------->| + |<--------200--------| +*/ + +int test_call_hold(struct context *ctx) +{ + BEGIN(); + + struct endpoint *a = &ctx->a, *b = &ctx->b; + struct call *a_call = a->call, *b_call = b->call; + struct event *e; + sip_t *sip; + int zero = 0; + struct nat_filter *f; + + a_call->sdp = + "m=audio 5008 RTP/AVP 0 8\n" + "m=video 6008 RTP/AVP 30\n"; + b_call->sdp = + "m=audio 5010 RTP/AVP 8\n" + "a=rtcp:5011\n" + "m=video 6010 RTP/AVP 30\n" + "a=rtcp:6011\n"; + + TEST_1(a_call->nh = nua_handle(a->nua, a_call, SIPTAG_TO(b->to), TAG_END())); + INVITE(a, a_call, a_call->nh, + TAG_IF(!ctx->proxy_tests, NUTAG_URL(b->contact->m_url)), + SOATAG_USER_SDP_STR(a_call->sdp), + TAG_END()); + + run_ab_until(ctx, -1, until_ready, -1, accept_call); + + /* + Client transitions: + INIT -(C1)-> CALLING: nua_invite(), nua_i_state + CALLING -(C2)-> PROCEEDING: nua_r_invite, nua_i_state + PROCEEDING -(C3+C4)-> READY: nua_r_invite, nua_i_state + */ + TEST_1(e = a->events->head); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_calling); /* CALLING */ + TEST_1(is_offer_sent(e->data->e_tags)); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_r_invite); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_proceeding); /* PROCEEDING */ + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_r_invite); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_ready); /* READY */ + TEST_1(is_answer_recv(e->data->e_tags)); + TEST(audio_activity(e->data->e_tags), SOA_ACTIVE_SENDRECV); + TEST_1(!e->next); + free_events_in_list(ctx, a->events); + + TEST_1(nua_handle_has_active_call(a_call->nh)); + TEST_1(!nua_handle_has_call_on_hold(a_call->nh)); + + /* + Server transitions: + INIT -(S1)-> RECEIVED: nua_i_invite, nua_i_state + RECEIVED -(S2a)-> EARLY: nua_respond(), nua_i_state + EARLY -(S3b)-> COMPLETED: nua_respond(), nua_i_state + COMPLETED -(S4)-> READY: nua_i_ack, nua_i_state + */ + TEST_1(e = b->events->head); TEST_E(e->data->e_event, nua_i_invite); + TEST(e->data->e_status, 100); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_received); /* RECEIVED */ + TEST_1(is_offer_recv(e->data->e_tags)); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_early); /* EARLY */ + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_completed); /* COMPLETED */ + TEST_1(is_answer_sent(e->data->e_tags)); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_ack); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_ready); /* READY */ + TEST(audio_activity(e->data->e_tags), SOA_ACTIVE_SENDRECV); + TEST_1(!e->next); + + TEST_1(nua_handle_has_active_call(b_call->nh)); + TEST_1(!nua_handle_has_call_on_hold(b_call->nh)); + + free_events_in_list(ctx, b->events); + + /* + : : + |--INVITE(sendonly)->| + |<---200(recvonly)---| + |---------ACK------->| + : : + */ + + if (print_headings) + printf("TEST NUA-7.1: put B on hold\n"); + + /* Put B on hold */ + INVITE(a, a_call, a_call->nh, SOATAG_HOLD("audio"), + SIPTAG_SUBJECT_STR("hold b"), + TAG_END()); + run_ab_until(ctx, -1, until_ready, -1, until_ready); + + /* Client transitions: + READY -(C1)-> CALLING: nua_invite(), nua_i_state + CALLING -(C3a+C4)-> READY: nua_r_invite, nua_i_state + */ + TEST_1(e = a->events->head); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_calling); /* CALLING */ + TEST_1(is_offer_sent(e->data->e_tags)); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_r_invite); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_ready); /* READY */ + TEST_1(is_answer_recv(e->data->e_tags)); + TEST(audio_activity(e->data->e_tags), SOA_ACTIVE_SENDONLY); + TEST_1(!e->next); + + TEST_1(nua_handle_has_active_call(a_call->nh)); + TEST_1(nua_handle_has_call_on_hold(a_call->nh)); + + free_events_in_list(ctx, a->events); + + /* + Server transitions: + READY -(S3a)-> COMPLETED: nua_i_invite, <auto-answer>, nua_i_state + COMPLETED -(S4)-> READY: nua_i_ack, nua_i_state + */ + TEST_1(e = b->events->head); TEST_E(e->data->e_event, nua_i_invite); + TEST(e->data->e_status, 200); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_completed); /* COMPLETED */ + TEST_1(is_answer_sent(e->data->e_tags)); + TEST(audio_activity(e->data->e_tags), SOA_ACTIVE_RECVONLY); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_ack); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_ready); /* READY */ + TEST(audio_activity(e->data->e_tags), SOA_ACTIVE_RECVONLY); + TEST_1(!e->next); + + TEST_1(nua_handle_has_active_call(b_call->nh)); + TEST_1(!nua_handle_has_call_on_hold(b_call->nh)); + + free_events_in_list(ctx, b->events); + + if (print_headings) + printf("TEST NUA-7.1: PASSED\n"); + + /* ------------------------------------------------------------------------ */ + /* + : : + |<-INVITE(inactive)--| + |----200(inactive)-->| + |<--------ACK--------| + : : + */ + + if (print_headings) + printf("TEST NUA-7.2: put A on hold\n"); + + /* Put A on hold, too. */ + INVITE(b, b_call, b_call->nh, SOATAG_HOLD("audio"), + SIPTAG_SUBJECT_STR("hold a"), + TAG_END()); + run_ab_until(ctx, -1, until_ready, -1, until_ready); + + /* Client transitions: + READY -(C1)-> CALLING: nua_invite(), nua_i_state + CALLING -(C3a+C4)-> READY: nua_r_invite, nua_i_state + */ + TEST_1(e = b->events->head); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_calling); /* CALLING */ + TEST_1(is_offer_sent(e->data->e_tags)); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_r_invite); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_ready); /* READY */ + TEST_1(is_answer_recv(e->data->e_tags)); + TEST(audio_activity(e->data->e_tags), SOA_ACTIVE_INACTIVE); + TEST(video_activity(e->data->e_tags), SOA_ACTIVE_SENDRECV); + TEST_1(!e->next); + + TEST_1(nua_handle_has_active_call(b_call->nh)); + TEST_1(nua_handle_has_call_on_hold(b_call->nh)); + + free_events_in_list(ctx, b->events); + + /* + Server transitions: + READY -(S3a)-> COMPLETED: nua_i_invite, <auto-answer>, nua_i_state + COMPLETED -(S4)-> READY: nua_i_ack, nua_i_state + */ + TEST_1(e = a->events->head); TEST_E(e->data->e_event, nua_i_invite); + TEST(e->data->e_status, 200); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_completed); /* COMPLETED */ + TEST_1(is_answer_sent(e->data->e_tags)); + TEST(audio_activity(e->data->e_tags), SOA_ACTIVE_INACTIVE); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_ack); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_ready); /* READY */ + TEST(audio_activity(e->data->e_tags), SOA_ACTIVE_INACTIVE); + TEST(video_activity(e->data->e_tags), SOA_ACTIVE_SENDRECV); + TEST_1(!e->next); + + TEST_1(nua_handle_has_active_call(a_call->nh)); + TEST_1(nua_handle_has_call_on_hold(a_call->nh)); + + free_events_in_list(ctx, a->events); + + if (print_headings) + printf("TEST NUA-7.2: PASSED\n"); + + /* ------------------------------------------------------------------------ */ + /* + : : + |--INVITE(recvonly)->| + |<---200(sendonly)---| + |---------ACK------->| + : : + */ + + if (print_headings) + printf("TEST NUA-7.3: resume B\n"); + + /* Resume B from hold */ + INVITE(a, a_call, a_call->nh, SOATAG_HOLD(NULL), + SIPTAG_SUBJECT_STR("resume b"), + TAG_END()); + run_ab_until(ctx, -1, until_ready, -1, until_ready); + + /* Client transitions: + READY -(C1)-> CALLING: nua_invite(), nua_i_state + CALLING -(C3a+C4)-> READY: nua_r_invite, nua_i_state + */ + TEST_1(e = a->events->head); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_calling); /* CALLING */ + TEST_1(is_offer_sent(e->data->e_tags)); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_r_invite); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_ready); /* READY */ + TEST_1(is_answer_recv(e->data->e_tags)); + TEST(audio_activity(e->data->e_tags), SOA_ACTIVE_RECVONLY); + TEST(video_activity(e->data->e_tags), SOA_ACTIVE_SENDRECV); + TEST_1(!e->next); + free_events_in_list(ctx, a->events); + + TEST_1(nua_handle_has_active_call(a_call->nh)); + TEST_1(!nua_handle_has_call_on_hold(a_call->nh)); + + /* + Server transitions: + READY -(S3a)-> COMPLETED: nua_i_invite, <auto-answer>, nua_i_state + COMPLETED -(S4)-> READY: nua_i_ack, nua_i_state + */ + TEST_1(e = b->events->head); TEST_E(e->data->e_event, nua_i_invite); + TEST(e->data->e_status, 200); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_completed); /* COMPLETED */ + TEST_1(is_answer_sent(e->data->e_tags)); + TEST(audio_activity(e->data->e_tags), SOA_ACTIVE_SENDONLY); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_ack); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_ready); /* READY */ + TEST(audio_activity(e->data->e_tags), SOA_ACTIVE_SENDONLY); + TEST(video_activity(e->data->e_tags), SOA_ACTIVE_SENDRECV); + TEST_1(!e->next); + + TEST_1(nua_handle_has_active_call(b_call->nh)); + TEST_1(nua_handle_has_call_on_hold(b_call->nh)); + + free_events_in_list(ctx, b->events); + + if (print_headings) + printf("TEST NUA-7.3: PASSED\n"); + + /* ------------------------------------------------------------------------ */ + /* + : : + |<-INVITE(sendrecv)--| + |----200(sendrecv)-->| + |<--------ACK--------| + : : + */ + + if (print_headings) + printf("TEST NUA-7.4: resume A\n"); + + /* Resume A on hold, too. */ + INVITE(b, b_call, b_call->nh, SOATAG_HOLD(""), + SIPTAG_SUBJECT_STR("TEST NUA-7.4: resume A"), + TAG_END()); + run_ab_until(ctx, -1, until_ready, -1, until_ready); + + /* Client transitions: + READY -(C1)-> CALLING: nua_invite(), nua_i_state + CALLING -(C3a+C4)-> READY: nua_r_invite, nua_i_state + */ + TEST_1(e = b->events->head); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_calling); /* CALLING */ + TEST_1(is_offer_sent(e->data->e_tags)); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_r_invite); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_ready); /* READY */ + TEST_1(is_answer_recv(e->data->e_tags)); + TEST(audio_activity(e->data->e_tags), SOA_ACTIVE_SENDRECV); + TEST(video_activity(e->data->e_tags), SOA_ACTIVE_SENDRECV); + TEST_1(!e->next); + free_events_in_list(ctx, b->events); + + TEST_1(nua_handle_has_active_call(a_call->nh)); + TEST_1(!nua_handle_has_call_on_hold(a_call->nh)); + + /* + Server transitions: + READY -(S3a)-> COMPLETED: nua_i_invite, <auto-answer>, nua_i_state + COMPLETED -(S4)-> READY: nua_i_ack, nua_i_state + */ + TEST_1(e = a->events->head); TEST_E(e->data->e_event, nua_i_invite); + TEST(e->data->e_status, 200); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_completed); /* COMPLETED */ + TEST_1(is_answer_sent(e->data->e_tags)); + TEST(audio_activity(e->data->e_tags), SOA_ACTIVE_SENDRECV); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_ack); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_ready); /* READY */ + TEST(audio_activity(e->data->e_tags), SOA_ACTIVE_SENDRECV); + TEST(video_activity(e->data->e_tags), SOA_ACTIVE_SENDRECV); + TEST_1(!e->next); + + TEST_1(nua_handle_has_active_call(b_call->nh)); + TEST_1(!nua_handle_has_call_on_hold(b_call->nh)); + + free_events_in_list(ctx, a->events); + + if (print_headings) + printf("TEST NUA-7.4: PASSED\n"); + + /* ---------------------------------------------------------------------- */ + /* + A B + |--------INFO------->| + |<--------200--------| + */ + if (print_headings) + printf("TEST NUA-7.5: send INFO\n"); + + INFO(a, a_call, a_call->nh, TAG_END()); + run_a_until(ctx, -1, save_until_final_response); + /* XXX - B should get a nua_i_info event with 405 */ + + /* A sent INFO, receives 405 */ + TEST_1(e = a->events->head); TEST_E(e->data->e_event, nua_r_info); + TEST(e->data->e_status, 405); + TEST_1(!e->next); + free_events_in_list(ctx, a->events); + +#if 0 /* XXX */ + /* B received INFO */ + TEST_1(e = b->events->head); TEST_E(e->data->e_event, nua_i_info); + TEST(e->data->e_status, 405); + TEST_1(!e->next); + free_events_in_list(ctx, b->events); +#endif + + /* Add INFO to allowed methods */ + nua_set_hparams(b_call->nh, NUTAG_ALLOW("INFO, PUBLISH"), TAG_END()); + run_b_until(ctx, nua_r_set_params, until_final_response); + + INFO(a, a_call, a_call->nh, TAG_END()); + run_ab_until(ctx, -1, save_until_final_response, -1, save_until_received); + + /* A sent INFO, receives 200 */ + TEST_1(e = a->events->head); TEST_E(e->data->e_event, nua_r_info); + TEST(e->data->e_status, 200); + TEST_1(!e->next); + free_events_in_list(ctx, a->events); + + /* B received INFO */ + TEST_1(e = b->events->head); TEST_E(e->data->e_event, nua_i_info); + TEST(e->data->e_status, 200); + TEST_1(!e->next); + free_events_in_list(ctx, b->events); + + if (print_headings) + printf("TEST NUA-7.5: PASSED\n"); + + /* ------------------------------------------------------------------------ */ + /* + : : + |<------INVITE-------| + |--------200-------->| + |<--------ACK--------| + : : + */ + + if (print_headings) + printf("TEST NUA-7.6.1: re-INVITE without auto-ack\n"); + + /* Turn off auto-ack */ + nua_set_hparams(b_call->nh, NUTAG_AUTOACK(0), TAG_END()); + run_b_until(ctx, nua_r_set_params, until_final_response); + + INVITE(b, b_call, b_call->nh, SOATAG_HOLD(""), + SIPTAG_SUBJECT_STR("TEST NUA-7.6: re-INVITE without auto-ack"), + TAG_END()); + run_ab_until(ctx, -1, until_complete, -1, complete_call); + + /* Client transitions: + READY -(C1)-> CALLING: nua_invite(), nua_i_state + CALLING -(C3a)-> COMPLETING: nua_r_invite, nua_i_state + COMPLETING -(C4)-> READY: nua_ack(), nua_i_state + */ + TEST_1(e = b->events->head); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_calling); /* CALLING */ + TEST_1(is_offer_sent(e->data->e_tags)); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_r_invite); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_completing); /* COMPLETING */ + TEST_1(is_answer_recv(e->data->e_tags)); + TEST(audio_activity(e->data->e_tags), SOA_ACTIVE_SENDRECV); + TEST(video_activity(e->data->e_tags), SOA_ACTIVE_SENDRECV); + TEST_1(!e->next); + + free_events_in_list(ctx, b->events); + + /* + Server transitions: + READY -(S3a)-> COMPLETED: nua_i_invite, <auto-answer>, nua_i_state + COMPLETED -(S4)-> READY: nua_i_ack, nua_i_state + */ + TEST_1(e = a->events->head); TEST_E(e->data->e_event, nua_i_invite); + TEST(e->data->e_status, 200); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_completed); /* COMPLETED */ + TEST_1(is_answer_sent(e->data->e_tags)); + TEST(audio_activity(e->data->e_tags), SOA_ACTIVE_SENDRECV); + TEST_1(!e->next); + free_events_in_list(ctx, a->events); + + ACK(b, b_call, b_call->nh, TAG_END()); + + run_ab_until(ctx, -1, until_ready, -1, until_ready); + + TEST_1(e = b->events->head); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_ready); /* READY */ + TEST_1(!e->next); + free_events_in_list(ctx, b->events); + + TEST_1(e = a->events->head); TEST_E(e->data->e_event, nua_i_ack); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_ready); /* READY */ + TEST(audio_activity(e->data->e_tags), SOA_ACTIVE_SENDRECV); + TEST(video_activity(e->data->e_tags), SOA_ACTIVE_SENDRECV); + TEST_1(!e->next); + free_events_in_list(ctx, a->events); + + if (print_headings) + printf("TEST NUA-7.6.1: PASSED\n"); + + /* ------------------------------------------------------------------------ */ + /* + : : + |<------INVITE-------| + |--------200-------->| + |<--------ACK--------| + : : + */ + + if (ctx->proxy_tests && ctx->nat) { + if (print_headings) + printf("TEST NUA-7.6.2: almost overlapping re-INVITE\n"); + + /* Turn off auto-ack */ + nua_set_hparams(b_call->nh, NUTAG_AUTOACK(0), TAG_END()); + run_b_until(ctx, nua_r_set_params, until_final_response); + + INVITE(b, b_call, b_call->nh, SOATAG_HOLD("#"), + SIPTAG_SUBJECT_STR("TEST NUA-7.6.2: re-INVITE"), + TAG_END()); + run_ab_until(ctx, -1, until_complete, -1, complete_call); + + /* Client transitions: + READY -(C1)-> CALLING: nua_invite(), nua_i_state + CALLING -(C3a)-> COMPLETING: nua_r_invite, nua_i_state + COMPLETING -(C4)-> READY: nua_ack(), nua_i_state + */ + TEST_1(e = b->events->head); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_calling); /* CALLING */ + TEST_1(is_offer_sent(e->data->e_tags)); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_r_invite); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_completing); /* COMPLETING */ + TEST_1(is_answer_recv(e->data->e_tags)); + TEST(audio_activity(e->data->e_tags), SOA_ACTIVE_INACTIVE); + TEST(video_activity(e->data->e_tags), SOA_ACTIVE_INACTIVE); + TEST_1(!e->next); + + free_events_in_list(ctx, b->events); + + /* + Server transitions: + READY -(S3a)-> COMPLETED: nua_i_invite, <auto-answer>, nua_i_state + COMPLETED -(S4)-> READY: nua_i_ack, nua_i_state + */ + TEST_1(e = a->events->head); TEST_E(e->data->e_event, nua_i_invite); + TEST(e->data->e_status, 200); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_completed); /* COMPLETED */ + TEST_1(is_answer_sent(e->data->e_tags)); + TEST(audio_activity(e->data->e_tags), SOA_ACTIVE_INACTIVE); + TEST_1(!e->next); + free_events_in_list(ctx, a->events); + + f = test_nat_add_filter(ctx->nat, remove_first_ack, &zero, nat_inbound); + + ACK(b, b_call, b_call->nh, TAG_END()); + INVITE(b, b_call, b_call->nh, SOATAG_HOLD("*"), + SIPTAG_SUBJECT_STR("TEST NUA-7.6.2: almost overlapping re-INVITE"), + NUTAG_AUTOACK(1), + TAG_END()); + + run_ab_until(ctx, -1, until_ready, -1, invite_responded); + + TEST_1(e = b->events->head); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_ready); /* READY */ + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_calling); /* CALLING */ + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_r_invite); + TEST(e->data->e_status, 100); + TEST_1(sip = sip_object(e->data->e_msg)); + TEST_1(sip->sip_retry_after); + TEST_1(!e->next); + free_events_in_list(ctx, b->events); + + TEST_1(e = a->events->head); TEST_E(e->data->e_event, nua_i_ack); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_ready); /* READY */ + TEST(audio_activity(e->data->e_tags), SOA_ACTIVE_INACTIVE); + TEST(video_activity(e->data->e_tags), SOA_ACTIVE_INACTIVE); + TEST_1(!e->next); + free_events_in_list(ctx, a->events); + + test_nat_remove_filter(ctx->nat, f); + + if (print_headings) + printf("TEST NUA-7.6.2: PASSED\n"); + } + + + /* ---------------------------------------------------------------------- */ + /* + A B + |---------BYE------->| + |<--------200--------| + */ + + if (print_headings) + printf("TEST NUA-7.6.3: terminate call\n"); + + BYE(a, a_call, a_call->nh, TAG_END()); + run_ab_until(ctx, -1, until_terminated, -1, until_terminated); + + /* + Transitions of A: + READY --(T2)--> TERMINATING: nua_bye() + TERMINATING --(T3)--> TERMINATED: nua_r_bye, nua_i_state + */ + TEST_1(e = a->events->head); TEST_E(e->data->e_event, nua_r_bye); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_terminated); /* TERMINATED */ + TEST_1(!e->next); + free_events_in_list(ctx, a->events); + + /* Transitions of B: + READY -(T1)-> TERMINATED: nua_i_bye, nua_i_state + */ + TEST_1(e = b->events->head); TEST_E(e->data->e_event, nua_i_bye); + TEST(e->data->e_status, 200); + if (ctx->proxy_tests && ctx->nat) { + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_r_invite); + TEST(e->data->e_status, 481); + } + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_terminated); /* TERMINATED */ + TEST_1(!e->next); + free_events_in_list(ctx, b->events); + + if (print_headings) + printf("TEST NUA-7.6.3: PASSED\n"); + + nua_handle_destroy(a_call->nh), a_call->nh = NULL; + nua_handle_destroy(b_call->nh), b_call->nh = NULL; + + END(); +} + +/* + INVITE without auto-ack + X + | | + |-------INVITE------>| + |<--------200--------| + | | + |---------ACK------->| +*/ +int complete_call(CONDITION_PARAMS) +{ + if (!(check_handle(ep, call, nh, SIP_500_INTERNAL_SERVER_ERROR))) + return 0; + + save_event_in_list(ctx, event, ep, call); + + switch (callstate(tags)) { + case nua_callstate_completing: + return 1; + case nua_callstate_ready: + return 1; + case nua_callstate_terminated: + if (call) + nua_handle_destroy(call->nh), call->nh = NULL; + return 1; + default: + return 0; + } +} + +/* + X INVITE + | | + |-------INVITE------>| + |<--------200--------| +*/ +int until_complete(CONDITION_PARAMS) +{ + if (!(check_handle(ep, call, nh, SIP_500_INTERNAL_SERVER_ERROR))) + return 0; + + save_event_in_list(ctx, event, ep, call); + + switch (callstate(tags)) { + case nua_callstate_completed: + case nua_callstate_ready: + return 1; + case nua_callstate_terminated: + if (call) + nua_handle_destroy(call->nh), call->nh = NULL; + return 1; + default: + return 0; + } +} + +static size_t remove_first_ack(void *_once, void *message, size_t len) +{ + int *once = _once; + + if (*once) + return len; + + if (strncmp("ACK ", message, 4) == 0) { + printf("FILTERING %.*s\n", strcspn(message, "\r\n"), (char *)message); + *once = 1; + return 0; + } + + return len; +} + +/* ======================================================================== */ +/* test_reinvite message sequence looks like this: + + A B + | | + |-------INVITE------>| + |<----100 Trying-----| + | | + |<----180 Ringing----| + | | + |<--------200--------| + |---------ACK------->| + : : + |<----re-INVITE------| + |<-------BYE---------| + |--------200-------->| + |-----487-INVITE---->| + |<--------ACK--------| +*/ + +int accept_no_save(CONDITION_PARAMS); +int ringing_until_terminated(CONDITION_PARAMS); +int bye_when_ringing(CONDITION_PARAMS); + +int test_reinvite(struct context *ctx) +{ + BEGIN(); + + struct endpoint *a = &ctx->a, *b = &ctx->b; + struct call *a_call = a->call, *b_call = b->call; + + if (print_headings) + printf("TEST NUA-7.7: Test re-INVITE and BYE\n"); + + a_call->sdp = "m=audio 5008 RTP/AVP 0 8\n"; + b_call->sdp = "m=audio 5010 RTP/AVP 8\n"; + + TEST_1(a_call->nh = + nua_handle(a->nua, a_call, + SIPTAG_FROM_STR("Alice <sip:alice@example.com>"), + SIPTAG_TO(b->to), + NUTAG_AUTOANSWER(0), + TAG_END())); + INVITE(a, a_call, a_call->nh, + TAG_IF(!ctx->proxy_tests, NUTAG_URL(b->contact->m_url)), + SOATAG_USER_SDP_STR(a_call->sdp), + TAG_END()); + + run_ab_until(ctx, -1, accept_no_save, -1, accept_no_save); + + TEST_1(nua_handle_has_active_call(a_call->nh)); + TEST_1(nua_handle_has_active_call(b_call->nh)); + + free_events_in_list(ctx, a->events); + free_events_in_list(ctx, b->events); + +/* + A B + |<----re-INVITE------| + |<------CANCEL-------| + |<-------BYE---------| + |-----200-CANCEL---->| + |------200-BYE------>| + |-----487-INVITE---->| + |<--------ACK--------| +*/ + + /* re-INVITE A, send BYE after receiving 180 */ + INVITE(b, b_call, b_call->nh, + SIPTAG_SUBJECT_STR("re-INVITE"), + TAG_END()); + /* Run until both a and b has terminated their call */ + run_ab_until(ctx, -1, ringing_until_terminated, -1, bye_when_ringing); + +#if notyet + struct event *e; + + /* XXX - check events later - now we are happy that calls get terminated */ + /* Client events: + READY -(C1)-> CALLING: nua_invite(), nua_i_state + CALLING --(C2)--> PROCEEDING: nua_r_invite, nua_i_state, nua_bye() + PROCEEDING--((C3a+C4)-> READY: nua_r_invite, nua_i_state + READY --(T2)--> TERMINATING: nua_bye() + TERMINATING --(T3)--> TERMINATED: nua_r_bye, nua_i_state + */ + TEST_1(e = b->events->head); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_calling); /* CALLING */ + TEST_1(is_offer_sent(e->data->e_tags)); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_r_invite); + TEST(e->data->e_status, 180); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_terminating); /* READY */ + /* Now we can receive events, in any possible order */ + /* XXX */ + TEST_1(e = e->next); + + TEST_1(!nua_handle_has_active_call(a_call->nh)); + + /* + Server transitions: + READY --(T2)--> CTERMINATING: nua_bye() + TERMINATING --(T3)--> TERMINATED: nua_r_bye, nua_i_state + READY -(S3a)-> COMPLETED: nua_i_invite, <auto-answer>, nua_i_state + COMPLETED -(S4)-> READY: nua_i_ack, nua_i_state + */ + TEST_1(e = b->events->head); TEST_E(e->data->e_event, nua_i_invite); + TEST(e->data->e_status, 200); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_completed); /* COMPLETED */ + TEST_1(is_answer_sent(e->data->e_tags)); + TEST(audio_activity(e->data->e_tags), SOA_ACTIVE_RECVONLY); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_ack); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_ready); /* READY */ + TEST(audio_activity(e->data->e_tags), SOA_ACTIVE_RECVONLY); + TEST_1(!e->next); + + TEST_1(nua_handle_has_active_call(b_call->nh)); + TEST_1(!nua_handle_has_call_on_hold(b_call->nh)); + +#endif + + if (print_headings) + printf("TEST NUA-7.7: PASSED\n"); + + free_events_in_list(ctx, a->events); + free_events_in_list(ctx, b->events); + + nua_handle_destroy(a_call->nh), a_call->nh = NULL; + nua_handle_destroy(b_call->nh), b_call->nh = NULL; + + END(); +} + +/* + Accept INVITE + X + | | + |-------INVITE------>| + |<--------200--------| + | | + |---------ACK------->| +*/ +int accept_no_save(CONDITION_PARAMS) +{ + if (!(check_handle(ep, call, nh, SIP_500_INTERNAL_SERVER_ERROR))) + return 0; + + switch (callstate(tags)) { + case nua_callstate_received: + RESPOND(ep, call, nh, SIP_200_OK, + TAG_IF(call->sdp, SOATAG_USER_SDP_STR(call->sdp)), + TAG_END()); + return 0; + case nua_callstate_ready: + return 1; + case nua_callstate_terminated: + if (call) + nua_handle_destroy(call->nh), call->nh = NULL; + return 1; + default: + return 0; + } +} + +int ringing_until_terminated(CONDITION_PARAMS) +{ + if (!(check_handle(ep, call, nh, SIP_500_INTERNAL_SERVER_ERROR))) + return 0; + + save_event_in_list(ctx, event, ep, call); + + switch (callstate(tags)) { + case nua_callstate_received: + RESPOND(ep, call, nh, SIP_180_RINGING, TAG_END()); + return 0; + case nua_callstate_ready: + return 0; + case nua_callstate_terminated: + if (call) + nua_handle_destroy(call->nh), call->nh = NULL; + return 1; + default: + return 0; + } +} + +/* ======================================================================== */ + +int accept_and_attempt_reinvite(CONDITION_PARAMS); +int until_ready2(CONDITION_PARAMS); + +/* test_reinvite2 message sequence looks like this: + + A B + | | + |-------INVITE------>| + |<----100 Trying-----| + | | + |<----180 Ringing----| + | | + | /-INVITE-| + | \---900->| + | | + |<--------200--------| + |---------ACK------->| + : : + : queue INVITE : + : : + |-----re-INVITE----->| + |<--------200--------| + |---------ACK------->| + |-----re-INVITE----->| + |<--------200--------| + |---------ACK------->| + : : + : glare : + : : + |-----re-INVITE----->| + |<----re-INVITE------| + |<--------491--------| + |---------491------->| + |---------ACK------->| + |<--------ACK--------| + : : + |---------BYE------->| + |<--------200--------| +*/ + +int test_reinvite2(struct context *ctx) +{ + BEGIN(); + + struct endpoint *a = &ctx->a, *b = &ctx->b; + struct call *a_call = a->call, *b_call = b->call; + struct event *e; + + if (print_headings) + printf("TEST NUA-7.8.1: Test re-INVITE glare\n"); + + a_call->sdp = "m=audio 5008 RTP/AVP 0 8\n"; + b_call->sdp = "m=audio 5010 RTP/AVP 8\n"; + + TEST_1(a_call->nh = + nua_handle(a->nua, a_call, + SIPTAG_FROM_STR("Alice <sip:alice@example.com>"), + SIPTAG_TO(b->to), + TAG_END())); + INVITE(a, a_call, a_call->nh, + TAG_IF(!ctx->proxy_tests, NUTAG_URL(b->contact->m_url)), + SOATAG_USER_SDP_STR(a_call->sdp), + TAG_END()); + + run_ab_until(ctx, -1, until_ready, -1, accept_and_attempt_reinvite); + + TEST_1(nua_handle_has_active_call(a_call->nh)); + TEST_1(nua_handle_has_active_call(b_call->nh)); + + free_events_in_list(ctx, a->events); + free_events_in_list(ctx, b->events); + + /* Check that we can queue INVITEs */ + INVITE(a, a_call, a_call->nh, TAG_END()); + INVITE(a, a_call, a_call->nh, TAG_END()); + + run_ab_until(ctx, -1, until_ready2, -1, until_ready2); + + free_events_in_list(ctx, a->events); + free_events_in_list(ctx, b->events); + + /* Check that INVITE glare works */ + INVITE(a, a_call, a_call->nh, TAG_END()); + INVITE(b, b_call, b_call->nh, TAG_END()); + + a->flags.n = 0, b->flags.n = 0; + run_ab_until(ctx, -1, until_ready2, -1, until_ready2); + + free_events_in_list(ctx, a->events); + free_events_in_list(ctx, b->events); + + if (print_headings) + printf("TEST NUA-7.8.1: PASSED\n"); + + + + /* ---------------------------------------------------------------------- */ + /* + A B + |---------BYE------->| + |<--------200--------| + */ + + if (print_headings) + printf("TEST NUA-7.8.2: terminate call\n"); + + BYE(a, a_call, a_call->nh, TAG_END()); + run_ab_until(ctx, -1, until_terminated, -1, until_terminated); + + /* + Transitions of A: + READY --(T2)--> TERMINATING: nua_bye() + TERMINATING --(T3)--> TERMINATED: nua_r_bye, nua_i_state + */ + TEST_1(e = a->events->head); TEST_E(e->data->e_event, nua_r_bye); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_terminated); /* TERMINATED */ + TEST_1(!e->next); + free_events_in_list(ctx, a->events); + + /* Transitions of B: + READY -(T1)-> TERMINATED: nua_i_bye, nua_i_state + */ + TEST_1(e = b->events->head); TEST_E(e->data->e_event, nua_i_bye); + TEST(e->data->e_status, 200); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_terminated); /* TERMINATED */ + TEST_1(!e->next); + free_events_in_list(ctx, b->events); + + if (print_headings) + printf("TEST NUA-7.8.2: PASSED\n"); + + nua_handle_destroy(a_call->nh), a_call->nh = NULL; + nua_handle_destroy(b_call->nh), b_call->nh = NULL; + + END(); +} + +int accept_and_attempt_reinvite(CONDITION_PARAMS) +{ + if (!(check_handle(ep, call, nh, SIP_500_INTERNAL_SERVER_ERROR))) + return 0; + + save_event_in_list(ctx, event, ep, call); + + if (event == nua_i_prack) { + INVITE(ep, call, nh, TAG_END()); + RESPOND(ep, call, nh, SIP_200_OK, + TAG_IF(call->sdp, SOATAG_USER_SDP_STR(call->sdp)), + TAG_END()); + } + else switch (callstate(tags)) { + case nua_callstate_received: + RESPOND(ep, call, nh, SIP_180_RINGING, + SIPTAG_REQUIRE_STR("100rel"), + TAG_END()); + return 0; + case nua_callstate_early: + return 0; + case nua_callstate_ready: + return 1; + case nua_callstate_terminated: + if (call) + nua_handle_destroy(call->nh), call->nh = NULL; + return 1; + default: + return 0; + } + return 0; +} + +/* + X INVITE + | | + |-------INVITE------>| + |<--------200--------| + |---------ACK------->| +*/ +int until_ready2(CONDITION_PARAMS) +{ + if (!(check_handle(ep, call, nh, SIP_500_INTERNAL_SERVER_ERROR))) + return 0; + + save_event_in_list(ctx, event, ep, call); + + if (event == nua_r_invite && status == 491) { + if (ep == &ctx->a && ++ctx->b.flags.n >= 2) ctx->b.running = 0; + if (ep == &ctx->b && ++ctx->a.flags.n >= 2) ctx->a.running = 0; + } + + switch (callstate(tags)) { + case nua_callstate_ready: + return ++ep->flags.n >= 2; + case nua_callstate_terminated: + if (call) + nua_handle_destroy(call->nh), call->nh = NULL; + return 1; + default: + return 0; + } +} + +int invite_responded(CONDITION_PARAMS) +{ + if (!(check_handle(ep, call, nh, SIP_500_INTERNAL_SERVER_ERROR))) + return 0; + + save_event_in_list(ctx, event, ep, call); + + return event == nua_r_invite && status >= 100; +} + + +int test_reinvites(struct context *ctx) +{ + int retval = 0; + + if (print_headings) + printf("TEST NUA-7: Test call hold and re-INVITEs\n"); + + retval = test_call_hold(ctx); + + if (retval == 0) + retval = test_reinvite(ctx); + + if (retval == 0) + retval = test_reinvite2(ctx); + + if (print_headings && retval == 0) + printf("TEST NUA-7: PASSED\n"); + + return retval; +} Added: freeswitch/trunk/libs/sofia-sip/tests/test_call_reject.c ============================================================================== --- (empty file) +++ freeswitch/trunk/libs/sofia-sip/tests/test_call_reject.c Wed May 14 15:10:54 2008 @@ -0,0 +1,1689 @@ +/* + * This file is part of the Sofia-SIP package + * + * Copyright (C) 2005 Nokia Corporation. + * + * Contact: Pekka Pessi <pekka.pessi@nokia.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public License + * as published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + */ + +/**@CFILE test_call_reject.c + * @brief NUA-4 tests: call reject cases + * + * @author Pekka Pessi <Pekka.Pessi@nokia.com> + * @author Martti Mela <Martti Mela@nokia.com> + * + * @date Created: Wed Aug 17 12:12:12 EEST 2005 ppessi + */ + +#include "config.h" + +#include "test_nua.h" +#include <sofia-sip/su_tag_class.h> + +#if HAVE_FUNC +#elif HAVE_FUNCTION +#define __func__ __FUNCTION__ +#else +#define __func__ "test_reject" +#endif + +/* ======================================================================== */ +/* + A reject-1 B + | | + |-------INVITE------>| + |<----100 Trying-----| + | | + |<--------486--------| + |---------ACK------->| +*/ +int reject_1(CONDITION_PARAMS) +{ + if (!(check_handle(ep, call, nh, SIP_500_INTERNAL_SERVER_ERROR))) + return 0; + + save_event_in_list(ctx, event, ep, call); + + switch (callstate(tags)) { + case nua_callstate_received: + RESPOND(ep, call, nh, SIP_486_BUSY_HERE, TAG_END()); + return 0; + case nua_callstate_terminated: + if (call) + nua_handle_destroy(call->nh), call->nh = NULL; + return 1; + default: + return 0; + } +} + + +int test_reject_a(struct context *ctx) +{ + BEGIN(); + + struct endpoint *a = &ctx->a, *b = &ctx->b; + struct call *a_call = a->call, *b_call = b->call; + struct event *e; + sip_t const *sip; + + if (print_headings) + printf("TEST NUA-4.1: reject before ringing\n"); + + /* + A reject-1 B + | | + |-------INVITE------>| + |<----100 Trying-----| + | | + |<--------486--------| + |---------ACK------->| + */ + + a_call->sdp = "m=audio 5008 RTP/AVP 8"; + b_call->sdp = "m=audio 5010 RTP/AVP 0 8"; + + TEST_1(a_call->nh = nua_handle(a->nua, a_call, SIPTAG_TO(b->to), TAG_END())); + INVITE(a, a_call, a_call->nh, + TAG_IF(!ctx->proxy_tests, NUTAG_URL(b->contact->m_url)), + SIPTAG_SUBJECT_STR("reject-1"), + SIPTAG_CONTACT_STR("sip:a@127.0.0.1"), + SOATAG_USER_SDP_STR(a_call->sdp), + TAG_END()); + run_ab_until(ctx, -1, until_terminated, -1, reject_1); + + /* + Client transitions in reject-1: + INIT -(C1)-> CALLING -(C6a)-> TERMINATED + */ + TEST_1(e = a->events->head); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_calling); /* CALLING */ + TEST_1(is_offer_sent(e->data->e_tags)); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_r_invite); + TEST(e->data->e_status, 486); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_terminated); /* TERMINATED */ + TEST_1(!e->next); + + /* + Server transitions in reject-1: + INIT -(S1)-> RECEIVED -(S6a)-> TERMINATED + */ + TEST_1(e = b->events->head); TEST_E(e->data->e_event, nua_i_invite); + TEST(e->data->e_status, 100); + TEST_1(sip = sip_object(e->data->e_msg)); + TEST_1(sip->sip_contact); TEST_1(!sip->sip_contact->m_next); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_received); /* RECEIVED */ + TEST_1(is_offer_recv(e->data->e_tags)); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_terminated); /* TERMINATED */ + TEST_1(!e->next); + + free_events_in_list(ctx, a->events); + nua_handle_destroy(a_call->nh), a_call->nh = NULL; + free_events_in_list(ctx, b->events); + nua_handle_destroy(b_call->nh), b_call->nh = NULL; + + if (print_headings) + printf("TEST NUA-4.1: PASSED\n"); + + END(); +} + +/* + A reject-2 B + | | + |-------INVITE------>| + |<----100 Trying-----| + | | + |<----180 Ringing----| + | | + |<--------602--------| + |---------ACK------->| +*/ +int reject_2(CONDITION_PARAMS) +{ + if (!(check_handle(ep, call, nh, SIP_500_INTERNAL_SERVER_ERROR))) + return 0; + + save_event_in_list(ctx, event, ep, call); + + switch (callstate(tags)) { + case nua_callstate_received: + RESPOND(ep, call, nh, SIP_180_RINGING, TAG_END()); + return 0; + case nua_callstate_early: + RESPOND(ep, call, nh, 602, "Rejected 2", TAG_END()); + return 0; + case nua_callstate_terminated: + if (call) + nua_handle_destroy(call->nh), call->nh = NULL; + return 1; + default: + return 0; + } +} + +int test_reject_b(struct context *ctx) +{ + BEGIN(); + + struct endpoint *a = &ctx->a, *b = &ctx->b; + struct call *a_call = a->call, *b_call = b->call; + struct event *e; + + /* ------------------------------------------------------------------------ */ + /* + A reject-2 B + | | + |-------INVITE------>| + |<----100 Trying-----| + | | + |<----180 Ringing----| + | | + |<--------602--------| + |---------ACK------->| + */ + + if (print_headings) + printf("TEST NUA-4.2: reject after ringing\n"); + + a_call->sdp = "m=audio 5008 RTP/AVP 8"; + b_call->sdp = "m=audio 5010 RTP/AVP 0 8"; + + /* Make call reject-2 */ + TEST_1(a_call->nh = nua_handle(a->nua, a_call, SIPTAG_TO(b->to), TAG_END())); + INVITE(a, a_call, a_call->nh, + TAG_IF(!ctx->proxy_tests, NUTAG_URL(b->contact->m_url)), + SIPTAG_SUBJECT_STR("reject-2"), + SOATAG_USER_SDP_STR(a_call->sdp), + TAG_END()); + run_ab_until(ctx, -1, until_terminated, -1, reject_2); + + /* + Client transitions in reject-2: + INIT -(C1)-> CALLING -(C2)-> PROCEEDING -(C6b)-> TERMINATED + */ + TEST_1(e = a->events->head); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_calling); /* CALLING */ + TEST_1(is_offer_sent(e->data->e_tags)); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_r_invite); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_proceeding); /* PROCEEDING */ + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_r_invite); + TEST(e->data->e_status, 602); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_terminated); /* TERMINATED */ + TEST_1(!e->next); + + /* + Server transitions in reject-2: + INIT -(S1)-> RECEIVED -(S2)-> EARLY -(S6a)-> TERMINATED + */ + TEST_1(e = b->events->head); TEST_E(e->data->e_event, nua_i_invite); + TEST(e->data->e_status, 100); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_received); /* RECEIVED */ + TEST_1(is_offer_recv(e->data->e_tags)); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_early); /* EARLY */ + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_terminated); /* TERMINATED */ + TEST_1(!e->next); + + free_events_in_list(ctx, a->events); + nua_handle_destroy(a_call->nh), a_call->nh = NULL; + free_events_in_list(ctx, b->events); + nua_handle_destroy(b_call->nh), b_call->nh = NULL; + + if (print_headings) + printf("TEST NUA-4.2: PASSED\n"); + + END(); +} + +/* ------------------------------------------------------------------------ */ + +int reject_302(CONDITION_PARAMS), reject_305(CONDITION_PARAMS); +int reject_500_retry_after(CONDITION_PARAMS); +int redirect_always(CONDITION_PARAMS); +int reject_604(CONDITION_PARAMS); + +/* + A reject-302 B + | | + |-------INVITE------>| + |<----100 Trying-----| + |<-----302 Other-----| + |--------ACK-------->| + | | + |-------INVITE------>| + |<----100 Trying-----| + |<--305 Use Proxy----| + |--------ACK-------->| + | | + |-------INVITE------>| + |<----100 Trying-----| + |<----500 Retry------| + |--------ACK-------->| + | | + |-------INVITE------>| + |<----100 Trying-----| + | | + |<----180 Ringing----| + | | + |<---604 Nowhere-----| + |--------ACK-------->| +*/ + +int reject_302(CONDITION_PARAMS) +{ + if (!(check_handle(ep, call, nh, SIP_500_INTERNAL_SERVER_ERROR))) + return 0; + + save_event_in_list(ctx, event, ep, call); + + switch (callstate(tags)) { + case nua_callstate_received: + { + sip_contact_t m[1]; + *m = *ep->contact; + m->m_url->url_user = "302"; + RESPOND(ep, call, nh, SIP_302_MOVED_TEMPORARILY, + SIPTAG_CONTACT(m), TAG_END()); + } + return 0; + case nua_callstate_terminated: + if (call) + nua_handle_destroy(call->nh), call->nh = NULL; + ep->next_condition = reject_305; + return 0; + default: + return 0; + } +} + +int reject_305(CONDITION_PARAMS) +{ + if (!(check_handle(ep, call, nh, SIP_500_INTERNAL_SERVER_ERROR))) + return 0; + + save_event_in_list(ctx, event, ep, call); + + switch (callstate(tags)) { + case nua_callstate_received: + { + sip_contact_t m[1]; + *m = *ep->contact; + m->m_url->url_user = "305"; + m->m_url->url_params = "lr=1"; + RESPOND(ep, call, nh, SIP_305_USE_PROXY, SIPTAG_CONTACT(m), TAG_END()); + } + return 0; + case nua_callstate_terminated: + if (call) + nua_handle_destroy(call->nh), call->nh = NULL; + ep->next_condition = reject_500_retry_after; + return 0; + default: + return 0; + } +} + +int reject_500_retry_after(CONDITION_PARAMS) +{ + if (!(check_handle(ep, call, nh, SIP_500_INTERNAL_SERVER_ERROR))) + return 0; + + save_event_in_list(ctx, event, ep, call); + + if (event == nua_i_invite) { + sip_retry_after_t af[1]; + sip_retry_after_init(af)->af_delta = 1; + RESPOND(ep, call, nh, 500, "Retry After", SIPTAG_RETRY_AFTER(af), TAG_END()); + } + else if (event == nua_i_state) switch (callstate(tags)) { + case nua_callstate_terminated: + if (call) + nua_handle_destroy(call->nh), call->nh = NULL; + ep->next_condition = reject_604; + break; + default: + break; + } + + return 0; +} + +int reject_604(CONDITION_PARAMS) +{ + if (!(check_handle(ep, call, nh, SIP_500_INTERNAL_SERVER_ERROR))) + return 0; + + save_event_in_list(ctx, event, ep, call); + + switch (callstate(tags)) { + case nua_callstate_received: + RESPOND(ep, call, nh, SIP_180_RINGING, TAG_END()); + return 0; + case nua_callstate_early: + RESPOND(ep, call, nh, SIP_604_DOES_NOT_EXIST_ANYWHERE, TAG_END()); + return 0; + case nua_callstate_terminated: + if (call) + nua_handle_destroy(call->nh), call->nh = NULL; + return 1; + default: + return 0; + } +} + +int redirect_always(CONDITION_PARAMS) +{ + if (!(check_handle(ep, call, nh, SIP_500_INTERNAL_SERVER_ERROR))) + return 0; + + if (event == nua_i_invite) { + char user[30]; + sip_contact_t m[1]; + *m = *ep->contact; + snprintf(user, sizeof user, "user-%u", ep->flags.n++); + m->m_url->url_user = user; + RESPOND(ep, call, nh, SIP_302_MOVED_TEMPORARILY, + SIPTAG_CONTACT(m), TAG_END()); + nua_handle_destroy(nh); + call->nh = NULL; + return 1; + } + + return 0; +} + + +int test_reject_302(struct context *ctx) +{ + BEGIN(); + + struct endpoint *a = &ctx->a, *b = &ctx->b; + struct call *a_call = a->call, *b_call = b->call; + struct event *e; + sip_t const *sip; + + /* Make call reject-3 */ + if (print_headings) + printf("TEST NUA-4.3: redirect then reject\n"); + + a_call->sdp = "m=audio 5008 RTP/AVP 8"; + b_call->sdp = "m=audio 5010 RTP/AVP 0 8"; + + TEST_1(a_call->nh = nua_handle(a->nua, a_call, SIPTAG_TO(b->to), TAG_END())); + INVITE(a, a_call, a_call->nh, + TAG_IF(!ctx->proxy_tests, NUTAG_URL(b->contact->m_url)), + SIPTAG_SUBJECT_STR("reject-3"), + SOATAG_USER_SDP_STR(a_call->sdp), + TAG_END()); + run_ab_until(ctx, -1, until_terminated, -1, reject_302); + + /* + A reject-3 B + | | + |-------INVITE------>| + |<----100 Trying-----| + | | + |<-----302 Other-----| + |--------ACK-------->| + | | + |-------INVITE------>| + |<----100 Trying-----| + |<---305 Use Proxy---| + |--------ACK-------->| + | | + |-------INVITE------>| + |<----100 Trying-----| + |<-----500 Retry-----| + |--------ACK-------->| + | | + | | + |-------INVITE------>| + |<----100 Trying-----| + | | + |<----180 Ringing----| + | | + |<---604 Nowhere-----| + |--------ACK-------->| + */ + + /* + Client transitions in reject-3: + INIT -(C1)-> PROCEEDING -(C6a)-> TERMINATED/INIT + INIT -(C1)-> CALLING -(C2)-> PROCEEDING -(C6b)-> TERMINATED + */ + + TEST_1(e = a->events->head); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_calling); /* CALLING */ + TEST_1(is_offer_sent(e->data->e_tags)); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_r_invite); + TEST(e->data->e_status, 100); + TEST(sip_object(e->data->e_msg)->sip_status->st_status, 302); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_calling); /* CALLING */ + TEST_1(is_offer_sent(e->data->e_tags)); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_r_invite); + TEST(e->data->e_status, 100); + TEST(sip_object(e->data->e_msg)->sip_status->st_status, 305); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_calling); /* CALLING */ + TEST_1(is_offer_sent(e->data->e_tags)); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_r_invite); + TEST(e->data->e_status, 100); + TEST(sip_object(e->data->e_msg)->sip_status->st_status, 500); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_calling); /* CALLING */ + TEST_1(is_offer_sent(e->data->e_tags)); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_r_invite); + TEST(e->data->e_status, 180); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_proceeding); /* PROCEEDING */ + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_r_invite); + TEST(e->data->e_status, 604); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_terminated); /* TERMINATED */ + TEST_1(!e->next); + + /* + Server transitions: + INIT -(S1)-> RECEIVED -(S6a)-> TERMINATED/INIT + INIT -(S1)-> RECEIVED -(S2)-> EARLY -(S6b)-> TERMINATED + */ + TEST_1(e = b->events->head); TEST_E(e->data->e_event, nua_i_invite); + TEST(e->data->e_status, 100); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_received); /* RECEIVED */ + TEST_1(is_offer_recv(e->data->e_tags)); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_terminated); /* TERMINATED */ + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_invite); + TEST(e->data->e_status, 100); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_received); /* RECEIVED */ + TEST_1(is_offer_recv(e->data->e_tags)); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_terminated); /* TERMINATED */ + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_invite); + TEST(e->data->e_status, 100); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_received); /* RECEIVED */ + TEST_1(is_offer_recv(e->data->e_tags)); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_terminated); /* TERMINATED */ + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_invite); + TEST(e->data->e_status, 100); + TEST_1(sip = sip_object(e->data->e_msg)); + TEST_1(sip->sip_request); + TEST_S(sip->sip_request->rq_url->url_user, "302"); + TEST_1(sip->sip_route); + TEST_S(sip->sip_route->r_url->url_user, "305"); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_received); /* RECEIVED */ + TEST_1(is_offer_recv(e->data->e_tags)); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_early); /* EARLY */ + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_terminated); /* TERMINATED */ + TEST_1(!e->next); + + free_events_in_list(ctx, a->events); + nua_handle_destroy(a_call->nh), a_call->nh = NULL; + free_events_in_list(ctx, b->events); + nua_handle_destroy(b_call->nh), b_call->nh = NULL; + + if (print_headings) + printf("TEST NUA-4.3: PASSED\n"); + + if (print_headings) + printf("TEST NUA-4.3.1: redirect until retry count is exceeded\n"); + + TEST_1(a_call->nh = nua_handle(a->nua, a_call, SIPTAG_TO(b->to), TAG_END())); + INVITE(a, a_call, a_call->nh, + TAG_IF(!ctx->proxy_tests, NUTAG_URL(b->contact->m_url)), + SIPTAG_SUBJECT_STR("redirect always"), + SOATAG_USER_SDP_STR(a_call->sdp), + TAG_END()); + run_ab_until(ctx, -1, until_terminated, -1, redirect_always); + + free_events_in_list(ctx, a->events); + nua_handle_destroy(a_call->nh), a_call->nh = NULL; + free_events_in_list(ctx, b->events); + nua_handle_destroy(b_call->nh), b_call->nh = NULL; + + if (print_headings) + printf("TEST NUA-4.3: PASSED\n"); + + END(); +} + +/* ------------------------------------------------------------------------ */ + +/* Reject call with 407, then 401 */ + +int reject_407(CONDITION_PARAMS); +int reject_401(CONDITION_PARAMS); +int authenticate_call(CONDITION_PARAMS); +int reject_403(CONDITION_PARAMS); + +/* + A reject-401 B + | | + |-------INVITE------>| + |<----100 Trying-----| + |<--------407--------| + |---------ACK------->| + | | + |-------INVITE------>| + |<----100 Trying-----| + | | + |<----180 Ringing----| + | | + |<--------401--------| + |---------ACK------->| + | | + |-------INVITE------>| + |<----100 Trying-----| + |<-------403---------| + |--------ACK-------->| +*/ + +int reject_407(CONDITION_PARAMS) +{ + if (!(check_handle(ep, call, nh, SIP_500_INTERNAL_SERVER_ERROR))) + return 0; + + save_event_in_list(ctx, event, ep, call); + + switch (callstate(tags)) { + case nua_callstate_received: + RESPOND(ep, call, nh, SIP_407_PROXY_AUTH_REQUIRED, + SIPTAG_PROXY_AUTHENTICATE_STR("Digest realm=\"test_nua\", " + "nonce=\"nsdhfuds\", algorithm=MD5, " + "qop=\"auth-int\""), + TAG_END()); + return 0; + case nua_callstate_terminated: + if (call) + nua_handle_destroy(call->nh), call->nh = NULL; + ep->next_condition = reject_401; + return 0; + default: + return 0; + } +} + +int reject_401(CONDITION_PARAMS) +{ + if (!(check_handle(ep, call, nh, SIP_500_INTERNAL_SERVER_ERROR))) + return 0; + + save_event_in_list(ctx, event, ep, call); + + switch (callstate(tags)) { + case nua_callstate_received: + RESPOND(ep, call, nh, SIP_180_RINGING, TAG_END()); + return 0; + case nua_callstate_early: + RESPOND(ep, call, nh, SIP_401_UNAUTHORIZED, + SIPTAG_WWW_AUTHENTICATE_STR("Digest realm=\"test_nua\", " + "nonce=\"nsdhfuds\", algorithm=MD5, " + "qop=\"auth\""), + TAG_END()); + return 0; + case nua_callstate_terminated: + if (call) + nua_handle_destroy(call->nh), call->nh = NULL; + ep->next_condition = reject_403; + return 0; + default: + return 0; + } +} + +int reject_403(CONDITION_PARAMS) +{ + if (!(check_handle(ep, call, nh, SIP_500_INTERNAL_SERVER_ERROR))) + return 0; + + save_event_in_list(ctx, event, ep, call); + + switch (callstate(tags)) { + case nua_callstate_received: + RESPOND(ep, call, nh, SIP_403_FORBIDDEN, TAG_END()); + return 0; + case nua_callstate_terminated: + if (call) + nua_handle_destroy(call->nh), call->nh = NULL; + ep->next_condition = NULL; + return 1; + default: + return 0; + } +} + +int authenticate_call(CONDITION_PARAMS) +{ + if (!(check_handle(ep, call, nh, SIP_500_INTERNAL_SERVER_ERROR))) + return 0; + + save_event_in_list(ctx, event, ep, call); + + if (event == nua_r_invite && status == 401) { + AUTHENTICATE(ep, call, nh, NUTAG_AUTH("Digest:\"test_nua\":jaska:secret"), + SIPTAG_SUBJECT_STR("Got 401"), + TAG_END()); + return 0; + } + + if (event == nua_r_invite && status == 407) { + AUTHENTICATE(ep, call, nh, NUTAG_AUTH("Digest:\"test_nua\":erkki:secret"), + SIPTAG_SUBJECT_STR("Got 407"), + TAG_END()); + return 0; + } + + switch (callstate(tags)) { + case nua_callstate_terminated: + if (call) + nua_handle_destroy(call->nh), call->nh = NULL; + return 1; + default: + return 0; + } +} + +int test_reject_401(struct context *ctx) +{ + BEGIN(); + + struct endpoint *a = &ctx->a, *b = &ctx->b; + struct call *a_call = a->call, *b_call = b->call; + struct event const *e; + sip_t const *sip; + + if (print_headings) + printf("TEST NUA-4.4: challenge then reject\n"); + + a_call->sdp = "m=audio 5008 RTP/AVP 8"; + b_call->sdp = "m=audio 5010 RTP/AVP 0 8"; + + TEST_1(a_call->nh = nua_handle(a->nua, a_call, SIPTAG_TO(b->to), TAG_END())); + INVITE(a, a_call, a_call->nh, + TAG_IF(!ctx->proxy_tests, NUTAG_URL(b->contact->m_url)), + SIPTAG_SUBJECT_STR("reject-401"), + SOATAG_USER_SDP_STR(a_call->sdp), + TAG_END()); + run_ab_until(ctx, -1, authenticate_call, -1, reject_407); + + /* + Client transitions in reject-3: + INIT -(C1)-> CALLING -(C2)-> PROCEEDING -(C6b)-> TERMINATED/INIT + INIT -(C1)-> CALLING -(C6a)-> TERMINATED + */ + TEST_1(e = a->events->head); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_calling); /* CALLING */ + TEST_1(is_offer_sent(e->data->e_tags)); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_r_invite); + TEST(sip_object(e->data->e_msg)->sip_status->st_status, 407); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_calling); /* CALLING */ + TEST_1(is_offer_sent(e->data->e_tags)); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_r_invite); + TEST(e->data->e_status, 180); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_proceeding); /* PROCEEDING */ + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_r_invite); + TEST(e->data->e_status, 401); + TEST(sip_object(e->data->e_msg)->sip_status->st_status, 401); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_calling); /* CALLING */ + TEST_1(is_offer_sent(e->data->e_tags)); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_r_invite); + TEST(e->data->e_status, 403); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_terminated); /* TERMINATED */ + TEST_1(!e->next); + + free_events_in_list(ctx, a->events); + + /* + Server transitions: + INIT -(S1)-> RECEIVED -(S6a)-> TERMINATED/INIT + INIT -(S1)-> RECEIVED -(S2)-> EARLY -(S6b)-> TERMINATED/INIT + INIT -(S1)-> RECEIVED -(S6a)-> TERMINATED + */ + TEST_1(e = b->events->head); TEST_E(e->data->e_event, nua_i_invite); + TEST_1(sip = sip_object(e->data->e_msg)); + TEST_1(sip->sip_subject); + TEST_S(sip->sip_subject->g_value, "reject-401"); + TEST(e->data->e_status, 100); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_received); /* RECEIVED */ + TEST_1(is_offer_recv(e->data->e_tags)); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_terminated); /* TERMINATED */ + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_invite); + TEST(e->data->e_status, 100); + TEST_1(sip = sip_object(e->data->e_msg)); + TEST_1(sip->sip_proxy_authorization); + /* Ensure that nua_authenticate() tags get added to the request */ + TEST_1(sip->sip_subject); + TEST_S(sip->sip_subject->g_value, "Got 407"); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_received); /* RECEIVED */ + TEST_1(is_offer_recv(e->data->e_tags)); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_early); /* EARLY */ + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_terminated); /* TERMINATED */ + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_invite); + TEST_1(sip = sip_object(e->data->e_msg)); + TEST_1(sip->sip_subject); + TEST_S(sip->sip_subject->g_value, "Got 401"); + TEST_1(sip->sip_authorization); + TEST_1(sip->sip_proxy_authorization); + TEST(e->data->e_status, 100); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_received); /* RECEIVED */ + TEST_1(is_offer_recv(e->data->e_tags)); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_terminated); /* TERMINATED */ + TEST_1(!e->next); + + free_events_in_list(ctx, b->events); + + nua_handle_destroy(a_call->nh), a_call->nh = NULL; + nua_handle_destroy(b_call->nh), b_call->nh = NULL; + + if (print_headings) + printf("TEST NUA-4.4: PASSED\n"); + + END(); +} + +/* ------------------------------------------------------------------------ */ + +/* Reject call with 401 and bad challenge */ + +/* + A reject-401-aka B + | | + |-------INVITE------>| + |<----100 Trying-----| + |<--------401--------| + |---------ACK------->| +*/ + +int reject_401_aka(CONDITION_PARAMS) +{ + if (!(check_handle(ep, call, nh, SIP_500_INTERNAL_SERVER_ERROR))) + return 0; + + save_event_in_list(ctx, event, ep, call); + + switch (callstate(tags)) { + case nua_callstate_received: + RESPOND(ep, call, nh, SIP_401_UNAUTHORIZED, + /* Send a challenge that we do not grok */ + SIPTAG_WWW_AUTHENTICATE_STR("Digest realm=\"test_nua\", " + "nonce=\"nsdhfuds\", algorithm=SHA0-AKAv6, " + "qop=\"auth\""), + TAG_END()); + return 0; + case nua_callstate_terminated: + if (call) + nua_handle_destroy(call->nh), call->nh = NULL; + return 1; + default: + return 0; + } +} + +int test_reject_401_aka(struct context *ctx) +{ + BEGIN(); + + struct endpoint *a = &ctx->a, *b = &ctx->b; + struct call *a_call = a->call, *b_call = b->call; + struct event const *e; + sip_t const *sip; + + if (print_headings) + printf("TEST NUA-4.6.1: invalid challenge \n"); + + a_call->sdp = "m=audio 5008 RTP/AVP 8"; + b_call->sdp = "m=audio 5010 RTP/AVP 0 8"; + + TEST_1(a_call->nh = nua_handle(a->nua, a_call, SIPTAG_TO(b->to), TAG_END())); + INVITE(a, a_call, a_call->nh, + TAG_IF(!ctx->proxy_tests, NUTAG_URL(b->contact->m_url)), + SIPTAG_SUBJECT_STR("reject-401-aka"), + SOATAG_USER_SDP_STR(a_call->sdp), + TAG_END()); + + run_ab_until(ctx, -1, until_terminated, -1, reject_401_aka); + + /* + Client transitions + INIT -(C1)-> CALLING -(C6a)-> TERMINATED/INIT + */ + TEST_1(e = a->events->head); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_calling); /* CALLING */ + TEST_1(is_offer_sent(e->data->e_tags)); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_r_invite); + TEST(sip_object(e->data->e_msg)->sip_status->st_status, 401); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_terminated); /* TERMINATED */ + TEST_1(!e->next); + + free_events_in_list(ctx, a->events); + + /* + Server transitions: + INIT -(S1)-> RECEIVED -(S6a)-> TERMINATED + */ + TEST_1(e = b->events->head); TEST_E(e->data->e_event, nua_i_invite); + TEST_1(sip = sip_object(e->data->e_msg)); + TEST(e->data->e_status, 100); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_received); /* RECEIVED */ + TEST_1(is_offer_recv(e->data->e_tags)); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_terminated); /* TERMINATED */ + TEST_1(!e->next); + + free_events_in_list(ctx, b->events); + + nua_handle_destroy(a_call->nh), a_call->nh = NULL; + nua_handle_destroy(b_call->nh), b_call->nh = NULL; + + if (print_headings) + printf("TEST NUA-4.6.1: PASSED\n"); + + END(); +} + + +/* ------------------------------------------------------------------------ */ + +/* Reject call with 401, twice */ + +/* + A reject-401-bad B + | | + |-------INVITE------>| + |<----100 Trying-----| + |<--------401--------| + |---------ACK------->| + | | + |-------INVITE------>| + |<----100 Trying-----| + |<--------401--------| + |---------ACK------->| +*/ + +int reject_401_bad(CONDITION_PARAMS) +{ + if (!(check_handle(ep, call, nh, SIP_500_INTERNAL_SERVER_ERROR))) + return 0; + + save_event_in_list(ctx, event, ep, call); + + switch (callstate(tags)) { + case nua_callstate_received: + RESPOND(ep, call, nh, SIP_401_UNAUTHORIZED, + /* Send a challenge that we do not grok */ + SIPTAG_WWW_AUTHENTICATE_STR("Digest realm=\"No hope\", " + "nonce=\"goO541ftNrw327aWpu2\", " + "algorithm=MD5, " + "qop=\"auth\""), + TAG_END()); + return 0; + case nua_callstate_terminated: + if (call) + nua_handle_destroy(call->nh), call->nh = NULL; + if (ep->flags.bit0) /* Terminate 2 calls */ + return 1; + ep->flags.bit0 = 1; + return 0; + default: + return 0; + } +} + +int authenticate_bad(CONDITION_PARAMS) +{ + if (!(check_handle(ep, call, nh, SIP_500_INTERNAL_SERVER_ERROR))) + return 0; + + save_event_in_list(ctx, event, ep, call); + + if (event == nua_r_invite && status == 401) { + AUTHENTICATE(ep, call, nh, NUTAG_AUTH("Digest:\"No hope\":jaska:secret"), + SIPTAG_SUBJECT_STR("Bad password"), + TAG_END()); + return 0; + } + + switch (callstate(tags)) { + case nua_callstate_terminated: + if (call) + nua_handle_destroy(call->nh), call->nh = NULL; + return 1; + default: + return 0; + } +} + + +int test_reject_401_bad(struct context *ctx) +{ + BEGIN(); + + struct endpoint *a = &ctx->a, *b = &ctx->b; + struct call *a_call = a->call, *b_call = b->call; + struct event const *e; + sip_t const *sip; + + if (print_headings) + printf("TEST NUA-4.6.2: bad username/password\n"); + + a_call->sdp = "m=audio 5008 RTP/AVP 8"; + + TEST_1(a_call->nh = nua_handle(a->nua, a_call, SIPTAG_TO(b->to), TAG_END())); + INVITE(a, a_call, a_call->nh, + TAG_IF(!ctx->proxy_tests, NUTAG_URL(b->contact->m_url)), + SIPTAG_SUBJECT_STR("reject-401-bad"), + SOATAG_USER_SDP_STR(a_call->sdp), + TAG_END()); + + + run_ab_until(ctx, -1, authenticate_bad, -1, reject_401_bad); + + /* + Client transitions + INIT -(C1)-> CALLING -(C6a)-> TERMINATED/INIT + */ + TEST_1(e = a->events->head); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_calling); /* CALLING */ + TEST_1(is_offer_sent(e->data->e_tags)); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_r_invite); + TEST(sip_object(e->data->e_msg)->sip_status->st_status, 401); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_calling); /* CALLING */ + TEST_1(is_offer_sent(e->data->e_tags)); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_r_invite); + TEST(sip_object(e->data->e_msg)->sip_status->st_status, 401); + /* nua_authenticate() fails and INVITE returns an internal error response */ + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_r_invite); + TEST(e->data->e_status, 904); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_terminated); /* TERMINATED */ + TEST_1(!e->next); + + free_events_in_list(ctx, a->events); + + /* + Server transitions: + INIT -(S1)-> RECEIVED -(S6a)-> TERMINATED + */ + TEST_1(e = b->events->head); TEST_E(e->data->e_event, nua_i_invite); + TEST_1(sip = sip_object(e->data->e_msg)); + TEST(e->data->e_status, 100); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_received); /* RECEIVED */ + TEST_1(is_offer_recv(e->data->e_tags)); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_terminated); /* TERMINATED */ + + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_invite); + TEST_1(sip = sip_object(e->data->e_msg)); + TEST(e->data->e_status, 100); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_received); /* RECEIVED */ + TEST_1(is_offer_recv(e->data->e_tags)); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_terminated); /* TERMINATED */ + TEST_1(!e->next); + + free_events_in_list(ctx, b->events); + + nua_handle_destroy(a_call->nh), a_call->nh = NULL; + nua_handle_destroy(b_call->nh), b_call->nh = NULL; + + if (print_headings) + printf("TEST NUA-4.6.2: PASSED\n"); + + END(); +} + + +/* ---------------------------------------------------------------------- */ + +int test_mime_negotiation(struct context *ctx) +{ + BEGIN(); + + struct endpoint *a = &ctx->a, *b = &ctx->b; + struct call *a_call = a->call, *b_call = b->call; + struct event *e; + sip_t const *sip; + + /* Make call reject-3 */ + if (print_headings) + printf("TEST NUA-4.5: check for rejections of invalid requests\n"); + + a_call->sdp = "m=audio 5008 RTP/AVP 8"; + b_call->sdp = "m=audio 5010 RTP/AVP 0 8"; + + TEST_1(a_call->nh = nua_handle(a->nua, a_call, SIPTAG_TO(b->to), TAG_END())); + + if (print_headings) + printf("TEST NUA-4.5.1: invalid Content-Type\n"); + + INVITE(a, a_call, a_call->nh, + TAG_IF(!ctx->proxy_tests, NUTAG_URL(b->contact->m_url)), + SIPTAG_SUBJECT_STR("reject-3"), + SOATAG_USER_SDP_STR(a_call->sdp), + SIPTAG_CONTENT_TYPE_STR("application/xyzzy+xml"), + SIPTAG_CONTENT_DISPOSITION_STR("session;required"), + SIPTAG_PAYLOAD_STR("m=audio 5008 RTP/AVP 8\n"), + TAG_END()); + run_ab_until(ctx, -1, until_terminated, -1, NULL); + + /* + A reject-5.1 B + | | + |-------INVITE------>| + |<-------415---------| + |--------ACK-------->| + */ + + /* + Client transitions in reject-3: + INIT -(C1)-> CALLING -(C6a)-> TERMINATED + */ + + TEST_1(e = a->events->head); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_calling); /* CALLING */ + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_r_invite); + TEST(e->data->e_status, 415); + TEST_1(sip = sip_object(e->data->e_msg)); + TEST(sip->sip_status->st_status, 415); + TEST_1(sip->sip_accept); + TEST_S(sip->sip_accept->ac_type, "application/sdp"); + TEST_1(sip->sip_accept_encoding); + /* No content-encoding is supported */ + TEST_S(sip->sip_accept_encoding->aa_value, ""); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_terminated); /* CALLING */ + TEST_1(!e->next); + + free_events_in_list(ctx, a->events); + + if (print_headings) + printf("TEST NUA-4.5.1: PASSED\n"); + + if (print_headings) + printf("TEST NUA-4.5.2: invalid Content-Encoding\n"); + + /* + A reject-5.2 B + | | + |-------INVITE------>| + |<-------415---------| + |--------ACK-------->| + */ + + INVITE(a, a_call, a_call->nh, + TAG_IF(!ctx->proxy_tests, NUTAG_URL(b->contact->m_url)), + SIPTAG_SUBJECT_STR("reject-5"), + SOATAG_USER_SDP_STR(a_call->sdp), + SIPTAG_CONTENT_ENCODING_STR("zyxxy"), + TAG_END()); + run_ab_until(ctx, -1, until_terminated, -1, NULL); + + /* + Client transitions in reject-3: + INIT -(C1)-> CALLING -(C6a)-> TERMINATED + */ + + TEST_1(e = a->events->head); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_calling); /* CALLING */ + TEST_1(is_offer_sent(e->data->e_tags)); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_r_invite); + TEST(e->data->e_status, 415); + TEST_1(sip = sip_object(e->data->e_msg)); + TEST(sip->sip_status->st_status, 415); + TEST_1(sip->sip_accept); + TEST_S(sip->sip_accept->ac_type, "application/sdp"); + TEST_1(sip->sip_accept_encoding); + /* No content-encoding is supported */ + TEST_S(sip->sip_accept_encoding->aa_value, ""); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_terminated); /* TERMINATED */ + TEST_1(!e->next); + + free_events_in_list(ctx, a->events); + + if (print_headings) + printf("TEST NUA-4.5.2: PASSED\n"); + + if (print_headings) + printf("TEST NUA-4.5.3: invalid Accept\n"); + + INVITE(a, a_call, a_call->nh, + TAG_IF(!ctx->proxy_tests, NUTAG_URL(b->contact->m_url)), + SIPTAG_SUBJECT_STR("reject-3"), + SOATAG_USER_SDP_STR(a_call->sdp), + SIPTAG_ACCEPT_STR("application/xyzzy+xml"), + TAG_END()); + run_ab_until(ctx, -1, until_terminated, -1, NULL); + + + /* + A reject-5.3 B + | | + |-------INVITE------>| + |<-------406---------| + |--------ACK-------->| + */ + + /* + Client transitions in reject-3: + INIT -(C1)-> CALLING -(C6a)-> TERMINATED + */ + + TEST_1(e = a->events->head); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_calling); /* CALLING */ + TEST_1(is_offer_sent(e->data->e_tags)); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_r_invite); + TEST(e->data->e_status, 406); + TEST_1(sip = sip_object(e->data->e_msg)); + TEST(sip->sip_status->st_status, 406); + TEST_1(sip->sip_accept); + TEST_S(sip->sip_accept->ac_type, "application/sdp"); + TEST_1(sip->sip_accept_encoding); + /* No content-encoding is supported */ + TEST_S(sip->sip_accept_encoding->aa_value, ""); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_terminated); /* TERMINATED */ + TEST_1(!e->next); + + free_events_in_list(ctx, a->events); + + if (print_headings) + printf("TEST NUA-4.5.3: PASSED\n"); + + nua_handle_destroy(a_call->nh), a_call->nh = NULL; + + free_events_in_list(ctx, b->events); + + if (print_headings) + printf("TEST NUA-4.5: PASSED\n"); + + END(); +} + +/* ---------------------------------------------------------------------- */ + +size_t filter_200_OK(void *arg, void *message, size_t len) +{ + (void)arg; + + if (len >= 11 && strncasecmp(message, "SIP/2.0 200", 11) == 0) + return 0; + return len; +} + +size_t filter_ACK(void *arg, void *message, size_t len) +{ + (void)arg; + + if (len >= 7 && strncasecmp(message, "ACK sip", 7) == 0) + return 0; + return len; +} + +int call_with_bad_ack(CONDITION_PARAMS); +int accept_call_with_bad_contact(CONDITION_PARAMS); + +int test_call_timeouts(struct context *ctx) +{ + BEGIN(); + + struct endpoint *a = &ctx->a, *b = &ctx->b; + struct call *a_call = a->call, *b_call = b->call; + struct event *e; + struct nat_filter *f, *f2; + + if (print_headings) + printf("TEST NUA-4.7: check for error and timeout handling\n"); + + a_call->sdp = "m=audio 5008 RTP/AVP 8"; + b_call->sdp = "m=audio 5010 RTP/AVP 0 8"; + + if (!ctx->nat) + goto completed_4_7_1; + + if (print_headings) + printf("TEST NUA-4.7.1: ACK timeout (200 OK filtered)\n"); + + TEST_1(a_call->nh = nua_handle(a->nua, a_call, SIPTAG_TO(b->to), TAG_END())); + + TEST_1(f = test_nat_add_filter(ctx->nat, filter_200_OK, NULL, nat_inbound)); + TEST_1(f2 = test_nat_add_filter(ctx->nat, filter_200_OK, + NULL, nat_outbound)); + + INVITE(a, a_call, a_call->nh, + TAG_IF(!ctx->proxy_tests, NUTAG_URL(b->contact->m_url)), + SIPTAG_SUBJECT_STR("NUA-4.7.1"), + SOATAG_USER_SDP_STR(a_call->sdp), + TAG_END()); + run_ab_until(ctx, -1, until_terminated, -1, accept_call); + + /* + A accept_call B + | | + |-------INVITE------>| + |<----100 Trying-----| + | | + |<----180 Ringing----| + | | + | X-----200--------| + | X-----200--------| + | X-----200--------| + | | + |<--------BYE--------| + |--------200 OK---X | + + */ + + /* + Client transitions: + */ + + TEST_1(e = a->events->head); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_calling); /* CALLING */ + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_r_invite); + TEST(e->data->e_status, 180); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_proceeding); /* PROCEEDING */ + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_bye); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_terminated); /* TERMINATED */ + TEST_1(!e->next); + + /* + Server transitions: + -(S1)-> RECEIVED -(S2a)-> EARLY -(S3b)-> COMPLETED -(S5)-> TERMINATING + -(S10)-> TERMINATED -X + */ + TEST_1(e = b->events->head); TEST_E(e->data->e_event, nua_i_invite); + TEST(e->data->e_status, 100); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_received); /* RECEIVED */ + TEST_1(is_offer_recv(e->data->e_tags)); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_early); /* EARLY */ + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_completed); /* COMPLETED */ + TEST_1(is_answer_sent(e->data->e_tags)); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_error); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_terminating); /* TERMINATING */ + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_r_bye); + TEST(e->data->e_status, 408); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_terminated); /* TERMINATED */ + TEST_1(!e->next); + + free_events_in_list(ctx, a->events); + nua_handle_destroy(a_call->nh), a_call->nh = NULL; + free_events_in_list(ctx, b->events); + nua_handle_destroy(b_call->nh), b_call->nh = NULL; + + TEST_1(test_nat_remove_filter(ctx->nat, f) == 0); + TEST_1(test_nat_remove_filter(ctx->nat, f2) == 0); + + if (print_headings) + printf("TEST NUA-4.7.1: PASSED\n"); + + completed_4_7_1: + + if (!ctx->nat) + goto completed_4_7_2; + + if (print_headings) + printf("TEST NUA-4.7.2: ACK timeout (ACK filtered)\n"); + + TEST_1(a_call->nh = nua_handle(a->nua, a_call, SIPTAG_TO(b->to), TAG_END())); + + TEST_1(f = test_nat_add_filter(ctx->nat, filter_ACK, NULL, nat_outbound)); + + INVITE(a, a_call, a_call->nh, + TAG_IF(!ctx->proxy_tests, NUTAG_URL(b->contact->m_url)), + SIPTAG_SUBJECT_STR("NUA-4.7.2"), + SOATAG_USER_SDP_STR(a_call->sdp), + TAG_END()); + run_ab_until(ctx, -1, until_terminated, -1, accept_call); + + /* + A accept_call B + | | + |-------INVITE------>| + |<----100 Trying-----| + | | + |<----180 Ringing----| + | | + |<--------200--------| + |--------ACK-----X | + | | + |<--------200--------| + |--------ACK-----X | + | | + |<--------200--------| + |--------ACK-----X | + | | + |<--------BYE--------| + |--------200 OK----->| + + */ + + /* + */ + + TEST_1(e = a->events->head); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_calling); /* CALLING */ + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_r_invite); + TEST(e->data->e_status, 180); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_proceeding); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_r_invite); + TEST(e->data->e_status, 200); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_ready); /* READY */ + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_bye); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_terminated); + TEST_1(!e->next); + + /* + Server transitions: + -(S1)-> RECEIVED -(S2a)-> EARLY -(S3b)-> COMPLETED -(S5)-> TERMINATING + -(S10)-> TERMINATED -X + */ + TEST_1(e = b->events->head); TEST_E(e->data->e_event, nua_i_invite); + TEST(e->data->e_status, 100); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_received); /* RECEIVED */ + TEST_1(is_offer_recv(e->data->e_tags)); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_early); /* EARLY */ + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_completed); /* COMPLETED */ + TEST_1(is_answer_sent(e->data->e_tags)); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_error); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_terminating); /* TERMINATING */ + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_r_bye); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_terminated); /* TERMINATED */ + TEST_1(!e->next); + + free_events_in_list(ctx, a->events); + nua_handle_destroy(a_call->nh), a_call->nh = NULL; + free_events_in_list(ctx, b->events); + nua_handle_destroy(b_call->nh), b_call->nh = NULL; + + TEST_1(test_nat_remove_filter(ctx->nat, f) == 0); + + if (print_headings) + printf("TEST NUA-4.7.2: PASSED\n"); + + completed_4_7_2: + + if (print_headings) + printf("TEST NUA-4.7.3: sending ACK fails\n"); + + TEST_1(a_call->nh = nua_handle(a->nua, a_call, SIPTAG_TO(b->to), TAG_END())); + + INVITE(a, a_call, a_call->nh, + TAG_IF(!ctx->proxy_tests, NUTAG_URL(b->contact->m_url)), + SIPTAG_SUBJECT_STR("NUA-4.7.3"), + SOATAG_USER_SDP_STR(a_call->sdp), + NUTAG_AUTOACK(0), + TAG_END()); + run_ab_until(ctx, -1, call_with_bad_ack, -1, accept_call); + + /* + A accept_call B + | | + |-------INVITE------>| + |<----100 Trying-----| + | | + |<----180 Ringing----| + | | + |<--------200--------| + |--ACK-X | + | | + |<--------BYE--------| + |--------200 OK----->| + + */ + + /* + */ + + TEST_1(e = a->events->head); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_calling); /* CALLING */ + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_r_invite); + TEST(e->data->e_status, 180); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_proceeding); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_r_invite); + TEST(e->data->e_status, 200); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_completing); /* COMPLETING */ + /* try to send ACK */ + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_terminating); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_r_bye); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_terminated); + TEST_1(!e->next); + + /* + Server transitions: + -(S1)-> RECEIVED -(S2a)-> EARLY -(S3b)-> COMPLETED -(S5)-> TERMINATING + -(S10)-> TERMINATED -X + */ + TEST_1(e = b->events->head); TEST_E(e->data->e_event, nua_i_invite); + TEST(e->data->e_status, 100); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_received); /* RECEIVED */ + TEST_1(is_offer_recv(e->data->e_tags)); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_early); /* EARLY */ + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_completed); /* COMPLETED */ + TEST_1(is_answer_sent(e->data->e_tags)); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_bye); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_terminated); /* TERMINATED */ + TEST_1(!e->next); + + free_events_in_list(ctx, a->events); + nua_handle_destroy(a_call->nh), a_call->nh = NULL; + free_events_in_list(ctx, b->events); + nua_handle_destroy(b_call->nh), b_call->nh = NULL; + + if (print_headings) + printf("TEST NUA-4.7.3: PASSED\n"); + + if (!ctx->nat) + goto completed_4_7_4; + + if (print_headings) + printf("TEST NUA-4.7.4: 200 OK timeout after client has timed out\n"); + + if (ctx->expensive) + nua_set_params(b->nua, NTATAG_SIP_T1X64(34000), TAG_END()); + else + nua_set_params(b->nua, NTATAG_SIP_T1X64(4000), TAG_END()); + run_b_until(ctx, nua_r_set_params, until_final_response); + + TEST_1(a_call->nh = nua_handle(a->nua, a_call, SIPTAG_TO(b->to), TAG_END())); + + TEST_1(f = test_nat_add_filter(ctx->nat, filter_ACK, NULL, nat_outbound)); + + INVITE(a, a_call, a_call->nh, + TAG_IF(!ctx->proxy_tests, NUTAG_URL(b->contact->m_url)), + SIPTAG_SUBJECT_STR("NUA-4.7.4"), + SOATAG_USER_SDP_STR(a_call->sdp), + TAG_END()); + run_ab_until(ctx, -1, until_terminated, -1, accept_call); + + /* + A accept_call B + | | + |-------INVITE------>| + |<----100 Trying-----| + | | + |<----180 Ringing----| + | | + |<--------200--------| Timer H' + |--------ACK-----X X--+ + | | | + |<--------200--------| | + |--------ACK-----X | | + | | | + |<--------200--------| | + | | | + |<--------200--------| | + | | | + | |<-+ + |<--------BYE--------| + |--------200 OK----->| + + */ + + /* + */ + + TEST_1(e = a->events->head); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_calling); /* CALLING */ + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_r_invite); + TEST(e->data->e_status, 180); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_proceeding); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_r_invite); + TEST(e->data->e_status, 200); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_ready); /* READY */ + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_bye); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_terminated); + TEST_1(!e->next); + + /* + Server transitions: + -(S1)-> RECEIVED -(S2a)-> EARLY -(S3b)-> COMPLETED -(S5)-> TERMINATING + -(S10)-> TERMINATED -X + */ + TEST_1(e = b->events->head); TEST_E(e->data->e_event, nua_i_invite); + TEST(e->data->e_status, 100); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_received); /* RECEIVED */ + TEST_1(is_offer_recv(e->data->e_tags)); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_early); /* EARLY */ + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_completed); /* COMPLETED */ + TEST_1(is_answer_sent(e->data->e_tags)); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_error); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_terminating); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_r_bye); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_terminated); /* TERMINATED */ + TEST_1(!e->next); + + free_events_in_list(ctx, a->events); + nua_handle_destroy(a_call->nh), a_call->nh = NULL; + free_events_in_list(ctx, b->events); + nua_handle_destroy(b_call->nh), b_call->nh = NULL; + + TEST_1(test_nat_remove_filter(ctx->nat, f) == 0); + + if (print_headings) + printf("TEST NUA-4.7.4: PASSED\n"); + + nua_set_params(b->nua, NTATAG_SIP_T1X64(2000), TAG_END()); + run_b_until(ctx, nua_r_set_params, until_final_response); + + completed_4_7_4: + + /* XXX - PRACK timeout, PRACK failing, media failing, re-INVITEs */ + + if (print_headings) + printf("TEST NUA-4.7: PASSED\n"); + + END(); +} + +int call_with_bad_ack(CONDITION_PARAMS) +{ + if (!check_handle(ep, call, nh, SIP_500_INTERNAL_SERVER_ERROR)) + return 0; + + save_event_in_list(ctx, event, ep, call); + + if (event == nua_r_invite && 200 <= status && status < 300) { + ACK(ep, call, nh, + /* Syntax error - sending ACK fails, we send BYE */ + SIPTAG_MAX_FORWARDS_STR("blue"), + TAG_END()); + } + + return event == nua_i_state && callstate(tags) == nua_callstate_terminated; +} + +/* ---------------------------------------------------------------------- */ + +int test_rejects(struct context *ctx) +{ + return + test_reject_401_bad(ctx) || + test_reject_a(ctx) || + test_reject_b(ctx) || + test_reject_302(ctx) || + test_reject_401(ctx) || + test_mime_negotiation(ctx) || + test_reject_401_aka(ctx) || + test_call_timeouts(ctx) || + 0; +} Added: freeswitch/trunk/libs/sofia-sip/tests/test_cancel_bye.c ============================================================================== --- (empty file) +++ freeswitch/trunk/libs/sofia-sip/tests/test_cancel_bye.c Wed May 14 15:10:54 2008 @@ -0,0 +1,1663 @@ +/* + * This file is part of the Sofia-SIP package + * + * Copyright (C) 2005 Nokia Corporation. + * + * Contact: Pekka Pessi <pekka.pessi@nokia.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public License + * as published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + */ + +/**@CFILE test_cancel_bye.c + * @brief Test CANCEL, weird BYE and handle destroy + * + * @author Pekka Pessi <Pekka.Pessi@nokia.com> + * @author Martti Mela <Martti Mela@nokia.com> + * + * @date Created: Wed Aug 17 12:12:12 EEST 2005 ppessi + */ + +#include "config.h" + +#include "test_nua.h" +#include <sofia-sip/su_tag_class.h> + +#if HAVE_FUNC +#elif HAVE_FUNCTION +#define __func__ __FUNCTION__ +#else +#define __func__ "test_cancel_bye" +#endif + +/* ======================================================================== */ + +/* Cancel cases: + + + A B + |-------INVITE------>| + |<----100 Trying-----| + | | + |------CANCEL------->| + |<------200 OK-------| + | | + |<-------487---------| + |--------ACK-------->| + | | + | | + + Client transitions: + INIT -(C1)-> CALLING -(C6a)-> TERMINATED + + Server transitions: + INIT -(S1)-> RECEIVED -(S6a)-> TERMINATED + + A B + |-------INVITE------>| + |<----100 Trying-----| + | | + |<----180 Ringing----| + | | + |------CANCEL------->| + |<------200 OK-------| + | | + |<-------487---------| + |--------ACK-------->| + | | + | | + + Client transitions: + INIT -(C1)-> CALLING -(C2)-> PROCEEDING -(C6b)-> TERMINATED + + Server transitions: + INIT -(S1)-> RECEIVED -(S2a)-> EARLY -(S6b)-> TERMINATED + +*/ + +int cancel_when_calling(CONDITION_PARAMS) +{ + if (!(check_handle(ep, call, nh, SIP_500_INTERNAL_SERVER_ERROR))) + return 0; + + save_event_in_list(ctx, event, ep, call); + + switch (callstate(tags)) { + case nua_callstate_calling: + CANCEL(ep, call, nh, + /* sf.net bug #173323 */ + SIPTAG_CALL_ID_STR("non-existing-call-id"), + TAG_END()); + return 0; + case nua_callstate_terminated: + return 1; + default: + return 0; + } +} + +int bye_when_calling(CONDITION_PARAMS) +{ + if (!(check_handle(ep, call, nh, SIP_500_INTERNAL_SERVER_ERROR))) + return 0; + + save_event_in_list(ctx, event, ep, call); + + switch (callstate(tags)) { + case nua_callstate_calling: + BYE(ep, call, nh, TAG_END()); + return 0; + case nua_callstate_terminated: + return 1; + default: + return 0; + } +} + + +int cancel_when_ringing(CONDITION_PARAMS) +{ + if (!(check_handle(ep, call, nh, SIP_500_INTERNAL_SERVER_ERROR))) + return 0; + + save_event_in_list(ctx, event, ep, call); + + switch (callstate(tags)) { + case nua_callstate_proceeding: + CANCEL(ep, call, nh, TAG_END()); + return 0; + case nua_callstate_ready: + return 1; + case nua_callstate_terminated: + return 1; + default: + return 0; + } +} + + +int alert_call(CONDITION_PARAMS) +{ + if (!(check_handle(ep, call, nh, SIP_500_INTERNAL_SERVER_ERROR))) + return 0; + + save_event_in_list(ctx, event, ep, call); + + switch (callstate(tags)) { + case nua_callstate_received: + RESPOND(ep, call, nh, SIP_180_RINGING, TAG_END()); + return 0; + case nua_callstate_terminated: + return 1; + default: + return 0; + } +} + +int accept_after_183(CONDITION_PARAMS); + +int test_call_cancel(struct context *ctx) +{ + BEGIN(); + + struct endpoint *a = &ctx->a, *b = &ctx->b; + struct call *a_call = a->call, *b_call = b->call; + struct event *e; + + a_call->sdp = "m=audio 5008 RTP/AVP 8"; + b_call->sdp = "m=audio 5010 RTP/AVP 0 8"; + + if (print_headings) + printf("TEST NUA-5.1.1: cancel call\n"); + + TEST_1(a_call->nh = nua_handle(a->nua, a_call, SIPTAG_TO(b->to), TAG_END())); + + INVITE(a, a_call, a_call->nh, + TAG_IF(!ctx->proxy_tests, NUTAG_URL(b->contact->m_url)), + SOATAG_USER_SDP_STR(a_call->sdp), + TAG_END()); + + run_ab_until(ctx, -1, cancel_when_calling, -1, until_terminated); + + /* Client transitions: + INIT -(C1)-> CALLING: nua_invite(), nua_i_state, nua_cancel() + CALLING -(C6a)-> TERMINATED: nua_r_invite(487), nua_i_state + */ + TEST_1(e = a->events->head); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_calling); /* CALLING */ + TEST_1(is_offer_sent(e->data->e_tags)); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_r_cancel); + TEST(e->data->e_status, 200); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_r_invite); + TEST(e->data->e_status, 487); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_terminated); /* TERMINATED */ + TEST_1(!e->next); + + /* + Server transitions: + INIT -(S1)-> RECEIVED: nua_i_invite, nua_i_state + RECEIVED -(S6a)--> TERMINATED: nua_i_cancel, nua_i_state + */ + TEST_1(e = b->events->head); TEST_E(e->data->e_event, nua_i_invite); + TEST(e->data->e_status, 100); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_received); /* RECEIVED */ + TEST_1(is_offer_recv(e->data->e_tags)); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_cancel); + TEST(e->data->e_status, 200); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_terminated); /* TERMINATED */ + TEST_1(!e->next); + + free_events_in_list(ctx, a->events); + nua_handle_destroy(a_call->nh), a_call->nh = NULL; + + free_events_in_list(ctx, b->events); + nua_handle_destroy(b_call->nh), b_call->nh = NULL; + + if (print_headings) + printf("TEST NUA-5.1.1: PASSED\n"); + + /* ------------------------------------------------------------------------ */ + + if (print_headings) + printf("TEST NUA-5.1.2: cancel call (with nua_bye())\n"); + + TEST_1(a_call->nh = nua_handle(a->nua, a_call, SIPTAG_TO(b->to), TAG_END())); + + INVITE(a, a_call, a_call->nh, + TAG_IF(!ctx->proxy_tests, NUTAG_URL(b->contact->m_url)), + SIPTAG_SUBJECT_STR("TEST NUA-5.1.2"), + SOATAG_USER_SDP_STR(a_call->sdp), + TAG_END()); + + run_ab_until(ctx, -1, bye_when_calling, -1, until_terminated); + + /* Client transitions: + INIT -(C1)-> CALLING: nua_invite(), nua_i_state, nua_cancel() + CALLING -(C6a)-> TERMINATED: nua_r_invite(487), nua_i_state + */ + TEST_1(e = a->events->head); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_calling); /* CALLING */ + TEST_1(is_offer_sent(e->data->e_tags)); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_r_bye); + TEST(e->data->e_status, 200); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_r_invite); + TEST(e->data->e_status, 487); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_terminated); /* TERMINATED */ + TEST_1(!e->next); + + /* + Server transitions: + INIT -(S1)-> RECEIVED: nua_i_invite, nua_i_state + RECEIVED -(S6a)--> TERMINATED: nua_i_cancel, nua_i_state + */ + TEST_1(e = b->events->head); TEST_E(e->data->e_event, nua_i_invite); + TEST(e->data->e_status, 100); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_received); /* RECEIVED */ + TEST_1(is_offer_recv(e->data->e_tags)); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_cancel); + TEST(e->data->e_status, 200); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_terminated); /* TERMINATED */ + TEST_1(!e->next); + + free_events_in_list(ctx, a->events); + nua_handle_destroy(a_call->nh), a_call->nh = NULL; + + free_events_in_list(ctx, b->events); + nua_handle_destroy(b_call->nh), b_call->nh = NULL; + + if (print_headings) + printf("TEST NUA-5.1.2: PASSED\n"); + + /* ----------------------------------------------------------------------- */ + + if (print_headings) + printf("TEST NUA-5.2.1: cancel call when ringing\n"); + + TEST_1(a_call->nh = nua_handle(a->nua, a_call, SIPTAG_TO(b->to), TAG_END())); + + INVITE(a, a_call, a_call->nh, + TAG_IF(!ctx->proxy_tests, NUTAG_URL(b->contact->m_url)), + SOATAG_USER_SDP_STR(a_call->sdp), + /*SIPTAG_REJECT_CONTACT_STR("*;audio=FALSE"),*/ + TAG_END()); + + run_ab_until(ctx, -1, cancel_when_ringing, -1, alert_call); + + /* Client transitions: + INIT -(C1)-> CALLING: nua_invite(), nua_i_state + CALLING -(C2)-> PROCEEDING: nua_r_invite(180, nua_i_state, nua_cancel() + PROCEEDING -(C6b)-> TERMINATED: nua_r_invite(487), nua_i_state + */ + TEST_1(e = a->events->head); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_calling); /* CALLING */ + TEST_1(is_offer_sent(e->data->e_tags)); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_r_invite); + TEST(e->data->e_status, 180); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_proceeding); /* PROCEEDING */ + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_r_cancel); + TEST(e->data->e_status, 200); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_r_invite); + TEST(e->data->e_status, 487); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_terminated); /* TERMINATED */ + TEST_1(!e->next); + + /* + Server transitions: + INIT -(S1)-> RECEIVED: nua_i_invite, nua_i_state + RECEIVED -(S2a)-> EARLY: nua_respond(180), nua_i_state + EARLY -(S6b)--> TERMINATED: nua_i_cancel, nua_i_state + */ + TEST_1(e = b->events->head); TEST_E(e->data->e_event, nua_i_invite); + TEST(e->data->e_status, 100); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_received); /* RECEIVED */ + TEST_1(is_offer_recv(e->data->e_tags)); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_early); /* EARLY */ + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_cancel); + TEST(e->data->e_status, 200); + /* Check for bug #1326727 */ + TEST_1(e->data->e_msg); +#if 0 + TEST_1(sip_object(e->data->e_msg)->sip_reject_contact); + TEST_1(sip_object(e->data->e_msg)->sip_reject_contact->cp_params && + sip_object(e->data->e_msg)->sip_reject_contact->cp_params[0]); + TEST_S(sip_object(e->data->e_msg)->sip_reject_contact->cp_params[0], + "audio=FALSE"); +#endif + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_terminated); /* TERMINATED */ + TEST_1(!e->next); + + free_events_in_list(ctx, a->events); + nua_handle_destroy(a_call->nh), a_call->nh = NULL; + + free_events_in_list(ctx, b->events); + nua_handle_destroy(b_call->nh), b_call->nh = NULL; + + if (print_headings) + printf("TEST NUA-5.2.1: PASSED\n"); + + /* ------------------------------------------------------------------------ */ + if (print_headings) + printf("TEST NUA-5.2.2: CANCEL call when server waits for PRACK\n"); + + TEST_1(a_call->nh = nua_handle(a->nua, a_call, SIPTAG_TO(b->to), TAG_END())); + + INVITE(a, a_call, a_call->nh, + TAG_IF(!ctx->proxy_tests, NUTAG_URL(b->contact->m_url)), + SOATAG_USER_SDP_STR(a_call->sdp), + NUTAG_APPL_METHOD("PRACK"), + /*SIPTAG_REJECT_CONTACT_STR("*;audio=FALSE"),*/ + TAG_END()); + + run_ab_until(ctx, -1, cancel_when_ringing, -1, accept_after_183); + + free_events_in_list(ctx, a->events); + free_events_in_list(ctx, b->events); + + nua_handle_destroy(a_call->nh), a_call->nh = NULL; + nua_handle_destroy(b_call->nh), b_call->nh = NULL; + + if (print_headings) + printf("TEST NUA-5.2.2: PASSED\n"); + + + END(); +} + +int accept_after_183(CONDITION_PARAMS) +{ + if (!(check_handle(ep, call, nh, SIP_500_INTERNAL_SERVER_ERROR))) + return 0; + + save_event_in_list(ctx, event, ep, call); + + switch (callstate(tags)) { + case nua_callstate_received: + RESPOND(ep, call, nh, SIP_183_SESSION_PROGRESS, TAG_END()); + RESPOND(ep, call, nh, SIP_200_OK, TAG_END()); + return 0; + case nua_callstate_terminated: + return 1; + default: + return 0; + } +} + + +/* ======================================================================== */ +/* Destroy call handle */ + +int destroy_when_calling(CONDITION_PARAMS) +{ + if (!(check_handle(ep, call, nh, SIP_500_INTERNAL_SERVER_ERROR))) + return 0; + + save_event_in_list(ctx, event, ep, call); + + switch (callstate(tags)) { + case nua_callstate_calling: + DESTROY(ep, call, nh); + return 1; + default: + return 0; + } +} + +int destroy_when_completing(CONDITION_PARAMS) +{ + if (!(check_handle(ep, call, nh, SIP_500_INTERNAL_SERVER_ERROR))) + return 0; + + save_event_in_list(ctx, event, ep, call); + + switch (callstate(tags)) { + case nua_callstate_completing: + DESTROY(ep, call, nh); + return 1; + case nua_callstate_ready: + return 1; + case nua_callstate_terminated: + if (call) + nua_handle_destroy(call->nh), call->nh = NULL; + return 1; + default: + return 0; + } +} + +int test_call_destroy_1(struct context *ctx) +{ + BEGIN(); + + struct endpoint *a = &ctx->a, *b = &ctx->b; + struct call *a_call = a->call, *b_call = b->call; + struct event *e; + + if (print_headings) + printf("TEST NUA-5.3: destroy when calling\n"); + + TEST_1(a_call->nh = nua_handle(a->nua, a_call, SIPTAG_TO(b->to), TAG_END())); + + INVITE(a, a_call, a_call->nh, + TAG_IF(!ctx->proxy_tests, NUTAG_URL(b->contact->m_url)), + SOATAG_USER_SDP_STR(a_call->sdp), + TAG_END()); + + run_ab_until(ctx, -1, destroy_when_calling, -1, until_terminated); + + /* Client transitions: + INIT -(C1)-> CALLING: nua_invite(), ... + */ + TEST_1(e = a->events->head); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_calling); /* CALLING */ + TEST_1(is_offer_sent(e->data->e_tags)); + TEST_1(!e->next); + + free_events_in_list(ctx, a->events); + nua_handle_destroy(a_call->nh), a_call->nh = NULL; + + /* + Server transitions: + INIT -(S1)-> RECEIVED: nua_i_invite, nua_i_state + RECEIVED -(S6a)--> TERMINATED: nua_i_cancel, nua_i_state + */ + TEST_1(e = b->events->head); TEST_E(e->data->e_event, nua_i_invite); + TEST(e->data->e_status, 100); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_received); /* RECEIVED */ + TEST_1(is_offer_recv(e->data->e_tags)); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_cancel); + TEST(e->data->e_status, 200); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_terminated); /* TERMINATED */ + TEST_1(!e->next); + + free_events_in_list(ctx, b->events); + nua_handle_destroy(b_call->nh), b_call->nh = NULL; + + if (print_headings) + printf("TEST NUA-5.3: PASSED\n"); + + END(); +} + +int accept_until_terminated(CONDITION_PARAMS) +{ + if (!(check_handle(ep, call, nh, SIP_500_INTERNAL_SERVER_ERROR))) + return 0; + + save_event_in_list(ctx, event, ep, call); + + switch (callstate(tags)) { + case nua_callstate_received: + RESPOND(ep, call, nh, SIP_180_RINGING, TAG_END()); + return 0; + case nua_callstate_early: + RESPOND(ep, call, nh, SIP_200_OK, + TAG_IF(call->sdp, SOATAG_USER_SDP_STR(call->sdp)), + TAG_END()); + return 0; + case nua_callstate_completed: + case nua_callstate_ready: + return 0; + case nua_callstate_terminated: + if (call) + nua_handle_destroy(call->nh), call->nh = NULL; + return 1; + default: + return 0; + } +} + +int test_call_destroy_2(struct context *ctx) +{ + BEGIN(); + + struct endpoint *a = &ctx->a, *b = &ctx->b; + struct call *a_call = a->call, *b_call = b->call; + struct event *e; + + if (print_headings) + printf("TEST NUA-5.4: destroy when completing\n"); + + TEST_1(a_call->nh = nua_handle(a->nua, a_call, SIPTAG_TO(b->to), TAG_END())); + + INVITE(a, a_call, a_call->nh, + TAG_IF(!ctx->proxy_tests, NUTAG_URL(b->contact->m_url)), + NUTAG_AUTOACK(0), + SOATAG_USER_SDP_STR(a_call->sdp), + TAG_END()); + + run_ab_until(ctx, -1, destroy_when_completing, -1, accept_until_terminated); + + /* Client transitions: + INIT -(C1)-> CALLING: nua_invite(), ... + */ + TEST_1(e = a->events->head); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_calling); /* CALLING */ + TEST_1(is_offer_sent(e->data->e_tags)); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_r_invite); + TEST(e->data->e_status, 180); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_proceeding); /* PROCEEDING */ + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_r_invite); + TEST(e->data->e_status, 200); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_completing); /* COMPLETING */ + TEST_1(is_answer_recv(e->data->e_tags)); + TEST_1(!e->next); + + free_events_in_list(ctx, a->events); + nua_handle_destroy(a_call->nh), a_call->nh = NULL; + + /* + Server transitions: + INIT -(S1)-> RECEIVED: nua_i_invite, nua_i_state + RECEIVED -(S2a)-> EARLY: nua_respond(), nua_i_state + EARLY -(S3b)-> COMPLETED: nua_respond(), nua_i_state + COMPLETED -(S4)-> READY: nua_i_ack, nua_i_state + */ + TEST_1(e = b->events->head); TEST_E(e->data->e_event, nua_i_invite); + TEST(e->data->e_status, 100); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_received); /* RECEIVED */ + TEST_1(is_offer_recv(e->data->e_tags)); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_early); /* EARLY */ + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_completed); /* COMPLETED */ + TEST_1(is_answer_sent(e->data->e_tags)); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_ack); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_ready); /* READY */ + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_bye); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_terminated); /* TERMINATED */ + TEST_1(!e->next); + + free_events_in_list(ctx, b->events); + nua_handle_destroy(b_call->nh), b_call->nh = NULL; + + if (print_headings) + printf("TEST NUA-5.4: PASSED\n"); + + END(); +} + +int destroy_when_early(CONDITION_PARAMS) +{ + if (!(check_handle(ep, call, nh, SIP_500_INTERNAL_SERVER_ERROR))) + return 0; + + save_event_in_list(ctx, event, ep, call); + + switch (callstate(tags)) { + case nua_callstate_received: + RESPOND(ep, call, nh, SIP_180_RINGING, TAG_END()); + return 0; + case nua_callstate_early: + if (call) + DESTROY(ep, call, nh), call->nh = NULL; + return 1; + case nua_callstate_completed: + case nua_callstate_ready: + case nua_callstate_terminated: + if (call) + DESTROY(ep, call, nh), call->nh = NULL; + return 1; + default: + return 0; + } +} + +int test_call_destroy_3(struct context *ctx) +{ + BEGIN(); + + struct endpoint *a = &ctx->a, *b = &ctx->b; + struct call *a_call = a->call, *b_call = b->call; + struct event *e; + + if (print_headings) + printf("TEST NUA-5.5: destroy when early\n"); + + TEST_1(a_call->nh = nua_handle(a->nua, a_call, SIPTAG_TO(b->to), TAG_END())); + + INVITE(a, a_call, a_call->nh, + TAG_IF(!ctx->proxy_tests, NUTAG_URL(b->contact->m_url)), + NUTAG_AUTOACK(0), + SOATAG_USER_SDP_STR(a_call->sdp), + TAG_END()); + + run_ab_until(ctx, -1, until_terminated, -1, destroy_when_early); + + /* Client transitions: + INIT -(C1)-> CALLING: nua_invite(), ... + */ + TEST_1(e = a->events->head); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_calling); /* CALLING */ + TEST_1(is_offer_sent(e->data->e_tags)); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_r_invite); + TEST(e->data->e_status, 180); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_proceeding); /* PROCEEDING */ + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_r_invite); + TEST(e->data->e_status, 480); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_terminated); /* TERMINATED */ + TEST_1(!e->next); + TEST_1(!e->next); + + free_events_in_list(ctx, a->events); + nua_handle_destroy(a_call->nh), a_call->nh = NULL; + + /* + Server transitions: + INIT -(S1)-> RECEIVED: nua_i_invite, nua_i_state + RECEIVED -(S2a)-> EARLY: nua_respond(), nua_i_state + EARLY -(S3b)-> COMPLETED: nua_respond(), nua_i_state ... DESTROY + */ + TEST_1(e = b->events->head); TEST_E(e->data->e_event, nua_i_invite); + TEST(e->data->e_status, 100); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_received); /* RECEIVED */ + TEST_1(is_offer_recv(e->data->e_tags)); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_early); /* EARLY */ + TEST_1(!e->next); + + free_events_in_list(ctx, b->events); + nua_handle_destroy(b_call->nh), b_call->nh = NULL; + + if (print_headings) + printf("TEST NUA-5.5: PASSED\n"); + + END(); +} + +int destroy_when_completed(CONDITION_PARAMS) +{ + if (!(check_handle(ep, call, nh, SIP_500_INTERNAL_SERVER_ERROR))) + return 0; + + save_event_in_list(ctx, event, ep, call); + + switch (callstate(tags)) { + case nua_callstate_received: + RESPOND(ep, call, nh, SIP_180_RINGING, TAG_END()); + return 0; + case nua_callstate_early: + RESPOND(ep, call, nh, SIP_200_OK, + TAG_IF(call->sdp, SOATAG_USER_SDP_STR(call->sdp)), + TAG_END()); + return 0; + case nua_callstate_completed: + case nua_callstate_ready: + case nua_callstate_terminated: + if (call) + DESTROY(ep, call, nh), call->nh = NULL; + return 1; + default: + return 0; + } +} + +int test_call_destroy_4(struct context *ctx) +{ + BEGIN(); + + struct endpoint *a = &ctx->a, *b = &ctx->b; + struct call *a_call = a->call, *b_call = b->call; + struct event *e; + + if (print_headings) + printf("TEST NUA-5.6: destroy when completed\n"); + + TEST_1(a_call->nh = nua_handle(a->nua, a_call, SIPTAG_TO(b->to), TAG_END())); + + INVITE(a, a_call, a_call->nh, + TAG_IF(!ctx->proxy_tests, NUTAG_URL(b->contact->m_url)), + NUTAG_AUTOACK(0), + SOATAG_USER_SDP_STR(a_call->sdp), + TAG_END()); + + run_ab_until(ctx, -1, until_terminated, -1, destroy_when_completed); + + /* Client transitions: + INIT -(C1)-> CALLING: nua_invite(), ... + */ + TEST_1(e = a->events->head); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_calling); /* CALLING */ + TEST_1(is_offer_sent(e->data->e_tags)); + TEST_1(e = e->next); if (e->data->e_event == nua_r_invite) { + TEST_E(e->data->e_event, nua_r_invite); + TEST(e->data->e_status, 180); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_proceeding); /* PROCEEDING */ + TEST_1(e = e->next); + } + if (e->data->e_event == nua_r_invite) { + TEST_E(e->data->e_event, nua_r_invite); + TEST(e->data->e_status, 200); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_completing); /* COMPLETING */ + TEST_1(is_answer_recv(e->data->e_tags)); + TEST_1(e = e->next); + } + TEST_E(e->data->e_event, nua_i_bye); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_terminated); /* TERMINATED */ + + free_events_in_list(ctx, a->events); + nua_handle_destroy(a_call->nh), a_call->nh = NULL; + + /* + Server transitions: + INIT -(S1)-> RECEIVED: nua_i_invite, nua_i_state + RECEIVED -(S2a)-> EARLY: nua_respond(), nua_i_state + EARLY -(S3b)-> COMPLETED: nua_respond(), nua_i_state ... DESTROY + */ + TEST_1(e = b->events->head); TEST_E(e->data->e_event, nua_i_invite); + TEST(e->data->e_status, 100); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_received); /* RECEIVED */ + TEST_1(is_offer_recv(e->data->e_tags)); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_early); /* EARLY */ + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_completed); /* COMPLETED */ + TEST_1(is_answer_sent(e->data->e_tags)); + TEST_1(!e->next); + + free_events_in_list(ctx, b->events); + nua_handle_destroy(b_call->nh), b_call->nh = NULL; + + if (print_headings) + printf("TEST NUA-5.6: PASSED\n"); + + END(); +} + +/* Destroy when one INVITE is queued. */ +int test_call_destroy_5(struct context *ctx) +{ + BEGIN(); + + struct endpoint *a = &ctx->a, *b = &ctx->b; + struct call *a_call = a->call, *b_call = b->call; + struct event *e; + + if (print_headings) + printf("TEST NUA-5.7: destroy when re-INVITE is queued\n"); + + TEST_1(a_call->nh = nua_handle(a->nua, a_call, SIPTAG_TO(b->to), TAG_END())); + + INVITE(a, a_call, a_call->nh, + TAG_IF(!ctx->proxy_tests, NUTAG_URL(b->contact->m_url)), + NUTAG_AUTOACK(0), + SOATAG_USER_SDP_STR(a_call->sdp), + TAG_END()); + + INVITE(a, a_call, a_call->nh, TAG_END()); + + run_ab_until(ctx, -1, until_terminated, -1, destroy_when_completed); + + /* Client transitions: + INIT -(C1)-> CALLING: nua_invite(), ... + */ + TEST_1(e = a->events->head); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_calling); /* CALLING */ + TEST_1(is_offer_sent(e->data->e_tags)); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_r_invite); + TEST(e->data->e_status, 180); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_proceeding); /* PROCEEDING */ + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_r_invite); + TEST(e->data->e_status, 200); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_completing); /* COMPLETING */ + TEST_1(is_answer_recv(e->data->e_tags)); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_bye); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_r_invite); + TEST(e->data->e_status, 481); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_terminated); /* TERMINATED */ + TEST_1(!e->next); + + free_events_in_list(ctx, a->events); + nua_handle_destroy(a_call->nh), a_call->nh = NULL; + + /* + Server transitions: + INIT -(S1)-> RECEIVED: nua_i_invite, nua_i_state + RECEIVED -(S2a)-> EARLY: nua_respond(), nua_i_state + EARLY -(S3b)-> COMPLETED: nua_respond(), nua_i_state ... DESTROY + */ + TEST_1(e = b->events->head); TEST_E(e->data->e_event, nua_i_invite); + TEST(e->data->e_status, 100); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_received); /* RECEIVED */ + TEST_1(is_offer_recv(e->data->e_tags)); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_early); /* EARLY */ + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_completed); /* COMPLETED */ + TEST_1(is_answer_sent(e->data->e_tags)); + TEST_1(!e->next); + + free_events_in_list(ctx, b->events); + nua_handle_destroy(b_call->nh), b_call->nh = NULL; + + if (print_headings) + printf("TEST NUA-5.7: PASSED\n"); + + END(); +} + +int test_call_destroy(struct context *ctx) +{ + struct endpoint *a = &ctx->a, *b = &ctx->b; + struct call *a_call = a->call, *b_call = b->call; + + a_call->sdp = "m=audio 5008 RTP/AVP 8"; + b_call->sdp = "m=audio 5010 RTP/AVP 0 8"; + + return + test_call_destroy_1(ctx) || + test_call_destroy_2(ctx) || + test_call_destroy_3(ctx) || + test_call_destroy_4(ctx) || + test_call_destroy_5(ctx); +} + +/* ======================================================================== */ + +/* Early BYE + + A B + |-------INVITE------>| + |<----100 Trying-----| + | | + |<----180 Ringing----| + | | + |--------BYE-------->| + |<------200 OK-------| + | | + |<-------487---------| + |--------ACK-------->| + | | + | | + + Client transitions: + INIT -(C1)-> CALLING -(C2)-> PROCEEDING -(8)-> TERMINATING -> TERMINATED + + Server transitions: + INIT -(S1)-> RECEIVED -(S2a)-> EARLY -(S8)-> TERMINATED + +*/ + +int bye_when_ringing(CONDITION_PARAMS) +{ + if (!(check_handle(ep, call, nh, SIP_500_INTERNAL_SERVER_ERROR))) + return 0; + + save_event_in_list(ctx, event, ep, call); + + switch (callstate(tags)) { + case nua_callstate_proceeding: + BYE(ep, call, nh, TAG_END()); + return 0; + case nua_callstate_terminated: + return 1; + default: + return 0; + } +} + +int bye_when_completing(CONDITION_PARAMS); + +static int ack_sent = 0; + +size_t count_acks(void *arg, void *message, size_t len) +{ + (void)arg; + + if (strncasecmp(message, "ACK sip:", 8) == 0) + ack_sent++; + + return len; +} + +int test_bye_before_200(struct context *ctx) +{ + BEGIN(); + struct endpoint *a = &ctx->a, *b = &ctx->b; + struct call *a_call = a->call, *b_call = b->call; + struct event *e; + + a_call->sdp = "m=audio 5008 RTP/AVP 8"; + b_call->sdp = "m=audio 5010 RTP/AVP 0 8"; + + if (print_headings) + printf("TEST NUA-6.1: BYE call when ringing\n"); + + TEST_1(a_call->nh = nua_handle(a->nua, a_call, SIPTAG_TO(b->to), TAG_END())); + + INVITE(a, a_call, a_call->nh, + TAG_IF(!ctx->proxy_tests, NUTAG_URL(b->contact->m_url)), + SOATAG_USER_SDP_STR(a_call->sdp), + TAG_END()); + + run_ab_until(ctx, -1, bye_when_ringing, -1, alert_call); + + /* Client transitions: + INIT -(C1)-> CALLING: nua_invite(), nua_i_state + CALLING -(C2)-> PROCEEDING: nua_r_invite(180, nua_i_state, nua_cancel() + PROCEEDING -(C6b)-> TERMINATED: nua_r_invite(487), nua_i_state + */ + TEST_1(e = a->events->head); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_calling); /* CALLING */ + TEST_1(is_offer_sent(e->data->e_tags)); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_r_invite); + TEST(e->data->e_status, 180); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_proceeding); /* PROCEEDING */ + TEST_1(e = e->next); + if (e->data->e_event == nua_r_bye) { + /* We might receive this before or after response to INVITE */ + /* If afterwards, it will come after nua_i_state and we just ignore it */ + TEST_E(e->data->e_event, nua_r_bye); TEST(e->data->e_status, 200); + TEST_1(e->data->e_msg); + /* Forking has not been enabled, so this should be actually a CANCEL */ + TEST(sip_object(e->data->e_msg)->sip_cseq->cs_method, sip_method_cancel); + TEST_1(e = e->next); + } + TEST_E(e->data->e_event, nua_r_invite); TEST(e->data->e_status, 487); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_terminated); /* TERMINATED */ + TEST_1(!e->next); + + /* + Server transitions: + INIT -(S1)-> RECEIVED: nua_i_invite, nua_i_state + RECEIVED -(S2a)-> EARLY: nua_respond(180), nua_i_state + EARLY -(S6b)--> TERMINATED: nua_i_cancel, nua_i_state + */ + TEST_1(e = b->events->head); TEST_E(e->data->e_event, nua_i_invite); + TEST(e->data->e_status, 100); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_received); /* RECEIVED */ + TEST_1(is_offer_recv(e->data->e_tags)); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_early); /* EARLY */ + /* Forking has not been enabled, so this should be actually a CANCEL */ + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_cancel); + TEST(e->data->e_status, 200); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_terminated); /* TERMINATED */ + TEST_1(!e->next); + + free_events_in_list(ctx, a->events); + nua_handle_destroy(a_call->nh), a_call->nh = NULL; + + free_events_in_list(ctx, b->events); + nua_handle_destroy(b_call->nh), b_call->nh = NULL; + + if (print_headings) + printf("TEST NUA-6.1: PASSED\n"); + + END(); +} + + +int bye_when_completing(CONDITION_PARAMS) +{ + if (!(check_handle(ep, call, nh, SIP_500_INTERNAL_SERVER_ERROR))) + return 0; + + save_event_in_list(ctx, event, ep, call); + + switch (callstate(tags)) { + case nua_callstate_completing: + ack_sent = 0; + BYE(ep, call, nh, TAG_END()); + return 0; + case nua_callstate_terminated: + return 1; + default: + return 0; + } +} + + +int test_bye_before_ack(struct context *ctx) +{ + BEGIN(); + struct endpoint *a = &ctx->a, *b = &ctx->b; + struct call *a_call = a->call, *b_call = b->call; + struct event *e; + struct nat_filter *f = NULL; + + a_call->sdp = "m=audio 5008 RTP/AVP 8"; + b_call->sdp = "m=audio 5010 RTP/AVP 0 8"; + +/* Early BYE 2 + + A B + |-------INVITE------>| + |<----100 Trying-----| + | | + |<----180 Ringing----| + |<-------200---------| + | | + |--------BYE-------->| + |<------200 OK-------| + |--------ACK-------->| + | | + | | +*/ + if (print_headings) + printf("TEST NUA-6.2: BYE call when completing\n"); + + if (ctx->nat) + TEST_1(f = test_nat_add_filter(ctx->nat, count_acks, NULL, nat_outbound)); + ack_sent = 0; + + TEST_1(a_call->nh = nua_handle(a->nua, a_call, SIPTAG_TO(b->to), TAG_END())); + + INVITE(a, a_call, a_call->nh, + TAG_IF(!ctx->proxy_tests, NUTAG_URL(b->contact->m_url)), + SOATAG_USER_SDP_STR(a_call->sdp), + NUTAG_AUTOACK(0), + TAG_END()); + + run_ab_until(ctx, -1, bye_when_completing, -1, accept_until_terminated); + + /* Client transitions: + INIT -(C1)-> CALLING: nua_invite(), nua_i_state + CALLING -(C2)-> PROCEEDING: nua_r_invite(180, nua_i_state, nua_cancel() + PROCEEDING -(C6b)-> TERMINATED: nua_r_invite(487), nua_i_state + */ + TEST_1(e = a->events->head); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_calling); + TEST_1(is_offer_sent(e->data->e_tags)); + + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_r_invite); + TEST(e->data->e_status, 180); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_proceeding); + + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_r_invite); + TEST(e->data->e_status, 200); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_completing); + TEST_1(e = e->next); + + TEST_E(e->data->e_event, nua_r_bye); TEST(e->data->e_status, 200); + TEST_1(e->data->e_msg); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_terminated); /* TERMINATED */ + TEST_1(!e->next); + + if (ctx->nat) { + while (ack_sent == 0) + su_root_step(ctx->root, 100); + TEST_1(ack_sent > 0); + TEST_1(test_nat_remove_filter(ctx->nat, f) == 0); + } + + /* + Server transitions: + INIT -(S1)-> RECEIVED: nua_i_invite, nua_i_state + RECEIVED -(S2a)-> EARLY: nua_respond(180), nua_i_state + EARLY -(S6b)--> TERMINATED: nua_i_bye, nua_i_state + */ + TEST_1(e = b->events->head); TEST_E(e->data->e_event, nua_i_invite); + TEST(e->data->e_status, 100); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_received); /* RECEIVED */ + TEST_1(is_offer_recv(e->data->e_tags)); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_early); /* EARLY */ + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_completed); /* COMPLETED */ + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_bye); + TEST(e->data->e_status, 200); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_terminated); /* TERMINATED */ + TEST_1(!e->next); + + free_events_in_list(ctx, a->events); + nua_handle_destroy(a_call->nh), a_call->nh = NULL; + + free_events_in_list(ctx, b->events); + nua_handle_destroy(b_call->nh), b_call->nh = NULL; + + if (print_headings) + printf("TEST NUA-6.2: PASSED\n"); + + END(); +} + +int reject_reinvite_401(CONDITION_PARAMS); + +int test_bye_after_receiving_401(struct context *ctx) +{ + BEGIN(); + struct endpoint *a = &ctx->a, *b = &ctx->b; + struct call *a_call = a->call, *b_call = b->call; + struct event *e; + + a_call->sdp = "m=audio 5008 RTP/AVP 8"; + b_call->sdp = "m=audio 5010 RTP/AVP 0 8"; + +/* BYE after receiving 401 + + A B + |-------INVITE------>| + |<----100 Trying-----| + | | + |<----180 Ringing----| + |<-------200---------| + |--------ACK-------->| + | | + |<------INVITE-------| + |--------401-------->| + |<--------ACK--------| + | | + |<--------BYE--------| + |------200 OK------->| + | | +*/ + if (print_headings) + printf("TEST NUA-6.3: BYE after receiving 401\n"); + + TEST_1(a_call->nh = nua_handle(a->nua, a_call, SIPTAG_TO(b->to), TAG_END())); + + INVITE(a, a_call, a_call->nh, + TAG_IF(!ctx->proxy_tests, NUTAG_URL(b->contact->m_url)), + SIPTAG_SUBJECT_STR("NUA-6.3"), + SOATAG_USER_SDP_STR(a_call->sdp), + NUTAG_AUTOANSWER(0), + TAG_END()); + + run_ab_until(ctx, -1, until_ready, -1, accept_call); + + free_events_in_list(ctx, a->events); + + TEST_1(e = b->events->head); TEST_E(e->data->e_event, nua_i_invite); + free_events_in_list(ctx, b->events); + + /* re-INVITE A. */ + INVITE(b, b_call, b_call->nh, + SIPTAG_SUBJECT_STR("NUA-6.3 re-INVITE"), + TAG_END()); + run_ab_until(ctx, -1, reject_reinvite_401, -1, save_until_final_response); + + TEST_1(nua_handle_has_active_call(a_call->nh)); + TEST_1(nua_handle_has_active_call(b_call->nh)); + + free_events_in_list(ctx, a->events); + free_events_in_list(ctx, b->events); + + BYE(b, b_call, b_call->nh, TAG_END()); + + run_ab_until(ctx, -1, until_terminated, -1, until_terminated); + + free_events_in_list(ctx, a->events); + nua_handle_destroy(a_call->nh), a_call->nh = NULL; + + free_events_in_list(ctx, b->events); + nua_handle_destroy(b_call->nh), b_call->nh = NULL; + + if (print_headings) + printf("TEST NUA-6.3: PASSED\n"); + + END(); +} + +int test_bye_after_sending_401(struct context *ctx) +{ + BEGIN(); + struct endpoint *a = &ctx->a, *b = &ctx->b; + struct call *a_call = a->call, *b_call = b->call; + struct event *e; + + a_call->sdp = "m=audio 5008 RTP/AVP 8"; + b_call->sdp = "m=audio 5010 RTP/AVP 0 8"; + +/* BYE after sending 401 + + A B + |-------INVITE------>| + |<----100 Trying-----| + | | + |<----180 Ringing----| + |<-------200---------| + |--------ACK-------->| + | | + |<------INVITE-------| + |--------401-------->| + |<--------ACK--------| + | | + |--------BYE-------->| + |<------200 OK-------| + | | +*/ + if (print_headings) + printf("TEST NUA-6.4.1: BYE after sending 401\n"); + + TEST_1(a_call->nh = nua_handle(a->nua, a_call, SIPTAG_TO(b->to), TAG_END())); + + INVITE(a, a_call, a_call->nh, + TAG_IF(!ctx->proxy_tests, NUTAG_URL(b->contact->m_url)), + SIPTAG_SUBJECT_STR("NUA-6.4.1"), + SOATAG_USER_SDP_STR(a_call->sdp), + NUTAG_AUTOANSWER(0), + TAG_END()); + + run_ab_until(ctx, -1, until_ready, -1, accept_call); + + free_events_in_list(ctx, a->events); + + TEST_1(e = b->events->head); TEST_E(e->data->e_event, nua_i_invite); + free_events_in_list(ctx, b->events); + + /* re-INVITE A. */ + INVITE(b, b_call, b_call->nh, + SIPTAG_SUBJECT_STR("NUA-6.4.1 re-INVITE"), + TAG_END()); + run_ab_until(ctx, -1, reject_reinvite_401, -1, save_until_final_response); + + TEST_1(nua_handle_has_active_call(a_call->nh)); + TEST_1(nua_handle_has_active_call(b_call->nh)); + + free_events_in_list(ctx, a->events); + free_events_in_list(ctx, b->events); + + BYE(a, a_call, a_call->nh, TAG_END()); + + run_ab_until(ctx, -1, until_terminated, -1, until_terminated); + + free_events_in_list(ctx, a->events); + nua_handle_destroy(a_call->nh), a_call->nh = NULL; + + free_events_in_list(ctx, b->events); + nua_handle_destroy(b_call->nh), b_call->nh = NULL; + + if (print_headings) + printf("TEST NUA-6.4.1: PASSED\n"); + + END(); +} + +int test_bye_after_receiving_401_to_update(struct context *ctx) +{ + BEGIN(); + struct endpoint *a = &ctx->a, *b = &ctx->b; + struct call *a_call = a->call, *b_call = b->call; + struct event *e; + + a_call->sdp = "m=audio 5008 RTP/AVP 8"; + b_call->sdp = "m=audio 5010 RTP/AVP 0 8"; + +/* BYE after receiving 401 + + A B + |-------INVITE------>| + |<----100 Trying-----| + | | + |<----180 Ringing----| + |<-------200---------| + |--------ACK-------->| + | | + |<------UPDATE-------| + |--------401-------->| + | | + |<--------BYE--------| + |------200 OK------->| + | | +*/ + if (print_headings) + printf("TEST NUA-6.4.2: BYE after receiving 401 to UPDATE\n"); + + TEST_1(a_call->nh = nua_handle(a->nua, a_call, SIPTAG_TO(b->to), TAG_END())); + + INVITE(a, a_call, a_call->nh, + TAG_IF(!ctx->proxy_tests, NUTAG_URL(b->contact->m_url)), + SIPTAG_SUBJECT_STR("NUA-6.4.2"), + SOATAG_USER_SDP_STR(a_call->sdp), + NUTAG_AUTOANSWER(0), + NUTAG_APPL_METHOD("UPDATE"), + TAG_END()); + + run_ab_until(ctx, -1, until_ready, -1, accept_call); + + free_events_in_list(ctx, a->events); + + TEST_1(e = b->events->head); TEST_E(e->data->e_event, nua_i_invite); + free_events_in_list(ctx, b->events); + + /* UPDATE A. */ + UPDATE(b, b_call, b_call->nh, + SIPTAG_SUBJECT_STR("NUA-6.4.2 UPDATE"), + TAG_END()); + BYE(b, b_call, b_call->nh, TAG_END()); /* Queued until nua_authenticate */ + run_ab_until(ctx, -1, reject_reinvite_401, -1, save_until_final_response); + + TEST_1(nua_handle_has_active_call(a_call->nh)); + TEST_1(nua_handle_has_active_call(b_call->nh)); + + free_events_in_list(ctx, a->events); + free_events_in_list(ctx, b->events); + + AUTHENTICATE(b, b_call, b_call->nh, TAG_END()); + + run_ab_until(ctx, -1, until_terminated, -1, until_terminated); + + free_events_in_list(ctx, a->events); + nua_handle_destroy(a_call->nh), a_call->nh = NULL; + + free_events_in_list(ctx, b->events); + nua_handle_destroy(b_call->nh), b_call->nh = NULL; + + if (print_headings) + printf("TEST NUA-6.4.2: PASSED\n"); + + END(); +} + +int reject_reinvite_401(CONDITION_PARAMS) +{ + void *request = nua_current_request(nua); + + save_event_in_list(ctx, event, ep, call); + + if (!(check_handle(ep, call, nh, SIP_500_INTERNAL_SERVER_ERROR))) + return 0; + + if (status < 200 && (event == nua_i_invite || event == nua_i_update)) { + RESPOND(ep, call, nh, SIP_401_UNAUTHORIZED, + NUTAG_WITH(request), + SIPTAG_WWW_AUTHENTICATE_STR("Digest realm=\"test_nua\", " + "nonce=\"nsdhfuds\", algorithm=MD5, " + "qop=\"auth\""), + TAG_END()); + return 0; + } + + if (event == nua_i_state) switch (callstate(tags)) { + case nua_callstate_ready: + return 1; + case nua_callstate_terminated: + if (call) + nua_handle_destroy(call->nh), call->nh = NULL; + return 1; + default: + return 0; + } + + return 0; +} + +int test_bye_with_407(struct context *ctx) +{ + BEGIN(); + struct endpoint *a = &ctx->a, *c = &ctx->c; + struct call *a_call = a->call, *c_call = c->call; + struct event *e; + + a_call->sdp = "m=audio 5008 RTP/AVP 8"; + c_call->sdp = "m=audio 5010 RTP/AVP 0 8"; + + if (!ctx->proxy_tests) + return 0; + +/* BYE after receiving 401 + + A C + |-------INVITE------>| + |<----100 Trying-----| + | | + |<----180 Ringing----| + |<-------200---------| + |--------ACK-------->| + | | + | |<----BYE----| + | |-----407--->| + |<-------BYE---------| + |--------200-------->| + | | +*/ + if (print_headings) + printf("TEST NUA-6.4.5: BYE with 407\n"); + + TEST_1(a_call->nh = nua_handle(a->nua, a_call, SIPTAG_TO(c->to), TAG_END())); + + INVITE(a, a_call, a_call->nh, + TAG_IF(!ctx->proxy_tests, NUTAG_URL(c->contact->m_url)), + SIPTAG_SUBJECT_STR("NUA-6.4.2"), + SOATAG_USER_SDP_STR(a_call->sdp), + NUTAG_AUTOANSWER(0), + NUTAG_APPL_METHOD("UPDATE"), + TAG_END()); + + run_abc_until(ctx, -1, until_ready, -1, NULL, -1, accept_call); + + free_events_in_list(ctx, a->events); + + TEST_1(e = c->events->head); TEST_E(e->data->e_event, nua_i_invite); + free_events_in_list(ctx, c->events); + + BYE(c, c_call, c_call->nh, + TAG_END()); + run_c_until(ctx, -1, save_until_final_response); + + TEST_1(nua_handle_has_active_call(a_call->nh)); + TEST_1(nua_handle_has_active_call(c_call->nh)); + + free_events_in_list(ctx, a->events); + free_events_in_list(ctx, c->events); + + AUTHENTICATE(c, c_call, c_call->nh, + NUTAG_AUTH("Digest:\"test-proxy\":charlie:secret"), TAG_END()); + + run_abc_until(ctx, -1, until_terminated, -1, NULL, -1, until_terminated); + + free_events_in_list(ctx, a->events); + nua_handle_destroy(a_call->nh), a_call->nh = NULL; + + free_events_in_list(ctx, c->events); + nua_handle_destroy(c_call->nh), c_call->nh = NULL; + + if (print_headings) + printf("TEST NUA-6.4.5: PASSED\n"); + + END(); +} + +int test_bye_to_invalid_contact(struct context *ctx) +{ + BEGIN(); + struct endpoint *a = &ctx->a, *b = &ctx->b; + struct call *a_call = a->call, *b_call = b->call; + struct event *e; + sip_t *sip = NULL; + + int seen_401; + + a_call->sdp = "m=audio 5008 RTP/AVP 8"; + b_call->sdp = "m=audio 5010 RTP/AVP 0 8"; + +/* Bad Contact URI + + A B + |-------INVITE------>| + |<----100 Trying-----| + | | + |<----180 Ringing----| + |<-------200---------| + | | + |--------ACK-------->| + | | + |<-------BYE---------| + |--------400-------->| + | | + |--------BYE-------->| + |<------200 OK-------| + | | +*/ + if (print_headings) + printf("TEST NUA-6.4.3: Test dialog with bad Contact info\n"); + + TEST_1(a_call->nh = nua_handle(a->nua, a_call, SIPTAG_TO(b->to), TAG_END())); + + INVITE(a, a_call, a_call->nh, + TAG_IF(!ctx->proxy_tests, NUTAG_URL(b->contact->m_url)), + SOATAG_USER_SDP_STR(a_call->sdp), + SIPTAG_CONTACT(NULL), + SIPTAG_HEADER_STR("Contact: <<sip:xyzzy@com.invalid>"), + TAG_END()); + + run_ab_until(ctx, -1, until_ready, -1, accept_call); + + /* Client transitions: + INIT -(C1)-> CALLING: nua_invite(), nua_i_state + CALLING -(C2)-> PROCEEDING: nua_r_invite(180, nua_i_state, nua_cancel() + PROCEEDING -(C6b)-> TERMINATED: nua_r_invite(487), nua_i_state + */ + TEST_1(e = a->events->head); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_calling); + TEST_1(is_offer_sent(e->data->e_tags)); + + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_r_invite); + TEST(e->data->e_status, 180); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_proceeding); + + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_r_invite); + TEST(e->data->e_status, 200); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_ready); + TEST_1(!e->next); + + /* + Server transitions: + INIT -(S1)-> RECEIVED: nua_i_invite, nua_i_state + RECEIVED -(S2a)-> EARLY: nua_respond(180), nua_i_state + EARLY -(S6b)--> TERMINATED: nua_i_cancel, nua_i_state + */ + TEST_1(e = b->events->head); TEST_E(e->data->e_event, nua_i_invite); + TEST(e->data->e_status, 100); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_received); /* RECEIVED */ + TEST_1(is_offer_recv(e->data->e_tags)); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_early); /* EARLY */ + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_completed); /* COMPLETED */ + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_ack); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_ready); /* READY */ + TEST_1(!e->next); + + free_events_in_list(ctx, a->events); + free_events_in_list(ctx, b->events); + + BYE(b, b_call, b_call->nh, TAG_END()); + + run_b_until(ctx, -1, until_terminated); + + /* B transitions: + READY --(T2)--> TERMINATING: nua_bye() + TERMINATING --(T3)--> TERMINATED: nua_r_bye, nua_i_state + */ + TEST_1(e = b->events->head); TEST_E(e->data->e_event, nua_r_bye); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_terminated); /* TERMINATED */ + + TEST_1(!nua_handle_has_active_call(b_call->nh)); + TEST_1(nua_handle_has_active_call(a_call->nh)); + + nua_handle_destroy(a_call->nh), a_call->nh = NULL; + nua_handle_destroy(b_call->nh), b_call->nh = NULL; + + if (print_headings) + printf("TEST NUA-6.4.3: PASSED\n"); + + if (!ctx->p) { + free_events_in_list(ctx, b->events); + return 0; + } + + if (print_headings) + printf("TEST NUA-6.4.4: Wait for re-REGISTER after connection has been closed\n"); + + if (!e->next || (!e->next->next || !e->next->data->e_status != 200)) + /* B is supposed to re-register pretty soon, wait for re-registration */ + run_b_until(ctx, -1, save_until_final_response); + + seen_401 = 0; + + for (e = e->next; e; e = e->next) { + TEST_E(e->data->e_event, nua_r_register); + TEST_1(sip = sip_object(e->data->e_msg)); + + if (e->data->e_status == 200) { + TEST(e->data->e_status, 200); + TEST_1(seen_401); + TEST_1(sip->sip_contact); + } + else if (sip->sip_status && sip->sip_status->st_status == 401) { + seen_401 = 1; + } + + if (!e->next) + break; + } + TEST_1(e); + TEST_S(sip->sip_contact->m_expires, "3600"); + TEST_1(!e->next); + free_events_in_list(ctx, b->events); + + if (print_headings) + printf("TEST NUA-6.4.4: PASSED\n"); + + END(); +} + +int test_early_bye(struct context *ctx) +{ + return + test_bye_with_407(ctx) || + test_bye_before_200(ctx) || + test_bye_before_ack(ctx) || + test_bye_after_receiving_401(ctx) || + test_bye_after_sending_401(ctx) || + test_bye_after_receiving_401_to_update(ctx) || + test_bye_to_invalid_contact(ctx) || + 0; +} Added: freeswitch/trunk/libs/sofia-sip/tests/test_extension.c ============================================================================== --- (empty file) +++ freeswitch/trunk/libs/sofia-sip/tests/test_extension.c Wed May 14 15:10:54 2008 @@ -0,0 +1,180 @@ +/* + * This file is part of the Sofia-SIP package + * + * Copyright (C) 2005 Nokia Corporation. + * + * Contact: Pekka Pessi <pekka.pessi@nokia.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public License + * as published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + */ + +/**@CFILE test_extension.c + * @brief NUA-12: Test extension methods. + * + * @author Pekka Pessi <Pekka.Pessi@nokia.com> + * + * @date Created: Mon Nov 13 15:37:05 EET 2006 + */ + +#include "config.h" + +#include "test_nua.h" + +#include <sofia-sip/su_tag_class.h> + +#if HAVE_FUNC +#elif HAVE_FUNCTION +#define __func__ __FUNCTION__ +#else +#define __func__ "test_extension" +#endif + + +int respond_to_extension(CONDITION_PARAMS) +{ + msg_t *with = nua_current_request(nua); + + if (!(check_handle(ep, call, nh, SIP_500_INTERNAL_SERVER_ERROR))) + return 0; + + save_event_in_list(ctx, event, ep, call); + + switch (event) { + case nua_i_method: + RESPOND(ep, call, nh, SIP_200_OK, + NUTAG_WITH(with), + SIPTAG_SUBJECT_STR("extended"), + TAG_END()); + return 1; + default: + return 0; + } +} + +int test_extension(struct context *ctx) +{ + BEGIN(); + + struct endpoint *a = &ctx->a, *b = &ctx->b; + struct call *a_call = a->call, *b_call = b->call; + struct event *e; + sip_t const *sip; + + +/* Test for EXTENSION + + A B + |------EXTENSION---->| + |<--------501--------| (method not recognized) + | | + |------EXTENSION---->| + |<-------200---------| (method allowed, responded) + | | +*/ + + if (print_headings) + printf("TEST NUA-13.1: EXTENSION\n"); + + + TEST_1(a_call->nh = nua_handle(a->nua, a_call, SIPTAG_TO(b->to), TAG_END())); + + /* Test first without NUTAG_METHOD() */ + METHOD(a, a_call, a_call->nh, + TAG_IF(!ctx->proxy_tests, NUTAG_URL(b->contact->m_url)), + TAG_END()); + + run_ab_until(ctx, -1, save_until_final_response, -1, NULL); + + /* Client events: + nua_method(), nua_r_method + */ + TEST_1(e = a->events->head); TEST_E(e->data->e_event, nua_r_method); + TEST(e->data->e_status, 900); /* Internal error */ + TEST_1(!e->data->e_msg); + TEST_1(!e->next); + + free_events_in_list(ctx, a->events); + nua_handle_destroy(a_call->nh), a_call->nh = NULL; + + TEST_1(a_call->nh = nua_handle(a->nua, a_call, SIPTAG_TO(b->to), TAG_END())); + + METHOD(a, a_call, a_call->nh, + TAG_IF(!ctx->proxy_tests, NUTAG_URL(b->contact->m_url)), + NUTAG_METHOD("EXTENSION"), + TAG_END()); + + run_ab_until(ctx, -1, save_until_final_response, -1, NULL); + + /* Client events: + nua_method(), nua_r_method + */ + TEST_1(e = a->events->head); TEST_E(e->data->e_event, nua_r_method); + TEST(e->data->e_status, 501); + TEST_1(!e->next); + + free_events_in_list(ctx, a->events); + nua_handle_destroy(a_call->nh), a_call->nh = NULL; + + free_events_in_list(ctx, b->events); + nua_handle_destroy(b_call->nh), b_call->nh = NULL; + + nua_set_params(b->nua, NUTAG_ALLOW("EXTENSION"), TAG_END()); + + run_b_until(ctx, nua_r_set_params, until_final_response); + + TEST_1(a_call->nh = nua_handle(a->nua, a_call, SIPTAG_TO(b->to), TAG_END())); + + METHOD(a, a_call, a_call->nh, + TAG_IF(!ctx->proxy_tests, NUTAG_URL(b->contact->m_url)), + NUTAG_METHOD("EXTENSION"), + TAG_END()); + + run_ab_until(ctx, -1, save_until_final_response, -1, respond_to_extension); + + /* Client events: + nua_method(), nua_r_method + */ + TEST_1(e = a->events->head); TEST_E(e->data->e_event, nua_r_method); + TEST(e->data->e_status, 200); + TEST_1(sip = sip_object(e->data->e_msg)); + TEST_1(!e->next); + + /* + Server events: + nua_i_method + */ + TEST_1(e = b->events->head); TEST_E(e->data->e_event, nua_i_method); + TEST(e->data->e_status, 100); + TEST_1(!e->next); + + free_events_in_list(ctx, a->events); + nua_handle_destroy(a_call->nh), a_call->nh = NULL; + + free_events_in_list(ctx, b->events); + nua_handle_destroy(b_call->nh), b_call->nh = NULL; + + nua_set_params(b->nua, + SIPTAG_ALLOW(b->allow), + NUTAG_APPL_METHOD(NULL), + NUTAG_APPL_METHOD(b->appl_method), + TAG_END()); + run_b_until(ctx, nua_r_set_params, until_final_response); + + if (print_headings) + printf("TEST NUA-13.1: PASSED\n"); + END(); +} Added: freeswitch/trunk/libs/sofia-sip/tests/test_init.c ============================================================================== --- (empty file) +++ freeswitch/trunk/libs/sofia-sip/tests/test_init.c Wed May 14 15:10:54 2008 @@ -0,0 +1,448 @@ +/* + * This file is part of the Sofia-SIP package + * + * Copyright (C) 2005 Nokia Corporation. + * + * Contact: Pekka Pessi <pekka.pessi@nokia.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public License + * as published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + */ + +/**@CFILE test_init.c + * @brief Init nua test context + * + * @author Pekka Pessi <Pekka.Pessi@nokia.com> + * @author Martti Mela <Martti Mela@nokia.com> + * + * @date Created: Wed Aug 17 12:12:12 EEST 2005 ppessi + */ + +#include "config.h" + +#include "test_nua.h" + +#include <sofia-sip/tport_tag.h> +#include <sofia-sip/auth_module.h> + +#if HAVE_FUNC +#elif HAVE_FUNCTION +#define __func__ __FUNCTION__ +#else +#define __func__ name +#endif + +static char passwd_name[] = "tmp_sippasswd.XXXXXX"; + +static void remove_tmp(void) +{ + if (passwd_name[0]) + unlink(passwd_name); +} + +static char const passwd[] = + "alice:secret:\n" + "bob:secret:\n" + "charlie:secret:\n"; + +int test_nua_init(struct context *ctx, + int start_proxy, + url_t const *o_proxy, + int start_nat, + tag_type_t tag, tag_value_t value, ...) +{ + BEGIN(); + struct event *e; + sip_contact_t const *m = NULL; + sip_from_t const *sipaddress = NULL; + sip_allow_t const *allow = NULL; + sip_supported_t const *supported = NULL; + char const *appl_method = NULL; + url_t const *p_uri, *a_uri, *b_uri; /* Proxy URI */ + char const *initial_route = NULL; /* Initial route towards internal proxy */ + char const *a_bind, *a_bind2; + url_t *e_proxy = NULL; + int err = -1; + url_t b_proxy[1]; + + a_bind = a_bind2 = "sip:0.0.0.0:*"; + +#if SU_HAVE_OSX_CF_API + if (ctx->osx_runloop) + ctx->root = su_root_osx_runloop_create(NULL); + else +#endif + ctx->root = su_root_create(NULL); + TEST_1(ctx->root); + + /* Disable threading by command line switch -s */ + su_root_threading(ctx->root, ctx->threading); + + if (start_proxy && !o_proxy) { + int temp; + + if (print_headings) + printf("TEST NUA-2.1.1: init proxy P\n"); + +#ifndef _WIN32 + temp = mkstemp(passwd_name); +#else + temp = open(passwd_name, O_WRONLY|O_CREAT|O_TRUNC, 666); +#endif + TEST_1(temp != -1); + atexit(remove_tmp); /* Make sure temp file is unlinked */ + + TEST_SIZE(write(temp, passwd, strlen(passwd)), strlen(passwd)); + + TEST_1(close(temp) == 0); + + ctx->p = test_proxy_create(ctx->root, + TAG_IF(ctx->proxy_logging, TPTAG_LOG(1)), + TAG_END()); + + if (ctx->p) { + ctx->a.domain = + test_proxy_add_domain(ctx->p, + URL_STRING_MAKE("sip:example.com")->us_url, + AUTHTAG_METHOD("Digest"), + AUTHTAG_REALM("test-proxy"), + AUTHTAG_OPAQUE("kuik"), + AUTHTAG_DB(passwd_name), + AUTHTAG_QOP("auth-int"), + AUTHTAG_ALGORITHM("md5"), + AUTHTAG_NEXT_EXPIRES(60), + TAG_END()); + + ctx->b.domain = + test_proxy_add_domain(ctx->p, + URL_STRING_MAKE("sip:example.org")->us_url, + AUTHTAG_METHOD("Digest"), + AUTHTAG_REALM("test-proxy"), + AUTHTAG_OPAQUE("kuik"), + AUTHTAG_DB(passwd_name), + AUTHTAG_QOP("auth-int"), + AUTHTAG_ALGORITHM("md5"), + AUTHTAG_NEXT_EXPIRES(60), + TAG_END()); + + test_proxy_domain_set_outbound(ctx->b.domain, 1); + + ctx->c.domain = + test_proxy_add_domain(ctx->p, + URL_STRING_MAKE("sip:example.net")->us_url, + AUTHTAG_METHOD("Digest"), + AUTHTAG_REALM("test-proxy"), + AUTHTAG_OPAQUE("kuik"), + AUTHTAG_DB(passwd_name), + AUTHTAG_QOP("auth-int"), + AUTHTAG_ALGORITHM("md5"), + AUTHTAG_NEXT_EXPIRES(60), + AUTHTAG_MAX_NCOUNT(1), + AUTHTAG_ALLOW("ACK, CANCEL"), + TAG_END()); + + test_proxy_domain_set_record_route(ctx->c.domain, 1); + + ctx->proxy_tests = 1; + } + + + if (print_headings) + printf("TEST NUA-2.1.1: PASSED\n"); + } + + p_uri = a_uri = b_uri = test_proxy_uri(ctx->p); + + if (o_proxy) { + TEST_1(e_proxy = url_hdup(ctx->home, (void *)o_proxy)); + ctx->external_proxy = e_proxy; + } + + if (start_nat && p_uri == NULL) + p_uri = e_proxy; + + if (ctx->p) + initial_route = test_proxy_route_uri(ctx->p, &ctx->lr); + + if (start_nat && p_uri != NULL) { + int family = 0; + su_sockaddr_t su[1]; + socklen_t sulen = sizeof su; + char b[64]; + size_t len; + ta_list ta; + + if (print_headings) + printf("TEST NUA-2.1.2: creating test NAT\n"); + + /* Try to use different family than proxy. */ + if (p_uri->url_host[0] == '[') + family = AF_INET; +#if defined(SU_HAVE_IN6) + else + family = AF_INET6; +#endif + + ta_start(ta, tag, value); + ctx->nat = test_nat_create(ctx->root, family, ta_tags(ta)); + ta_end(ta); + + /* + * NAT thingy works so that we set the outgoing proxy URI to point + * towards its "private" address and give the real address of the proxy + * as its "public" address. If we use different IP families here, we may + * even manage to test real connectivity problems as proxy and endpoint + * can not talk to each other. + */ + + if (test_nat_private(ctx->nat, su, &sulen) < 0) { + printf("%s:%u: NUA-2.1.2: failed to get private NAT address\n", + __FILE__, __LINE__); + } + +#if defined(SU_HAVE_IN6) + else if (su->su_family == AF_INET6) { + a_uri = (void *) + su_sprintf(ctx->home, "sip:[%s]:%u", + inet_ntop(su->su_family, SU_ADDR(su), b, sizeof b), + ntohs(su->su_port)); + a_bind = "sip:[::]:*"; + } +#endif + else if (su->su_family == AF_INET) { + a_uri = (void *) + su_sprintf(ctx->home, "sip:%s:%u", + inet_ntop(su->su_family, SU_ADDR(su), b, sizeof b), + ntohs(su->su_port)); + } + +#if defined(SU_HAVE_IN6) + if (p_uri->url_host[0] == '[') { + su->su_len = sulen = (sizeof su->su_sin6), su->su_family = AF_INET6; + len = strcspn(p_uri->url_host + 1, "]"); assert(len < sizeof b); + memcpy(b, p_uri->url_host + 1, len); b[len] = '\0'; + inet_pton(su->su_family, b, SU_ADDR(su)); + } + else { + su->su_len = sulen = (sizeof su->su_sin), su->su_family = AF_INET; + inet_pton(su->su_family, p_uri->url_host, SU_ADDR(su)); + } +#else + su->su_len = sulen = (sizeof su->su_sin), su->su_family = AF_INET; + inet_pton(su->su_family, p_uri->url_host, SU_ADDR(su)); +#endif + + su->su_port = htons(strtoul(url_port(p_uri), NULL, 10)); + + if (test_nat_public(ctx->nat, su, sulen) < 0) { + printf("%s:%u: NUA-2.1.2: failed to set public address\n", + __FILE__, __LINE__); + a_uri = NULL; + } + + if (print_headings) { + if (ctx->nat && a_uri) { + printf("TEST NUA-2.1.2: PASSED\n"); + } else { + printf("TEST NUA-2.1.2: FAILED\n"); + } + } + } + + if (print_headings) + printf("TEST NUA-2.2.1: init endpoint A\n"); + + if (a_uri == NULL) + a_uri = p_uri; + + ctx->a.instance = nua_generate_instance_identifier(ctx->home); + + ctx->a.nua = nua_create(ctx->root, a_callback, ctx, + NUTAG_PROXY(a_uri ? a_uri : e_proxy), + NUTAG_INITIAL_ROUTE_STR(initial_route), + SIPTAG_FROM_STR("sip:alice@example.com"), + NUTAG_URL(a_bind), + TAG_IF(a_bind != a_bind2, NUTAG_SIPS_URL(a_bind2)), + SOATAG_USER_SDP_STR("m=audio 5004 RTP/AVP 0 8"), + NTATAG_SIP_T1X64(2000), + NUTAG_INSTANCE(ctx->a.instance), + TAG_IF(ctx->a.logging, TPTAG_LOG(1)), + TAG_END()); + TEST_1(ctx->a.nua); + + nua_get_params(ctx->a.nua, TAG_ANY(), TAG_END()); + run_a_until(ctx, nua_r_get_params, save_until_final_response); + TEST_1(e = ctx->a.specials->head); + err = tl_gets(e->data->e_tags, + NTATAG_CONTACT_REF(m), + SIPTAG_FROM_REF(sipaddress), + SIPTAG_ALLOW_REF(allow), + NUTAG_APPL_METHOD_REF(appl_method), + SIPTAG_SUPPORTED_REF(supported), + TAG_END()); + TEST(err, 5); + TEST_1(m); + TEST_1(ctx->a.contact = sip_contact_dup(ctx->home, m)); + TEST_1(ctx->a.to = sip_to_dup(ctx->home, sipaddress)); + TEST_1(ctx->a.allow = sip_allow_dup(ctx->home, allow)); + TEST_1(ctx->a.appl_method = su_strdup(ctx->home, appl_method)); + TEST_1(ctx->a.supported = sip_supported_dup(ctx->home, supported)); + + free_events_in_list(ctx, ctx->a.specials); + + if (print_headings) + printf("TEST NUA-2.2.1: PASSED\n"); + + if (print_headings) + printf("TEST NUA-2.2.2: init endpoint B\n"); + + ctx->b.instance = nua_generate_instance_identifier(ctx->home); + + if (ctx->p) { + /* B uses TCP when talking with proxy */ + *b_proxy = *b_uri; + b_uri = b_proxy; + b_proxy->url_params = "transport=tcp"; + } + + ctx->b.nua = nua_create(ctx->root, b_callback, ctx, + NUTAG_PROXY(b_uri ? b_uri : e_proxy), + SIPTAG_FROM_STR("sip:bob@example.org"), + NUTAG_URL("sip:0.0.0.0:*"), + SOATAG_USER_SDP_STR("m=audio 5006 RTP/AVP 8 0"), + NUTAG_INSTANCE(ctx->b.instance), + /* Quicker timeout */ + NTATAG_SIP_T1X64(2000), + TPTAG_KEEPALIVE(100), + TAG_IF(ctx->b.logging, TPTAG_LOG(1)), + TAG_END()); + TEST_1(ctx->b.nua); + + nua_get_params(ctx->b.nua, TAG_ANY(), TAG_END()); + run_b_until(ctx, nua_r_get_params, save_until_final_response); + TEST_1(e = ctx->b.specials->head); + err = tl_gets(e->data->e_tags, + NTATAG_CONTACT_REF(m), + SIPTAG_FROM_REF(sipaddress), + SIPTAG_ALLOW_REF(allow), + NUTAG_APPL_METHOD_REF(appl_method), + SIPTAG_SUPPORTED_REF(supported), + TAG_END()); + TEST(err, 5); TEST_1(m); + + TEST_1(ctx->b.contact = sip_contact_dup(ctx->home, m)); + TEST_1(ctx->b.to = sip_to_dup(ctx->home, sipaddress)); + TEST_1(ctx->b.allow = sip_allow_dup(ctx->home, allow)); + TEST_1(ctx->b.appl_method = su_strdup(ctx->home, appl_method)); + TEST_1(ctx->b.supported = sip_supported_dup(ctx->home, supported)); + + free_events_in_list(ctx, ctx->b.specials); + + if (print_headings) + printf("TEST NUA-2.2.2: PASSED\n"); + + if (print_headings) + printf("TEST NUA-2.2.3: init endpoint C\n"); + + /* ctx->c.instance = nua_generate_instance_identifier(ctx->home); */ + + ctx->c.to = sip_from_make(ctx->home, "Charlie <sip:charlie@example.net>"); + + ctx->c.nua = nua_create(ctx->root, c_callback, ctx, + NUTAG_PROXY(p_uri ? p_uri : e_proxy), + NUTAG_URL("sip:0.0.0.0:*"), + SOATAG_USER_SDP_STR("m=audio 5400 RTP/AVP 8 0"), + NUTAG_INSTANCE(ctx->c.instance), + TAG_IF(ctx->c.logging, TPTAG_LOG(1)), + TAG_END()); + TEST_1(ctx->c.nua); + + nua_get_params(ctx->c.nua, TAG_ANY(), TAG_END()); + run_c_until(ctx, nua_r_get_params, save_until_final_response); + TEST_1(e = ctx->c.specials->head); + err = tl_gets(e->data->e_tags, + NTATAG_CONTACT_REF(m), + SIPTAG_ALLOW_REF(allow), + NUTAG_APPL_METHOD_REF(appl_method), + SIPTAG_SUPPORTED_REF(supported), + TAG_END()); + + TEST(err, 4); TEST_1(m); + TEST_1(ctx->c.contact = sip_contact_dup(ctx->home, m)); + TEST_1(ctx->c.allow = sip_allow_dup(ctx->home, allow)); + TEST_1(ctx->c.appl_method = su_strdup(ctx->home, appl_method)); + TEST_1(ctx->c.supported = sip_supported_dup(ctx->home, supported)); + free_events_in_list(ctx, ctx->c.specials); + + if (print_headings) + printf("TEST NUA-2.2.3: PASSED\n"); + + END(); +} + + +/* ====================================================================== */ + +int test_deinit(struct context *ctx) +{ + BEGIN(); + + struct call *call; + + if (!ctx->threading) + su_root_step(ctx->root, 100); + + if (ctx->a.nua) { + for (call = ctx->a.call; call; call = call->next) + nua_handle_destroy(call->nh), call->nh = NULL; + + nua_shutdown(ctx->a.nua); + run_a_until(ctx, nua_r_shutdown, until_final_response); + free_events_in_list(ctx, ctx->a.events); + free_events_in_list(ctx, ctx->a.specials); + nua_destroy(ctx->a.nua), ctx->a.nua = NULL; + } + + if (ctx->b.nua) { + for (call = ctx->b.call; call; call = call->next) + nua_handle_destroy(call->nh), call->nh = NULL; + + nua_shutdown(ctx->b.nua); + run_b_until(ctx, nua_r_shutdown, until_final_response); + free_events_in_list(ctx, ctx->b.events); + free_events_in_list(ctx, ctx->b.specials); + nua_destroy(ctx->b.nua), ctx->b.nua = NULL; + } + + if (ctx->c.nua) { + for (call = ctx->c.call; call; call = call->next) + nua_handle_destroy(call->nh), call->nh = NULL; + + nua_shutdown(ctx->c.nua); + run_c_until(ctx, nua_r_shutdown, until_final_response); + free_events_in_list(ctx, ctx->c.events); + free_events_in_list(ctx, ctx->c.specials); + nua_destroy(ctx->c.nua), ctx->c.nua = NULL; + } + + test_proxy_destroy(ctx->p), ctx->p = NULL; + + test_nat_destroy(ctx->nat), ctx->nat = NULL; + + su_root_destroy(ctx->root), ctx->root = NULL; + + END(); +} Added: freeswitch/trunk/libs/sofia-sip/tests/test_nat.c ============================================================================== --- (empty file) +++ freeswitch/trunk/libs/sofia-sip/tests/test_nat.c Wed May 14 15:10:54 2008 @@ -0,0 +1,999 @@ +/* + * This file is part of the Sofia-SIP package + * + * Copyright (C) 2005 Nokia Corporation. + * + * Contact: Pekka Pessi <pekka.pessi@nokia.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public License + * as published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + */ + +/**@CFILE test_nat.c + * @brief Simulated NAT for testing + * + * NAT thing works so that we set the outgoing proxy URI to point + * towards its "private" address and give the real address of the proxy + * as its "public" address. If we use different IP families here, we may + * even manage to test real connectivity problems as proxy and endpoint + * can not talk to each other. + * + * @author Pekka Pessi <Pekka.Pessi@nokia.com> + * + * @date Created: Wed Mar 8 19:54:28 EET 2006 + */ + +#include "config.h" + +struct nat; +struct binding; + +#define SU_ROOT_MAGIC_T struct nat +#define SU_WAKEUP_ARG_T struct binding + +#include <sofia-sip/su.h> +#include <sofia-sip/su_errno.h> +#include <sofia-sip/su_wait.h> +#include <sofia-sip/su_tagarg.h> +#include <sofia-sip/su_localinfo.h> +#include <sofia-sip/su_log.h> + +#include <stdlib.h> +#include <assert.h> +#include <string.h> + +#define LIST_PROTOS(STORAGE, PREFIX, T) \ +STORAGE void PREFIX ##_insert(T **list, T *node), \ + PREFIX ##_remove(T *node) + +#define LIST_BODIES(STORAGE, PREFIX, T, NEXT, PREV) \ +STORAGE void PREFIX ##_insert(T **list, T *node) \ +{ \ + if ((node->NEXT = *list)) { \ + node->PREV = node->NEXT->PREV; \ + node->NEXT->PREV = &node->NEXT; \ + } \ + else \ + node->PREV = list; \ + *list = node; \ +} \ +STORAGE void PREFIX ##_remove(T *node) \ +{ \ + if (node->PREV) \ + if ((*node->PREV = node->NEXT)) \ + node->NEXT->PREV = node->PREV; \ + node->PREV = NULL; \ +} \ +extern int LIST_DUMMY_VARIABLE + +#include "test_nat.h" + +struct nat { + su_home_t home[1]; + su_root_t *parent; + su_clone_r clone; + tagi_t *tags; + + su_root_t *root; + + struct binding *bindings; + + struct nat_filter *in_filters, *out_filters; + + /* True if we act in symmetric way */ + int symmetric; + /* True if we do logging */ + int logging; + + /* Everything sent to in_address will be forwarded to out_address */ + su_sockaddr_t in_address[1], out_address[1]; + socklen_t in_addrlen, out_addrlen; + + int family; /* Preferred private family */ + + /* ...but source address will be "fake" */ + su_localinfo_t *localinfo, *private, *fake; + + su_socket_t udp_socket, tcp_socket; + int udp_register, tcp_register; + + char buffer[65536]; +}; + +LIST_PROTOS(static, nat_binding, struct binding); + +struct binding +{ + struct binding *next, **prev; + struct nat *nat; /* backpointer */ + int socktype, protocol; + su_socket_t in_socket, out_socket; + int in_register, out_register; + int in_closed, out_closed; + char in_name[64], out_name[64]; +}; + +static struct binding *nat_binding_new(struct nat *nat, + char const *protoname, + int socktype, int protocol, + int connected, + su_socket_t in_socket, + su_sockaddr_t *from, + socklen_t fromlen); +static void nat_binding_destroy(struct binding *); + +static int binding_init(struct binding *b, + char const *protoname, + int connected, + su_localinfo_t *li, + su_sockaddr_t *from, + socklen_t fromlen); + +static void flush_bindings(struct nat *nat); +static int invalidate_bindings(void *nat); + +static int new_udp(struct nat *, su_wait_t *wait, struct binding *dummy); +static int udp_in_to_out(struct nat *, su_wait_t *wait, struct binding *); +static int udp_out_to_in(struct nat *, su_wait_t *wait, struct binding *); + +static int new_tcp(struct nat *, su_wait_t *wait, struct binding *dummy); +static int tcp_in_to_out(struct nat *, su_wait_t *wait, struct binding *); +static int tcp_out_to_in(struct nat *, su_wait_t *wait, struct binding *); + +static int invalidate_binding(struct binding *b); + +LIST_PROTOS(static, nat_filter, struct nat_filter); + +struct nat_filter +{ + struct nat_filter *next, **prev; + size_t (*condition)(void *arg, void *message, size_t len); + void *arg; +}; + +/* nat entry point */ +static int +test_nat_init(su_root_t *root, struct nat *nat) +{ + su_localinfo_t *li, hints[1] = {{ 0 }}; + int error; + unsigned port = 0, port0 = 0; + su_sockaddr_t su[1]; + socklen_t sulen; + su_wait_t wait[1]; + + nat->root = root; + nat->udp_socket = INVALID_SOCKET, nat->tcp_socket = INVALID_SOCKET; + + tl_gets(nat->tags, + TESTNATTAG_SYMMETRIC_REF(nat->symmetric), + TESTNATTAG_LOGGING_REF(nat->logging), + TAG_END()); + + hints->li_scope = LI_SCOPE_HOST | LI_SCOPE_SITE | LI_SCOPE_GLOBAL; + + error = su_getlocalinfo(hints, &nat->localinfo); + if (error) { + fprintf(stderr, "test_nat: su_getlocalinfo: %s\n", su_gli_strerror(error)); + return -1; + } + + /* We must have two different IP addresses. */ + if (!nat->localinfo || !nat->localinfo->li_next) { + fprintf(stderr, "test_nat: only one IP address available\n"); + return -1; + } + + for (li = nat->localinfo; li; li = li->li_next) { + if (nat->family == 0 || nat->family == li->li_family) + break; + } + if (li == NULL) + li = nat->localinfo; + + memcpy(su, li->li_addr, sulen = li->li_addrlen); + memset(SU_ADDR(su), 0, SU_ADDRLEN(su)); + + nat->private = li; + + /* Bind TCP and UDP to same port */ + for (;;) { + nat->udp_socket = su_socket(li->li_family, SOCK_DGRAM, IPPROTO_UDP); + if (nat->udp_socket == INVALID_SOCKET) + return -1; + + if (bind(nat->udp_socket, (void *)su, sulen) < 0) { + if (port0 == 0) { + su_perror("nat: bind(udp_socket)"); + return -1; + } + + fprintf(stderr, "test_nat: port %u: %s\n", + port, su_strerror(su_errno())); + su_close(nat->udp_socket); + + nat->udp_socket = INVALID_SOCKET; + + if (++port > 65535) + port = 1024; + if (port == port0) { + fprintf(stderr, "test_nat: could not find free port pairt\n"); + return -1; + } + + continue; + } + + if (getsockname(nat->udp_socket, (void *)su, &sulen) < 0) { + su_perror("nat: getsockname(udp_socket)"); + return -1; + } + + if (port0 == 0) { + port = port0 = ntohs(su->su_port); + if (port0 == 0) { + fprintf(stderr, "test_nat: bind did not return port\n"); + return -1; + } + } + + nat->tcp_socket = su_socket(li->li_family, SOCK_STREAM, IPPROTO_TCP); + if (nat->tcp_socket == INVALID_SOCKET) + return -1; + + if (bind(nat->tcp_socket, (void *)su, sulen) < 0) { + su_close(nat->tcp_socket); + nat->tcp_socket = INVALID_SOCKET; + + fprintf(stderr, "test_nat: port %u: %s\n", + port, su_strerror(su_errno())); + + if (++port > 65535) + port = 1024; + if (port == port0) { + fprintf(stderr, "test_nat: could not find free port pair\n"); + return -1; + } + + continue; + } + + break; + } + + memcpy(nat->in_address, li->li_addr, nat->in_addrlen = li->li_addrlen); + nat->in_address->su_port = su->su_port; + + if (su_setreuseaddr(nat->udp_socket, 1) < 0) { + su_perror("nat: su_setreuseaddr(udp_socket)"); + return -1; + } + + if (listen(nat->tcp_socket, 5) < 0) { + su_perror("nat: listen(tcp_socket)"); + return -1; + } + + if (su_wait_create(wait, nat->udp_socket, SU_WAIT_IN) < 0) { + su_perror("nat: su_wait_create"); + return -1; + } + + nat->udp_register = su_root_register(root, wait, new_udp, NULL, 0); + if (nat->udp_register < 0) { + su_perror("nat: su_root_register"); + return -1; + } + + if (su_wait_create(wait, nat->tcp_socket, SU_WAIT_IN) < 0) { + su_perror("nat: su_wait_create"); + return -1; + } + + nat->tcp_register = su_root_register(root, wait, new_tcp, NULL, 0); + if (nat->tcp_register < 0) { + su_perror("nat: su_root_register"); + return -1; + } + + return 0; +} + +static void +test_nat_deinit(su_root_t *root, struct nat *nat) +{ + flush_bindings(nat); + + if (nat->tcp_register) + su_root_deregister(root, nat->tcp_register); + if (nat->udp_register) + su_root_deregister(root, nat->udp_register); + + if (nat->udp_socket != INVALID_SOCKET) + su_close(nat->udp_socket); + if (nat->tcp_socket != INVALID_SOCKET) + su_close(nat->tcp_socket); + + su_freelocalinfo(nat->localinfo); + + free(nat->tags); +} + +struct nat *test_nat_create(su_root_t *root, + int family, + tag_type_t tag, tag_value_t value, ...) +{ + struct nat *nat = su_home_new(sizeof *nat); + + if (nat) { + ta_list ta; + + nat->parent = root; + nat->family = family; + + ta_start(ta, tag, value); + nat->tags = tl_llist(ta_tags(ta)); + ta_end(ta); + + if (su_clone_start(root, + nat->clone, + nat, + test_nat_init, + test_nat_deinit) == -1) + su_home_unref(nat->home), nat = NULL; + } + + return nat; +} + +void test_nat_destroy(struct nat *nat) +{ + if (nat) { + su_clone_wait(nat->parent, nat->clone); + su_home_unref(nat->home); + } +} + +/** Get "private" address. */ +int test_nat_private(struct nat *nat, void *address, socklen_t *return_addrlen) +{ + if (nat == NULL || address == NULL || return_addrlen == NULL) + return su_seterrno(EFAULT); + + if (*return_addrlen < nat->in_addrlen) + return su_seterrno(EINVAL); + + memcpy(address, nat->in_address, *return_addrlen = nat->in_addrlen); + + return 0; +} + +/** Set "public" address. */ +int test_nat_public(struct nat *nat, void const *address, int addrlen) +{ + su_sockaddr_t const *su = address; + su_localinfo_t *li; + + if (nat == NULL) + return su_seterrno(EFAULT); + + if (address == NULL) { + nat->fake = NULL; + return 0; + } + + if ((size_t)addrlen > sizeof nat->out_address) + return su_seterrno(EINVAL); + + for (li = nat->localinfo; li; li = li->li_next) { + if (li != nat->private && + li->li_scope == LI_SCOPE_HOST && + li->li_family == su->su_family) + break; + } + + if (li == NULL) + for (li = nat->localinfo; li; li = li->li_next) { + if (li != nat->private && li->li_family == su->su_family) + break; + } + + if (li == NULL) + return su_seterrno(EADDRNOTAVAIL); + + su_clone_pause(nat->clone); + memcpy(nat->out_address, address, nat->out_addrlen = addrlen); + nat->fake = li; + su_clone_resume(nat->clone); + + return 0; +} + +int test_nat_flush(struct nat *nat) +{ + if (nat == NULL) + return su_seterrno(EFAULT); + + return su_task_execute(su_clone_task(nat->clone), + invalidate_bindings, nat, NULL); +} + +/* ====================================================================== */ + +struct binding *nat_binding_new(struct nat *nat, + char const *protoname, + int socktype, + int protocol, + int connected, + su_socket_t in_socket, + su_sockaddr_t *from, + socklen_t fromlen) +{ + struct binding *b; + + if (nat->fake == NULL) { /* Xyzzy */ + fprintf(stderr, "test_nat: fake address missing\n"); + su_close(in_socket); + return NULL; + } + + b = su_zalloc(nat->home, sizeof *b); + if (b == NULL) { + su_perror("nat_binding_new: su_zalloc"); + su_close(in_socket); + return 0; + } + + b->nat = nat; + b->socktype = socktype; + b->protocol = protocol; + b->in_socket = in_socket, b->out_socket = INVALID_SOCKET; + b->in_register = -1, b->out_register = -1; + + if (binding_init(b, protoname, connected, nat->fake, from, fromlen) < 0) + nat_binding_destroy(b), b = NULL; + + return b; +} + +static int binding_init(struct binding *b, + char const *protoname, + int connected, + su_localinfo_t *li, + su_sockaddr_t *from, + socklen_t fromlen) +{ + struct nat *nat = b->nat; + su_socket_t out_socket; + su_sockaddr_t addr[1]; + socklen_t addrlen = (sizeof addr); + char ipname[64]; + su_wait_t wait[1]; + su_wakeup_f in_to_out, out_to_in; + + if (b->socktype == SOCK_STREAM) + in_to_out = tcp_in_to_out, out_to_in = tcp_out_to_in; + else + in_to_out = udp_in_to_out, out_to_in = udp_out_to_in; + + if (b->in_socket == INVALID_SOCKET) { + int in_socket; + + in_socket = su_socket(from->su_family, b->socktype, b->protocol); + if (in_socket == INVALID_SOCKET) { + su_perror("nat_binding_new: socket"); + return -1; + } + b->in_socket = in_socket; + if (su_setreuseaddr(in_socket, 1) < 0) { + su_perror("nat_binding_new: su_setreuseaddr(in_socket)"); + return -1; + } + if (bind(in_socket, (void *)nat->in_address, nat->in_addrlen) < 0) { + su_perror("nat_binding_new: bind(in_socket)"); + return -1; + } + if (connect(in_socket, (void *)from, fromlen) < 0) { + su_perror("nat_binding_new: connect(in_socket)"); + return -1; + } + } + + out_socket = su_socket(li->li_family, b->socktype, b->protocol); + if (out_socket == INVALID_SOCKET) { + su_perror("nat_binding_new: socket"); + return -1; + } + b->out_socket = out_socket; + + if (bind(out_socket, (void *)li->li_addr, li->li_addrlen) < 0) { + su_perror("nat_binding_new: bind(to)"); + return -1; + } + + if (connected) + if (connect(out_socket, (void *)nat->out_address, nat->out_addrlen) < 0) { + su_perror("nat_binding_new: connect(to)"); + return -1; + } + + getpeername(b->in_socket, (void *)addr, &addrlen); + inet_ntop(addr->su_family, SU_ADDR(addr), ipname, sizeof ipname); + snprintf(b->in_name, sizeof b->in_name, + addr->su_family == AF_INET6 ? "[%s]:%u" : "%s:%u", + ipname, ntohs(addr->su_port)); + + getsockname(out_socket, (void *)addr, &addrlen); + inet_ntop(addr->su_family, SU_ADDR(addr), ipname, sizeof ipname); + snprintf(b->out_name, sizeof b->out_name, + addr->su_family == AF_INET6 ? "[%s]:%u" : "%s:%u", + ipname, ntohs(addr->su_port)); + + if (su_wait_create(wait, b->in_socket, SU_WAIT_IN) < 0) { + su_perror("nat_binding_new: su_wait_create"); + return -1; + } + b->in_register = su_root_register(nat->root, wait, in_to_out, b, 0); + if (b->in_register < 0) { + su_perror("nat_binding_new: su_root_register"); + su_wait_destroy(wait); + return -1; + } + + if (su_wait_create(wait, out_socket, SU_WAIT_IN) < 0) { + su_perror("nat_binding_new: su_wait_create"); + return -1; + } + b->out_register = su_root_register(nat->root, wait, out_to_in, b, 0); + if (b->out_register < 0) { + su_perror("nat_binding_new: su_root_register"); + su_wait_destroy(wait); + return -1; + } + + nat_binding_insert(&nat->bindings, b); + + if (nat->logging) + printf("nat: new %s binding %s <=> %s\n", + protoname, b->in_name, b->out_name); + + return 0; +} + +static void nat_binding_destroy(struct binding *b) +{ + nat_binding_remove(b); + if (b->in_register != -1) + su_root_deregister(b->nat->root, b->in_register); + if (b->out_register != -1) + su_root_deregister(b->nat->root, b->out_register); + su_close(b->in_socket), su_close(b->out_socket); +} + +static void flush_bindings(struct nat *nat) +{ + struct binding *b; + + for (b = nat->bindings; b; b = b->next) { + if (b->in_register) + su_root_deregister(nat->root, b->in_register); + su_close(b->in_socket); + if (b->out_register) + su_root_deregister(nat->root, b->out_register); + su_close(b->out_socket); + } +} + +static int invalidate_bindings(void *arg) +{ + struct nat *nat = arg; + struct binding *b; + + for (b = nat->bindings; b; b = b->next) { + invalidate_binding(b); + } + return 0; +} + +#if 0 +static struct binding *nat_binding_find(struct nat *nat, + su_sockaddr_t *from, + int fromlen) +{ + char name[64], ipname[64]; + size_t namelen; + struct binding *b; + + inet_ntop(from->su_family, SU_ADDR(from), ipname, sizeof ipname); + snprintf(name, sizeof name, + from->su_family == AF_INET6 ? "[%s]:%u" : "%s:%u", + ipname, ntohs(from->su_port)); + namelen = strlen(name) + 1; + + for (b = nat->bindings; b; b = b->next) { + if (memcmp(name, b->in_name, namelen) == 0) + return b; + } + + if (b == NULL) + b = nat_binding_new(nat, "UDP", SOCK_DGRAM, IPPROTO_UDP, nat->symmetric, + INVALID_SOCKET, from, fromlen); + + return b; +} +#endif + +/* ====================================================================== */ + +LIST_BODIES(static, nat_binding, struct binding, next, prev); + +/* ====================================================================== */ + +static int new_udp(struct nat *nat, su_wait_t *wait, struct binding *dummy) +{ + int events; + su_sockaddr_t from[1]; + socklen_t fromlen = (sizeof from); + struct binding *b; + ssize_t n, m; + + events = su_wait_events(wait, nat->udp_socket); + + n = su_recvfrom(nat->udp_socket, nat->buffer, sizeof nat->buffer, 0, + from, &fromlen); + if (n < 0) { + su_perror("new_udp: recvfrom"); + return 0; + } + + b = nat_binding_new(nat, "UDP", SOCK_DGRAM, IPPROTO_UDP, nat->symmetric, + INVALID_SOCKET, from, fromlen); + if (b == NULL) + return 0; + + if (nat->symmetric) + m = su_send(b->out_socket, nat->buffer, n, 0); + else + m = su_sendto(b->out_socket, nat->buffer, n, 0, + nat->out_address, nat->out_addrlen); + + if (nat->logging) + printf("nat: udp out %d/%d %s => %s\n", + (int)m, (int)n, b->in_name, b->out_name); + + return 0; +} + +static int udp_in_to_out(struct nat *nat, su_wait_t *wait, struct binding *b) +{ + int events; + ssize_t n, m; + size_t len, filtered; + struct nat_filter *f; + + events = su_wait_events(wait, b->in_socket); + + n = su_recv(b->in_socket, nat->buffer, sizeof nat->buffer, 0); + if (n == -1) { + su_perror("udp_in_to_out: recv"); + return 0; + } + + len = (size_t)n; + + for (f = nat->out_filters; f; f = f->next) { + filtered = f->condition(f->arg, nat->buffer, len); + if (filtered != len) { + if (nat->logging) + printf("nat: udp filtered "MOD_ZU" from %s => "MOD_ZU" to %s\n", + len, b->in_name, filtered, b->out_name); + if (filtered == 0) + return 0; + len = filtered; + } + } + + if (nat->symmetric) + m = su_send(b->out_socket, nat->buffer, len, 0); + else + m = su_sendto(b->out_socket, nat->buffer, len, 0, + nat->out_address, nat->out_addrlen); + + if (nat->logging) + printf("nat: udp out %d/%d %s => %s\n", + (int)m, (int)n, b->in_name, b->out_name); + + return 0; +} + +static int udp_out_to_in(struct nat *nat, su_wait_t *wait, struct binding *b) +{ + int events; + ssize_t n, m; + size_t len, filtered; + struct nat_filter *f; + + events = su_wait_events(wait, b->out_socket); + + n = su_recv(b->out_socket, nat->buffer, sizeof nat->buffer, 0); + if (n < 0) { + su_perror("udp_out_to_out: recv"); + return 0; + } + + len = (size_t)n; + + for (f = nat->in_filters; f; f = f->next) { + filtered = f->condition(f->arg, nat->buffer, len); + if (filtered != len) { + if (nat->logging) + printf("nat: udp filtered "MOD_ZU" from %s => "MOD_ZU" to %s\n", + len, b->out_name, filtered, b->in_name); + if (filtered == 0) + return 0; + len = filtered; + } + } + + m = su_send(b->in_socket, nat->buffer, n, 0); + + if (nat->logging) + printf("nat: udp in %d/%d %s => %s\n", + (int)m, (int)n, b->out_name, b->in_name); + + return 0; +} + +/* ====================================================================== */ + +static int new_tcp(struct nat *nat, su_wait_t *wait, struct binding *dummy) +{ + int events; + su_socket_t in_socket; + su_sockaddr_t from[1]; + socklen_t fromlen = (sizeof from); + struct binding *b; + + events = su_wait_events(wait, nat->tcp_socket); + + in_socket = accept(nat->tcp_socket, (void *)from, &fromlen); + if (in_socket == INVALID_SOCKET) { + su_perror("new_tcp: accept"); + return 0; + } + + b = nat_binding_new(nat, "TCP", SOCK_STREAM, IPPROTO_TCP, 1, + in_socket, from, fromlen); + if (b == NULL) + return 0; + + return 0; +} + +static int tcp_in_to_out(struct nat *nat, su_wait_t *wait, struct binding *b) +{ + int events; + ssize_t n, m, o; + + events = su_wait_events(wait, b->in_socket); + + n = su_recv(b->in_socket, nat->buffer, sizeof nat->buffer, 0); + if (n < 0) { + su_perror("tcp_in_to_out: recv"); + return 0; + } + + if (n == 0) { + if (nat->logging) + printf("nat: tcp out FIN %s => %s\n", b->in_name, b->out_name); + shutdown(b->out_socket, 1); + su_root_eventmask(nat->root, b->in_register, b->in_socket, 0); + b->in_closed = 1; + if (b->out_closed && b->in_closed) + nat_binding_destroy(b); + return 0; + } + + for (m = 0; m < n; m += o) { + o = su_send(b->out_socket, nat->buffer + m, n - m, 0); + if (o < 0) { + su_perror("tcp_in_to_out: send"); + break; + } + } + + if (nat->logging) + printf("nat: tcp out %d/%d %s => %s\n", + (int)m, (int)n, b->in_name, b->out_name); + + return 0; +} + +static int tcp_out_to_in(struct nat *nat, su_wait_t *wait, struct binding *b) +{ + int events; + ssize_t n, m, o; + + events = su_wait_events(wait, b->out_socket); + + n = su_recv(b->out_socket, nat->buffer, sizeof nat->buffer, 0); + if (n < 0) { + su_perror("tcp_out_to_in: recv"); + return 0; + } + + if (n == 0) { + if (nat->logging) + printf("nat: tcp out FIN %s => %s\n", b->out_name, b->in_name); + shutdown(b->in_socket, 1); + su_root_eventmask(nat->root, b->in_register, b->out_socket, 0); + b->out_closed = 1; + if (b->out_closed && b->in_closed) + nat_binding_destroy(b); + return 0; + } + + for (m = 0; m < n; m += o) { + o = su_send(b->in_socket, nat->buffer + m, n - m, 0); + if (o < 0) { + if (su_errno() != EPIPE) + su_perror("tcp_in_to_out: send"); + break; + } + } + + if (nat->logging) + printf("nat: tcp in %d/%d %s => %s\n", + (int)m, (int)n, b->out_name, b->in_name); + + return 0; +} + +static int invalidate_binding(struct binding *b) +{ + struct nat *nat = b->nat; + su_sockaddr_t addr[1]; + socklen_t addrlen = (sizeof addr); + su_socket_t out; + int out_register; + su_wait_t wout[1]; + char name[64]; + + out = su_socket(nat->fake->li_family, b->socktype, 0); + if (out == INVALID_SOCKET) { + su_perror("new_udp: socket"); + return -1; + } + if (bind(out, (void *)nat->fake->li_addr, nat->fake->li_addrlen) < 0) { + su_perror("new_udp: bind(to)"); + su_close(out); + return -1; + } + + if (nat->symmetric) + if (connect(out, (void *)nat->out_address, nat->out_addrlen) < 0) { + su_perror("new_udp: connect(to)"); + su_close(out); + return -1; + } + + if (su_wait_create(wout, out, SU_WAIT_IN) < 0) { + su_perror("new_udp: su_wait_create"); + su_close(out); + return -1; + } + + if (b->socktype == SOCK_DGRAM) + out_register = su_root_register(nat->root, wout, udp_out_to_in, b, 0); + else + out_register = su_root_register(nat->root, wout, tcp_out_to_in, b, 0); + + if (out_register < 0) { + su_perror("new_udp: su_root_register"); + su_wait_destroy(wout); + su_close(out); + return -1; + } + + su_root_deregister(nat->root, b->out_register); + su_close(b->out_socket); + + b->out_socket = out; + b->out_register = out_register; + + getsockname(out, (void *)addr, &addrlen); + inet_ntop(addr->su_family, SU_ADDR(addr), name, sizeof name); + snprintf(b->out_name, sizeof b->out_name, + addr->su_family == AF_INET6 ? "[%s]:%u" : "%s:%u", + name, ntohs(addr->su_port)); + + if (nat->logging) + printf("nat: flushed binding %s <=> %s\n", b->in_name, b->out_name); + + return 0; +} + +LIST_BODIES(static, nat_filter, struct nat_filter, next, prev); + +struct args { + struct nat *nat; + struct nat_filter *f; + int outbound; +}; + +int execute_nat_filter_insert(void *_args) +{ + struct args *a = (struct args *)_args; + if (a->outbound) + nat_filter_insert(&a->nat->out_filters, a->f); + else + nat_filter_insert(&a->nat->in_filters, a->f); + return 0; +} + +int execute_nat_filter_remove(void *_args) +{ + struct args *a = (struct args *)_args; + nat_filter_remove(a->f); + return 0; +} + +struct nat_filter *test_nat_add_filter(struct nat *nat, + size_t (*condition)(void *arg, + void *message, + size_t len), + void *arg, + int outbound) +{ + struct args a[1]; + + if (nat == NULL) + return su_seterrno(EFAULT), NULL; + + a->nat = nat; + a->f = su_zalloc(nat->home, sizeof *a->f); + a->outbound = outbound; + + if (a->f) { + a->f->condition = condition; + a->f->arg = arg; + if (su_task_execute(su_clone_task(nat->clone), + execute_nat_filter_insert, a, NULL) < 0) + su_free(nat->home, a->f), a->f = NULL; + } + + return a->f; +} + + +int test_nat_remove_filter(struct nat *nat, + struct nat_filter *filter) +{ + struct args a[1]; + + if (nat == NULL) + return su_seterrno(EFAULT); + + a->nat = nat; + a->f = filter; + + if (su_task_execute(su_clone_task(nat->clone), + execute_nat_filter_remove, a, NULL) < 0) + return -1; + + su_free(nat->home, filter); + return 0; +} Added: freeswitch/trunk/libs/sofia-sip/tests/test_nat.h ============================================================================== --- (empty file) +++ freeswitch/trunk/libs/sofia-sip/tests/test_nat.h Wed May 14 15:10:54 2008 @@ -0,0 +1,74 @@ +/* + * This file is part of the Sofia-SIP package + * + * Copyright (C) 2005 Nokia Corporation. + * + * Contact: Pekka Pessi <pekka.pessi@nokia.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public License + * as published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + */ + +#ifndef TEST_NAT_H +#define TEST_NAT_H + +#include <sofia-sip/su_wait.h> +#include <sofia-sip/nta.h> + +SOFIA_BEGIN_DECLS + +struct nat; +struct nat_filter; + +struct nat *test_nat_create(su_root_t *, int family, + tag_type_t, tag_value_t, ...); + +void test_nat_destroy(struct nat *); + +int test_nat_private(struct nat *nat, void *address, socklen_t *return_addrlen); +int test_nat_public(struct nat *nat, void const *address, int addrlen); + +int test_nat_flush(struct nat *nat); + +struct nat_filter *test_nat_add_filter(struct nat *nat, + size_t (*condition)(void *arg, + void *message, + size_t len), + void *arg, + int outbound); + +enum { nat_inbound, nat_outbound }; + +int test_nat_remove_filter(struct nat *nat, + struct nat_filter *filter); + +/* Tags */ + +/** If true, act as symmetric nat. */ +#define TESTNATTAG_SYMMETRIC(x) testnattag_symmetric, tag_bool_v((x)) +#define TESTNATTAG_SYMMETRIC_REF(x) testnattag_symmetric_ref, tag_bool_vr(&(x)) +extern tag_typedef_t testnattag_symmetric; +extern tag_typedef_t testnattag_symmetric_ref; + +/** If true, print information about connections. */ +#define TESTNATTAG_LOGGING(x) testnattag_logging, tag_bool_v((x)) +#define TESTNATTAG_LOGGING_REF(x) testnattag_logging_ref, tag_bool_vr(&(x)) +extern tag_typedef_t testnattag_logging; +extern tag_typedef_t testnattag_logging_ref; + +SOFIA_END_DECLS + +#endif Added: freeswitch/trunk/libs/sofia-sip/tests/test_nat_tags.c ============================================================================== --- (empty file) +++ freeswitch/trunk/libs/sofia-sip/tests/test_nat_tags.c Wed May 14 15:10:54 2008 @@ -0,0 +1,40 @@ +/* + * This file is part of the Sofia-SIP package + * + * Copyright (C) 2005 Nokia Corporation. + * + * Contact: Pekka Pessi <pekka.pessi@nokia.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public License + * as published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + */ + +/**@CFILE test_nat_tags.c + * @brief Tags for simulated NAT + * + * @author Pekka Pessi <Pekka.Pessi@nokia.com> + * + * @date Created: Wed Mar 8 19:54:28 EET 2006 + */ + +#include "config.h" + +#include "test_nat.h" + +tag_typedef_t testnattag_symmetric = BOOLTAG_TYPEDEF(symmetric); +tag_typedef_t testnattag_symmetric_ref = REFTAG_TYPEDEF(testnattag_symmetric); +tag_typedef_t testnattag_logging = BOOLTAG_TYPEDEF(symmetric); +tag_typedef_t testnattag_logging_ref = REFTAG_TYPEDEF(testnattag_logging); Added: freeswitch/trunk/libs/sofia-sip/tests/test_nua.c ============================================================================== --- (empty file) +++ freeswitch/trunk/libs/sofia-sip/tests/test_nua.c Wed May 14 15:10:54 2008 @@ -0,0 +1,397 @@ +/* + * This file is part of the Sofia-SIP package + * + * Copyright (C) 2005 Nokia Corporation. + * + * Contact: Pekka Pessi <pekka.pessi@nokia.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public License + * as published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + */ + +/**@CFILE test_nua.c + * @brief High-level tester for Sofia SIP User Agent Engine + * + * @author Pekka Pessi <Pekka.Pessi@nokia.com> + * @author Martti Mela <Martti Mela@nokia.com> + * + * @date Created: Wed Aug 17 12:12:12 EEST 2005 ppessi + */ + +#include "config.h" + +#include "test_nua.h" + +#if HAVE_ALARM +#include <signal.h> +#endif + +#if defined(_WIN32) +#include <fcntl.h> +#endif + +SOFIAPUBVAR su_log_t nua_log[]; +SOFIAPUBVAR su_log_t soa_log[]; +SOFIAPUBVAR su_log_t nea_log[]; +SOFIAPUBVAR su_log_t nta_log[]; +SOFIAPUBVAR su_log_t tport_log[]; +SOFIAPUBVAR su_log_t su_log_default[]; + +char const name[] = "test_nua"; +int print_headings = 1; +int tstflags = 0; + +#if HAVE_FUNC +#elif HAVE_FUNCTION +#define __func__ __FUNCTION__ +#else +#define __func__ name +#endif + +#if HAVE_ALARM +static RETSIGTYPE sig_alarm(int s) +{ + fprintf(stderr, "%s: FAIL! test timeout!\n", name); + if (tstflags & tst_abort) + abort(); + exit(1); +} +#endif + +static char const options_usage[] = + " -v | --verbose be verbose\n" + " -q | --quiet be quiet\n" + " -a | --abort abort on error\n" + " -s use only single thread\n" + " -l level set logging level (0 by default)\n" + " -e | --events print nua events\n" + " -A print nua events for A\n" + " -B print nua events for B\n" + " -C print nua events for C\n" + " --log=a log messages for A\n" + " --log=b log messages for B\n" + " --log=c log messages for C\n" + " --log=proxy log messages for proxy\n" + " --attach print pid, wait for a debugger to be attached\n" + " --no-proxy do not use internal proxy\n" + " --no-nat do not use internal \"nat\"\n" + " --symmetric run internal \"nat\" in symmetric mode\n" + " -N print events from internal \"nat\"\n" + " --loop loop main tests for ever\n" + " --no-alarm don't ask for guard ALARM\n" + " -p uri specify uri of outbound proxy (implies --no-proxy)\n" + " --proxy-tests run tests involving proxy, too\n" +#if SU_HAVE_OSX_CF_API /* If compiled with CoreFoundation events */ + " --osx-runloop use OSX CoreFoundation runloop instead of poll() loop\n" +#endif + " -k do not exit after first error\n" + ; + +void usage(int exitcode) +{ + fprintf(stderr, "usage: %s OPTIONS\n where OPTIONS are\n%s", + name, options_usage); + exit(exitcode); +} + +int main(int argc, char *argv[]) +{ + int retval = 0; + int i, o_quiet = 0, o_attach = 0, o_alarm = 1, o_loop = 0; + int o_events_init = 0, o_events_a = 0, o_events_b = 0, o_events_c = 0; + int o_iproxy = 1, o_inat = 1; + int o_inat_symmetric = 0, o_inat_logging = 0, o_expensive = 0; + url_t const *o_proxy = NULL; + int level = 0; + + struct context ctx[1] = {{{ SU_HOME_INIT(ctx) }}}; + +#if HAVE_OPEN_C + dup2(1, 2); +#endif + + if (getenv("EXPENSIVE_CHECKS")) + o_expensive = 1; + + ctx->threading = 1; + ctx->quit_on_single_failure = 1; + + endpoint_init(ctx, &ctx->a, 'a'); + endpoint_init(ctx, &ctx->b, 'b'); + endpoint_init(ctx, &ctx->c, 'c'); + + for (i = 1; argv[i]; i++) { + if (strcmp(argv[i], "-v") == 0 || strcmp(argv[i], "--verbose") == 0) + tstflags |= tst_verbatim; + else if (strcmp(argv[i], "-a") == 0 || strcmp(argv[i], "--abort") == 0) + tstflags |= tst_abort; + else if (strcmp(argv[i], "-q") == 0 || strcmp(argv[i], "--quiet") == 0) + tstflags &= ~tst_verbatim, o_quiet = 1; + else if (strcmp(argv[i], "-k") == 0) + ctx->quit_on_single_failure = 0; + else if (strncmp(argv[i], "-l", 2) == 0) { + char *rest = NULL; + + if (argv[i][2]) + level = strtol(argv[i] + 2, &rest, 10); + else if (argv[i + 1]) + level = strtol(argv[i + 1], &rest, 10), i++; + else + level = 3, rest = ""; + + if (rest == NULL || *rest) + usage(1); + + su_log_set_level(nua_log, level); + su_log_soft_set_level(soa_log, level); + su_log_soft_set_level(nea_log, level); + su_log_soft_set_level(nta_log, level); + su_log_soft_set_level(tport_log, level); + } + else if (strcmp(argv[i], "-e") == 0 || strcmp(argv[i], "--events") == 0) { + o_events_init = o_events_a = o_events_b = o_events_c = 1; + } + else if (strcmp(argv[i], "-I") == 0) { + o_events_init = 1; + } + else if (strcmp(argv[i], "-A") == 0) { + o_events_a = 1; + } + else if (strcmp(argv[i], "-B") == 0) { + o_events_b = 1; + } + else if (strcmp(argv[i], "-C") == 0) { + o_events_c = 1; + } + else if (strcmp(argv[i], "-s") == 0) { + ctx->threading = 0; + } + else if (strcmp(argv[i], "--attach") == 0) { + o_attach = 1; + } + else if (strncmp(argv[i], "-p", 2) == 0) { + if (argv[i][2]) + o_proxy = URL_STRING_MAKE(argv[i] + 2)->us_url; + else if (!argv[++i] || argv[i][0] == '-') + usage(1); + else + o_proxy = URL_STRING_MAKE(argv[i])->us_url; + } + else if (strcmp(argv[i], "--proxy-tests") == 0) { + ctx->proxy_tests = 1; + } + else if (strcmp(argv[i], "--no-proxy") == 0) { + o_iproxy = 0; + } + else if (strcmp(argv[i], "--no-nat") == 0) { + o_inat = 0; + } + else if (strcmp(argv[i], "--nat") == 0) { + o_inat = 1; + } + else if (strcmp(argv[i], "--symmetric") == 0) { + o_inat_symmetric = 1; + } + else if (strcmp(argv[i], "-N") == 0) { + o_inat_logging = 1; + } + else if (strcmp(argv[i], "--expensive") == 0) { + o_expensive = 1; + } + else if (strcmp(argv[i], "--no-alarm") == 0) { + o_alarm = 0; + } + else if (strcmp(argv[i], "--loop") == 0) { + o_alarm = 0, o_loop = 1; + } + else if (strcmp(argv[i], "--print-tags") == 0) { + ctx->print_tags = 1; + } + else if (strcmp(argv[i], "--tags=a") == 0) { + ctx->a.print_tags = 1; + } + else if (strcmp(argv[i], "--tags=b") == 0) { + ctx->b.print_tags = 1; + } + else if (strcmp(argv[i], "--tags=c") == 0) { + ctx->c.print_tags = 1; + } + else if (strcmp(argv[i], "--log=a") == 0) { + ctx->a.logging = 1; + } + else if (strcmp(argv[i], "--log=b") == 0) { + ctx->b.logging = 1; + } + else if (strcmp(argv[i], "--log=c") == 0) { + ctx->c.logging = 1; + } + else if (strcmp(argv[i], "--log=proxy") == 0) { + ctx->proxy_logging = 1; + } +#if SU_HAVE_OSX_CF_API /* If compiled with CoreFoundation events */ + else if (strcmp(argv[i], "--osx-runloop") == 0) { + ctx->osx_runloop = 1; + } +#endif + else if (strcmp(argv[i], "-") == 0) { + i++; break; + } + else if (argv[i][0] != '-') { + break; + } + else { + fprintf(stderr, "test_nua: unknown argument \"%s\"\n\n", argv[i]); + usage(1); + } + } + + if (o_attach) { + char line[10], *l; + printf("%s: pid %lu\n", name, (unsigned long)getpid()); + printf("<Press RETURN to continue>\n"); + l = fgets(line, sizeof line, stdin); + } +#if HAVE_ALARM + else if (o_alarm) { + signal(SIGALRM, sig_alarm); + if (o_expensive) { + printf("%s: extending timeout to %u because expensive tests\n", + name, 240); + alarm(240); + } + else { + alarm(120); + } + } +#endif + +#if HAVE_OPEN_C + tstflags |= tst_verbatim; + level = 9; + o_inat = 1; /* No NATs */ + ctx->threading = 1; + ctx->quit_on_single_failure = 1; + su_log_soft_set_level(nua_log, level); + su_log_soft_set_level(soa_log, level); + su_log_soft_set_level(su_log_default, level); + su_log_soft_set_level(nea_log, level); + su_log_soft_set_level(nta_log, level); + su_log_soft_set_level(tport_log, level); + setenv("SU_DEBUG", "9", 1); + setenv("NUA_DEBUG", "9", 1); + setenv("NTA_DEBUG", "9", 1); + setenv("TPORT_DEBUG", "9", 1); + o_events_a = o_events_b = 1; +#endif + + su_init(); + + if (!(TSTFLAGS & tst_verbatim)) { + if (level == 0 && !o_quiet) + level = 1; + su_log_soft_set_level(nua_log, level); + su_log_soft_set_level(soa_log, level); + su_log_soft_set_level(su_log_default, level); + su_log_soft_set_level(nea_log, level); + su_log_soft_set_level(nta_log, level); + su_log_soft_set_level(tport_log, level); + } + + if (!o_quiet || (TSTFLAGS & tst_verbatim) + || o_events_a || o_events_b || o_events_c) + print_headings = 1; + +#if !HAVE_OPEN_C +#define SINGLE_FAILURE_CHECK() \ + do { fflush(stdout); \ + if (retval && ctx->quit_on_single_failure) { \ + su_deinit(); return retval; } \ + } while(0) +#else +#define SINGLE_FAILURE_CHECK() \ + do { fflush(stdout); \ + if (retval && ctx->quit_on_single_failure) { \ + su_deinit(); sleep(10); return retval; } \ + } while(0) +#endif + + ctx->a.printer = o_events_init ? print_event : NULL; + + retval |= test_nua_api_errors(ctx); SINGLE_FAILURE_CHECK(); + + retval |= test_tag_filter(); SINGLE_FAILURE_CHECK(); + + retval |= test_nua_params(ctx); SINGLE_FAILURE_CHECK(); + + retval |= test_nua_destroy(ctx); SINGLE_FAILURE_CHECK(); + + retval |= test_stack_errors(ctx); SINGLE_FAILURE_CHECK(); + + retval |= test_nua_init(ctx, o_iproxy, o_proxy, o_inat, + TESTNATTAG_SYMMETRIC(o_inat_symmetric), + TESTNATTAG_LOGGING(o_inat_logging), + TAG_END()); + + ctx->expensive = o_expensive; + + if (retval == 0) { + ctx->a.printer = o_events_a ? print_event : NULL; + if (o_events_b) + ctx->b.printer = print_event; + if (o_events_c) + ctx->c.printer = print_event; + + retval |= test_register(ctx); + + if (retval == 0) + retval |= test_connectivity(ctx); + + if (retval == 0 && o_inat) + retval |= test_nat_timeout(ctx); + + while (retval == 0) { + retval |= test_basic_call(ctx); SINGLE_FAILURE_CHECK(); + retval |= test_rejects(ctx); SINGLE_FAILURE_CHECK(); + retval |= test_call_cancel(ctx); SINGLE_FAILURE_CHECK(); + retval |= test_call_destroy(ctx); SINGLE_FAILURE_CHECK(); + retval |= test_early_bye(ctx); SINGLE_FAILURE_CHECK(); + retval |= test_offer_answer(ctx); SINGLE_FAILURE_CHECK(); + retval |= test_reinvites(ctx); SINGLE_FAILURE_CHECK(); + retval |= test_session_timer(ctx); SINGLE_FAILURE_CHECK(); + retval |= test_refer(ctx); SINGLE_FAILURE_CHECK(); + retval |= test_100rel(ctx); SINGLE_FAILURE_CHECK(); + retval |= test_simple(ctx); SINGLE_FAILURE_CHECK(); + retval |= test_events(ctx); SINGLE_FAILURE_CHECK(); + retval |= test_extension(ctx); SINGLE_FAILURE_CHECK(); + if (!o_loop) + break; + } + + if (ctx->proxy_tests && (retval == 0 || !ctx->p)) + retval |= test_unregister(ctx); SINGLE_FAILURE_CHECK(); + } + retval |= test_deinit(ctx); + + su_home_deinit(ctx->home); + + su_deinit(); + +#if HAVE_OPEN_C + sleep(7); +#endif + + return retval; +} Added: freeswitch/trunk/libs/sofia-sip/tests/test_nua.h ============================================================================== --- (empty file) +++ freeswitch/trunk/libs/sofia-sip/tests/test_nua.h Wed May 14 15:10:54 2008 @@ -0,0 +1,364 @@ +/* + * This file is part of the Sofia-SIP package + * + * Copyright (C) 2005 Nokia Corporation. + * + * Contact: Pekka Pessi <pekka.pessi@nokia.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public License + * as published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + */ + +/**@@internal + * @file test_nua.h + * @brief High-level tester framework for Sofia SIP User Agent Engine + * + * @author Pekka Pessi <Pekka.Pessi@nokia.com> + * @author Martti Mela <Martti Mela@nokia.com> + * + * @date Created: Wed Aug 17 12:12:12 EEST 2005 ppessi + */ + +#ifndef TEST_NUA_H +#define TEST_NUA_H + +struct context; +#define NUA_MAGIC_T struct context + +struct call; +#define NUA_HMAGIC_T struct call + +#include "sofia-sip/nua.h" +#include "sofia-sip/sip_status.h" + +#include <sofia-sip/sdp.h> +#include <sofia-sip/sip_header.h> + +#include <sofia-sip/su_log.h> +#include <sofia-sip/su_tagarg.h> +#include <sofia-sip/su_tag_io.h> +#include <sofia-sip/nua_tag.h> + +#if __APPLE_CC__ +#include <sofia-sip/su_osx_runloop.h> +#endif + +#include "test_proxy.h" +#include "test_nat.h" +#include <sofia-sip/auth_module.h> + +#include <stddef.h> +#include <stdlib.h> +#include <string.h> + +#include <assert.h> +#include <unistd.h> + +extern char const name[]; + +extern int print_headings; +extern int tstflags; +#define TSTFLAGS tstflags + +#include <sofia-sip/tstdef.h> + +#define TEST_E(a, b) TEST_S(nua_event_name(a), nua_event_name(b)) + +#define NONE ((void*)-1) + +struct endpoint; + +typedef +int condition_function(nua_event_t event, + int status, char const *phrase, + nua_t *nua, struct context *ctx, + struct endpoint *ep, + nua_handle_t *nh, struct call *call, + sip_t const *sip, + tagi_t tags[]); + +typedef +void printer_function(nua_event_t event, + char const *operation, + int status, char const *phrase, + nua_t *nua, struct context *ctx, + struct endpoint *ep, + nua_handle_t *nh, struct call *call, + sip_t const *sip, + tagi_t tags[]); + +struct proxy_transaction; +struct registration_entry; + +enum { event_is_extra, event_is_normal, event_is_special }; + +struct eventlist +{ + nua_event_t kind; + struct event *head, **tail; +}; + +struct event +{ + struct event *next, **prev; + struct call *call; + nua_saved_event_t saved_event[1]; + nua_event_data_t const *data; +}; + + +struct context +{ + su_home_t home[1]; + su_root_t *root; + + int threading, proxy_tests, expensive, quit_on_single_failure, osx_runloop; + int print_tags; + + url_t *external_proxy; + + int proxy_logging; + + struct endpoint { + char name[4]; + struct context *ctx; /* Backpointer */ + + int logging; + int print_tags; + + int running; + + struct domain *domain; + condition_function *next_condition; + nua_event_t next_event, last_event; + nua_t *nua; + sip_contact_t *contact; + sip_from_t *to; + + sip_allow_t *allow; + char const *appl_method; + sip_supported_t *supported; + + printer_function *printer; + + char const *instance; + + /* Per-call stuff */ + struct call { + struct call *next; + nua_handle_t *nh; + char const *sdp; + struct eventlist *events; + } call[1], reg[1]; + + int (*is_special)(nua_event_t e); + + /* Normal events are saved here */ + struct eventlist events[1]; + /* Special events are saved here */ + struct eventlist specials[1]; + + /* State flags for complex scenarios */ + struct { + unsigned n; + unsigned bit0:1, bit1:1, bit2:1, bit3:1; + unsigned bit4:1, bit5:1, bit6:1, bit7:1; + unsigned :0; + } flags; + /* Accross-run state information */ + struct { + unsigned n; + } state; + } a, b, c; + + struct proxy *p; + sip_route_t const *lr; + struct nat *nat; +}; + +#define RETURN_ON_SINGLE_FAILURE(retval) \ + do { \ + fflush(stdout); \ + if (retval && ctx->quit_on_single_failure) { return retval; } \ + } while(0) + + +int save_event_in_list(struct context *, + nua_event_t nevent, + struct endpoint *, + struct call *); +void free_events_in_list(struct context *, + struct eventlist *); +void free_event_in_list(struct context *ctx, + struct eventlist *list, + struct event *e); + +struct event *event_by_type(struct event *e, nua_event_t); +size_t count_events(struct event const *e); + +#define CONDITION_PARAMS \ + nua_event_t event, \ + int status, char const *phrase, \ + nua_t *nua, struct context *ctx, \ + struct endpoint *ep, \ + nua_handle_t *nh, struct call *call, \ + sip_t const *sip, \ + tagi_t tags[] + +int save_events(CONDITION_PARAMS); +int until_final_response(CONDITION_PARAMS); +int save_until_final_response(CONDITION_PARAMS); +int save_until_received(CONDITION_PARAMS); +int save_until_special(CONDITION_PARAMS); + +int until_terminated(CONDITION_PARAMS); +int until_ready(CONDITION_PARAMS); +int accept_call(CONDITION_PARAMS); +int cancel_when_ringing(CONDITION_PARAMS); + +int accept_notify(CONDITION_PARAMS); + +void a_callback(nua_event_t event, + int status, char const *phrase, + nua_t *nua, struct context *ctx, + nua_handle_t *nh, struct call *call, + sip_t const *sip, + tagi_t tags[]); +void b_callback(nua_event_t event, + int status, char const *phrase, + nua_t *nua, struct context *ctx, + nua_handle_t *nh, struct call *call, + sip_t const *sip, + tagi_t tags[]); +void c_callback(nua_event_t event, + int status, char const *phrase, + nua_t *nua, struct context *ctx, + nua_handle_t *nh, struct call *call, + sip_t const *sip, + tagi_t tags[]); + +void run_abc_until(struct context *ctx, + nua_event_t a_event, condition_function *a_condition, + nua_event_t b_event, condition_function *b_condition, + nua_event_t c_event, condition_function *c_condition); + +void run_ab_until(struct context *ctx, + nua_event_t a_event, condition_function *a_condition, + nua_event_t b_event, condition_function *b_condition); + +void run_bc_until(struct context *ctx, + nua_event_t b_event, condition_function *b_condition, + nua_event_t c_event, condition_function *c_condition); + +int run_a_until(struct context *, nua_event_t, condition_function *); +int run_b_until(struct context *, nua_event_t, condition_function *); +int run_c_until(struct context *, nua_event_t, condition_function *); + +typedef int operation_f(struct endpoint *ep, struct call *call, + nua_handle_t *nh, tag_type_t tag, tag_value_t value, + ...); + +operation_f INVITE, ACK, BYE, CANCEL, AUTHENTICATE, UPDATE, INFO, PRACK, + REFER, MESSAGE, METHOD, OPTIONS, PUBLISH, UNPUBLISH, REGISTER, UNREGISTER, + SUBSCRIBE, UNSUBSCRIBE, NOTIFY, NOTIFIER, TERMINATE, AUTHORIZE; + +int RESPOND(struct endpoint *ep, + struct call *call, + nua_handle_t *nh, + int status, char const *phrase, + tag_type_t tag, tag_value_t value, + ...); + +int DESTROY(struct endpoint *ep, + struct call *call, + nua_handle_t *nh); + +struct call *check_handle(struct endpoint *ep, + struct call *call, + nua_handle_t *nh, + int status, char const *phrase); + +int is_special(nua_event_t e); +int callstate(tagi_t const *tags); +int is_offer_sent(tagi_t const *tags); +int is_answer_sent(tagi_t const *tags); +int is_offer_recv(tagi_t const *tags); +int is_answer_recv(tagi_t const *tags); +int is_offer_answer_done(tagi_t const *tags); +int audio_activity(tagi_t const *tags); +int video_activity(tagi_t const *tags); + +void print_event(nua_event_t event, + char const *operation, + int status, char const *phrase, + nua_t *nua, struct context *ctx, + struct endpoint *ep, + nua_handle_t *nh, struct call *call, + sip_t const *sip, + tagi_t tags[]); + +su_inline +void eventlist_init(struct eventlist *list) +{ + list->tail = &list->head; +} + +su_inline +void call_init(struct call *call) +{ +} + +void endpoint_init(struct context *ctx, struct endpoint *e, char id); + +int test_nua_init(struct context *ctx, + int start_proxy, + url_t const *o_proxy, + int start_nat, + tag_type_t tag, tag_value_t value, ...); + +int test_deinit(struct context *ctx); + +int test_nua_api_errors(struct context *ctx); +int test_nua_destroy(struct context *ctx); +int test_stack_errors(struct context *ctx); +int test_tag_filter(void); +int test_nua_params(struct context *ctx); + +int test_register(struct context *ctx); +int test_connectivity(struct context *ctx); +int test_nat_timeout(struct context *ctx); +int test_unregister(struct context *ctx); + +int test_basic_call(struct context *ctx); +int test_offer_answer(struct context *ctx); +int test_rejects(struct context *ctx); +int test_mime_negotiation(struct context *ctx); +int test_call_timeouts(struct context *ctx); +int test_reject_401_aka(struct context *ctx); +int test_call_cancel(struct context *ctx); +int test_call_destroy(struct context *ctx); +int test_early_bye(struct context *ctx); +int test_call_hold(struct context *ctx); +int test_reinvites(struct context *ctx); +int test_session_timer(struct context *ctx); +int test_refer(struct context *ctx); +int test_100rel(struct context *ctx); +int test_simple(struct context *ctx); +int test_events(struct context *ctx); + +int test_extension(struct context *ctx); + +#endif Added: freeswitch/trunk/libs/sofia-sip/tests/test_nua_api.c ============================================================================== --- (empty file) +++ freeswitch/trunk/libs/sofia-sip/tests/test_nua_api.c Wed May 14 15:10:54 2008 @@ -0,0 +1,460 @@ +/* + * This file is part of the Sofia-SIP package + * + * Copyright (C) 2005 Nokia Corporation. + * + * Contact: Pekka Pessi <pekka.pessi@nokia.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public License + * as published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + */ + +/**@CFILE test_nua_api.c + * @brief NUA API tester. + * + * @author Pekka Pessi <Pekka.Pessi@nokia.com> + * @author Martti Mela <Martti Mela@nokia.com> + * + * @date Created: Wed Aug 17 12:12:12 EEST 2005 ppessi + */ + +#include "config.h" + +#include "test_nua.h" +#include "sofia-sip/tport_tag.h" + +#if HAVE_FUNC +#elif HAVE_FUNCTION +#define __func__ __FUNCTION__ +#else +#define __func__ "test_nua_api_errors" +#endif + +/* ------------------------------------------------------------------------ */ +/* API tests */ + +SOFIAPUBVAR su_log_t nua_log[]; + +int check_set_status(int status, char const *phrase) +{ + return status == 200 && strcmp(phrase, sip_200_OK) == 0; +} + +int test_nua_api_errors(struct context *ctx) +{ + BEGIN(); + + /* Invoke every API function with invalid arguments */ + + int level; + + int status; char const *phrase; + + if (print_headings) + printf("TEST NUA-1.0: test API\n"); + + /* This is a nasty macro. Test it. */ +#define SET_STATUS1(x) ((status = x), status), (phrase = ((void)x)) + TEST_1(check_set_status(SET_STATUS1(SIP_200_OK))); + TEST(status, 200); TEST_S(phrase, sip_200_OK); + + su_log_init(nua_log); + level = nua_log->log_level; + if (!(tstflags & tst_verbatim)) + su_log_set_level(nua_log, 0); /* Log at level 0 by default */ + + TEST_1(!nua_create(NULL, NULL, NULL, TAG_END())); + TEST_VOID(nua_shutdown(NULL)); + TEST_VOID(nua_destroy(NULL)); + TEST_VOID(nua_set_params(NULL, TAG_END())); + TEST_VOID(nua_get_params(NULL, TAG_END())); + TEST_1(!nua_default(NULL)); + TEST_1(!nua_handle(NULL, NULL, TAG_END())); + TEST_VOID(nua_handle_destroy(NULL)); + TEST_VOID(nua_handle_bind(NULL, NULL)); + TEST_1(!nua_handle_has_invite(NULL)); + TEST_1(!nua_handle_has_subscribe(NULL)); + TEST_1(!nua_handle_has_register(NULL)); + TEST_1(!nua_handle_has_active_call(NULL)); + TEST_1(!nua_handle_has_call_on_hold(NULL)); + TEST_1(!nua_handle_has_events(NULL)); + TEST_1(!nua_handle_has_registrations(NULL)); + TEST_1(!nua_handle_remote(NULL)); + TEST_1(!nua_handle_local(NULL)); + TEST_S(nua_event_name(-1), "NUA_UNKNOWN"); + TEST_VOID(nua_register(NULL, TAG_END())); + TEST_VOID(nua_unregister(NULL, TAG_END())); + TEST_VOID(nua_invite(NULL, TAG_END())); + TEST_VOID(nua_ack(NULL, TAG_END())); + TEST_VOID(nua_prack(NULL, TAG_END())); + TEST_VOID(nua_options(NULL, TAG_END())); + TEST_VOID(nua_publish(NULL, TAG_END())); + TEST_VOID(nua_message(NULL, TAG_END())); + TEST_VOID(nua_chat(NULL, TAG_END())); + TEST_VOID(nua_info(NULL, TAG_END())); + TEST_VOID(nua_subscribe(NULL, TAG_END())); + TEST_VOID(nua_unsubscribe(NULL, TAG_END())); + TEST_VOID(nua_notify(NULL, TAG_END())); + TEST_VOID(nua_notifier(NULL, TAG_END())); + TEST_VOID(nua_terminate(NULL, TAG_END())); + TEST_VOID(nua_refer(NULL, TAG_END())); + TEST_VOID(nua_update(NULL, TAG_END())); + TEST_VOID(nua_bye(NULL, TAG_END())); + TEST_VOID(nua_cancel(NULL, TAG_END())); + TEST_VOID(nua_authenticate(NULL, TAG_END())); + TEST_VOID(nua_redirect(NULL, TAG_END())); + TEST_VOID(nua_respond(NULL, 0, "", TAG_END())); + + TEST_1(!nua_handle_home(NULL)); + TEST_1(!nua_save_event(NULL, NULL)); + TEST_1(!nua_event_data(NULL)); + TEST_VOID(nua_destroy_event(NULL)); + + { + nua_saved_event_t event[1]; + + memset(event, 0, sizeof event); + + TEST_1(!nua_save_event(NULL, event)); + TEST_1(!nua_event_data(event)); + TEST_VOID(nua_destroy_event(event)); + } + + su_log_set_level(nua_log, level); + + if (print_headings) + printf("TEST NUA-1.0: PASSED\n"); + + END(); +} + +/* ======================================================================== */ + +void destroy_callback(nua_event_t event, + int status, char const *phrase, + nua_t *nua, struct context *ctx, + nua_handle_t *nh, struct call *call, + sip_t const *sip, + tagi_t tags[]) +{ + if (status >= 200) { + nua_destroy(ctx->a.nua), ctx->a.nua = NULL; + su_root_break(ctx->root); + } +} + +/* Test different nua_destroy() corner cases */ +int test_nua_destroy(struct context *ctx) +{ + BEGIN(); + + struct endpoint *a = &ctx->a; + + TEST_1(ctx->root = su_root_create(NULL)); + +#if 0 + a->nua = nua_create(ctx->root, destroy_callback, ctx, + NUTAG_URL("sip:0.0.0.0:*"), + TAG_IF(ctx->a.logging, TPTAG_LOG(1)), + TAG_END()); + TEST_1(a->nua); + + nua_get_params(a->nua, TAG_ANY(), TAG_END()); + + su_root_run(ctx->root); + + TEST_1(a->nua == NULL); +#endif + + a->nua = nua_create(ctx->root, destroy_callback, ctx, + NUTAG_URL("sip:0.0.0.0:*"), + TAG_IF(ctx->a.logging, TPTAG_LOG(1)), + TAG_END()); + TEST_1(a->nua); + + nua_shutdown(a->nua); + + su_root_run(ctx->root); + + TEST_1(a->nua == NULL); + + su_root_destroy(ctx->root), ctx->root = NULL; + + END(); +} + +/* ======================================================================== */ + +int test_byecancel_without_invite(struct context *ctx) +{ + BEGIN(); + + struct endpoint *a = &ctx->a, *b = &ctx->b; + struct call *a_call = a->call; + struct event *e; + + int internal_error = 900; + + if (print_headings) + printf("TEST NUA-1.2.1: CANCEL without INVITE\n"); + + TEST_1(a_call->nh = nua_handle(a->nua, a_call, SIPTAG_TO(b->to), TAG_END())); + + CANCEL(a, a_call, a_call->nh, TAG_END()); + + run_a_until(ctx, -1, save_until_final_response); + + TEST_1(e = a->events->head); TEST_E(e->data->e_event, nua_r_cancel); + TEST(e->data->e_status, 481); + TEST_1(!e->next); + + free_events_in_list(ctx, a->events); + nua_handle_destroy(a_call->nh), a_call->nh = NULL; + + if (print_headings) + printf("TEST NUA-1.2.1: PASSED\n"); + + /* -BYE without INVITE--------------------------------------------------- */ + + if (print_headings) + printf("TEST NUA-1.2.2: BYE without INVITE\n"); + + a_call->nh = nua_handle(a->nua, a_call, SIPTAG_TO(b->to), TAG_END()); + TEST_1(a_call->nh); + + BYE(a, a_call, a_call->nh, TAG_END()); + + run_a_until(ctx, -1, save_until_final_response); + + TEST_1(e = a->events->head); TEST_E(e->data->e_event, nua_r_bye); + TEST(e->data->e_status, internal_error); + TEST_1(!e->next); + + free_events_in_list(ctx, a->events); + nua_handle_destroy(a_call->nh), a_call->nh = NULL; + + if (print_headings) + printf("TEST NUA-1.2.2: PASSED\n"); + + END(); +} + + +int test_unregister_without_register(struct context *ctx) +{ + BEGIN(); + + struct endpoint *a = &ctx->a, *b = &ctx->b; + struct call *a_call = a->call; + struct event *e; + + /* -Un-register without REGISTER--------------------------------------- */ + + if (print_headings) + printf("TEST NUA-1.2.3: unregister without register\n"); + + a_call->nh = nua_handle(a->nua, a_call, SIPTAG_TO(a->to), TAG_END()); + TEST_1(a_call->nh); + + UNREGISTER(a, a_call, a_call->nh, TAG_END()); + + run_a_until(ctx, -1, save_until_final_response); + + TEST_1(e = a->events->head); TEST_E(e->data->e_event, nua_r_unregister); + if (e->data->e_status == 401) + TEST(e->data->e_status, 401); + else + TEST(e->data->e_status, 406); + TEST_1(!e->next); + + free_events_in_list(ctx, a->events); + nua_handle_destroy(a_call->nh), a_call->nh = NULL; + + if (print_headings) + printf("TEST NUA-1.2.3: PASSED\n"); + + /* -Un-publish without publish--------------------------------------- */ + + if (print_headings) + printf("TEST NUA-1.2.4: unpublish without publish\n"); + + a_call->nh = nua_handle(a->nua, a_call, SIPTAG_TO(b->to), TAG_END()); + TEST_1(a_call->nh); + + UNPUBLISH(a, a_call, a_call->nh, TAG_END()); + + run_a_until(ctx, -1, save_until_final_response); + + TEST_1(e = a->events->head); TEST_E(e->data->e_event, nua_r_unpublish); + TEST(e->data->e_status, 404); + TEST_1(!e->next); + + free_events_in_list(ctx, a->events); + nua_handle_destroy(a_call->nh), a_call->nh = NULL; + + if (print_headings) + printf("TEST NUA-1.2.4: PASSED\n"); + + END(); +} + +/* -terminate without notifier--------------------------------------- */ + +int test_terminate_without_notifier(struct context *ctx) +{ + BEGIN(); + + struct endpoint *a = &ctx->a, *b = &ctx->b; + struct call *a_call = a->call; + struct event *e; + + int internal_error = 900; + + if (print_headings) + printf("TEST NUA-1.2.5: terminate without notifier\n"); + + TEST_1(a_call->nh = nua_handle(a->nua, a_call, SIPTAG_TO(b->to), TAG_END())); + + TERMINATE(a, a_call, a_call->nh, TAG_END()); + + run_a_until(ctx, -1, save_until_final_response); + + TEST_1(e = a->events->head); TEST_E(e->data->e_event, nua_r_terminate); + TEST(e->data->e_status, internal_error); + TEST_1(!e->next); + + free_events_in_list(ctx, a->events); + + AUTHORIZE(a, a_call, a_call->nh, TAG_END()); + + run_a_until(ctx, -1, save_until_final_response); + + TEST_1(e = a->events->head); TEST_E(e->data->e_event, nua_r_authorize); + TEST(e->data->e_status, internal_error); + TEST_1(!e->next); + + free_events_in_list(ctx, a->events); + + nua_handle_destroy(a_call->nh), a_call->nh = NULL; + + if (print_headings) + printf("TEST NUA-1.2.5: PASSED\n"); + + END(); +} + +int destroy_on_503(CONDITION_PARAMS) +{ + save_event_in_list(ctx, event, ep, call); + + if (status == 503) { + assert(nh == call->nh); + nua_handle_destroy(call->nh), call->nh = NULL; + } + + return + nua_r_set_params <= event && event < nua_i_network_changed + && status >= 200; +} + + +int test_register_503(struct context *ctx) +{ + BEGIN(); + + struct endpoint *a = &ctx->a; + struct call *a_reg = a->reg; + struct event *e; + +/* REGISTER test + + A + |------REGISTER--\ + |<-------503-----/ + | + +*/ + + if (print_headings) + printf("TEST NUA-1.2.6: REGISTER with bad domain\n"); + + TEST_1(a_reg->nh = nua_handle(a->nua, a_reg, TAG_END())); + + REGISTER(a, a_reg, a_reg->nh, + NUTAG_REGISTRAR(URL_STRING_MAKE("sip:bad.domain")), + SIPTAG_TO_STR("sip:lissu@bad.domain"), + TAG_END()); + run_a_until(ctx, -1, destroy_on_503); + + TEST_1(e = a->events->head); + TEST_E(e->data->e_event, nua_r_register); + TEST(e->data->e_status, 503); + TEST_1(!e->next); + free_events_in_list(ctx, a->events); + + TEST_1(a_reg->nh = nua_handle(a->nua, a_reg, TAG_END())); + + REGISTER(a, a_reg, a_reg->nh, + NUTAG_REGISTRAR(URL_STRING_MAKE("sip:bad.domain")), + SIPTAG_TO_STR("sip:lissu@bad.domain"), + TAG_END()); + nua_handle_destroy(a_reg->nh), a_reg->nh = NULL; + + if (print_headings) + printf("TEST NUA-1.2.6: PASSED\n"); + + END(); +} + +int test_stack_errors(struct context *ctx) +{ + BEGIN(); + + struct endpoint *a = &ctx->a; + + if (print_headings) + printf("TEST NUA-1.2: Stack error handling\n"); + + TEST_1(ctx->root == NULL); + TEST_1(ctx->root = su_root_create(NULL)); + + a->nua = nua_create(ctx->root, a_callback, ctx, + NUTAG_URL("sip:0.0.0.0:*"), + TAG_IF(ctx->a.logging, TPTAG_LOG(1)), + TAG_END()); + TEST_1(a->nua); + + TEST(test_byecancel_without_invite(ctx), 0); + if (ctx->proxy_tests) + TEST(test_unregister_without_register(ctx), 0); + TEST(test_terminate_without_notifier(ctx), 0); + TEST(test_register_503(ctx), 0); + TEST(test_register_503(ctx), 0); + + nua_shutdown(a->nua); + + run_a_until(ctx, -1, until_final_response); + + TEST_VOID(nua_destroy(a->nua)); + + su_root_destroy(ctx->root), ctx->root = NULL; + + if (print_headings) + printf("TEST NUA-1.2: PASSED\n"); + + END(); +} + Added: freeswitch/trunk/libs/sofia-sip/tests/test_nua_params.c ============================================================================== --- (empty file) +++ freeswitch/trunk/libs/sofia-sip/tests/test_nua_params.c Wed May 14 15:10:54 2008 @@ -0,0 +1,656 @@ +/* + * This file is part of the Sofia-SIP package + * + * Copyright (C) 2005 Nokia Corporation. + * + * Contact: Pekka Pessi <pekka.pessi@nokia.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public License + * as published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + */ + +/**@CFILE test_nua_params.c + * @brief Test NUA parameter handling. + * + * @author Pekka Pessi <Pekka.Pessi@nokia.com> + * @author Martti Mela <Martti Mela@nokia.com> + * + * @date Created: Wed Aug 17 12:12:12 EEST 2005 ppessi + */ + +#include "config.h" + +#include "test_nua.h" +#include <sofia-sip/su_tag_class.h> + +#if HAVE_FUNC +#elif HAVE_FUNCTION +#define __func__ __FUNCTION__ +#else +#define __func__ "test_nua_params" +#endif + +sip_route_t *GLOBAL_ROUTE; + +int test_tag_filter(void) +{ + BEGIN(); + +#undef TAG_NAMESPACE +#define TAG_NAMESPACE "test" + tag_typedef_t tag_a = STRTAG_TYPEDEF(a); +#define TAG_A(s) tag_a, tag_str_v((s)) + tag_typedef_t tag_b = STRTAG_TYPEDEF(b); +#define TAG_B(s) tag_b, tag_str_v((s)) + + tagi_t filter[2] = {{ NUTAG_ANY() }, { TAG_END() }}; + + tagi_t *lst, *result; + + lst = tl_list(TAG_A("X"), + TAG_SKIP(2), + NUTAG_URL((void *)"urn:foo"), + TAG_B("Y"), + NUTAG_URL((void *)"urn:bar"), + TAG_NULL()); + + TEST_1(lst); + + result = tl_afilter(NULL, filter, lst); + + TEST_1(result); + TEST_P(result[0].t_tag, nutag_url); + TEST_P(result[1].t_tag, nutag_url); + + tl_vfree(lst); + free(result); + + END(); +} + +int test_nua_params(struct context *ctx) +{ + BEGIN(); + + char const Alice[] = "Alice <sip:a@wonderland.org>"; + sip_from_t const *from; + su_home_t tmphome[SU_HOME_AUTO_SIZE(16384)]; + nua_handle_t *nh; + struct event *e; + tagi_t const *t; + int n; + + su_home_auto(tmphome, sizeof(tmphome)); + + if (print_headings) + printf("TEST NUA-1.1: PARAMETERS\n"); + +#if SU_HAVE_OSX_CF_API + if (ctx->osx_runloop) + ctx->root = su_root_osx_runloop_create(NULL); + else +#endif + ctx->root = su_root_create(NULL); + TEST_1(ctx->root); + + su_root_threading(ctx->root, ctx->threading); + + ctx->a.nua = nua_create(ctx->root, a_callback, ctx, + SIPTAG_FROM_STR("sip:alice@example.com"), + NUTAG_URL("sip:0.0.0.0:*;transport=udp"), + TAG_END()); + + TEST_1(ctx->a.nua); + + nua_get_params(ctx->a.nua, TAG_ANY(), TAG_END()); + run_a_until(ctx, nua_r_get_params, save_until_final_response); + + TEST_1(e = ctx->a.specials->head); + TEST_E(e->data->e_event, nua_r_get_params); + for (n = 0, t = e->data->e_tags; t; n++, t = tl_next(t)) + ; + TEST_1(n > 32); + free_events_in_list(ctx, ctx->a.specials); + + nh = nua_handle(ctx->a.nua, NULL, TAG_END()); TEST_1(nh); + nua_handle_unref(nh); + + nh = nua_handle(ctx->a.nua, NULL, TAG_END()); TEST_1(nh); + nua_handle_destroy(nh); + + from = sip_from_make(tmphome, Alice); + + nh = nua_handle(ctx->a.nua, NULL, TAG_END()); + + nua_set_hparams(nh, NUTAG_INVITE_TIMER(90), TAG_END()); + run_a_until(ctx, nua_r_set_params, until_final_response); + + /* Modify all pointer values */ + nua_set_params(ctx->a.nua, + SIPTAG_FROM_STR(Alice), + + NUTAG_MEDIA_ENABLE(0), + NUTAG_SOA_NAME("test"), + + NUTAG_REGISTRAR("sip:openlaboratory.net"), + + SIPTAG_SUPPORTED_STR("test"), + SIPTAG_ALLOW_STR("DWIM, OPTIONS, INFO"), + NUTAG_APPL_METHOD(NULL), + NUTAG_APPL_METHOD("INVITE, REGISTER, PUBLISH, SUBSCRIBE"), + SIPTAG_ALLOW_EVENTS_STR("reg"), + SIPTAG_USER_AGENT_STR("test_nua/1.0"), + + SIPTAG_ORGANIZATION_STR("Open Laboratory"), + + NUTAG_M_DISPLAY("XXX"), + NUTAG_M_USERNAME("xxx"), + NUTAG_M_PARAMS("user=ip"), + NUTAG_M_FEATURES("language=\"fi\""), + NUTAG_INSTANCE("urn:uuid:3eb007b1-6d7f-472e-8b64-29e482795da8"), + NUTAG_OUTBOUND("bar"), + + NUTAG_INITIAL_ROUTE(NULL), + NUTAG_INITIAL_ROUTE(sip_route_make(tmphome, "<sip:tst@example.net;lr>")), + NUTAG_INITIAL_ROUTE_STR("<sip:str1@example.net;lr>"), + NUTAG_INITIAL_ROUTE_STR("sip:str2@example.net;lr=foo"), + NUTAG_INITIAL_ROUTE_STR(NULL), + + TAG_END()); + + run_a_until(ctx, nua_r_set_params, until_final_response); + + /* Modify everything from their default value */ + nua_set_params(ctx->a.nua, + SIPTAG_FROM(from), + NUTAG_RETRY_COUNT(9), + NUTAG_MAX_SUBSCRIPTIONS(6), + + NUTAG_ENABLEINVITE(0), + NUTAG_AUTOALERT(1), + NUTAG_EARLY_MEDIA(1), + NUTAG_AUTOANSWER(1), + NUTAG_AUTOACK(0), + NUTAG_INVITE_TIMER(60), + + NUTAG_SESSION_TIMER(600), + NUTAG_MIN_SE(35), + NUTAG_SESSION_REFRESHER(nua_remote_refresher), + NUTAG_UPDATE_REFRESH(1), + + NUTAG_ENABLEMESSAGE(0), + NUTAG_ENABLEMESSENGER(1), + /* NUTAG_MESSAGE_AUTOANSWER(0), */ + + NUTAG_CALLEE_CAPS(1), + NUTAG_MEDIA_FEATURES(1), + NUTAG_SERVICE_ROUTE_ENABLE(0), + NUTAG_PATH_ENABLE(0), + NUTAG_AUTH_CACHE(nua_auth_cache_challenged), + NUTAG_REFER_EXPIRES(333), + NUTAG_REFER_WITH_ID(0), + NUTAG_SUBSTATE(nua_substate_pending), + NUTAG_SUB_EXPIRES(3700), + + NUTAG_KEEPALIVE(66), + NUTAG_KEEPALIVE_STREAM(33), + + NUTAG_INSTANCE("urn:uuid:97701ad9-39df-1229-1083-dbc0a85f029c"), + NUTAG_M_DISPLAY("Joe"), + NUTAG_M_USERNAME("joe"), + NUTAG_M_PARAMS("user=phone"), + NUTAG_M_FEATURES("language=\"en\""), + NUTAG_OUTBOUND("foo"), + SIPTAG_SUPPORTED(sip_supported_make(tmphome, "foo")), + NUTAG_SUPPORTED("foo, bar"), + SIPTAG_SUPPORTED_STR(",baz,"), + + SIPTAG_ALLOW_STR("OPTIONS"), + SIPTAG_ALLOW(sip_allow_make(tmphome, "INFO")), + NUTAG_ALLOW("ACK, INFO"), + + NUTAG_APPL_METHOD("NOTIFY"), + + SIPTAG_ALLOW_EVENTS_STR("reg"), + SIPTAG_ALLOW_EVENTS(sip_allow_events_make(tmphome, "presence")), + NUTAG_ALLOW_EVENTS("presence.winfo"), + + NUTAG_INITIAL_ROUTE(NULL), + NUTAG_INITIAL_ROUTE(sip_route_make(nua_handle_home(nh), "<sip:1@example.com;lr>")), + NUTAG_INITIAL_ROUTE_STR("<sip:2@example.com;lr>"), + /* Check for sip_route_fix() */ + NUTAG_INITIAL_ROUTE_STR("sip:3@example.com;lr=foo"), + NUTAG_INITIAL_ROUTE_STR(NULL), + + SIPTAG_USER_AGENT(sip_user_agent_make(tmphome, "test_nua")), + + SIPTAG_ORGANIZATION(sip_organization_make(tmphome, "Pussy Galore's Flying Circus")), + + NUTAG_MEDIA_ENABLE(0), + NUTAG_REGISTRAR(url_hdup(tmphome, (url_t *)"sip:sip.wonderland.org")), + + TAG_END()); + + run_a_until(ctx, nua_r_set_params, until_final_response); + + /* Modify something... */ + nua_set_params(ctx->a.nua, + NUTAG_RETRY_COUNT(5), + TAG_END()); + run_a_until(ctx, nua_r_set_params, until_final_response); + + { + sip_from_t const *from = NONE; + char const *from_str = "NONE"; + + unsigned retry_count = (unsigned)-1; + unsigned max_subscriptions = (unsigned)-1; + + char const *soa_name = "NONE"; + int media_enable = -1; + int invite_enable = -1; + int auto_alert = -1; + int early_media = -1; + int only183_100rel = -1; + int auto_answer = -1; + int auto_ack = -1; + unsigned invite_timeout = (unsigned)-1; + + unsigned session_timer = (unsigned)-1; + unsigned min_se = (unsigned)-1; + int refresher = -1; + int update_refresh = -1; + + int message_enable = -1; + int win_messenger_enable = -1; + int message_auto_respond = -1; + + int callee_caps = -1; + int media_features = -1; + int service_route_enable = -1; + int path_enable = -1; + int auth_cache = -1; + unsigned refer_expires = (unsigned)-1; + int refer_with_id = -1; + unsigned sub_expires = (unsigned)-1; + int substate = -1; + + sip_allow_t const *allow = NONE; + char const *allow_str = "NONE"; + char const *appl_method = "NONE"; + sip_allow_events_t const *allow_events = NONE; + char const *allow_events_str = "NONE"; + sip_supported_t const *supported = NONE; + char const *supported_str = "NONE"; + sip_user_agent_t const *user_agent = NONE; + char const *user_agent_str = "NONE"; + char const *ua_name = "NONE"; + sip_organization_t const *organization = NONE; + char const *organization_str = "NONE"; + + sip_route_t const *initial_route = NONE; + char const *initial_route_str = NONE; + + char const *outbound = "NONE"; + char const *m_display = "NONE"; + char const *m_username = "NONE"; + char const *m_params = "NONE"; + char const *m_features = "NONE"; + char const *instance = "NONE"; + + url_string_t const *registrar = NONE; + unsigned keepalive = (unsigned)-1, keepalive_stream = (unsigned)-1; + + nua_get_params(ctx->a.nua, TAG_ANY(), TAG_END()); + run_a_until(ctx, nua_r_get_params, save_until_final_response); + + TEST_1(e = ctx->a.specials->head); + TEST_E(e->data->e_event, nua_r_get_params); + + n = tl_gets(e->data->e_tags, + SIPTAG_FROM_REF(from), + SIPTAG_FROM_STR_REF(from_str), + + NUTAG_RETRY_COUNT_REF(retry_count), + NUTAG_MAX_SUBSCRIPTIONS_REF(max_subscriptions), + + NUTAG_SOA_NAME_REF(soa_name), + NUTAG_MEDIA_ENABLE_REF(media_enable), + NUTAG_ENABLEINVITE_REF(invite_enable), + NUTAG_AUTOALERT_REF(auto_alert), + NUTAG_EARLY_MEDIA_REF(early_media), + NUTAG_ONLY183_100REL_REF(only183_100rel), + NUTAG_AUTOANSWER_REF(auto_answer), + NUTAG_AUTOACK_REF(auto_ack), + NUTAG_INVITE_TIMER_REF(invite_timeout), + + NUTAG_SESSION_TIMER_REF(session_timer), + NUTAG_MIN_SE_REF(min_se), + NUTAG_SESSION_REFRESHER_REF(refresher), + NUTAG_UPDATE_REFRESH_REF(update_refresh), + + NUTAG_ENABLEMESSAGE_REF(message_enable), + NUTAG_ENABLEMESSENGER_REF(win_messenger_enable), + /* NUTAG_MESSAGE_AUTOANSWER(message_auto_respond), */ + + NUTAG_CALLEE_CAPS_REF(callee_caps), + NUTAG_MEDIA_FEATURES_REF(media_features), + NUTAG_SERVICE_ROUTE_ENABLE_REF(service_route_enable), + NUTAG_PATH_ENABLE_REF(path_enable), + NUTAG_AUTH_CACHE_REF(auth_cache), + NUTAG_REFER_EXPIRES_REF(refer_expires), + NUTAG_REFER_WITH_ID_REF(refer_with_id), + NUTAG_SUBSTATE_REF(substate), + NUTAG_SUB_EXPIRES_REF(sub_expires), + + SIPTAG_SUPPORTED_REF(supported), + SIPTAG_SUPPORTED_STR_REF(supported_str), + SIPTAG_ALLOW_REF(allow), + SIPTAG_ALLOW_STR_REF(allow_str), + NUTAG_APPL_METHOD_REF(appl_method), + SIPTAG_ALLOW_EVENTS_REF(allow_events), + SIPTAG_ALLOW_EVENTS_STR_REF(allow_events_str), + SIPTAG_USER_AGENT_REF(user_agent), + SIPTAG_USER_AGENT_STR_REF(user_agent_str), + NUTAG_USER_AGENT_REF(ua_name), + + SIPTAG_ORGANIZATION_REF(organization), + SIPTAG_ORGANIZATION_STR_REF(organization_str), + + NUTAG_INITIAL_ROUTE_REF(initial_route), + NUTAG_INITIAL_ROUTE_STR_REF(initial_route_str), + + NUTAG_REGISTRAR_REF(registrar), + NUTAG_KEEPALIVE_REF(keepalive), + NUTAG_KEEPALIVE_STREAM_REF(keepalive_stream), + + NUTAG_OUTBOUND_REF(outbound), + NUTAG_M_DISPLAY_REF(m_display), + NUTAG_M_USERNAME_REF(m_username), + NUTAG_M_PARAMS_REF(m_params), + NUTAG_M_FEATURES_REF(m_features), + NUTAG_INSTANCE_REF(instance), + + TAG_END()); + TEST(n, 51); + + TEST_S(sip_header_as_string(tmphome, (void *)from), Alice); + TEST_S(from_str, Alice); + + TEST(retry_count, 5); + TEST(max_subscriptions, 6); + + TEST_S(soa_name, "test"); + TEST(media_enable, 0); + TEST(invite_enable, 0); + TEST(auto_alert, 1); + TEST(early_media, 1); + TEST(auto_answer, 1); + TEST(auto_ack, 0); + TEST(invite_timeout, 60); + + TEST(session_timer, 600); + TEST(min_se, 35); + TEST(refresher, nua_remote_refresher); + TEST(update_refresh, 1); + + TEST(message_enable, 0); + TEST(win_messenger_enable, 1); + TEST(message_auto_respond, -1); /* XXX */ + + TEST(callee_caps, 1); + TEST(media_features, 1); + TEST(service_route_enable, 0); + TEST(path_enable, 0); + TEST(auth_cache, nua_auth_cache_challenged); + TEST(refer_expires, 333); + TEST(refer_with_id, 0); + TEST(substate, nua_substate_pending); + TEST(sub_expires, 3700); + + TEST_S(sip_header_as_string(tmphome, (void *)allow), "OPTIONS, INFO, ACK"); + TEST_S(allow_str, "OPTIONS, INFO, ACK"); + TEST_S(appl_method, "INVITE, REGISTER, PUBLISH, SUBSCRIBE, NOTIFY"); + TEST_S(sip_header_as_string(tmphome, (void *)allow_events), + "reg, presence, presence.winfo"); + TEST_S(allow_events_str, "reg, presence, presence.winfo"); + TEST_S(sip_header_as_string(tmphome, (void *)supported), + "foo, bar, baz"); + TEST_S(supported_str, "foo, bar, baz"); + TEST_S(sip_header_as_string(tmphome, (void *)user_agent), "test_nua"); + TEST_S(user_agent_str, "test_nua"); + TEST_S(sip_header_as_string(tmphome, (void *)organization), + "Pussy Galore's Flying Circus"); + TEST_S(organization_str, "Pussy Galore's Flying Circus"); + + TEST_1(initial_route); TEST_1(initial_route != (void *)-1); + TEST_S(initial_route->r_url->url_user, "1"); + TEST_1(url_has_param(initial_route->r_url, "lr")); + TEST_1(initial_route->r_next); + TEST_S(initial_route->r_next->r_url->url_user, "2"); + TEST_1(url_has_param(initial_route->r_next->r_url, "lr")); + TEST_1(initial_route->r_next->r_next); + TEST_S(initial_route->r_next->r_next->r_url->url_user, "3"); + TEST_1(url_has_param(initial_route->r_next->r_next->r_url, "lr")); + TEST_1(!initial_route->r_next->r_next->r_next); + + TEST_S(url_as_string(tmphome, registrar->us_url), + "sip:sip.wonderland.org"); + TEST(keepalive, 66); + TEST(keepalive_stream, 33); + + TEST_S(instance, "urn:uuid:97701ad9-39df-1229-1083-dbc0a85f029c"); + TEST_S(m_display, "Joe"); + TEST_S(m_username, "joe"); + TEST_S(m_params, "user=phone"); + { char const *expect_m_features = "language=\"en\""; + TEST_S(m_features, expect_m_features); } + TEST_S(outbound, "foo"); + + free_events_in_list(ctx, ctx->a.specials); + } + + /* Test that only those tags that have been set per handle are returned by nua_get_hparams() */ + + { + sip_from_t const *from = NONE; + char const *from_str = "NONE"; + + unsigned retry_count = (unsigned)-1; + unsigned max_subscriptions = (unsigned)-1; + + int invite_enable = -1; + int auto_alert = -1; + int early_media = -1; + int auto_answer = -1; + int auto_ack = -1; + unsigned invite_timeout = (unsigned)-1; + + unsigned session_timer = (unsigned)-1; + unsigned min_se = (unsigned)-1; + int refresher = -1; + int update_refresh = -1; + + int message_enable = -1; + int win_messenger_enable = -1; + int message_auto_respond = -1; + + int callee_caps = -1; + int media_features = -1; + int service_route_enable = -1; + int path_enable = -1; + int auth_cache = -1; + unsigned refer_expires = (unsigned)-1; + int refer_with_id = -1; + int substate = -1; + unsigned sub_expires = (unsigned)-1; + + sip_allow_t const *allow = NONE; + char const *allow_str = "NONE"; + sip_supported_t const *supported = NONE; + char const *supported_str = "NONE"; + sip_user_agent_t const *user_agent = NONE; + char const *user_agent_str = "NONE"; + sip_organization_t const *organization = NONE; + char const *organization_str = "NONE"; + + sip_route_t *initial_route = NONE; + char const *initial_route_str = "NONE"; + + url_string_t const *registrar = NONE; + + char const *outbound = "NONE"; + char const *m_display = "NONE"; + char const *m_username = "NONE"; + char const *m_params = "NONE"; + char const *m_features = "NONE"; + char const *instance = "NONE"; + + int n; + struct event *e; + + nua_get_hparams(nh, TAG_ANY(), TAG_END()); + run_a_until(ctx, nua_r_get_params, save_until_final_response); + + TEST_1(e = ctx->a.events->head); + TEST_E(e->data->e_event, nua_r_get_params); + + n = tl_gets(e->data->e_tags, + SIPTAG_FROM_REF(from), + SIPTAG_FROM_STR_REF(from_str), + + NUTAG_RETRY_COUNT_REF(retry_count), + NUTAG_MAX_SUBSCRIPTIONS_REF(max_subscriptions), + + NUTAG_ENABLEINVITE_REF(invite_enable), + NUTAG_AUTOALERT_REF(auto_alert), + NUTAG_EARLY_MEDIA_REF(early_media), + NUTAG_AUTOANSWER_REF(auto_answer), + NUTAG_AUTOACK_REF(auto_ack), + NUTAG_INVITE_TIMER_REF(invite_timeout), + + NUTAG_SESSION_TIMER_REF(session_timer), + NUTAG_MIN_SE_REF(min_se), + NUTAG_SESSION_REFRESHER_REF(refresher), + NUTAG_UPDATE_REFRESH_REF(update_refresh), + + NUTAG_ENABLEMESSAGE_REF(message_enable), + NUTAG_ENABLEMESSENGER_REF(win_messenger_enable), + /* NUTAG_MESSAGE_AUTOANSWER(message_auto_respond), */ + + NUTAG_CALLEE_CAPS_REF(callee_caps), + NUTAG_MEDIA_FEATURES_REF(media_features), + NUTAG_SERVICE_ROUTE_ENABLE_REF(service_route_enable), + NUTAG_PATH_ENABLE_REF(path_enable), + NUTAG_AUTH_CACHE_REF(auth_cache), + NUTAG_SUBSTATE_REF(substate), + NUTAG_SUB_EXPIRES_REF(sub_expires), + + SIPTAG_SUPPORTED_REF(supported), + SIPTAG_SUPPORTED_STR_REF(supported_str), + SIPTAG_ALLOW_REF(allow), + SIPTAG_ALLOW_STR_REF(allow_str), + SIPTAG_USER_AGENT_REF(user_agent), + SIPTAG_USER_AGENT_STR_REF(user_agent_str), + + SIPTAG_ORGANIZATION_REF(organization), + SIPTAG_ORGANIZATION_STR_REF(organization_str), + + NUTAG_OUTBOUND_REF(outbound), + NUTAG_M_DISPLAY_REF(m_display), + NUTAG_M_USERNAME_REF(m_username), + NUTAG_M_PARAMS_REF(m_params), + NUTAG_M_FEATURES_REF(m_features), + NUTAG_INSTANCE_REF(instance), + + NUTAG_REGISTRAR_REF(registrar), + + TAG_END()); + TEST(n, 3); + + TEST(invite_timeout, 90); + + TEST_1(from != NULL && from != NONE); + TEST_1(strcmp(from_str, "NONE")); + + /* Nothing else should be set */ + TEST(retry_count, (unsigned)-1); + TEST(max_subscriptions, (unsigned)-1); + + TEST(invite_enable, -1); + TEST(auto_alert, -1); + TEST(early_media, -1); + TEST(auto_answer, -1); + TEST(auto_ack, -1); + + TEST(session_timer, (unsigned)-1); + TEST(min_se, (unsigned)-1); + TEST(refresher, -1); + TEST(update_refresh, -1); + + TEST(message_enable, -1); + TEST(win_messenger_enable, -1); + TEST(message_auto_respond, -1); /* XXX */ + + TEST(callee_caps, -1); + TEST(media_features, -1); + TEST(service_route_enable, -1); + TEST(path_enable, -1); + TEST(auth_cache, -1); + TEST(refer_expires, (unsigned)-1); + TEST(refer_with_id, -1); + TEST(substate, -1); + TEST(sub_expires, -1); + + TEST_P(allow, NONE); + TEST_S(allow_str, "NONE"); + TEST_P(supported, NONE); + TEST_S(supported_str, "NONE"); + TEST_P(user_agent, NONE); + TEST_S(user_agent_str, "NONE"); + TEST_P(organization, NONE); + TEST_S(organization_str, "NONE"); + + TEST_1(initial_route == (void *)-1); + TEST_S(initial_route_str, "NONE"); + + TEST_S(outbound, "NONE"); + TEST_S(m_display, "NONE"); + TEST_S(m_username, "NONE"); + TEST_S(m_params, "NONE"); + TEST_S(m_features, "NONE"); + TEST_S(instance, "NONE"); + + TEST_P(registrar->us_url, NONE); + + free_events_in_list(ctx, ctx->a.events); + } + + nua_handle_destroy(nh); + + nua_shutdown(ctx->a.nua); + run_a_until(ctx, nua_r_shutdown, until_final_response); + nua_destroy(ctx->a.nua), ctx->a.nua = NULL; + + su_root_destroy(ctx->root), ctx->root = NULL; + + su_home_deinit(tmphome); + + if (print_headings) + printf("TEST NUA-1.1: PASSED\n"); + + END(); +} Added: freeswitch/trunk/libs/sofia-sip/tests/test_offer_answer.c ============================================================================== --- (empty file) +++ freeswitch/trunk/libs/sofia-sip/tests/test_offer_answer.c Wed May 14 15:10:54 2008 @@ -0,0 +1,479 @@ +/* + * This file is part of the Sofia-SIP package + * + * Copyright (C) 2005 Nokia Corporation. + * + * Contact: Pekka Pessi <pekka.pessi@nokia.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public License + * as published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + */ + +/**@CFILE test_offer_answer.c + * @brief Test offer/answer failures. + * + * @author Pekka Pessi <Pekka.Pessi@nokia.com> + * + * @date Created: Wed Aug 17 12:12:12 EEST 2005 ppessi + */ + +#include "config.h" + +#include "test_nua.h" +#include <sofia-sip/su_tag_class.h> + +#if HAVE_FUNC +#elif HAVE_FUNCTION +#define __func__ __FUNCTION__ +#else +#define __func__ "test_offer_answer" +#endif + + +/* ======================================================================== */ + +/* Send offer in INVITE, no answer: + + A B + --nua_invite()-->| | + |-------INVITE------>| + <--nua_i_state---| | + |<----100 Trying-----| + | | + |<--------180--------| + <--nua_r_invite--| | + <--nua_i_state---| | + |<------200 OK-------| + <--nua_r_invite--| | + |--------ACK-------->| + |--------BYE-------->| + <--nua_i_state---| | + |<-------200 OK------| + <--nua_r_bye-----| | + <--nua_i_state---| | + + Client transitions: + INIT -(C1)-> CALLING -(C2a)-> PROCEEDING -(C3+C4)-> + TERMINATING -> TERMINATED + Server transitions: + INIT -(S1)-> RECEIVED -(S2a)-> EARLY -(S3b)-> COMPLETED -(S4)-> READY + -> TERMINATED + +*/ + +int no_media_terminated(CONDITION_PARAMS); + +int test_no_answer_1(struct context *ctx) +{ + BEGIN(); + + struct endpoint *a = &ctx->a, *b = &ctx->b; + struct call *a_call = a->call, *b_call = b->call; + struct event *e; + sip_t *sip; + + if (print_headings) + printf("TEST NUA-6.5: No SDP answer from callee\n"); + + a_call->sdp = "m=audio 5008 RTP/AVP 8"; + b_call->sdp = NULL; + + TEST_1(a_call->nh = nua_handle(a->nua, a_call, SIPTAG_TO(b->to), TAG_END())); + + TEST_1(!nua_handle_has_active_call(a_call->nh)); + TEST_1(!nua_handle_has_call_on_hold(a_call->nh)); + + INVITE(a, a_call, a_call->nh, + TAG_IF(!ctx->proxy_tests, NUTAG_URL(b->contact->m_url)), + SOATAG_USER_SDP_STR(a_call->sdp), + NUTAG_M_USERNAME("a+a"), + NUTAG_M_DISPLAY("Alice"), + TAG_END()); + + run_ab_until(ctx, -1, until_terminated, -1, no_media_terminated); + + TEST_1(!nua_handle_has_active_call(a_call->nh)); + TEST_1(!nua_handle_has_call_on_hold(a_call->nh)); + + TEST_1(!nua_handle_has_active_call(b_call->nh)); + TEST_1(!nua_handle_has_call_on_hold(b_call->nh)); + + /* Client transitions: + INIT -(C1)-> CALLING: nua_invite(), nua_i_state + CALLING -(C3)-> PROCEEDING: nua_r_invite, nua_i_state + PROCEEDING -(C3a+C5)-> TERMINATING: nua_r_invite, nua_i_state + TERMINATING -(C12)-> TERMINATED: nua_r_bye, nua_i_state + */ + TEST_1(e = a->events->head); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_calling); /* CALLING */ + TEST_1(is_offer_sent(e->data->e_tags)); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_r_invite); + TEST(e->data->e_status, 180); + TEST_1(sip = sip_object(e->data->e_msg)); + TEST_1(!sip->sip_payload); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_proceeding); /* PROCEEDING */ + TEST_1(!is_answer_recv(e->data->e_tags)); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_r_invite); + TEST(e->data->e_status, 200); + TEST_1(sip = sip_object(e->data->e_msg)); + TEST_1(!sip->sip_payload); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_media_error); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_terminating); /* TERMINATING */ + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_r_bye); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_terminated); /* TERMINATED */ + TEST_1(!e->next); + free_events_in_list(ctx, a->events); + + /* + Server transitions: + INIT -(S1)-> RECEIVED: nua_i_invite, nua_i_state + RECEIVED -(S2a)-> EARLY: nua_respond(), nua_i_state + EARLY -(S3b)-> COMPLETED: nua_respond(), nua_i_state + COMPLETED -(S4)-> READY: nua_i_ack, nua_i_state + READY -(T1)-> TERMINATED: nua_i_bye, nua_i_state + */ + TEST_1(e = b->events->head); TEST_E(e->data->e_event, nua_i_invite); + TEST(e->data->e_status, 100); + TEST_1(sip = sip_object(e->data->e_msg)); + TEST_1(sip->sip_contact); + TEST_S(sip->sip_contact->m_display, "Alice"); + TEST_S(sip->sip_contact->m_url->url_user, "a+a"); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_received); /* RECEIVED */ + TEST_1(is_offer_recv(e->data->e_tags)); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_early); /* EARLY */ + TEST_1(!is_answer_sent(e->data->e_tags)); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_completed); /* COMPLETED */ + TEST_1(!is_answer_sent(e->data->e_tags)); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_ack); + TEST_1(sip = sip_object(e->data->e_msg)); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_ready); /* READY */ + TEST_1(!is_answer_recv(e->data->e_tags)); + TEST_1(!is_offer_recv(e->data->e_tags)); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_bye); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_terminated); /* TERMINATED */ + TEST_1(!e->next); + + free_events_in_list(ctx, a->events); + free_events_in_list(ctx, b->events); + + TEST_1(!nua_handle_has_active_call(a_call->nh)); + TEST_1(!nua_handle_has_active_call(b_call->nh)); + + nua_handle_destroy(a_call->nh), a_call->nh = NULL; + nua_handle_destroy(b_call->nh), b_call->nh = NULL; + + if (print_headings) + printf("TEST NUA-6.5: PASSED\n"); + + END(); +} + +int no_media_terminated(CONDITION_PARAMS) +{ + if (!(check_handle(ep, call, nh, SIP_500_INTERNAL_SERVER_ERROR))) + return 0; + + save_event_in_list(ctx, event, ep, call); + + switch (callstate(tags)) { + case nua_callstate_received: + RESPOND(ep, call, nh, SIP_180_RINGING, + NUTAG_MEDIA_ENABLE(0), + TAG_END()); + return 0; + case nua_callstate_early: + RESPOND(ep, call, nh, SIP_200_OK, + NUTAG_MEDIA_ENABLE(0), + TAG_END()); + return 0; + case nua_callstate_terminated: + if (call) + nua_handle_destroy(call->nh), call->nh = NULL; + return 1; + default: + return 0; + } +} + +int accept_call_until_terminated(CONDITION_PARAMS); + +/* No offer in INVITE, offer in 200 OK, no answer in ACK */ +int test_no_answer_2(struct context *ctx) +{ + BEGIN(); + + struct endpoint *a = &ctx->a, *b = &ctx->b; + struct call *a_call = a->call, *b_call = b->call; + struct event *e; + sip_t const *sip; + + if (print_headings) + printf("TEST NUA-6.6: No SDP offer from caller\n"); + + a_call->sdp = "v=0\r\n" + "o=- 1 1 IN IP4 127.0.0.1\r\n" + "s=-\r\n" + "c=IN IP4 127.0.0.1\r\n" + "t=0 0\r\n" + "m=audio 5008 RTP/AVP 8\r\n"; + + b_call->sdp = + "v=0\r\n" + "o=- 2 1 IN IP4 127.0.0.1\r\n" + "s=-\r\n" + "c=IN IP4 127.0.0.1\r\n" + "t=0 0\r\n" + "m=audio 5010 RTP/AVP 0 8\r\n"; + + TEST_1(a_call->nh = nua_handle(a->nua, a_call, + SIPTAG_TO_STR("<sip:b@x.org>"), + TAG_END())); + + TEST_1(!nua_handle_has_active_call(a_call->nh)); + TEST_1(!nua_handle_has_call_on_hold(a_call->nh)); + + INVITE(a, a_call, a_call->nh, + NUTAG_MEDIA_ENABLE(0), + NUTAG_URL(b->contact->m_url), + NUTAG_AUTOACK(1), + TAG_END()); + + run_ab_until(ctx, -1, until_terminated, + -1, accept_call_until_terminated); + + /* Client transitions: + INIT -(C1)-> CALLING: nua_invite(), nua_i_state + CALLING -(C2)-> PROCEEDING: nua_r_invite, nua_i_state + PROCEEDING -(C3+C4)-> READY: nua_r_invite, nua_i_state + */ + TEST_1(e = a->events->head); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_calling); /* CALLING */ + TEST_1(!is_offer_sent(e->data->e_tags)); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_r_invite); + TEST(e->data->e_status, 180); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_proceeding); /* PROCEEDING */ + TEST_1(!is_answer_recv(e->data->e_tags)); + TEST_1(!is_offer_recv(e->data->e_tags)); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_r_invite); + TEST(e->data->e_status, 200); + TEST_1(sip = sip_object(e->data->e_msg)); + TEST_1(sip->sip_content_type); + TEST_S(sip->sip_content_type->c_type, "application/sdp"); + TEST_1(sip->sip_payload); /* there is sdp in 200 OK */ + TEST_1(sip->sip_contact); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_ready); /* READY */ + TEST_1(!is_answer_sent(e->data->e_tags)); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_bye); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_terminated); /* READY */ + TEST_1(!e->next); + free_events_in_list(ctx, a->events); + + TEST_1(!nua_handle_has_active_call(a_call->nh)); + TEST_1(!nua_handle_has_call_on_hold(a_call->nh)); + + /* + Server transitions: + INIT -(S1)-> RECEIVED: nua_i_invite, nua_i_state + RECEIVED -(S2a)-> EARLY: nua_respond(), nua_i_state + EARLY -(S3b)-> COMPLETED: nua_respond(), nua_i_state + COMPLETED -(S4)-> READY: nua_i_ack, nua_i_state + */ + TEST_1(e = b->events->head); TEST_E(e->data->e_event, nua_i_invite); + TEST(e->data->e_status, 100); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_received); /* RECEIVED */ + TEST_1(!is_offer_recv(e->data->e_tags)); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_early); /* EARLY */ + TEST_1(!is_answer_sent(e->data->e_tags)); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_completed); /* COMPLETED */ + TEST_1(is_offer_sent(e->data->e_tags)); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_ack); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_media_error); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_terminating); /* TERMINATING */ + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_r_bye); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_terminated); /* TERMINATED */ + TEST_1(!e->next); + free_events_in_list(ctx, b->events); + + TEST_1(!nua_handle_has_active_call(b_call->nh)); + TEST_1(!nua_handle_has_call_on_hold(b_call->nh)); + + nua_handle_destroy(a_call->nh), a_call->nh = NULL; + nua_handle_destroy(b_call->nh), b_call->nh = NULL; + + if (print_headings) + printf("TEST NUA-6.6: PASSED\n"); + + END(); +} + +int accept_call_until_terminated(CONDITION_PARAMS) +{ + if (!(check_handle(ep, call, nh, SIP_500_INTERNAL_SERVER_ERROR))) + return 0; + + save_event_in_list(ctx, event, ep, call); + + switch (callstate(tags)) { + case nua_callstate_received: + RESPOND(ep, call, nh, SIP_180_RINGING, + TAG_END()); + return 0; + case nua_callstate_early: + RESPOND(ep, call, nh, SIP_200_OK, + NUTAG_MEDIA_ENABLE(1), + TAG_IF(call->sdp, SOATAG_USER_SDP_STR(call->sdp)), + TAG_END()); + return 0; + case nua_callstate_terminated: + if (call) + nua_handle_destroy(call->nh), call->nh = NULL; + return 1; + default: + return 0; + } +} + + +/* No offer in INVITE, no user SDP in 200 OK */ +int test_missing_user_sdp(struct context *ctx) +{ + BEGIN(); + + struct endpoint *a = &ctx->a, *b = &ctx->b; + struct call *a_call = a->call, *b_call = b->call; + struct event *e; + + if (print_headings) + printf("TEST NUA-6.6.2: No SDP offer from caller\n"); + + a_call->sdp = "v=0\r\n" + "o=- 1 1 IN IP4 127.0.0.1\r\n" + "s=-\r\n" + "c=IN IP4 127.0.0.1\r\n" + "t=0 0\r\n" + "m=audio 5008 RTP/AVP 8\r\n"; + + b_call->sdp = NULL; + + nua_set_params(b->nua, NUTAG_MEDIA_ENABLE(0), TAG_END()); + run_b_until(ctx, nua_r_set_params, until_final_response); + + TEST_1(a_call->nh = nua_handle(a->nua, a_call, + SIPTAG_TO_STR("<sip:b@x.org>"), + TAG_END())); + + TEST_1(!nua_handle_has_active_call(a_call->nh)); + TEST_1(!nua_handle_has_call_on_hold(a_call->nh)); + + INVITE(a, a_call, a_call->nh, + NUTAG_MEDIA_ENABLE(0), + NUTAG_URL(b->contact->m_url), + NUTAG_AUTOACK(1), + TAG_END()); + + run_ab_until(ctx, -1, until_terminated, + -1, accept_call_until_terminated); + + /* Client transitions: + INIT -(C1)-> CALLING: nua_invite(), nua_i_state + CALLING -(C2)-> PROCEEDING: nua_r_invite, nua_i_state + PROCEEDING -(C3+C4)-> READY: nua_r_invite, nua_i_state + */ + TEST_1(e = a->events->head); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_calling); /* CALLING */ + TEST_1(!is_offer_sent(e->data->e_tags)); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_r_invite); + TEST(e->data->e_status, 180); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_proceeding); /* PROCEEDING */ + TEST_1(!is_answer_recv(e->data->e_tags)); + TEST_1(!is_offer_recv(e->data->e_tags)); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_r_invite); + TEST(e->data->e_status, 500); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_terminated); /* READY */ + TEST_1(!e->next); + free_events_in_list(ctx, a->events); + + TEST_1(!nua_handle_has_active_call(a_call->nh)); + TEST_1(!nua_handle_has_call_on_hold(a_call->nh)); + + /* + Server transitions: + INIT -(S1)-> RECEIVED: nua_i_invite, nua_i_state + RECEIVED -(S2a)-> EARLY: nua_respond(), nua_i_state + EARLY -(S3b)-> COMPLETED: nua_respond(), nua_i_state + COMPLETED -(S4)-> READY: nua_i_ack, nua_i_state + */ + TEST_1(e = b->events->head); TEST_E(e->data->e_event, nua_i_invite); + TEST(e->data->e_status, 100); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_received); /* RECEIVED */ + TEST_1(!is_offer_recv(e->data->e_tags)); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_early); /* EARLY */ + TEST_1(!is_answer_sent(e->data->e_tags)); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_error); + TEST(e->data->e_status, 500); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_terminated); /* COMPLETED */ + TEST_1(!e->next); + free_events_in_list(ctx, b->events); + + TEST_1(!nua_handle_has_active_call(b_call->nh)); + TEST_1(!nua_handle_has_call_on_hold(b_call->nh)); + + nua_handle_destroy(a_call->nh), a_call->nh = NULL; + nua_handle_destroy(b_call->nh), b_call->nh = NULL; + + nua_set_params(b->nua, + NUTAG_MEDIA_ENABLE(1), + SOATAG_USER_SDP_STR("m=audio 5006 RTP/AVP 8 0"), + TAG_END()); + run_b_until(ctx, nua_r_set_params, until_final_response); + + if (print_headings) + printf("TEST NUA-6.6.2: PASSED\n"); + + END(); +} + + + +int test_offer_answer(struct context *ctx) +{ + return + test_no_answer_1(ctx) || + test_no_answer_2(ctx) || + test_missing_user_sdp(ctx) || + 0; +} Added: freeswitch/trunk/libs/sofia-sip/tests/test_ops.c ============================================================================== --- (empty file) +++ freeswitch/trunk/libs/sofia-sip/tests/test_ops.c Wed May 14 15:10:54 2008 @@ -0,0 +1,566 @@ +/* + * This file is part of the Sofia-SIP package + * + * Copyright (C) 2005 Nokia Corporation. + * + * Contact: Pekka Pessi <pekka.pessi@nokia.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public License + * as published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + */ + +/**@CFILE test_ops.c + * @brief High-level test framework for Sofia SIP User Agent Engine + * + * @author Pekka Pessi <Pekka.Pessi@nokia.com> + * @author Martti Mela <Martti Mela@nokia.com> + * + * @date Created: Wed Aug 17 12:12:12 EEST 2005 ppessi + */ + +#include "config.h" + +#include "test_nua.h" + +#if HAVE_FUNC +#elif HAVE_FUNCTION +#define __func__ __FUNCTION__ +#else +#define __func__ name +#endif + +int save_events(CONDITION_PARAMS) +{ + return save_event_in_list(ctx, event, ep, call) == event_is_normal; +} + +int until_final_response(CONDITION_PARAMS) +{ + return status >= 200; +} + +int save_until_final_response(CONDITION_PARAMS) +{ + save_event_in_list(ctx, event, ep, call); + return + nua_r_set_params <= event && event < nua_i_network_changed + && status >= 200; +} + +/** Save events. + * + * Terminate when a event is saved. + */ +int save_until_received(CONDITION_PARAMS) +{ + return save_event_in_list(ctx, event, ep, call) == event_is_normal; +} + +/** Save events until nua_i_outbound is received. */ +int save_until_special(CONDITION_PARAMS) +{ + return save_event_in_list(ctx, event, ep, call) == event_is_special; +} + +/* Return call state from event tag list */ +int callstate(tagi_t const *tags) +{ + tagi_t const *ti = tl_find(tags, nutag_callstate); + return ti ? ti->t_value : -1; +} + +/* Return true if offer is sent */ +int is_offer_sent(tagi_t const *tags) +{ + tagi_t const *ti = tl_find(tags, nutag_offer_sent); + return ti ? ti->t_value : 0; +} + +/* Return true if answer is sent */ +int is_answer_sent(tagi_t const *tags) +{ + tagi_t const *ti = tl_find(tags, nutag_answer_sent); + return ti ? ti->t_value : 0; +} + +/* Return true if offer is recv */ +int is_offer_recv(tagi_t const *tags) +{ + tagi_t const *ti = tl_find(tags, nutag_offer_recv); + return ti ? ti->t_value : 0; +} + +/* Return true if answer is recv */ +int is_answer_recv(tagi_t const *tags) +{ + tagi_t const *ti = tl_find(tags, nutag_answer_recv); + return ti ? ti->t_value : 0; +} + +/* Return true if offer/answer is sent/recv */ +int is_offer_answer_done(tagi_t const *tags) +{ + tagi_t const *ti; + + return + ((ti = tl_find(tags, nutag_answer_recv)) && ti->t_value) || + ((ti = tl_find(tags, nutag_offer_sent)) && ti->t_value) || + ((ti = tl_find(tags, nutag_offer_recv)) && ti->t_value) || + ((ti = tl_find(tags, nutag_answer_sent)) && ti->t_value); +} + +/* Return audio state from event tag list */ +int audio_activity(tagi_t const *tags) +{ + tagi_t const *ti = tl_find(tags, soatag_active_audio); + return ti ? ti->t_value : -1; +} + +/* Return video state from event tag list */ +int video_activity(tagi_t const *tags) +{ + tagi_t const *ti = tl_find(tags, soatag_active_video); + return ti ? ti->t_value : -1; +} + +void print_event(nua_event_t event, + char const *operation, + int status, char const *phrase, + nua_t *nua, struct context *ctx, + struct endpoint *ep, + nua_handle_t *nh, struct call *call, + sip_t const *sip, + tagi_t tags[]) +{ + tagi_t const *t; + static su_nanotime_t started = 0; + su_nanotime_t now; + char timestamp[32]; + + su_nanotime(&now); + + if (started == 0) started = now; + + now -= started; now /= 1000000; + + snprintf(timestamp, sizeof timestamp, "%03u.%03u", + (unsigned)(now / 1000), (unsigned)(now % 1000)); + + if (event == nua_i_state) { + fprintf(stderr, "%s %s.nua(%p): event %s %s\n", timestamp, + ep->name, (void *)nh, nua_event_name(event), + nua_callstate_name(callstate(tags))); + } + else if ((int)event >= nua_r_set_params) { + t = tl_find(tags, nutag_substate); + if (t) { + fprintf(stderr, "%s %s.nua(%p): event %s status %u %s (%s)\n", timestamp, + ep->name, (void*)nh, nua_event_name(event), status, phrase, + nua_substate_name(t->t_value)); + } + else { + fprintf(stderr, "%s %s.nua(%p): event %s status %u %s\n", timestamp, + ep->name, (void *)nh, nua_event_name(event), status, phrase); + } + } + else if (event == nua_i_notify) { + t = tl_find(tags, nutag_substate); + fprintf(stderr, "%s %s.nua(%p): event %s %s (%s)\n", timestamp, + ep->name, (void *)nh, nua_event_name(event), phrase, + nua_substate_name(t ? t->t_value : 0)); + } + else if ((int)event >= nua_i_bye || + event == nua_i_invite || event == nua_i_cancel || + event == nua_i_ack) { + fprintf(stderr, "%s %s.nua(%p): event %s %03d %s\n", timestamp, + ep->name, (void *)nh, nua_event_name(event), status, phrase); + } + else if ((int)event >= 0) { + fprintf(stderr, "%s %s.nua(%p): event %s %s\n", timestamp, + ep->name, (void *)nh, nua_event_name(event), phrase); + } + else if (status > 0) { + fprintf(stderr, "%s %s.nua(%p): call %s() with status %u %s\n", timestamp, + ep->name, (void *)nh, operation, status, phrase); + } + else { + t = tl_find(tags, siptag_subject_str); + if (t && t->t_value) { + char const *subject = (char const *)t->t_value; + fprintf(stderr, "%s %s.nua(%p): call %s() \"%s\"\n", timestamp, + ep->name, (void *)nh, operation, subject); + } + else + fprintf(stderr, "%s %s.nua(%p): call %s()\n", timestamp, + ep->name, (void *)nh, operation); + } + + if (tags && + ((tstflags & tst_verbatim) || ctx->print_tags || ep->print_tags)) + tl_print(stderr, "", tags); +} + +void ep_callback(nua_event_t event, + int status, char const *phrase, + nua_t *nua, struct context *ctx, + struct endpoint *ep, + nua_handle_t *nh, struct call *call, + sip_t const *sip, + tagi_t tags[]) +{ + if (ep->printer) + ep->printer(event, "", status, phrase, nua, ctx, ep, nh, call, sip, tags); + + if (call == NULL && nh) { + for (call = ep->call; call; call = call->next) { + if (!call->nh) + break; + if (nh == call->nh) + break; + } + + if (call && call->nh == NULL) { + call->nh = nh; + nua_handle_bind(nh, call); + } + } + + if ((ep->next_condition == NULL || + ep->next_condition(event, status, phrase, + nua, ctx, ep, nh, call, sip, tags)) + && + (ep->next_event == -1 || ep->next_event == event)) + ep->running = 0; + + ep->last_event = event; + + if (call == NULL && nh) + nua_handle_destroy(nh); +} + +void a_callback(nua_event_t event, + int status, char const *phrase, + nua_t *nua, struct context *ctx, + nua_handle_t *nh, struct call *call, + sip_t const *sip, + tagi_t tags[]) +{ + ep_callback(event, status, phrase, nua, ctx, &ctx->a, nh, call, sip, tags); +} + +void b_callback(nua_event_t event, + int status, char const *phrase, + nua_t *nua, struct context *ctx, + nua_handle_t *nh, struct call *call, + sip_t const *sip, + tagi_t tags[]) +{ + ep_callback(event, status, phrase, nua, ctx, &ctx->b, nh, call, sip, tags); +} + +void c_callback(nua_event_t event, + int status, char const *phrase, + nua_t *nua, struct context *ctx, + nua_handle_t *nh, struct call *call, + sip_t const *sip, + tagi_t tags[]) +{ + ep_callback(event, status, phrase, nua, ctx, &ctx->c, nh, call, sip, tags); +} + +void run_abc_until(struct context *ctx, + nua_event_t a_event, condition_function *a_condition, + nua_event_t b_event, condition_function *b_condition, + nua_event_t c_event, condition_function *c_condition) +{ + struct endpoint *a = &ctx->a, *b = &ctx->b, *c = &ctx->c; + + a->next_event = a_event; + a->next_condition = a_condition; + a->last_event = -1; + a->running = a_condition != NULL && a_condition != save_events; + a->running |= a_event != -1; + memset(&a->flags, 0, sizeof a->flags); + + b->next_event = b_event; + b->next_condition = b_condition; + b->last_event = -1; + b->running = b_condition != NULL && b_condition != save_events; + b->running |= b_event != -1; + memset(&b->flags, 0, sizeof b->flags); + + c->next_event = c_event; + c->next_condition = c_condition; + c->last_event = -1; + c->running = c_condition != NULL && c_condition != save_events; + c->running |= c_event != -1; + memset(&c->flags, 0, sizeof c->flags); + + for (; a->running || b->running || c->running;) { + su_root_step(ctx->root, 100); + } +} + +void run_ab_until(struct context *ctx, + nua_event_t a_event, condition_function *a_condition, + nua_event_t b_event, condition_function *b_condition) +{ + run_abc_until(ctx, a_event, a_condition, b_event, b_condition, -1, NULL); +} + +void run_bc_until(struct context *ctx, + nua_event_t b_event, condition_function *b_condition, + nua_event_t c_event, condition_function *c_condition) +{ + run_abc_until(ctx, -1, NULL, b_event, b_condition, c_event, c_condition); +} + +int run_a_until(struct context *ctx, + nua_event_t a_event, + condition_function *a_condition) +{ + run_abc_until(ctx, a_event, a_condition, -1, NULL, -1, NULL); + return ctx->a.last_event; +} + +int run_b_until(struct context *ctx, + nua_event_t b_event, + condition_function *b_condition) +{ + run_abc_until(ctx, -1, NULL, b_event, b_condition, -1, NULL); + return ctx->b.last_event; +} + +int run_c_until(struct context *ctx, + nua_event_t event, + condition_function *condition) +{ + run_abc_until(ctx, -1, NULL, -1, NULL, event, condition); + return ctx->c.last_event; +} + +#define OPERATION(X, x) \ +int X(struct endpoint *ep, \ + struct call *call, nua_handle_t *nh, \ + tag_type_t tag, tag_value_t value, \ + ...) \ +{ \ + ta_list ta; \ + ta_start(ta, tag, value); \ +\ + if (ep->printer) \ + ep->printer(-1, "nua_" #x, 0, "", ep->nua, ep->ctx, ep, \ + nh, call, NULL, ta_args(ta)); \ +\ + nua_##x(nh, ta_tags(ta)); \ +\ + ta_end(ta); \ + return 0; \ +} extern int dummy + +OPERATION(INVITE, invite); +OPERATION(ACK, ack); +OPERATION(BYE, bye); +OPERATION(CANCEL, cancel); +OPERATION(AUTHENTICATE, authenticate); +OPERATION(UPDATE, update); +OPERATION(INFO, info); +OPERATION(PRACK, prack); +OPERATION(REFER, refer); +OPERATION(MESSAGE, message); +OPERATION(METHOD, method); +OPERATION(OPTIONS, options); +OPERATION(PUBLISH, publish); +OPERATION(UNPUBLISH, unpublish); +OPERATION(REGISTER, register); +OPERATION(UNREGISTER, unregister); +OPERATION(SUBSCRIBE, subscribe); +OPERATION(UNSUBSCRIBE, unsubscribe); +OPERATION(NOTIFY, notify); +OPERATION(NOTIFIER, notifier); +OPERATION(TERMINATE, terminate); +OPERATION(AUTHORIZE, authorize); + +/* Respond via endpoint and handle */ +int RESPOND(struct endpoint *ep, + struct call *call, + nua_handle_t *nh, + int status, char const *phrase, + tag_type_t tag, tag_value_t value, + ...) +{ + ta_list ta; + + ta_start(ta, tag, value); + + if (ep->printer) + ep->printer(-1, "nua_respond", status, phrase, ep->nua, ep->ctx, ep, + nh, call, NULL, ta_args(ta)); + + nua_respond(nh, status, phrase, ta_tags(ta)); + ta_end(ta); + + return 0; +} + +/* Destroy a handle */ +int DESTROY(struct endpoint *ep, + struct call *call, + nua_handle_t *nh) +{ + if (ep->printer) + ep->printer(-1, "nua_handle_destroy", 0, "", ep->nua, ep->ctx, ep, + nh, call, NULL, NULL); + + nua_handle_destroy(nh); + + if (call->nh == nh) + call->nh = NULL; + + return 0; +} + + +/* Reject all but currently used handles */ +struct call *check_handle(struct endpoint *ep, + struct call *call, + nua_handle_t *nh, + int status, char const *phrase) +{ + if (call) + return call; + + if (status) + RESPOND(ep, call, nh, status, phrase, TAG_END()); + + nua_handle_destroy(nh); + return NULL; +} + +/* Save nua event in call-specific list */ +int save_event_in_list(struct context *ctx, + nua_event_t nevent, + struct endpoint *ep, + struct call *call) + +{ + struct eventlist *list; + struct event *e; + int action = ep->is_special(nevent); + + if (action == event_is_extra) + return 0; + else if (action == event_is_special || call == NULL) + list = ep->specials; + else if (call->events) + list = call->events; + else + list = ep->events; + + e = su_zalloc(ctx->home, sizeof *e); + + if (!e) { perror("su_zalloc"), abort(); } + + if (!nua_save_event(ep->nua, e->saved_event)) { + su_free(ctx->home, e); + return -1; + } + + *(e->prev = list->tail) = e; list->tail = &e->next; + + e->call = call; + e->data = nua_event_data(e->saved_event); + + return action; +} + +/* Free nua events from endpoint list */ +void free_events_in_list(struct context *ctx, + struct eventlist *list) +{ + struct event *e; + + while ((e = list->head)) { + if ((*e->prev = e->next)) + e->next->prev = e->prev; + nua_destroy_event(e->saved_event); + su_free(ctx->home, e); + } + + list->tail = &list->head; +} + +void free_event_in_list(struct context *ctx, + struct eventlist *list, + struct event *e) +{ + if (e) { + if ((*e->prev = e->next)) + e->next->prev = e->prev; + nua_destroy_event(e->saved_event); + su_free(ctx->home, e); + + if (list->head == NULL) + list->tail = &list->head; + } +} + +struct event *event_by_type(struct event *e, nua_event_t etype) +{ + for (; e; e = e->next) { + if (e->data->e_event == etype) + break; + } + + return e; +} + +size_t count_events(struct event const *e) +{ + size_t n; + + for (n = 0; e; e = e->next) + n++; + + return n; +} + + +int is_special(nua_event_t e) +{ + if (e == nua_i_active || e == nua_i_terminated) + return event_is_extra; + if (e == nua_i_outbound) + return event_is_special; + + return event_is_normal; +} + +void +endpoint_init(struct context *ctx, struct endpoint *e, char id) +{ + e->name[0] = id; + e->ctx = ctx; + + e->is_special = is_special; + + call_init(e->call); + call_init(e->reg); + eventlist_init(e->events); + eventlist_init(e->specials); +} + +void nolog(void *stream, char const *fmt, va_list ap) {} Added: freeswitch/trunk/libs/sofia-sip/tests/test_proxy.c ============================================================================== --- (empty file) +++ freeswitch/trunk/libs/sofia-sip/tests/test_proxy.c Wed May 14 15:10:54 2008 @@ -0,0 +1,1604 @@ +/* + * This file is part of the Sofia-SIP package + * + * Copyright (C) 2005 Nokia Corporation. + * + * Contact: Pekka Pessi <pekka.pessi@nokia.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public License + * as published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + */ + +/**@CFILE test_proxy.c + * @brief Extremely simple proxy and registrar for testing nua + * + * @author Pekka Pessi <Pekka.Pessi@nokia.com> + * + * @date Created: Thu Nov 3 22:49:46 EET 2005 + */ + +#include "config.h" + +#include <string.h> + +struct proxy; +struct domain; +union proxy_or_domain; +struct proxy_tr; +struct client_tr; +struct registration_entry; +struct binding; + +#define SU_ROOT_MAGIC_T struct proxy +#define NTA_LEG_MAGIC_T union proxy_or_domain +#define NTA_OUTGOING_MAGIC_T struct client_tr +#define NTA_INCOMING_MAGIC_T struct proxy_tr +#define SU_TIMER_ARG_T struct proxy_tr + +#include <sofia-sip/su_wait.h> +#include <sofia-sip/nta.h> +#include <sofia-sip/sip_header.h> +#include <sofia-sip/sip_status.h> +#include <sofia-sip/sip_util.h> +#include <sofia-sip/auth_module.h> +#include <sofia-sip/su_tagarg.h> +#include <sofia-sip/msg_addr.h> +#include <sofia-sip/hostdomain.h> +#include <sofia-sip/tport.h> +#include <sofia-sip/nta_tport.h> + +#include <stdlib.h> +#include <assert.h> + +#define LIST_PROTOS(STORAGE, PREFIX, T) \ +STORAGE void PREFIX ##_insert(T **list, T *node), \ + PREFIX ##_remove(T *node) + +#define LIST_BODIES(STORAGE, PREFIX, T, NEXT, PREV) \ +STORAGE void PREFIX ##_insert(T **list, T *node) \ +{ \ + if ((node->NEXT = *list)) { \ + node->PREV = node->NEXT->PREV; \ + node->NEXT->PREV = &node->NEXT; \ + } \ + else \ + node->PREV = list; \ + *list = node; \ +} \ +STORAGE void PREFIX ##_remove(T *node) \ +{ \ + if (node->PREV) \ + if ((*node->PREV = node->NEXT)) \ + node->NEXT->PREV = node->PREV; \ + node->PREV = NULL; \ +} \ +extern int LIST_DUMMY_VARIABLE + +#include "test_proxy.h" +#include <sofia-sip/auth_module.h> + +struct proxy { + su_home_t home[1]; + void *magic; + su_root_t *parent; + su_clone_r clone; + tagi_t *tags; + + su_root_t *root; + + nta_agent_t *agent; + url_t const *uri; + sip_route_t *lr; + char const *lr_str; + url_t const *rr_uri; + + nta_leg_t *defleg; + + sip_contact_t *transport_contacts; + + struct proxy_tr *stateless; + struct proxy_tr *transactions; + struct proxy_tr *invite_waiting; + + struct domain *domains; + + struct { + unsigned t1x64; + sip_time_t session_expires, min_se; + } prefs; +}; + +struct domain { + su_home_t home[1]; + void *magic; + struct proxy *proxy; + struct domain *next, **prev; + + url_t *uri; + + nta_leg_t *rleg, *uleg; + + auth_mod_t *auth; + struct registration_entry *entries; + + struct { + sip_time_t min_expires, expires, max_expires; + int outbound_tcp; /**< Use inbound TCP connection as outbound */ + char const *authorize; /**< Authorization realm to use */ + int record_route; + } prefs; + + tagi_t *tags; +}; + +LIST_PROTOS(static, domain, struct domain); +static int _domain_init(void *_d); +static int domain_init(struct domain *domain); +static void domain_destroy(struct domain *domain); + +LIST_BODIES(static, domain, struct domain, next, prev); + +LIST_PROTOS(static, registration_entry, struct registration_entry); +static struct registration_entry *registration_entry_new(struct domain *, + url_t const *); +static void registration_entry_destroy(struct registration_entry *e); + +struct registration_entry +{ + struct registration_entry *next, **prev; + struct domain *domain; /* backpointer */ + url_t *aor; /* address-of-record */ + struct binding *bindings; /* list of bindings */ + sip_contact_t *contacts; +}; + +struct binding +{ + struct binding *next, **prev; + sip_contact_t *contact; /* binding */ + sip_time_t registered, expires; /* When registered and when expires */ + sip_call_id_t *call_id; + uint32_t cseq; + tport_t *tport; /**< Reference to tport */ +}; + +static struct binding *binding_new(su_home_t *home, + sip_contact_t *contact, + tport_t *tport, + sip_call_id_t const *call_id, + uint32_t cseq, + sip_time_t registered, + sip_time_t expires); +static void binding_destroy(su_home_t *home, struct binding *b); +static int binding_is_active(struct binding const *b) +{ + return + b->expires > sip_now() && + (b->tport == NULL || tport_is_clear_to_send(b->tport)); +} + +LIST_PROTOS(static, proxy_tr, struct proxy_tr); +struct proxy_tr *proxy_tr_new(struct proxy *); +static void proxy_tr_timeout(struct proxy_tr *t); +static void proxy_tr_destroy(struct proxy_tr *t); + +struct proxy_tr +{ + struct proxy_tr *next, **prev; + + struct proxy *proxy; /**< Backpointer to proxy */ + + struct domain *origin; /**< Originating domain */ + struct domain *domain; /**< Destination domain */ + + sip_time_t now; /**< When received */ + + nta_incoming_t *server; /**< server transaction */ + msg_t *msg; /**< request message */ + sip_t *sip; /**< request headers */ + + sip_method_t method; /**< request method */ + char const *method_name; + int status; /**< best status */ + url_t *target; /**< request-URI */ + + struct client_tr *clients; /**< Client transactions */ + + struct registration_entry *entry; + /**< Registration entry */ + + auth_mod_t *am; /**< Authentication module */ + auth_status_t *as; /**< Authentication status */ + char const *realm; /**< Authentication realm to use */ + unsigned use_auth; /**< Authentication method (401/407) to use */ + + su_timer_t *timer; /**< Timer */ + + unsigned rr:1; +}; + +LIST_PROTOS(static, client_tr, struct client_tr); + +struct client_tr +{ + struct client_tr *next, **prev; + struct proxy_tr *t; + + int status; /* response status */ + sip_request_t *rq; /* request line */ + msg_t *msg; /* request message */ + sip_t *sip; /* request headers */ + nta_outgoing_t *client; /* transaction */ +}; + +LIST_BODIES(static, client_tr, struct client_tr, next, prev); + +static sip_contact_t *create_transport_contacts(struct proxy *p); + +union proxy_or_domain { struct proxy proxy[1]; struct domain domain[1]; }; + +static int proxy_request(union proxy_or_domain *proxy, + nta_leg_t *leg, + nta_incoming_t *irq, + sip_t const *sip); + +static int domain_request(union proxy_or_domain *domain, + nta_leg_t *leg, + nta_incoming_t *irq, + sip_t const *sip); + +static int proxy_response(struct client_tr *client, + nta_outgoing_t *orq, + sip_t const *sip); + +static int close_tports(void *proxy); + +static auth_challenger_t registrar_challenger[1]; +static auth_challenger_t proxy_challenger[1]; + +/* Proxy entry point */ +static int +test_proxy_init(su_root_t *root, struct proxy *proxy) +{ + struct proxy_tr *t; + struct client_tr *c; + + auth_challenger_t _proxy_challenger[1] = + {{ + SIP_407_PROXY_AUTH_REQUIRED, + sip_proxy_authenticate_class, + sip_proxy_authentication_info_class + }}; + + auth_challenger_t _registrar_challenger[1] = + {{ + SIP_401_UNAUTHORIZED, + sip_www_authenticate_class, + sip_authentication_info_class + }}; + + *proxy_challenger = *_proxy_challenger; + *registrar_challenger = *_registrar_challenger; + + proxy->root = root; + + proxy->agent = nta_agent_create(root, + URL_STRING_MAKE("sip:0.0.0.0:*"), + NULL, NULL, + NTATAG_UA(0), + NTATAG_CANCEL_487(0), + NTATAG_SERVER_RPORT(1), + NTATAG_CLIENT_RPORT(1), + TAG_NEXT(proxy->tags)); + + if (!proxy->agent) + return -1; + + proxy->transport_contacts = create_transport_contacts(proxy); + + proxy->defleg = nta_leg_tcreate(proxy->agent, + proxy_request, + (union proxy_or_domain *)proxy, + NTATAG_NO_DIALOG(1), + TAG_END()); + + proxy->prefs.session_expires = 180; + proxy->prefs.min_se = 90; + proxy->prefs.t1x64 = 64 * 500; + + nta_agent_get_params(proxy->agent, + NTATAG_SIP_T1X64_REF(proxy->prefs.t1x64), + TAG_END()); + + if (!proxy->defleg) + return -1; + /* if (!proxy->example_net || !proxy->example_org || !proxy->example_com) + return -1; */ + + /* Create stateless client */ + t = su_zalloc(proxy->home, sizeof *t); + c = su_zalloc(proxy->home, sizeof *c); + + if (!t || !c) + return -1; + + proxy->stateless = t; + t->proxy = proxy; + c->t = t, client_tr_insert(&t->clients, c); + t->server = nta_incoming_default(proxy->agent); + c->client = nta_outgoing_default(proxy->agent, proxy_response, c); + + if (!c->client || !t->server) + return -1; + + proxy->uri = nta_agent_contact(proxy->agent)->m_url; + proxy->lr_str = su_sprintf(proxy->home, "<" URL_PRINT_FORMAT ";lr>", URL_PRINT_ARGS(proxy->uri)); + proxy->lr = sip_route_make(proxy->home, proxy->lr_str); + + if (!proxy->lr) + return -1; + + return 0; +} + +static void +test_proxy_deinit(su_root_t *root, struct proxy *proxy) +{ + struct proxy_tr *t; + + while (proxy->transactions) + proxy_tr_destroy(proxy->transactions); + + if ((t = proxy->stateless)) { + proxy->stateless = NULL; + proxy_tr_destroy(t); + } + + while (proxy->domains) + domain_destroy(proxy->domains); + + nta_agent_destroy(proxy->agent); + + free(proxy->tags); +} + +/* Create test proxy object */ +struct proxy *test_proxy_create(su_root_t *root, + tag_type_t tag, tag_value_t value, ...) +{ + struct proxy *p = su_home_new(sizeof *p); + + if (p) { + ta_list ta; + + p->magic = test_proxy_create; + + p->parent = root; + + ta_start(ta, tag, value); + p->tags = tl_llist(ta_tags(ta)); + ta_end(ta); + + if (su_clone_start(root, + p->clone, + p, + test_proxy_init, + test_proxy_deinit) == -1) + su_home_unref(p->home), p = NULL; + } + + return p; +} + +/* Destroy the proxy object */ +void test_proxy_destroy(struct proxy *p) +{ + if (p) { + su_clone_wait(p->parent, p->clone); + su_home_unref(p->home); + } +} + +/* Return the proxy URI */ +url_t const *test_proxy_uri(struct proxy const *p) +{ + return p ? p->uri : NULL; +} + +/* Return the proxy route URI */ +char const *test_proxy_route_uri(struct proxy const *p, + sip_route_t const **return_route) +{ + if (p == NULL) + return NULL; + + if (return_route) + *return_route = p->lr; + + return p->lr_str; +} + +struct _set_logging { + struct proxy *p; + int logging; +}; + +static int _set_logging(void *_a) +{ + struct _set_logging *a = _a; + return nta_agent_set_params(a->p->agent, TPTAG_LOG(a->logging), TAG_END()); +} + +void test_proxy_set_logging(struct proxy *p, int logging) +{ + if (p) { + struct _set_logging a[1] = {{ p, logging }}; + su_task_execute(su_clone_task(p->clone), _set_logging, a, NULL); + } +} + +void test_proxy_domain_set_expiration(struct domain *d, + sip_time_t min_expires, + sip_time_t expires, + sip_time_t max_expires) +{ + if (d) { + d->prefs.min_expires = min_expires; + d->prefs.expires = expires; + d->prefs.max_expires = max_expires; + } +} + +void test_proxy_domain_get_expiration(struct domain *d, + sip_time_t *return_min_expires, + sip_time_t *return_expires, + sip_time_t *return_max_expires) +{ + if (d) { + if (return_min_expires) *return_min_expires = d->prefs.min_expires; + if (return_expires) *return_expires = d->prefs.expires; + if (return_max_expires) *return_max_expires = d->prefs.max_expires; + } +} + +void test_proxy_set_session_timer(struct proxy *p, + sip_time_t session_expires, + sip_time_t min_se) +{ + if (p) { + p->prefs.session_expires = session_expires; + p->prefs.min_se = min_se; + } +} + +void test_proxy_get_session_timer(struct proxy *p, + sip_time_t *return_session_expires, + sip_time_t *return_min_se) +{ + if (p) { + if (return_session_expires) + *return_session_expires = p->prefs.session_expires; + if (return_min_se) *return_min_se = p->prefs.min_se; + } +} + +void test_proxy_domain_set_outbound(struct domain *d, + int use_outbound) +{ + if (d) { + d->prefs.outbound_tcp = use_outbound; + } +} + +void test_proxy_domain_get_outbound(struct domain *d, + int *return_use_outbound) +{ + if (d) { + if (return_use_outbound) + *return_use_outbound = d->prefs.outbound_tcp; + } +} + +void test_proxy_domain_set_record_route(struct domain *d, + int use_record_route) +{ + if (d) { + d->prefs.record_route = use_record_route; + } +} + +void test_proxy_domain_get_record_route(struct domain *d, + int *return_use_record_route) +{ + if (d) { + if (return_use_record_route) + *return_use_record_route = d->prefs.record_route; + } +} + +int test_proxy_domain_set_authorize(struct domain *d, + char const *realm) +{ + if (d) { + if (realm) { + realm = su_strdup(d->home, realm); + if (!realm) + return -1; + } + + d->prefs.authorize = realm; + + return 0; + } + return -1; +} + +int test_proxy_domain_get_authorize(struct domain *d, + char const **return_realm) +{ + if (d) { + if (return_realm) { + *return_realm = d->prefs.authorize; + return 0; + } + } + return -1; +} + +int test_proxy_close_tports(struct proxy *p) +{ + if (p) { + int retval = -EPROTO; + + su_task_execute(su_clone_task(p->clone), close_tports, p, &retval); + + if (retval < 0) + return errno = -retval, -1; + else + return 0; + } + return errno = EFAULT, -1; +} + +/* ---------------------------------------------------------------------- */ + +struct domain *test_proxy_add_domain(struct proxy *p, + url_t const *uri, + tag_type_t tag, tag_value_t value, ...) +{ + struct domain *d; + + if (p == NULL || uri == NULL) + return NULL; + + d = su_home_clone(p->home, sizeof *d); + + if (d) { + ta_list ta; + int init = 0; + + ta_start(ta, tag, value); + + d->magic = domain_init; + + d->proxy = p; + d->uri = url_hdup(d->home, uri); + d->tags = tl_adup(d->home, ta_args(ta)); + + d->prefs.min_expires = 300; + d->prefs.expires = 3600; + d->prefs.max_expires = 36000; + d->prefs.outbound_tcp = 0; + d->prefs.authorize = NULL; + + if (d->uri && d->tags && + !su_task_execute(su_clone_task(p->clone), _domain_init, d, &init)) { + if (init == 0) + /* OK */; + else + d = NULL; + } + else + su_home_unref(d->home); + } + + return d; +} + +static int _domain_init(void *_d) +{ + return domain_init(_d); +} + +static int domain_init(struct domain *d) +{ + struct proxy *p = d->proxy; + url_t uri[1]; + + *uri = *d->uri; + + d->auth = auth_mod_create(p->root, TAG_NEXT(d->tags)); + + /* Leg for URIs without userpart */ + d->rleg = nta_leg_tcreate(d->proxy->agent, + domain_request, + (union proxy_or_domain *)d, + NTATAG_NO_DIALOG(1), + URLTAG_URL(uri), + TAG_END()); + + /* Leg for URIs with wildcard userpart */ + uri->url_user = "%"; + d->uleg = nta_leg_tcreate(d->proxy->agent, + domain_request, + (union proxy_or_domain *)d, + NTATAG_NO_DIALOG(1), + URLTAG_URL(uri), + TAG_END()); + + if (d->auth && d->rleg && d->uleg) { + domain_insert(&p->domains, d); + return 0; + } + + domain_destroy(d); + + return -1; +} + +static void domain_destroy(struct domain *d) +{ + while (d->entries) + registration_entry_destroy(d->entries); + + nta_leg_destroy(d->rleg), d->rleg = NULL; + nta_leg_destroy(d->uleg), d->uleg = NULL; + auth_mod_destroy(d->auth), d->auth = NULL; + + domain_remove(d); + + su_home_unref(d->home); +} + +/* ---------------------------------------------------------------------- */ + +static sip_contact_t *create_transport_contacts(struct proxy *p) +{ + su_home_t *home = p->home; + sip_via_t *v; + sip_contact_t *retval = NULL, **mm = &retval; + + if (!p->agent) + return NULL; + + for (v = nta_agent_via(p->agent); v; v = v->v_next) { + char const *proto = v->v_protocol; + + if (v->v_next && + strcasecmp(v->v_host, v->v_next->v_host) == 0 && + str0cmp(v->v_port, v->v_next->v_port) == 0 && + ((proto == sip_transport_udp && + v->v_next->v_protocol == sip_transport_tcp) || + (proto == sip_transport_tcp && + v->v_next->v_protocol == sip_transport_udp))) + /* We have udp/tcp pair, insert URL without tport parameter */ + *mm = sip_contact_create_from_via_with_transport(home, v, NULL, NULL); + if (*mm) mm = &(*mm)->m_next; + + *mm = sip_contact_create_from_via_with_transport(home, v, NULL, proto); + + if (*mm) mm = &(*mm)->m_next; + } + + return retval; +} + +/* ---------------------------------------------------------------------- */ + +static int proxy_tr_with(struct proxy *proxy, + struct domain *domain, + nta_incoming_t *irq, + sip_t const *sip, + int (*process)(struct proxy_tr *)); +static int proxy_transaction(struct proxy_tr *t); +static int respond_transaction(struct proxy_tr *t, + int status, char const *phrase, + tag_type_t tag, tag_value_t value, + ...); +static int validate_transaction(struct proxy_tr *t); +static int originating_transaction(struct proxy_tr *t); +static int challenge_transaction(struct proxy_tr *t); +static int session_timers(struct proxy_tr *t); +static int incoming_transaction(struct proxy_tr *t); +static int target_transaction(struct proxy_tr *t, + url_t const *target, + tport_t *tport); +static int process_register(struct proxy_tr *t); +static int process_options(struct proxy_tr *t); + +static int proxy_ack_cancel(struct proxy_tr *t, + nta_incoming_t *irq, + sip_t const *sip); + +static struct registration_entry * +registration_entry_find(struct domain const *domain, url_t const *uri); + +static int proxy_request(union proxy_or_domain *pod, + nta_leg_t *leg, + nta_incoming_t *irq, + sip_t const *sip) +{ + assert(pod->proxy->magic = test_proxy_init); + + return proxy_tr_with(pod->proxy, NULL, irq, sip, proxy_transaction); +} + +static int domain_request(union proxy_or_domain *pod, + nta_leg_t *leg, + nta_incoming_t *irq, + sip_t const *sip) +{ + int (*process)(struct proxy_tr *) = NULL; + sip_method_t method = sip->sip_request->rq_method; + + assert(pod->domain->magic = domain_init); + + if (leg == pod->domain->uleg) + process = proxy_transaction; + else if (method == sip_method_register) + process = process_register; + else if (method == sip_method_options) + process = process_options; + + if (process == NULL) + return 501; /* Not implemented */ + + return proxy_tr_with(pod->domain->proxy, pod->domain, irq, sip, process); +} + +static int proxy_tr_with(struct proxy *proxy, + struct domain *domain, + nta_incoming_t *irq, + sip_t const *sip, + int (*process)(struct proxy_tr *)) +{ + struct proxy_tr *t = NULL; + int status = 500; + + assert(proxy->magic = test_proxy_init); + + t = proxy_tr_new(proxy); + if (t) { + t->proxy = proxy, t->domain = domain, t->server = irq; + t->msg = nta_incoming_getrequest(irq); + t->sip = sip_object(t->msg); + + t->method = sip->sip_request->rq_method; + t->method_name = sip->sip_request->rq_method_name; + t->target = sip->sip_request->rq_url; + t->now = nta_incoming_received(irq, NULL); + + if (t->method != sip_method_ack && t->method != sip_method_cancel) + nta_incoming_bind(irq, proxy_ack_cancel, t); + + if (domain && domain->prefs.record_route) + t->rr = 1; + + if (process(t) < 200) + return 0; + + proxy_tr_destroy(t); + } + else { + nta_incoming_treply(irq, SIP_500_INTERNAL_SERVER_ERROR, TAG_END()); + } + + return status; +} + +/** Forward request */ +static int proxy_transaction(struct proxy_tr *t) +{ + if (originating_transaction(t)) + return t->status; + + if (validate_transaction(t)) + return t->status; + + if (session_timers(t)) + return t->status; + + if (t->domain) + return incoming_transaction(t); + + return target_transaction(t, t->target, NULL); +} + +static int respond_transaction(struct proxy_tr *t, + int status, char const *phrase, + tag_type_t tag, tag_value_t value, + ...) +{ + ta_list ta; + void *info = NULL, *response = NULL; + + ta_start(ta, tag, value); + + if (t->as) + info = t->as->as_info, response = t->as->as_response; + + if (nta_incoming_treply(t->server, t->status = status, phrase, + SIPTAG_HEADER(info), + SIPTAG_HEADER(response), + ta_tags(ta)) < 0) + t->status = status = 500; + + ta_end(ta); + + return status; +} + +static int originating_transaction(struct proxy_tr *t) +{ + struct domain *o; + char const *host; + + host = t->sip->sip_from->a_url->url_host; + if (!host) + return 0; + + for (o = t->proxy->domains; o; o = o->next) + if (strcasecmp(host, o->uri->url_host) == 0) + break; + + t->origin = o; + + if (o && o->auth && o->prefs.authorize) { + t->am = o->auth; + t->realm = o->prefs.authorize; + t->use_auth = 407; + } + + if (o && o->prefs.record_route) + t->rr = 1; + + return 0; +} + +static int validate_transaction(struct proxy_tr *t) +{ + sip_max_forwards_t *mf; + + mf = t->sip->sip_max_forwards; + + if (mf && mf->mf_count <= 1) { + if (t->method == sip_method_options) + return process_options(t); + + return respond_transaction(t, SIP_483_TOO_MANY_HOPS, TAG_END()); + } + + /* Remove our routes */ + while (t->sip->sip_route && + url_has_param(t->sip->sip_route->r_url, "lr") && + (url_cmp(t->proxy->lr->r_url, t->sip->sip_route->r_url) == 0 || + url_cmp(t->proxy->rr_uri, t->sip->sip_route->r_url) == 0)) { + sip_route_remove(t->msg, t->sip); + /* add record-route also to the forwarded request */ + } + + if (t->use_auth) + return challenge_transaction(t); + + return 0; +} + +static int session_timers(struct proxy_tr *t) +{ + sip_t *sip = t->sip; + sip_session_expires_t *x = NULL, x0[1]; + sip_min_se_t *min_se = NULL, min_se0[1]; + char const *require = NULL; + + if (t->method == sip_method_invite) { + if (t->proxy->prefs.min_se) { + if (!sip->sip_min_se || + sip->sip_min_se->min_delta < t->proxy->prefs.min_se) { + min_se = sip_min_se_init(min_se0); + min_se->min_delta = t->proxy->prefs.min_se; + } + + if (sip->sip_session_expires + && sip->sip_session_expires->x_delta < t->proxy->prefs.min_se + && sip_has_supported(sip->sip_supported, "timer")) { + if (min_se == NULL) + min_se = sip->sip_min_se; assert(min_se); + return respond_transaction(t, SIP_422_SESSION_TIMER_TOO_SMALL, + SIPTAG_MIN_SE(min_se), + TAG_END()); + } + } + + if (t->proxy->prefs.session_expires) { + if (!sip->sip_session_expires || + sip->sip_session_expires->x_delta > t->proxy->prefs.session_expires) { + x = sip_session_expires_init(x0); + x->x_delta = t->proxy->prefs.session_expires; + if (!sip_has_supported(sip->sip_supported, "timer")) + require = "timer"; + } + } + + if (x || min_se || require) + sip_add_tl(t->msg, t->sip, + SIPTAG_REQUIRE_STR(require), + SIPTAG_MIN_SE(min_se), + SIPTAG_SESSION_EXPIRES(x), + TAG_END()); + } + + return 0; +} + +static int incoming_transaction(struct proxy_tr *t) +{ + struct registration_entry *e; + struct binding *b; + +#if 0 + if (sip->sip_request->rq_method == sip_method_register) + return process_register(proxy, irq, sip); +#endif + + t->entry = e = registration_entry_find(t->domain, t->target); + if (e == NULL) + return respond_transaction(t, SIP_404_NOT_FOUND, TAG_END()); + + for (b = e->bindings; b; b = b->next) { + if (binding_is_active(b)) + target_transaction(t, b->contact->m_url, b->tport); + + if (t->clients) /* XXX - enable forking */ + break; + } + + if (t->clients != NULL) + return 0; + + return respond_transaction(t, SIP_480_TEMPORARILY_UNAVAILABLE, TAG_END()); +} + +static int target_transaction(struct proxy_tr *t, + url_t const *target, + tport_t *tport) +{ + struct client_tr *c = su_zalloc(t->proxy->home, sizeof *c); + int stateless = t->method == sip_method_ack; + + if (c == NULL) + return 500; + + c->t = t; + c->msg = msg_copy(t->msg); + c->sip = sip_object(c->msg); + + if (c->msg) + c->rq = sip_request_create(msg_home(c->msg), + t->method, t->method_name, + (url_string_t *)target, + NULL); + + msg_header_insert(c->msg, (msg_pub_t *)c->sip, (msg_header_t *)c->rq); + + if (t->rr) { + sip_record_route_t rr[1]; + + if (t->proxy->rr_uri) { + *sip_record_route_init(rr)->r_url = *t->proxy->rr_uri; + msg_header_add_dup(c->msg, (msg_pub_t *)c->sip, (msg_header_t *)rr); + } + else if (t->proxy->lr) { + *sip_record_route_init(rr)->r_url = *t->proxy->lr->r_url; + msg_header_add_dup(c->msg, (msg_pub_t *)c->sip, (msg_header_t *)rr); + } + } + + if (c->rq) + /* Forward request */ + c->client = nta_outgoing_mcreate(t->proxy->agent, + proxy_response, c, + NULL, + msg_ref_create(c->msg), + NTATAG_TPORT(tport), + NTATAG_STATELESS(stateless), + TAG_END()); + + if (!c->client) { + msg_destroy(c->msg); + su_free(t->proxy->home, c); + return 500; + } + + client_tr_insert(&t->clients, c); + + return stateless ? 200 : 0; +} + +static int challenge_transaction(struct proxy_tr *t) +{ + auth_status_t *as; + sip_t *sip = t->sip; + + assert(t->am); + + t->as = as = auth_status_new(t->proxy->home); + if (!as) + return respond_transaction(t, SIP_500_INTERNAL_SERVER_ERROR, TAG_END()); + + as->as_method = sip->sip_request->rq_method_name; + as->as_source = msg_addrinfo(t->msg); + as->as_realm = t->realm; + + as->as_user_uri = sip->sip_from->a_url; + as->as_display = sip->sip_from->a_display; + + if (sip->sip_payload) + as->as_body = sip->sip_payload->pl_data, + as->as_bodylen = sip->sip_payload->pl_len; + + if (t->use_auth == 401) + auth_mod_check_client(t->am, as, sip->sip_authorization, + registrar_challenger); + else + auth_mod_check_client(t->am, as, sip->sip_proxy_authorization, + proxy_challenger); + + if (as->as_status) + return respond_transaction(t, as->as_status, as->as_phrase, TAG_END()); + + if (as->as_match) + msg_header_remove(t->msg, (msg_pub_t *)sip, as->as_match); + + return 0; +} + +int proxy_ack_cancel(struct proxy_tr *t, + nta_incoming_t *irq, + sip_t const *sip) +{ + struct client_tr *c; + int status; + + if (sip == NULL) { /* timeout */ + proxy_tr_destroy(t); + return 0; + } + + if (sip->sip_request->rq_method != sip_method_cancel) + return 500; + + status = 200; + + for (c = t->clients; c; c = c->next) { + if (c->client && c->status < 200) + /* + * We don't care about response to CANCEL (or ACK) + * so we give NULL as callback pointer (and nta immediately + * destroys transaction object or marks it disposable) + */ + if (nta_outgoing_tcancel(c->client, NULL, NULL, TAG_END()) == NULL) + status = 500; + } + + return status; +} + +int proxy_response(struct client_tr *c, + nta_outgoing_t *client, + sip_t const *sip) +{ + int final, timeout = 0; + + assert(c->t); + + if (sip) { + msg_t *response = nta_outgoing_getresponse(client); + if (c->t->method == sip_method_invite) + final = sip->sip_status->st_status >= 300, + timeout = sip->sip_status->st_status >= 200; + else + final = sip->sip_status->st_status >= 200; + sip_via_remove(response, sip_object(response)); + nta_incoming_mreply(c->t->server, response); + } + else { + int status = nta_outgoing_status(c->client); + char const *phrase; + + if (status < 300 || status > 699) + status = 500; + phrase = sip_status_phrase(status); + respond_transaction(c->t, status, phrase, TAG_END()); + final = 1; + } + + if (final) + proxy_tr_destroy(c->t); + else if (timeout) + proxy_tr_timeout(c->t); + + return 0; +} + +int proxy_late_response(struct client_tr *c, + nta_outgoing_t *client, + sip_t const *sip) +{ + assert(c->t); + + if (sip && + sip->sip_status->st_status >= 200 && + sip->sip_status->st_status < 300) { + msg_t *response = nta_outgoing_getresponse(client); + sip_via_remove(response, sip_object(response)); + nta_incoming_mreply(c->t->server, response); + } + + return 0; +} + +static void proxy_tr_remove_late(su_root_magic_t *magic, + su_timer_t *timer, + struct proxy_tr *t) +{ + proxy_tr_destroy(t); +} + + +/** Proxy only late responses + * + * Keeping the invite transactions live + */ +static void proxy_tr_timeout(struct proxy_tr *t) +{ + struct client_tr *c; + + if (t == t->proxy->stateless) + return; + + for (c = t->clients; c; c = c->next) { + if (c->client && c->status < 300) { + nta_outgoing_bind(c->client, proxy_late_response, c); + if (c->status < 200) { + nta_outgoing_tcancel(c->client, NULL, NULL, TAG_END()); + } + } + } + + t->timer = su_timer_create(su_root_task(t->proxy->root), t->proxy->prefs.t1x64); + if (su_timer_set(t->timer, proxy_tr_remove_late, t) < 0) { + proxy_tr_destroy(t); + } +} + +struct proxy_tr * +proxy_tr_new(struct proxy *proxy) +{ + struct proxy_tr *t; + + t = su_zalloc(proxy->home, sizeof *t); + if (t) { + t->proxy = proxy; + proxy_tr_insert(&proxy->transactions, t); + } + return t; +} + +static +void proxy_tr_destroy(struct proxy_tr *t) +{ + struct client_tr *c; + + if (t == t->proxy->stateless) + return; + + proxy_tr_remove(t); + + if (t->as) + su_home_unref(t->as->as_home), t->as = NULL; + + while (t->clients) { + client_tr_remove(c = t->clients); + nta_outgoing_destroy(c->client), c->client = NULL; + msg_destroy(c->msg), c->msg = NULL; + su_free(t->proxy->home, c); + } + + su_timer_destroy(t->timer), t->timer = NULL; + + msg_destroy(t->msg); + + nta_incoming_destroy(t->server); + + su_free(t->proxy->home, t); +} + +LIST_BODIES(static, proxy_tr, struct proxy_tr, next, prev); + +/* ---------------------------------------------------------------------- */ + +static int process_options(struct proxy_tr *t) +{ + return respond_transaction(t, SIP_200_OK, + SIPTAG_CONTACT(t->proxy->transport_contacts), + TAG_END()); +} + +/* ---------------------------------------------------------------------- */ + +static int check_received_contact(struct proxy_tr *t); +static int validate_contacts(struct proxy_tr *t); +static int check_out_of_order(struct proxy_tr *t); +static int update_bindings(struct proxy_tr *t); + +int process_register(struct proxy_tr *t) +{ + /* This is before authentication because we want to be bug-compatible */ + if (check_received_contact(t)) + return t->status; + + if (t->domain->auth) { + t->am = t->domain->auth, t->use_auth = 401; + if (t->domain->prefs.authorize) + t->realm = t->domain->prefs.authorize; + if (challenge_transaction(t)) + return t->status; + } + + if (validate_contacts(t)) + return t->status; + + t->entry = registration_entry_find(t->domain, t->sip->sip_to->a_url); + + if (check_out_of_order(t)) + return t->status; + + return update_bindings(t); +} + +static int check_received_contact(struct proxy_tr *t) +{ + sip_t *sip = t->sip; + sip_contact_t *m = sip->sip_contact; + sip_via_t *v = sip->sip_via; + + if (m && v && v->v_received && m->m_url->url_host + && strcasecmp(v->v_received, m->m_url->url_host) + && host_is_ip_address(m->m_url->url_host)) + return respond_transaction(t, 406, "Unacceptable Contact", TAG_END()); + + return 0; +} + +/* Validate expiration times */ +static int validate_contacts(struct proxy_tr *t) +{ + sip_contact_t const *m = t->sip->sip_contact; + sip_expires_t const *ex = t->sip->sip_expires; + sip_date_t const *date = t->sip->sip_date; + sip_time_t expires; + + if (m && m->m_url->url_type == url_any) { + if (!ex || ex->ex_delta || ex->ex_time || m->m_next) + return respond_transaction(t, SIP_400_BAD_REQUEST, TAG_END()); + return 0; + } + + for (; m; m = m->m_next) { + expires = sip_contact_expires(m, ex, date, t->domain->prefs.expires, t->now); + + if (expires > 0 && expires < t->domain->prefs.min_expires) { + sip_min_expires_t me[1]; + + sip_min_expires_init(me)->me_delta = t->domain->prefs.min_expires; + + return respond_transaction(t, SIP_423_INTERVAL_TOO_BRIEF, + SIPTAG_MIN_EXPIRES(me), + TAG_END()); + } + } + + return 0; +} + +/** Check for out-of-order register request */ +static int check_out_of_order(struct proxy_tr *t) +{ + struct binding const *b; + sip_call_id_t const *id = t->sip->sip_call_id; + uint32_t cseq = t->sip->sip_cseq->cs_seq; + sip_contact_t *m; + + if (t->entry == NULL || !t->sip->sip_contact) + return 0; + + /* RFC 3261 subsection 10.3 step 6 and step 7 (p. 66): */ + /* Check for reordered register requests */ + for (b = t->entry->bindings; b; b = b->next) { + if (binding_is_active(b) && + strcmp(id->i_id, b->call_id->i_id) == 0 && + cseq <= b->cseq) { + for (m = t->sip->sip_contact; m; m = m->m_next) { + if (m->m_url->url_type == url_any || + url_cmp_all(m->m_url, b->contact->m_url) == 0) + return respond_transaction(t, SIP_500_INTERNAL_SERVER_ERROR, + TAG_END()); + } + } + } + + return 0; +} + +static struct registration_entry * +registration_entry_find(struct domain const *d, url_t const *uri) +{ + struct registration_entry *e; + + /* Our routing table */ + for (e = d->entries; e; e = e->next) { + if (url_cmp(uri, e->aor) == 0) + return e; + } + + return NULL; +} + +static struct registration_entry * +registration_entry_new(struct domain *d, url_t const *aor) +{ + struct registration_entry *e; + + if (d == NULL) + return NULL; + + e = su_zalloc(d->home, sizeof *e); + if (!e) + return NULL; + + e->domain = d; + e->aor = url_hdup(d->home, aor); + if (!e->aor) { + su_free(d->home, e); + return NULL; + } + + registration_entry_insert(&d->entries, e); + + return e; +} + +static void +registration_entry_destroy(struct registration_entry *e) +{ + if (e) { + registration_entry_remove(e); + su_free(e->domain->home, e->aor); + while (e->bindings) + binding_destroy(e->domain->home, e->bindings); + msg_header_free(e->domain->home, (void *)e->contacts); + su_free(e->domain->home, e); + } +} + +sip_contact_t *entry_contacts(struct registration_entry *entry) +{ + return entry ? entry->contacts : NULL; +} + +LIST_BODIES(static, registration_entry, struct registration_entry, next, prev); + +/* ---------------------------------------------------------------------- */ +/* Bindings */ + +static +struct binding *binding_new(su_home_t *home, + sip_contact_t *contact, + tport_t *tport, + sip_call_id_t const *call_id, + uint32_t cseq, + sip_time_t registered, + sip_time_t expires) +{ + struct binding *b; + + b = su_zalloc(home, sizeof *b); + + if (b) { + sip_contact_t m[1]; + *m = *contact; m->m_next = NULL; + + b->contact = sip_contact_dup(home, m); + b->tport = tport_ref(tport); + b->call_id = sip_call_id_dup(home, call_id); + b->cseq = cseq; + b->registered = registered; + b->expires = expires; + + if (!b->contact || !b->call_id) + binding_destroy(home, b), b = NULL; + + if (b) + msg_header_remove_param(b->contact->m_common, "expires"); + } + + return b; +} + +static +void binding_destroy(su_home_t *home, struct binding *b) +{ + if (b->prev) { + if ((*b->prev = b->next)) + b->next->prev = b->prev; + } + msg_header_free(home, (void *)b->contact); + msg_header_free(home, (void *)b->call_id); + tport_unref(b->tport); + su_free(home, b); +} + +static int update_bindings(struct proxy_tr *t) +{ + struct domain *d = t->domain; + struct binding *b, *old, *next, *last, *bindings = NULL, **bb = &bindings; + sip_contact_t *m; + sip_call_id_t const *id = t->sip->sip_call_id; + uint32_t cseq = t->sip->sip_cseq->cs_seq; + sip_expires_t *ex = t->sip->sip_expires; + sip_date_t *date = t->sip->sip_date; + sip_time_t expires; + tport_t *tport = NULL; + sip_contact_t *contacts = NULL, **mm = &contacts; + void *tbf; + + if (t->sip->sip_contact == NULL) { + if (t->entry) + contacts = t->entry->contacts; + goto ok200; + } + + if (t->entry == NULL) + t->entry = registration_entry_new(d, t->sip->sip_to->a_url); + if (t->entry == NULL) + return respond_transaction(t, SIP_500_INTERNAL_SERVER_ERROR, TAG_END()); + + if (d->prefs.outbound_tcp && + str0casecmp(t->sip->sip_via->v_protocol, sip_transport_tcp) == 0) + tport = nta_incoming_transport(t->proxy->agent, t->server, NULL); + + /* Create new bindings */ + for (m = t->sip->sip_contact; m; m = m->m_next) { + if (m->m_url->url_type == url_any) + break; + + expires = sip_contact_expires(m, ex, date, d->prefs.expires, t->now); + + if (expires > d->prefs.max_expires) + expires = d->prefs.max_expires; + + msg_header_remove_param(m->m_common, "expires"); + + b = binding_new(d->home, m, tport, id, cseq, t->now, t->now + expires); + if (!b) + break; + + *bb = b, b->prev = bb, bb = &b->next; + } + + tport_unref(tport); + + last = NULL; + + if (m == NULL) { + /* Merge new bindings with old ones */ + for (old = t->entry->bindings; old; old = next) { + next = old->next; + + for (b = bindings; b != last; b = b->next) { + if (url_cmp_all(old->contact->m_url, b->contact->m_url) != 0) + continue; + + if (strcmp(old->call_id->i_id, b->call_id->i_id) == 0) { + b->registered = old->registered; + } + binding_destroy(d->home, old); + break; + } + } + + for (bb = &t->entry->bindings; *bb; bb = &(*bb)->next) + ; + + if ((*bb = bindings)) + bindings->prev = bb; + } + else if (m->m_url->url_type == url_any) { + /* Unregister all */ + for (b = t->entry->bindings; b; b = b->next) { + b->expires = t->now; + } + } + else { + /* Infernal error */ + + for (old = bindings; old; old = next) { + next = old->next; + binding_destroy(d->home, old); + } + + return respond_transaction(t, SIP_500_INTERNAL_SERVER_ERROR, TAG_END()); + } + + for (b = t->entry->bindings; b; b = b->next) { + char const *expires; + + if (b->expires <= t->now) + continue; + + *mm = sip_contact_copy(d->home, b->contact); + if (*mm) { + expires = su_sprintf(d->home, "expires=%u", + (unsigned)(b->expires - t->now)); + msg_header_add_param(d->home, (*mm)->m_common, expires); + mm = &(*mm)->m_next; + } + } + + tbf = t->entry->contacts; + t->entry->contacts = contacts; + msg_header_free(d->home, tbf); + + ok200: + return respond_transaction(t, SIP_200_OK, + SIPTAG_CONTACT(contacts), + TAG_END()); +} + +/* ---------------------------------------------------------------------- */ + +static int close_tports(void *_proxy) +{ + struct proxy *p = _proxy; + struct domain *d; + struct registration_entry *e; + struct binding *b; + + /* Close all outbound transports */ + for (d = p->domains; d; d = d->next) { + for (e = d->entries; e; e = e->next) { + for (b = e->bindings; b; b = b->next) { + if (b->tport) { + tport_shutdown(b->tport, 1); + tport_unref(b->tport); + b->tport = NULL; + } + } + } + } + + return 0; +} Added: freeswitch/trunk/libs/sofia-sip/tests/test_proxy.h ============================================================================== --- (empty file) +++ freeswitch/trunk/libs/sofia-sip/tests/test_proxy.h Wed May 14 15:10:54 2008 @@ -0,0 +1,87 @@ +/* + * This file is part of the Sofia-SIP package + * + * Copyright (C) 2005 Nokia Corporation. + * + * Contact: Pekka Pessi <pekka.pessi@nokia.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public License + * as published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + */ + +#ifndef TEST_PROXY_H +#define TEST_PROXY_H + +#include <sofia-sip/su_wait.h> +#include <sofia-sip/nta.h> + +SOFIA_BEGIN_DECLS + +struct proxy; +struct domain; + +struct proxy *test_proxy_create(su_root_t *, tag_type_t, tag_value_t, ...); + +void test_proxy_destroy(struct proxy *); + +url_t const *test_proxy_uri(struct proxy const *); + +char const *test_proxy_route_uri(struct proxy const *p, + sip_route_t const **return_route); + +struct domain *test_proxy_add_domain(struct proxy *, + url_t const *domain, + tag_type_t, tag_value_t, ...); + +void test_proxy_set_logging(struct proxy *, int logging); + +void test_proxy_domain_set_expiration(struct domain *, + sip_time_t min_expires, + sip_time_t expires, + sip_time_t max_expires); + +void test_proxy_domain_get_expiration(struct domain *, + sip_time_t *return_min_expires, + sip_time_t *return_expires, + sip_time_t *return_max_expires); + +void test_proxy_set_session_timer(struct proxy *p, + sip_time_t session_expires, + sip_time_t min_se); + +void test_proxy_get_session_timer(struct proxy *p, + sip_time_t *return_session_expires, + sip_time_t *return_min_se); + +int test_proxy_domain_set_authorize(struct domain *, char const *realm); +int test_proxy_domain_get_authorize(struct domain *, + char const **return_realm); + +void test_proxy_domain_set_outbound(struct domain *d, + int use_outbound); +void test_proxy_domain_get_outbound(struct domain *d, + int *return_use_outbound); + +void test_proxy_domain_set_record_route(struct domain *d, + int use_record_route); +void test_proxy_domain_get_record_route(struct domain *d, + int *return_use_record_route); + +int test_proxy_close_tports(struct proxy *p); + +SOFIA_END_DECLS + +#endif Added: freeswitch/trunk/libs/sofia-sip/tests/test_refer.c ============================================================================== --- (empty file) +++ freeswitch/trunk/libs/sofia-sip/tests/test_refer.c Wed May 14 15:10:54 2008 @@ -0,0 +1,852 @@ +/* + * This file is part of the Sofia-SIP package + * + * Copyright (C) 2005 Nokia Corporation. + * + * Contact: Pekka Pessi <pekka.pessi@nokia.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public License + * as published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + */ + +/**@CFILE test_nua_re_invite.c + * @brief Test re_inviteing, outbound, nat traversal. + * + * @author Pekka Pessi <Pekka.Pessi@nokia.com> + * @author Martti Mela <Martti Mela@nokia.com> + * + * @date Created: Wed Aug 17 12:12:12 EEST 2005 ppessi + */ + +#include "config.h" + +#include "test_nua.h" +#include <sofia-sip/su_tag_class.h> + +#if HAVE_FUNC +#elif HAVE_FUNCTION +#define __func__ __FUNCTION__ +#else +#define __func__ "test_call_hold" +#endif + +/* ======================================================================== */ +/* NUA-9 tests: REFER */ + +int test_refer0(struct context *ctx, char const *tests, + int refer_with_id, int notify_by_appl); +int notify_until_terminated(CONDITION_PARAMS); +int test_challenge_refer(struct context *ctx); + +int test_refer(struct context *ctx) +{ + /* test twice, once without id and once with id */ + return + test_challenge_refer(ctx) || + test_refer0(ctx, "NUA-9.1", 0, 0) || + test_refer0(ctx, "NUA-9.2", 1, 0) || + test_refer0(ctx, "NUA-9.3", 0, 1) || + test_refer0(ctx, "NUA-9.4", 1, 1); +} + +/* Referred call: + + A B + | | + |-------INVITE------>| + |<----100 Trying-----| + | | + |<----180 Ringing----| + | | + |<------200 OK-------| + |--------ACK-------->| + | | + |<------REFER--------| + |-------200 OK------>| C + [|-------NOTIFY------>|] | + [|<------200 OK-------|] | + | | | + | | | + |<-----SUBSCRIBE-----| | + |-------200 OK------>| | + | | | + | | | + |-----------------INVITE-------------------->| + | | | + |<------------------180----------------------| + |-------NOTIFY------>| | + |<------200 OK-------| | + | | | + |<------------------200----------------------| + |-------NOTIFY------>| | + |<------200 OK-------| | + |-------------------ACK--------------------->| + | | | + |--------BYE-------->| | + |<------200 OK-------| | + | X | + | | + |-------------------BYE--------------------->| + |<------------------200----------------------| + | | + +*/ + +int test_refer0(struct context *ctx, char const *tests, + int refer_with_id, int notify_by_appl) +{ + BEGIN(); + + struct endpoint *a = &ctx->a, *b = &ctx->b, *c = &ctx->c; + struct call *a_call = a->call, *b_call = b->call, *c_call = c->call; + struct call *a_refer, *a_c2, *b_refer; + struct eventlist *a_revents, *b_revents; + struct event *e, *notify_e; + sip_t const *sip; + sip_event_t const *a_event, *b_event; + sip_refer_to_t const *refer_to; + sip_referred_by_t const *referred_by; + + sip_refer_to_t r0[1]; + sip_to_t to[1]; + + su_home_t tmphome[SU_HOME_AUTO_SIZE(16384)]; + + su_home_auto(tmphome, sizeof(tmphome)); + + if (print_headings) + printf("TEST %s: REFER: refer A to C%s%s%s\n", tests, + refer_with_id ? " with Event id" : "", + refer_with_id && !notify_by_appl ? " and" : "", + !notify_by_appl ? " nua generating the NOTIFYs" : ""); + + if (print_headings) + printf("TEST %s.1: REFER: make a call between A and B\n", tests); + + /* Do (not) include id with first implicit Event: refer */ + nua_set_params(ctx->a.nua, NUTAG_REFER_WITH_ID(refer_with_id), TAG_END()); + run_a_until(ctx, nua_r_set_params, until_final_response); + + if (refer_with_id) { + TEST_1(a_refer = calloc(1, (sizeof *a_refer) + (sizeof *a_refer->events))); + call_init(a_refer); + a_refer->events = (void *)(a_refer + 1); + eventlist_init(a_refer->events); + + a_call->next = a_refer; + a_revents = a_refer->events; + + TEST_1(b_refer = calloc(1, (sizeof *b_refer) + (sizeof *b_refer->events))); + call_init(b_refer); + b_refer->events = (void *)(b_refer + 1); + eventlist_init(b_refer->events); + + b_call->next = b_refer; + b_revents = b_refer->events; + } + else { + a_refer = a_call, b_refer = b_call; + a_revents = a->events, b_revents = b->events; + } + + TEST_1(a_c2 = calloc(1, (sizeof *a_c2) + (sizeof *a_c2->events))); + call_init(a_c2); + a_c2->events = (void *)(a_c2 + 1); + eventlist_init(a_c2->events); + + a_call->sdp = "m=audio 5008 RTP/AVP 8"; + b_call->sdp = "m=audio 5010 RTP/AVP 0 8"; + a_c2->sdp = "m=audio 5012 RTP/AVP 8"; + c_call->sdp = "m=audio 5014 RTP/AVP 0 8"; + + TEST_1(a_call->nh = nua_handle(a->nua, a_call, SIPTAG_TO(b->to), TAG_END())); + + INVITE(a, a_call, a_call->nh, + TAG_IF(!ctx->proxy_tests, NUTAG_URL(b->contact->m_url)), + SOATAG_USER_SDP_STR(a_call->sdp), + TAG_END()); + + run_ab_until(ctx, -1, until_ready, -1, accept_call); + + /* Client transitions: + INIT -(C1)-> CALLING: nua_invite(), nua_i_state + CALLING -(C2)-> PROCEEDING: nua_r_invite, nua_i_state + PROCEEDING -(C3+C4)-> READY: nua_r_invite, nua_i_state + */ + TEST_1(e = a->events->head); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_calling); /* CALLING */ + TEST_1(is_offer_sent(e->data->e_tags)); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_r_invite); + TEST(e->data->e_status, 180); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_proceeding); /* PROCEEDING */ + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_r_invite); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_ready); /* READY */ + TEST_1(is_answer_recv(e->data->e_tags)); + TEST_1(!e->next); + free_events_in_list(ctx, a->events); + + /* + Server transitions: + INIT -(S1)-> RECEIVED: nua_i_invite, nua_i_state + RECEIVED -(S2a)-> EARLY: nua_respond(), nua_i_state + EARLY -(S3b)-> COMPLETED: nua_respond(), nua_i_state + COMPLETED -(S4)-> READY: nua_i_ack, nua_i_state + */ + TEST_1(e = b->events->head); TEST_E(e->data->e_event, nua_i_invite); + TEST(e->data->e_status, 100); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_received); /* RECEIVED */ + TEST_1(is_offer_recv(e->data->e_tags)); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_early); /* EARLY */ + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_completed); /* COMPLETED */ + TEST_1(is_answer_sent(e->data->e_tags)); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_ack); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_ready); /* READY */ + TEST_1(!e->next); + free_events_in_list(ctx, b->events); + + if (print_headings) + printf("TEST %s.1: PASSED\n", tests); + + /* ---------------------------------------------------------------------- */ + /* REFER (initial NOTIFY is no more sent unless REFER creates a new dialog) + A B + |<------REFER--------| + |-------200 OK------>| + [|-------NOTIFY------>|] | + [|<------200 OK-------|] | + */ + + if (print_headings) + printf("TEST %s.2: B refers A to C\n", tests); + + if (b_refer != b_call) + TEST_1(b_refer->nh = + nua_handle(b->nua, b_refer, SIPTAG_TO(a->to), TAG_END())); + + *sip_refer_to_init(r0)->r_url = *c->contact->m_url; + r0->r_url->url_headers = "subject=referred"; + r0->r_display = "C"; + + REFER(b, b_refer, b_refer->nh, SIPTAG_REFER_TO(r0), + TAG_IF(!ctx->proxy_tests && b_refer != b_call, + NUTAG_URL(a->contact->m_url)), + TAG_END()); + run_ab_until(ctx, -1, save_until_received, + -1, save_until_final_response); + + /* + Events in A: + nua_i_refer + */ + TEST_1(e = a_revents->head); TEST_E(e->data->e_event, nua_i_refer); + TEST(e->data->e_status, 202); + a_event = NULL; + TEST(tl_gets(e->data->e_tags, + NUTAG_REFER_EVENT_REF(a_event), + TAG_END()), 1); + TEST_1(a_event); TEST_1(a_event = sip_event_dup(tmphome, a_event)); + TEST_1(sip = sip_object(e->data->e_msg)); + TEST_1(sip->sip_refer_to); + TEST_1(refer_to = sip_refer_to_dup(tmphome, sip->sip_refer_to)); + TEST_1(sip->sip_referred_by); + TEST_1(referred_by = sip_referred_by_dup(tmphome, sip->sip_referred_by)); + + /* + Events in B after nua_refer(): + nua_r_refer + */ + TEST_1(e = b_revents->head); TEST_E(e->data->e_event, nua_r_refer); + TEST(e->data->e_status, 100); + TEST(tl_gets(e->data->e_tags, + NUTAG_REFER_EVENT_REF(b_event), + TAG_END()), 1); + TEST_1(b_event); TEST_1(b_event->o_id); + TEST_1(b_event = sip_event_dup(tmphome, b_event)); + + notify_e = NULL; + + TEST_1(e = e->next); + if (e->data->e_event == nua_i_notify) { + notify_e = e; + TEST_1(e = e->next); + } + TEST_E(e->data->e_event, nua_r_refer); + TEST(e->data->e_status, 202); + TEST_1(sip = sip_object(e->data->e_msg)); + TEST_SIZE(strtoul(b_event->o_id, NULL, 10), sip->sip_cseq->cs_seq); + + if (a_refer != a_call) { + while (!notify_e) { + for (e = b_revents->head; e; e = e->next) { + if (e->data->e_event == nua_i_notify) { + notify_e = e; + break; + } + } + if (!notify_e) + run_ab_until(ctx, -1, save_until_received, nua_i_notify, save_events); + } + + if (a_revents->head->next == NULL) + run_a_until(ctx, -1, save_until_received); + + TEST_1(e = a_revents->head->next); TEST_E(e->data->e_event, nua_r_notify); + TEST_1(!e->next); + + TEST_1(e = notify_e); + TEST_E(e->data->e_event, nua_i_notify); + TEST(e->data->e_status, 200); + TEST_1(sip = sip_object(e->data->e_msg)); + TEST_1(sip->sip_event); + if (refer_with_id) + TEST_S(sip->sip_event->o_id, b_event->o_id); + TEST_1(sip->sip_subscription_state); + TEST_S(sip->sip_subscription_state->ss_substate, "pending"); + TEST_1(sip->sip_payload && sip->sip_payload->pl_data); + TEST_M(sip->sip_payload->pl_data, "SIP/2.0 100 Trying\r\n", + sip->sip_payload->pl_len); + } + + free_events_in_list(ctx, a_revents); + free_events_in_list(ctx, b_revents); + + if (print_headings) + printf("TEST %s.2: PASSED\n", tests); + +#if 0 + /* ---------------------------------------------------------------------- */ + /* + A B + |<-----SUBSCRIBE-----| + |-------200 OK------>| + |-------NOTIFY------>| | + |<------200 OK-------| | + */ + + if (print_headings) + printf("TEST %s.3: extend expiration time for implied subscription\n", tests); + + SUBSCRIBE(b, b_call, b_call->nh, + SIPTAG_EVENT(b_event), + SIPTAG_EXPIRES_STR("3600"), + TAG_END()); + run_ab_until(ctx, -1, save_until_final_response, + -1, save_until_final_response); + + /* + Events in A: + nua_i_subscribe, nua_r_notify + */ + TEST_1(e = a->events->head); + if (e->data->e_event == nua_r_notify) + TEST_1(e = e->next); + TEST_E(e->data->e_event, nua_i_subscribe); + TEST(e->data->e_status, 202); + TEST_1(sip = sip_object(e->data->e_msg)); + TEST_1(sip->sip_event); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_r_notify); + TEST_1(!e->next); + free_events_in_list(ctx, a->events); + + /* + Events in B after nua_subscribe(): + nua_r_subscribe, nua_i_notify + */ + TEST_1(e = b->events->head); + if (e->data->e_event == nua_i_notify) { + TEST(e->data->e_status, 200); + TEST_1(sip = sip_object(e->data->e_msg)); + TEST_1(sip->sip_event); + if (refer_with_id) + TEST_S(sip->sip_event->o_id, b_event->o_id); + TEST_1(sip->sip_subscription_state); + TEST_S(sip->sip_subscription_state->ss_substate, "pending"); + TEST_1(sip->sip_payload && sip->sip_payload->pl_data); + TEST_M(sip->sip_payload->pl_data, "SIP/2.0 100 Trying\r\n", + sip->sip_payload->pl_len); + TEST_1(e = e->next); + } + TEST_E(e->data->e_event, nua_r_subscribe); + TEST(e->data->e_status, 202); + if (!e->next) + run_b_until(ctx, -1, save_until_received); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_notify); + TEST(e->data->e_status, 200); + TEST_1(sip = sip_object(e->data->e_msg)); + TEST_1(sip->sip_event); + if (refer_with_id) + TEST_S(sip->sip_event->o_id, b_event->o_id); + TEST_1(sip->sip_subscription_state); + TEST_S(sip->sip_subscription_state->ss_substate, "pending"); + TEST_1(!e->next); + free_events_in_list(ctx, b->events); + + if (print_headings) + printf("TEST %s.3: PASSED\n", tests); +#endif + + /* ---------------------------------------------------------------------- */ + /* + A B C + | | | + |-----------------INVITE-------------------->| + | | | + XXX | | + | | | + |<------------------180----------------------| + |-------NOTIFY------>| | + |<------200 OK-------| | + | | | + XXX | | + | | | + |<------------------200----------------------| + |-------NOTIFY------>| | + |<------200 OK-------| | + |-------------------ACK--------------------->| + */ + + if (print_headings) + printf("TEST %s.4: A invites C\n", tests); + + *sip_to_init(to)->a_url = *refer_to->r_url; + to->a_display = refer_to->r_display; + + a_refer->next = a_c2; + + TEST_1(a_c2->nh = nua_handle(a->nua, a_c2, SIPTAG_TO(to), TAG_END())); + + INVITE(a, a_c2, a_c2->nh, /* NUTAG_URL(refer_to->r_url), */ + TAG_IF(!notify_by_appl, NUTAG_REFER_EVENT(a_event)), + TAG_IF(!notify_by_appl, NUTAG_NOTIFY_REFER(a_refer->nh)), + SOATAG_USER_SDP_STR(a_c2->sdp), + SIPTAG_REFERRED_BY(referred_by), + TAG_END()); + + run_abc_until(ctx, + -1, notify_by_appl ? notify_until_terminated : until_ready, + -1, save_until_received, + -1, accept_call); + + /* Wait until both NOTIFY has been responded */ + while (a_revents->head == NULL || a_revents->head->next == NULL) + run_ab_until(ctx, -1, save_until_received, -1, save_events); + while (b_revents->head == NULL || b_revents->head->next == NULL) + run_ab_until(ctx, -1, save_events, -1, save_until_received); + + /* Client A transitions: + INIT -(C1)-> CALLING: nua_invite(), nua_i_state + CALLING -(C2+C4)-> PROCEEDING: nua_r_invite, nua_i_state + nua_r_notify + PROCEEDING -(C3+C4)-> READY: nua_r_invite, nua_i_state + nua_r_notify + */ + TEST_1(e = a_c2->events->head); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_calling); /* CALLING */ + TEST_1(is_offer_sent(e->data->e_tags)); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_r_invite); + TEST(e->data->e_status, 180); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_proceeding); /* PROCEEDING */ + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_r_invite); + TEST(e->data->e_status, 200); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_ready); /* READY */ + TEST_1(is_answer_recv(e->data->e_tags)); + TEST_1(!e->next); + free_events_in_list(ctx, a_c2->events); + + TEST_1(e = a_revents->head); TEST_E(e->data->e_event, nua_r_notify); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_r_notify); + if (a_refer == a_call && notify_by_appl) { + free_event_in_list(ctx, a_revents, a_revents->head); + free_event_in_list(ctx, a_revents, a_revents->head); + } + else { + TEST_1(!e->next); + free_events_in_list(ctx, a_revents); + } + + /* + Events in B after nua_refer(): + nua_i_notify + */ + TEST_1(e = b_revents->head); TEST_E(e->data->e_event, nua_i_notify); + TEST(e->data->e_status, 200); + TEST_1(sip = sip_object(e->data->e_msg)); + TEST_1(sip->sip_subscription_state); + TEST_S(sip->sip_subscription_state->ss_substate, "active"); + TEST_1(sip->sip_payload && sip->sip_payload->pl_data); + TEST_M(sip->sip_payload->pl_data, "SIP/2.0 180 Ringing\r\n", sip->sip_payload->pl_len); + TEST_1(sip->sip_event); + if (refer_with_id) + TEST_S(sip->sip_event->o_id, b_event->o_id); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_notify); + TEST(e->data->e_status, 200); + TEST_1(sip = sip_object(e->data->e_msg)); + TEST_1(sip->sip_subscription_state); + TEST_S(sip->sip_subscription_state->ss_substate, "terminated"); + TEST_1(sip->sip_payload && sip->sip_payload->pl_data); + TEST_M(sip->sip_payload->pl_data, "SIP/2.0 200 OK\r\n", sip->sip_payload->pl_len); + TEST_1(sip->sip_event); + if (refer_with_id) + TEST_S(sip->sip_event->o_id, b_event->o_id); + if (b_refer == b_call && notify_by_appl) { + free_event_in_list(ctx, b_revents, b_revents->head); + free_event_in_list(ctx, b_revents, b_revents->head); + } + else { + TEST_1(!e->next); + free_events_in_list(ctx, b_revents); + } + + /* + C transitions: + INIT -(S1)-> RECEIVED: nua_i_invite, nua_i_state + RECEIVED -(S2a)-> EARLY: nua_respond(), nua_i_state + EARLY -(S3b)-> COMPLETED: nua_respond(), nua_i_state + COMPLETED -(S4)-> READY: nua_i_ack, nua_i_state + */ + TEST_1(e = c->events->head); TEST_E(e->data->e_event, nua_i_invite); + TEST(e->data->e_status, 100); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_received); /* RECEIVED */ + TEST_1(is_offer_recv(e->data->e_tags)); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_early); /* EARLY */ + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_completed); /* COMPLETED */ + TEST_1(is_answer_sent(e->data->e_tags)); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_ack); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_ready); /* READY */ + TEST_1(!e->next); + free_events_in_list(ctx, c->events); + + if (print_headings) + printf("TEST %s.4: PASSED\n", tests); + + /* ---------------------------------------------------------------------- */ + /* + A B + |---------BYE------->| + |<--------200--------| + */ + + if (print_headings) + printf("TEST %s.5.1: terminate call between A and B\n", tests); + + if (notify_by_appl) { + if (!a->events->head || !a->events->head->next) + run_ab_until(ctx, -1, until_terminated, -1, save_events); + if (!b->events->head || !b->events->head->next) + run_ab_until(ctx, -1, save_events, -1, until_terminated); + } + else { + BYE(a, a_call, a_call->nh, TAG_END()); + run_ab_until(ctx, -1, until_terminated, -1, until_terminated); + } + + /* + Transitions of A: + READY --(T2)--> TERMINATING: nua_bye() + TERMINATING --(T3)--> TERMINATED: nua_r_bye, nua_i_state + */ + TEST_1(e = a->events->head); TEST_E(e->data->e_event, nua_r_bye); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_terminated); /* TERMINATED */ + TEST_1(!e->next); + free_events_in_list(ctx, a->events); + + /* Transitions of B: + READY -(T1)-> TERMINATED: nua_i_bye, nua_i_state + */ + TEST_1(e = b->events->head); TEST_E(e->data->e_event, nua_i_bye); + TEST(e->data->e_status, 200); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_terminated); /* TERMINATED */ + TEST_1(!e->next); + free_events_in_list(ctx, b->events); + + if (print_headings) + printf("TEST %s.5.1: PASSED\n", tests); + + nua_handle_destroy(a_call->nh), a_call->nh = NULL; + nua_handle_destroy(b_call->nh), b_call->nh = NULL; + + + /* ---------------------------------------------------------------------- */ + /* + A C + |-------------------BYE--------------------->| + |<------------------200----------------------| + */ + + if (print_headings) + printf("TEST %s.5.2: terminate call between A and C\n", tests); + + BYE(a, a_c2, a_c2->nh, TAG_END()); + run_abc_until(ctx, -1, until_terminated, -1, NULL, -1, until_terminated); + + /* + Transitions of A: + READY --(T2)--> TERMINATING: nua_bye() + TERMINATING --(T3)--> TERMINATED: nua_r_bye, nua_i_state + */ + TEST_1(e = a_c2->events->head); TEST_E(e->data->e_event, nua_r_bye); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_terminated); /* TERMINATED */ + TEST_1(!e->next); + free_events_in_list(ctx, a_c2->events); + + /* Transitions of B: + READY -(T1)-> TERMINATED: nua_i_bye, nua_i_state + */ + TEST_1(e = c->events->head); TEST_E(e->data->e_event, nua_i_bye); + TEST(e->data->e_status, 200); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_terminated); /* TERMINATED */ + TEST_1(!e->next); + free_events_in_list(ctx, c->events); + + if (print_headings) + printf("TEST %s.5.2: PASSED\n", tests); + + nua_handle_destroy(c_call->nh), c_call->nh = NULL; + + nua_handle_destroy(a_c2->nh), a_c2->nh = NULL; + a_refer->next = NULL; free(a_c2); + + if (a_refer != a_call) { + nua_handle_destroy(a_refer->nh), a_refer->nh = NULL; + a_call->next = NULL; free(a_refer); + } + + if (b_refer != b_call) { + nua_handle_destroy(b_refer->nh), b_refer->nh = NULL; + b_call->next = NULL; free(b_refer); + } + + if (print_headings) + printf("TEST %s: PASSED\n", tests); + + su_home_deinit(tmphome); + + END(); +} + + +/* + X INVITE + | | + |-----------------INVITE-------------------->| + | | | + | | | + |<------------------200----------------------| + |-------NOTIFY------>| | + |--------BYE-------->| | + |-------------------ACK--------------------->| + +*/ +int notify_until_terminated(CONDITION_PARAMS) +{ + if (!(check_handle(ep, call, nh, SIP_500_INTERNAL_SERVER_ERROR))) + return 0; + + save_event_in_list(ctx, event, ep, call); + + if (event == nua_r_invite) { + sip_status_t *st = sip->sip_status; + sip_payload_t *pl; + struct call *r_call; + + if (!nua_handle_has_events(ep->call->nh)) + r_call = ep->call->next; + else + r_call = ep->call; + + assert(nua_handle_has_events(r_call->nh)); + + pl = sip_payload_format(NULL, "SIP/2.0 %u %s\r\n", + st->st_status, st->st_phrase); + + NOTIFY(ep, r_call, r_call->nh, + SIPTAG_CONTENT_TYPE_STR("message/sipfrag"), + SIPTAG_PAYLOAD(pl), + NUTAG_SUBSTATE(st->st_status >= 200 + ? nua_substate_terminated + : nua_substate_active), + TAG_END()); + + su_free(NULL, pl); + + if (st->st_status >= 200) + BYE(ep, ep->call, ep->call->nh, TAG_END()); + + return 0; + } + + if (call != ep->call) + return 0; + + switch (callstate(tags)) { + case nua_callstate_terminated: + return 1; + default: + return 0; + } +} + +int authenticate_refer(CONDITION_PARAMS); +int reject_refer_after_notified(CONDITION_PARAMS); + +int test_challenge_refer(struct context *ctx) +{ + BEGIN(); + + struct endpoint *a = &ctx->a, *b = &ctx->b, *c = &ctx->c; + struct call *a_call = a->call, *c_call = c->call; + struct event *e; + sip_t const *sip; + + sip_refer_to_t r0[1]; + + if (!ctx->proxy_tests) + return 0; + + if (print_headings) + printf("TEST NUA-9.0.1: challenge REFER\n"); + + nua_set_params(ctx->a.nua, NUTAG_APPL_METHOD("REFER"), TAG_END()); + run_a_until(ctx, nua_r_set_params, until_final_response); + + *sip_refer_to_init(r0)->r_url = *b->contact->m_url; + r0->r_url->url_headers = "subject=referred"; + r0->r_display = "B"; + + TEST_1(c_call->nh = nua_handle(c->nua, c_call, SIPTAG_TO(a->to), TAG_END())); + + REFER(c, c_call, c_call->nh, + SIPTAG_FROM(c->to), + SIPTAG_REFER_TO(r0), + TAG_END()); + + run_abc_until(ctx, -1, reject_refer_after_notified, -1, NULL, -1, authenticate_refer); + + /* + Events in A: + nua_i_refer + */ + TEST_1(e = a->events->head); TEST_E(e->data->e_event, nua_i_refer); + TEST(e->data->e_status, 100); + TEST_1(sip = sip_object(e->data->e_msg)); + TEST_1(sip->sip_refer_to); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_r_notify); + TEST(e->data->e_status, 200); + TEST_1(!e->next); + /* + Events in C after nua_refer(): + nua_r_refer + */ + TEST_1(e = c->events->head); TEST_E(e->data->e_event, nua_r_refer); + TEST(e->data->e_status, 100); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_r_refer); + TEST(e->data->e_status, 407); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_r_refer); + TEST(e->data->e_status, 100); + + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_notify); + TEST(e->data->e_status, 200); + + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_r_refer); + TEST(e->data->e_status, 480); + + TEST_1(!e->next); + + free_events_in_list(ctx, a->events); + free_events_in_list(ctx, c->events); + + nua_handle_destroy(a_call->nh), a_call->nh = NULL; + nua_handle_destroy(c_call->nh), c_call->nh = NULL; + + nua_set_params(ctx->a.nua, + NUTAG_APPL_METHOD(NULL), + NUTAG_APPL_METHOD("INVITE, REGISTER, PUBLISH, SUBSCRIBE"), + TAG_END()); + run_a_until(ctx, nua_r_set_params, until_final_response); + + if (print_headings) + printf("TEST NUA-9.0.1: PASSED\n"); + + END(); +} + +int reject_refer_after_notified(CONDITION_PARAMS) +{ + if (!(check_handle(ep, call, nh, SIP_500_INTERNAL_SERVER_ERROR))) + return 0; + + save_event_in_list(ctx, event, ep, call); + + if (event == nua_i_refer) { + } + + if (event == nua_r_notify) { + /* Respond to refer only after initial notify has been responded */ + struct eventlist *list; + struct event *e; + + if (call->events) + list = call->events; + else + list = ep->events; + + for (e = list->head; e; e = e->next) + if (e->data->e_event == nua_i_refer) + break; + + if (e) { + RESPOND(ep, call, nh, SIP_480_TEMPORARILY_UNAVAILABLE, + NUTAG_WITH(e->data->e_msg), + TAG_END()); + return 1; + } + return 0; + } + + return 0; +} + +int authenticate_refer(CONDITION_PARAMS) +{ + if (!(check_handle(ep, call, nh, SIP_500_INTERNAL_SERVER_ERROR))) + return 0; + + save_event_in_list(ctx, event, ep, call); + + if (status == 401 || status == 407) { + AUTHENTICATE(ep, call, nh, + NUTAG_AUTH("Digest:\"test-proxy\":charlie:secret"), + TAG_END()); + } + + return status == 480; +} Added: freeswitch/trunk/libs/sofia-sip/tests/test_register.c ============================================================================== --- (empty file) +++ freeswitch/trunk/libs/sofia-sip/tests/test_register.c Wed May 14 15:10:54 2008 @@ -0,0 +1,1043 @@ +/* + * This file is part of the Sofia-SIP package + * + * Copyright (C) 2005 Nokia Corporation. + * + * Contact: Pekka Pessi <pekka.pessi@nokia.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public License + * as published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + */ + +/**@CFILE test_register.c + * @brief Test registering, outbound, nat traversal. + * + * @author Pekka Pessi <Pekka.Pessi@nokia.com> + * @author Martti Mela <Martti Mela@nokia.com> + * + * @date Created: Wed Aug 17 12:12:12 EEST 2005 ppessi + */ + +#include "config.h" + +#include "test_nua.h" +#include <sofia-sip/su_tag_class.h> + +#if HAVE_FUNC +#elif HAVE_FUNCTION +#define __func__ __FUNCTION__ +#else +#define __func__ "test_register" +#endif + +/* ======================================================================== */ +/* Test REGISTER */ + +int test_clear_registrations(struct context *ctx); +int test_outbound_cases(struct context *ctx); +int test_register_a(struct context *ctx); +int test_register_b(struct context *ctx); +int test_register_c(struct context *ctx); +int test_register_refresh(struct context *ctx); + +int test_register_to_proxy(struct context *ctx) +{ + return + test_clear_registrations(ctx) || + test_outbound_cases(ctx) || + test_register_a(ctx) || + test_register_b(ctx) || + test_register_c(ctx) || + test_register_refresh(ctx); +} + +int test_clear_registrations(struct context *ctx) +{ + BEGIN(); + + struct endpoint *a = &ctx->a, *b = &ctx->b, *c = &ctx->c; + struct call *a_reg = a->reg, *b_reg = b->reg, *c_reg = c->reg; + + if (print_headings) + printf("TEST NUA-2.3.0.1: un-REGISTER a\n"); + + TEST_1(a_reg->nh = nua_handle(a->nua, a_reg, TAG_END())); + UNREGISTER(a, a_reg, a_reg->nh, SIPTAG_TO(a->to), + SIPTAG_CONTACT_STR("*"), + TAG_END()); + run_a_until(ctx, -1, until_final_response); + AUTHENTICATE(a, a_reg, a_reg->nh, + NUTAG_AUTH("Digest:\"test-proxy\":alice:secret"), TAG_END()); + run_a_until(ctx, -1, until_final_response); + nua_handle_destroy(a_reg->nh); + + if (print_headings) + printf("TEST NUA-2.3.0.1: PASSED\n"); + + if (print_headings) + printf("TEST NUA-2.3.0.2: un-REGISTER b\n"); + + TEST_1(b_reg->nh = nua_handle(b->nua, b_reg, TAG_END())); + UNREGISTER(b, b_reg, b_reg->nh, SIPTAG_TO(b->to), + SIPTAG_CONTACT_STR("*"), + TAG_END()); + run_b_until(ctx, -1, until_final_response); + AUTHENTICATE(b, b_reg, b_reg->nh, + NUTAG_AUTH("Digest:\"test-proxy\":bob:secret"), TAG_END()); + run_b_until(ctx, -1, until_final_response); + nua_handle_destroy(b_reg->nh); + + if (print_headings) + printf("TEST NUA-2.3.0.2: PASSED\n"); + + if (print_headings) + printf("TEST NUA-2.3.0.3: un-REGISTER c\n"); + + TEST_1(c_reg->nh = nua_handle(c->nua, c_reg, TAG_END())); + UNREGISTER(c, c_reg, c_reg->nh, + SIPTAG_FROM(c->to), SIPTAG_TO(c->to), + SIPTAG_CONTACT_STR("*"), + TAG_END()); + run_c_until(ctx, -1, until_final_response); + AUTHENTICATE(c, c_reg, c_reg->nh, + NUTAG_AUTH("Digest:\"test-proxy\":charlie:secret"), TAG_END()); + run_c_until(ctx, -1, until_final_response); + nua_handle_destroy(c_reg->nh); + + if (print_headings) + printf("TEST NUA-2.3.0.3: PASSED\n"); + + END(); +} + +int test_outbound_cases(struct context *ctx) +{ + BEGIN(); + +#if 0 + + struct endpoint *a = &ctx->a, *x; + struct call *a_reg = a->reg; + struct event *e; + sip_t const *sip; + sip_contact_t m[1]; + +/* REGISTER test + + A R + |------REGISTER----->| + |<-------401---------| + |------REGISTER----->| + |<-------200---------| + | | + +*/ + + if (print_headings) + printf("TEST NUA-2.3.1: REGISTER a\n"); + + test_proxy_domain_set_expiration(ctx->a.domain, 5, 5, 10); + + TEST_1(a_reg->nh = nua_handle(a->nua, a_reg, TAG_END())); + + sip_contact_init(m); + m->m_display = "Lissu"; + *m->m_url = *a->contact->m_url; + m->m_url->url_user = "a"; + m->m_url->url_params = "transport=udp"; + + REGISTER(a, a_reg, a_reg->nh, SIPTAG_TO(a->to), + NUTAG_OUTBOUND("use-rport no-options-keepalive"), + SIPTAG_CONTACT(m), + TAG_END()); + run_a_until(ctx, -1, save_until_final_response); + + TEST_1(e = a->events->head); + TEST_E(e->data->e_event, nua_r_register); + TEST(e->data->e_status, 401); + TEST_1(sip = sip_object(e->data->e_msg)); + TEST(sip->sip_status->st_status, 401); + TEST_1(!sip->sip_contact); + TEST_1(!e->next); + free_events_in_list(ctx, a->events); + + AUTHENTICATE(a, a_reg, a_reg->nh, + NUTAG_AUTH("Digest:\"test-proxy\":alice:secret"), TAG_END()); + run_a_until(ctx, -1, save_until_final_response); + + TEST_1(e = a->events->head); + TEST_E(e->data->e_event, nua_r_register); + TEST(e->data->e_status, 200); + TEST_1(sip = sip_object(e->data->e_msg)); + TEST_1(sip->sip_contact); + TEST_S(sip->sip_contact->m_display, "Lissu"); + TEST_S(sip->sip_contact->m_url->url_user, "a"); + TEST_1(strstr(sip->sip_contact->m_url->url_params, "transport=udp")); + + if (ctx->nat) { + TEST_1(e = a->specials->head); + } + + test_proxy_domain_set_expiration(ctx->a.domain, 600, 3600, 36000); + + if (print_headings) + printf("TEST NUA-2.3.1: PASSED\n"); + + if (print_headings) + printf("TEST NUA-2.3.4: refresh REGISTER\n"); + + if (!ctx->p) { + free_events_in_list(ctx, a->events); + return 0; + } + + /* Wait for A to refresh its registrations */ + + /* + * Avoid race condition: if X has already refreshed registration + * with expiration time of 3600 seconds, do not wait for new refresh + */ + a->next_condition = save_until_final_response; + + for (x = a; x; x = NULL) { + for (e = x->events->head; e; e = e->next) { + if (e->data->e_event == nua_r_register && + e->data->e_status == 200 && + (sip = sip_object(e->data->e_msg)) && + sip->sip_contact && + sip->sip_contact->m_expires && + strcmp(sip->sip_contact->m_expires, "3600") == 0) { + x->next_condition = NULL; + break; + } + } + } + + run_a_until(ctx, -1, a->next_condition); + + for (e = a->events->head; e; e = e->next) { + TEST_E(e->data->e_event, nua_r_register); + TEST(e->data->e_status, 200); + TEST_1(sip = sip_object(e->data->e_msg)); + TEST_1(sip->sip_contact); + if (!e->next) + break; + } + TEST_1(e); + TEST_1(sip = sip_object(e->data->e_msg)); + TEST_S(sip->sip_contact->m_expires, "3600"); + TEST_1(!e->next); + free_events_in_list(ctx, a->events); + + if (print_headings) + printf("TEST NUA-2.3.4: PASSED\n"); + + TEST_1(0); + +#endif + + END(); +} + +int test_register_a(struct context *ctx) +{ + BEGIN(); + + struct endpoint *a = &ctx->a; + struct call *a_reg = a->reg; + struct event *e; + sip_t const *sip; + sip_cseq_t cseq[1]; + +/* REGISTER test + + A R + |------REGISTER----->| + |<-------401---------| + |------REGISTER----->| + |<-------200---------| + | | + +*/ + + if (print_headings) + printf("TEST NUA-2.3.1: REGISTER a\n"); + + test_proxy_domain_set_expiration(ctx->a.domain, 5, 5, 10); + + TEST_1(a_reg->nh = nua_handle(a->nua, a_reg, TAG_END())); + + sip_cseq_init(cseq)->cs_seq = 12; + cseq->cs_method = sip_method_register; + cseq->cs_method_name = sip_method_name_register; + + REGISTER(a, a_reg, a_reg->nh, SIPTAG_TO(a->to), + NUTAG_OUTBOUND("natify options-keepalive validate"), + NUTAG_KEEPALIVE(1000), + NUTAG_M_DISPLAY("A&A"), + NUTAG_M_USERNAME("a"), + NUTAG_M_PARAMS("foo=bar"), + NUTAG_M_FEATURES("q=0.9"), + SIPTAG_CSEQ(cseq), + TAG_END()); + run_a_until(ctx, -1, save_until_final_response); + + TEST_1(e = a->events->head); + TEST_1(sip = sip_object(e->data->e_msg)); + if (ctx->nat && e->data->e_status == 100) { + TEST_E(e->data->e_event, nua_r_register); + TEST(e->data->e_status, 100); + TEST(sip->sip_status->st_status, 406); + /* Check that CSeq included in tags is actually used in the request */ + TEST(sip->sip_cseq->cs_seq, 13); + TEST_1(!sip->sip_contact); + TEST_1(e = e->next); + TEST_1(sip = sip_object(e->data->e_msg)); + TEST(sip->sip_cseq->cs_seq, 14); + } + else { + /* Check that CSeq included in tags is actually used in the request */ + TEST(sip->sip_cseq->cs_seq, 13); + } + TEST_E(e->data->e_event, nua_r_register); + TEST(e->data->e_status, 401); + TEST(sip->sip_status->st_status, 401); + TEST_1(!sip->sip_contact); + TEST_1(!e->next); + free_events_in_list(ctx, a->events); + + AUTHENTICATE(a, a_reg, a_reg->nh, + NUTAG_AUTH("Digest:\"test-proxy\":alice:secret"), TAG_END()); + run_a_until(ctx, -1, save_until_final_response); + + TEST_1(e = a->events->head); + TEST_E(e->data->e_event, nua_r_register); + TEST(e->data->e_status, 200); + TEST_1(sip = sip_object(e->data->e_msg)); + TEST_1(sip->sip_contact); + { char const *expect_m_display = "\"A&A\""; + /* VC does not dig \" with TEST_S() */ + TEST_S(sip->sip_contact->m_display, expect_m_display); } + TEST_S(sip->sip_contact->m_url->url_user, "a"); + TEST_1(strstr(sip->sip_contact->m_url->url_params, "foo=bar")); + TEST_S(sip->sip_contact->m_q, "0.9"); + + if (ctx->nat) { + TEST_1(e = a->specials->head); + } + + test_proxy_domain_set_expiration(ctx->a.domain, 600, 3600, 36000); + + if (print_headings) + printf("TEST NUA-2.3.1: PASSED\n"); + + END(); +} + +int test_register_b(struct context *ctx) +{ + BEGIN(); + + struct endpoint *b = &ctx->b; + struct call *b_reg = b->reg; + struct event *e; + sip_t const *sip; + + if (print_headings) + printf("TEST NUA-2.3.2: REGISTER b\n"); + + test_proxy_domain_set_expiration(ctx->b.domain, 5, 5, 10); + + TEST_1(b_reg->nh = nua_handle(b->nua, b_reg, TAG_END())); + + /* Test application-supplied contact */ + { + sip_contact_t m[1]; + sip_contact_init(m)->m_url[0] = b->contact->m_url[0]; + + m->m_display = "B"; + m->m_url->url_user = "b"; + + /* Include "tcp" transport parameter in Contact */ + if (ctx->p) + m->m_url->url_params = "transport=tcp"; + + REGISTER(b, b_reg, b_reg->nh, SIPTAG_TO(b->to), + SIPTAG_CONTACT(m), + /* Do not include credentials unless challenged */ + NUTAG_AUTH_CACHE(nua_auth_cache_challenged), + TAG_END()); + } + run_ab_until(ctx, -1, save_events, -1, save_until_final_response); + + TEST_1(e = b->events->head); + TEST_E(e->data->e_event, nua_r_register); + TEST_1(sip = sip_object(e->data->e_msg)); + TEST(e->data->e_status, 401); + TEST(sip->sip_status->st_status, 401); + TEST_1(!sip->sip_contact); + TEST_1(!e->next); + free_events_in_list(ctx, b->events); + + AUTHENTICATE(b, b_reg, b_reg->nh, + NUTAG_AUTH("Digest:\"test-proxy\":bob:secret"), TAG_END()); + run_ab_until(ctx, -1, save_events, -1, save_until_final_response); + + TEST_1(e = b->events->head); + TEST_E(e->data->e_event, nua_r_register); + TEST(e->data->e_status, 200); + TEST_1(sip = sip_object(e->data->e_msg)); + TEST_1(sip->sip_contact); + TEST_S(sip->sip_contact->m_display, "B"); + TEST_S(sip->sip_contact->m_url->url_user, "b"); + free_events_in_list(ctx, b->events); + + test_proxy_domain_set_expiration(ctx->b.domain, 600, 3600, 36000); + + if (print_headings) + printf("TEST NUA-2.3.2: PASSED\n"); + + END(); +} + +int test_register_c(struct context *ctx) +{ + BEGIN(); + + struct endpoint *c = &ctx->c; + struct call *c_reg = c->reg; + struct event *e; + sip_t const *sip; + + if (print_headings) + printf("TEST NUA-2.3.3: REGISTER c\n"); + + test_proxy_domain_set_expiration(ctx->c.domain, 600, 3600, 36000); + test_proxy_domain_set_authorize(ctx->c.domain, "test-proxy-0"); + + TEST_1(c_reg->nh = nua_handle(c->nua, c_reg, TAG_END())); + + REGISTER(c, c_reg, c_reg->nh, SIPTAG_TO(c->to), + SIPTAG_FROM(c->to), + NUTAG_OUTBOUND(NULL), + NUTAG_M_DISPLAY("C"), + NUTAG_M_USERNAME("c"), + NUTAG_M_PARAMS("c=1"), + NUTAG_M_FEATURES("q=0.987;expires=5"), + NUTAG_CALLEE_CAPS(1), + SIPTAG_EXPIRES_STR("5"), /* Test 423 negotiation */ + TAG_END()); + run_abc_until(ctx, -1, save_events, -1, save_events, + -1, save_until_final_response); + + TEST_1(e = c->events->head); + TEST_E(e->data->e_event, nua_r_register); + TEST_1(sip = sip_object(e->data->e_msg)); + TEST(e->data->e_status, 401); + TEST(sip->sip_status->st_status, 401); + TEST_1(!sip->sip_contact); + TEST_1(!e->next); + free_events_in_list(ctx, c->events); + + AUTHENTICATE(c, c_reg, c_reg->nh, + NUTAG_AUTH("Digest:\"test-proxy-0\":charlie:secret"), TAG_END()); + run_abc_until(ctx, -1, save_events, -1, save_events, + -1, save_until_final_response); + + TEST_1(e = c->events->head); + TEST_E(e->data->e_event, nua_r_register); + TEST(e->data->e_status, 100); + TEST_1(sip = sip_object(e->data->e_msg)); + TEST(sip->sip_status->st_status, 423); + TEST_1(e = e->next); + if (e->data->e_status == 100 && e->data->e_event == nua_r_register) { + TEST_1(sip = sip_object(e->data->e_msg)); + TEST(sip->sip_status->st_status, 401); + TEST_1(e = e->next); + } + TEST(e->data->e_status, 200); TEST_E(e->data->e_event, nua_r_register); + TEST_1(sip = sip_object(e->data->e_msg)); + TEST_1(sip->sip_contact); + TEST_S(sip->sip_contact->m_display, "C"); + TEST_S(sip->sip_contact->m_url->url_user, "c"); + TEST_1(strstr(sip->sip_contact->m_url->url_params, "c=1")); + TEST_S(sip->sip_contact->m_q, "0.987"); + TEST_1(msg_header_find_param(sip->sip_contact->m_common, "methods=")); + TEST_1(!e->next); + free_events_in_list(ctx, c->events); + + if (print_headings) + printf("TEST NUA-2.3.3: PASSED\n"); + + END(); +} + +int test_register_refresh(struct context *ctx) +{ + BEGIN(); + + struct endpoint *a = &ctx->a, *b = &ctx->b, *x; + struct event *e; + sip_t const *sip; + int seen_401; + + if (print_headings) + printf("TEST NUA-2.3.4: refresh REGISTER\n"); + + if (!ctx->p) { + free_events_in_list(ctx, a->events); + free_events_in_list(ctx, b->events); + return 0; + } + + /* Wait for A and B to refresh their registrations */ + + /* + * Avoid race condition: if X has already refreshed registration + * with expiration time of 3600 seconds, do not wait for new refresh + */ + a->next_condition = save_until_final_response; + b->next_condition = save_until_final_response; + + for (x = a; x; x = x == a ? b : NULL) { + for (e = x->events->head; e; e = e->next) { + if (e->data->e_event == nua_r_register && + e->data->e_status == 200 && + (sip = sip_object(e->data->e_msg)) && + sip->sip_contact && + sip->sip_contact->m_expires && + strcmp(sip->sip_contact->m_expires, "3600") == 0) { + x->next_condition = NULL; + break; + } + } + } + + run_ab_until(ctx, -1, a->next_condition, -1, b->next_condition); + + for (e = a->events->head; e; e = e->next) { + TEST_E(e->data->e_event, nua_r_register); + TEST(e->data->e_status, 200); + TEST_1(sip = sip_object(e->data->e_msg)); + TEST_1(sip->sip_contact); + if (!e->next) + break; + } + TEST_1(e); + TEST_1(sip = sip_object(e->data->e_msg)); + TEST_S(sip->sip_contact->m_expires, "3600"); + TEST_1(!e->next); + free_events_in_list(ctx, a->events); + + seen_401 = 0; + + for (e = b->events->head; e; e = e->next) { + TEST_E(e->data->e_event, nua_r_register); + TEST_1(sip = sip_object(e->data->e_msg)); + + if (e->data->e_status == 200) { + TEST(e->data->e_status, 200); + TEST_1(seen_401); + TEST_1(sip->sip_contact); + } + else if (sip->sip_status && sip->sip_status->st_status == 401) { + seen_401 = 1; + } + + if (!e->next) + break; + } + TEST_1(e); + TEST_S(sip->sip_contact->m_expires, "3600"); + TEST_1(!e->next); + free_events_in_list(ctx, b->events); + + if (print_headings) + printf("TEST NUA-2.3.4: PASSED\n"); + + if (!ctx->p) + return 0; + + if (print_headings) + printf("TEST NUA-2.3.5: re-REGISTER when TCP connection is closed\n"); + + test_proxy_close_tports(ctx->p); + + run_b_until(ctx, -1, save_until_final_response); + + TEST_1(e = b->events->head); + TEST_E(e->data->e_event, nua_r_register); + if (e->data->e_status == 100) + TEST_1(e = e->next); + TEST_1(sip = sip_object(e->data->e_msg)); + TEST_1(sip->sip_contact); + TEST_S(sip->sip_contact->m_expires, "3600"); + TEST_1(!e->next); + + free_events_in_list(ctx, b->events); + + if (print_headings) + printf("TEST NUA-2.3.5: PASSED\n"); + + END(); +} + +int registrar_299(CONDITION_PARAMS) +{ + msg_t *request = nua_current_request(nua); + + save_event_in_list(ctx, event, ep, ep->call); + + if (event == nua_i_register) { + RESPOND(ep, call, nh, 299, "YES", NUTAG_WITH(request), TAG_END()); + return 1; + } + + return 0; +} + +int test_register_to_c(struct context *ctx) +{ + BEGIN(); + + struct endpoint *b = &ctx->b, *c = &ctx->c; + struct call *b_call = b->call, *c_call = c->call; + struct event *e; + sip_t const *sip; + + if (print_headings) + printf("TEST NUA-2.6.1: REGISTER b to c\n"); + + nua_set_params(ctx->c.nua, + NUTAG_ALLOW("REGISTER"), + TAG_END()); + run_c_until(ctx, nua_r_set_params, until_final_response); + + TEST_1(b_call->nh = nua_handle(b->nua, b_call, TAG_END())); + + REGISTER(b, b_call, b_call->nh, + NUTAG_REGISTRAR((url_string_t *)c->contact->m_url), + SIPTAG_TO(b->to), + NUTAG_OUTBOUND(NULL), + SIPTAG_CONTACT_STR(NULL), + TAG_END()); + run_bc_until(ctx, -1, save_until_final_response, -1, registrar_299); + + TEST_1(e = b->events->head); + TEST_E(e->data->e_event, nua_r_register); + TEST(e->data->e_status, 299); + TEST_1(sip = sip_object(e->data->e_msg)); + TEST_1(!sip->sip_contact); + + free_events_in_list(ctx, b->events); + nua_handle_destroy(b_call->nh), b_call->nh = NULL; + + TEST_1(e = c->events->head); + TEST_E(e->data->e_event, nua_i_register); + TEST(e->data->e_status, 100); + TEST_1(sip = sip_object(e->data->e_msg)); + TEST_1(!sip->sip_contact); + + free_events_in_list(ctx, c->events); + nua_handle_destroy(c_call->nh), c_call->nh = NULL; + + if (print_headings) + printf("TEST NUA-2.6.1: PASSED\n"); + + END(); +} + + +int test_register(struct context *ctx) +{ + if (test_register_to_c(ctx)) + return 1; + + if (ctx->proxy_tests) + if (test_register_to_proxy(ctx)) return 1; + + return 0; +} + + +int test_connectivity(struct context *ctx) +{ + if (!ctx->proxy_tests) + return 0; /* No proxy */ + + BEGIN(); + + struct endpoint *a = &ctx->a, *b = &ctx->b, *c = &ctx->c; + struct call *a_call = a->call, *b_call = b->call, *c_call = c->call; + struct event *e; + sip_t const *sip; + + /* Connectivity test using OPTIONS */ + + if (print_headings) + printf("TEST NUA-2.4.1: OPTIONS from A to B\n"); + + TEST_1(a_call->nh = nua_handle(a->nua, a_call, SIPTAG_TO(b->to), TAG_END())); + + OPTIONS(a, a_call, a_call->nh, + TAG_IF(!ctx->proxy_tests, NUTAG_URL(b->contact->m_url)), + NUTAG_ALLOW("OPTIONS"), + TAG_END()); + + run_ab_until(ctx, -1, save_until_final_response, -1, save_until_received); + + /* Client events: nua_options(), nua_r_options */ + TEST_1(e = a->events->head); TEST_E(e->data->e_event, nua_r_options); + TEST(e->data->e_status, 200); + TEST_1(sip = sip_object(e->data->e_msg)); + TEST_1(sip->sip_allow); TEST_1(sip->sip_accept); TEST_1(sip->sip_supported); + /* TEST_1(sip->sip_content_type); */ + /* TEST_1(sip->sip_payload); */ + TEST_1(!e->next); + + free_events_in_list(ctx, a->events); + nua_handle_destroy(a_call->nh), a_call->nh = NULL; + + /* Server events: nua_i_options */ + TEST_1(e = b->events->head); TEST_E(e->data->e_event, nua_i_options); + TEST(e->data->e_status, 200); + TEST_1(!e->next); + + free_events_in_list(ctx, b->events); + nua_handle_destroy(b_call->nh), b_call->nh = NULL; + + if (print_headings) + printf("TEST NUA-2.4.1: PASSED\n"); + + if (print_headings) + printf("TEST NUA-2.4.2: OPTIONS from B to C\n"); + + TEST_1(b_call->nh = nua_handle(b->nua, b_call, SIPTAG_TO(c->to), TAG_END())); + + OPTIONS(b, b_call, b_call->nh, + TAG_IF(!ctx->proxy_tests, NUTAG_URL(c->contact->m_url)), + TAG_END()); + + run_abc_until(ctx, -1, NULL, + -1, save_until_final_response, + -1, save_until_received); + + /* Client events: nua_options(), nua_r_options */ + TEST_1(e = b->events->head); TEST_E(e->data->e_event, nua_r_options); + TEST(e->data->e_status, 200); + TEST_1(sip = sip_object(e->data->e_msg)); + TEST_1(sip->sip_allow); TEST_1(sip->sip_accept); TEST_1(sip->sip_supported); + /* TEST_1(sip->sip_content_type); */ + /* TEST_1(sip->sip_payload); */ + TEST_1(!e->next); + + free_events_in_list(ctx, b->events); + nua_handle_destroy(b_call->nh), b_call->nh = NULL; + + /* Server events: nua_i_options */ + TEST_1(e = c->events->head); TEST_E(e->data->e_event, nua_i_options); + TEST(e->data->e_status, 200); + TEST_1(!e->next); + + free_events_in_list(ctx, c->events); + nua_handle_destroy(c_call->nh), c_call->nh = NULL; + + if (print_headings) + printf("TEST NUA-2.4.2: PASSED\n"); + + if (print_headings) + printf("TEST NUA-2.4.3: OPTIONS from C to A\n"); + + TEST_1(c_call->nh = nua_handle(c->nua, c_call, SIPTAG_TO(a->to), TAG_END())); + + OPTIONS(c, c_call, c_call->nh, + SIPTAG_FROM(c->to), + TAG_IF(!ctx->proxy_tests, NUTAG_URL(a->contact->m_url)), + TAG_END()); + + if (ctx->proxy_tests) { + run_abc_until(ctx, -1, NULL, -1, NULL, -1, save_until_final_response); + + /* Client events: nua_options(), nua_r_options */ + TEST_1(e = c->events->head); TEST_E(e->data->e_event, nua_r_options); + TEST(e->data->e_status, 407); + TEST_1(!e->next); + + free_events_in_list(ctx, c->events); + + /* Sneakily change the realm */ + + TEST(test_proxy_domain_set_authorize(ctx->c.domain, "test-proxy"), 0); + + AUTHENTICATE(c, c_call, c_call->nh, + NUTAG_AUTH("Digest:\"test-proxy-0\":charlie:secret"), + TAG_END()); + + run_abc_until(ctx, -1, NULL, -1, NULL, -1, save_until_final_response); + + /* Client events: nua_options(), nua_r_options */ + TEST_1(e = c->events->head); TEST_E(e->data->e_event, nua_r_options); + TEST(e->data->e_status, 407); + TEST_1(!e->next); + + free_events_in_list(ctx, c->events); + + AUTHENTICATE(c, c_call, c_call->nh, + NUTAG_AUTH("Digest:\"test-proxy\":charlie:secret"), + TAG_END()); + } + + run_abc_until(ctx, -1, save_until_received, + -1, NULL, + -1, save_until_final_response); + + /* Client events: nua_options(), nua_r_options */ + TEST_1(e = c->events->head); TEST_E(e->data->e_event, nua_r_options); + TEST(e->data->e_status, 200); + TEST_1(sip = sip_object(e->data->e_msg)); + TEST_1(sip->sip_allow); TEST_1(sip->sip_accept); TEST_1(sip->sip_supported); + /* TEST_1(sip->sip_content_type); */ + /* TEST_1(sip->sip_payload); */ + TEST_1(!e->next); + + free_events_in_list(ctx, c->events); + nua_handle_destroy(c_call->nh), c_call->nh = NULL; + + /* Server events: nua_i_options */ + TEST_1(e = a->events->head); TEST_E(e->data->e_event, nua_i_options); + TEST(e->data->e_status, 200); + TEST_1(!e->next); + + free_events_in_list(ctx, a->events); + nua_handle_destroy(a_call->nh), a_call->nh = NULL; + + if (print_headings) + printf("TEST NUA-2.4.3: PASSED\n"); + + END(); +} + +int test_nat_timeout(struct context *ctx) +{ + if (!ctx->proxy_tests || !ctx->nat) + return 0; /* No proxy */ + + BEGIN(); + + struct endpoint *a = &ctx->a, *b = &ctx->b; + struct call *a_call = a->call, *b_call = b->call; + struct event *e; + sip_t const *sip; + + /* Test what happens when NAT bindings go away */ + + if (print_headings) + printf("TEST NUA-2.5.1: NAT binding change\n"); + + free_events_in_list(ctx, a->specials); + + test_nat_flush(ctx->nat); /* Break our connections */ + + /* Run until we get final response to REGISTER */ + run_a_until(ctx, -1, save_until_final_response); + + TEST_1(e = a->specials->head); + TEST_E(e->data->e_event, nua_i_outbound); + TEST(e->data->e_status, 102); + TEST_S(e->data->e_phrase, "NAT binding changed"); + TEST_1(!e->next); + + free_events_in_list(ctx, a->specials); + + TEST_1(e = a->events->head); + TEST_E(e->data->e_event, nua_r_register); + TEST(e->data->e_status, 200); + TEST_1(!e->next); + + free_events_in_list(ctx, a->events); + + if (print_headings) + printf("TEST NUA-2.5.1: PASSED\n"); + + if (print_headings) + printf("TEST NUA-2.5.2: OPTIONS from B to A\n"); + + TEST_1(b_call->nh = nua_handle(b->nua, b_call, SIPTAG_TO(a->to), TAG_END())); + + OPTIONS(b, b_call, b_call->nh, TAG_END()); + + run_ab_until(ctx, -1, save_until_received, + -1, save_until_final_response); + + /* Client events: nua_options(), nua_r_options */ + TEST_1(e = b->events->head); TEST_E(e->data->e_event, nua_r_options); + TEST(e->data->e_status, 200); + TEST_1(sip = sip_object(e->data->e_msg)); + TEST_1(sip->sip_allow); TEST_1(sip->sip_accept); TEST_1(sip->sip_supported); + /* TEST_1(sip->sip_content_type); */ + /* TEST_1(sip->sip_payload); */ + TEST_1(!e->next); + + free_events_in_list(ctx, b->events); + nua_handle_destroy(b_call->nh), b_call->nh = NULL; + + /* Server events: nua_i_options */ + TEST_1(e = a->events->head); TEST_E(e->data->e_event, nua_i_options); + TEST(e->data->e_status, 200); + TEST_1(!e->next); + + free_events_in_list(ctx, a->events); + nua_handle_destroy(a_call->nh), a_call->nh = NULL; + + if (print_headings) + printf("TEST NUA-2.5.2: PASSED\n"); + + END(); +} + +int test_unregister(struct context *ctx) +{ + if (!ctx->proxy_tests) + return 0; /* No proxy */ + + BEGIN(); + + struct endpoint *a = &ctx->a, *b = &ctx->b, *c = &ctx->c; + struct event *e; + sip_t const *sip; + +/* un-REGISTER test + + A B + |----un-REGISTER---->| + |<-------200---------| + | | + +*/ + if (print_headings) + printf("TEST NUA-13.1: un-REGISTER a\n"); + + if (a->reg->nh) { + free_events_in_list(ctx, a->events); + UNREGISTER(a, NULL, a->reg->nh, TAG_END()); + run_a_until(ctx, -1, save_until_final_response); + TEST_1(e = a->events->head); + TEST_E(e->data->e_event, nua_r_unregister); + if (e->data->e_status == 100) { + TEST_1(e = e->next); + TEST_E(e->data->e_event, nua_r_unregister); + } + TEST(e->data->e_status, 200); + TEST_1(sip = sip_object(e->data->e_msg)); + TEST_1(!sip->sip_contact); + TEST_1(!e->next); + free_events_in_list(ctx, a->events); + nua_handle_destroy(a->reg->nh), a->reg->nh = NULL; + } + + if (print_headings) + printf("TEST NUA-13.1: PASSED\n"); + + if (print_headings) + printf("TEST NUA-13.2: un-REGISTER b\n"); + + if (b->reg->nh) { + free_events_in_list(ctx, b->events); + UNREGISTER(b, NULL, b->reg->nh, TAG_END()); + run_b_until(ctx, -1, save_until_final_response); + TEST_1(e = b->events->head); + TEST_E(e->data->e_event, nua_r_unregister); + if (e->data->e_status == 100) { + TEST_1(e = e->next); + TEST_E(e->data->e_event, nua_r_unregister); + } + TEST(e->data->e_status, 200); + TEST_1(sip = sip_object(e->data->e_msg)); + TEST_1(!sip->sip_contact); + TEST_1(!e->next); + free_events_in_list(ctx, b->events); + nua_handle_destroy(b->reg->nh), b->reg->nh = NULL; + } + if (print_headings) + printf("TEST NUA-13.2: PASSED\n"); + + if (print_headings) + printf("TEST NUA-13.3: un-REGISTER c\n"); + + /* Unregister using another handle */ + free_events_in_list(ctx, c->events); + TEST_1(c->call->nh = nua_handle(c->nua, c->call, TAG_END())); + UNREGISTER(c, c->call, c->call->nh, SIPTAG_TO(c->to), SIPTAG_FROM(c->to), + NUTAG_M_DISPLAY("C"), + NUTAG_M_USERNAME("c"), + NUTAG_M_PARAMS("c=1"), + TAG_END()); + run_c_until(ctx, -1, save_until_final_response); + + TEST_1(e = c->events->head); + TEST_E(e->data->e_event, nua_r_unregister); + TEST_1(sip = sip_object(e->data->e_msg)); + TEST(e->data->e_status, 401); + TEST(sip->sip_status->st_status, 401); + TEST_1(!sip->sip_contact); + TEST_1(!e->next); + free_events_in_list(ctx, c->events); + + AUTHENTICATE(c, c->call, c->call->nh, + NUTAG_AUTH("Digest:\"test-proxy\":charlie:secret"), TAG_END()); + run_c_until(ctx, -1, save_until_final_response); + + TEST_1(e = c->events->head); + TEST_E(e->data->e_event, nua_r_unregister); + TEST(e->data->e_status, 200); + TEST_1(sip = sip_object(e->data->e_msg)); + TEST_1(!sip->sip_contact); + TEST_1(!e->next); + free_events_in_list(ctx, c->events); + nua_handle_destroy(c->call->nh), c->call->nh = NULL; + + if (c->reg->nh) { + UNREGISTER(c, NULL, c->reg->nh, TAG_END()); + run_c_until(ctx, -1, save_until_final_response); + TEST_1(e = c->events->head); + TEST_E(e->data->e_event, nua_r_unregister); + if (e->data->e_status == 100) { + TEST_1(e = e->next); + TEST_E(e->data->e_event, nua_r_unregister); + } + if (e->data->e_status == 401) { + TEST_1(!e->next); + free_events_in_list(ctx, c->events); + AUTHENTICATE(c, NULL, c->reg->nh, + NUTAG_AUTH("Digest:\"test-proxy\":charlie:secret"), TAG_END()); + run_c_until(ctx, -1, save_until_final_response); + TEST_1(e = c->events->head); + TEST_E(e->data->e_event, nua_r_unregister); + } + TEST(e->data->e_status, 200); + TEST_1(sip = sip_object(e->data->e_msg)); + TEST_1(sip->sip_from->a_url->url_user); + TEST_1(!sip->sip_contact); + TEST_1(!e->next); + free_events_in_list(ctx, c->events); + nua_handle_destroy(c->reg->nh), c->reg->nh = NULL; + } + + if (print_headings) + printf("TEST NUA-13.3: PASSED\n"); + + END(); +} Added: freeswitch/trunk/libs/sofia-sip/tests/test_session_timer.c ============================================================================== --- (empty file) +++ freeswitch/trunk/libs/sofia-sip/tests/test_session_timer.c Wed May 14 15:10:54 2008 @@ -0,0 +1,434 @@ +/* + * This file is part of the Sofia-SIP package + * + * Copyright (C) 2005 Nokia Corporation. + * + * Contact: Pekka Pessi <pekka.pessi@nokia.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public License + * as published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + */ + +/**@CFILE test_session_timer.c + * @brief NUA-8 tests: Session timer, UPDATE + * + * @author Pekka Pessi <Pekka.Pessi@nokia.com> + * @author Martti Mela <Martti Mela@nokia.com> + * + * @date Created: Wed Aug 17 12:12:12 EEST 2005 ppessi + */ + +#include "config.h" + +#include "test_nua.h" +#include <sofia-sip/su_tag_class.h> + +#if HAVE_FUNC +#elif HAVE_FUNCTION +#define __func__ __FUNCTION__ +#else +#define __func__ "test_session_timer" +#endif + +/* ======================================================================== */ + +static size_t remove_update(void *a, void *message, size_t len); + +int test_session_timer(struct context *ctx) +{ + BEGIN(); + + struct endpoint *a = &ctx->a, *b = &ctx->b; + struct call *a_call = a->call, *b_call = b->call; + struct event *e; + sip_t const *sip; + +/* Session timer test: + + A P B + |-------INVITE------>| + |<----100 Trying-----| + | | + |<----180 Ringing----| + | | + |<------200 OK-------| + |------------------->| + | | + | | + |--INVITE->| | + |<--422----| | + |---ACK--->| | + +*/ + + if (print_headings) + printf("TEST NUA-8.1.1: Session timers\n"); + + a_call->sdp = "m=audio 5008 RTP/AVP 8"; + b_call->sdp = "m=audio 5010 RTP/AVP 0 8"; + + /* We negotiate session timer of 6 second */ + /* Disable session timer from proxy */ + test_proxy_set_session_timer(ctx->p, 0, 0); + + nua_set_params(ctx->b.nua, + NUTAG_SESSION_REFRESHER(nua_any_refresher), + NUTAG_MIN_SE(1), + NUTAG_SESSION_TIMER(6), + NTATAG_SIP_T1X64(8000), + TAG_END()); + + run_b_until(ctx, nua_r_set_params, until_final_response); + + TEST_1(a_call->nh = nua_handle(a->nua, a_call, SIPTAG_TO(b->to), TAG_END())); + + INVITE(a, a_call, a_call->nh, + TAG_IF(!ctx->proxy_tests, NUTAG_URL(b->contact->m_url)), + SOATAG_USER_SDP_STR(a_call->sdp), + SIPTAG_SUPPORTED_STR("100rel"), + TAG_END()); + + run_ab_until(ctx, -1, until_ready, -1, accept_call); + + /* Client transitions: + INIT -(C1)-> CALLING: nua_i_state + CALLING -(C2)-> PROCEEDING: nua_r_invite, nua_i_state + PROCEEDING -(C3+C4)-> READY: nua_r_invite, nua_i_state + */ + TEST_1(e = a->events->head); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_calling); /* CALLING */ + TEST_1(is_offer_sent(e->data->e_tags)); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_r_invite); + TEST(e->data->e_status, 180); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_proceeding); /* PROCEEDING */ + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_r_invite); + TEST(e->data->e_status, 200); + TEST_1(sip = sip_object(e->data->e_msg)); + TEST_1(sip->sip_session_expires); + TEST_S(sip->sip_session_expires->x_refresher, "uas"); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_ready); /* READY */ + TEST_1(is_answer_recv(e->data->e_tags)); + TEST_1(!e->next); + free_events_in_list(ctx, a->events); + + /* + Server transitions: + INIT -(S1)-> RECEIVED: nua_i_invite, nua_i_state + RECEIVED -(S2a)-> EARLY: nua_respond(), nua_i_state + EARLY -(S3b)-> COMPLETED: nua_respond(), nua_i_state + COMPLETED -(S4)-> READY: nua_i_ack, nua_i_state + */ + TEST_1(e = b->events->head); TEST_E(e->data->e_event, nua_i_invite); + TEST(e->data->e_status, 100); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_received); /* RECEIVED */ + TEST_1(is_offer_recv(e->data->e_tags)); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_early); /* EARLY */ + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_completed); /* COMPLETED */ + TEST_1(is_answer_sent(e->data->e_tags)); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_ack); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_ready); /* READY */ + TEST_1(!e->next); + free_events_in_list(ctx, b->events); + + if (print_headings) + printf("TEST NUA-8.1.1: PASSED\n"); + + if (ctx->expensive) { + nua_set_hparams(a_call->nh, + NUTAG_SUPPORTED("timer"), + NUTAG_MIN_SE(1), + NUTAG_SESSION_TIMER(5), + TAG_END()); + run_a_until(ctx, nua_r_set_params, until_final_response); + + if (print_headings) + printf("TEST NUA-8.1.2: Wait for refresh using INVITE\n"); + + run_ab_until(ctx, -1, until_ready, -1, until_ready); + + free_events_in_list(ctx, a->events); + free_events_in_list(ctx, b->events); + + if (print_headings) + printf("TEST NUA-8.1.2: PASSED\n"); + + nua_set_hparams(b_call->nh, + NUTAG_UPDATE_REFRESH(1), + TAG_END()); + run_b_until(ctx, nua_r_set_params, until_final_response); + + if (print_headings) + printf("TEST NUA-8.1.3: Wait for refresh using UPDATE\n"); + + run_ab_until(ctx, -1, until_ready, -1, until_ready); + + TEST_1(e = a->events->head); TEST_E(e->data->e_event, nua_i_update); + + free_events_in_list(ctx, a->events); + free_events_in_list(ctx, b->events); + + if (print_headings) + printf("TEST NUA-8.1.3: PASSED\n"); + + if (ctx->nat) { + struct nat_filter *f; + + if (print_headings) + printf("TEST NUA-8.1.4: filter UPDATE, wait until session expires\n"); + + f = test_nat_add_filter(ctx->nat, remove_update, NULL, nat_inbound); + TEST_1(f); + + run_ab_until(ctx, -1, until_terminated, -1, until_terminated); + + TEST_1(e = a->events->head); TEST_E(e->data->e_event, nua_r_bye); + + free_events_in_list(ctx, a->events); + free_events_in_list(ctx, b->events); + + nua_handle_destroy(a_call->nh), a_call->nh = NULL; + nua_handle_destroy(b_call->nh), b_call->nh = NULL; + + test_nat_remove_filter(ctx->nat, f); + + if (print_headings) + printf("TEST NUA-8.1.4: PASSED\n"); + } + } + + if (b_call->nh) { + if (print_headings) + printf("TEST NUA-8.1.9: Terminate first session timer call\n"); + + BYE(b, b_call, b_call->nh, TAG_END()); + run_ab_until(ctx, -1, until_terminated, -1, until_terminated); + free_events_in_list(ctx, a->events); + free_events_in_list(ctx, b->events); + + nua_handle_destroy(a_call->nh), a_call->nh = NULL; + nua_handle_destroy(b_call->nh), b_call->nh = NULL; + + if (print_headings) + printf("TEST NUA-8.1.9: PASSED\n"); + } + + /* + | | + |-------INVITE------>| + |<-------422---------| + |--------ACK-------->| + | | + |-------INVITE------>| + |<----100 Trying-----| + | | + |<----180 Ringing----| + | | + |<------200 OK-------| + |--------ACK-------->| + | | + |<-------BYE---------| + |-------200 OK-------| + | | + */ + + if (print_headings) + printf("TEST NUA-8.2: Session timers negotiation\n"); + + test_proxy_set_session_timer(ctx->p, 180, 90); + + nua_set_params(a->nua, + NUTAG_SUPPORTED("timer"), + TAG_END()); + + run_a_until(ctx, nua_r_set_params, until_final_response); + + nua_set_params(b->nua, + NUTAG_AUTOANSWER(0), + NUTAG_MIN_SE(120), + NTATAG_SIP_T1X64(2000), + TAG_END()); + + run_b_until(ctx, nua_r_set_params, until_final_response); + + TEST_1(a_call->nh = nua_handle(a->nua, a_call, SIPTAG_TO(b->to), + TAG_END())); + + INVITE(a, a_call, a_call->nh, + TAG_IF(!ctx->proxy_tests, NUTAG_URL(b->contact->m_url)), + SOATAG_USER_SDP_STR(a_call->sdp), + SIPTAG_SUPPORTED_STR("100rel, timer"), + NUTAG_SESSION_TIMER(4), + NUTAG_MIN_SE(3), + TAG_END()); + + run_ab_until(ctx, -1, until_ready, -1, accept_call); + + /* Client transitions: + INIT -(C1)-> CALLING: nua_invite(), nua_i_state + CALLING -(C6a)-> (TERMINATED/INIT): nua_r_invite + when testing with proxy, second 422 when call reaches UAS: + (INIT) -(C1)-> CALLING: nua_i_state + CALLING -(C6a)-> (TERMINATED/INIT): nua_r_invite + (INIT) -(C1)-> CALLING: nua_i_state + CALLING -(C2)-> PROCEEDING: nua_r_invite, nua_i_state + PROCEEDING -(C3+C4)-> READY: nua_r_invite, nua_i_state + */ + TEST_1(e = a->events->head); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_calling); /* CALLING */ + TEST_1(is_offer_sent(e->data->e_tags)); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_r_invite); + TEST(e->data->e_status, 100); + TEST(sip_object(e->data->e_msg)->sip_status->st_status, 422); + + if (ctx->proxy_tests) { + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_calling); /* CALLING */ + TEST_1(is_offer_sent(e->data->e_tags)); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_r_invite); + TEST(e->data->e_status, 100); + TEST(sip_object(e->data->e_msg)->sip_status->st_status, 422); + } + + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_calling); /* CALLING */ + TEST_1(is_offer_sent(e->data->e_tags)); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_r_invite); + TEST(e->data->e_status, 180); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_proceeding); /* PROCEEDING */ + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_r_invite); + TEST(e->data->e_status, 200); + TEST_1(sip = sip_object(e->data->e_msg)); + TEST_1(sip->sip_session_expires); + TEST(sip->sip_session_expires->x_delta, 120); + TEST_S(sip->sip_session_expires->x_refresher, "uac"); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_ready); /* READY */ + TEST_1(is_answer_recv(e->data->e_tags)); + TEST_1(!e->next); + free_events_in_list(ctx, a->events); + + /* + Server transitions: + INIT -(S1)-> RECEIVED: nua_i_invite, nua_i_state + RECEIVED -(S2a)-> EARLY: nua_respond(), nua_i_state + EARLY -(S3b)-> COMPLETED: nua_respond(), nua_i_state + COMPLETED -(S4)-> READY: nua_i_ack, nua_i_state + */ + TEST_1(e = b->events->head); TEST_E(e->data->e_event, nua_i_invite); + TEST(e->data->e_status, 100); + TEST_1(sip = sip_object(e->data->e_msg)); + TEST_1(sip->sip_session_expires); + TEST_1(!sip->sip_session_expires->x_refresher); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_received); /* RECEIVED */ + TEST_1(is_offer_recv(e->data->e_tags)); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_early); /* EARLY */ + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_completed); /* COMPLETED */ + TEST_1(is_answer_sent(e->data->e_tags)); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_ack); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_ready); /* READY */ + TEST_1(!e->next); + free_events_in_list(ctx, b->events); + + if (print_headings) + printf("TEST NUA-8.2: PASSED\n"); + + if (print_headings) + printf("TEST NUA-8.3: UPDATE with session timer headers\n"); + + UPDATE(b, b_call, b_call->nh, TAG_END()); + run_ab_until(ctx, -1, until_ready, -1, until_ready); + + /* Events from B (who sent UPDATE) */ + TEST_1(e = b->events->head); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_calling); /* CALLING */ + TEST_1(is_offer_sent(e->data->e_tags)); + if (!e->next) + run_b_until(ctx, -1, until_ready); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_r_update); + TEST(e->data->e_status, 200); + TEST_1(sip = sip_object(e->data->e_msg)); + TEST_1(sip->sip_session_expires); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_ready); /* READY */ + TEST_1(is_answer_recv(e->data->e_tags)); + TEST_1(!e->next); + free_events_in_list(ctx, b->events); + + /* Events from A (who received UPDATE) */ + TEST_1(e = a->events->head); TEST_E(e->data->e_event, nua_i_update); + TEST(e->data->e_status, 200); + TEST_1(sip = sip_object(e->data->e_msg)); + TEST_1(sip->sip_session_expires); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_ready); /* READY */ + TEST_1(is_offer_recv(e->data->e_tags)); + TEST_1(is_answer_sent(e->data->e_tags)); + TEST_1(!e->next); + free_events_in_list(ctx, a->events); + + BYE(b, b_call, b_call->nh, TAG_END()); + run_ab_until(ctx, -1, until_terminated, -1, until_terminated); + + /* B transitions: + READY --(T2)--> TERMINATING: nua_bye() + TERMINATING --(T3)--> TERMINATED: nua_r_bye, nua_i_state + */ + TEST_1(e = b->events->head); TEST_E(e->data->e_event, nua_r_bye); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_terminated); /* TERMINATED */ + TEST_1(!e->next); + free_events_in_list(ctx, b->events); + + /* A: READY -(T1)-> TERMINATED: nua_i_bye, nua_i_state */ + TEST_1(e = a->events->head); TEST_E(e->data->e_event, nua_i_bye); + TEST(e->data->e_status, 200); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state); + TEST(callstate(e->data->e_tags), nua_callstate_terminated); /* TERMINATED */ + TEST_1(!e->next); + free_events_in_list(ctx, a->events); + + nua_handle_destroy(a_call->nh), a_call->nh = NULL; + nua_handle_destroy(b_call->nh), b_call->nh = NULL; + + if (print_headings) + printf("TEST NUA-8.3: PASSED\n"); + + END(); +} + +static +size_t remove_update(void *a, void *message, size_t len) +{ + (void)a; + + if (strncmp("UPDATE ", message, 7) == 0) { + return 0; + } + + return len; +} + Added: freeswitch/trunk/libs/sofia-sip/tests/test_simple.c ============================================================================== --- (empty file) +++ freeswitch/trunk/libs/sofia-sip/tests/test_simple.c Wed May 14 15:10:54 2008 @@ -0,0 +1,2099 @@ +/* + * This file is part of the Sofia-SIP package + * + * Copyright (C) 2005 Nokia Corporation. + * + * Contact: Pekka Pessi <pekka.pessi@nokia.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public License + * as published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + */ + +/**@CFILE test_nua_simple.c + * @brief NUA-11: Test SIMPLE methods: MESSAGE, PUBLISH and SUBSCRIBE/NOTIFY. + * + * @author Pekka Pessi <Pekka.Pessi@nokia.com> + * @author Martti Mela <Martti Mela@nokia.com> + * + * @date Created: Wed Aug 17 12:12:12 EEST 2005 ppessi + */ + +#include "config.h" + +#include "test_nua.h" +#include <sofia-sip/su_tag_class.h> + +#if HAVE_FUNC +#elif HAVE_FUNCTION +#define __func__ __FUNCTION__ +#else +#define __func__ "test_simple" +#endif + +extern int accept_request(CONDITION_PARAMS); + +int save_until_nth_final_response(CONDITION_PARAMS); +int accept_n_notifys(CONDITION_PARAMS); + +int test_message(struct context *ctx) +{ + BEGIN(); + + struct endpoint *a = &ctx->a, *b = &ctx->b; + struct call *a_call = a->call, *b_call = b->call; + struct event *e; + sip_t const *sip; + url_t url[1]; + +/* Message test + + A B + |-------MESSAGE----->| + |<-------200---------| + | | + +*/ + if (print_headings) + printf("TEST NUA-11.1.1: MESSAGE\n"); + + if (ctx->proxy_tests) + *url = *b->to->a_url; + else + *url = *b->contact->m_url; + + /* Test that query part is included in request sent to B */ + url->url_headers = "organization=United%20Testers"; + + TEST_1(a_call->nh = nua_handle(a->nua, a_call, TAG_END())); + + MESSAGE(a, a_call, a_call->nh, + NUTAG_URL(url), + SIPTAG_SUBJECT_STR("NUA-11.1.1"), + SIPTAG_CONTENT_TYPE_STR("text/plain"), + SIPTAG_PAYLOAD_STR("Hello hellO!\n"), + TAG_END()); + + run_ab_until(ctx, -1, save_until_final_response, -1, save_until_received); + + /* Client events: + nua_message(), nua_r_message + */ + TEST_1(e = a->events->head); TEST_E(e->data->e_event, nua_r_message); + TEST(e->data->e_status, 200); + TEST_1(!e->next); + + /* + Server events: + nua_i_message + */ + TEST_1(e = b->events->head); TEST_E(e->data->e_event, nua_i_message); + TEST(e->data->e_status, 200); + TEST_1(sip = sip_object(e->data->e_msg)); + TEST_1(sip->sip_subject && sip->sip_subject->g_string); + TEST_S(sip->sip_subject->g_string, "NUA-11.1.1"); + TEST_1(sip->sip_organization); + TEST_S(sip->sip_organization->g_string, "United Testers"); + TEST_1(!e->next); + + free_events_in_list(ctx, a->events); + nua_handle_destroy(a_call->nh), a_call->nh = NULL; + + free_events_in_list(ctx, b->events); + nua_handle_destroy(b_call->nh), b_call->nh = NULL; + + if (print_headings) + printf("TEST NUA-11.1.1: PASSED\n"); + +/* MESSAGE as application method + + A B + |-------MESSAGE----->| + |<-------202---------| + | | +*/ + + if (print_headings) + printf("TEST NUA-11.1.2: MESSAGE\n"); + + nua_set_params(b->nua, NUTAG_APPL_METHOD("MESSAGE"), TAG_END()); + run_b_until(ctx, nua_r_set_params, until_final_response); + + TEST_1(a_call->nh = nua_handle(a->nua, a_call, TAG_END())); + + MESSAGE(a, a_call, a_call->nh, + NUTAG_URL(url), + SIPTAG_SUBJECT_STR("NUA-11.1.2"), + SIPTAG_CONTENT_TYPE_STR("text/plain"), + SIPTAG_PAYLOAD_STR("Hello hellO!\n"), + TAG_END()); + + run_ab_until(ctx, -1, save_until_final_response, -1, accept_request); + + /* Client events: + nua_message(), nua_r_message + */ + TEST_1(e = a->events->head); TEST_E(e->data->e_event, nua_r_message); + TEST(e->data->e_status, 202); + TEST_1(sip = sip_object(e->data->e_msg)); + TEST_1(sip_user_agent(sip)); + TEST_S(sip_user_agent(sip)->g_value, "007"); + TEST_1(!e->next); + + /* + Server events: + nua_i_message + */ + TEST_1(e = b->events->head); TEST_E(e->data->e_event, nua_i_message); + TEST(e->data->e_status, 100); + TEST_1(sip = sip_object(e->data->e_msg)); + TEST_1(sip->sip_subject && sip->sip_subject->g_string); + TEST_S(sip->sip_subject->g_string, "NUA-11.1.2"); + TEST_1(sip->sip_organization); + TEST_S(sip->sip_organization->g_string, "United Testers"); + TEST_1(!e->next); + + free_events_in_list(ctx, a->events); + nua_handle_destroy(a_call->nh), a_call->nh = NULL; + + free_events_in_list(ctx, b->events); + nua_handle_destroy(b_call->nh), b_call->nh = NULL; + + if (print_headings) + printf("TEST NUA-11.1.2: PASSED\n"); + + +/* Message test + + A + |-------MESSAGE--\ + |<---------------/ + |--------200-----\ + |<---------------/ + | + +*/ + if (print_headings) + printf("TEST NUA-11.2: MESSAGE to myself\n"); + + TEST_1(a_call->nh = nua_handle(a->nua, a_call, SIPTAG_TO(a->to), TAG_END())); + + MESSAGE(a, a_call, a_call->nh, + /* We cannot reach us by using our contact! */ + NUTAG_URL(!ctx->p && !ctx->proxy_tests ? a->contact->m_url : NULL), + SIPTAG_SUBJECT_STR("NUA-11.2"), + SIPTAG_CONTENT_TYPE_STR("text/plain"), + SIPTAG_PAYLOAD_STR("Hello hellO!\n"), + TAG_END()); + + run_a_until(ctx, -1, save_until_final_response); + + /* Events: + nua_message(), nua_i_message, nua_r_message + */ + TEST_1(e = a->specials->head); + while (e->data->e_event == nua_i_outbound) + e = e->next; + TEST_E(e->data->e_event, nua_i_message); + TEST(e->data->e_status, 200); + TEST_1(sip = sip_object(e->data->e_msg)); + TEST_1(sip->sip_subject && sip->sip_subject->g_string); + TEST_S(sip->sip_subject->g_string, "NUA-11.2"); + + TEST_1(e = a->events->head); TEST_E(e->data->e_event, nua_r_message); + TEST(e->data->e_status, 200); + TEST_1(!e->next); + + free_events_in_list(ctx, a->events); + free_events_in_list(ctx, a->specials); + nua_handle_destroy(a_call->nh), a_call->nh = NULL; + + if (print_headings) + printf("TEST NUA-11.2: PASSED\n"); + + END(); +} + +int accept_request(CONDITION_PARAMS) +{ + msg_t *with = nua_current_request(nua); + + if (!(check_handle(ep, call, nh, SIP_500_INTERNAL_SERVER_ERROR))) + return 0; + + save_event_in_list(ctx, event, ep, call); + + if (status < 200) { + RESPOND(ep, call, nh, SIP_202_ACCEPTED, + NUTAG_WITH(with), + SIPTAG_USER_AGENT_STR("007"), + TAG_END()); + return 1; + } + + return 0; +} + +char const *test_etag = "tagtag"; + +int respond_with_etag(CONDITION_PARAMS) +{ + msg_t *with = nua_current_request(nua); + + if (!(check_handle(ep, call, nh, SIP_500_INTERNAL_SERVER_ERROR))) + return 1; + + save_event_in_list(ctx, event, ep, call); + + switch (event) { + char const *etag; + case nua_i_publish: + etag = sip->sip_if_match ? sip->sip_if_match->g_value : NULL; + if (sip->sip_if_match && (etag == NULL || strcmp(etag, test_etag))) { + RESPOND(ep, call, nh, SIP_412_PRECONDITION_FAILED, + NUTAG_WITH(with), + TAG_END()); + } + else { + RESPOND(ep, call, nh, SIP_200_OK, + NUTAG_WITH(with), + SIPTAG_ETAG_STR(test_etag), + SIPTAG_EXPIRES_STR("3600"), + SIPTAG_EXPIRES(sip->sip_expires), /* overrides 3600 */ + TAG_END()); + } + return 1; + default: + return 0; + } +} + +static int close_handle(CONDITION_PARAMS) +{ + if (call->nh == nh) + call->nh = NULL; + nua_handle_destroy(nh); + return 1; +} + +int test_publish(struct context *ctx) +{ + BEGIN(); + + struct endpoint *a = &ctx->a, *b = &ctx->b; + struct call *a_call = a->call, *b_call = b->call; + struct event *e; + sip_t const *sip; + + +/* PUBLISH test + + A B + |-------PUBLISH----->| + |<-------405---------| (method not allowed by default) + | | + |-------PUBLISH----->| + |<-------501---------| (no events allowed) + | | + |-------PUBLISH----->| + |<-------489---------| (event not allowed by default) + | | + |-------PUBLISH----->| + |<-------200---------| (event allowed, responded) + | | + |-----un-PUBLISH---->| + |<-------200---------| (event allowed, responded) + +*/ + if (print_headings) + printf("TEST NUA-11.3: PUBLISH\n"); + + TEST_1(a_call->nh = nua_handle(a->nua, a_call, SIPTAG_TO(b->to), TAG_END())); + + PUBLISH(a, a_call, a_call->nh, + TAG_IF(!ctx->proxy_tests, NUTAG_URL(b->contact->m_url)), + SIPTAG_EVENT_STR("presence"), + SIPTAG_CONTENT_TYPE_STR("text/urllist"), + SIPTAG_PAYLOAD_STR("sip:example.com\n"), + TAG_END()); + + run_ab_until(ctx, -1, save_until_final_response, -1, NULL); + + /* Client events: + nua_publish(), nua_r_publish + */ + TEST_1(e = a->events->head); TEST_E(e->data->e_event, nua_r_publish); + TEST(e->data->e_status, 405); + TEST_1(!e->next); + + free_events_in_list(ctx, a->events); + nua_handle_destroy(a_call->nh), a_call->nh = NULL; + + free_events_in_list(ctx, b->events); + nua_handle_destroy(b_call->nh), b_call->nh = NULL; + + nua_set_params(b->nua, NUTAG_ALLOW("PUBLISH"), TAG_END()); + + run_b_until(ctx, nua_r_set_params, until_final_response); + + TEST_1(a_call->nh = nua_handle(a->nua, a_call, SIPTAG_TO(b->to), TAG_END())); + + PUBLISH(a, a_call, a_call->nh, + TAG_IF(!ctx->proxy_tests, NUTAG_URL(b->contact->m_url)), + SIPTAG_EVENT_STR("presence"), + SIPTAG_CONTENT_TYPE_STR("text/urllist"), + SIPTAG_PAYLOAD_STR("sip:example.com\n"), + TAG_END()); + + run_a_until(ctx, -1, save_until_final_response); + + /* Client events: + nua_publish(), nua_r_publish + */ + TEST_1(e = a->events->head); TEST_E(e->data->e_event, nua_r_publish); + TEST(e->data->e_status, 501); /* Not implemented */ + TEST_1(!e->next); + + free_events_in_list(ctx, a->events); + nua_handle_destroy(a_call->nh), a_call->nh = NULL; + + /* Allow presence event */ + + nua_set_params(b->nua, NUTAG_ALLOW_EVENTS("presence"), TAG_END()); + + run_b_until(ctx, nua_r_set_params, until_final_response); + + TEST_1(a_call->nh = nua_handle(a->nua, a_call, SIPTAG_TO(b->to), TAG_END())); + + PUBLISH(a, a_call, a_call->nh, + TAG_IF(!ctx->proxy_tests, NUTAG_URL(b->contact->m_url)), + SIPTAG_EVENT_STR("reg"), + SIPTAG_CONTENT_TYPE_STR("text/urllist"), + SIPTAG_PAYLOAD_STR("sip:example.com\n"), + TAG_END()); + + run_a_until(ctx, -1, save_until_final_response); + + /* Client events: + nua_publish(), nua_r_publish + */ + TEST_1(e = a->events->head); TEST_E(e->data->e_event, nua_r_publish); + TEST(e->data->e_status, 489); /* Bad Event */ + TEST_1(!e->next); + + free_events_in_list(ctx, a->events); + nua_handle_destroy(a_call->nh), a_call->nh = NULL; + + TEST_1(a_call->nh = nua_handle(a->nua, a_call, SIPTAG_TO(b->to), TAG_END())); + + PUBLISH(a, a_call, a_call->nh, + TAG_IF(!ctx->proxy_tests, NUTAG_URL(b->contact->m_url)), + SIPTAG_EVENT_STR("presence"), + SIPTAG_CONTENT_TYPE_STR("text/urllist"), + SIPTAG_PAYLOAD_STR("sip:example.com\n"), + SIPTAG_EXPIRES_STR("5"), + TAG_END()); + + run_ab_until(ctx, -1, save_until_final_response, -1, respond_with_etag); + + /* Client events: + nua_publish(), nua_r_publish + */ + TEST_1(e = a->events->head); TEST_E(e->data->e_event, nua_r_publish); + TEST(e->data->e_status, 200); + TEST_1(sip = sip_object(e->data->e_msg)); + TEST_1(sip->sip_etag); + TEST_S(sip->sip_etag->g_string, test_etag); + TEST_1(!e->next); + + /* + Server events: + nua_i_publish + */ + TEST_1(e = b->events->head); TEST_E(e->data->e_event, nua_i_publish); + TEST(e->data->e_status, 100); + TEST_1(!e->next); + + free_events_in_list(ctx, a->events); + free_events_in_list(ctx, b->events); + nua_handle_destroy(b_call->nh), b_call->nh = NULL; + + if (!ctx->expensive && 0) + goto skip_republish; + + run_ab_until(ctx, -1, save_until_final_response, -1, respond_with_etag); + + /* Client events: nua_r_publish + */ + TEST_1(e = a->events->head); TEST_E(e->data->e_event, nua_r_publish); + TEST(e->data->e_status, 200); + TEST_1(sip = sip_object(e->data->e_msg)); + TEST_1(sip->sip_etag); + TEST_S(sip->sip_etag->g_string, test_etag); + TEST_1(!e->next); + + free_events_in_list(ctx, a->events); + + /* + Server events: + nua_i_publish + */ + TEST_1(e = b->events->head); TEST_E(e->data->e_event, nua_i_publish); + TEST(e->data->e_status, 100); + TEST_1(sip = sip_object(e->data->e_msg)); + TEST_1(sip = sip_object(e->data->e_msg)); + TEST_1(sip->sip_if_match); + TEST_S(sip->sip_if_match->g_string, "tagtag"); + TEST_1(!sip->sip_content_type); + TEST_1(!sip->sip_payload); + TEST_1(!e->next); + + free_events_in_list(ctx, b->events); + nua_handle_destroy(b_call->nh), b_call->nh = NULL; + + skip_republish: + + UNPUBLISH(a, a_call, a_call->nh, TAG_END()); + + run_ab_until(ctx, -1, save_until_final_response, -1, respond_with_etag); + + /* Client events: + nua_publish(), nua_r_publish + */ + TEST_1(e = a->events->head); TEST_E(e->data->e_event, nua_r_unpublish); + TEST(e->data->e_status, 200); + TEST_1(!e->next); + + /* + Server events: + nua_i_publish + */ + TEST_1(e = b->events->head); TEST_E(e->data->e_event, nua_i_publish); + TEST(e->data->e_status, 100); + TEST_1(!e->next); + + free_events_in_list(ctx, a->events); + free_events_in_list(ctx, b->events); + + nua_handle_destroy(a_call->nh), a_call->nh = NULL; + nua_handle_destroy(b_call->nh), b_call->nh = NULL; + + TEST_1(a_call->nh = nua_handle(a->nua, a_call, SIPTAG_TO(b->to), TAG_END())); + + /* Let server close handle without responding to PUBLISH */ + PUBLISH(a, a_call, a_call->nh, + TAG_IF(!ctx->proxy_tests, NUTAG_URL(b->contact->m_url)), + SIPTAG_EVENT_STR("presence"), + SIPTAG_CONTENT_TYPE_STR("text/urllist"), + SIPTAG_PAYLOAD_STR("sip:example.com\n"), + TAG_END()); + + run_ab_until(ctx, -1, save_until_final_response, -1, close_handle); + + /* Client events: + nua_publish(), nua_r_publish + */ + TEST_1(e = a->events->head); TEST_E(e->data->e_event, nua_r_publish); + TEST(e->data->e_status, 500); + TEST_1(!e->next); + + free_events_in_list(ctx, a->events); + + nua_handle_destroy(a_call->nh), a_call->nh = NULL; + + /* No Event header */ + + TEST_1(a_call->nh = nua_handle(a->nua, a_call, SIPTAG_TO(b->to), TAG_END())); + + PUBLISH(a, a_call, a_call->nh, + TAG_IF(!ctx->proxy_tests, NUTAG_URL(b->contact->m_url)), + SIPTAG_CONTENT_TYPE_STR("text/urllist"), + SIPTAG_PAYLOAD_STR("sip:example.com\n"), + TAG_END()); + + run_ab_until(ctx, -1, save_until_final_response, -1, save_events); + + /* Client events: + nua_publish(), nua_r_publish + */ + TEST_1(e = a->events->head); TEST_E(e->data->e_event, nua_r_publish); + TEST(e->data->e_status, 489); + TEST_1(!e->next); + + free_events_in_list(ctx, a->events); + + /* + Server events: nothing + */ + TEST_1(!b->events->head); + + nua_handle_destroy(a_call->nh), a_call->nh = NULL; + + if (print_headings) + printf("TEST NUA-11.3: PASSED\n"); + + END(); +} + +static char const presence_open[] = + "<?xml version='1.0' encoding='UTF-8'?>\n" + "<presence xmlns='urn:ietf:params:xml:ns:cpim-pidf' \n" + " entity='pres:bob@example.org'>\n" + " <tuple id='ksac9udshce'>\n" + " <status><basic>open</basic></status>\n" + " <contact priority='1.0'>sip:bob@example.org</contact>\n" + " </tuple>\n" + "</presence>\n"; + +static char const presence_closed[] = + "<?xml version='1.0' encoding='UTF-8'?>\n" + "<presence xmlns='urn:ietf:params:xml:ns:cpim-pidf' \n" + " entity='pres:bob@example.org'>\n" + " <tuple id='ksac9udshce'>\n" + " <status><basic>closed</basic></status>\n" + " </tuple>\n" + "</presence>\n"; + + +int accept_and_notify_twice(CONDITION_PARAMS) +{ + msg_t *with = nua_current_request(nua); + + if (!(check_handle(ep, call, nh, SIP_500_INTERNAL_SERVER_ERROR))) + return 0; + + save_event_in_list(ctx, event, ep, call); + + switch (event) { + case nua_i_subscribe: + if (status < 200) { + NOTIFY(ep, call, nh, + SIPTAG_EVENT(sip->sip_event), + SIPTAG_CONTENT_TYPE_STR("application/pidf+xml"), + SIPTAG_PAYLOAD_STR(presence_closed), + NUTAG_SUBSTATE(nua_substate_pending), + TAG_END()); + NOTIFY(ep, call, nh, + SIPTAG_EVENT(sip->sip_event), + SIPTAG_CONTENT_TYPE_STR("application/pidf+xml"), + SIPTAG_PAYLOAD_STR(presence_open), + NUTAG_SUBSTATE(nua_substate_active), + TAG_END()); + RESPOND(ep, call, nh, SIP_202_ACCEPTED, + NUTAG_WITH(with), + SIPTAG_EXPIRES_STR("360"), + TAG_END()); + } + return 0; + + case nua_r_notify: + return status >= 200 && + tl_find(tags, nutag_substate)->t_value == nua_substate_active; + + default: + return 0; + } +} + +int save_until_responded_and_notified_twice(CONDITION_PARAMS) +{ + save_event_in_list(ctx, event, ep, call); + + if (event == nua_i_notify) { + if (ep->flags.bit0) + ep->flags.bit1 = 1; + ep->flags.bit0 = 1; + } + + if (event == nua_r_subscribe || event == nua_r_unsubscribe) { + if (status >= 300) + return 1; + else if (status >= 200) { + ep->flags.bit2 = 1; + } + } + + return ep->flags.bit0 && ep->flags.bit1 && ep->flags.bit2; +} + + +int accept_and_notify(CONDITION_PARAMS) +{ + msg_t *with = nua_current_request(nua); + + if (!(check_handle(ep, call, nh, SIP_500_INTERNAL_SERVER_ERROR))) + return 0; + + save_event_in_list(ctx, event, ep, call); + + switch (event) { + case nua_i_subscribe: + if (status < 200) { + int fetch = sip->sip_expires && sip->sip_expires->ex_delta == 0; + + RESPOND(ep, call, nh, SIP_202_ACCEPTED, + NUTAG_WITH(with), + SIPTAG_EXPIRES_STR("360"), + SIPTAG_EXPIRES(sip->sip_expires), + TAG_END()); + + NOTIFY(ep, call, nh, + SIPTAG_EVENT(sip->sip_event), + SIPTAG_CONTENT_TYPE_STR("application/pidf+xml"), + SIPTAG_PAYLOAD_STR(presence_closed), + NUTAG_SUBSTATE(fetch + ? nua_substate_pending + : nua_substate_terminated), + TAG_END()); + + } + return 0; + + case nua_r_notify: + return status >= 200; + + default: + return 0; + } +} + +int save_and_notify(CONDITION_PARAMS) +{ + if (!(check_handle(ep, call, nh, SIP_500_INTERNAL_SERVER_ERROR))) + return 0; + + save_event_in_list(ctx, event, ep, call); + + switch (event) { + case nua_i_subscribe: + NOTIFY(ep, call, nh, + SIPTAG_EVENT(sip->sip_event), + SIPTAG_CONTENT_TYPE_STR("application/pidf+xml"), + SIPTAG_PAYLOAD_STR(presence_closed), + NUTAG_SUBSTATE(nua_substate_active), + TAG_END()); + return 0; + + case nua_r_notify: + return status >= 200; + + default: + return 0; + } +} + +extern int save_until_notified_and_responded(CONDITION_PARAMS); +extern int save_until_notified(CONDITION_PARAMS); + +int test_subscribe_notify(struct context *ctx) +{ + BEGIN(); + + struct endpoint *a = &ctx->a, *b = &ctx->b; + struct call *a_call = a->call, *b_call = b->call; + struct event *e, *en1, *en2, *es; + sip_t const *sip; + tagi_t const *n_tags, *r_tags; + + if (print_headings) + printf("TEST NUA-11.4: notifier server using nua_notify()\n"); + + if (print_headings) + printf("TEST NUA-11.4.1: establishing subscription\n"); + + TEST_1(a_call->nh = nua_handle(a->nua, a_call, SIPTAG_TO(b->to), TAG_END())); + + SUBSCRIBE(a, a_call, a_call->nh, NUTAG_URL(b->contact->m_url), + SIPTAG_EVENT_STR("presence"), + SIPTAG_EXPIRES_STR("333"), + SIPTAG_ACCEPT_STR("application/xpidf, application/pidf+xml"), + TAG_END()); + + run_ab_until(ctx, -1, save_until_responded_and_notified_twice, + -1, accept_and_notify_twice); + + /* Client events: + nua_subscribe(), nua_i_notify/nua_r_subscribe/nua_i_notify + */ + for (en1 = en2 = es = NULL, e = a->events->head; e; e = e->next) { + if (en1 == NULL && e->data->e_event == nua_i_notify) + en1 = e; + else if (en2 == NULL && e->data->e_event == nua_i_notify) + en2 = e; + else if (e->data->e_event == nua_r_subscribe) + es = e; + else + TEST_1(!e); + } + + TEST_1(e = en1); + TEST_E(e->data->e_event, nua_i_notify); + TEST_1(tl_find(e->data->e_tags, nutag_substate)); + TEST(tl_find(e->data->e_tags, nutag_substate)->t_value, nua_substate_pending); + TEST_1(sip = sip_object(e->data->e_msg)); + TEST_1(sip->sip_event); TEST_S(sip->sip_event->o_type, "presence"); + TEST_1(sip->sip_content_type); + TEST_S(sip->sip_content_type->c_type, "application/pidf+xml"); + TEST_1(sip->sip_subscription_state); + TEST_S(sip->sip_subscription_state->ss_substate, "pending"); + TEST_1(sip->sip_subscription_state->ss_expires); + + TEST_1(e = es); TEST_E(e->data->e_event, nua_r_subscribe); + TEST_1(e->data->e_status == 202 || e->data->e_status == 200); + TEST_1(tl_find(e->data->e_tags, nutag_substate)); + r_tags = tl_find(e->data->e_tags, nutag_substate); + if (es == a->events->head) { + TEST(r_tags->t_value, nua_substate_embryonic); + } + else if (es == a->events->head->next) { + TEST_1(r_tags->t_value == nua_substate_pending); + } + else { + TEST_1(r_tags->t_value == nua_substate_active); + } + TEST_1(sip = sip_object(e->data->e_msg)); + TEST_1(sip->sip_expires); + TEST_1(sip->sip_expires->ex_delta <= 333); + + free_events_in_list(ctx, a->events); + + /* Server events: nua_i_subscribe, nua_r_notify */ + TEST_1(e = b->events->head); + TEST_E(e->data->e_event, nua_i_subscribe); + TEST_E(e->data->e_status, 100); + TEST_1(sip = sip_object(e->data->e_msg)); + + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_r_notify); + r_tags = e->data->e_tags; + TEST_1(tl_find(r_tags, nutag_substate)); + TEST(tl_find(r_tags, nutag_substate)->t_value, nua_substate_pending); + + free_events_in_list(ctx, b->events); + + if (print_headings) + printf("TEST NUA-11.4.1: PASSED\n"); + + /* ---------------------------------------------------------------------- */ + +/* NOTIFY with updated content + + A B + | | + |<------NOTIFY-------| + |-------200 OK------>| + | | +*/ + if (print_headings) + printf("TEST NUA-11.4.2: send NOTIFY\n"); + + /* Update presence data */ + + NOTIFY(b, b_call, b_call->nh, + NUTAG_SUBSTATE(nua_substate_active), + SIPTAG_EVENT_STR("presence"), + SIPTAG_CONTENT_TYPE_STR("application/pidf+xml"), + SIPTAG_PAYLOAD_STR(presence_open), + TAG_END()); + + run_ab_until(ctx, -1, save_until_notified, + -1, save_until_final_response); + + /* subscriber events: + nua_i_notify + */ + TEST_1(e = a->events->head); TEST_E(e->data->e_event, nua_i_notify); + TEST_1(sip = sip_object(e->data->e_msg)); + TEST_1(sip->sip_event); TEST_S(sip->sip_event->o_type, "presence"); + TEST_1(sip->sip_content_type); + TEST_S(sip->sip_content_type->c_type, "application/pidf+xml"); + TEST_1(sip->sip_subscription_state); + TEST_S(sip->sip_subscription_state->ss_substate, "active"); + TEST_1(sip->sip_subscription_state->ss_expires); + n_tags = e->data->e_tags; + TEST_1(tl_find(n_tags, nutag_substate)); + TEST(tl_find(n_tags, nutag_substate)->t_value, nua_substate_active); + TEST_1(!e->next); + free_events_in_list(ctx, a->events); + + /* Notifier events: nua_r_notify */ + TEST_1(e = b->events->head); TEST_E(e->data->e_event, nua_r_notify); + r_tags = e->data->e_tags; + TEST_1(tl_find(r_tags, nutag_substate)); + TEST(tl_find(r_tags, nutag_substate)->t_value, nua_substate_active); + + free_events_in_list(ctx, b->events); + + if (print_headings) + printf("TEST NUA-11.4.2: PASSED\n"); + + /* ---------------------------------------------------------------------- */ + +/* Re-SUBSCRIBE + + A B + | | + |<------NOTIFY-------| + |-------200 OK------>| + | | +*/ + if (print_headings) + printf("TEST NUA-11.4.3: re-SUBSCRIBE\n"); + + /* Set default expiration time */ + nua_set_hparams(b_call->nh, NUTAG_SUB_EXPIRES(365), TAG_END()); + run_b_until(ctx, nua_r_set_params, until_final_response); + + SUBSCRIBE(a, a_call, a_call->nh, NUTAG_URL(b->contact->m_url), + SIPTAG_EVENT_STR("presence"), + SIPTAG_EXPIRES_STR("3600"), + SIPTAG_ACCEPT_STR("application/xpidf, application/pidf+xml"), + TAG_END()); + + run_ab_until(ctx, -1, save_until_notified_and_responded, + -1, save_until_final_response); + + /* Client events: + nua_subscribe(), nua_i_notify/nua_r_subscribe + */ + for (en1 = en2 = es = NULL, e = a->events->head; e; e = e->next) { + if (en1 == NULL && e->data->e_event == nua_i_notify) + en1 = e; + else if (e->data->e_event == nua_r_subscribe) + es = e; + else + TEST_1(!e); + } + TEST_1(e = en1); + TEST_E(e->data->e_event, nua_i_notify); + TEST_1(sip = sip_object(e->data->e_msg)); + TEST_1(sip->sip_event); TEST_S(sip->sip_event->o_type, "presence"); + TEST_1(sip->sip_content_type); + TEST_S(sip->sip_content_type->c_type, "application/pidf+xml"); + TEST_1(sip->sip_subscription_state); + TEST_S(sip->sip_subscription_state->ss_substate, "active"); + TEST_1(sip->sip_subscription_state->ss_expires); + n_tags = e->data->e_tags; + TEST_1(tl_find(n_tags, nutag_substate)); + TEST(tl_find(n_tags, nutag_substate)->t_value, nua_substate_active); + + TEST_1(e = es); TEST_E(e->data->e_event, nua_r_subscribe); + TEST_1(tl_find(e->data->e_tags, nutag_substate)); + TEST(tl_find(e->data->e_tags, nutag_substate)->t_value, nua_substate_active); + TEST_1(sip = sip_object(e->data->e_msg)); + TEST_1(sip->sip_expires); + TEST_1(sip->sip_expires->ex_delta <= 365); + + free_events_in_list(ctx, a->events); + + /* Server events: nua_i_subscribe, nua_r_notify */ + TEST_1(e = b->events->head); + TEST_E(e->data->e_event, nua_i_subscribe); + TEST_E(e->data->e_status, 100); + TEST_1(sip = sip_object(e->data->e_msg)); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_r_notify); + r_tags = e->data->e_tags; + TEST_1(tl_find(r_tags, nutag_substate)); + TEST(tl_find(r_tags, nutag_substate)->t_value, nua_substate_active); + + free_events_in_list(ctx, b->events); + + if (print_headings) + printf("TEST NUA-11.4.3: PASSED\n"); + + /* ---------------------------------------------------------------------- */ + +/* un-SUBSCRIBE + + A B + | | + |------SUBSCRIBE---->| + |<--------202--------| + |<------NOTIFY-------| + |-------200 OK------>| + | | +*/ + if (print_headings) + printf("TEST NUA-11.4.4: un-SUBSCRIBE\n"); + + UNSUBSCRIBE(a, a_call, a_call->nh, TAG_END()); + + run_ab_until(ctx, -1, save_until_final_response, + -1, save_until_final_response); + + /* Client events: + nua_unsubscribe(), nua_i_notify/nua_r_unsubscribe + */ + for (en1 = en2 = es = NULL, e = a->events->head; e; e = e->next) { + if (en1 == NULL && e->data->e_event == nua_i_notify) + en1 = e; + else if (e->data->e_event == nua_r_unsubscribe) + es = e; + else + TEST_1(!e); + } + if (en1) { + TEST_1(e = en1); TEST_E(e->data->e_event, nua_i_notify); + TEST_1(sip = sip_object(e->data->e_msg)); + TEST_1(sip->sip_event); + TEST_1(sip->sip_subscription_state); + TEST_S(sip->sip_subscription_state->ss_substate, "terminated"); + TEST_1(!sip->sip_subscription_state->ss_expires); + n_tags = e->data->e_tags; + TEST_1(tl_find(n_tags, nutag_substate)); + TEST(tl_find(n_tags, nutag_substate)->t_value, nua_substate_terminated); + } + + TEST_1(e = es); TEST_E(e->data->e_event, nua_r_unsubscribe); + TEST(e->data->e_status, 200); + r_tags = e->data->e_tags; + TEST_1(tl_find(r_tags, nutag_substate)); + TEST(tl_find(r_tags, nutag_substate)->t_value, nua_substate_terminated); + TEST_1(sip = sip_object(e->data->e_msg)); + TEST_1(sip->sip_expires); + TEST_1(sip->sip_expires->ex_delta == 0); + + free_events_in_list(ctx, a->events); + + /* Notifier events: nua_r_notify */ + TEST_1(e = b->events->head); + TEST_E(e->data->e_event, nua_i_subscribe); + TEST(e->data->e_status, 200); + TEST_1(sip = sip_object(e->data->e_msg)); + TEST_1(tl_find(e->data->e_tags, nutag_substate)); + TEST(tl_find(e->data->e_tags, nutag_substate)->t_value, + nua_substate_terminated); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_r_notify); + TEST_1(e->data->e_status >= 200); + r_tags = e->data->e_tags; + TEST_1(tl_find(r_tags, nutag_substate)); + TEST(tl_find(r_tags, nutag_substate)->t_value, nua_substate_terminated); + + free_events_in_list(ctx, b->events); + + nua_handle_destroy(a_call->nh), a_call->nh = NULL; + nua_handle_destroy(b_call->nh), b_call->nh = NULL; + + if (print_headings) + printf("TEST NUA-11.4.4: PASSED\n"); + + if (print_headings) + printf("TEST NUA-11.4: PASSED\n"); + + END(); +} + +/* ---------------------------------------------------------------------- */ +/* Subscriber gracefully terminates dialog upon 483 */ + +static +size_t change_status_to_483(void *a, void *message, size_t len); +int save_until_notified_and_responded_twice(CONDITION_PARAMS); +int save_until_notify_responded_twice(CONDITION_PARAMS); +int accept_subscription_until_terminated(CONDITION_PARAMS); + +int test_subscribe_notify_graceful(struct context *ctx) +{ + if (!ctx->nat) + return 0; + + BEGIN(); + + struct endpoint *a = &ctx->a, *b = &ctx->b; + struct call *a_call = a->call, *b_call = b->call; + struct event *e, *en1, *en2, *es; + sip_t const *sip; + tagi_t const *n_tags, *r_tags; + struct nat_filter *f; + + if (print_headings) + printf("TEST NUA-11.5.1: establishing subscription\n"); + + TEST_1(a_call->nh = nua_handle(a->nua, a_call, SIPTAG_TO(b->to), TAG_END())); + + SUBSCRIBE(a, a_call, a_call->nh, NUTAG_URL(b->contact->m_url), + SIPTAG_EVENT_STR("presence"), + SIPTAG_ACCEPT_STR("application/xpidf, application/pidf+xml"), + TAG_END()); + + run_ab_until(ctx, -1, save_until_notified_and_responded, + -1, accept_and_notify); + + /* Client events: + nua_subscribe(), nua_i_notify/nua_r_subscribe + */ + for (en1 = en2 = es = NULL, e = a->events->head; e; e = e->next) { + if (en1 == NULL && e->data->e_event == nua_i_notify) + en1 = e; + else if (es == NULL && e->data->e_event == nua_r_subscribe) + es = e; + else + TEST_1(!e); + } + TEST_1(e = en1); TEST_E(e->data->e_event, nua_i_notify); + TEST_1(sip = sip_object(e->data->e_msg)); + TEST_1(sip->sip_event); TEST_S(sip->sip_event->o_type, "presence"); + TEST_1(sip->sip_content_type); + TEST_S(sip->sip_content_type->c_type, "application/pidf+xml"); + TEST_1(sip->sip_subscription_state); + TEST_S(sip->sip_subscription_state->ss_substate, "pending"); + TEST_1(sip->sip_subscription_state->ss_expires); + n_tags = e->data->e_tags; + TEST_1(tl_find(n_tags, nutag_substate)); + TEST(tl_find(n_tags, nutag_substate)->t_value, nua_substate_pending); + + TEST_1(e = es); TEST_E(e->data->e_event, nua_r_subscribe); + r_tags = e->data->e_tags; + TEST_1(tl_find(r_tags, nutag_substate)); + if (es == a->events->head) + TEST(tl_find(r_tags, nutag_substate)->t_value, nua_substate_embryonic); + else + TEST(tl_find(r_tags, nutag_substate)->t_value, nua_substate_pending); + free_events_in_list(ctx, a->events); + + /* Server events: nua_i_subscribe, nua_r_notify */ + TEST_1(e = b->events->head); + TEST_E(e->data->e_event, nua_i_subscribe); + TEST_E(e->data->e_status, 100); + TEST_1(sip = sip_object(e->data->e_msg)); + + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_r_notify); + r_tags = e->data->e_tags; + TEST_1(tl_find(r_tags, nutag_substate)); + TEST(tl_find(r_tags, nutag_substate)->t_value, nua_substate_pending); + + free_events_in_list(ctx, b->events); + + if (print_headings) + printf("TEST NUA-11.5.1: PASSED\n"); + + if (print_headings) + printf("TEST NUA-11.5.2: terminate gracefully upon 483\n"); + + TEST_1(f = test_nat_add_filter(ctx->nat, + change_status_to_483, NULL, + nat_inbound)); + + SUBSCRIBE(a, a_call, a_call->nh, TAG_END()); + + run_ab_until(ctx, -1, save_until_notified_and_responded_twice, + -1, accept_subscription_until_terminated); + +#if 0 + /* Client events: + nua_unsubscribe(), nua_i_notify/nua_r_unsubscribe + */ + TEST_1(e = a->events->head); + if (e->data->e_event == nua_i_notify) { + TEST_E(e->data->e_event, nua_i_notify); + n_tags = e->data->e_tags; + TEST_1(sip = sip_object(e->data->e_msg)); + TEST_1(sip->sip_event); + TEST_1(sip->sip_subscription_state); + TEST_S(sip->sip_subscription_state->ss_substate, "terminated"); + TEST_1(!sip->sip_subscription_state->ss_expires); + TEST_1(tl_find(n_tags, nutag_substate)); + TEST(tl_find(n_tags, nutag_substate)->t_value, nua_substate_terminated); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_r_unsubscribe); + TEST(e->data->e_status, 200); + r_tags = e->data->e_tags; + TEST_1(tl_find(r_tags, nutag_substate)); + TEST(tl_find(r_tags, nutag_substate)->t_value, nua_substate_terminated); + } + else { + TEST_E(e->data->e_event, nua_r_unsubscribe); + TEST(e->data->e_status, 200); + r_tags = e->data->e_tags; + TEST_1(tl_find(r_tags, nutag_substate)); + TEST(tl_find(r_tags, nutag_substate)->t_value, nua_substate_terminated); + } + TEST_1(!e->next); + + /* Notifier events: nua_r_notify */ + TEST_1(e = b->events->head); + TEST_E(e->data->e_event, nua_i_subscribe); + TEST(e->data->e_status, 200); + TEST_1(sip = sip_object(e->data->e_msg)); + TEST_1(tl_find(e->data->e_tags, nutag_substate)); + TEST(tl_find(e->data->e_tags, nutag_substate)->t_value, + nua_substate_terminated); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_r_notify); + TEST_1(e->data->e_status >= 200); + r_tags = e->data->e_tags; + TEST_1(tl_find(r_tags, nutag_substate)); + TEST(tl_find(r_tags, nutag_substate)->t_value, nua_substate_terminated); +#endif + + free_events_in_list(ctx, a->events); + free_events_in_list(ctx, b->events); + + nua_handle_destroy(a_call->nh), a_call->nh = NULL; + nua_handle_destroy(b_call->nh), b_call->nh = NULL; + + test_nat_remove_filter(ctx->nat, f); + + if (print_headings) + printf("TEST NUA-11.5.2: PASSED\n"); + + END(); +} + +static +size_t change_status_to_483(void *a, void *message, size_t len) +{ + (void)a; + + if (strncmp("SIP/2.0 2", message, 9) == 0) { + memcpy(message, "SIP/2.0 483", 11); + } + return len; +} + +int save_until_notified_and_responded_twice(CONDITION_PARAMS) +{ + save_event_in_list(ctx, event, ep, call); + + if (event == nua_i_notify) { + if (ep->flags.bit0) + ep->flags.bit1 = 1; + ep->flags.bit0 = 1; + } + + if (event == nua_r_subscribe || event == nua_r_unsubscribe) { + if (status >= 300) + return 1; + else if (status >= 200) { + if (ep->flags.bit2) + ep->flags.bit3 = 1; + ep->flags.bit2 = 1; + } + } + + return ep->flags.bit0 && ep->flags.bit1 && ep->flags.bit2 && ep->flags.bit3; +} + +int save_until_notify_responded_twice(CONDITION_PARAMS) +{ + save_event_in_list(ctx, event, ep, call); + + if (event == nua_r_notify) { + if (ep->flags.bit0) + ep->flags.bit1 = 1; + ep->flags.bit0 = 1; + } + + return ep->flags.bit0 && ep->flags.bit1; +} + +/* ---------------------------------------------------------------------- */ + +/* + * When incoming SUBSCRIBE, send NOTIFY, + * 200 OK SUBSCRIBE when NOTIFY has been responded. + */ +int notify_and_accept(CONDITION_PARAMS) +{ + if (!(check_handle(ep, call, nh, SIP_500_INTERNAL_SERVER_ERROR))) + return 0; + + save_event_in_list(ctx, event, ep, call); + + switch (event) { + case nua_i_subscribe: + if (status < 200) { + NOTIFY(ep, call, nh, + SIPTAG_EVENT(sip->sip_event), + SIPTAG_CONTENT_TYPE_STR("application/pidf+xml"), + SIPTAG_PAYLOAD_STR(presence_closed), + TAG_END()); + } + return 0; + + case nua_r_notify: + if (status >= 200) { + struct event *e; + for (e = ep->events->head; e; e = e->next) { + if (e->data->e_event == nua_i_subscribe) { + RESPOND(ep, call, nh, SIP_200_OK, + NUTAG_WITH(e->data->e_msg), + TAG_END()); + break; + } + } + return 1; + } + + default: + return 0; + } +} + +int test_event_fetch(struct context *ctx) +{ + BEGIN(); + + struct endpoint *a = &ctx->a, *b = &ctx->b; + struct call *a_call = a->call, *b_call = b->call; + struct event *e, *en1, *en2, *es; + sip_t const *sip; + tagi_t const *r_tags; + + if (print_headings) + printf("TEST NUA-11.6.1: event fetch using nua_notify()\n"); + + TEST_1(a_call->nh = nua_handle(a->nua, a_call, SIPTAG_TO(b->to), TAG_END())); + +/* Fetch 1: + + A B + | | + |------SUBSCRIBE----->| + | Expires: 0 | + |<---------202--------| + | | + |<-------NOTIFY-------| + | S-State: terminated | + |-------200 OK------->| + | | +*/ + + SUBSCRIBE(a, a_call, a_call->nh, NUTAG_URL(b->contact->m_url), + SIPTAG_EVENT_STR("presence"), + SIPTAG_EXPIRES_STR("0"), + SIPTAG_ACCEPT_STR("application/xpidf, application/pidf+xml"), + TAG_END()); + + run_ab_until(ctx, -1, save_until_notified_and_responded, + -1, accept_and_notify); + + /* Client events: + nua_subscribe(), nua_i_notify/nua_r_subscribe/nua_i_notify + */ + for (en1 = en2 = es = NULL, e = a->events->head; e; e = e->next) { + if (en1 == NULL && e->data->e_event == nua_i_notify) + en1 = e; + else if (en2 == NULL && e->data->e_event == nua_i_notify) + en2 = e; + else if (e->data->e_event == nua_r_subscribe) + es = e; + else + TEST_1(!e); + } + + TEST_1(e = en1); + TEST_E(e->data->e_event, nua_i_notify); + TEST_1(tl_find(e->data->e_tags, nutag_substate)); + TEST(tl_find(e->data->e_tags, nutag_substate)->t_value, nua_substate_terminated); + TEST_1(sip = sip_object(e->data->e_msg)); + TEST_1(sip->sip_event); TEST_S(sip->sip_event->o_type, "presence"); + TEST_1(sip->sip_content_type); + TEST_S(sip->sip_content_type->c_type, "application/pidf+xml"); + TEST_1(sip->sip_subscription_state); + TEST_S(sip->sip_subscription_state->ss_substate, "terminated"); + + TEST_1(e = es); TEST_E(e->data->e_event, nua_r_subscribe); + TEST_1(e->data->e_status == 202 || e->data->e_status == 200); + TEST_1(tl_find(e->data->e_tags, nutag_substate)); + + if (es == a->events->head) + TEST(tl_find(e->data->e_tags, nutag_substate)->t_value, nua_substate_embryonic); + else + TEST(tl_find(e->data->e_tags, nutag_substate)->t_value, nua_substate_terminated); + TEST_1(sip = sip_object(e->data->e_msg)); + TEST_1(sip->sip_expires); + TEST_1(sip->sip_expires->ex_delta == 0); + + free_events_in_list(ctx, a->events); + + /* Server events: nua_i_subscribe, nua_r_notify */ + TEST_1(e = b->events->head); + TEST_E(e->data->e_event, nua_i_subscribe); + TEST_E(e->data->e_status, 100); + TEST_1(sip = sip_object(e->data->e_msg)); + + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_r_notify); + r_tags = e->data->e_tags; + TEST_1(tl_find(r_tags, nutag_substate)); + TEST(tl_find(r_tags, nutag_substate)->t_value, nua_substate_terminated); + + free_events_in_list(ctx, b->events); + + if (print_headings) + printf("TEST NUA-11.6.1: PASSED\n"); + + nua_handle_destroy(a_call->nh), a_call->nh = NULL; + nua_handle_destroy(b_call->nh), b_call->nh = NULL; + + + if (print_headings) + printf("TEST NUA-11.6.2: event fetch, NOTIFY comes before 202\n"); + + TEST_1(a_call->nh = nua_handle(a->nua, a_call, SIPTAG_TO(b->to), TAG_END())); + +/* Fetch 2: + + A B + | | + |------SUBSCRIBE----->| + | Expires: 0 | + |<-------NOTIFY-------| + | S-State: terminated | + |-------200 OK------->| + | | + |<---------202--------| + | | +*/ + + SUBSCRIBE(a, a_call, a_call->nh, NUTAG_URL(b->contact->m_url), + SIPTAG_EVENT_STR("presence"), + SIPTAG_EXPIRES_STR("0"), + SIPTAG_ACCEPT_STR("application/xpidf, application/pidf+xml"), + TAG_END()); + + run_ab_until(ctx, -1, save_until_notified_and_responded, + -1, notify_and_accept); + + /* Client events: + nua_subscribe(), nua_i_notify/nua_r_subscribe/nua_i_notify + */ + for (en1 = en2 = es = NULL, e = a->events->head; e; e = e->next) { + if (en1 == NULL && e->data->e_event == nua_i_notify) + en1 = e; + else if (en2 == NULL && e->data->e_event == nua_i_notify) + en2 = e; + else if (e->data->e_event == nua_r_subscribe) + es = e; + else + TEST_1(!e); + } + + TEST_1(e = en1); + TEST_E(e->data->e_event, nua_i_notify); + TEST_1(tl_find(e->data->e_tags, nutag_substate)); + TEST(tl_find(e->data->e_tags, nutag_substate)->t_value, nua_substate_terminated); + TEST_1(sip = sip_object(e->data->e_msg)); + TEST_1(sip->sip_event); TEST_S(sip->sip_event->o_type, "presence"); + TEST_1(sip->sip_content_type); + TEST_S(sip->sip_content_type->c_type, "application/pidf+xml"); + TEST_1(sip->sip_subscription_state); + TEST_S(sip->sip_subscription_state->ss_substate, "terminated"); + + TEST_1(e = es); TEST_E(e->data->e_event, nua_r_subscribe); + TEST_1(e->data->e_status == 202 || e->data->e_status == 200); + TEST_1(tl_find(e->data->e_tags, nutag_substate)); + + if (es == a->events->head) + TEST(tl_find(e->data->e_tags, nutag_substate)->t_value, nua_substate_embryonic); + else + TEST(tl_find(e->data->e_tags, nutag_substate)->t_value, nua_substate_terminated); + TEST_1(sip = sip_object(e->data->e_msg)); + TEST_1(sip->sip_expires); + TEST_1(sip->sip_expires->ex_delta == 0); + + free_events_in_list(ctx, a->events); + + /* Server events: nua_i_subscribe, nua_r_notify */ + TEST_1(e = b->events->head); + TEST_E(e->data->e_event, nua_i_subscribe); + TEST_E(e->data->e_status, 100); + TEST_1(sip = sip_object(e->data->e_msg)); + + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_r_notify); + r_tags = e->data->e_tags; + TEST_1(tl_find(r_tags, nutag_substate)); + TEST(tl_find(r_tags, nutag_substate)->t_value, nua_substate_terminated); + + free_events_in_list(ctx, b->events); + + if (print_headings) + printf("TEST NUA-11.6.2: PASSED\n"); + + nua_handle_destroy(a_call->nh), a_call->nh = NULL; + nua_handle_destroy(b_call->nh), b_call->nh = NULL; + + END(); +} + +/* ---------------------------------------------------------------------- */ +/* Unsolicited NOTIFY */ + +int accept_notify(CONDITION_PARAMS); + +int test_newsub_notify(struct context *ctx) +{ + BEGIN(); + + struct endpoint *a = &ctx->a, *b = &ctx->b; + struct call *a_call = a->call, *b_call = b->call; + struct event *e; + sip_t const *sip; + sip_call_id_t *i; + tagi_t const *n_tags, *r_tags; + +#if 0 + + if (print_headings) + printf("TEST NUA-11.7.1: rejecting NOTIFY without subscription locally\n"); + + TEST_1(a_call->nh = nua_handle(a->nua, a_call, SIPTAG_TO(b->to), TAG_END())); + + NOTIFY(a, a_call, a_call->nh, NUTAG_URL(b->contact->m_url), + SIPTAG_SUBJECT_STR("NUA-11.7.1"), + SIPTAG_EVENT_STR("message-summary"), + SIPTAG_CONTENT_TYPE_STR("application/simple-message-summary"), + SIPTAG_PAYLOAD_STR("Messages-Waiting: yes"), + TAG_END()); + + run_a_until(ctx, -1, save_until_final_response); + + /* Client events: + nua_notify(), nua_r_notify + */ + TEST_1(e = a->events->head); + TEST_E(e->data->e_event, nua_r_notify); + TEST(e->data->e_status, 481); + TEST_1(!e->data->e_msg); + TEST_1(!e->next); + free_events_in_list(ctx, a->events); + + if (print_headings) + printf("TEST NUA-11.7.1: PASSED\n"); + + if (print_headings) + printf("TEST NUA-11.7.2: rejecting NOTIFY without subscription\n"); + + TEST_1(i = sip_call_id_create(nua_handle_home(a_call->nh), NULL)); + + NOTIFY(a, a_call, a_call->nh, NUTAG_URL(b->contact->m_url), + NUTAG_NEWSUB(1), + SIPTAG_SUBJECT_STR("NUA-11.7.2 first"), + SIPTAG_FROM_STR("<sip:alice@example.com>;tag=nua-11.7.2"), + SIPTAG_CALL_ID(i), + SIPTAG_CSEQ_STR("1 NOTIFY"), + SIPTAG_EVENT_STR("message-summary"), + SIPTAG_CONTENT_TYPE_STR("application/simple-message-summary"), + SIPTAG_PAYLOAD_STR("Messages-Waiting: yes"), + TAG_END()); + + run_a_until(ctx, -1, save_until_final_response); + + /* Client events: + nua_notify(), nua_r_notify + */ + TEST_1(e = a->events->head); + TEST_E(e->data->e_event, nua_r_notify); + TEST(e->data->e_status, 481); + TEST_1(e->data->e_msg); + TEST_1(!e->next); + + free_events_in_list(ctx, a->events); + + /* 2nd NOTIFY using same dialog */ + /* Check that server really discards the dialog */ + + NOTIFY(a, a_call, a_call->nh, NUTAG_URL(b->contact->m_url), + NUTAG_NEWSUB(1), + SIPTAG_SUBJECT_STR("NUA-11.7.2 second"), + SIPTAG_FROM_STR("<sip:alice@example.com>;tag=nua-11.7.2"), + SIPTAG_CALL_ID(i), + SIPTAG_CSEQ_STR("2 NOTIFY"), + SIPTAG_EVENT_STR("message-summary"), + SIPTAG_CONTENT_TYPE_STR("application/simple-message-summary"), + SIPTAG_PAYLOAD_STR("Messages-Waiting: yes"), + TAG_END()); + + run_a_until(ctx, -1, save_until_final_response); + + /* Client events: + nua_notify(), nua_r_notify + */ + TEST_1(e = a->events->head); + TEST_E(e->data->e_event, nua_r_notify); + TEST(e->data->e_status, 481); + TEST_1(e->data->e_msg); + TEST_1(!e->next); + + free_events_in_list(ctx, a->events); + + if (print_headings) + printf("TEST NUA-11.7.2: PASSED\n"); + + /* ---------------------------------------------------------------------- */ + + if (print_headings) + printf("TEST NUA-11.7.3: accept NOTIFY\n"); + + nua_set_params(b->nua, NUTAG_APPL_METHOD("NOTIFY"), TAG_END()); + run_b_until(ctx, nua_r_set_params, until_final_response); + + NOTIFY(a, a_call, a_call->nh, + NUTAG_URL(b->contact->m_url), + NUTAG_NEWSUB(1), + SIPTAG_SUBJECT_STR("NUA-11.7.3"), + SIPTAG_EVENT_STR("message-summary"), + SIPTAG_CONTENT_TYPE_STR("application/simple-message-summary"), + SIPTAG_PAYLOAD_STR("Messages-Waiting: yes"), + TAG_END()); + + run_ab_until(ctx, -1, save_until_final_response, -1, accept_notify); + + /* Notifier events: nua_r_notify */ + TEST_1(e = a->events->head); TEST_E(e->data->e_event, nua_r_notify); + TEST(e->data->e_status, 200); + r_tags = e->data->e_tags; + TEST_1(tl_find(r_tags, nutag_substate)); + TEST(tl_find(r_tags, nutag_substate)->t_value, nua_substate_terminated); + + /* subscriber events: + nua_i_notify + */ + TEST_1(e = b->events->head); TEST_E(e->data->e_event, nua_i_notify); + TEST_1(sip = sip_object(e->data->e_msg)); + TEST_1(sip->sip_subscription_state); + TEST_S(sip->sip_subscription_state->ss_substate, "terminated"); + n_tags = e->data->e_tags; + TEST_1(tl_find(n_tags, nutag_substate)); + TEST(tl_find(n_tags, nutag_substate)->t_value, nua_substate_terminated); + TEST_1(!e->next); + + free_events_in_list(ctx, a->events); + free_events_in_list(ctx, b->events); + + if (print_headings) + printf("TEST NUA-11.7.3: PASSED\n"); + + nua_handle_destroy(a_call->nh), a_call->nh = NULL; + nua_handle_destroy(b_call->nh), b_call->nh = NULL; + +#else + (void)i; + nua_set_params(b->nua, NUTAG_APPL_METHOD("NOTIFY"), TAG_END()); + run_b_until(ctx, nua_r_set_params, until_final_response); +#endif + + /* ---------------------------------------------------------------------- */ + + if (print_headings) + printf("TEST NUA-11.7.4: multiple unsolicited NOTIFYs\n"); + + TEST_1(a_call->nh = nua_handle(a->nua, a_call, SIPTAG_TO(b->to), TAG_END())); + + NOTIFY(a, a_call, a_call->nh, + NUTAG_URL(b->contact->m_url), + NUTAG_NEWSUB(1), + SIPTAG_EXPIRES_STR("10"), + SIPTAG_SUBJECT_STR("NUA-11.7.4aa"), + SIPTAG_CONTENT_TYPE_STR("application/simple-message-summary"), + SIPTAG_PAYLOAD_STR("Messages-Waiting: no"), + TAG_END()); + + NOTIFY(a, a_call, a_call->nh, + NUTAG_URL(b->contact->m_url), + NUTAG_NEWSUB(1), + SIPTAG_SUBSCRIPTION_STATE_STR("active; expires=333"), + SIPTAG_SUBJECT_STR("NUA-11.7.4a"), + SIPTAG_EVENT_STR("message-summary"), + SIPTAG_CONTENT_TYPE_STR("application/simple-message-summary"), + SIPTAG_PAYLOAD_STR("Messages-Waiting: no"), + TAG_END()); + + NOTIFY(a, a_call, a_call->nh, + NUTAG_URL(b->contact->m_url), + NUTAG_NEWSUB(1), + SIPTAG_SUBSCRIPTION_STATE_STR("active; expires=3000"), + SIPTAG_SUBJECT_STR("NUA-11.7.4b"), + SIPTAG_EVENT_STR("message-summary"), + SIPTAG_CONTENT_TYPE_STR("application/simple-message-summary"), + SIPTAG_PAYLOAD_STR("Messages-Waiting: yes"), + TAG_END()); + + NOTIFY(a, a_call, a_call->nh, + NUTAG_URL(b->contact->m_url), + NUTAG_NEWSUB(1), + SIPTAG_SUBSCRIPTION_STATE_STR("terminated"), + SIPTAG_SUBJECT_STR("NUA-11.7.4c"), + SIPTAG_EVENT_STR("message-summary"), + SIPTAG_CONTENT_TYPE_STR("application/simple-message-summary"), + SIPTAG_PAYLOAD_STR("Messages-Waiting: yes"), + TAG_END()); + + a->state.n = 4; + b->state.n = 4; + + run_ab_until(ctx, -1, save_until_nth_final_response, + -1, accept_n_notifys); + + /* Notifier events: nua_r_notify nua_r_notify */ + TEST_1(e = a->events->head); TEST_E(e->data->e_event, nua_r_notify); + TEST(e->data->e_status, 200); + r_tags = e->data->e_tags; + TEST_1(tl_find(r_tags, nutag_substate)); + TEST(tl_find(r_tags, nutag_substate)->t_value, nua_substate_terminated); + + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_r_notify); + TEST(e->data->e_status, 200); + r_tags = e->data->e_tags; + TEST_1(tl_find(r_tags, nutag_substate)); + TEST(tl_find(r_tags, nutag_substate)->t_value, nua_substate_active); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_r_notify); + TEST(e->data->e_status, 200); + r_tags = e->data->e_tags; + TEST_1(tl_find(r_tags, nutag_substate)); + TEST(tl_find(r_tags, nutag_substate)->t_value, nua_substate_active); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_r_notify); + TEST(e->data->e_status, 200); + r_tags = e->data->e_tags; + TEST_1(tl_find(r_tags, nutag_substate)); + TEST(tl_find(r_tags, nutag_substate)->t_value, nua_substate_terminated); + TEST_1(!e->next); + + /* subscriber events: + nua_i_notify + */ + TEST_1(e = b->events->head); TEST_E(e->data->e_event, nua_i_notify); + TEST_1(sip = sip_object(e->data->e_msg)); + TEST_1(sip->sip_subscription_state); + TEST_S(sip->sip_subscription_state->ss_substate, "terminated"); + n_tags = e->data->e_tags; + TEST_1(tl_find(n_tags, nutag_substate)); + TEST(tl_find(n_tags, nutag_substate)->t_value, nua_substate_terminated); + + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_notify); + TEST_1(sip = sip_object(e->data->e_msg)); + TEST_1(sip->sip_subscription_state); + TEST_S(sip->sip_subscription_state->ss_substate, "active"); + n_tags = e->data->e_tags; + TEST_1(tl_find(n_tags, nutag_substate)); + TEST(tl_find(n_tags, nutag_substate)->t_value, nua_substate_active); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_notify); + TEST_1(sip = sip_object(e->data->e_msg)); + TEST_1(sip->sip_subscription_state); + TEST_S(sip->sip_subscription_state->ss_substate, "active"); + n_tags = e->data->e_tags; + TEST_1(tl_find(n_tags, nutag_substate)); + TEST(tl_find(n_tags, nutag_substate)->t_value, nua_substate_active); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_notify); + TEST_1(sip = sip_object(e->data->e_msg)); + TEST_1(sip->sip_subscription_state); + TEST_S(sip->sip_subscription_state->ss_substate, "terminated"); + n_tags = e->data->e_tags; + TEST_1(tl_find(n_tags, nutag_substate)); + TEST(tl_find(n_tags, nutag_substate)->t_value, nua_substate_terminated); + TEST_1(!e->next); + + free_events_in_list(ctx, a->events); + free_events_in_list(ctx, b->events); + + nua_handle_destroy(a_call->nh), a_call->nh = NULL; + nua_handle_destroy(b_call->nh), b_call->nh = NULL; + + if (print_headings) + printf("TEST NUA-11.7.4: PASSED\n"); + + /* ---------------------------------------------------------------------- */ + + if (print_headings) + printf("TEST NUA-11.7.5: multiple unsolicited NOTIFYs\n"); + + TEST_1(a_call->nh = nua_handle(a->nua, a_call, SIPTAG_TO(b->to), TAG_END())); + + NOTIFY(a, a_call, a_call->nh, + NUTAG_URL(b->contact->m_url), + NUTAG_NEWSUB(1), + SIPTAG_SUBSCRIPTION_STATE_STR("active; expires=333"), + SIPTAG_SUBJECT_STR("NUA-11.7.5a"), + SIPTAG_EVENT_STR("message-summary"), + SIPTAG_CONTENT_TYPE_STR("application/simple-message-summary"), + SIPTAG_PAYLOAD_STR("Messages-Waiting: no"), + TAG_END()); + + NOTIFY(a, a_call, a_call->nh, + NUTAG_URL(b->contact->m_url), + NUTAG_NEWSUB(1), + SIPTAG_SUBSCRIPTION_STATE_STR("active; expires=3000"), + SIPTAG_SUBJECT_STR("NUA-11.7.5b"), + SIPTAG_EVENT_STR("message-summary"), + SIPTAG_CONTENT_TYPE_STR("application/simple-message-summary"), + SIPTAG_PAYLOAD_STR("Messages-Waiting: yes"), + TAG_END()); + + NOTIFY(a, a_call, a_call->nh, + NUTAG_URL(b->contact->m_url), + NUTAG_NEWSUB(1), + SIPTAG_SUBSCRIPTION_STATE_STR("terminated"), + SIPTAG_SUBJECT_STR("NUA-11.7.5c"), + SIPTAG_EVENT_STR("message-summary"), + SIPTAG_CONTENT_TYPE_STR("application/simple-message-summary"), + SIPTAG_PAYLOAD_STR("Messages-Waiting: yes"), + TAG_END()); + + a->state.n = 3; + b->state.n = 3; + + run_ab_until(ctx, -1, save_until_nth_final_response, + -1, accept_n_notifys); + + /* Notifier events: nua_r_notify nua_r_notify */ + TEST_1(e = a->events->head); TEST_E(e->data->e_event, nua_r_notify); + TEST(e->data->e_status, 200); + r_tags = e->data->e_tags; + TEST_1(tl_find(r_tags, nutag_substate)); + TEST(tl_find(r_tags, nutag_substate)->t_value, nua_substate_active); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_r_notify); + TEST(e->data->e_status, 200); + r_tags = e->data->e_tags; + TEST_1(tl_find(r_tags, nutag_substate)); + TEST(tl_find(r_tags, nutag_substate)->t_value, nua_substate_active); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_r_notify); + TEST(e->data->e_status, 200); + r_tags = e->data->e_tags; + TEST_1(tl_find(r_tags, nutag_substate)); + TEST(tl_find(r_tags, nutag_substate)->t_value, nua_substate_terminated); + TEST_1(!e->next); + + /* subscriber events: + nua_i_notify + */ + TEST_1(e = b->events->head); TEST_E(e->data->e_event, nua_i_notify); + TEST_1(sip = sip_object(e->data->e_msg)); + TEST_1(sip->sip_subscription_state); + TEST_S(sip->sip_subscription_state->ss_substate, "active"); + n_tags = e->data->e_tags; + TEST_1(tl_find(n_tags, nutag_substate)); + TEST(tl_find(n_tags, nutag_substate)->t_value, nua_substate_active); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_notify); + TEST_1(sip = sip_object(e->data->e_msg)); + TEST_1(sip->sip_subscription_state); + TEST_S(sip->sip_subscription_state->ss_substate, "active"); + n_tags = e->data->e_tags; + TEST_1(tl_find(n_tags, nutag_substate)); + TEST(tl_find(n_tags, nutag_substate)->t_value, nua_substate_active); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_notify); + TEST_1(sip = sip_object(e->data->e_msg)); + TEST_1(sip->sip_subscription_state); + TEST_S(sip->sip_subscription_state->ss_substate, "terminated"); + n_tags = e->data->e_tags; + TEST_1(tl_find(n_tags, nutag_substate)); + TEST(tl_find(n_tags, nutag_substate)->t_value, nua_substate_terminated); + TEST_1(!e->next); + + free_events_in_list(ctx, a->events); + free_events_in_list(ctx, b->events); + + nua_handle_destroy(a_call->nh), a_call->nh = NULL; + nua_handle_destroy(b_call->nh), b_call->nh = NULL; + + if (print_headings) + printf("TEST NUA-11.7.5: PASSED\n"); + + /* ---------------------------------------------------------------------- */ + +#if 0 + + if (print_headings) + printf("TEST NUA-11.7.6: unsolicited NOTIFY handle destroyed\n"); + + TEST_1(a_call->nh = nua_handle(a->nua, a_call, SIPTAG_TO(b->to), TAG_END())); + + NOTIFY(a, a_call, a_call->nh, + NUTAG_URL(b->contact->m_url), + NUTAG_NEWSUB(1), + SIPTAG_SUBSCRIPTION_STATE_STR("active; expires=333"), + SIPTAG_SUBJECT_STR("NUA-11.7.6a"), + SIPTAG_EVENT_STR("message-summary"), + SIPTAG_CONTENT_TYPE_STR("application/simple-message-summary"), + SIPTAG_PAYLOAD_STR("Messages-Waiting: no"), + TAG_END()); + + NOTIFY(a, a_call, a_call->nh, + NUTAG_URL(b->contact->m_url), + NUTAG_NEWSUB(1), + SIPTAG_SUBSCRIPTION_STATE_STR("active; expires=3000"), + SIPTAG_SUBJECT_STR("NUA-11.7.6b"), + SIPTAG_EVENT_STR("message-summary"), + SIPTAG_CONTENT_TYPE_STR("application/simple-message-summary"), + SIPTAG_PAYLOAD_STR("Messages-Waiting: yes"), + TAG_END()); + + nua_handle_destroy(a_call->nh), a_call->nh = NULL; + + a->state.n = 3; + b->state.n = 3; + + run_b_until(ctx, -1, accept_n_notifys); + + /* subscriber events: + nua_i_notify + */ + TEST_1(e = b->events->head); TEST_E(e->data->e_event, nua_i_notify); + TEST_1(sip = sip_object(e->data->e_msg)); + TEST_1(sip->sip_subscription_state); + TEST_S(sip->sip_subscription_state->ss_substate, "active"); + n_tags = e->data->e_tags; + TEST_1(tl_find(n_tags, nutag_substate)); + TEST(tl_find(n_tags, nutag_substate)->t_value, nua_substate_active); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_notify); + TEST_1(sip = sip_object(e->data->e_msg)); + TEST_1(sip->sip_subscription_state); + TEST_S(sip->sip_subscription_state->ss_substate, "active"); + n_tags = e->data->e_tags; + TEST_1(tl_find(n_tags, nutag_substate)); + TEST(tl_find(n_tags, nutag_substate)->t_value, nua_substate_active); + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_notify); + TEST_1(sip = sip_object(e->data->e_msg)); + TEST_1(sip->sip_subscription_state); + TEST_S(sip->sip_subscription_state->ss_substate, "terminated"); + n_tags = e->data->e_tags; + TEST_1(tl_find(n_tags, nutag_substate)); + TEST(tl_find(n_tags, nutag_substate)->t_value, nua_substate_terminated); + TEST_1(!e->next); + + free_events_in_list(ctx, b->events); + + if (print_headings) + printf("TEST NUA-11.7.6: PASSED\n"); + + nua_handle_destroy(b_call->nh), b_call->nh = NULL; + +#endif + + if (print_headings) + printf("TEST NUA-11.7: PASSED\n"); + + END(); +} + +/**Terminate when received notify. + * Respond to NOTIFY with 200 OK if it has not been responded. + * Save events (except nua_i_active or terminated). + */ +int accept_notify(CONDITION_PARAMS) +{ + if (event == nua_i_notify && status < 200) + RESPOND(ep, call, nh, SIP_200_OK, + NUTAG_WITH_THIS(ep->nua), + TAG_END()); + + save_event_in_list(ctx, event, ep, call); + + return event == nua_i_notify; +} + +int save_until_nth_final_response(CONDITION_PARAMS) +{ + save_event_in_list(ctx, event, ep, call); + + if (nua_r_set_params <= event && event < nua_i_network_changed + && status >= 200) { + if (ep->state.n > 0) + ep->state.n--; + return ep->state.n == 0; + } + + return 0; +} + +int accept_n_notifys(CONDITION_PARAMS) +{ + tagi_t const *substate = tl_find(tags, nutag_substate); + + if (event == nua_i_notify && status < 200) + RESPOND(ep, call, nh, SIP_200_OK, + NUTAG_WITH_THIS(ep->nua), + TAG_END()); + + save_event_in_list(ctx, event, ep, call); + + if (event != nua_i_notify) + return 0; + + if (ep->state.n > 0) + ep->state.n--; + + if (ep->state.n == 0) + return 1; + + if (substate && substate->t_value == nua_substate_terminated) { + if (call && call->nh == nh) { + call->nh = NULL; + nua_handle_destroy(nh); + } + } + + return 0; +} + +/* ======================================================================== */ + +int save_until_subscription_terminated(CONDITION_PARAMS); +int accept_subscription_until_terminated(CONDITION_PARAMS); + +/* Timeout subscription */ +int test_subscription_timeout(struct context *ctx) +{ + BEGIN(); + + struct endpoint *a = &ctx->a, *b = &ctx->b; + struct call *a_call = a->call, *b_call = b->call; + struct event *e, *en, *es; + sip_t const *sip; + tagi_t const *n_tags, *r_tags; + + if (print_headings) + printf("TEST NUA-11.8: subscribe and wait until subscription times out\n"); + + TEST_1(a_call->nh = nua_handle(a->nua, a_call, SIPTAG_TO(b->to), TAG_END())); + + METHOD(a, a_call, a_call->nh, + NUTAG_METHOD("SUBSCRIBE"), + NUTAG_URL(b->contact->m_url), + SIPTAG_EVENT_STR("presence"), + SIPTAG_ACCEPT_STR("application/xpidf, application/pidf+xml"), + SIPTAG_EXPIRES_STR("2"), + NUTAG_APPL_METHOD("NOTIFY"), + NUTAG_DIALOG(2), + TAG_END()); + + run_ab_until(ctx, + -1, save_until_subscription_terminated, + -1, accept_subscription_until_terminated); + + /* Client events: + nua_method(), nua_i_notify/nua_r_method, nua_i_notify + */ + TEST_1(en = event_by_type(a->events->head, nua_i_notify)); + TEST_1(es = event_by_type(a->events->head, nua_r_method)); + + TEST_1(e = en); TEST_E(e->data->e_event, nua_i_notify); + TEST_1(sip = sip_object(e->data->e_msg)); + n_tags = e->data->e_tags; + + TEST_1(e = es); TEST_E(e->data->e_event, nua_r_method); + r_tags = e->data->e_tags; + + TEST_1(sip->sip_event); TEST_S(sip->sip_event->o_type, "presence"); + TEST_1(sip->sip_content_type); + TEST_S(sip->sip_content_type->c_type, "application/pidf+xml"); + TEST_1(sip->sip_subscription_state); + TEST_S(sip->sip_subscription_state->ss_substate, "pending"); + TEST_1(sip->sip_subscription_state->ss_expires); + TEST_1(tl_find(n_tags, nutag_substate)); + TEST(tl_find(n_tags, nutag_substate)->t_value, nua_substate_pending); + + if (es->next == en) + e = en->next; + else + e = es->next; + + TEST_1(e); TEST_E(e->data->e_event, nua_i_notify); + n_tags = e->data->e_tags; + TEST_1(tl_find(n_tags, nutag_substate)); + TEST(tl_find(n_tags, nutag_substate)->t_value, nua_substate_terminated); + TEST_1(sip = sip_object(e->data->e_msg)); + TEST_1(sip->sip_event); + TEST_1(sip->sip_subscription_state); + TEST_S(sip->sip_subscription_state->ss_substate, "terminated"); + TEST_1(!sip->sip_subscription_state->ss_expires); + TEST_1(!e->next); + + free_events_in_list(ctx, a->events); + + /* Server events: nua_i_subscribe, nua_r_notify */ + TEST_1(e = b->events->head); + TEST_E(e->data->e_event, nua_i_subscribe); + TEST_E(e->data->e_status, 100); + TEST_1(sip = sip_object(e->data->e_msg)); + + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_r_notify); + r_tags = e->data->e_tags; + TEST_1(tl_find(r_tags, nutag_substate)); + TEST(tl_find(r_tags, nutag_substate)->t_value, nua_substate_pending); + + /* Notifier events: 2nd nua_r_notify */ + TEST_1(e = e->next); TEST_E(e->data->e_event, nua_r_notify); + TEST_1(e->data->e_status >= 200); + r_tags = e->data->e_tags; + TEST_1(tl_find(r_tags, nutag_substate)); + TEST(tl_find(r_tags, nutag_substate)->t_value, nua_substate_terminated); + + free_events_in_list(ctx, b->events); + + nua_handle_destroy(a_call->nh), a_call->nh = NULL; + nua_handle_destroy(b_call->nh), b_call->nh = NULL; + + if (print_headings) + printf("TEST NUA-11.8: PASSED\n"); + + END(); +} + +int save_until_subscription_terminated(CONDITION_PARAMS) +{ + void *with = nua_current_request(nua); + + save_event_in_list(ctx, event, ep, call); + + if (event == nua_i_notify) { + if (status < 200) + RESPOND(ep, call, nh, SIP_200_OK, NUTAG_WITH(with), TAG_END()); + + tags = tl_find(tags, nutag_substate); + return tags && tags->t_value == nua_substate_terminated; + } + + return 0; +} + +int accept_subscription_until_terminated(CONDITION_PARAMS) +{ + void *with = nua_current_request(nua); + + save_event_in_list(ctx, event, ep, call); + + if (event == nua_i_subscribe && status < 200) { + RESPOND(ep, call, nh, SIP_202_ACCEPTED, + NUTAG_WITH(with), + SIPTAG_EXPIRES_STR("360"), + TAG_END()); + NOTIFY(ep, call, nh, + SIPTAG_EVENT(sip->sip_event), + SIPTAG_CONTENT_TYPE_STR("application/pidf+xml"), + SIPTAG_PAYLOAD_STR(presence_closed), + NUTAG_SUBSTATE(nua_substate_pending), + TAG_END()); + } + else if (event == nua_r_notify) { + tags = tl_find(tags, nutag_substate); + return tags && tags->t_value == nua_substate_terminated; + } + + return 0; +} + + +/* ======================================================================== */ +/* Test simple methods: MESSAGE, PUBLISH, SUBSCRIBE/NOTIFY */ + +int test_simple(struct context *ctx) +{ + return 0 + || test_message(ctx) + || test_publish(ctx) + || test_subscribe_notify(ctx) + || test_event_fetch(ctx) + || test_subscribe_notify_graceful(ctx) + || test_newsub_notify(ctx) + || test_subscription_timeout(ctx) + ; +} Added: freeswitch/trunk/libs/sofia-sip/tests/test_sip_events.c ============================================================================== --- (empty file) +++ freeswitch/trunk/libs/sofia-sip/tests/test_sip_events.c Wed May 14 15:10:54 2008 @@ -0,0 +1,601 @@ +/* + * This file is part of the Sofia-SIP package + * + * Copyright (C) 2005 Nokia Corporation. + * + * Contact: Pekka Pessi <pekka.pessi@nokia.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public License + * as published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + */ + +/**@CFILE test_sip_events.c + * @brief NUA-12 tests: SUBSCRIBE/NOTIFY. + * + * @author Pekka Pessi <Pekka.Pessi@nokia.com> + * @author Martti Mela <Martti Mela@nokia.com> + * + * @date Created: Wed Aug 17 12:12:12 EEST 2005 ppessi + */ + +#include "config.h" + +#include "test_nua.h" +#include <sofia-sip/su_tag_class.h> +#include <sofia-sip/nea.h> + +#if !HAVE_MEMMEM +void *memmem(const void *haystack, size_t haystacklen, + const void *needle, size_t needlelen); +#endif + +#if HAVE_FUNC +#elif HAVE_FUNCTION +#define __func__ __FUNCTION__ +#else +#define __func__ "test_sip_events" +#endif + +/* ======================================================================== */ +/* Test events methods: SUBSCRIBE/NOTIFY */ + +/**Terminate until received notify. + * Save events (except nua_i_active or terminated). + */ +int save_until_notified(CONDITION_PARAMS) +{ + save_event_in_list(ctx, event, ep, call); + return event == nua_i_notify; +} + +int save_until_notified_and_responded(CONDITION_PARAMS) +{ + save_event_in_list(ctx, event, ep, call); + + if (event == nua_i_notify) ep->flags.bit0 = 1; + if (event == nua_r_subscribe || event == nua_r_unsubscribe) { + if (status == 407) { + AUTHENTICATE(ep, call, nh, + NUTAG_AUTH("Digest:\"test-proxy\":charlie:secret"), + TAG_END()); + } + else if (status >= 300) + return 1; + else if (status >= 200) + ep->flags.bit1 = 1; + } + + return ep->flags.bit0 && ep->flags.bit1; +} + + +int save_until_subscription(CONDITION_PARAMS) +{ + save_event_in_list(ctx, event, ep, call); + return event == nua_i_subscription; +} + + +int test_events(struct context *ctx) +{ + BEGIN(); + + struct endpoint *a = &ctx->a, *b = &ctx->b; + struct call *a_call = a->call, *b_call = b->call; + struct event *e, *en, *es; + sip_t const *sip; + tagi_t const *t, *n_tags, *r_tags; + url_t b_url[1]; + enum nua_substate substate; + nea_sub_t *sub = NULL; + + char const open[] = + "<?xml version='1.0' encoding='UTF-8'?>\n" + "<presence xmlns='urn:ietf:params:xml:ns:cpim-pidf' \n" + " entity='pres:bob@example.org'>\n" + " <tuple id='ksac9udshce'>\n" + " <status><basic>open</basic></status>\n" + " <contact priority='1.0'>sip:bob@example.org</contact>\n" + " </tuple>\n" + "</presence>\n"; + + char const closed[] = + "<?xml version='1.0' encoding='UTF-8'?>\n" + "<presence xmlns='urn:ietf:params:xml:ns:cpim-pidf' \n" + " entity='pres:bob@example.org'>\n" + " <tuple id='ksac9udshce'>\n" + " <status><basic>closed</basic></status>\n" + " </tuple>\n" + "</presence>\n"; + + +/* SUBSCRIBE test + + A B + |------SUBSCRIBE---->| + |<--------405--------| + | | + +*/ + if (print_headings) + printf("TEST NUA-12.1: SUBSCRIBE without notifier\n"); + + nua_set_params(b->nua, SIPTAG_ALLOW_EVENTS(NULL), TAG_END()); + run_b_until(ctx, nua_r_set_params, until_final_response); + + TEST_1(a_call->nh = nua_handle(a->nua, a_call, SIPTAG_TO(b->to), TAG_END())); + + SUBSCRIBE(a, a_call, a_call->nh, NUTAG_URL(b->contact->m_url), + SIPTAG_EVENT_STR("presence"), + TAG_END()); + + run_ab_until(ctx, -1, save_until_final_response, + -1, NULL /* XXX save_until_received */); + + /* Client events: + nua_subscribe(), nua_r_subscribe + */ + TEST_1(e = a->events->head); TEST_E(e->data->e_event, nua_r_subscribe); + TEST(e->data->e_status, 489); + TEST_1(!e->next); + +#if 0 /* XXX */ + /* + Server events: + nua_i_subscribe + */ + TEST_1(e = b->events->head); + TEST_E(e->data->e_event, nua_i_subscribe); + TEST(e->data->e_status, 405); + TEST_1(sip = sip_object(e->data->e_msg)); + TEST_1(sip->sip_event); + TEST_S(sip->sip_event->o_type, "presence"); + TEST_1(!e->next); +#endif + + free_events_in_list(ctx, a->events); + free_events_in_list(ctx, b->events); + nua_handle_destroy(b_call->nh), b_call->nh = NULL; + + if (print_headings) + printf("TEST NUA-12.1: PASSED\n"); + + /* ---------------------------------------------------------------------- */ + +/* SUBSCRIBE test using notifier and establishing subscription + + A B + | | + |------SUBSCRIBE---->| + |<--------202--------| + |<------NOTIFY-------| + |-------200 OK------>| + | | +*/ + + if (print_headings) + printf("TEST NUA-12.2: using notifier and establishing subscription\n"); + + TEST_1(b_call->nh = nua_handle(b->nua, b_call, TAG_END())); + + *b_url = *b->contact->m_url; + + NOTIFIER(b, b_call, b_call->nh, + NUTAG_URL(b_url), + SIPTAG_EVENT_STR("presence"), + SIPTAG_CONTENT_TYPE_STR("application/pidf+xml"), + SIPTAG_PAYLOAD_STR(closed), + NEATAG_THROTTLE(1), + TAG_END()); + run_b_until(ctx, nua_r_notifier, until_final_response); + + SUBSCRIBE(a, a_call, a_call->nh, NUTAG_URL(b->contact->m_url), + SIPTAG_EVENT_STR("presence"), + SIPTAG_ACCEPT_STR("application/xpidf, application/pidf+xml"), + TAG_END()); + + run_ab_until(ctx, -1, save_until_notified_and_responded, + -1, save_until_received); + + /* Client events: + nua_subscribe(), nua_i_notify/nua_r_subscribe + */ + TEST_1(en = event_by_type(a->events->head, nua_i_notify)); + TEST_1(es = event_by_type(a->events->head, nua_r_subscribe)); + + TEST_1(e = es); TEST_E(e->data->e_event, nua_r_subscribe); + r_tags = e->data->e_tags; + TEST_1(tl_find(r_tags, nutag_substate)); + if (es->next == en) { + TEST_1(200 <= e->data->e_status && e->data->e_status < 300); + TEST(tl_find(r_tags, nutag_substate)->t_value, nua_substate_embryonic); + } + else { + TEST_1(200 <= e->data->e_status && e->data->e_status < 300); + TEST(tl_find(r_tags, nutag_substate)->t_value, nua_substate_active); + } + + TEST_1(e = en); TEST_E(e->data->e_event, nua_i_notify); + TEST_1(sip = sip_object(e->data->e_msg)); + n_tags = e->data->e_tags; + + TEST_1(sip->sip_event); TEST_S(sip->sip_event->o_type, "presence"); + TEST_1(sip->sip_content_type); + TEST_S(sip->sip_content_type->c_type, "application/pidf+xml"); + TEST_1(sip->sip_subscription_state); + TEST_S(sip->sip_subscription_state->ss_substate, "active"); + TEST_1(sip->sip_subscription_state->ss_expires); + TEST_1(tl_find(n_tags, nutag_substate)); + TEST(tl_find(n_tags, nutag_substate)->t_value, nua_substate_active); + TEST_1(!en->next || !es->next); + free_events_in_list(ctx, a->events); + + /* XXX --- Do not check server side events */ + free_events_in_list(ctx, b->events); + + if (print_headings) + printf("TEST NUA-12.2: PASSED\n"); + + /* ---------------------------------------------------------------------- */ + +/* NOTIFY with updated content + + A B + | | + |<------NOTIFY-------| + |-------200 OK------>| + | | +*/ + if (print_headings) + printf("TEST NUA-12.3: update notifier\n"); + + /* Update presence data */ + + NOTIFIER(b, b_call, b_call->nh, + SIPTAG_EVENT_STR("presence"), + SIPTAG_CONTENT_TYPE_STR("application/pidf+xml"), + SIPTAG_PAYLOAD_STR(open), + TAG_END()); + + run_ab_until(ctx, -1, save_until_notified, -1, save_until_received); + + /* subscriber events: + nua_i_notify + */ + TEST_1(e = a->events->head); TEST_E(e->data->e_event, nua_i_notify); + TEST_1(sip = sip_object(e->data->e_msg)); + TEST_1(sip->sip_event); TEST_S(sip->sip_event->o_type, "presence"); + TEST_1(sip->sip_content_type); + TEST_S(sip->sip_content_type->c_type, "application/pidf+xml"); + TEST_1(sip->sip_subscription_state); + TEST_S(sip->sip_subscription_state->ss_substate, "active"); + TEST_1(sip->sip_subscription_state->ss_expires); + n_tags = e->data->e_tags; + TEST_1(tl_find(n_tags, nutag_substate)); + TEST(tl_find(n_tags, nutag_substate)->t_value, + nua_substate_active); + TEST_1(!e->next); + free_events_in_list(ctx, a->events); + + /* XXX --- Do not check server side events */ + free_events_in_list(ctx, b->events); + + if (print_headings) + printf("TEST NUA-12.3: PASSED\n"); + + /* ---------------------------------------------------------------------- */ + +/* un-SUBSCRIBE + + A B + | | + |------SUBSCRIBE---->| + |<--------202--------| + |<------NOTIFY-------| + |-------200 OK------>| + | | +*/ + if (print_headings) + printf("TEST NUA-12.5: un-SUBSCRIBE\n"); + + UNSUBSCRIBE(a, a_call, a_call->nh, TAG_END()); + + run_ab_until(ctx, -1, save_until_final_response, + -1, save_until_subscription); + + /* Client events: + nua_unsubscribe(), nua_i_notify/nua_r_unsubscribe + */ + TEST_1(e = a->events->head); + if (e->data->e_event == nua_i_notify) { + TEST_E(e->data->e_event, nua_i_notify); + TEST_1(sip = sip_object(e->data->e_msg)); + n_tags = e->data->e_tags; + TEST_1(sip->sip_event); + TEST_1(sip->sip_subscription_state); + TEST_S(sip->sip_subscription_state->ss_substate, "terminated"); + TEST_1(!sip->sip_subscription_state->ss_expires); + TEST_1(tl_find(n_tags, nutag_substate)); + TEST(tl_find(n_tags, nutag_substate)->t_value, nua_substate_terminated); + TEST_1(e = e->next); + } + TEST_E(e->data->e_event, nua_r_unsubscribe); + TEST_1(tl_find(e->data->e_tags, nutag_substate)); + TEST(tl_find(e->data->e_tags, nutag_substate)->t_value, + nua_substate_terminated); + /* Currently, NOTIFY is dropped after successful response to unsubscribe */ + /* But we don't really care.. */ + /* TEST_1(!e->next); */ + free_events_in_list(ctx, a->events); + + /* Server events: nua_i_subscription with terminated status */ + TEST_1(e = b->events->head); + TEST_E(e->data->e_event, nua_i_subscription); + TEST(tl_gets(e->data->e_tags, + NEATAG_SUB_REF(sub), + NUTAG_SUBSTATE_REF(substate), + TAG_END()), 2); + TEST_1(sub); + TEST(substate, nua_substate_terminated); + TEST_1(!e->next); + + free_events_in_list(ctx, b->events); + + if (print_headings) + printf("TEST NUA-12.5: PASSED\n"); + + /* ---------------------------------------------------------------------- */ +/* Fetch event, SUBSCRIBE with expires: 0 + + A B + | | + |------SUBSCRIBE---->| + |<--------202--------| + |<------NOTIFY-------| + |-------200 OK------>| + | | +*/ + if (print_headings) + printf("TEST NUA-12.5.1: event fetch\n"); + + SUBSCRIBE(a, a_call, a_call->nh, NUTAG_URL(b->contact->m_url), + SIPTAG_EVENT_STR("presence"), + SIPTAG_ACCEPT_STR("application/pidf+xml"), + SIPTAG_EXPIRES_STR("0"), + TAG_END()); + + run_ab_until(ctx, -1, save_until_notified_and_responded, + -1, save_until_subscription); + + /* Client events: + nua_subscribe(), nua_i_notify/nua_r_subscribe + */ + TEST_1(en = event_by_type(a->events->head, nua_i_notify)); + TEST_1(es = event_by_type(a->events->head, nua_r_subscribe)); + + e = es; TEST_E(e->data->e_event, nua_r_subscribe); + TEST_1(t = tl_find(e->data->e_tags, nutag_substate)); + TEST_1(t->t_value == nua_substate_pending || + t->t_value == nua_substate_terminated || + t->t_value == nua_substate_embryonic); + + e = en; TEST_E(e->data->e_event, nua_i_notify); + TEST_1(sip = sip_object(e->data->e_msg)); + n_tags = e->data->e_tags; + + TEST_1(sip->sip_event); TEST_S(sip->sip_event->o_type, "presence"); + TEST_1(sip->sip_content_type); + TEST_S(sip->sip_content_type->c_type, "application/pidf+xml"); + TEST_1(sip->sip_payload && sip->sip_payload->pl_data); + TEST_1(sip->sip_subscription_state); + TEST_S(sip->sip_subscription_state->ss_substate, "terminated"); + TEST_1(tl_find(n_tags, nutag_substate)); + TEST(tl_find(n_tags, nutag_substate)->t_value, + nua_substate_terminated); + TEST_1(!en->next || !es->next); + free_events_in_list(ctx, a->events); + + /* + Server events: + nua_i_subscription + */ + TEST_1(e = b->events->head); + TEST_E(e->data->e_event, nua_i_subscription); + TEST(tl_gets(e->data->e_tags, NEATAG_SUB_REF(sub), TAG_END()), 1); + TEST_1(sub); + TEST_1(!e->next); + + free_events_in_list(ctx, b->events); + + if (print_headings) + printf("TEST NUA-12.4.1: PASSED\n"); + + + /* ---------------------------------------------------------------------- */ +/* 2nd SUBSCRIBE with event id + + A B + | | + |------SUBSCRIBE---->| + |<--------202--------| + |<------NOTIFY-------| + |-------200 OK------>| + | | +*/ + /* XXX - we should do this before unsubscribing first one */ + if (print_headings) + printf("TEST NUA-12.4.2: establishing 2nd subscription\n"); + + NOTIFIER(b, b_call, b_call->nh, + SIPTAG_EVENT_STR("presence"), + SIPTAG_CONTENT_TYPE_STR("application/xpidf+xml"), + SIPTAG_PAYLOAD_STR(open), + NEATAG_THROTTLE(1), + NUTAG_SUBSTATE(nua_substate_pending), + TAG_END()); + run_b_until(ctx, nua_r_notifier, until_final_response); + + NOTIFIER(b, b_call, b_call->nh, + SIPTAG_EVENT_STR("presence"), + SIPTAG_CONTENT_TYPE_STR("application/xpidf+xml"), + SIPTAG_PAYLOAD_STR(closed), + NEATAG_THROTTLE(1), + NEATAG_FAKE(1), + NUTAG_SUBSTATE(nua_substate_pending), + TAG_END()); + run_b_until(ctx, nua_r_notifier, until_final_response); + + SUBSCRIBE(a, a_call, a_call->nh, NUTAG_URL(b->contact->m_url), + SIPTAG_EVENT_STR("presence;id=1"), + SIPTAG_ACCEPT_STR("application/xpidf+xml"), + TAG_END()); + + run_ab_until(ctx, -1, save_until_notified_and_responded, + -1, save_until_subscription); + + /* Client events: + nua_subscribe(), nua_i_notify/nua_r_subscribe + */ + TEST_1(en = event_by_type(a->events->head, nua_i_notify)); + TEST_1(es = event_by_type(a->events->head, nua_r_subscribe)); + + e = es; TEST_E(e->data->e_event, nua_r_subscribe); + TEST_1(t = tl_find(e->data->e_tags, nutag_substate)); + TEST_1(t->t_value == nua_substate_pending || + t->t_value == nua_substate_embryonic); + + e = en; TEST_E(e->data->e_event, nua_i_notify); + TEST_1(sip = sip_object(e->data->e_msg)); + n_tags = e->data->e_tags; + + TEST_1(sip->sip_event); TEST_S(sip->sip_event->o_type, "presence"); + TEST_S(sip->sip_event->o_id, "1"); + TEST_1(sip->sip_content_type); + TEST_S(sip->sip_content_type->c_type, "application/xpidf+xml"); + TEST_1(sip->sip_payload && sip->sip_payload->pl_data); + /* Check that we really got "fake" content */ + TEST_1(memmem(sip->sip_payload->pl_data, sip->sip_payload->pl_len, + "<basic>closed</basic>", strlen("<basic>closed</basic>"))); + TEST_1(sip->sip_subscription_state); + TEST_S(sip->sip_subscription_state->ss_substate, "pending"); + TEST_1(sip->sip_subscription_state->ss_expires); + TEST_1(tl_find(n_tags, nutag_substate)); + TEST(tl_find(n_tags, nutag_substate)->t_value, + nua_substate_pending); + TEST_1(!en->next || !es->next); + free_events_in_list(ctx, a->events); + + /* + Server events: + nua_i_subscription + */ + TEST_1(e = b->events->head); + TEST_E(e->data->e_event, nua_i_subscription); + TEST(tl_gets(e->data->e_tags, NEATAG_SUB_REF(sub), TAG_END()), 1); + TEST_1(sub); + TEST_1(!e->next); + + free_events_in_list(ctx, b->events); + + /* Authorize user A */ + AUTHORIZE(b, b_call, b_call->nh, + NUTAG_SUBSTATE(nua_substate_active), + NEATAG_SUB(sub), + NEATAG_FAKE(0), + TAG_END()); + + run_ab_until(ctx, -1, save_until_notified, + -1, save_until_final_response); + + /* subscriber events: + nua_i_notify with NUTAG_SUBSTATE(nua_substate_active) + */ + TEST_1(e = a->events->head); TEST_E(e->data->e_event, nua_i_notify); + TEST_1(sip = sip_object(e->data->e_msg)); + TEST_1(sip->sip_event); TEST_S(sip->sip_event->o_type, "presence"); + TEST_1(sip->sip_content_type); + TEST_S(sip->sip_content_type->c_type, "application/xpidf+xml"); + TEST_1(sip->sip_payload && sip->sip_payload->pl_data); + /* Check that we really got real content */ + TEST_1(memmem(sip->sip_payload->pl_data, sip->sip_payload->pl_len, + "<basic>open</basic>", strlen("<basic>open</basic>"))); + TEST_1(sip->sip_subscription_state); + TEST_S(sip->sip_subscription_state->ss_substate, "active"); + TEST_1(sip->sip_subscription_state->ss_expires); + n_tags = e->data->e_tags; + TEST_1(tl_find(n_tags, nutag_substate)); + TEST(tl_find(n_tags, nutag_substate)->t_value, nua_substate_active); + TEST_1(!e->next); + free_events_in_list(ctx, a->events); + + /* + Server events: + nua_r_authorize + */ + TEST_1(e = b->events->head); + TEST_E(e->data->e_event, nua_r_authorize); + TEST_1(!e->next); + + free_events_in_list(ctx, b->events); + + if (print_headings) + printf("TEST NUA-12.4: PASSED\n"); + + /* ---------------------------------------------------------------------- */ + +/* NOTIFY terminating subscription + + A B + | | + |<------NOTIFY-------| + |-------200 OK------>| + | | +*/ + + if (print_headings) + printf("TEST NUA-12.6: terminate notifier\n"); + + TERMINATE(b, b_call, b_call->nh, TAG_END()); + + run_ab_until(ctx, -1, save_until_notified, -1, until_final_response); + + /* Client events: + nua_i_notify + */ + TEST_1(e = a->events->head); TEST_E(e->data->e_event, nua_i_notify); + TEST_1(sip = sip_object(e->data->e_msg)); + TEST_1(sip->sip_event); TEST_S(sip->sip_event->o_type, "presence"); + TEST_S(sip->sip_event->o_id, "1"); + TEST_1(sip->sip_subscription_state); + TEST_S(sip->sip_subscription_state->ss_substate, "terminated"); + TEST_1(!sip->sip_subscription_state->ss_expires); + n_tags = e->data->e_tags; + TEST_1(tl_find(n_tags, nutag_substate)); + TEST(tl_find(n_tags, nutag_substate)->t_value, nua_substate_terminated); + TEST_1(!e->next); + free_events_in_list(ctx, a->events); + + if (print_headings) + printf("TEST NUA-12.6: PASSED\n"); + + /* ---------------------------------------------------------------------- */ + + + nua_handle_destroy(a_call->nh), a_call->nh = NULL; + nua_handle_destroy(b_call->nh), b_call->nh = NULL; + + END(); /* test_events */ +}