From dd9f6af78d86ce4fff84447fd8476ade35cf8586 Mon Sep 17 00:00:00 2001 From: Michael Jerris Date: Wed, 14 May 2008 19:10:54 +0000 Subject: [PATCH] complete r8400 commit git-svn-id: http://svn.freeswitch.org/svn/freeswitch/trunk@8406 d0543943-73ff-0310-b7d9-9358b9ac24b2 --- .../sofia-sip/libsofia-sip-ua/nua/check_nua.c | 120 ++ .../sofia-sip/libsofia-sip-ua/nua/check_nua.h | 15 + .../libsofia-sip-ua/nua/check_register.c | 741 ++++++++ .../libsofia-sip-ua/nua/check_session.c | 1564 ++++++++++++++++ libs/sofia-sip/libsofia-sip-ua/nua/test_s2.c | 1589 +++++++++++++++++ libs/sofia-sip/libsofia-sip-ua/nua/test_s2.h | 174 ++ .../nua => tests}/test_100rel.c | 0 .../nua => tests}/test_basic_call.c | 0 .../nua => tests}/test_call_hold.c | 0 .../nua => tests}/test_call_reject.c | 0 .../nua => tests}/test_cancel_bye.c | 0 .../nua => tests}/test_extension.c | 0 .../nua => tests}/test_init.c | 0 .../{libsofia-sip-ua/nua => tests}/test_nat.c | 0 .../{libsofia-sip-ua/nua => tests}/test_nat.h | 0 .../nua => tests}/test_nat_tags.c | 0 .../{libsofia-sip-ua/nua => tests}/test_nua.c | 0 .../{libsofia-sip-ua/nua => tests}/test_nua.h | 0 .../nua => tests}/test_nua_api.c | 0 .../nua => tests}/test_nua_params.c | 0 .../nua => tests}/test_offer_answer.c | 0 .../{libsofia-sip-ua/nua => tests}/test_ops.c | 0 .../nua => tests}/test_proxy.c | 0 .../nua => tests}/test_proxy.h | 0 .../nua => tests}/test_refer.c | 0 .../nua => tests}/test_register.c | 0 .../nua => tests}/test_session_timer.c | 0 .../nua => tests}/test_simple.c | 0 .../nua => tests}/test_sip_events.c | 0 29 files changed, 4203 insertions(+) create mode 100644 libs/sofia-sip/libsofia-sip-ua/nua/check_nua.c create mode 100644 libs/sofia-sip/libsofia-sip-ua/nua/check_nua.h create mode 100644 libs/sofia-sip/libsofia-sip-ua/nua/check_register.c create mode 100644 libs/sofia-sip/libsofia-sip-ua/nua/check_session.c create mode 100644 libs/sofia-sip/libsofia-sip-ua/nua/test_s2.c create mode 100644 libs/sofia-sip/libsofia-sip-ua/nua/test_s2.h rename libs/sofia-sip/{libsofia-sip-ua/nua => tests}/test_100rel.c (100%) rename libs/sofia-sip/{libsofia-sip-ua/nua => tests}/test_basic_call.c (100%) rename libs/sofia-sip/{libsofia-sip-ua/nua => tests}/test_call_hold.c (100%) rename libs/sofia-sip/{libsofia-sip-ua/nua => tests}/test_call_reject.c (100%) rename libs/sofia-sip/{libsofia-sip-ua/nua => tests}/test_cancel_bye.c (100%) rename libs/sofia-sip/{libsofia-sip-ua/nua => tests}/test_extension.c (100%) rename libs/sofia-sip/{libsofia-sip-ua/nua => tests}/test_init.c (100%) rename libs/sofia-sip/{libsofia-sip-ua/nua => tests}/test_nat.c (100%) rename libs/sofia-sip/{libsofia-sip-ua/nua => tests}/test_nat.h (100%) rename libs/sofia-sip/{libsofia-sip-ua/nua => tests}/test_nat_tags.c (100%) rename libs/sofia-sip/{libsofia-sip-ua/nua => tests}/test_nua.c (100%) rename libs/sofia-sip/{libsofia-sip-ua/nua => tests}/test_nua.h (100%) rename libs/sofia-sip/{libsofia-sip-ua/nua => tests}/test_nua_api.c (100%) rename libs/sofia-sip/{libsofia-sip-ua/nua => tests}/test_nua_params.c (100%) rename libs/sofia-sip/{libsofia-sip-ua/nua => tests}/test_offer_answer.c (100%) rename libs/sofia-sip/{libsofia-sip-ua/nua => tests}/test_ops.c (100%) rename libs/sofia-sip/{libsofia-sip-ua/nua => tests}/test_proxy.c (100%) rename libs/sofia-sip/{libsofia-sip-ua/nua => tests}/test_proxy.h (100%) rename libs/sofia-sip/{libsofia-sip-ua/nua => tests}/test_refer.c (100%) rename libs/sofia-sip/{libsofia-sip-ua/nua => tests}/test_register.c (100%) rename libs/sofia-sip/{libsofia-sip-ua/nua => tests}/test_session_timer.c (100%) rename libs/sofia-sip/{libsofia-sip-ua/nua => tests}/test_simple.c (100%) rename libs/sofia-sip/{libsofia-sip-ua/nua => tests}/test_sip_events.c (100%) diff --git a/libs/sofia-sip/libsofia-sip-ua/nua/check_nua.c b/libs/sofia-sip/libsofia-sip-ua/nua/check_nua.c new file mode 100644 index 0000000000..ae9e630ec3 --- /dev/null +++ b/libs/sofia-sip/libsofia-sip-ua/nua/check_nua.c @@ -0,0 +1,120 @@ +/* + * This file is part of the Sofia-SIP package + * + * Copyright (C) 2007 Nokia Corporation. + * + * Contact: Pekka Pessi + * + * 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 + * + * @copyright (C) 2007 Nokia Corporation. + */ + +#include "config.h" + +#include "check_nua.h" + +#include +#include +#include + +#if HAVE_FNMATCH_H +#include +#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); +} diff --git a/libs/sofia-sip/libsofia-sip-ua/nua/check_nua.h b/libs/sofia-sip/libsofia-sip-ua/nua/check_nua.h new file mode 100644 index 0000000000..d58680f724 --- /dev/null +++ b/libs/sofia-sip/libsofia-sip-ua/nua/check_nua.h @@ -0,0 +1,15 @@ +#ifndef CHECK_NUA_H + +#include + +#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 + diff --git a/libs/sofia-sip/libsofia-sip-ua/nua/check_register.c b/libs/sofia-sip/libsofia-sip-ua/nua/check_register.c new file mode 100644 index 0000000000..6bc1761fc2 --- /dev/null +++ b/libs/sofia-sip/libsofia-sip-ua/nua/check_register.c @@ -0,0 +1,741 @@ +/* + * This file is part of the Sofia-SIP package + * + * Copyright (C) 2008 Nokia Corporation. + * + * Contact: Pekka Pessi + * + * 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 + * + * @copyright (C) 2008 Nokia Corporation. + */ + +#include "config.h" + +#include "check_nua.h" + +#include "test_s2.h" + +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +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()); +} + diff --git a/libs/sofia-sip/libsofia-sip-ua/nua/check_session.c b/libs/sofia-sip/libsofia-sip-ua/nua/check_session.c new file mode 100644 index 0000000000..1472344289 --- /dev/null +++ b/libs/sofia-sip/libsofia-sip-ua/nua/check_session.c @@ -0,0 +1,1564 @@ +/* + * This file is part of the Sofia-SIP package + * + * Copyright (C) 2008 Nokia Corporation. + * + * Contact: Pekka Pessi + * + * 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 + * + * @copyright (C) 2008 Nokia Corporation. + */ + +#include "config.h" + +#include "check_nua.h" + +#include "test_s2.h" + +#include +#include +#include +#include +#include + +#include +#include +#include + +/* ====================================================================== */ +/* 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()); +} diff --git a/libs/sofia-sip/libsofia-sip-ua/nua/test_s2.c b/libs/sofia-sip/libsofia-sip-ua/nua/test_s2.c new file mode 100644 index 0000000000..1639ae98ff --- /dev/null +++ b/libs/sofia-sip/libsofia-sip-ua/nua/test_s2.c @@ -0,0 +1,1589 @@ +/* + * This file is part of the Sofia-SIP package + * + * Copyright (C) 2008 Nokia Corporation. + * + * Contact: Pekka Pessi + * + * 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 + * + * @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 +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +/* -- 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 : ""); +} + +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 ", + 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, "", + 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, "", + 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, "", + tpn->tpn_host, + tpn->tpn_port, + tpn->tpn_proto); + } + else { + m = sip_contact_format(s2->home, "", + 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 + +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 + +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 "), + /* 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. + * + *
+ *  A                  B
+ *  |-----REGISTER---->|
+ *  |<-----200 OK------|
+ *  |                  |
+ * 
+ */ +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. + * + *
+ *  A                  B
+ *  |-----REGISTER---->|
+ *  |<-----200 OK------|
+ *  |                  |
+ * 
+ */ +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; + } +} + diff --git a/libs/sofia-sip/libsofia-sip-ua/nua/test_s2.h b/libs/sofia-sip/libsofia-sip-ua/nua/test_s2.h new file mode 100644 index 0000000000..5104d800e6 --- /dev/null +++ b/libs/sofia-sip/libsofia-sip-ua/nua/test_s2.h @@ -0,0 +1,174 @@ +/* + * This file is part of the Sofia-SIP package + * + * Copyright (C) 2008 Nokia Corporation. + * + * Contact: Pekka Pessi + * + * 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 +#include +#include +#include + +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 diff --git a/libs/sofia-sip/libsofia-sip-ua/nua/test_100rel.c b/libs/sofia-sip/tests/test_100rel.c similarity index 100% rename from libs/sofia-sip/libsofia-sip-ua/nua/test_100rel.c rename to libs/sofia-sip/tests/test_100rel.c diff --git a/libs/sofia-sip/libsofia-sip-ua/nua/test_basic_call.c b/libs/sofia-sip/tests/test_basic_call.c similarity index 100% rename from libs/sofia-sip/libsofia-sip-ua/nua/test_basic_call.c rename to libs/sofia-sip/tests/test_basic_call.c diff --git a/libs/sofia-sip/libsofia-sip-ua/nua/test_call_hold.c b/libs/sofia-sip/tests/test_call_hold.c similarity index 100% rename from libs/sofia-sip/libsofia-sip-ua/nua/test_call_hold.c rename to libs/sofia-sip/tests/test_call_hold.c diff --git a/libs/sofia-sip/libsofia-sip-ua/nua/test_call_reject.c b/libs/sofia-sip/tests/test_call_reject.c similarity index 100% rename from libs/sofia-sip/libsofia-sip-ua/nua/test_call_reject.c rename to libs/sofia-sip/tests/test_call_reject.c diff --git a/libs/sofia-sip/libsofia-sip-ua/nua/test_cancel_bye.c b/libs/sofia-sip/tests/test_cancel_bye.c similarity index 100% rename from libs/sofia-sip/libsofia-sip-ua/nua/test_cancel_bye.c rename to libs/sofia-sip/tests/test_cancel_bye.c diff --git a/libs/sofia-sip/libsofia-sip-ua/nua/test_extension.c b/libs/sofia-sip/tests/test_extension.c similarity index 100% rename from libs/sofia-sip/libsofia-sip-ua/nua/test_extension.c rename to libs/sofia-sip/tests/test_extension.c diff --git a/libs/sofia-sip/libsofia-sip-ua/nua/test_init.c b/libs/sofia-sip/tests/test_init.c similarity index 100% rename from libs/sofia-sip/libsofia-sip-ua/nua/test_init.c rename to libs/sofia-sip/tests/test_init.c diff --git a/libs/sofia-sip/libsofia-sip-ua/nua/test_nat.c b/libs/sofia-sip/tests/test_nat.c similarity index 100% rename from libs/sofia-sip/libsofia-sip-ua/nua/test_nat.c rename to libs/sofia-sip/tests/test_nat.c diff --git a/libs/sofia-sip/libsofia-sip-ua/nua/test_nat.h b/libs/sofia-sip/tests/test_nat.h similarity index 100% rename from libs/sofia-sip/libsofia-sip-ua/nua/test_nat.h rename to libs/sofia-sip/tests/test_nat.h diff --git a/libs/sofia-sip/libsofia-sip-ua/nua/test_nat_tags.c b/libs/sofia-sip/tests/test_nat_tags.c similarity index 100% rename from libs/sofia-sip/libsofia-sip-ua/nua/test_nat_tags.c rename to libs/sofia-sip/tests/test_nat_tags.c diff --git a/libs/sofia-sip/libsofia-sip-ua/nua/test_nua.c b/libs/sofia-sip/tests/test_nua.c similarity index 100% rename from libs/sofia-sip/libsofia-sip-ua/nua/test_nua.c rename to libs/sofia-sip/tests/test_nua.c diff --git a/libs/sofia-sip/libsofia-sip-ua/nua/test_nua.h b/libs/sofia-sip/tests/test_nua.h similarity index 100% rename from libs/sofia-sip/libsofia-sip-ua/nua/test_nua.h rename to libs/sofia-sip/tests/test_nua.h diff --git a/libs/sofia-sip/libsofia-sip-ua/nua/test_nua_api.c b/libs/sofia-sip/tests/test_nua_api.c similarity index 100% rename from libs/sofia-sip/libsofia-sip-ua/nua/test_nua_api.c rename to libs/sofia-sip/tests/test_nua_api.c diff --git a/libs/sofia-sip/libsofia-sip-ua/nua/test_nua_params.c b/libs/sofia-sip/tests/test_nua_params.c similarity index 100% rename from libs/sofia-sip/libsofia-sip-ua/nua/test_nua_params.c rename to libs/sofia-sip/tests/test_nua_params.c diff --git a/libs/sofia-sip/libsofia-sip-ua/nua/test_offer_answer.c b/libs/sofia-sip/tests/test_offer_answer.c similarity index 100% rename from libs/sofia-sip/libsofia-sip-ua/nua/test_offer_answer.c rename to libs/sofia-sip/tests/test_offer_answer.c diff --git a/libs/sofia-sip/libsofia-sip-ua/nua/test_ops.c b/libs/sofia-sip/tests/test_ops.c similarity index 100% rename from libs/sofia-sip/libsofia-sip-ua/nua/test_ops.c rename to libs/sofia-sip/tests/test_ops.c diff --git a/libs/sofia-sip/libsofia-sip-ua/nua/test_proxy.c b/libs/sofia-sip/tests/test_proxy.c similarity index 100% rename from libs/sofia-sip/libsofia-sip-ua/nua/test_proxy.c rename to libs/sofia-sip/tests/test_proxy.c diff --git a/libs/sofia-sip/libsofia-sip-ua/nua/test_proxy.h b/libs/sofia-sip/tests/test_proxy.h similarity index 100% rename from libs/sofia-sip/libsofia-sip-ua/nua/test_proxy.h rename to libs/sofia-sip/tests/test_proxy.h diff --git a/libs/sofia-sip/libsofia-sip-ua/nua/test_refer.c b/libs/sofia-sip/tests/test_refer.c similarity index 100% rename from libs/sofia-sip/libsofia-sip-ua/nua/test_refer.c rename to libs/sofia-sip/tests/test_refer.c diff --git a/libs/sofia-sip/libsofia-sip-ua/nua/test_register.c b/libs/sofia-sip/tests/test_register.c similarity index 100% rename from libs/sofia-sip/libsofia-sip-ua/nua/test_register.c rename to libs/sofia-sip/tests/test_register.c diff --git a/libs/sofia-sip/libsofia-sip-ua/nua/test_session_timer.c b/libs/sofia-sip/tests/test_session_timer.c similarity index 100% rename from libs/sofia-sip/libsofia-sip-ua/nua/test_session_timer.c rename to libs/sofia-sip/tests/test_session_timer.c diff --git a/libs/sofia-sip/libsofia-sip-ua/nua/test_simple.c b/libs/sofia-sip/tests/test_simple.c similarity index 100% rename from libs/sofia-sip/libsofia-sip-ua/nua/test_simple.c rename to libs/sofia-sip/tests/test_simple.c diff --git a/libs/sofia-sip/libsofia-sip-ua/nua/test_sip_events.c b/libs/sofia-sip/tests/test_sip_events.c similarity index 100% rename from libs/sofia-sip/libsofia-sip-ua/nua/test_sip_events.c rename to libs/sofia-sip/tests/test_sip_events.c