[Freeswitch-svn] [commit] r8406 - in freeswitch/trunk/libs/sofia-sip: libsofia-sip-ua/nua tests

Freeswitch SVN mikej at freeswitch.org
Wed May 14 15:10:54 EDT 2008


Author: mikej
Date: Wed May 14 15:10:54 2008
New Revision: 8406

Added:
   freeswitch/trunk/libs/sofia-sip/libsofia-sip-ua/nua/check_nua.c
   freeswitch/trunk/libs/sofia-sip/libsofia-sip-ua/nua/check_nua.h
   freeswitch/trunk/libs/sofia-sip/libsofia-sip-ua/nua/check_register.c
   freeswitch/trunk/libs/sofia-sip/libsofia-sip-ua/nua/check_session.c
   freeswitch/trunk/libs/sofia-sip/libsofia-sip-ua/nua/test_s2.c
   freeswitch/trunk/libs/sofia-sip/libsofia-sip-ua/nua/test_s2.h
   freeswitch/trunk/libs/sofia-sip/tests/test_100rel.c
   freeswitch/trunk/libs/sofia-sip/tests/test_basic_call.c
   freeswitch/trunk/libs/sofia-sip/tests/test_call_hold.c
   freeswitch/trunk/libs/sofia-sip/tests/test_call_reject.c
   freeswitch/trunk/libs/sofia-sip/tests/test_cancel_bye.c
   freeswitch/trunk/libs/sofia-sip/tests/test_extension.c
   freeswitch/trunk/libs/sofia-sip/tests/test_init.c
   freeswitch/trunk/libs/sofia-sip/tests/test_nat.c
   freeswitch/trunk/libs/sofia-sip/tests/test_nat.h
   freeswitch/trunk/libs/sofia-sip/tests/test_nat_tags.c
   freeswitch/trunk/libs/sofia-sip/tests/test_nua.c
   freeswitch/trunk/libs/sofia-sip/tests/test_nua.h
   freeswitch/trunk/libs/sofia-sip/tests/test_nua_api.c
   freeswitch/trunk/libs/sofia-sip/tests/test_nua_params.c
   freeswitch/trunk/libs/sofia-sip/tests/test_offer_answer.c
   freeswitch/trunk/libs/sofia-sip/tests/test_ops.c
   freeswitch/trunk/libs/sofia-sip/tests/test_proxy.c
   freeswitch/trunk/libs/sofia-sip/tests/test_proxy.h
   freeswitch/trunk/libs/sofia-sip/tests/test_refer.c
   freeswitch/trunk/libs/sofia-sip/tests/test_register.c
   freeswitch/trunk/libs/sofia-sip/tests/test_session_timer.c
   freeswitch/trunk/libs/sofia-sip/tests/test_simple.c
   freeswitch/trunk/libs/sofia-sip/tests/test_sip_events.c
Removed:
   freeswitch/trunk/libs/sofia-sip/libsofia-sip-ua/nua/test_100rel.c
   freeswitch/trunk/libs/sofia-sip/libsofia-sip-ua/nua/test_basic_call.c
   freeswitch/trunk/libs/sofia-sip/libsofia-sip-ua/nua/test_call_hold.c
   freeswitch/trunk/libs/sofia-sip/libsofia-sip-ua/nua/test_call_reject.c
   freeswitch/trunk/libs/sofia-sip/libsofia-sip-ua/nua/test_cancel_bye.c
   freeswitch/trunk/libs/sofia-sip/libsofia-sip-ua/nua/test_extension.c
   freeswitch/trunk/libs/sofia-sip/libsofia-sip-ua/nua/test_init.c
   freeswitch/trunk/libs/sofia-sip/libsofia-sip-ua/nua/test_nat.c
   freeswitch/trunk/libs/sofia-sip/libsofia-sip-ua/nua/test_nat.h
   freeswitch/trunk/libs/sofia-sip/libsofia-sip-ua/nua/test_nat_tags.c
   freeswitch/trunk/libs/sofia-sip/libsofia-sip-ua/nua/test_nua.c
   freeswitch/trunk/libs/sofia-sip/libsofia-sip-ua/nua/test_nua.h
   freeswitch/trunk/libs/sofia-sip/libsofia-sip-ua/nua/test_nua_api.c
   freeswitch/trunk/libs/sofia-sip/libsofia-sip-ua/nua/test_nua_params.c
   freeswitch/trunk/libs/sofia-sip/libsofia-sip-ua/nua/test_offer_answer.c
   freeswitch/trunk/libs/sofia-sip/libsofia-sip-ua/nua/test_ops.c
   freeswitch/trunk/libs/sofia-sip/libsofia-sip-ua/nua/test_proxy.c
   freeswitch/trunk/libs/sofia-sip/libsofia-sip-ua/nua/test_proxy.h
   freeswitch/trunk/libs/sofia-sip/libsofia-sip-ua/nua/test_refer.c
   freeswitch/trunk/libs/sofia-sip/libsofia-sip-ua/nua/test_register.c
   freeswitch/trunk/libs/sofia-sip/libsofia-sip-ua/nua/test_session_timer.c
   freeswitch/trunk/libs/sofia-sip/libsofia-sip-ua/nua/test_simple.c
   freeswitch/trunk/libs/sofia-sip/libsofia-sip-ua/nua/test_sip_events.c

Log:
complete r8400 commit

Added: freeswitch/trunk/libs/sofia-sip/libsofia-sip-ua/nua/check_nua.c
==============================================================================
--- (empty file)
+++ freeswitch/trunk/libs/sofia-sip/libsofia-sip-ua/nua/check_nua.c	Wed May 14 15:10:54 2008
@@ -0,0 +1,120 @@
+/*
+ * This file is part of the Sofia-SIP package
+ *
+ * Copyright (C) 2007 Nokia Corporation.
+ *
+ * Contact: Pekka Pessi <pekka.pessi at nokia.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA
+ *
+ */
+
+/**@CFILE check2_sofia.c
+ *
+ * @brief Check-driven tester for Sofia SIP User Agent library
+ *
+ * @author Pekka Pessi <Pekka.Pessi at nokia.com>
+ *
+ * @copyright (C) 2007 Nokia Corporation.
+ */
+
+#include "config.h"
+
+#include "check_nua.h"
+
+#include <stdlib.h>
+#include <string.h>
+#include <stdio.h>
+
+#if HAVE_FNMATCH_H
+#include <fnmatch.h>
+#endif
+
+static char const * const default_patterns[] = { "*", NULL };
+static char const * const *test_patterns = default_patterns;
+
+void check_nua_tcase_add_test(TCase *tc, TFun tf, char const *name)
+{
+  char const * const *patterns;
+
+#if HAVE_FNMATCH_H
+  for (patterns = test_patterns; *patterns; patterns++) {
+    if (!fnmatch(*patterns, name, 0)) {
+      if (strcmp(*patterns, "*")) {
+	printf("%s: match with %s\n", name, *patterns);
+      }
+      _tcase_add_test(tc, tf, name, 0, 0, 1);
+      return;
+    }
+  }
+#else
+  for (patterns = test_patterns; *patterns; patterns++) {
+    if (!strcmp(*patterns, name) || !strcmp(*patterns, "*")) {
+      if (strcmp(*patterns, "*")) {
+	printf("%s: match with %s\n", name, *patterns);
+      }
+      _tcase_add_test(tc, tf, name, 0, 0, 1);
+      return;
+    }
+  }
+#endif
+  printf("%s: no match\n", name);
+}
+
+int main(int argc, char *argv[])
+{
+  int failed = 0;
+
+  Suite *suite = suite_create("Unit tests for Sofia-SIP UA Engine");
+  SRunner *runner;
+
+  if (getenv("CHECK_NUA_CASES")) {
+    size_t i;
+    char *s, **patterns;
+    char *cases = strdup(getenv("CHECK_NUA_CASES"));
+
+    /* Count commas */
+    for (i = 2, s = cases; (s = strchr(s, ',')); s++, i++);
+
+    patterns = calloc(i, sizeof *patterns);
+
+    for (i = 0, s = cases;; i++) {
+      patterns[i] = s;
+      if (s == NULL)
+	break;
+      s = strchr(s, ',');
+      if (s)
+	*s++ = '\0';
+    }
+
+    test_patterns = (char const * const *)patterns;
+  }
+
+  check_register_cases(suite);
+  check_session_cases(suite);
+
+  runner = srunner_create(suite);
+
+  if (argv[1]) {
+    srunner_set_xml(runner, argv[1]);
+  }
+  srunner_run_all(runner, CK_ENV);
+
+  failed = srunner_ntests_failed(runner);
+  srunner_free(runner);
+
+  exit(failed ? EXIT_FAILURE : EXIT_SUCCESS);
+}

Added: freeswitch/trunk/libs/sofia-sip/libsofia-sip-ua/nua/check_nua.h
==============================================================================
--- (empty file)
+++ freeswitch/trunk/libs/sofia-sip/libsofia-sip-ua/nua/check_nua.h	Wed May 14 15:10:54 2008
@@ -0,0 +1,15 @@
+#ifndef CHECK_NUA_H
+
+#include <check.h>
+
+#undef tcase_add_test
+#define tcase_add_test(tc, tf) \
+  check_nua_tcase_add_test(tc, tf, "" #tf "")
+
+void check_nua_tcase_add_test(TCase *, TFun, char const *name);
+
+void check_session_cases(Suite *suite);
+void check_register_cases(Suite *suite);
+
+#endif
+

Added: freeswitch/trunk/libs/sofia-sip/libsofia-sip-ua/nua/check_register.c
==============================================================================
--- (empty file)
+++ freeswitch/trunk/libs/sofia-sip/libsofia-sip-ua/nua/check_register.c	Wed May 14 15:10:54 2008
@@ -0,0 +1,741 @@
+/*
+ * This file is part of the Sofia-SIP package
+ *
+ * Copyright (C) 2008 Nokia Corporation.
+ *
+ * Contact: Pekka Pessi <pekka.pessi at nokia.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA
+ *
+ */
+
+/**@CFILE check_register.c
+ *
+ * @brief Check-driven tester for Sofia SIP User Agent library
+ *
+ * @author Pekka Pessi <Pekka.Pessi at nokia.com>
+ *
+ * @copyright (C) 2008 Nokia Corporation.
+ */
+
+#include "config.h"
+
+#include "check_nua.h"
+
+#include "test_s2.h"
+
+#include <sofia-sip/sip_status.h>
+#include <sofia-sip/sip_header.h>
+#include <sofia-sip/soa.h>
+#include <sofia-sip/su_tagarg.h>
+#include <sofia-sip/su_tag_io.h>
+#include <sofia-sip/su_log.h>
+
+#include <stdlib.h>
+#include <string.h>
+#include <assert.h>
+
+SOFIAPUBVAR su_log_t tport_log[];
+
+static nua_t *nua;
+
+static void register_setup(void)
+{
+  nua = s2_nua_setup(TAG_END());
+}
+
+static void register_pingpong_setup(void)
+{
+  nua = s2_nua_setup(TPTAG_PINGPONG(20000),
+		     TPTAG_KEEPALIVE(10000),
+		     TAG_END());
+  tport_set_params(s2->tcp.tport, TPTAG_PONG2PING(1), TAG_END());
+}
+
+
+static void register_teardown(void)
+{
+  nua_shutdown(nua);
+  fail_unless(s2_check_event(nua_r_shutdown, 200));
+  s2_nua_teardown();
+}
+
+
+/* ---------------------------------------------------------------------- */
+
+START_TEST(register_1_0_1)
+{
+  nua_handle_t *nh = nua_handle(nua, NULL, TAG_END());
+  struct message *m;
+
+  s2_case("1.0.1", "Failed Register", "REGISTER returned 403 response");
+
+  nua_register(nh, TAG_END());
+
+  fail_unless((m = s2_wait_for_request(SIP_METHOD_REGISTER)) != NULL, NULL);
+
+  s2_respond_to(m, NULL,
+		SIP_403_FORBIDDEN,
+		TAG_END());
+  s2_free_message(m);
+
+  nua_handle_destroy(nh);
+
+} END_TEST
+
+
+START_TEST(register_1_1_1)
+{
+  s2_case("1.1.1", "Basic Register", "REGISTER returning 200 OK");
+
+  s2_register_setup();
+
+  s2_register_teardown();
+
+} END_TEST
+
+
+START_TEST(register_1_1_2)
+{
+  nua_handle_t *nh;
+  struct message *m;
+
+  s2_case("1.1.2", "Register with dual authentication",
+	  "Register, authenticate");
+
+  nh = nua_handle(nua, NULL, TAG_END());
+
+  nua_register(nh, TAG_END());
+
+  m = s2_wait_for_request(SIP_METHOD_REGISTER); fail_if(!m);
+  s2_respond_to(m, NULL,
+		SIP_407_PROXY_AUTH_REQUIRED,
+		SIPTAG_PROXY_AUTHENTICATE_STR(s2_auth_digest_str),
+		TAG_END());
+  s2_free_message(m);
+  s2_check_event(nua_r_register, 407);
+
+  nua_authenticate(nh, NUTAG_AUTH(s2_auth_credentials), TAG_END());
+
+  m = s2_wait_for_request(SIP_METHOD_REGISTER); fail_if(!m);
+  s2_respond_to(m, NULL,
+		SIP_401_UNAUTHORIZED,
+		SIPTAG_WWW_AUTHENTICATE_STR(s2_auth2_digest_str),
+		SIPTAG_PROXY_AUTHENTICATE_STR(s2_auth_digest_str),
+		TAG_END());
+  s2_free_message(m);
+  s2_check_event(nua_r_register, 401);
+
+  nua_authenticate(nh, NUTAG_AUTH(s2_auth2_credentials), TAG_END());
+
+  m = s2_wait_for_request(SIP_METHOD_REGISTER);
+  fail_if(!m);
+  fail_if(!m->sip->sip_authorization);
+  fail_if(!m->sip->sip_proxy_authorization);
+  s2_save_register(m);
+
+  s2_respond_to(m, NULL,
+		SIP_200_OK,
+		SIPTAG_CONTACT(s2->registration->contact),
+		TAG_END());
+  s2_free_message(m);
+
+  assert(s2->registration->contact != NULL);
+  s2_check_event(nua_r_register, 200);
+
+  s2->registration->nh = nh;
+
+  s2_register_teardown();
+
+} END_TEST
+
+/* ---------------------------------------------------------------------- */
+
+static char const *receive_natted = "received=4.255.255.9";
+
+/* Return Via that looks very natted */
+static sip_via_t *natted_via(struct message *m)
+{
+  su_home_t *h;
+  sip_via_t *via;
+
+  h = msg_home(m->msg);
+  via = sip_via_dup(h, m->sip->sip_via);
+  msg_header_replace_param(h, via->v_common, receive_natted);
+
+  if (via->v_protocol == sip_transport_udp)
+    msg_header_replace_param(h, via->v_common, "rport=9");
+
+  if (via->v_protocol == sip_transport_tcp && via->v_rport) {
+    tp_name_t const *tpn = tport_name(m->tport);
+    char *rport = su_sprintf(h, "rport=%s", tpn->tpn_port);
+    msg_header_replace_param(h, via->v_common, rport);
+  }
+
+  return via;
+}
+
+/* ---------------------------------------------------------------------- */
+
+START_TEST(register_1_2_1) {
+  nua_handle_t *nh;
+  struct message *m;
+
+  s2_case("1.2.1", "Register behind NAT",
+	  "Register through NAT, detect NAT, re-REGISTER");
+
+  nh = nua_handle(nua, NULL, TAG_END());
+
+  nua_register(nh, TAG_END());
+
+  m = s2_wait_for_request(SIP_METHOD_REGISTER);
+  fail_if(!m);
+  fail_if(!m->sip->sip_contact || m->sip->sip_contact->m_next);
+  s2_save_register(m);
+
+  s2_respond_to(m, NULL,
+		SIP_200_OK,
+		SIPTAG_CONTACT(s2->registration->contact),
+		SIPTAG_VIA(natted_via(m)),
+		TAG_END());
+  s2_free_message(m);
+
+  assert(s2->registration->contact != NULL);
+  s2_check_event(nua_r_register, 100);
+
+  m = s2_wait_for_request(SIP_METHOD_REGISTER);
+  fail_if(!m);
+  fail_if(!m->sip->sip_contact || !m->sip->sip_contact->m_next);
+  s2_save_register(m);
+
+  s2_respond_to(m, NULL,
+		SIP_200_OK,
+		SIPTAG_CONTACT(s2->registration->contact),
+		SIPTAG_VIA(natted_via(m)),
+		TAG_END());
+  s2_free_message(m);
+
+  fail_unless(s2->registration->contact != NULL);
+  fail_if(s2->registration->contact->m_next != NULL);
+  s2_check_event(nua_r_register, 200);
+
+  s2->registration->nh = nh;
+
+  s2_register_teardown();
+
+} END_TEST
+
+
+static nua_handle_t *make_auth_natted_register(
+  nua_handle_t *nh,
+  tag_type_t tag, tag_value_t value, ...)
+{
+  struct message *m;
+
+  ta_list ta;
+  ta_start(ta, tag, value);
+  nua_register(nh, ta_tags(ta));
+  ta_end(ta);
+
+  m = s2_wait_for_request(SIP_METHOD_REGISTER); fail_if(!m);
+  s2_respond_to(m, NULL,
+		SIP_401_UNAUTHORIZED,
+		SIPTAG_WWW_AUTHENTICATE_STR(s2_auth_digest_str),
+		SIPTAG_VIA(natted_via(m)),
+		TAG_END());
+  s2_free_message(m);
+
+  s2_check_event(nua_r_register, 401);
+
+  nua_authenticate(nh, NUTAG_AUTH(s2_auth_credentials), TAG_END());
+
+  m = s2_wait_for_request(SIP_METHOD_REGISTER);
+  fail_if(!m);
+  fail_if(!m->sip->sip_authorization);
+  s2_save_register(m);
+
+  s2_respond_to(m, NULL,
+		SIP_200_OK,
+		SIPTAG_CONTACT(s2->registration->contact),
+		SIPTAG_VIA(natted_via(m)),
+		TAG_END());
+  s2_free_message(m);
+
+  assert(s2->registration->contact != NULL);
+  s2_check_event(nua_r_register, 200);
+
+  return nh;
+}
+
+START_TEST(register_1_2_2_1)
+{
+  nua_handle_t *nh = nua_handle(nua, NULL, TAG_END());
+
+  s2_case("1.2.2.1", "Register behind NAT",
+	  "Authenticate, outbound activated");
+
+  mark_point();
+  make_auth_natted_register(nh, TAG_END());
+  s2->registration->nh = nh;
+  s2_register_teardown();
+}
+END_TEST
+
+START_TEST(register_1_2_2_2)
+{
+  nua_handle_t *nh = nua_handle(nua, NULL, TAG_END());
+  struct message *m;
+
+  s2_case("1.2.2.2", "Register behind NAT",
+	  "Authenticate, outbound activated, "
+	  "authenticate OPTIONS probe, "
+	  "NAT binding change");
+
+  mark_point();
+  make_auth_natted_register(nh, TAG_END());
+  s2->registration->nh = nh;
+
+  mark_point();
+
+  m = s2_wait_for_request(SIP_METHOD_OPTIONS);
+  fail_if(!m);
+  s2_respond_to(m, NULL,
+		SIP_407_PROXY_AUTH_REQUIRED,
+		SIPTAG_VIA(natted_via(m)),
+		SIPTAG_PROXY_AUTHENTICATE_STR(s2_auth_digest_str),
+		TAG_END());
+  s2_free_message(m);
+  mark_point();
+
+  m = s2_wait_for_request(SIP_METHOD_OPTIONS);
+  fail_if(!m); fail_if(!m->sip->sip_proxy_authorization);
+  s2_respond_to(m, NULL,
+		SIP_200_OK,
+		SIPTAG_VIA(natted_via(m)),
+		TAG_END());
+  s2_free_message(m);
+
+  su_root_step(s2->root, 20); su_root_step(s2->root, 20);
+  s2_fast_forward(120);	  /* Default keepalive interval */
+  mark_point();
+
+  m = s2_wait_for_request(SIP_METHOD_OPTIONS);
+  s2_respond_to(m, NULL,
+		SIP_200_OK,
+		SIPTAG_VIA(natted_via(m)),
+		TAG_END());
+  s2_free_message(m);
+
+  su_root_step(s2->root, 20); su_root_step(s2->root, 20);
+  s2_fast_forward(120);	  /* Default keepalive interval */
+  mark_point();
+
+  receive_natted = "received=4.255.255.10";
+
+  m = s2_wait_for_request(SIP_METHOD_OPTIONS);
+  s2_respond_to(m, NULL,
+		SIP_200_OK,
+		SIPTAG_VIA(natted_via(m)),
+		TAG_END());
+  s2_free_message(m);
+
+  s2_check_event(nua_i_outbound, 0);
+
+  m = s2_wait_for_request(SIP_METHOD_REGISTER);
+  fail_if(!m); fail_if(!m->sip->sip_authorization);
+  fail_if(!m->sip->sip_contact || !m->sip->sip_contact->m_next);
+  s2_save_register(m);
+
+  s2_respond_to(m, NULL,
+		SIP_200_OK,
+		SIPTAG_CONTACT(s2->registration->contact),
+		SIPTAG_VIA(natted_via(m)),
+		TAG_END());
+  s2_free_message(m);
+
+  s2_check_event(nua_r_register, 200);
+
+  fail_unless(s2->registration->contact != NULL);
+  fail_if(s2->registration->contact->m_next != NULL);
+
+  s2_register_teardown();
+
+} END_TEST
+
+
+START_TEST(register_1_2_2_3)
+{
+  nua_handle_t *nh = nua_handle(nua, NULL, TAG_END());
+  struct message *m;
+
+  s2_case("1.2.2.3", "Register behind NAT",
+	  "Authenticate, outbound activated, "
+	  "detect NAT binding change when re-REGISTERing");
+
+  mark_point();
+
+  make_auth_natted_register(nh,
+			    NUTAG_OUTBOUND("no-options-keepalive"),
+			    TAG_END());
+  s2->registration->nh = nh;
+
+  receive_natted = "received=4.255.255.10";
+
+  s2_fast_forward(3600);
+  mark_point();
+
+  m = s2_wait_for_request(SIP_METHOD_REGISTER);
+  fail_if(!m); fail_if(!m->sip->sip_authorization);
+  s2_save_register(m);
+
+  s2_respond_to(m, NULL,
+		SIP_200_OK,
+		SIPTAG_CONTACT(s2->registration->contact),
+		SIPTAG_VIA(natted_via(m)),
+		TAG_END());
+  s2_free_message(m);
+
+  s2_check_event(nua_r_register, 100);
+
+  m = s2_wait_for_request(SIP_METHOD_REGISTER);
+  fail_if(!m); fail_if(!m->sip->sip_authorization);
+  fail_if(!m->sip->sip_contact || !m->sip->sip_contact->m_next);
+  s2_save_register(m);
+
+  s2_respond_to(m, NULL,
+		SIP_200_OK,
+		SIPTAG_CONTACT(s2->registration->contact),
+		SIPTAG_VIA(natted_via(m)),
+		TAG_END());
+  s2_free_message(m);
+
+  fail_unless(s2->registration->contact != NULL);
+  fail_if(s2->registration->contact->m_next != NULL);
+
+  s2_check_event(nua_r_register, 200);
+
+  s2_register_teardown();
+
+} END_TEST
+
+
+START_TEST(register_1_2_3) {
+  nua_handle_t *nh;
+  struct message *m;
+
+  s2_case("1.2.3", "Register behind NAT",
+	  "Outbound activated by error response");
+
+  nh = nua_handle(nua, NULL, TAG_END());
+  nua_register(nh, TAG_END());
+
+  mark_point();
+
+  m = s2_wait_for_request(SIP_METHOD_REGISTER);
+  fail_if(!m);
+  fail_if(!m->sip->sip_contact || m->sip->sip_contact->m_next);
+
+  s2_respond_to(m, NULL,
+		400, "Bad Contact",
+		SIPTAG_VIA(natted_via(m)),
+		TAG_END());
+  s2_free_message(m);
+
+  s2_check_event(nua_r_register, 100);
+
+  m = s2_wait_for_request(SIP_METHOD_REGISTER);
+  fail_if(!m);
+  s2_save_register(m);
+
+  s2_respond_to(m, NULL,
+		SIP_200_OK,
+		SIPTAG_CONTACT(s2->registration->contact),
+		SIPTAG_VIA(natted_via(m)),
+		TAG_END());
+  s2_free_message(m);
+
+  fail_unless(s2->registration->contact != NULL);
+  fail_if(s2->registration->contact->m_next != NULL);
+  s2_check_event(nua_r_register, 200);
+
+  s2->registration->nh = nh;
+
+  s2_register_teardown();
+
+} END_TEST
+
+
+/* ---------------------------------------------------------------------- */
+
+START_TEST(register_1_3_1)
+{
+  nua_handle_t *nh;
+  struct message *m;
+
+  s2_case("1.3.1", "Register over TCP via NAT",
+	  "REGISTER via TCP, detect NTA, re-REGISTER");
+
+  nh = nua_handle(nua, NULL, TAG_END());
+
+  nua_register(nh, NUTAG_PROXY(s2->tcp.contact->m_url), TAG_END());
+
+  m = s2_wait_for_request(SIP_METHOD_REGISTER);
+  fail_if(!m); fail_if(!m->sip->sip_contact || m->sip->sip_contact->m_next);
+  fail_if(!tport_is_tcp(m->tport));
+  s2_save_register(m);
+
+  s2_respond_to(m, NULL,
+		SIP_200_OK,
+		SIPTAG_CONTACT(s2->registration->contact),
+		SIPTAG_VIA(natted_via(m)),
+		TAG_END());
+  s2_free_message(m);
+
+  assert(s2->registration->contact != NULL);
+  s2_check_event(nua_r_register, 100);
+
+  m = s2_wait_for_request(SIP_METHOD_REGISTER);
+  fail_if(!m);
+  fail_if(!m->sip->sip_contact || !m->sip->sip_contact->m_next);
+  s2_save_register(m);
+
+  s2_respond_to(m, NULL,
+		SIP_200_OK,
+		SIPTAG_CONTACT(s2->registration->contact),
+		SIPTAG_VIA(natted_via(m)),
+		TAG_END());
+  s2_free_message(m);
+
+  fail_unless(s2->registration->contact != NULL);
+  fail_if(s2->registration->contact->m_next != NULL);
+  fail_unless(
+    url_has_param(s2->registration->contact->m_url, "transport=tcp"));
+  s2_check_event(nua_r_register, 200);
+
+  s2->registration->nh = nh;
+
+  s2_register_teardown();
+
+} END_TEST
+
+
+START_TEST(register_1_3_2_1)
+{
+  nua_handle_t *nh = nua_handle(nua, NULL, TAG_END());
+
+  s2_case("1.3.2.1", "Register behind NAT",
+	  "Authenticate, outbound activated");
+
+  mark_point();
+  s2->registration->nh = nh;
+  make_auth_natted_register(nh, NUTAG_PROXY(s2->tcp.contact->m_url), TAG_END());
+  fail_if(!tport_is_tcp(s2->registration->tport));
+  s2_register_teardown();
+}
+END_TEST
+
+
+START_TEST(register_1_3_2_2)
+{
+  nua_handle_t *nh = nua_handle(nua, NULL, TAG_END());
+  struct message *m;
+
+  s2_case("1.3.2.2", "Register behind NAT with TCP",
+	  "Detect NAT over TCP using rport. "
+	  "Authenticate, detect NAT, "
+	  "close TCP at server, wait for re-REGISTERs.");
+
+  nua_set_params(nua, NTATAG_TCP_RPORT(1), TAG_END());
+  s2_check_event(nua_r_set_params, 200);
+
+  mark_point();
+  s2->registration->nh = nh;
+  make_auth_natted_register(
+    nh, NUTAG_PROXY(s2->tcp.contact->m_url),
+    NUTAG_OUTBOUND("no-options-keepalive, no-validate"),
+    TAG_END());
+  fail_if(!tport_is_tcp(s2->registration->tport));
+  tport_shutdown(s2->registration->tport, 2);
+
+  m = s2_wait_for_request(SIP_METHOD_REGISTER);
+  fail_if(!m); fail_if(!m->sip->sip_authorization);
+  s2_save_register(m);
+
+  s2_respond_to(m, NULL,
+		SIP_200_OK,
+		SIPTAG_CONTACT(s2->registration->contact),
+		SIPTAG_VIA(natted_via(m)),
+		TAG_END());
+  s2_free_message(m);
+
+  /* The "NAT binding" changed when new TCP connection is established */
+  /* => NUA re-REGISTERs with newly detected contact */
+  s2_check_event(nua_r_register, 100);
+
+  m = s2_wait_for_request(SIP_METHOD_REGISTER);
+  fail_if(!m); fail_if(!m->sip->sip_authorization);
+  fail_if(!m->sip->sip_contact || !m->sip->sip_contact->m_next);
+  s2_save_register(m);
+
+  s2_respond_to(m, NULL,
+		SIP_200_OK,
+		SIPTAG_CONTACT(s2->registration->contact),
+		SIPTAG_VIA(natted_via(m)),
+		TAG_END());
+  s2_free_message(m);
+
+  s2_check_event(nua_r_register, 200);
+
+  fail_unless(s2->registration->contact != NULL);
+  fail_if(s2->registration->contact->m_next != NULL);
+
+  s2_register_teardown();
+}
+END_TEST
+
+START_TEST(register_1_3_3_1)
+{
+  nua_handle_t *nh = nua_handle(nua, NULL, TAG_END());
+  struct message *m;
+  tport_t *tcp;
+
+  s2_case("1.3.3.1", "Register behind NAT with UDP and TCP",
+	  "Register with UDP, UDP time-outing, then w/ TCP using rport. ");
+
+  nua_set_params(nua, NTATAG_TCP_RPORT(1), TAG_END());
+  s2_check_event(nua_r_set_params, 200);
+
+  mark_point();
+  s2->registration->nh = nh;
+
+  nua_register(nh,
+	       NUTAG_OUTBOUND("no-options-keepalive, no-validate"),
+	       TAG_END());
+
+  /* NTA tries with UDP, we drop them */
+  for (;;) {
+    m = s2_wait_for_request(SIP_METHOD_REGISTER); fail_if(!m);
+    if (!tport_is_udp(m->tport)) /* Drop UDP */
+      break;
+    s2_free_message(m);
+    s2_fast_forward(4);
+  }
+
+  tcp = tport_ref(m->tport);
+
+  /* Respond to request over TCP */
+  s2_respond_to(m, NULL,
+		SIP_401_UNAUTHORIZED,
+		SIPTAG_WWW_AUTHENTICATE_STR(s2_auth_digest_str),
+		SIPTAG_VIA(natted_via(m)),
+		TAG_END());
+  s2_free_message(m);
+  s2_check_event(nua_r_register, 401);
+  nua_authenticate(nh, NUTAG_AUTH(s2_auth_credentials), TAG_END());
+
+  /* Turn off pong */
+  tport_set_params(tcp, TPTAG_PONG2PING(0), TAG_END());
+
+  /* Now request over UDP ... registering TCP contact! */
+  m = s2_wait_for_request(SIP_METHOD_REGISTER);
+  fail_if(!m); fail_if(!m->sip->sip_authorization);
+  s2_save_register(m);
+  fail_unless(
+    url_has_param(s2->registration->contact->m_url, "transport=tcp"));
+  s2_respond_to(m, NULL,
+		SIP_200_OK,
+		SIPTAG_CONTACT(s2->registration->contact),
+		SIPTAG_VIA(natted_via(m)),
+		TAG_END());
+  s2_free_message(m);
+
+  /* NUA detects oops... re-registers UDP */
+  s2_check_event(nua_r_register, 100);
+
+  m = s2_wait_for_request(SIP_METHOD_REGISTER);
+  fail_if(!m); fail_if(!m->sip->sip_authorization);
+  fail_if(!m->sip->sip_contact || !m->sip->sip_contact->m_next);
+  s2_save_register(m);
+
+  s2_respond_to(m, NULL,
+		SIP_200_OK,
+		SIPTAG_CONTACT(s2->registration->contact),
+		SIPTAG_VIA(natted_via(m)),
+		TAG_END());
+  s2_free_message(m);
+
+  s2_check_event(nua_r_register, 200);
+
+  fail_unless(s2->registration->contact != NULL);
+  fail_if(s2->registration->contact->m_next != NULL);
+
+  /* Wait until ping-pong failure closes the TCP connection */
+  {
+    int i;
+    for (i = 0; i < 5; i++) {
+      su_root_step(s2->root, 5);
+      su_root_step(s2->root, 5);
+      su_root_step(s2->root, 5);
+      s2_fast_forward(5);
+    }
+  }
+
+  s2_register_teardown();
+}
+END_TEST
+
+/* ---------------------------------------------------------------------- */
+
+TCase *register_tcase(void)
+{
+  TCase *tc = tcase_create("1 - REGISTER");
+  /* Each testcase is run in different process */
+  tcase_add_checked_fixture(tc, register_setup, register_teardown);
+  {
+    tcase_add_test(tc, register_1_0_1);
+    tcase_add_test(tc, register_1_1_1);
+    tcase_add_test(tc, register_1_1_2);
+    tcase_add_test(tc, register_1_2_1);
+    tcase_add_test(tc, register_1_2_2_1);
+    tcase_add_test(tc, register_1_2_2_2);
+    tcase_add_test(tc, register_1_2_2_3);
+    tcase_add_test(tc, register_1_2_3);
+    tcase_add_test(tc, register_1_3_1);
+    tcase_add_test(tc, register_1_3_2_1);
+    tcase_add_test(tc, register_1_3_2_2);
+  }
+  tcase_set_timeout(tc, 5);
+  return tc;
+}
+
+TCase *pingpong_tcase(void)
+{
+  TCase *tc = tcase_create("1 - REGISTER with PingPong");
+  /* Each testcase is run in different process */
+  tcase_add_checked_fixture(tc, register_pingpong_setup, register_teardown);
+  {
+    tcase_add_test(tc, register_1_3_3_1);
+  }
+  tcase_set_timeout(tc, 5);
+  return tc;
+}
+
+void check_register_cases(Suite *suite)
+{
+  suite_add_tcase(suite, register_tcase());
+  suite_add_tcase(suite, pingpong_tcase());
+}
+

Added: freeswitch/trunk/libs/sofia-sip/libsofia-sip-ua/nua/check_session.c
==============================================================================
--- (empty file)
+++ freeswitch/trunk/libs/sofia-sip/libsofia-sip-ua/nua/check_session.c	Wed May 14 15:10:54 2008
@@ -0,0 +1,1564 @@
+/*
+ * This file is part of the Sofia-SIP package
+ *
+ * Copyright (C) 2008 Nokia Corporation.
+ *
+ * Contact: Pekka Pessi <pekka.pessi at nokia.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA
+ *
+ */
+
+/**@CFILE check_session.c
+ *
+ * @brief NUA module tests for SIP session handling
+ *
+ * @author Pekka Pessi <Pekka.Pessi at nokia.com>
+ *
+ * @copyright (C) 2008 Nokia Corporation.
+ */
+
+#include "config.h"
+
+#include "check_nua.h"
+
+#include "test_s2.h"
+
+#include <sofia-sip/sip_status.h>
+#include <sofia-sip/sip_header.h>
+#include <sofia-sip/soa.h>
+#include <sofia-sip/su_tagarg.h>
+#include <sofia-sip/su_tag_io.h>
+
+#include <stdlib.h>
+#include <string.h>
+#include <assert.h>
+
+/* ====================================================================== */
+/* Call cases */
+
+static nua_t *nua;
+static soa_session_t *soa = NULL;
+static struct dialog *dialog = NULL;
+
+#define CRLF "\r\n"
+
+static void call_setup(void)
+{
+  s2_case("0.1.1", "Setup for Call Tests", "");
+
+  nua = s2_nua_setup(TAG_END());
+
+  soa = soa_create(NULL, s2->root, NULL);
+
+  fail_if(!soa);
+
+  soa_set_params(soa,
+		 SOATAG_USER_SDP_STR("m=audio 5008 RTP/AVP 8 0" CRLF
+				     "m=video 5010 RTP/AVP 34" CRLF),
+		 TAG_END());
+
+  dialog = su_home_new(sizeof *dialog); fail_if(!dialog);
+
+  s2_register_setup();
+}
+
+static void call_teardown(void)
+{
+  s2_case("0.1.2", "Teardown Call Test Setup", "");
+
+  mark_point();
+
+  s2_register_teardown();
+
+  nua_shutdown(nua);
+  fail_unless(s2_check_event(nua_r_shutdown, 200));
+
+  s2_nua_teardown();
+}
+
+static void save_sdp_to_soa(struct message *message)
+{
+  sip_payload_t *pl;
+  char const *body;
+  isize_t bodylen;
+
+  fail_if(!message);
+
+  fail_if(!message->sip->sip_content_length);
+  fail_if(!message->sip->sip_content_type);
+  fail_if(strcmp(message->sip->sip_content_type->c_type,
+		 "application/sdp"));
+
+  fail_if(!message->sip->sip_payload);
+  pl = message->sip->sip_payload;
+  body = pl->pl_data, bodylen = pl->pl_len;
+
+  fail_if(soa_set_remote_sdp(soa, NULL, body, (issize_t)bodylen) < 0);
+}
+
+static void process_offer(struct message *message)
+{
+  save_sdp_to_soa(message);
+  fail_if(soa_generate_answer(soa, NULL) < 0);
+}
+
+static void process_answer(struct message *message)
+{
+  save_sdp_to_soa(message);
+  fail_if(soa_process_answer(soa, NULL) < 0);
+}
+
+static void
+respond_with_sdp(struct message *request,
+		 struct dialog *dialog,
+		 int status, char const *phrase,
+		 tag_type_t tag, tag_value_t value, ...)
+{
+  ta_list ta;
+
+  char const *body;
+  isize_t bodylen;
+
+  fail_if(soa_get_local_sdp(soa, NULL, &body, &bodylen) != 1);
+
+  ta_start(ta, tag, value);
+  s2_respond_to(request, dialog, status, phrase,
+		SIPTAG_CONTENT_TYPE_STR("application/sdp"),
+		SIPTAG_PAYLOAD_STR(body),
+		SIPTAG_CONTENT_DISPOSITION_STR("session"),
+		ta_tags(ta));
+  ta_end(ta);
+}
+
+static void
+request_with_sdp(struct dialog *dialog,
+		 sip_method_t method, char const *name,
+		 tport_t *tport,
+		 tag_type_t tag, tag_value_t value, ...)
+{
+  ta_list ta;
+
+  char const *body;
+  isize_t bodylen;
+
+  fail_if(soa_get_local_sdp(soa, NULL, &body, &bodylen) != 1);
+
+  ta_start(ta, tag, value);
+  fail_if(
+    s2_request_to(dialog, method, name, tport,
+		  SIPTAG_CONTENT_TYPE_STR("application/sdp"),
+		  SIPTAG_PAYLOAD_STR(body),
+		  ta_tags(ta)));
+  ta_end(ta);
+}
+
+static struct message *
+invite_sent_by_nua(nua_handle_t *nh,
+		   tag_type_t tag, tag_value_t value, ...)
+{
+  ta_list ta;
+  ta_start(ta, tag, value);
+  nua_invite(nh, SOATAG_USER_SDP_STR("m=audio 5004 RTP/AVP 0 8"),
+	     ta_tags(ta));
+  ta_end(ta);
+
+  fail_unless(s2_check_callstate(nua_callstate_calling));
+
+  return s2_wait_for_request(SIP_METHOD_INVITE);
+}
+
+static struct message *
+respond_with_100rel(struct message *invite,
+		    struct dialog *d,
+		    int sdp,
+		    int status, char const *phrase,
+		    tag_type_t tag, tag_value_t value, ...)
+{
+  struct message *prack;
+  ta_list ta;
+  static uint32_t rseq;
+  sip_rseq_t rs[1];
+
+  assert(100 < status && status < 200);
+
+  sip_rseq_init(rs);
+  rs->rs_response = ++rseq;
+
+  ta_start(ta, tag, value);
+
+  if (sdp) {
+    respond_with_sdp(
+      invite, dialog, status, phrase,
+      SIPTAG_REQUIRE_STR("100rel"),
+      SIPTAG_RSEQ(rs),
+      ta_tags(ta));
+  }
+  else {
+    s2_respond_to(
+      invite, dialog, status, phrase,
+      SIPTAG_REQUIRE_STR("100rel"),
+      SIPTAG_RSEQ(rs),
+      ta_tags(ta));
+  }
+  ta_end(ta);
+
+  fail_unless(s2_check_event(nua_r_invite, status));
+
+  prack = s2_wait_for_request(SIP_METHOD_PRACK);
+  /* Assumes auto-prack, so there is no offer in prack */
+  s2_respond_to(prack, dialog, SIP_200_OK, TAG_END());
+
+  return prack;
+}
+
+static void
+invite_by_nua(nua_handle_t *nh,
+	      tag_type_t tag, tag_value_t value, ...)
+{
+  struct message *invite;
+  ta_list ta;
+
+  ta_start(ta, tag, value);
+  invite = invite_sent_by_nua(
+    nh, SOATAG_USER_SDP_STR("m=audio 5004 RTP/AVP 0 8"),
+    ta_tags(ta));
+  ta_end(ta);
+
+  process_offer(invite);
+  respond_with_sdp(
+    invite, dialog, SIP_180_RINGING,
+    SIPTAG_CONTENT_DISPOSITION_STR("session;handling=optional"),
+    TAG_END());
+
+  fail_unless(s2_check_event(nua_r_invite, 180));
+  fail_unless(s2_check_callstate(nua_callstate_proceeding));
+
+  respond_with_sdp(invite, dialog, SIP_200_OK, TAG_END());
+  s2_free_message(invite);
+  fail_unless(s2_check_event(nua_r_invite, 200));
+  fail_unless(s2_check_callstate(nua_callstate_ready));
+  fail_unless(s2_check_request(SIP_METHOD_ACK));
+}
+
+static nua_handle_t *
+invite_to_nua(tag_type_t tag, tag_value_t value, ...)
+{
+  ta_list ta;
+  struct event *invite;
+  struct message *response;
+  nua_handle_t *nh;
+  sip_cseq_t cseq[1];
+
+  soa_generate_offer(soa, 1, NULL);
+
+  ta_start(ta, tag, value);
+  request_with_sdp(dialog, SIP_METHOD_INVITE, NULL, ta_tags(ta));
+  ta_end(ta);
+
+  invite = s2_wait_for_event(nua_i_invite, 100); fail_unless(invite != NULL);
+  fail_unless(s2_check_callstate(nua_callstate_received));
+
+  nh = invite->nh;
+  fail_if(!nh);
+
+  sip_cseq_init(cseq);
+  cseq->cs_method = sip_method_ack;
+  cseq->cs_method_name = "ACK";
+  cseq->cs_seq = sip_object(invite->data->e_msg)->sip_cseq->cs_seq;
+
+  s2_free_event(invite);
+
+  response = s2_wait_for_response(100, SIP_METHOD_INVITE);
+  fail_if(!response);
+
+  nua_respond(nh, SIP_180_RINGING,
+	      SOATAG_USER_SDP_STR("m=audio 5004 RTP/AVP 0 8"),
+	      TAG_END());
+  fail_unless(s2_check_callstate(nua_callstate_early));
+
+  response = s2_wait_for_response(180, SIP_METHOD_INVITE);
+  fail_if(!response);
+  s2_update_dialog(dialog, response);
+  process_answer(response);
+  s2_free_message(response);
+
+  nua_respond(nh, SIP_200_OK, TAG_END());
+
+  fail_unless(s2_check_callstate(nua_callstate_completed));
+
+  response = s2_wait_for_response(200, SIP_METHOD_INVITE);
+
+  fail_if(!response);
+  s2_update_dialog(dialog, response);
+  s2_free_message(response);
+
+  fail_if(s2_request_to(dialog, SIP_METHOD_ACK, NULL,
+			SIPTAG_CSEQ(cseq), TAG_END()));
+
+  fail_unless(s2_check_event(nua_i_ack, 200));
+  fail_unless(s2_check_callstate(nua_callstate_ready));
+
+  return nh;
+}
+
+static void
+bye_by_nua(nua_handle_t *nh,
+	   tag_type_t tag, tag_value_t value, ...)
+{
+  ta_list ta;
+  struct message *bye;
+
+  ta_start(ta, tag, value);
+  nua_bye(nh, ta_tags(ta));
+  ta_end(ta);
+
+  bye = s2_wait_for_request(SIP_METHOD_BYE);
+  fail_if(!bye);
+  s2_respond_to(bye, dialog, SIP_200_OK, TAG_END());
+  s2_free_message(bye);
+  fail_unless(s2_check_event(nua_r_bye, 200));
+  fail_unless(s2_check_callstate(nua_callstate_terminated));
+}
+
+static void 
+bye_by_nua_challenged(nua_handle_t *nh,
+		      tag_type_t tag, tag_value_t value, ...)
+{
+  ta_list ta;
+  struct message *bye;
+
+  s2_flush_events();
+
+  ta_start(ta, tag, value);
+  nua_bye(nh, ta_tags(ta));
+  ta_end(ta);
+
+  bye = s2_wait_for_request(SIP_METHOD_BYE);
+  fail_if(!bye);
+  s2_respond_to(bye, dialog, SIP_407_PROXY_AUTH_REQUIRED,
+		SIPTAG_PROXY_AUTHENTICATE_STR(s2_auth_digest_str),
+		TAG_END());
+  s2_free_message(bye);
+  fail_unless(s2_check_event(nua_r_bye, 407));
+
+  nua_authenticate(nh, NUTAG_AUTH("Digest:\"s2test\":abc:abc"), TAG_END());
+  bye = s2_wait_for_request(SIP_METHOD_BYE);
+  fail_if(!bye);
+  s2_respond_to(bye, dialog, SIP_200_OK, TAG_END());
+  s2_free_message(bye);
+  fail_unless(s2_check_event(nua_r_bye, 200));
+  fail_unless(s2_check_callstate(nua_callstate_terminated));
+  fail_if(s2->events);
+}
+
+
+static void 
+cancel_by_nua(nua_handle_t *nh,
+	      struct message *invite,
+	      struct dialog *dialog,
+	      tag_type_t tag, tag_value_t value, ...)
+{
+  ta_list ta;
+  struct message *cancel;
+
+  ta_start(ta, tag, value);
+  nua_cancel(nh, ta_tags(ta));
+  ta_end(ta);
+
+  cancel = s2_wait_for_request(SIP_METHOD_CANCEL);
+  fail_if(!cancel);
+  s2_respond_to(cancel, dialog, SIP_200_OK, TAG_END());
+  s2_free_message(cancel);
+  fail_unless(s2_check_event(nua_r_cancel, 200));
+
+  s2_respond_to(invite, dialog, SIP_487_REQUEST_CANCELLED, TAG_END());
+  fail_unless(s2_check_request(SIP_METHOD_ACK));
+
+  fail_unless(s2_check_event(nua_r_invite, 487));
+}
+
+static void 
+bye_to_nua(nua_handle_t *nh,
+	   tag_type_t tag, tag_value_t value, ...)
+{
+  ta_list ta;
+
+  ta_start(ta, tag, value);
+  fail_if(s2_request_to(dialog, SIP_METHOD_BYE, NULL, ta_tags(ta)));
+  ta_end(ta);
+
+  fail_unless(s2_check_event(nua_i_bye, 200));
+  fail_unless(s2_check_callstate(nua_callstate_terminated));
+  fail_unless(s2_check_response(200, SIP_METHOD_BYE));
+}
+
+/* ====================================================================== */
+/* 2 - Call cases */
+
+/* 2.1 - Basic call cases */
+
+START_TEST(basic_call_with_bye_by_nua)
+{
+  nua_handle_t *nh;
+
+  s2_case("2.1.1", "Basic call",
+	  "NUA sends INVITE, NUA sends BYE");
+
+  nh = nua_handle(nua, NULL, SIPTAG_TO(s2->local), TAG_END());
+
+  invite_by_nua(nh, TAG_END());
+
+  bye_by_nua(nh, TAG_END());
+
+  nua_handle_destroy(nh);
+}
+END_TEST
+
+
+START_TEST(basic_call_with_bye_to_nua)
+{
+  nua_handle_t *nh;
+
+  s2_case("2.1.2", "Basic call",
+	  "NUA sends INVITE, NUA receives BYE");
+
+  nh = nua_handle(nua, NULL, SIPTAG_TO(s2->local), TAG_END());
+
+  invite_by_nua(nh, TAG_END());
+
+  bye_to_nua(nh, TAG_END());
+
+  nua_handle_destroy(nh);
+}
+END_TEST
+
+
+START_TEST(call_to_nua_with_bye_to_nua)
+{
+  nua_handle_t *nh;
+
+  s2_case("2.1.3", "Incoming call",
+	  "NUA receives INVITE and BYE");
+
+  nh = invite_to_nua(TAG_END());
+
+  bye_to_nua(nh, TAG_END());
+
+  nua_handle_destroy(nh);
+}
+END_TEST
+
+
+START_TEST(call_to_nua_with_bye_by_nua)
+{
+  nua_handle_t *nh;
+
+  s2_case("2.1.4", "Incoming call",
+	  "NUA receives INVITE and sends BYE");
+
+  nh = invite_to_nua(TAG_END());
+
+  bye_by_nua(nh, TAG_END());
+
+  nua_handle_destroy(nh);
+}
+END_TEST
+
+
+START_TEST(call_to_nua_with_bye_by_nua_challenged)
+{
+  nua_handle_t *nh;
+
+  s2_case("2.1.5", "Incoming call",
+	  "NUA receives INVITE and sends BYE, BYE is challenged");
+
+  nh = invite_to_nua(TAG_END());
+
+  bye_by_nua_challenged(nh, TAG_END());
+
+  nua_handle_destroy(nh);
+}
+END_TEST
+
+
+START_TEST(call_2_1_6)
+{
+  nua_handle_t *nh;
+  struct message *bye;
+  struct event *invite;
+  struct message *response;
+  sip_cseq_t cseq[1];
+
+  s2_case("2.1.6", "Basic call",
+	  "NUA received INVITE, "
+	  "NUA responds (and saves proxy for dialog), "
+	  "NUA sends BYE");
+
+  soa_generate_offer(soa, 1, NULL);
+
+  request_with_sdp(dialog, SIP_METHOD_INVITE, NULL, TAG_END());
+
+  invite = s2_wait_for_event(nua_i_invite, 100); fail_unless(invite != NULL);
+  fail_unless(s2_check_callstate(nua_callstate_received));
+
+  nh = invite->nh;
+  fail_if(!nh);
+
+  sip_cseq_init(cseq);
+  cseq->cs_method = sip_method_ack;
+  cseq->cs_method_name = "ACK";
+  cseq->cs_seq = sip_object(invite->data->e_msg)->sip_cseq->cs_seq;
+
+  s2_free_event(invite);
+
+  response = s2_wait_for_response(100, SIP_METHOD_INVITE);
+  fail_if(!response);
+
+  nua_respond(nh, SIP_180_RINGING,
+	      /* Dialog-specific proxy is saved */
+	      NUTAG_PROXY(s2->tcp.contact->m_url),
+	      SOATAG_USER_SDP_STR("m=audio 5004 RTP/AVP 0 8"),
+	      TAG_END());
+  fail_unless(s2_check_callstate(nua_callstate_early));
+
+  response = s2_wait_for_response(180, SIP_METHOD_INVITE);
+  fail_if(!response);
+  s2_update_dialog(dialog, response);
+  process_answer(response);
+  s2_free_message(response);
+
+  nua_respond(nh, SIP_200_OK, TAG_END());
+
+  fail_unless(s2_check_callstate(nua_callstate_completed));
+
+  response = s2_wait_for_response(200, SIP_METHOD_INVITE);
+
+  fail_if(!response);
+  s2_update_dialog(dialog, response);
+  s2_free_message(response);
+
+  fail_if(s2_request_to(dialog, SIP_METHOD_ACK, NULL,
+			SIPTAG_CSEQ(cseq), TAG_END()));
+
+  fail_unless(s2_check_event(nua_i_ack, 200));
+  fail_unless(s2_check_callstate(nua_callstate_ready));
+
+  nua_bye(nh, TAG_END());
+  bye = s2_wait_for_request(SIP_METHOD_BYE);
+  fail_if(!bye);
+  /* Check that NUA used dialog-specific proxy with BYE */
+  fail_unless(tport_is_tcp(bye->tport));
+  s2_respond_to(bye, dialog, SIP_200_OK, TAG_END());
+  s2_free_message(bye);
+  fail_unless(s2_check_event(nua_r_bye, 200));
+  fail_unless(s2_check_callstate(nua_callstate_terminated));
+
+  nua_handle_destroy(nh);
+}
+END_TEST
+
+
+TCase *invite_tcase(void)
+{
+  TCase *tc = tcase_create("2.1 - Basic INVITE");
+  tcase_add_checked_fixture(tc, call_setup, call_teardown);
+  {
+    tcase_add_test(tc, basic_call_with_bye_by_nua);
+    tcase_add_test(tc, basic_call_with_bye_to_nua);
+    tcase_add_test(tc, call_to_nua_with_bye_to_nua);
+    tcase_add_test(tc, call_to_nua_with_bye_by_nua);
+    tcase_add_test(tc, call_to_nua_with_bye_by_nua_challenged);
+    tcase_add_test(tc, call_2_1_6);
+  }
+  return tc;
+}
+
+/* ---------------------------------------------------------------------- */
+/* 2.2 - Call CANCEL cases */
+
+START_TEST(cancel_outgoing)
+{
+  nua_handle_t *nh;
+  struct message *invite, *cancel;
+
+  s2_case("2.2.1", "Cancel call",
+	  "NUA is callee, NUA sends CANCEL immediately");
+
+  nh = nua_handle(nua, NULL, SIPTAG_TO(s2->local), TAG_END());
+
+  nua_invite(nh, SOATAG_USER_SDP_STR("m=audio 5004 RTP/AVP 0 8"),
+	     TAG_END());
+  fail_unless(s2_check_callstate(nua_callstate_calling));
+  nua_cancel(nh, TAG_END());
+
+  invite = s2_wait_for_request(SIP_METHOD_INVITE);
+  fail_if(!invite);
+  fail_if(s2->received != NULL);
+  s2_respond_to(invite, dialog, SIP_100_TRYING, TAG_END());
+  cancel = s2_wait_for_request(SIP_METHOD_CANCEL);
+  fail_if(!cancel);
+  s2_respond_to(invite, dialog, SIP_487_REQUEST_CANCELLED, TAG_END());
+  s2_respond_to(cancel, dialog, SIP_200_OK, TAG_END());
+
+  fail_unless(s2_check_request(SIP_METHOD_ACK));
+
+  fail_unless(s2_check_event(nua_r_invite, 487));
+  fail_unless(s2_check_callstate(nua_callstate_terminated));
+  fail_unless(s2_check_event(nua_r_cancel, 200));
+  fail_if(s2->events != NULL);
+
+  nua_handle_destroy(nh);
+}
+END_TEST
+
+
+START_TEST(cancel_outgoing_after_100)
+{
+  nua_handle_t *nh;
+  struct message *invite;
+
+  s2_case("2.2.2", "Canceled call",
+	  "NUA is callee, NUA sends CANCEL after receiving 100");
+
+  nh = nua_handle(nua, NULL, SIPTAG_TO(s2->local), TAG_END());
+
+  nua_invite(nh, SOATAG_USER_SDP_STR("m=audio 5004 RTP/AVP 0 8"),
+	     TAG_END());
+  fail_unless(s2_check_callstate(nua_callstate_calling));
+
+  invite = s2_wait_for_request(SIP_METHOD_INVITE);
+  process_offer(invite);
+  s2_respond_to(invite, dialog, SIP_100_TRYING, TAG_END());
+
+  cancel_by_nua(nh, invite, dialog, TAG_END());
+
+  fail_unless(s2_check_callstate(nua_callstate_terminated));
+
+  nua_handle_destroy(nh);
+}
+END_TEST
+
+
+START_TEST(cancel_outgoing_after_180)
+{
+  nua_handle_t *nh;
+  struct message *invite;
+
+  s2_case("2.2.3", "Canceled call",
+	  "NUA is callee, NUA sends CANCEL after receiving 180");
+
+  nh = nua_handle(nua, NULL, SIPTAG_TO(s2->local), TAG_END());
+
+  nua_invite(nh, SOATAG_USER_SDP_STR("m=audio 5004 RTP/AVP 0 8"),
+	     TAG_END());
+  fail_unless(s2_check_callstate(nua_callstate_calling));
+
+  invite = s2_wait_for_request(SIP_METHOD_INVITE);
+  process_offer(invite);
+  respond_with_sdp(
+    invite, dialog, SIP_180_RINGING,
+    SIPTAG_CONTENT_DISPOSITION_STR("session;handling=optional"),
+    TAG_END());
+  fail_unless(s2_check_event(nua_r_invite, 180));
+  fail_unless(s2_check_callstate(nua_callstate_proceeding));
+
+  cancel_by_nua(nh, invite, dialog, TAG_END());
+  fail_unless(s2_check_callstate(nua_callstate_terminated));
+
+  nua_handle_destroy(nh);
+}
+END_TEST
+
+
+START_TEST(cancel_outgoing_glare)
+{
+  nua_handle_t *nh;
+  struct message *invite, *cancel;
+
+  s2_case("2.2.4", "Cancel and 200 OK glare",
+	  "NUA is callee, NUA sends CANCEL after receiving 180 "
+	  "but UAS already sent 200 OK.");
+
+  nh = nua_handle(nua, NULL, SIPTAG_TO(s2->local), TAG_END());
+
+  nua_invite(nh, SOATAG_USER_SDP_STR("m=audio 5004 RTP/AVP 0 8"),
+	     TAG_END());
+  fail_unless(s2_check_callstate(nua_callstate_calling));
+
+  invite = s2_wait_for_request(SIP_METHOD_INVITE);
+  process_offer(invite);
+  respond_with_sdp(
+    invite, dialog, SIP_180_RINGING,
+    SIPTAG_CONTENT_DISPOSITION_STR("session;handling=optional"),
+    TAG_END());
+  fail_unless(s2_check_event(nua_r_invite, 180));
+  fail_unless(s2_check_callstate(nua_callstate_proceeding));
+
+  nua_cancel(nh, TAG_END());
+  cancel = s2_wait_for_request(SIP_METHOD_CANCEL);
+  fail_if(!cancel);
+
+  respond_with_sdp(invite, dialog, SIP_200_OK, TAG_END());
+
+  s2_respond_to(cancel, dialog, SIP_481_NO_TRANSACTION, TAG_END());
+  s2_free_message(cancel);
+  fail_unless(s2_check_event(nua_r_cancel, 481));
+
+  fail_unless(s2_check_request(SIP_METHOD_ACK));
+
+  fail_unless(s2_check_callstate(nua_callstate_ready));
+
+  bye_by_nua(nh, TAG_END());
+
+  nua_handle_destroy(nh);
+}
+END_TEST
+
+
+TCase *cancel_tcase(void)
+{
+  TCase *tc = tcase_create("2.2 - CANCEL");
+  tcase_add_checked_fixture(tc, call_setup, call_teardown);
+
+  tcase_add_test(tc, cancel_outgoing);
+  tcase_add_test(tc, cancel_outgoing_after_100);
+  tcase_add_test(tc, cancel_outgoing_after_180);
+  tcase_add_test(tc, cancel_outgoing_glare);
+
+  return tc;
+}
+
+
+/* ---------------------------------------------------------------------- */
+/* 2.3 - Session timers */
+
+static void invite_timer_round(nua_handle_t *nh,
+			       char const *session_expires)
+{
+  struct message *invite, *ack;
+
+  fail_unless(s2_check_callstate(nua_callstate_calling));
+  invite = s2_wait_for_request(SIP_METHOD_INVITE);
+  process_offer(invite);
+  respond_with_sdp(
+    invite, dialog, SIP_200_OK,
+    SIPTAG_SESSION_EXPIRES_STR(session_expires),
+    SIPTAG_REQUIRE_STR("timer"),
+    TAG_END());
+  s2_free_message(invite);
+  fail_unless(s2_check_event(nua_r_invite, 200));
+  fail_unless(s2_check_callstate(nua_callstate_ready));
+  ack = s2_wait_for_request(SIP_METHOD_ACK);
+  s2_free_message(ack);
+}
+
+START_TEST(call_to_nua_with_timer)
+{
+  nua_handle_t *nh;
+
+  s2_case("2.3.1", "Incoming call with call timers",
+	  "NUA receives INVITE, "
+	  "activates call timers, "
+	  "sends re-INVITE twice, "
+	  "sends BYE.");
+
+  nh = invite_to_nua(
+    SIPTAG_SESSION_EXPIRES_STR("300;refresher=uas"),
+    SIPTAG_REQUIRE_STR("timer"),
+    TAG_END());
+
+  s2_fast_forward(300);
+  invite_timer_round(nh, "300;refresher=uac");
+  s2_fast_forward(300);
+  invite_timer_round(nh, "300;refresher=uac");
+
+  bye_by_nua(nh, TAG_END());
+
+  nua_handle_destroy(nh);
+}
+END_TEST
+
+START_TEST(call_to_nua_with_timer_2)
+{
+  nua_handle_t *nh;
+
+  s2_case("2.3.2", "Incoming call with call timers",
+	  "NUA receives INVITE, "
+	  "activates call timers, "
+	  "sends re-INVITE, "
+	  "sends BYE.");
+
+  nh = invite_to_nua(
+    SIPTAG_SESSION_EXPIRES_STR("300;refresher=uas"),
+    SIPTAG_REQUIRE_STR("timer"),
+    TAG_END());
+
+  s2_fast_forward(300);
+  invite_timer_round(nh, "300");
+  s2_fast_forward(300);
+  invite_timer_round(nh, "300");
+
+  bye_by_nua(nh, TAG_END());
+
+  nua_handle_destroy(nh);
+}
+END_TEST
+
+
+TCase *session_timer_tcase(void)
+{
+  TCase *tc = tcase_create("2.3 - Session timers");
+  tcase_add_checked_fixture(tc, call_setup, call_teardown);
+  {
+    tcase_add_test(tc, call_to_nua_with_timer);
+    tcase_add_test(tc, call_to_nua_with_timer_2);
+  }
+  return tc;
+}
+
+/* ====================================================================== */
+/* 2.4 - 100rel */
+
+START_TEST(call_with_prack_by_nua)
+{
+  nua_handle_t *nh;
+  struct message *invite, *prack;
+
+  s2_case("2.4.1", "Call with 100rel",
+	  "NUA sends INVITE, "
+	  "receives 183, sends PRACK, receives 200 for it, "
+	  "receives 180, sends PRACK, receives 200 for it, "
+          "receives 200, send ACK.");
+
+  nh = nua_handle(nua, NULL, SIPTAG_TO(s2->local), TAG_END());
+
+  invite = invite_sent_by_nua(
+    nh, SOATAG_USER_SDP_STR("m=audio 5004 RTP/AVP 0 8"),
+    TAG_END());
+  process_offer(invite);
+
+  prack = respond_with_100rel(invite, dialog, 1,
+			      SIP_183_SESSION_PROGRESS,
+			      TAG_END());
+  s2_free_message(prack), prack = NULL;
+  fail_unless(s2_check_callstate(nua_callstate_proceeding));
+  fail_unless(s2_check_event(nua_r_prack, 200));
+
+  prack = respond_with_100rel(invite, dialog, 0,
+			      SIP_180_RINGING,
+			      TAG_END());
+  s2_free_message(prack), prack = NULL;
+  fail_unless(s2_check_callstate(nua_callstate_proceeding));
+  fail_unless(s2_check_event(nua_r_prack, 200));
+
+  s2_respond_to(invite, dialog, SIP_200_OK, TAG_END());
+  s2_free_message(invite);
+  fail_unless(s2_check_event(nua_r_invite, 200));
+  fail_unless(s2_check_callstate(nua_callstate_ready));
+  fail_unless(s2_check_request(SIP_METHOD_ACK));
+
+  bye_to_nua(nh, TAG_END());
+
+  nua_handle_destroy(nh);
+}
+END_TEST
+
+START_TEST(call_with_prack_sans_soa)
+{
+  nua_handle_t *nh;
+  struct message *invite, *prack;
+
+  s2_case("2.4.1", "Call with 100rel",
+	  "NUA sends INVITE, "
+	  "receives 183, sends PRACK, receives 200 for it, "
+	  "receives 180, sends PRACK, receives 200 for it, "
+          "receives 200, send ACK.");
+
+  nh = nua_handle(nua, NULL, SIPTAG_TO(s2->local), TAG_END());
+
+  invite = invite_sent_by_nua(
+    nh,
+    NUTAG_MEDIA_ENABLE(0),
+    SIPTAG_CONTENT_TYPE_STR("application/sdp"),
+    SIPTAG_PAYLOAD_STR(
+      "v=0" CRLF
+      "o=- 6805647540234172778 5821668777690722690 IN IP4 127.0.0.1" CRLF
+      "s=-" CRLF
+      "c=IN IP4 127.0.0.1" CRLF
+      "m=audio 5004 RTP/AVP 0 8" CRLF),
+    TAG_END());
+
+  prack = respond_with_100rel(invite, dialog, 0,
+			      SIP_183_SESSION_PROGRESS,
+			      TAG_END());
+  s2_free_message(prack), prack = NULL;
+  fail_unless(s2_check_callstate(nua_callstate_proceeding));
+  fail_unless(s2_check_event(nua_r_prack, 200));
+
+  prack = respond_with_100rel(invite, dialog, 0,
+			      SIP_180_RINGING,
+			      TAG_END());
+  s2_free_message(prack), prack = NULL;
+  fail_unless(s2_check_callstate(nua_callstate_proceeding));
+  fail_unless(s2_check_event(nua_r_prack, 200));
+
+  s2_respond_to(invite, dialog, SIP_200_OK, TAG_END());
+  s2_free_message(invite);
+  fail_unless(s2_check_event(nua_r_invite, 200));
+  fail_unless(s2_check_callstate(nua_callstate_ready));
+  fail_unless(s2_check_request(SIP_METHOD_ACK));
+
+  bye_to_nua(nh, TAG_END());
+
+  nua_handle_destroy(nh);
+}
+END_TEST
+
+TCase *invite_100rel_tcase(void)
+{
+  TCase *tc = tcase_create("2.4 - INVITE with 100rel");
+  tcase_add_checked_fixture(tc, call_setup, call_teardown);
+  {
+    tcase_add_test(tc, call_with_prack_by_nua);
+    tcase_add_test(tc, call_with_prack_sans_soa);
+  }
+  return tc;
+}
+
+/* ====================================================================== */
+/* 3.1 - Call error cases */
+
+START_TEST(call_forbidden)
+{
+  nua_handle_t *nh;
+  struct message *invite;
+
+  s2_case("3.1.1", "Call failure", "Call fails with 403 response");
+  nh = nua_handle(nua, NULL, SIPTAG_TO(s2->local),
+		  TAG_END());
+
+  nua_invite(nh, SOATAG_USER_SDP_STR("m=audio 5004 RTP/AVP 0 8"),
+	     TAG_END());
+
+  fail_unless(s2_check_callstate(nua_callstate_calling));
+
+  invite = s2_wait_for_request(SIP_METHOD_INVITE);
+  fail_if(!invite);
+  s2_respond_to(invite, NULL, SIP_403_FORBIDDEN, TAG_END());
+  s2_free_message(invite);
+
+  fail_unless(s2_check_request(SIP_METHOD_ACK));
+  fail_unless(s2_check_event(nua_r_invite, 403));
+  fail_unless(s2_check_callstate(nua_callstate_terminated));
+
+  nua_handle_destroy(nh);
+}
+END_TEST
+
+
+START_TEST(too_many_retrys)
+{
+  nua_handle_t *nh;
+  struct message *invite;
+  int i;
+
+  s2_case("3.1.2", "Call fails after too many retries",
+	  "Call fails after 4 times 500 Retry-After");
+
+  nh = nua_handle(nua, NULL, SIPTAG_TO(s2->local),
+		  NUTAG_RETRY_COUNT(3),
+		  TAG_END());
+
+  nua_invite(nh, SOATAG_USER_SDP_STR("m=audio 5004 RTP/AVP 0 8"),
+	     TAG_END());
+
+  for (i = 0;; i++) {
+    fail_unless(s2_check_callstate(nua_callstate_calling));
+    invite = s2_wait_for_request(SIP_METHOD_INVITE);
+    fail_if(!invite);
+    s2_respond_to(invite, NULL, SIP_500_INTERNAL_SERVER_ERROR,
+		  SIPTAG_RETRY_AFTER_STR("5"),
+		  TAG_END());
+    s2_free_message(invite);
+    fail_unless(s2_check_request(SIP_METHOD_ACK));
+    if (i == 3)
+      break;
+    fail_unless(s2_check_event(nua_r_invite, 100));
+    s2_fast_forward(5);
+  }
+
+  fail_unless(s2_check_event(nua_r_invite, 500));
+  fail_unless(s2_check_callstate(nua_callstate_terminated));
+
+  nua_handle_destroy(nh);
+}
+END_TEST
+
+START_TEST(reinvite_forbidden)
+{
+  nua_handle_t *nh;
+  struct message *invite;
+
+  s2_case("3.2.1", "Re-INVITE failure", "Re-INVITE fails with 403 response");
+  nh = nua_handle(nua, NULL, SIPTAG_TO(s2->local),
+		  TAG_END());
+
+  invite_by_nua(nh, TAG_END());
+
+  nua_invite(nh, TAG_END());
+
+  fail_unless(s2_check_callstate(nua_callstate_calling));
+
+  invite = s2_wait_for_request(SIP_METHOD_INVITE);
+  fail_if(!invite);
+  s2_respond_to(invite, NULL, SIP_403_FORBIDDEN, TAG_END());
+  s2_free_message(invite);
+
+  fail_unless(s2_check_request(SIP_METHOD_ACK));
+  fail_unless(s2_check_event(nua_r_invite, 403));
+  /* Return to previous state */
+  fail_unless(s2_check_callstate(nua_callstate_ready));
+
+  bye_by_nua(nh, TAG_END());
+}
+END_TEST
+
+
+START_TEST(reinvite_too_many_retrys)
+{
+  nua_handle_t *nh;
+  struct message *invite, *bye;
+  int i;
+
+  s2_case("3.2.2", "Re-INVITE fails after too many retries",
+	  "Call fails after 4 times 500 Retry-After");
+
+  nh = nua_handle(nua, NULL, SIPTAG_TO(s2->local),
+		  NUTAG_RETRY_COUNT(3),
+		  TAG_END());
+
+  invite_by_nua(nh, TAG_END());
+
+  nua_invite(nh, SOATAG_USER_SDP_STR("m=audio 5004 RTP/AVP 0 8"),
+	     TAG_END());
+
+  for (i = 0;; i++) {
+    fail_unless(s2_check_callstate(nua_callstate_calling));
+    invite = s2_wait_for_request(SIP_METHOD_INVITE);
+    fail_if(!invite);
+    s2_respond_to(invite, NULL, SIP_500_INTERNAL_SERVER_ERROR,
+		  SIPTAG_RETRY_AFTER_STR("5"),
+		  TAG_END());
+    s2_free_message(invite);
+    fail_unless(s2_check_request(SIP_METHOD_ACK));
+    if (i == 3)
+      break;
+    fail_unless(s2_check_event(nua_r_invite, 100));
+    s2_fast_forward(5);
+  }
+
+  fail_unless(s2_check_event(nua_r_invite, 500));
+  /* Graceful termination */
+  fail_unless(s2_check_callstate(nua_callstate_terminating));
+  bye = s2_wait_for_request(SIP_METHOD_BYE);
+  fail_if(!bye);
+  s2_respond_to(bye, dialog, SIP_200_OK, TAG_END());
+  s2_free_message(bye);
+  fail_unless(s2_check_event(nua_r_bye, 200));
+  fail_unless(s2_check_callstate(nua_callstate_terminated));
+
+  nua_handle_destroy(nh);
+}
+END_TEST
+
+
+TCase *invite_error_tcase(void)
+{
+  TCase *tc = tcase_create("3 - Call Errors");
+  tcase_add_checked_fixture(tc, call_setup, call_teardown);
+  {
+    tcase_add_test(tc, call_forbidden);
+    tcase_add_test(tc, too_many_retrys);
+    tcase_add_test(tc, reinvite_forbidden);
+    tcase_add_test(tc, reinvite_too_many_retrys);
+    tcase_set_timeout(tc, 5);
+  }
+  return tc;
+}
+
+
+/* ====================================================================== */
+/* Weird call termination cases */
+
+START_TEST(terminating_re_invite)
+{
+  nua_handle_t *nh;
+  struct message *bye, *r481;
+
+  s2_case("4.1.1", "Re-INVITE while terminating",
+	  "NUA sends BYE, "
+	  "BYE is challenged, "
+	  "and NUA is re-INVITEd at the same time.");
+
+  nh = invite_to_nua(TAG_END());
+
+  s2_flush_events();
+
+  nua_bye(nh, TAG_END());
+
+  bye = s2_wait_for_request(SIP_METHOD_BYE);
+  fail_if(!bye);
+  s2_respond_to(bye, dialog, SIP_407_PROXY_AUTH_REQUIRED,
+		SIPTAG_PROXY_AUTHENTICATE_STR(s2_auth_digest_str),
+		TAG_END());
+  s2_free_message(bye);
+  fail_unless(s2_check_event(nua_r_bye, 407));
+
+  soa_generate_offer(soa, 1, NULL);
+
+  request_with_sdp(dialog, SIP_METHOD_INVITE, NULL, TAG_END());
+
+  do {
+    r481 = s2_wait_for_response(0, SIP_METHOD_INVITE);
+  }
+  while (r481->sip->sip_status->st_status < 200);
+
+  s2_update_dialog(dialog, r481); /* send ACK */
+
+  fail_unless(s2_check_callstate(nua_callstate_terminated));
+
+  nua_handle_destroy(nh);
+}
+END_TEST
+
+
+START_TEST(bye_invite_glare)
+{
+  nua_handle_t *nh;
+  struct message *bye, *r481;
+
+  s2_case("4.1.2", "Re-INVITE while terminating",
+	  "NUA sends BYE, and gets re-INVITEd at same time");
+
+  nh = invite_to_nua(TAG_END());
+
+  s2_flush_events();
+
+  nua_bye(nh, TAG_END());
+  bye = s2_wait_for_request(SIP_METHOD_BYE);
+  fail_if(!bye);
+
+  request_with_sdp(dialog, SIP_METHOD_INVITE, NULL, TAG_END());
+  do {
+    r481 = s2_wait_for_response(0, SIP_METHOD_INVITE);
+  }
+  while (r481->sip->sip_status->st_status < 200);
+
+  s2_update_dialog(dialog, r481); /* send ACK */
+
+  fail_unless(s2_check_callstate(nua_callstate_terminated));
+
+  s2_respond_to(bye, dialog, SIP_200_OK,
+		TAG_END());
+  s2_free_message(bye);
+  fail_unless(s2_check_event(nua_r_bye, 200));
+
+  nua_handle_destroy(nh);
+}
+END_TEST
+
+START_TEST(call_4_1_3)
+{
+  nua_handle_t *nh;
+  struct message *bye;
+  struct event *i_bye;
+
+  s2_case("4.1.3", "BYE while terminating",
+	  "NUA sends BYE and receives BYE");
+
+  nh = invite_to_nua(TAG_END());
+
+  mark_point();
+
+  nua_set_hparams(nh, NUTAG_APPL_METHOD("BYE"), TAG_END());
+  fail_unless(s2_check_event(nua_r_set_params, 200));
+
+  s2_flush_events();
+
+  nua_bye(nh, TAG_END());
+  bye = s2_wait_for_request(SIP_METHOD_BYE);
+  fail_if(!bye);
+
+  s2_request_to(dialog, SIP_METHOD_BYE, NULL, TAG_END());
+  i_bye = s2_wait_for_event(nua_i_bye, 100);
+  fail_if(!i_bye);
+
+  nua_respond(nh, 200, "OKOK", NUTAG_WITH(i_bye->data->e_msg), TAG_END());
+
+  s2_respond_to(bye, dialog, SIP_200_OK, TAG_END());
+  s2_free_message(bye);
+
+  fail_unless(s2_check_event(nua_r_bye, 200));
+  fail_unless(s2_check_callstate(nua_callstate_terminated));
+
+  fail_unless(s2_check_response(200, SIP_METHOD_BYE));
+
+  nua_handle_destroy(nh);
+}
+END_TEST
+
+
+START_TEST(call_4_1_4)
+{
+  nua_handle_t *nh;
+  struct message *bye;
+  struct event *i_bye;
+
+  s2_case("4.1.4", "Send BYE after BYE has been received",
+	  "NUA receives BYE, tries to send BYE at same time");
+
+  nh = invite_to_nua(TAG_END());
+
+  mark_point();
+  nua_set_hparams(nh, NUTAG_APPL_METHOD("BYE"), TAG_END());
+  fail_unless(s2_check_event(nua_r_set_params, 200));
+  s2_flush_events();
+
+  s2_request_to(dialog, SIP_METHOD_BYE, NULL, TAG_END());
+  i_bye = s2_wait_for_event(nua_i_bye, 100);
+  fail_if(!i_bye);
+
+  nua_bye(nh, TAG_END());
+
+  bye = s2_wait_for_request(SIP_METHOD_BYE);
+  fail_if(!bye);
+
+  s2_respond_to(bye, dialog, SIP_200_OK, TAG_END());
+  s2_free_message(bye);
+
+  fail_unless(s2_check_event(nua_r_bye, 200));
+  fail_unless(s2_check_callstate(nua_callstate_terminated));
+
+  nua_respond(nh, 200, "OKOK", NUTAG_WITH(i_bye->data->e_msg), TAG_END());
+  fail_unless(s2_check_response(200, SIP_METHOD_BYE));
+
+  nua_handle_destroy(nh);
+}
+END_TEST
+
+
+START_TEST(call_4_1_5)
+{
+  nua_handle_t *nh;
+  struct message *bye;
+  struct event *i_bye;
+
+  s2_case("4.1.5", "Send BYE after BYE has been received",
+	  "NUA receives BYE, tries to send BYE at same time");
+
+  nh = invite_to_nua(TAG_END());
+
+  mark_point();
+  nua_set_hparams(nh, NUTAG_APPL_METHOD("BYE"), TAG_END());
+  fail_unless(s2_check_event(nua_r_set_params, 200));
+  s2_flush_events();
+
+  s2_request_to(dialog, SIP_METHOD_BYE, NULL, TAG_END());
+  i_bye = s2_wait_for_event(nua_i_bye, 100);
+  fail_if(!i_bye);
+
+  nua_bye(nh, TAG_END());
+
+  bye = s2_wait_for_request(SIP_METHOD_BYE);
+  fail_if(!bye);
+
+  s2_respond_to(bye, dialog, SIP_200_OK, TAG_END());
+  s2_free_message(bye);
+
+  fail_unless(s2_check_event(nua_r_bye, 200));
+  fail_unless(s2_check_callstate(nua_callstate_terminated));
+
+  nua_handle_destroy(nh);
+  fail_unless(s2_check_response(500, SIP_METHOD_BYE));
+}
+END_TEST
+
+
+START_TEST(bye_invite_glare2)
+{
+  nua_handle_t *nh;
+  struct message *bye, *r486;
+
+  s2_case("4.1.6", "Send BYE after INVITE has been received",
+	  "NUA receives INVITE, sends BYE at same time");
+
+  nh = invite_to_nua(TAG_END());
+
+  nua_set_hparams(nh, NUTAG_AUTOANSWER(0), TAG_END());
+  fail_unless(s2_check_event(nua_r_set_params, 200));
+
+  s2_flush_events();
+
+  request_with_sdp(dialog, SIP_METHOD_INVITE, NULL, TAG_END());
+  fail_unless(s2_check_response(100, SIP_METHOD_INVITE));
+  nua_bye(nh, TAG_END());
+  fail_unless(s2_check_event(nua_i_invite, 100));
+  fail_unless(s2_check_callstate(nua_callstate_received));
+
+  do {
+    r486 = s2_wait_for_response(0, SIP_METHOD_INVITE);
+  }
+  while (r486->sip->sip_status->st_status < 200);
+  s2_update_dialog(dialog, r486); /* send ACK */
+  fail_unless(r486->sip->sip_status->st_status == 486);
+
+  bye = s2_wait_for_request(SIP_METHOD_BYE);
+  fail_if(!bye);
+  s2_respond_to(bye, dialog, SIP_200_OK, TAG_END());
+  s2_free_message(bye);
+
+  nua_handle_destroy(nh);
+}
+END_TEST
+
+
+START_TEST(bye_invite_glare3)
+{
+  nua_handle_t *nh;
+  struct message *bye, *r486;
+
+  s2_case("4.1.7", "Send BYE after INVITE has been received",
+	  "NUA receives INVITE, sends BYE at same time");
+
+  nh = invite_to_nua(TAG_END());
+
+  nua_set_hparams(nh, NUTAG_AUTOANSWER(0), TAG_END());
+  fail_unless(s2_check_event(nua_r_set_params, 200));
+
+  s2_flush_events();
+
+  request_with_sdp(dialog, SIP_METHOD_INVITE, NULL, TAG_END());
+  fail_unless(s2_check_response(100, SIP_METHOD_INVITE));
+  nua_bye(nh, TAG_END());
+  fail_unless(s2_check_event(nua_i_invite, 100));
+  fail_unless(s2_check_callstate(nua_callstate_received));
+
+  bye = s2_wait_for_request(SIP_METHOD_BYE);
+  fail_if(!bye);
+  s2_respond_to(bye, dialog, SIP_200_OK, TAG_END());
+  s2_free_message(bye);
+
+  do {
+    r486 = s2_wait_for_response(0, SIP_METHOD_INVITE);
+  }
+  while (r486->sip->sip_status->st_status < 200);
+  s2_update_dialog(dialog, r486); /* send ACK */
+  fail_unless(r486->sip->sip_status->st_status == 486);
+
+  nua_handle_destroy(nh);
+}
+END_TEST
+
+START_TEST(bye_then_respond)
+{
+  nua_handle_t *nh;
+  struct message *bye, *r486;
+
+  s2_case("4.1.8", "BYE followed by response to INVITE",
+	  "NUA receives INVITE, sends BYE at same time");
+
+  nh = nua_handle(nua, NULL, SIPTAG_TO(s2->local), TAG_END());
+
+  invite_by_nua(nh, NUTAG_AUTOANSWER(0), TAG_END());
+
+  s2_flush_events();
+
+  request_with_sdp(dialog, SIP_METHOD_INVITE, NULL, TAG_END());
+  fail_unless(s2_check_response(100, SIP_METHOD_INVITE));
+  nua_bye(nh, TAG_END());
+  fail_unless(s2_check_event(nua_i_invite, 100));
+  fail_unless(s2_check_callstate(nua_callstate_received));
+
+  nua_respond(nh, SIP_486_BUSY_HERE, TAG_END());
+
+  do {
+    r486 = s2_wait_for_response(0, SIP_METHOD_INVITE);
+  }
+  while (r486->sip->sip_status->st_status < 200);
+  s2_update_dialog(dialog, r486); /* send ACK */
+  fail_unless(r486->sip->sip_status->st_status == 486);
+
+  bye = s2_wait_for_request(SIP_METHOD_BYE);
+  fail_if(!bye);
+  s2_respond_to(bye, dialog, SIP_200_OK, TAG_END());
+  s2_free_message(bye);
+
+  nua_handle_destroy(nh);
+}
+END_TEST
+
+
+START_TEST(bye_with_timer)
+{
+  nua_handle_t *nh;
+  struct message *bye;
+
+  s2_case("4.2.1", "BYE in progress while call timer expires",
+	  "NUA receives INVITE, "
+	  "activates call timers, "
+	  "sends BYE, BYE challenged, "
+	  "waits until session expires.");
+
+  nh = invite_to_nua(
+    SIPTAG_SESSION_EXPIRES_STR("300;refresher=uas"),
+    SIPTAG_REQUIRE_STR("timer"),
+    TAG_END());
+
+  s2_fast_forward(300);
+  invite_timer_round(nh, "300");
+
+  nua_bye(nh, TAG_END());
+
+  bye = s2_wait_for_request(SIP_METHOD_BYE);
+  fail_if(!bye);
+  s2_respond_to(bye, dialog, SIP_407_PROXY_AUTH_REQUIRED,
+		SIPTAG_PROXY_AUTHENTICATE_STR(s2_auth_digest_str),
+		TAG_END());
+  s2_free_message(bye);
+  fail_unless(s2_check_event(nua_r_bye, 407));
+
+  s2_fast_forward(300);
+
+  nua_authenticate(nh, NUTAG_AUTH("Digest:\"s2test\":abc:abc"), TAG_END());
+  bye = s2_wait_for_request(SIP_METHOD_BYE);
+  fail_if(!bye);
+  s2_respond_to(bye, dialog, SIP_200_OK, TAG_END());
+  s2_free_message(bye);
+  fail_unless(s2_check_event(nua_r_bye, 200));
+  fail_unless(s2_check_callstate(nua_callstate_terminated));
+  fail_if(s2->events);
+
+  nua_handle_destroy(nh);
+}
+END_TEST
+
+START_TEST(bye_with_timer2)
+{
+  nua_handle_t *nh;
+  struct message *bye;
+
+  s2_case("4.2.1", "BYE in progress while call timer expires",
+	  "NUA receives INVITE, "
+	  "activates call timers, "
+	  "sends BYE, BYE challenged, "
+	  "waits until session expires.");
+
+  nh = invite_to_nua(
+    SIPTAG_SESSION_EXPIRES_STR("300;refresher=uas"),
+    SIPTAG_REQUIRE_STR("timer"),
+    TAG_END());
+
+  s2_fast_forward(300);
+  invite_timer_round(nh, "300");
+
+  s2_fast_forward(300);
+
+  nua_bye(nh, TAG_END());
+
+  bye = s2_wait_for_request(SIP_METHOD_BYE);
+  fail_if(!bye);
+  s2_respond_to(bye, dialog, SIP_407_PROXY_AUTH_REQUIRED,
+		SIPTAG_PROXY_AUTHENTICATE_STR(s2_auth_digest_str),
+		TAG_END());
+  s2_free_message(bye);
+  fail_unless(s2_check_event(nua_r_bye, 407));
+
+  s2_fast_forward(300);
+
+  nua_authenticate(nh, NUTAG_AUTH(s2_auth_credentials), TAG_END());
+  bye = s2_wait_for_request(SIP_METHOD_BYE);
+  fail_if(!bye);
+  s2_respond_to(bye, dialog, SIP_200_OK, TAG_END());
+  s2_free_message(bye);
+  fail_unless(s2_check_event(nua_r_bye, 200));
+  fail_unless(s2_check_callstate(nua_callstate_terminated));
+  fail_if(s2->events);
+
+  nua_handle_destroy(nh);
+}
+END_TEST
+
+TCase *termination_tcase(void)
+{
+  TCase *tc = tcase_create("4 - Call Termination");
+  tcase_add_checked_fixture(tc, call_setup, call_teardown);
+  {
+    tcase_add_test(tc, terminating_re_invite);
+    tcase_add_test(tc, bye_invite_glare);
+    tcase_add_test(tc, call_4_1_3);
+    tcase_add_test(tc, call_4_1_4);
+    tcase_add_test(tc, call_4_1_5);
+    tcase_add_test(tc, bye_invite_glare2);
+    tcase_add_test(tc, bye_invite_glare3);
+    tcase_add_test(tc, bye_with_timer);
+    tcase_add_test(tc, bye_with_timer2);
+    tcase_add_test(tc, bye_then_respond);
+    tcase_set_timeout(tc, 5);
+  }
+  return tc;
+}
+
+/* ====================================================================== */
+
+/* Test case template */
+
+START_TEST(empty)
+{
+  s2_case("0.0.0", "Empty test case",
+	  "Detailed explanation for empty test case.");
+
+  tport_set_params(s2->master, TPTAG_LOG(1), TAG_END());
+  s2_setup_logs(7);
+  s2_setup_logs(0);
+  tport_set_params(s2->master, TPTAG_LOG(0), TAG_END());
+}
+
+END_TEST
+
+TCase *empty_tcase(void)
+{
+  TCase *tc = tcase_create("0 - Empty");
+  tcase_add_checked_fixture(tc, call_setup, call_teardown);
+  tcase_add_test(tc, empty);
+
+  return tc;
+}
+
+/* ====================================================================== */
+
+void check_session_cases(Suite *suite)
+{
+  suite_add_tcase(suite, invite_tcase());
+  suite_add_tcase(suite, cancel_tcase());
+  suite_add_tcase(suite, session_timer_tcase());
+  suite_add_tcase(suite, invite_100rel_tcase());
+  suite_add_tcase(suite, invite_error_tcase());
+  suite_add_tcase(suite, termination_tcase());
+
+  if (0)			/* Template */
+    suite_add_tcase(suite, empty_tcase());
+}

Added: freeswitch/trunk/libs/sofia-sip/libsofia-sip-ua/nua/test_s2.c
==============================================================================
--- (empty file)
+++ freeswitch/trunk/libs/sofia-sip/libsofia-sip-ua/nua/test_s2.c	Wed May 14 15:10:54 2008
@@ -0,0 +1,1589 @@
+/*
+ * This file is part of the Sofia-SIP package
+ *
+ * Copyright (C) 2008 Nokia Corporation.
+ *
+ * Contact: Pekka Pessi <pekka.pessi at nokia.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA
+ *
+ */
+
+/**@CFILE s2tester.c
+ * @brief 2nd test Suite for Sofia SIP User Agent Engine
+ *
+ * @author Pekka Pessi <Pekka.Pessi at nokia.com>
+ *
+ * @date Created: Wed Apr 30 12:48:27 EEST 2008 ppessi
+ */
+
+#include "config.h"
+
+#undef NDEBUG
+
+#define TP_MAGIC_T struct tp_magic_s
+
+#include "test_s2.h"
+
+#include <sofia-sip/sip_header.h>
+#include <sofia-sip/sip_status.h>
+#include <sofia-sip/msg_addr.h>
+#include <sofia-sip/su_log.h>
+#include <sofia-sip/su_tagarg.h>
+#include <sofia-sip/su_alloc.h>
+
+#include <stdlib.h>
+#include <string.h>
+#include <assert.h>
+#include <limits.h>
+#include <time.h>
+
+/* -- Module types ------------------------------------------------------ */
+
+struct tp_magic_s
+{
+  sip_via_t *via;
+  sip_contact_t *contact;
+};
+
+/* -- Module prototypes ------------------------------------------------- */
+
+static msg_t *s2_msg(int flags);
+static int s2_complete_response(msg_t *response, 
+				int status, char const *phrase, 
+				msg_t *request);
+static char *s2_generate_tag(su_home_t *home);
+
+/* -- Module globals ---------------------------------------------------- */
+
+struct tester *s2;
+
+static char const *_s2case = "0.0";
+static unsigned s2_tag_generator = 0;
+
+/* -- Globals ----------------------------------------------------------- */
+
+unsigned s2_default_registration_duration = 3600;
+
+char const s2_auth_digest_str[] =
+  "Digest realm=\"s2test\", "
+  "nonce=\"dcd98b7102dd2f0e8b11d0f600bfb0c093\", "
+  "qop=\"auth\", "
+  "algorithm=\"MD5\"";
+
+char const s2_auth_credentials[] = "Digest:\"s2test\":abc:abc";
+
+char const s2_auth2_digest_str[] =
+  "Digest realm=\"s2test2\", "
+  "nonce=\"fb0c093dcd98b7102dd2f0e8b11d0f600b\", "
+  "qop=\"auth\", "
+  "algorithm=\"MD5\"";
+
+char const s2_auth2_credentials[] = "Digest:\"s2test2\":abc:abc";
+
+char const s2_auth3_digest_str[] =
+  "Digest realm=\"s2test3\", "
+  "nonce=\"e8b11d0f600bfb0c093dcd98b7102dd2f0\", "
+  "qop=\"auth-int\", "
+  "algorithm=\"MD5-sess\"";
+
+char const s2_auth3_credentials[] = "Digest:\"s2test3\":abc:abc";
+
+/* -- Delay scenarios --------------------------------------------------- */
+
+static unsigned long time_offset;
+
+extern void (*_su_time)(su_time_t *tv);
+
+static void _su_time_fast_forwarder(su_time_t *tv)
+{
+  tv->tv_sec += time_offset;
+}
+
+void s2_fast_forward(unsigned long seconds)
+{
+  if (_su_time == NULL)
+    _su_time = _su_time_fast_forwarder;
+
+  time_offset += seconds;
+}
+
+/* -- NUA events -------------------------------------------------------- */
+
+struct event *s2_remove_event(struct event *e)
+{
+  if ((*e->prev = e->next))
+    e->next->prev = e->prev;
+
+  e->prev = NULL, e->next = NULL;
+
+  return e; 
+}
+
+void s2_free_event(struct event *e)
+{
+  if (e) {
+    if (e->prev) {
+      if ((*e->prev = e->next))
+	e->next->prev = e->prev;
+    }
+    nua_destroy_event(e->event);
+    nua_handle_unref(e->nh);
+    free(e);
+  }
+}
+
+void s2_flush_events(void)
+{
+  while (s2->events) {
+    s2_free_event(s2->events);
+  }
+}
+
+struct event *s2_next_event(void)
+{
+  for (;;) {
+    if (s2->events)
+      return s2_remove_event(s2->events);
+
+    su_root_step(s2->root, 100);
+  }
+} 
+
+struct event *s2_wait_for_event(nua_event_t event, int status)
+{
+  struct event *e;
+
+  for (;;) {
+    for (e = s2->events; e; e = e->next) {
+      if (event != nua_i_none && event != e->data->e_event)
+	continue;
+      if (status && e->data->e_status != status)
+	continue;
+      return s2_remove_event(e);
+    }
+
+    su_root_step(s2->root, 100);
+  }
+} 
+
+int s2_check_event(nua_event_t event, int status)
+{
+  struct event *e = s2_wait_for_event(event, status);
+  s2_free_event(e);
+  return e != NULL;
+}
+
+int s2_check_callstate(enum nua_callstate state)
+{
+  int retval = 0;
+  tagi_t const *tagi;
+  struct event *e;
+
+  e = s2_wait_for_event(nua_i_state, 0);
+  if (e) {
+    tagi = tl_find(e->data->e_tags, nutag_callstate);
+    if (tagi) {
+      retval = (tag_value_t)state == tagi->t_value;
+    }
+  }
+  s2_free_event(e);
+  return retval;
+}
+
+static void 
+s2_nua_callback(nua_event_t event,
+		int status, char const *phrase,
+		nua_t *nua, nua_magic_t *_t,
+		nua_handle_t *nh, nua_hmagic_t *hmagic,
+		sip_t const *sip,
+		tagi_t tags[])
+{
+  struct event *e, **prev;
+
+  if (event == nua_i_active || event == nua_i_terminated)
+    return;
+  
+  e = calloc(1, sizeof *e);
+  nua_save_event(nua, e->event);
+  e->nh = nua_handle_ref(nh);
+  e->data = nua_event_data(e->event);
+
+  for (prev = &s2->events; *prev; prev = &(*prev)->next)
+    ;
+
+  *prev = e, e->prev = prev;
+}
+
+
+struct message *
+s2_remove_message(struct message *m)
+{
+  if ((*m->prev = m->next))
+    m->next->prev = m->prev;
+
+  m->prev = NULL, m->next = NULL;
+
+  return m; 
+}
+
+void
+s2_free_message(struct message *m)
+{
+  if (m) {
+    if (m->prev) {
+      if ((*m->prev = m->next))
+	m->next->prev = m->prev;
+    }
+    msg_destroy(m->msg);
+    tport_unref(m->tport);
+    free(m);
+  }
+}
+
+void s2_flush_messages(void)
+{
+  while (s2->received) {
+    s2_free_message(s2->received);
+  }
+}
+
+struct message *
+s2_next_response(void)
+{
+  struct message *m;
+
+  for (;;) {
+    for (m = s2->received; m; m = m->next) {
+      if (m->sip->sip_status)
+	return s2_remove_message(m);
+    }
+    su_root_step(s2->root, 100);
+  }
+}
+
+struct message *
+s2_wait_for_response(int status, sip_method_t method, char const *name)
+{
+  struct message *m;
+
+  for (;;) {
+    for (m = s2->received; m; m = m->next) {
+      if (!m->sip->sip_status)
+	continue;
+
+      if (status != 0 && m->sip->sip_status->st_status != status)
+	continue;
+
+      if (method == sip_method_unknown && name == NULL)
+	break;
+      
+      if (m->sip->sip_cseq == NULL)
+	continue;
+      
+      if (m->sip->sip_cseq->cs_method != method)
+	continue;
+      if (name == NULL)
+	break;
+      if (strcmp(m->sip->sip_cseq->cs_method_name, name) == 0)
+	break;
+    }
+
+    if (m)
+      return s2_remove_message(m);
+
+    su_root_step(s2->root, 100);
+  }
+} 
+
+int
+s2_check_response(int status, sip_method_t method, char const *name)
+{
+  struct message *m = s2_wait_for_response(status, method, name);
+  s2_free_message(m);
+  return m != NULL;
+}
+
+
+struct message *
+s2_next_request(void)
+{
+  struct message *m;
+
+  for (;;) {
+    for (m = s2->received; m; m = m->next) {
+      if (m->sip->sip_request)
+	return s2_remove_message(m);
+    }
+
+    su_root_step(s2->root, 100);
+  }
+  
+  return NULL;
+} 
+
+struct message *
+s2_wait_for_request(sip_method_t method, char const *name)
+{
+  struct message *m;
+
+  for (;;) {
+    for (m = s2->received; m; m = m->next) {
+      if (m->sip->sip_request) {
+	if (method == sip_method_unknown && name == NULL)
+	  return s2_remove_message(m);
+
+	if (m->sip->sip_request->rq_method == method &&
+	    strcmp(m->sip->sip_request->rq_method_name, name) == 0)
+	  return s2_remove_message(m);
+      }
+    }
+
+    su_root_step(s2->root, 100);
+  }
+  
+  return NULL;
+} 
+
+int
+s2_check_request(sip_method_t method, char const *name)
+{
+  struct message *m = s2_wait_for_request(method, name);
+  s2_free_message(m);
+  return m != NULL;
+}
+
+struct message *
+s2_respond_to(struct message *m, struct dialog *d,
+	      int status, char const *phrase,
+	      tag_type_t tag, tag_value_t value, ...)
+{
+  ta_list ta;
+  msg_t *reply;
+  sip_t *sip;
+  su_home_t *home;
+  tp_name_t tpn[1];
+  char *rport;
+
+  assert(m); assert(m->msg); assert(m->tport);
+  assert(100 <= status && status < 700);
+
+  ta_start(ta, tag, value);
+
+  reply = s2_msg(0); sip = sip_object(reply); home = msg_home(reply);
+
+  assert(reply && home && sip);
+
+  if (sip_add_tl(reply, sip, ta_tags(ta)) < 0) {
+    abort();
+  }
+
+  s2_complete_response(reply, status, phrase, m->msg);
+
+  if (sip->sip_status && sip->sip_status->st_status > 100 &&
+      sip->sip_to && !sip->sip_to->a_tag &&
+      sip->sip_cseq && sip->sip_cseq->cs_method != sip_method_cancel) {
+    char const *ltag = NULL;
+
+    if (d && d->local)
+      ltag = d->local->a_tag;
+
+    if (ltag == NULL)
+      ltag = s2_generate_tag(home);
+
+    if (sip_to_tag(msg_home(reply), sip->sip_to, ltag) < 0) {
+      assert(!"add To tag");
+    }
+  }
+
+  if (d && !d->local) {
+    d->local = sip_from_dup(d->home, sip->sip_to);
+    d->remote = sip_to_dup(d->home, sip->sip_from);
+    d->call_id = sip_call_id_dup(d->home, sip->sip_call_id);
+    d->rseq = sip->sip_cseq->cs_seq;
+    /* d->route = sip_route_dup(d->home, sip->sip_record_route); */
+    d->target = sip_contact_dup(d->home, m->sip->sip_contact);
+    d->contact = sip_contact_dup(d->home, sip->sip_contact);
+  }
+
+  *tpn = *tport_name(m->tport);
+
+  rport = su_sprintf(home, "rport=%u", 
+		     ntohs(((su_sockaddr_t *)
+			    msg_addrinfo(m->msg)->ai_addr)->su_port));
+
+  if (s2->server_uses_rport &&
+      sip->sip_via->v_rport &&
+      sip->sip_via->v_rport[0] == '\0') {
+    msg_header_add_param(home, sip->sip_via->v_common, rport);
+  }    
+
+  tpn->tpn_port = rport + strlen("rport=");
+
+  tport_tsend(m->tport, reply, tpn, TPTAG_MTU(INT_MAX), ta_tags(ta));
+  msg_destroy(reply);
+
+  ta_end(ta);
+
+  return m;
+}
+
+/** Add headers from the request to the response message. */
+static int 
+s2_complete_response(msg_t *response, 
+		     int status, char const *phrase, 
+		     msg_t *request)
+{
+  su_home_t *home = msg_home(response);
+  sip_t *response_sip = sip_object(response);
+  sip_t const *request_sip = sip_object(request);
+
+  int incomplete = 0;
+
+  if (!response_sip || !request_sip || !request_sip->sip_request)
+    return -1;
+
+  if (!response_sip->sip_status)
+    response_sip->sip_status = sip_status_create(home, status, phrase, NULL);
+  if (!response_sip->sip_via)
+    response_sip->sip_via = sip_via_dup(home, request_sip->sip_via);
+  if (!response_sip->sip_from)
+    response_sip->sip_from = sip_from_dup(home, request_sip->sip_from);
+  if (!response_sip->sip_to)
+    response_sip->sip_to = sip_to_dup(home, request_sip->sip_to);
+  if (!response_sip->sip_call_id)
+    response_sip->sip_call_id = 
+      sip_call_id_dup(home, request_sip->sip_call_id);
+  if (!response_sip->sip_cseq)
+    response_sip->sip_cseq = sip_cseq_dup(home, request_sip->sip_cseq);
+
+  if (!response_sip->sip_record_route && request_sip->sip_record_route)
+    sip_add_dup(response, response_sip, (void*)request_sip->sip_record_route);
+
+  incomplete = sip_complete_message(response) < 0;
+
+  msg_serialize(response, (msg_pub_t *)response_sip);
+
+  if (incomplete ||
+      !response_sip->sip_status ||
+      !response_sip->sip_via ||
+      !response_sip->sip_from ||
+      !response_sip->sip_to ||
+      !response_sip->sip_call_id ||
+      !response_sip->sip_cseq ||
+      !response_sip->sip_content_length ||
+      !response_sip->sip_separator ||
+      (request_sip->sip_record_route && !response_sip->sip_record_route))
+    return -1;
+
+  return 0;
+}
+
+/* Send request (updating dialog). 
+ *
+ * Return zero upon success, nonzero upon failure.
+ */
+int 
+s2_request_to(struct dialog *d,
+	      sip_method_t method, char const *name,
+	      tport_t *tport,
+	      tag_type_t tag, tag_value_t value, ...)
+{
+  ta_list ta;
+  tagi_t const *tags;
+
+  msg_t *msg = s2_msg(0);
+  sip_t *sip = sip_object(msg);
+  url_string_t *target = NULL;
+  sip_cseq_t cseq[1];
+  sip_via_t via[1]; char const *v_params[8];
+  sip_content_length_t l[1];
+  tp_name_t tpn[1];
+  tp_magic_t *magic;
+
+  ta_start(ta, tag, value);
+  tags = ta_args(ta);
+
+  if (sip_add_tagis(msg, sip, &tags) < 0)
+    goto error;
+
+  if (!sip->sip_request) {
+    sip_request_t *rq;
+
+    if (d->target)
+      target = (url_string_t *)d->target->m_url;
+    else if (s2->registration->contact)
+      target = (url_string_t *)s2->registration->contact->m_url;
+    else
+      target = NULL;
+
+    if (target == NULL)
+      goto error;
+
+    rq = sip_request_create(msg_home(msg), method, name, target, NULL);
+    sip_header_insert(msg, sip, (sip_header_t *)rq);
+  }
+
+  if (!d->local && sip->sip_from)
+    d->local = sip_from_dup(d->home, sip->sip_from);
+  if (!d->contact && sip->sip_contact) 
+    d->contact = sip_contact_dup(d->home, sip->sip_contact);
+  if (!d->remote && sip->sip_to)
+    d->remote = sip_to_dup(d->home, sip->sip_to);
+  if (!d->target && sip->sip_request)
+    d->target = sip_contact_create(d->home,
+				   (url_string_t *)sip->sip_request->rq_url,
+				   NULL);
+  if (!d->call_id && sip->sip_call_id)
+    d->call_id = sip_call_id_dup(d->home, sip->sip_call_id);
+  if (!d->lseq && sip->sip_cseq)
+    d->lseq = sip->sip_cseq->cs_seq;
+  
+  if (!d->local)
+    d->local = sip_from_dup(d->home, s2->local);
+  if (!d->contact)
+    d->contact = sip_contact_dup(d->home, s2->contact);
+  if (!d->remote)
+    d->remote = sip_to_dup(d->home, s2->registration->aor);
+  if (!d->call_id)
+    d->call_id = sip_call_id_create(d->home, NULL);
+  assert(d->local && d->contact);
+  assert(d->remote && d->target);
+  assert(d->call_id);
+
+  if (tport == NULL)
+    tport = d->tport;
+
+  if (tport == NULL)
+    tport = s2->registration->tport;
+
+  if (tport == NULL && d->target->m_url->url_type == url_sips)
+    tport = s2->tls.tport;
+
+  if (tport == NULL)
+    tport = s2->udp.tport;
+  else if (tport == NULL)
+    tport = s2->tcp.tport;
+  else if (tport == NULL)
+    tport = s2->tls.tport;
+
+  assert(tport);
+
+  *tpn = *tport_name(tport);
+  tpn->tpn_host = d->target->m_url->url_host;
+  tpn->tpn_port = d->target->m_url->url_port;
+
+  magic = tport_magic(tport);
+  assert(magic != NULL);
+
+  sip_cseq_init(cseq);
+  cseq->cs_method = method;
+  cseq->cs_method_name = name;
+  
+  if (d->invite && (method == sip_method_ack || method == sip_method_cancel)) {
+    cseq->cs_seq = sip_object(d->invite)->sip_cseq->cs_seq;
+  }
+  else {
+    cseq->cs_seq = ++d->lseq;
+  }
+
+  if (d->invite && method == sip_method_cancel) {
+    *via = *sip_object(d->invite)->sip_via;
+  }
+  else {
+    *via = *magic->via;
+    via->v_params = v_params;
+    v_params[0] = su_sprintf(msg_home(msg), "branch=z9hG4bK%lx", ++s2->tid);
+    v_params[1] = NULL;
+  }
+
+  sip_content_length_init(l);
+  if (sip->sip_payload)
+    l->l_length = sip->sip_payload->pl_len;
+
+  sip_add_tl(msg, sip, 
+	     TAG_IF(!sip->sip_from, SIPTAG_FROM(d->local)),
+	     TAG_IF(!sip->sip_contact, SIPTAG_CONTACT(d->contact)),
+	     TAG_IF(!sip->sip_to, SIPTAG_TO(d->remote)),
+	     TAG_IF(!sip->sip_call_id, SIPTAG_CALL_ID(d->call_id)),
+	     TAG_IF(!sip->sip_cseq, SIPTAG_CSEQ(cseq)),
+	     SIPTAG_VIA(via),
+	     TAG_IF(!sip->sip_content_length, SIPTAG_CONTENT_LENGTH(l)),
+	     TAG_IF(!sip->sip_separator, SIPTAG_SEPARATOR_STR("\r\n")),
+	     TAG_END());
+
+  msg_serialize(msg, NULL);
+
+  if (method == sip_method_invite) {
+    msg_destroy(d->invite);
+    d->invite = msg_ref_create(msg);
+  }
+
+  tport = tport_tsend(tport, msg, tpn, ta_tags(ta));
+  ta_end(ta);
+
+  if (d->tport != tport) {
+    tport_unref(d->tport);
+    d->tport = tport_ref(tport);
+  }
+
+  return tport ? 0 : -1;
+  
+ error:
+  ta_end(ta);
+  return -1;
+}
+
+/** Save information from response.
+ *
+ * Send ACK for error messages to INVITE.
+ */
+int s2_update_dialog(struct dialog *d, struct message *m)
+{
+  int status = 0;
+
+  if (m->sip->sip_status)
+    status = m->sip->sip_status->st_status;
+
+  if (100 < status && status < 300) {
+    d->remote = sip_to_dup(d->home, m->sip->sip_to);
+    if (m->sip->sip_contact)
+      d->contact = sip_contact_dup(d->home, m->sip->sip_contact);
+  }
+
+  if (300 <= status && m->sip->sip_cseq &&
+      m->sip->sip_cseq->cs_method == sip_method_invite &&
+      d->invite) {
+    msg_t *ack = s2_msg(0);
+    sip_t *sip = sip_object(ack);
+    sip_t *invite = sip_object(d->invite);
+    sip_request_t rq[1];
+    sip_cseq_t cseq[1];
+    tp_name_t tpn[1];
+
+    *rq = *invite->sip_request;
+    rq->rq_method = sip_method_ack, rq->rq_method_name = "ACK";
+    *cseq = *invite->sip_cseq;
+    cseq->cs_method = sip_method_ack, cseq->cs_method_name = "ACK";
+
+    sip_add_tl(ack, sip,
+	       SIPTAG_REQUEST(rq),
+	       SIPTAG_VIA(invite->sip_via),
+	       SIPTAG_FROM(invite->sip_from),
+	       SIPTAG_TO(invite->sip_to),
+	       SIPTAG_CALL_ID(invite->sip_call_id),
+	       SIPTAG_CSEQ(cseq),
+	       SIPTAG_CONTENT_LENGTH_STR("0"),
+	       SIPTAG_SEPARATOR_STR("\r\n"),
+	       TAG_END());
+
+    *tpn = *tport_name(d->tport);
+    if (!tport_is_secondary(d->tport) ||
+	!tport_is_clear_to_send(d->tport)) {
+      tpn->tpn_host = rq->rq_url->url_host;
+      tpn->tpn_port = rq->rq_url->url_port;
+    }
+
+    msg_serialize(ack, NULL);
+    tport_tsend(d->tport, ack, tpn, TAG_END());
+  }
+
+  return 0;
+}
+
+/* ---------------------------------------------------------------------- */
+
+int
+s2_save_register(struct message *rm)
+{
+  sip_contact_t *contact, *m, **m_prev;
+  sip_expires_t const *ex;
+  sip_date_t const *date;
+  sip_time_t now = rm->when.tv_sec, expires;
+
+  msg_header_free_all(s2->home, (msg_header_t *)s2->registration->aor);
+  msg_header_free_all(s2->home, (msg_header_t *)s2->registration->contact);
+  tport_unref(s2->registration->tport);
+
+  s2->registration->aor = NULL;
+  s2->registration->contact = NULL;
+  s2->registration->tport = NULL;
+
+  if (rm == NULL)
+    return 0;
+
+  assert(rm && rm->sip && rm->sip->sip_request);
+  assert(rm->sip->sip_request->rq_method == sip_method_register);
+
+  ex = rm->sip->sip_expires;
+  date = rm->sip->sip_date;
+
+  contact = sip_contact_dup(s2->home, rm->sip->sip_contact);
+
+  for (m_prev = &contact; *m_prev;) {
+    m = *m_prev;
+
+    expires = sip_contact_expires(m, ex, date,
+				  s2_default_registration_duration,
+				  now);
+    if (expires) {
+      char *p = su_sprintf(s2->home, "expires=%lu", (unsigned long)expires);
+      msg_header_add_param(s2->home, m->m_common, p);
+      m_prev = &m->m_next;
+    }
+    else {
+      *m_prev = m->m_next;
+      m->m_next = NULL;
+      msg_header_free(s2->home, (msg_header_t *)m);
+    }
+  }
+
+  if (contact == NULL)
+    return 0;
+
+  s2->registration->aor = sip_to_dup(s2->home, rm->sip->sip_to);
+  s2->registration->contact = contact;
+  s2->registration->tport = tport_ref(rm->tport);
+
+  return 0;
+}
+
+/* ---------------------------------------------------------------------- */
+
+static char *
+s2_generate_tag(su_home_t *home)
+{
+  s2_tag_generator += 1;
+
+  return su_sprintf(home, "tag=N2-%s/%u", _s2case, s2_tag_generator);
+}
+
+void s2_case(char const *number,
+	     char const *title,
+	     char const *desciption)
+{
+  _s2case = number;
+}
+
+
+/* ---------------------------------------------------------------------- */
+/* tport interface */
+static void 
+s2_stack_recv(struct tester *s2,
+	      tport_t *tp,
+	      msg_t *msg,
+	      tp_magic_t *magic,
+	      su_time_t now)
+{
+  struct message *next = calloc(1, sizeof *next), **prev;
+
+  next->msg = msg;
+  next->sip = sip_object(msg);
+  next->when = now;
+  next->tport = tport_ref(tp);
+
+#if 0
+  if (next->sip->sip_request)
+    printf("nua sent: %s\n", next->sip->sip_request->rq_method_name);
+  else
+    printf("nua sent: SIP/2.0 %u %s\n",
+	   next->sip->sip_status->st_status,
+	   next->sip->sip_status->st_phrase);
+#endif
+
+  for (prev = &s2->received; *prev; prev = &(*prev)->next)
+    ;
+
+  next->prev = prev, *prev = next;
+}
+
+static void
+s2_stack_error(struct tester *s2,
+	       tport_t *tp,
+	       int errcode,
+	       char const *remote)
+{
+  fprintf(stderr, "%s(%p): error %d (%s) from %s\n", 
+	  "nua_tester_error",
+	  (void *)tp, errcode, su_strerror(errcode), 
+	  remote ? remote : "<unknown destination>");
+}
+
+static msg_t *
+s2_stack_alloc(struct tester *s2, int flags,
+	       char const data[], usize_t size,
+	       tport_t const *tport, 
+	       tp_client_t *tpc)
+{
+  return msg_create(s2->mclass, flags | s2->flags);
+}
+
+static msg_t *
+s2_msg(int flags)
+{
+  return msg_create(s2->mclass, flags | s2->flags);
+}
+
+tp_stack_class_t const s2_stack[1] =
+  {{
+      /* tpac_size */ (sizeof s2_stack),
+      /* tpac_recv */  s2_stack_recv,
+      /* tpac_error */ s2_stack_error,
+      /* tpac_alloc */ s2_stack_alloc,
+  }};
+
+/** Basic setup for test cases */
+void s2_setup_base(char const *hostname)
+{
+  assert(s2 == NULL);
+
+  su_init();
+
+  s2 = su_home_new(sizeof *s2);
+
+  assert(s2 != NULL);
+
+  s2->root = su_root_create(s2);
+
+  assert(s2->root != NULL);
+
+  su_root_threading(s2->root, 0);	/* disable multithreading */
+
+  s2->local = sip_from_format(s2->home, "Bob <sip:bob@%s>",
+			     hostname ? hostname : "example.net");
+  
+  if (hostname == NULL)
+    hostname = "127.0.0.1";
+
+  s2->hostname = hostname;
+  s2->tid = (unsigned long)time(NULL) * 510633671UL;
+}
+
+SOFIAPUBVAR su_log_t nua_log[];
+SOFIAPUBVAR su_log_t soa_log[];
+SOFIAPUBVAR su_log_t nea_log[];
+SOFIAPUBVAR su_log_t nta_log[];
+SOFIAPUBVAR su_log_t tport_log[];
+SOFIAPUBVAR su_log_t su_log_default[];
+
+void
+s2_setup_logs(int level)
+{
+  assert(s2);
+
+  su_log_soft_set_level(nua_log, level);
+  su_log_soft_set_level(soa_log, level);
+  su_log_soft_set_level(su_log_default, level);
+  su_log_soft_set_level(nea_log, level);
+  su_log_soft_set_level(nta_log, level);
+  su_log_soft_set_level(tport_log, level);
+}
+
+static char const * default_protocols[] = { "udp", "tcp", NULL };
+
+void
+s2_setup_tport(char const * const *protocols,
+	       tag_type_t tag, tag_value_t value, ...)
+{
+  ta_list ta;
+  tp_name_t tpn[1];
+  int bound;
+  tport_t *tp;
+
+  assert(s2 != NULL);
+
+  ta_start(ta, tag, value);
+
+  if (s2->master == NULL) {
+    s2->master = tport_tcreate(s2, s2_stack, s2->root, ta_tags(ta));
+
+    if (s2->master == NULL) {
+      assert(s2->master);
+    }
+    s2->mclass = sip_default_mclass();
+    s2->flags = 0;
+  }
+
+  memset(tpn, 0, (sizeof tpn));
+  tpn->tpn_proto = "*";
+  tpn->tpn_host = s2->hostname;
+  tpn->tpn_port = "*";
+
+  if (protocols == NULL)
+    protocols = default_protocols;
+  
+  bound = tport_tbind(s2->master, tpn, protocols, 
+		      TPTAG_SERVER(1),
+		      ta_tags(ta));
+  assert(bound != -1);
+
+  tp = tport_primaries(s2->master);
+
+  if (protocols == default_protocols && s2->contact == NULL) {
+    *tpn = *tport_name(tp);
+    s2->contact = sip_contact_format(s2->home, "<sip:%s:%s>",
+				    tpn->tpn_host,
+				    tpn->tpn_port);
+  }
+
+  for (;tp; tp = tport_next(tp)) {
+    sip_via_t *v;
+    sip_contact_t *m;
+    tp_magic_t *magic;
+
+    if (tport_magic(tp))
+      continue;
+
+    *tpn = *tport_name(tp);
+
+    v = sip_via_format(s2->home, "SIP/2.0/%s %s:%s",
+		       tpn->tpn_proto,
+		       tpn->tpn_host, 
+		       tpn->tpn_port);
+    assert(v != NULL);
+    if (strncasecmp(tpn->tpn_proto, "tls", 3)) {
+      m = sip_contact_format(s2->home, "<sip:%s:%s;transport=%s>",
+			     tpn->tpn_host,
+			     tpn->tpn_port,
+			     tpn->tpn_proto);
+      if (s2->udp.contact == NULL && strcasecmp(tpn->tpn_proto, "udp") == 0) {
+	s2->udp.tport = tport_ref(tp); 
+	s2->udp.contact = m;
+      }
+      if (s2->tcp.contact == NULL && strcasecmp(tpn->tpn_proto, "tcp") == 0) {
+	s2->tcp.tport = tport_ref(tp); 
+	s2->tcp.contact = m;
+      }
+    }
+    else if (strcasecmp(tpn->tpn_proto, "tls")) {
+      m = sip_contact_format(s2->home, "<sips:%s:%s;transport=%s>",
+			     tpn->tpn_host,
+			     tpn->tpn_port,
+			     tpn->tpn_proto);
+    }
+    else {
+      m = sip_contact_format(s2->home, "<sips:%s:%s>",
+			     tpn->tpn_host,
+			     tpn->tpn_port);
+      if (s2->tls.contact == NULL) {
+	s2->tls.tport = tport_ref(tp); 
+	s2->tls.contact = m;
+      }
+    }
+    assert(m != NULL);
+
+    magic = su_zalloc(s2->home, (sizeof *magic));
+    magic->via = v, magic->contact = m;
+
+    if (s2->contact == NULL)
+      s2->contact = m;
+
+    tport_set_magic(tp, magic);
+  }
+}
+
+/* ---------------------------------------------------------------------- */
+/* S2 DNS server */
+
+#include <sofia-resolv/sres_record.h>
+
+extern uint16_t _sres_default_port;
+
+static int s2_dns_query(struct tester *s2,
+			su_wait_t *w,
+			su_wakeup_arg_t *arg);
+
+void s2_setup_dns(void)
+{
+  int n;
+  su_socket_t socket;
+  su_wait_t *wait;
+  su_sockaddr_t su[1];
+  socklen_t sulen = sizeof su->su_sin;
+
+  assert(s2->nua == NULL); assert(s2->root != NULL);
+
+  memset(su, 0, sulen);
+  su->su_len = sulen;
+  su->su_family = AF_INET;
+
+  /* su->su_port = htons(1053); */
+
+  socket = su_socket(su->su_family, SOCK_DGRAM, 0);
+
+  n = bind(socket, &su->su_sa, sulen); assert(n == 0);
+  n = getsockname(socket, &su->su_sa, &sulen); assert(n == 0);
+
+  _sres_default_port = ntohs(su->su_port);
+
+  wait = s2->dns.wait;
+  n = su_wait_create(wait, socket, SU_WAIT_IN); assert(n == 0);
+  s2->dns.reg = su_root_register(s2->root, wait, s2_dns_query, NULL, 0);
+  assert(s2->dns.reg > 0);
+  s2->dns.socket = socket;
+}
+
+static
+struct s2_dns_response {
+  struct s2_dns_response *next;
+  uint16_t qlen, dlen;
+  struct m_header {
+    /* Header defined in RFC 1035 section 4.1.1 (page 26) */
+    uint16_t mh_id;		/* Query ID */
+    uint16_t mh_flags;		/* Flags */
+    uint16_t mh_qdcount;	/* Question record count */
+    uint16_t mh_ancount;	/* Answer record count */
+    uint16_t mh_nscount;	/* Authority records count */
+    uint16_t mh_arcount;	/* Additional records count */
+  } header[1];
+  uint8_t data[1500];
+} *zonedata;
+
+enum {
+  FLAGS_QR = (1 << 15),
+  FLAGS_QUERY = (0 << 11),
+  FLAGS_IQUERY = (1 << 11),
+  FLAGS_STATUS = (2 << 11),
+  FLAGS_OPCODE = (15 << 11),	/* mask */
+  FLAGS_AA = (1 << 10),		/*  */
+  FLAGS_TC = (1 << 9),
+  FLAGS_RD = (1 << 8),
+  FLAGS_RA = (1 << 7),
+
+  FLAGS_RCODE = (15 << 0),	/* mask of return code */
+
+  FLAGS_OK = 0,			/* No error condition. */
+  FLAGS_FORMAT_ERR = 1,		/* Server could not interpret query. */
+  FLAGS_SERVER_ERR = 2,		/* Server error. */
+  FLAGS_NAME_ERR = 3,		/* No domain name. */
+  FLAGS_UNIMPL_ERR = 4,		/* Not implemented. */
+  FLAGS_AUTH_ERR = 5,		/* Refused */
+};
+
+uint32_t s2_dns_ttl = 3600;
+
+static int
+s2_dns_query(struct tester *s2,
+	     su_wait_t *w,
+	     su_wakeup_arg_t *arg)
+{
+  union {
+    struct m_header header[1];
+    uint8_t buffer[1500];
+  } request;
+  ssize_t len;
+
+  su_socket_t socket;
+  su_sockaddr_t su[1];
+  socklen_t sulen = sizeof su;
+  uint16_t flags;
+  struct s2_dns_response *r;
+  size_t const hlen = sizeof r->header;
+
+  (void)arg;
+
+  socket = s2->dns.socket;
+
+  len = su_recvfrom(socket, request.buffer, sizeof request.buffer, 0,
+		    &su->su_sa, &sulen);
+
+  flags = ntohs(request.header->mh_flags);
+
+  if (len < (ssize_t)hlen)
+    return 0;
+  if ((flags & FLAGS_QR) == FLAGS_QR)
+    return 0;
+  if ((flags & FLAGS_RCODE) != FLAGS_OK)
+    return 0;
+
+  if ((flags & FLAGS_OPCODE) != FLAGS_QUERY
+      || ntohs(request.header->mh_qdcount) != 1) {
+    flags |= FLAGS_QR | FLAGS_UNIMPL_ERR;
+    request.header->mh_flags = htons(flags);
+    su_sendto(socket, request.buffer, len, 0, &su->su_sa, sulen);
+    return 0;
+  }
+
+  for (r = zonedata; r; r = r->next) {
+    if (memcmp(r->data, request.buffer + hlen, r->qlen) == 0)
+      break;
+  }
+
+  if (r) {
+    flags |= FLAGS_QR | FLAGS_AA | FLAGS_OK;
+    request.header->mh_flags = htons(flags);
+    request.header->mh_ancount = htons(r->header->mh_ancount);
+    request.header->mh_nscount = htons(r->header->mh_nscount);
+    request.header->mh_arcount = htons(r->header->mh_arcount);
+    memcpy(request.buffer + hlen + r->qlen,
+	   r->data + r->qlen,
+	   r->dlen - r->qlen);
+    len = hlen + r->dlen;
+  }
+  else {
+    flags |= FLAGS_QR | FLAGS_AA | FLAGS_NAME_ERR;
+  }
+
+  request.header->mh_flags = htons(flags);
+  su_sendto(socket, request.buffer, len, 0, &su->su_sa, sulen);
+  return 0;
+}
+
+static void put_uint16(struct s2_dns_response *m, uint16_t h)
+{
+  uint8_t *p = m->data + m->dlen;
+
+  assert(m->dlen + (sizeof h) < sizeof m->data);
+  p[0] = h >> 8; p[1] = h;
+  m->dlen += (sizeof h);
+}
+
+static void put_uint32(struct s2_dns_response *m, uint32_t w)
+{
+  uint8_t *p = m->data + m->dlen;
+
+  assert(m->dlen + (sizeof w) < sizeof m->data);
+  p[0] = w >> 24; p[1] = w >> 16; p[2] = w >> 8; p[3] = w;
+  m->dlen += (sizeof w);
+}
+
+static void put_domain(struct s2_dns_response *m, char const *domain)
+{
+  char const *label;
+  size_t llen;
+
+  /* Copy domain into query label at a time */
+  for (label = domain; label && label[0]; label += llen) {
+    assert(!(label[0] == '.' && label[1] != '\0'));
+    llen = strcspn(label, ".");
+    assert(llen < 64);
+    assert(m->dlen + llen + 1 < sizeof m->data);
+    m->data[m->dlen++] = (uint8_t)llen;
+    if (llen == 0)
+      return;
+
+    memcpy(m->data + m->dlen, label, llen);
+    m->dlen += (uint16_t)llen;
+
+    if (label[llen] == '\0')
+      break;
+    if (label[llen + 1])
+      llen++;
+  }
+
+  assert(m->dlen < sizeof m->data);
+  m->data[m->dlen++] = '\0';
+}
+
+static void put_string(struct s2_dns_response *m, char const *string)
+{
+  uint8_t *p = m->data + m->dlen;
+  size_t len = strlen(string);
+
+  assert(len <= 255);
+  assert(m->dlen + len + 1 < sizeof m->data);
+
+  *p++ = (uint8_t)len;
+  memcpy(p, string, len);
+  m->dlen += len + 1;
+}
+
+static uint16_t put_len_at(struct s2_dns_response *m)
+{
+  uint16_t at = m->dlen;
+  assert(m->dlen + sizeof(at) < sizeof m->data);
+  memset(m->data + m->dlen, 0, sizeof(at));
+  m->dlen += sizeof(at);
+  return at;
+}
+
+static void put_len(struct s2_dns_response *m, uint16_t start)
+{
+  uint8_t *p = m->data + start;
+  uint16_t len = m->dlen - (start + 2);
+  p[0] = len >> 8; p[1] = len;
+}
+
+static void put_data(struct s2_dns_response *m, void *data, uint16_t len)
+{
+  assert(m->dlen + len < sizeof m->data);
+  memcpy(m->data + m->dlen, data, len);
+  m->dlen += len;
+}
+
+static void put_query(struct s2_dns_response *m, char const *domain,
+		      uint16_t qtype)
+{
+  assert(m->header->mh_qdcount == 0);
+  put_domain(m, domain), put_uint16(m, qtype), put_uint16(m, sres_class_in);
+  m->header->mh_qdcount++;
+  m->qlen = m->dlen;
+}
+
+static void put_a_record(struct s2_dns_response *m,
+			 char const *domain,
+			 struct in_addr addr)
+{
+  uint16_t start;
+
+  put_domain(m, domain);
+  put_uint16(m, sres_type_a);
+  put_uint16(m, sres_class_in);
+  put_uint32(m, s2_dns_ttl);
+  start = put_len_at(m);
+
+  put_data(m, &addr, sizeof addr);
+  put_len(m, start);
+}
+
+static void put_srv_record(struct s2_dns_response *m,
+			   char const *domain,
+			   uint16_t prio, uint16_t weight,
+			   uint16_t port, char const *target)
+{
+  uint16_t start;
+  put_domain(m, domain);
+  put_uint16(m, sres_type_srv);
+  put_uint16(m, sres_class_in);
+  put_uint32(m, s2_dns_ttl);
+  start = put_len_at(m);
+
+  put_uint16(m, prio);
+  put_uint16(m, weight);
+  put_uint16(m, port);
+  put_domain(m, target);
+  put_len(m, start);
+}
+
+static void put_naptr_record(struct s2_dns_response *m,
+			     char const *domain,
+			     uint16_t order, uint16_t preference,
+			     char const *flags,
+			     char const *services,
+			     char const *regexp,
+			     char const *replace)
+{
+  uint16_t start;
+  put_domain(m, domain);
+  put_uint16(m, sres_type_naptr);
+  put_uint16(m, sres_class_in);
+  put_uint32(m, s2_dns_ttl);
+  start = put_len_at(m);
+
+  put_uint16(m, order);
+  put_uint16(m, preference);
+  put_string(m, flags);
+  put_string(m, services);
+  put_string(m, regexp);
+  put_domain(m, replace);
+  put_len(m, start);
+}
+
+static void put_srv_record_from_uri(struct s2_dns_response *m,
+				    char const *base,
+				    uint16_t prio, uint16_t weight,
+				    url_t const *uri, char const *server)
+{
+  char domain[1024] = "none";
+  char const *service = url_port(uri);
+  uint16_t port;
+
+  if (uri->url_type == url_sips) {
+    strcpy(domain, "_sips._tcp.");
+  }
+  else if (uri->url_type == url_sip) {
+    if (url_has_param(uri, "transport=udp")) {
+      strcpy(domain, "_sip._udp.");
+    }
+    else if (url_has_param(uri, "transport=tcp")) {
+      strcpy(domain, "_sip._tcp.");
+    }
+  }
+
+  assert(strcmp(domain, "none"));
+
+  strcat(domain, base);
+
+  if (m->header->mh_qdcount == 0)
+    put_query(m, domain, sres_type_srv);
+
+  port = (uint16_t)strtoul(service, NULL, 10);
+
+  put_srv_record(m, domain, prio, weight, port, server);
+}
+
+static
+void s2_add_to_zone(struct s2_dns_response *_r)
+{
+  size_t size = offsetof(struct s2_dns_response, data[_r->dlen]);
+  struct s2_dns_response *r = malloc(size); assert(r);
+
+  memcpy(r, _r, size);
+  r->next = zonedata;
+  zonedata = r;
+}
+
+
+static void make_server(char *server, char const *prefix, char const *domain)
+{
+  strcpy(server, prefix);
+
+  if (strlen(server) == 0 || server[strlen(server) - 1] != '.') {
+    strcat(server, ".");
+    strcat(server, domain);
+  }
+}
+
+/** Set up DNS domain */
+void s2_dns_domain(char const *domain, int use_naptr,
+		   /* char *prefix, int priority, url_t const *uri, */
+		   ...)
+{
+  struct s2_dns_response m[1];
+
+  char server[1024], target[1024];
+
+  va_list va0, va;
+  char const *prefix; int priority; url_t const *uri;
+  struct in_addr localhost;
+
+  assert(s2->dns.reg != 0);
+
+  inet_pton(AF_INET, "127.0.0.1", &localhost);
+
+  va_start(va0, use_naptr);
+
+  if (use_naptr) {
+    memset(m, 0, sizeof m);
+    put_query(m, domain, sres_type_naptr);
+
+    va_copy(va, va0);
+
+    for (;(prefix = va_arg(va, char *));) {
+      char *services = NULL;
+
+      priority = va_arg(va, int);
+      uri = va_arg(va, url_t *); assert(uri);
+
+      if (uri->url_type == url_sips) {
+	services = "SIPS+D2T";
+	strcpy(target, "_sips._tcp.");
+      }
+      else if (uri->url_type == url_sip) {
+	if (url_has_param(uri, "transport=udp")) {
+	  services = "SIP+D2U";
+	  strcpy(target, "_sip._udp.");
+	}
+	else if (url_has_param(uri, "transport=tcp")) {
+	  services = "SIP+D2T";
+	  strcpy(target, "_sip._tcp.");
+	}
+      }
+
+      strcat(target, domain);
+      assert(services);
+      put_naptr_record(m, domain, 1, priority, "s", services, "", target);
+      m->header->mh_ancount++;
+    }
+
+    va_end(va);
+    va_copy(va, va0);
+
+    for (;(prefix = va_arg(va, char *));) {
+      priority = va_arg(va, int);
+      uri = va_arg(va, url_t *); assert(uri);
+
+      make_server(server, prefix, domain);
+
+      put_srv_record_from_uri(m, domain, priority, 10, uri, server);
+      m->header->mh_arcount++;
+
+      put_a_record(m, server, localhost);
+      m->header->mh_arcount++;
+    }
+    va_end(va);
+
+    s2_add_to_zone(m);
+  }
+
+  /* Add SRV records */
+  va_copy(va, va0);
+  for (;(prefix = va_arg(va, char *));) {
+    priority = va_arg(va, int);
+    uri = va_arg(va, url_t *); assert(uri);
+
+    make_server(server, prefix, domain);
+
+    memset(m, 0, sizeof m);
+    put_srv_record_from_uri(m, domain, priority, 10, uri, server);
+    m->header->mh_ancount++;
+
+    strcpy(server, prefix); strcat(server, domain);
+
+    put_a_record(m, server, localhost);
+    m->header->mh_arcount++;
+
+    s2_add_to_zone(m);
+  }
+  va_end(va);
+
+  /* Add A records */
+  va_copy(va, va0);
+  for (;(prefix = va_arg(va, char *));) {
+    (void)va_arg(va, int);
+    (void)va_arg(va, url_t *);
+
+    memset(m, 0, sizeof m);
+    make_server(server, prefix, domain);
+
+    put_query(m, server, sres_type_a);
+    put_a_record(m, server, localhost);
+    m->header->mh_ancount++;
+
+    s2_add_to_zone(m);
+  }
+  va_end(va);
+
+  va_end(va0);
+}
+
+void
+s2_teardown(void)
+{
+  s2 = NULL;
+  su_deinit();
+}
+
+/* ====================================================================== */
+
+#include <sofia-sip/sresolv.h>
+
+nua_t *s2_nua_setup(tag_type_t tag, tag_value_t value, ...)
+{
+  ta_list ta;
+
+  s2_setup_base(NULL);
+  s2_setup_dns();
+
+  s2_setup_logs(0);
+  s2_setup_tport(NULL, TPTAG_LOG(0), TAG_END());
+  assert(s2->contact);
+
+  s2_dns_domain("example.org", 1,
+		"s2", 1, s2->udp.contact->m_url,
+		"s2", 1, s2->tcp.contact->m_url,
+		NULL);
+
+  ta_start(ta, tag, value);
+  s2->nua = 
+    nua_create(s2->root,
+	       s2_nua_callback,
+	       s2,
+	       SIPTAG_FROM_STR("Alice <sip:alice at example.org>"),
+	       /* NUTAG_PROXY((url_string_t *)s2->contact->m_url), */
+	       /* Use internal DNS server */
+	       NUTAG_PROXY("sip:example.org"),
+#if HAVE_WIN32
+	       SRESTAG_RESOLV_CONF("NUL"),
+#else
+	       SRESTAG_RESOLV_CONF("/dev/null"),
+#endif
+	       ta_tags(ta));
+  ta_end(ta);
+  
+  return s2->nua;
+}
+
+void s2_nua_teardown(void)
+{
+  nua_destroy(s2->nua);
+  s2->nua = NULL;
+  s2_teardown();
+}
+
+/* ====================================================================== */
+
+/** Register NUA user.
+ *
+ * <pre>
+ *  A                  B
+ *  |-----REGISTER---->|
+ *  |<-----200 OK------|
+ *  |                  |
+ * </pre>
+ */
+void s2_register_setup(void)
+{
+  nua_handle_t *nh;
+  struct message *m;
+
+  assert(s2 && s2->nua);
+  assert(!s2->registration->nh);
+
+  nh = nua_handle(s2->nua, NULL, TAG_END());
+
+  nua_register(nh, TAG_END());
+
+  m = s2_wait_for_request(SIP_METHOD_REGISTER);
+  assert(m);
+  s2_save_register(m);
+
+  s2_respond_to(m, NULL,
+		SIP_200_OK,
+		SIPTAG_CONTACT(s2->registration->contact),
+		TAG_END());
+  s2_free_message(m);
+
+  assert(s2->registration->contact != NULL);
+  s2_check_event(nua_r_register, 200);
+
+  s2->registration->nh = nh;
+}
+
+/** Un-register NUA user.
+ *
+ * <pre>
+ *  A                  B
+ *  |-----REGISTER---->|
+ *  |<-----200 OK------|
+ *  |                  |
+ * </pre>
+ */
+void s2_register_teardown(void)
+{
+  if (s2 && s2->registration->nh) {
+    nua_handle_t *nh = s2->registration->nh;
+    struct message *m;
+
+    nua_unregister(nh, TAG_END());
+    
+    m = s2_wait_for_request(SIP_METHOD_REGISTER); assert(m);
+    s2_save_register(m);
+    s2_respond_to(m, NULL,
+		  SIP_200_OK,
+		  SIPTAG_CONTACT(s2->registration->contact),
+		  TAG_END());
+    assert(s2->registration->contact == NULL);
+
+    s2_free_message(m);
+
+    s2_check_event(nua_r_unregister, 200);
+
+    nua_handle_destroy(nh);
+    s2->registration->nh = NULL;
+  }
+}
+

Added: freeswitch/trunk/libs/sofia-sip/libsofia-sip-ua/nua/test_s2.h
==============================================================================
--- (empty file)
+++ freeswitch/trunk/libs/sofia-sip/libsofia-sip-ua/nua/test_s2.h	Wed May 14 15:10:54 2008
@@ -0,0 +1,174 @@
+/*
+ * This file is part of the Sofia-SIP package
+ *
+ * Copyright (C) 2008 Nokia Corporation.
+ *
+ * Contact: Pekka Pessi <pekka.pessi at nokia.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA
+ */
+
+#ifndef S2TESTER_H
+#define S2TESTER_H 
+
+#define TP_STACK_T struct tester
+#define SU_ROOT_MAGIC_T struct tester
+
+#include <sofia-sip/su_wait.h>
+#include <sofia-sip/sip.h>
+#include <sofia-sip/tport.h>
+#include <sofia-sip/nua.h>
+
+struct tester
+{
+  su_home_t home[1];
+
+  su_root_t *root;
+  msg_mclass_t const *mclass;
+  int flags;
+
+  char const *hostname;
+  tport_t *master;
+
+  sip_to_t *local;
+  sip_contact_t *contact;
+  struct {
+    sip_contact_t *contact;
+    tport_t *tport;
+  } udp, tcp, tls;
+
+  struct message {
+    struct message *next, **prev;
+    msg_t *msg;
+    sip_t *sip;
+    tport_t *tport;
+    su_time_t when;
+  } *received;
+
+  struct {
+    su_socket_t socket;
+    su_wait_t wait[1];
+    int reg;
+  } dns;
+
+  nua_t *nua;
+
+  struct event {
+    struct event *next, **prev;
+    nua_saved_event_t event[1];
+    nua_handle_t *nh;
+    nua_event_data_t const *data;
+    su_time_t when;
+  } *events;
+
+  struct {
+    nua_handle_t *nh;
+    sip_to_t *aor;
+    sip_contact_t *contact;
+    tport_t *tport;
+  } registration[1];
+
+  unsigned long tid;
+
+  /* Settings */
+  int server_uses_rport;
+};
+
+struct dialog
+{
+  su_home_t home[1];
+  sip_from_t *local;
+  sip_to_t *remote;
+  sip_call_id_t *call_id;
+  uint32_t lseq, rseq;
+  sip_contact_t *target;
+  sip_route_t *route;
+  sip_contact_t *contact;
+
+  tport_t *tport;
+  msg_t *invite;		/* latest invite sent */
+};
+
+extern struct tester *s2;
+extern tp_stack_class_t const s2_stack[1];
+
+extern unsigned s2_default_registration_duration;
+extern char const s2_auth_digest_str[];
+extern char const s2_auth_credentials[];
+
+extern char const s2_auth2_digest_str[];
+extern char const s2_auth2_credentials[];
+
+extern char const s2_auth3_digest_str[];
+extern char const s2_auth3_credentials[];
+
+void s2_fast_forward(unsigned long seconds);
+
+void s2_case(char const *tag,
+	    char const *title,
+	    char const *description);
+
+struct event *s2_remove_event(struct event *);
+void s2_free_event(struct event *);
+void s2_flush_events(void);
+
+struct event *s2_next_event(void);
+struct event *s2_wait_for_event(nua_event_t event, int status);
+int s2_check_event(nua_event_t event, int status);
+int s2_check_callstate(enum nua_callstate state);
+
+struct message *s2_remove_message(struct message *m);
+void s2_free_message(struct message *m);
+void s2_flush_messages(void);
+
+struct message *s2_next_response(void);
+struct message *s2_wait_for_response(int status, sip_method_t , char const *);
+int s2_check_response(int status, sip_method_t method, char const *name);
+
+struct message *s2_next_request(void);
+struct message *s2_wait_for_request(sip_method_t method, char const *name);
+int s2_check_request(sip_method_t method, char const *name);
+
+#define SIP_METHOD_UNKNOWN sip_method_unknown, NULL
+
+struct message *s2_respond_to(struct message *m, struct dialog *d,
+			      int status, char const *phrase,
+			      tag_type_t tag, tag_value_t value, ...);
+
+int s2_request_to(struct dialog *d,
+		  sip_method_t method, char const *name,
+		  tport_t *tport,
+		  tag_type_t tag, tag_value_t value, ...);
+
+int s2_update_dialog(struct dialog *d, struct message *response);
+
+int s2_save_register(struct message *m);
+
+void s2_flush_all(void);
+
+void s2_setup_base(char const *hostname);
+void s2_setup_logs(int level);
+void s2_setup_tport(char const * const *protocols,
+		    tag_type_t tag, tag_value_t value, ...);
+void s2_teardown(void);
+
+nua_t *s2_nua_setup(tag_type_t tag, tag_value_t value, ...);
+void s2_nua_teardown(void);
+
+void s2_register_setup(void);
+void s2_register_teardown(void);
+
+#endif

Added: freeswitch/trunk/libs/sofia-sip/tests/test_100rel.c
==============================================================================
--- (empty file)
+++ freeswitch/trunk/libs/sofia-sip/tests/test_100rel.c	Wed May 14 15:10:54 2008
@@ -0,0 +1,2624 @@
+/*
+ * This file is part of the Sofia-SIP package
+ *
+ * Copyright (C) 2005 Nokia Corporation.
+ *
+ * Contact: Pekka Pessi <pekka.pessi at nokia.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA
+ *
+ */
+
+/**@CFILE test_nua_100rel.c
+ * @brief NUA-10 tests: early session, PRACK, UPDATE, precondition.
+ *
+ * @author Pekka Pessi <Pekka.Pessi at nokia.com>
+ * @author Martti Mela <Martti.Mela at nokia.com>
+ *
+ * @date Created: Wed Aug 17 12:12:12 EEST 2005 ppessi
+ */
+
+#include "config.h"
+
+#include "test_nua.h"
+#include <sofia-sip/auth_common.h>
+#include <sofia-sip/su_tag_class.h>
+
+#if HAVE_FUNC
+#elif HAVE_FUNCTION
+#define __func__ __FUNCTION__
+#else
+#define __func__ "test_call_hold"
+#endif
+
+/* ======================================================================== */
+
+/*
+ X  accept_pracked    ep
+ |-------INVITE------>|
+ |        (sdp)       |
+ |                    |
+ |<----100 Trying-----|
+ |                    |
+ |<-------180---------|
+ |       (sdp)        |
+ |-------PRACK------->|
+ |<-------200---------|
+ |                    |
+ |<------200 OK-------|
+ |--------ACK-------->|
+ |                    |
+*/
+int accept_pracked(CONDITION_PARAMS)
+{
+  if (!(check_handle(ep, call, nh, SIP_500_INTERNAL_SERVER_ERROR)))
+    return 0;
+
+  save_event_in_list(ctx, event, ep, call);
+
+  switch (event) {
+  case nua_i_prack:
+    if (200 <= status && status < 300) {
+      RESPOND(ep, call, nh, SIP_200_OK, TAG_END());
+      ep->next_condition = until_ready;
+    }
+  default:
+    break;
+  }
+
+  switch (callstate(tags)) {
+  case nua_callstate_received:
+    RESPOND(ep, call, nh, SIP_180_RINGING,
+	    TAG_IF(call->sdp, SOATAG_USER_SDP_STR(call->sdp)),
+	    TAG_END());
+    return 0;
+  case nua_callstate_terminated:
+    if (call)
+      nua_handle_destroy(call->nh), call->nh = NULL;
+    return 1;
+  default:
+    return 0;
+  }
+}
+
+int accept_pracked2(CONDITION_PARAMS)
+{
+  if (!(check_handle(ep, call, nh, SIP_500_INTERNAL_SERVER_ERROR)))
+    return 0;
+
+  save_event_in_list(ctx, event, ep, call);
+
+  switch (callstate(tags)) {
+  case nua_callstate_received:
+    RESPOND(ep, call, nh, SIP_180_RINGING,
+	    TAG_IF(call->sdp, SOATAG_USER_SDP_STR(call->sdp)),
+	    TAG_END());
+    RESPOND(ep, call, nh, SIP_200_OK, 
+	    NUTAG_INCLUDE_EXTRA_SDP(1),
+	    TAG_END());
+    return 0;
+  case nua_callstate_ready:
+    return 1;
+  case nua_callstate_terminated:
+    if (call)
+      nua_handle_destroy(call->nh), call->nh = NULL;
+    return 1;
+  default:
+    return 0;
+  }
+}
+
+
+int test_180rel(struct context *ctx)
+{
+  BEGIN();
+
+  struct endpoint *a = &ctx->a,  *b = &ctx->b;
+  struct call *a_call = a->call, *b_call = b->call;
+  struct event *e, *ep, *ei;
+  sip_t *sip;
+
+  if (print_headings)
+    printf("TEST NUA-10.1.1: Call with 100rel and 180\n");
+
+/* Test for 100rel:
+
+   A			B
+   |-------INVITE------>|
+   |<----100 Trying-----|
+   |			|
+   |<-------180---------|
+   |-------PRACK------->|
+   |<-------200---------|
+   |			|
+   |<------200 OK-------|
+   |--------ACK-------->|
+   |			|
+   |<-------BYE---------|
+   |-------200 OK-------|
+   |			|
+
+*/
+
+  a_call->sdp = "m=audio 5008 RTP/AVP 8";
+  b_call->sdp = "m=audio 5010 RTP/AVP 0 8";
+
+  nua_set_params(ctx->a.nua,
+		 NUTAG_EARLY_MEDIA(1),
+		 TAG_END());
+  run_a_until(ctx, nua_r_set_params, until_final_response);
+
+  nua_set_params(ctx->b.nua,
+		 NUTAG_EARLY_MEDIA(1),
+		 NUTAG_SESSION_TIMER(180),
+		 TAG_END());
+  run_b_until(ctx, nua_r_set_params, until_final_response);
+
+  TEST_1(a_call->nh = nua_handle(a->nua, a_call, SIPTAG_TO(b->to), TAG_END()));
+
+  INVITE(a, a_call, a_call->nh,
+	 TAG_IF(!ctx->proxy_tests, NUTAG_URL(b->contact->m_url)),
+	 SOATAG_USER_SDP_STR(a_call->sdp),
+	 TAG_END());
+
+  run_ab_until(ctx, -1, until_ready, -1, accept_pracked2);
+
+  /* Client transitions:
+     INIT -(C1)-> CALLING: nua_invite(), nua_i_state
+     CALLING -(C2)-> PROCEEDING: nua_r_invite, nua_i_state, nua_r_prack
+     PROCEEDING -(C3+C4)-> READY: nua_r_invite, nua_i_state
+  */
+  TEST_1(e = a->events->head); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_calling); /* CALLING */
+  TEST_1(is_offer_sent(e->data->e_tags));
+
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_r_invite);
+  TEST(e->data->e_status, 180);
+
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_proceeding);
+  TEST_1(is_answer_recv(e->data->e_tags));
+  TEST_1(!is_offer_sent(e->data->e_tags));
+
+  ei = event_by_type(e->next, nua_r_invite);
+  ep = event_by_type(e->next, nua_r_prack);
+  if (!ep) {
+    run_a_until(ctx, -1, save_until_final_response);
+    ep = event_by_type(e->next, nua_r_prack);
+  }
+
+  TEST_1(e = ep); TEST_E(e->data->e_event, nua_r_prack);
+  TEST(e->data->e_status, 200);
+
+  TEST_1(e = ei); TEST_E(e->data->e_event, nua_r_invite);
+  TEST(e->data->e_status, 200);
+  TEST_1(sip = sip_object(e->data->e_msg));
+  TEST_1(sip->sip_content_type);
+  TEST_S(sip->sip_content_type->c_type, "application/sdp");
+  TEST_1(sip->sip_payload);
+
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_ready); /* READY */
+  TEST_1(!is_offer_answer_done(e->data->e_tags));
+
+  TEST_1(!e->next || !ep->next);
+  free_events_in_list(ctx, a->events);
+
+  /*
+   Server transitions:
+   INIT -(S1)-> RECEIVED: nua_i_invite, nua_i_state
+   RECEIVED -(S2a)-> EARLY: nua_respond(), nua_i_state
+   EARLY -(S3b)-> COMPLETED: nua_respond(), nua_i_state
+   COMPLETED -(S4)-> READY: nua_i_ack, nua_i_state
+  */
+  TEST_1(e = b->events->head); TEST_E(e->data->e_event, nua_i_invite);
+  TEST(e->data->e_status, 100);
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_received); /* RECEIVED */
+  TEST_1(is_offer_recv(e->data->e_tags));
+
+  /* Responded with 180 Ringing */
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_early); /* EARLY */
+  TEST_1(is_answer_sent(e->data->e_tags));
+
+  /* 180 is PRACKed */
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_prack);
+  /* Does not have effect on call state */
+
+  /* Respond with 200 OK */
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_completed); /* COMPLETED */
+  TEST_1(!is_offer_answer_done(e->data->e_tags));
+
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_ack);
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_ready); /* READY */
+  TEST_1(!is_offer_answer_done(e->data->e_tags));
+  TEST_1(!e->next);
+  free_events_in_list(ctx, b->events);
+
+  if (print_headings)
+    printf("TEST NUA-10.1.1: PASSED\n");
+
+  if (print_headings)
+    printf("TEST NUA-10.1.2: terminate call\n");
+
+  BYE(b, b_call, b_call->nh, TAG_END());
+  run_ab_until(ctx, -1, until_terminated, -1, until_terminated);
+
+  /* B transitions:
+   READY --(T2)--> TERMINATING: nua_bye()
+   TERMINATING --(T3)--> TERMINATED: nua_r_bye, nua_i_state
+  */
+  TEST_1(e = b->events->head); TEST_E(e->data->e_event, nua_r_bye);
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_terminated); /* TERMINATED */
+  TEST_1(!e->next);
+  free_events_in_list(ctx, b->events);
+
+  /* A: READY -(T1)-> TERMINATED: nua_i_bye, nua_i_state */
+  TEST_1(e = a->events->head); TEST_E(e->data->e_event, nua_i_bye);
+  TEST(e->data->e_status, 200);
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_terminated); /* TERMINATED */
+  TEST_1(!e->next);
+  free_events_in_list(ctx, a->events);
+
+  nua_handle_destroy(a_call->nh), a_call->nh = NULL;
+  nua_handle_destroy(b_call->nh), b_call->nh = NULL;
+
+  if (print_headings)
+    printf("TEST NUA-10.1.2: PASSED\n");
+  
+  END();
+}
+
+/*
+ X      INVITE
+ |                    |
+ |-------INVITE------>|
+ |<--------200--------|
+ |---------ACK------->|
+*/
+int authenticate_until_ready(CONDITION_PARAMS)
+{
+  if (!(check_handle(ep, call, nh, SIP_500_INTERNAL_SERVER_ERROR)))
+    return 0;
+
+  save_event_in_list(ctx, event, ep, call);
+
+  if (status == 401 || status == 407) {
+    AUTHENTICATE(ep, call, nh,
+		 NUTAG_AUTH("Digest:\"test-proxy\":charlie:secret"),
+		 TAG_END());
+  }
+
+  switch (callstate(tags)) {
+  case nua_callstate_ready:
+    return 1;
+  case nua_callstate_terminated:
+    if (call)
+      nua_handle_destroy(call->nh), call->nh = NULL;
+    return 1;
+  default:
+    return 0;
+  }
+}
+
+/** Test authentication for PRACK */
+int test_prack_auth(struct context *ctx)
+{
+  if (!ctx->proxy_tests)
+    return 0;
+
+  BEGIN();
+
+  struct endpoint *c = &ctx->c,  *b = &ctx->b;
+  struct call *c_call = c->call, *b_call = b->call;
+  struct event *e, *ep, *ei;
+  sip_t *sip;
+  sip_proxy_authenticate_t *au;
+
+  if (print_headings)
+    printf("TEST NUA-10.1.3: Call with 100rel, PRACK is challenged\n");
+
+/* Test for authentication during 100rel
+
+   C			B
+   |-------INVITE--\    |
+   |<-------407----/    |
+   |			|
+   |-------INVITE------>|
+   |<----100 Trying-----|
+   |			|
+   |<-------180---------|
+   |-------PRACK---\    |
+   |<-------407----/    |
+   |-------PRACK------->|
+   |<-------200---------|
+   |			|
+   |<------200 OK-------|
+   |--------ACK-------->|
+   |			|
+   |<-------BYE---------|
+   |-------200 OK-------|
+   |			|
+
+*/
+
+  c_call->sdp = "m=audio 5008 RTP/AVP 8";
+  b_call->sdp = "m=audio 5010 RTP/AVP 0 8";
+
+  nua_set_params(ctx->c.nua,
+		 NUTAG_EARLY_MEDIA(1),
+		 TAG_END());
+  run_c_until(ctx, nua_r_set_params, until_final_response);
+
+  nua_set_params(ctx->b.nua,
+		 NUTAG_EARLY_MEDIA(1),
+		 TAG_END());
+  run_b_until(ctx, nua_r_set_params, until_final_response);
+
+  TEST_1(c_call->nh = nua_handle(c->nua, c_call, SIPTAG_TO(b->to), TAG_END()));
+
+  INVITE(c, c_call, c_call->nh,
+	 TAG_IF(!ctx->proxy_tests, NUTAG_URL(b->contact->m_url)),
+	 SIPTAG_FROM(c->to),
+	 SOATAG_USER_SDP_STR(c_call->sdp),
+	 TAG_END());
+
+  run_bc_until(ctx, -1, accept_pracked, -1, authenticate_until_ready);
+
+  /* Client transitions:
+     INIT -(C1)-> CALLING: nua_invite(), nua_i_state
+     CALLING -(C2)-> PROCEEDING: nua_r_invite, nua_i_state, nua_r_prack
+     PROCEEDING -(C3+C4)-> READY: nua_r_invite, nua_i_state
+  */
+  TEST_1(e = c->events->head); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_calling); /* CALLING */
+  TEST_1(is_offer_sent(e->data->e_tags));
+
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_r_invite);
+  TEST(e->data->e_status, 407);
+  TEST_1(sip = sip_object(e->data->e_msg));
+  TEST_1(au = sip->sip_proxy_authenticate); 
+
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_calling); /* CALLING */
+  TEST_1(is_offer_sent(e->data->e_tags));
+
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_r_invite);
+  TEST(e->data->e_status, 180);
+
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_proceeding);
+  TEST_1(is_answer_recv(e->data->e_tags));
+  TEST_1(!is_offer_sent(e->data->e_tags));
+
+  ei = event_by_type(e->next, nua_r_invite);
+  ep = event_by_type(e->next, nua_r_prack);
+
+  TEST_1(e = ep); TEST_E(e->data->e_event, nua_r_prack);
+  if (e->data->e_status == 100 || e->data->e_status == 407) {
+    /* The final response to PRACK may be received after ACK is sent */
+    if (!event_by_type(e->next, nua_r_prack))
+      run_bc_until(ctx, -1, save_events, -1, save_until_final_response);
+    TEST_1(e = ep = event_by_type(e->next, nua_r_prack));
+  }
+  TEST_E(e->data->e_event, nua_r_prack);
+  TEST(e->data->e_status, 200);
+  TEST_1(sip = sip_object(e->data->e_msg));
+  TEST_1(sip->sip_from->a_url->url_user);
+
+  TEST_1(e = ei); TEST_E(e->data->e_event, nua_r_invite);
+  TEST(e->data->e_status, 200);
+
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_ready); /* READY */
+  TEST_1(!is_offer_answer_done(e->data->e_tags));
+  TEST_1(!e->next || !ep->next);
+  free_events_in_list(ctx, c->events);
+
+  /*
+   Server transitions:
+   INIT -(S1)-> RECEIVED: nua_i_invite, nua_i_state
+   RECEIVED -(S2a)-> EARLY: nua_respond(), nua_i_state
+   EARLY -(S3b)-> COMPLETED: nua_respond(), nua_i_state
+   COMPLETED -(S4)-> READY: nua_i_ack, nua_i_state
+  */
+  TEST_1(e = b->events->head); TEST_E(e->data->e_event, nua_i_invite);
+  TEST(e->data->e_status, 100);
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_received); /* RECEIVED */
+  TEST_1(is_offer_recv(e->data->e_tags));
+
+  /* Responded with 180 Ringing */
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_early); /* EARLY */
+  TEST_1(is_answer_sent(e->data->e_tags));
+
+  /* 180 is PRACKed */
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_prack);
+  /* Does not have effect on call state */
+
+  /* Respond with 200 OK */
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_completed); /* COMPLETED */
+  TEST_1(!is_offer_answer_done(e->data->e_tags));
+
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_ack);
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_ready); /* READY */
+  TEST_1(!is_offer_answer_done(e->data->e_tags));
+  TEST_1(!e->next);
+  free_events_in_list(ctx, b->events);
+
+  if (print_headings)
+    printf("TEST NUA-10.1.3: PASSED\n");
+
+  if (print_headings)
+    printf("TEST NUA-10.1.4: terminate call\n");
+
+  BYE(b, b_call, b_call->nh, TAG_END());
+  run_bc_until(ctx, -1, until_terminated, -1, until_terminated);
+
+  /* B transitions:
+   READY --(T2)--> TERMINATING: nua_bye()
+   TERMINATING --(T3)--> TERMINATED: nua_r_bye, nua_i_state
+  */
+  TEST_1(e = b->events->head); TEST_E(e->data->e_event, nua_r_bye);
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_terminated); /* TERMINATED */
+  TEST_1(!e->next);
+  free_events_in_list(ctx, b->events);
+
+  /* C: READY -(T1)-> TERMINATED: nua_i_bye, nua_i_state */
+  TEST_1(e = c->events->head); TEST_E(e->data->e_event, nua_i_bye);
+  TEST(e->data->e_status, 200);
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_terminated); /* TERMINATED */
+  TEST_1(!e->next);
+  free_events_in_list(ctx, c->events);
+
+  nua_handle_destroy(c_call->nh), c_call->nh = NULL;
+  nua_handle_destroy(b_call->nh), b_call->nh = NULL;
+
+  if (print_headings)
+    printf("TEST NUA-10.1.4: PASSED\n");
+  
+  END();
+}
+
+/*
+ X  ringing_pracked    ep
+ |-------INVITE------>|
+ |<----100 Trying-----|
+ |                    |
+ |<-------183---------|
+ |-------PRACK------->|
+ |<-------200---------|
+ |                    |
+ |<-------180---------|
+ |-------PRACK------->|
+ |<-------200---------|
+ |                    |
+ |<------200 OK-------|
+ |--------ACK-------->|
+ |                    |
+*/
+int ringing_pracked(CONDITION_PARAMS)
+{
+  if (!(check_handle(ep, call, nh, SIP_500_INTERNAL_SERVER_ERROR)))
+    return 0;
+
+  save_event_in_list(ctx, event, ep, call);
+
+  switch (event) {
+  case nua_i_prack:
+    if (200 <= status && status < 300) {
+      RESPOND(ep, call, nh, SIP_180_RINGING, TAG_END());
+      ep->next_condition = accept_pracked;
+    }
+  default:
+    break;
+  }
+
+  switch (callstate(tags)) {
+  case nua_callstate_received:
+    RESPOND(ep, call, nh, SIP_183_SESSION_PROGRESS,
+	    TAG_IF(call->sdp, SOATAG_USER_SDP_STR(call->sdp)),
+	    TAG_END());
+    return 0;
+  case nua_callstate_terminated:
+    if (call)
+      nua_handle_destroy(call->nh), call->nh = NULL;
+    return 1;
+  default:
+    return 0;
+  }
+}
+
+int respond_483_to_prack(CONDITION_PARAMS);
+static int prack_100rel(CONDITION_PARAMS);
+
+int test_183rel(struct context *ctx)
+{
+  BEGIN();
+
+  struct endpoint *a = &ctx->a,  *b = &ctx->b;
+  struct call *a_call = a->call, *b_call = b->call;
+  struct event *e, *ei, *ep;
+
+  if (print_headings)
+    printf("TEST NUA-10.2.1: Call with 100rel, 183 and 180\n");
+
+  TEST_1(a_call->nh = nua_handle(a->nua, a_call, SIPTAG_TO(b->to), TAG_END()));
+
+  INVITE(a, a_call, a_call->nh,
+	 TAG_IF(!ctx->proxy_tests, NUTAG_URL(b->contact->m_url)),
+	 SOATAG_USER_SDP_STR(a_call->sdp),
+	 TAG_END());
+
+  run_ab_until(ctx, -1, until_ready, -1, ringing_pracked);
+
+  /* Client transitions:
+     INIT -(C1)-> CALLING: nua_invite(), nua_i_state
+     CALLING -(C2)-> PROCEEDING: nua_r_invite, nua_i_state, nua_r_prack
+     PROCEEDING -(C2)-> PROCEEDING: nua_r_invite, nua_i_state, nua_r_prack
+     PROCEEDING -(C3+C4)-> READY: nua_r_invite, nua_i_state
+  */
+  TEST_1(e = a->events->head); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_calling); /* CALLING */
+  TEST_1(is_offer_sent(e->data->e_tags));
+
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_r_invite);
+  TEST(e->data->e_status, 183);
+
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_proceeding);
+  TEST_1(is_answer_recv(e->data->e_tags));
+  TEST_1(!is_offer_sent(e->data->e_tags));
+
+  TEST_1(e = e->next);
+
+  ep = e->data->e_event == nua_r_prack ? e : NULL;
+
+  if (ep) {
+    TEST(ep->data->e_status, 200); TEST_1(e = e->next);
+  }
+
+  TEST_E(e->data->e_event, nua_r_invite);
+  TEST(e->data->e_status, 180);
+
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_proceeding);
+  TEST_1(!is_offer_answer_done(e->data->e_tags));
+  
+  if (!ep) {
+    TEST_1(e = e->next); TEST_E(e->data->e_event, nua_r_prack);
+    TEST(e->data->e_status, 200);
+  }
+    
+  ei = event_by_type(e->next, nua_r_invite);
+  ep = event_by_type(e->next, nua_r_prack);
+  if (!ep) {
+    run_a_until(ctx, -1, save_until_final_response);
+    ep = event_by_type(e->next, nua_r_prack);
+  }
+
+  TEST_1(e = ep); TEST_E(e->data->e_event, nua_r_prack);
+  TEST(e->data->e_status, 200);
+
+  TEST_1(e = ei); TEST_E(e->data->e_event, nua_r_invite);
+  TEST(e->data->e_status, 200);
+
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_ready); /* READY */
+  TEST_1(!is_offer_answer_done(e->data->e_tags));
+  TEST_1(!e->next || !ep->next);
+  free_events_in_list(ctx, a->events);
+
+  /*
+   Server transitions:
+   INIT -(S1)-> RECEIVED: nua_i_invite, nua_i_state
+   RECEIVED -(S2a)-> EARLY: nua_respond(), nua_i_state
+   EARLY -(S3b)-> COMPLETED: nua_respond(), nua_i_state
+   COMPLETED -(S4)-> READY: nua_i_ack, nua_i_state
+  */
+  TEST_1(e = b->events->head); TEST_E(e->data->e_event, nua_i_invite);
+  TEST(e->data->e_status, 100);
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_received); /* RECEIVED */
+  TEST_1(is_offer_recv(e->data->e_tags));
+
+  /* Responded with 183 Session Progress */
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(e->data->e_status, 183);
+  TEST(callstate(e->data->e_tags), nua_callstate_early); /* EARLY */
+  TEST_1(is_answer_sent(e->data->e_tags));
+
+  /* 183 is PRACKed */
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_prack);
+  /* Does not have effect on call state */
+
+  /* Responded with 180 Ringing */
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(e->data->e_status, 180);
+  TEST(callstate(e->data->e_tags), nua_callstate_early); /* EARLY */
+  TEST_1(!is_offer_answer_done(e->data->e_tags));
+
+  /* 180 is PRACKed */
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_prack);
+  /* Does not have effect on call state */
+
+  /* Respond with 200 OK */
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(e->data->e_status, 200);
+  TEST(callstate(e->data->e_tags), nua_callstate_completed); /* COMPLETED */
+  TEST_1(!is_offer_answer_done(e->data->e_tags));
+
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_ack);
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_ready); /* READY */
+  TEST_1(!is_offer_answer_done(e->data->e_tags));
+  TEST_1(!e->next);
+  free_events_in_list(ctx, b->events);
+
+  if (print_headings)
+    printf("TEST NUA-10.2.1: PASSED\n");
+
+  /* Test for graceful termination by client because 483 sent to PRACK */
+  if (print_headings)
+    printf("TEST NUA-10.2.2: graceful termination because PRACK fails\n");
+
+  nua_set_hparams(a_call->nh, NUTAG_APPL_METHOD("PRACK"), TAG_END());
+  nua_set_hparams(b_call->nh, NUTAG_APPL_METHOD("PRACK"),
+		  NUTAG_AUTOANSWER(0), TAG_END());
+  run_ab_until(ctx, nua_r_set_params, NULL, nua_r_set_params, NULL);
+
+  INVITE(a, a_call, a_call->nh, TAG_END());
+
+  run_ab_until(ctx, -1, prack_100rel, -1, respond_483_to_prack);
+
+  /* Client transitions:
+     INIT -(C1)-> CALLING: nua_invite(), nua_i_state
+     CALLING -(C2)-> TERMINATING: nua_r_invite, nua_i_state, 
+                                  nua_r_prack, nua_i_state
+     TERMINATING -(T1)-> TERMINATED: nua_r_invite, nua_i_state
+  */
+  TEST_1(e = a->events->head); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_calling); /* CALLING */
+  TEST_1(is_offer_sent(e->data->e_tags));
+
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_r_invite);
+  TEST(e->data->e_status, 183);
+
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_proceeding);
+  TEST_1(is_answer_recv(e->data->e_tags));
+  TEST_1(!is_offer_sent(e->data->e_tags));
+
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_r_prack);
+  TEST(e->data->e_status, 483);
+
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_terminating);
+
+  {
+    int bye = 1, cancel = 1, invite = 1;
+
+    while (bye || cancel || invite) {
+      TEST_1(e = e->next); 
+      if (e->data->e_event == nua_r_bye) {
+	TEST_E(e->data->e_event, nua_r_bye);
+	TEST(e->data->e_status, 200);
+	bye = 0;
+	break;
+      }
+      else if (e->data->e_event == nua_r_invite) {
+	TEST_E(e->data->e_event, nua_r_invite);
+	TEST(e->data->e_status, 487);
+	invite = 0;
+      }
+      else if (e->data->e_event == nua_r_cancel) {
+	TEST_E(e->data->e_event, nua_r_cancel);
+	TEST_1(e->data->e_status == 200 || e->data->e_status == 481);
+	cancel = 0;
+      }
+    }
+    TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+    TEST(callstate(e->data->e_tags), nua_callstate_terminated);
+  }
+
+  TEST_1(!e->next);
+
+  free_events_in_list(ctx, a->events);
+
+  /*
+   Server transitions:
+   INIT -(S1)-> RECEIVED: nua_i_invite, nua_i_state
+   RECEIVED -(S2a)-> EARLY: nua_respond(), nua_i_state, nua_respond(to PRACK)
+   EARLY -(S3b)-> TERMINATED: nua_i_bye, nua_i_state
+  */
+  TEST_1(e = b->events->head); TEST_E(e->data->e_event, nua_i_invite);
+  TEST(e->data->e_status, 100);
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_received); /* RECEIVED */
+  TEST_1(is_offer_recv(e->data->e_tags));
+
+  /* Responded with 183 Session Progress */
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(e->data->e_status, 183);
+  TEST(callstate(e->data->e_tags), nua_callstate_early); /* EARLY */
+  TEST_1(is_answer_sent(e->data->e_tags));
+
+  /* 183 is PRACKed, PRACK is responded with 483 */
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_prack);
+
+  /* Client terminates the call 
+     - we may (it is received before BYE) or may not (received after BYE) 
+       get CANCEL request 
+  */
+  TEST_1(e = e->next);
+  if (e->data->e_event == nua_i_cancel) {
+    TEST_E(e->data->e_event, nua_i_cancel);
+    TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+    TEST(callstate(e->data->e_tags), nua_callstate_ready);
+    TEST_1(e = e->next);
+  }
+
+  TEST_E(e->data->e_event, nua_i_bye);
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_terminated);
+  TEST_1(!e->next);
+
+  free_events_in_list(ctx, b->events);
+
+  nua_handle_destroy(a_call->nh), a_call->nh = NULL;
+  nua_handle_destroy(b_call->nh), b_call->nh = NULL;
+
+  if (print_headings)
+    printf("TEST NUA-10.2.2: PASSED\n");
+
+  END();
+}
+
+static int prack_100rel(CONDITION_PARAMS)
+{
+  if (!check_handle(ep, call, nh, SIP_500_INTERNAL_SERVER_ERROR))
+    return 0;
+
+  save_event_in_list(ctx, event, ep, call);
+
+  if (event == nua_r_invite && 100 < status && status < 200 &&
+      sip_has_feature(sip->sip_require, "100rel")) {
+    sip_rack_t rack[1];
+
+    sip_rack_init(rack);
+    rack->ra_response = sip->sip_rseq->rs_response;
+    rack->ra_cseq = sip->sip_cseq->cs_seq;
+    rack->ra_method = sip->sip_cseq->cs_method;
+    rack->ra_method_name = sip->sip_cseq->cs_method_name;
+
+    nua_prack(nh, SIPTAG_RACK(rack), TAG_END());
+  }
+
+  return event == nua_i_state && callstate(tags) == nua_callstate_terminated;
+}
+
+int respond_483_to_prack(CONDITION_PARAMS)
+{
+  if (!(check_handle(ep, call, nh, SIP_500_INTERNAL_SERVER_ERROR)))
+    return 0;
+
+  switch (event) {
+  case nua_i_prack:
+    if (status <= 200) {
+      RESPOND(ep, call, nh, 483, "Foo",
+	      NUTAG_WITH_THIS(nua),
+	      TAG_END());
+    }
+  default:
+    break;
+  }
+
+  save_event_in_list(ctx, event, ep, call);
+
+  switch (callstate(tags)) {
+  case nua_callstate_received:
+    RESPOND(ep, call, nh, SIP_183_SESSION_PROGRESS,
+	    TAG_IF(call->sdp, SOATAG_USER_SDP_STR(call->sdp)),
+	    TAG_END());
+    return 0;
+  case nua_callstate_terminated:
+    if (call)
+      nua_handle_destroy(call->nh), call->nh = NULL;
+    return 1;
+  default:
+    return 0;
+  }
+}
+
+/*
+ X  ringing_updated   ep
+ |-------INVITE------>|
+ |       (sdp)        |
+ |<----100 Trying-----|
+ |                    |
+ |<-------183---------|
+ |       (sdp)        |
+ |-------PRACK------->|
+ |       (sdp)        |
+ |<-------200---------|
+ |       (sdp)        |
+ |                    |
+ |-------UPDATE------>|
+ |       (sdp)        |
+ |<-------200---------|
+ |       (sdp)        |
+ |                    |
+<using  acccept_pracked>
+ |                    |
+ |<-------180---------|
+ |-------PRACK------->|
+ |<-------200---------|
+ |                    |
+ |<------200 OK-------|
+ |--------ACK-------->|
+ |                    |
+*/
+int ringing_updated(CONDITION_PARAMS)
+{
+  if (!(check_handle(ep, call, nh, SIP_500_INTERNAL_SERVER_ERROR)))
+    return 0;
+
+  save_event_in_list(ctx, event, ep, call);
+
+  switch (event) {
+  case nua_i_update:
+    if (200 <= status && status < 300) {
+      RESPOND(ep, call, nh, SIP_180_RINGING, TAG_END());
+      ep->next_condition = accept_pracked;
+    }
+    return 0;
+  default:
+    break;
+  }
+
+  switch (callstate(tags)) {
+  case nua_callstate_received:
+    RESPOND(ep, call, nh, SIP_183_SESSION_PROGRESS,
+	    SIPTAG_REQUIRE_STR("100rel"),
+	    TAG_IF(call->sdp, SOATAG_USER_SDP_STR(call->sdp)),
+	    TAG_END());
+    return 0;
+  case nua_callstate_early:
+    return 0;
+  case nua_callstate_ready:
+    return 1;
+  case nua_callstate_terminated:
+    if (call)
+      nua_handle_destroy(call->nh), call->nh = NULL;
+    return 1;
+  default:
+    return 0;
+  }
+}
+
+int test_preconditions(struct context *ctx)
+{
+  BEGIN();
+
+  struct endpoint *a = &ctx->a,  *b = &ctx->b;
+  struct call *a_call = a->call, *b_call = b->call;
+  struct event *e, *ep, *ei;
+  sip_t *sip;
+
+  if (print_headings)
+    printf("TEST NUA-10.3.1: Call with 100rel and preconditions\n");
+
+/* Test for precondition:
+
+   A			B
+   |-------INVITE------>|
+   |<----100 Trying-----|
+   |			|
+   |<-------183---------|
+   |-------PRACK------->|
+   |<-------200---------|
+   |			|
+   |-------UPDATE------>|
+   |<-------200---------|
+   |			|
+   |<-------180---------|
+   |-------PRACK------->|
+   |<-------200---------|
+   |			|
+   |<------200 OK-------|
+   |--------ACK-------->|
+   |			|
+   |<-------BYE---------|
+   |-------200 OK-------|
+   |			|
+
+*/
+
+  a_call->sdp = "m=audio 5008 RTP/AVP 8";
+  b_call->sdp = "m=audio 5010 RTP/AVP 0 8";
+
+  nua_set_params(ctx->a.nua,
+		 NUTAG_EARLY_MEDIA(1),
+		 SIPTAG_SUPPORTED_STR("100rel, precondition"),
+		 TAG_END());
+  run_a_until(ctx, nua_r_set_params, until_final_response);
+
+  nua_set_params(ctx->b.nua,
+		 NUTAG_EARLY_MEDIA(0),
+		 SIPTAG_SUPPORTED_STR("100rel, precondition, timer"),
+		 TAG_END());
+  run_b_until(ctx, nua_r_set_params, until_final_response);
+
+  TEST_1(a_call->nh = nua_handle(a->nua, a_call, SIPTAG_TO(b->to), TAG_END()));
+
+  INVITE(a, a_call, a_call->nh,
+	 TAG_IF(!ctx->proxy_tests, NUTAG_URL(b->contact->m_url)),
+	 SOATAG_USER_SDP_STR(a_call->sdp),
+	 SIPTAG_SUPPORTED_STR("100rel"),
+	 SIPTAG_REQUIRE_STR("precondition"),
+	 TAG_END());
+
+  run_ab_until(ctx, -1, until_ready, -1, ringing_updated);
+
+  /* Client transitions:
+     INIT -(C1)-> CALLING: nua_invite(), nua_i_state
+     CALLING -(C2)-> PROCEEDING: nua_r_invite, nua_i_state
+     PROCEEDING --> PROCEEDING: nua_r_prack, nua_i_state
+     PROCEEDING --> PROCEEDING: nua_r_update, nua_i_state
+     PROCEEDING -(C3+C4)-> READY: nua_r_invite, nua_i_state
+  */
+  TEST_1(e = a->events->head); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_calling); /* CALLING */
+  TEST_1(is_offer_sent(e->data->e_tags));
+
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_r_invite);
+  TEST(e->data->e_status, 183);
+
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_proceeding);
+  TEST_1(is_answer_recv(e->data->e_tags));
+  TEST_1(is_offer_sent(e->data->e_tags));
+
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_r_prack);
+  TEST(e->data->e_status, 200);
+
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_proceeding);
+  TEST_1(is_answer_recv(e->data->e_tags));
+  TEST_1(!is_offer_sent(e->data->e_tags));
+
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_proceeding);
+  TEST_1(!is_answer_recv(e->data->e_tags));
+  TEST_1(is_offer_sent(e->data->e_tags));
+
+  ei = event_by_type(e->next, nua_r_invite);
+  ep = event_by_type(e->next, nua_r_update);
+
+  TEST_1(e = ep); TEST_E(e->data->e_event, nua_r_update);
+  TEST(e->data->e_status, 200);
+  TEST_1(sip = sip_object(e->data->e_msg));
+  TEST_1(sip->sip_session_expires);
+
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_proceeding);
+  TEST_1(is_answer_recv(e->data->e_tags));
+  TEST_1(!is_offer_sent(e->data->e_tags));
+
+  TEST_1(e = ei); TEST_E(e->data->e_event, nua_r_invite);
+  TEST(e->data->e_status, 180);
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_proceeding); /* PROCEEDING */
+  TEST_1(!is_offer_answer_done(e->data->e_tags));
+
+  if (e == ep)			/* invite was responded before update */
+    e = ep->next->next;
+
+  ei = event_by_type(e->next, nua_r_invite);
+  ep = event_by_type(e->next, nua_r_prack);
+  if (!ep) {
+    run_a_until(ctx, -1, save_until_final_response);
+    ep = event_by_type(e->next, nua_r_prack);
+  }
+
+  TEST_1(e = ep); TEST_E(e->data->e_event, nua_r_prack);
+  TEST(e->data->e_status, 200);
+  /* Does not have effect on call state */
+
+  TEST_1(e = ei); TEST_E(e->data->e_event, nua_r_invite);
+  TEST(e->data->e_status, 200);
+  TEST_1(sip = sip_object(e->data->e_msg));
+  if (ctx->proxy_tests) {
+    TEST_1(sip->sip_session_expires);
+    TEST_S(sip->sip_session_expires->x_refresher, "uas");
+    TEST_1(!sip_has_supported(sip->sip_require, "timer"));
+  }
+
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_ready); /* READY */
+  TEST_1(!is_offer_answer_done(e->data->e_tags));
+  TEST_1(!e->next || !ep->next);
+  free_events_in_list(ctx, a->events);
+
+  /*
+   Server transitions:
+   INIT -(S1)-> RECEIVED: nua_i_invite, nua_i_state
+   RECEIVED -(S2a)-> EARLY: nua_respond(), nua_i_state
+   EARLY --> EARLY: nua_i_prack, nua_i_state
+   EARLY --> EARLY: nua_i_update, nua_i_state
+   EARLY --> EARLY: nua_r_update, nua_i_state
+   EARLY -(S3b)-> COMPLETED: nua_respond(), nua_i_state
+   COMPLETED -(S4)-> READY: nua_i_ack, nua_i_state
+  */
+  TEST_1(e = b->events->head); TEST_E(e->data->e_event, nua_i_invite);
+  TEST(e->data->e_status, 100);
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_received); /* RECEIVED */
+
+  /* Responded with 183 Session Progress */
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(e->data->e_status, 183);
+  TEST(callstate(e->data->e_tags), nua_callstate_early); /* EARLY */
+  TEST_1(is_answer_sent(e->data->e_tags));
+
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_prack);
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_early); /* EARLY */
+  TEST_1(is_offer_recv(e->data->e_tags));
+  TEST_1(is_answer_sent(e->data->e_tags));
+
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_update);
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_early); /* EARLY */
+  TEST_1(!is_offer_sent(e->data->e_tags)); 
+  TEST_1(is_offer_recv(e->data->e_tags));
+  TEST_1(is_answer_sent(e->data->e_tags));
+  TEST_1(!is_answer_recv(e->data->e_tags));
+
+  /* Responded with 180 Ringing */
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(e->data->e_status, 180);
+  TEST(callstate(e->data->e_tags), nua_callstate_early); /* EARLY */
+  TEST_1(!is_offer_answer_done(e->data->e_tags));
+
+  /* 180 PRACKed */
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_prack);
+  /* Does not have effect on call state */
+
+  /* Responded with 200 OK */
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(e->data->e_status, 200);
+  TEST(callstate(e->data->e_tags), nua_callstate_completed); /* COMPLETED */
+  TEST_1(!is_offer_answer_done(e->data->e_tags));
+
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_ack);
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_ready); /* READY */
+  TEST_1(!is_offer_answer_done(e->data->e_tags));
+  TEST_1(!e->next);
+  free_events_in_list(ctx, b->events);
+
+  if (print_headings)
+    printf("TEST NUA-10.3.1: PASSED\n");
+
+  if (print_headings)
+    printf("TEST NUA-10.3.2: terminate call\n");
+
+  BYE(b, b_call, b_call->nh, TAG_END());
+  run_ab_until(ctx, -1, until_terminated, -1, until_terminated);
+
+  /* B transitions:
+   READY --(T2)--> TERMINATING: nua_bye()
+   TERMINATING --(T3)--> TERMINATED: nua_r_bye, nua_i_state
+  */
+  TEST_1(e = b->events->head); TEST_E(e->data->e_event, nua_r_bye);
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_terminated); /* TERMINATED */
+  TEST_1(!e->next);
+  free_events_in_list(ctx, b->events);
+
+  /* A: READY -(T1)-> TERMINATED: nua_i_bye, nua_i_state */
+  TEST_1(e = a->events->head); TEST_E(e->data->e_event, nua_i_bye);
+  TEST(e->data->e_status, 200);
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_terminated); /* TERMINATED */
+  TEST_1(!e->next);
+  free_events_in_list(ctx, a->events);
+
+  nua_handle_destroy(a_call->nh), a_call->nh = NULL;
+  nua_handle_destroy(b_call->nh), b_call->nh = NULL;
+
+  if (print_headings)
+    printf("TEST NUA-10.3.2: PASSED\n");
+
+  END();
+}
+
+/*
+ X  accept_updated    ep
+ |-------INVITE------>|
+ |       (sdp)        |
+ |<----100 Trying-----|
+ |                    |
+ |<-------183---------|
+ |       (sdp)        |
+ |-------PRACK------->|
+ |       (sdp)        |
+ |<-------200---------|
+ |       (sdp)        |
+ |                    |
+ |-------UPDATE------>|
+ |       (sdp)        |
+ |<-------200---------|
+ |       (sdp)        |
+ |                    |
+ |                    |
+ |<-------180---------|
+ |                    |
+ |<------200 OK-------|
+ |--------ACK-------->|
+ |                    |
+*/
+int accept_updated(CONDITION_PARAMS)
+{
+  if (!(check_handle(ep, call, nh, SIP_500_INTERNAL_SERVER_ERROR)))
+    return 0;
+
+  save_event_in_list(ctx, event, ep, call);
+
+  switch (event) {
+  case nua_i_update:
+    if (200 <= status && status < 300) {
+      RESPOND(ep, call, nh, SIP_180_RINGING, TAG_END());
+    }
+    return 0;
+  default:
+    break;
+  }
+
+  switch (callstate(tags)) {
+  case nua_callstate_received:
+    RESPOND(ep, call, nh, SIP_183_SESSION_PROGRESS,
+	    SIPTAG_REQUIRE_STR("100rel"),
+	    TAG_IF(call->sdp, SOATAG_USER_SDP_STR(call->sdp)),
+	    TAG_END());
+    return 0;
+  case nua_callstate_early:
+    if (status == 180)
+      RESPOND(ep, call, nh, SIP_200_OK, TAG_END());
+    return 0;
+  case nua_callstate_ready:
+    return 1;
+  case nua_callstate_terminated:
+    if (call)
+      nua_handle_destroy(call->nh), call->nh = NULL;
+    return 1;
+  default:
+    return 0;
+  }
+}
+
+
+int test_preconditions2(struct context *ctx)
+{
+  BEGIN();
+
+  struct endpoint *a = &ctx->a,  *b = &ctx->b;
+  struct call *a_call = a->call, *b_call = b->call;
+  struct event *e, *eu, *ei;
+  enum nua_callstate ustate, istate;
+
+  if (print_headings)
+    printf("TEST NUA-10.4.1: Call with preconditions and non-100rel 180\n");
+
+/* Test 100rel and preconditions with NUTAG_ONLY183_100REL(1):
+
+   A			B
+   |-------INVITE------>|
+   |<----100 Trying-----|
+   |			|
+   |<-------183---------|
+   |-------PRACK------->|
+   |<-------200---------|
+   |			|
+   |-------UPDATE------>|
+ +------------------------+
+ | |<-------200---------| |
+ | |			| |
+ | |<-------180---------| |
+ | |			| |
+ | |<------200 OK-------| |
+ +------------------------+
+   |--------ACK-------->|
+   |			|
+   |<-------BYE---------|
+   |-------200 OK-------|
+   |			|
+
+   Note that the boxed responses above can be re-ordered 
+   (180 or 200 OK to INVITE is received before 200 OK to UPDATE).
+   ACK, however, is sent only after 200 OK to both UPDATE and INVITE. 
+*/
+
+  a_call->sdp = "m=audio 5008 RTP/AVP 8";
+  b_call->sdp = "m=audio 5010 RTP/AVP 0 8";
+
+  nua_set_params(ctx->a.nua,
+		 NUTAG_EARLY_MEDIA(1),
+		 SIPTAG_SUPPORTED_STR("100rel, precondition, timer"),
+		 TAG_END());
+  run_a_until(ctx, nua_r_set_params, until_final_response);
+
+  nua_set_params(ctx->b.nua,
+		 NUTAG_EARLY_MEDIA(1),
+		 NUTAG_ONLY183_100REL(1),
+		 SIPTAG_SUPPORTED_STR("100rel, precondition, timer"),
+		 TAG_END());
+  run_b_until(ctx, nua_r_set_params, until_final_response);
+
+  TEST_1(a_call->nh = nua_handle(a->nua, a_call, SIPTAG_TO(b->to), TAG_END()));
+
+  INVITE(a, a_call, a_call->nh,
+	 TAG_IF(!ctx->proxy_tests, NUTAG_URL(b->contact->m_url)),
+	 SOATAG_USER_SDP_STR(a_call->sdp),
+	 SIPTAG_SUPPORTED_STR("100rel"),
+	 SIPTAG_REQUIRE_STR("precondition"),
+	 TAG_END());
+
+  run_ab_until(ctx, -1, until_ready, -1, accept_updated);
+
+  /* Client transitions:
+     INIT -(C1)-> CALLING: nua_invite(), nua_i_state
+     CALLING -(C2)-> PROCEEDING: nua_r_invite, nua_i_state
+     PROCEEDING -(C3+C4)-> READY: nua_r_invite, nua_i_state
+  */
+  TEST_1(e = a->events->head); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_calling); /* CALLING */
+  TEST_1(is_offer_sent(e->data->e_tags));
+
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_r_invite);
+  TEST(e->data->e_status, 183);
+
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_proceeding);
+  TEST_1(is_answer_recv(e->data->e_tags));
+  /* Offer is sent in PRACK */
+  TEST_1(is_offer_sent(e->data->e_tags));
+
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_r_prack);
+  TEST(e->data->e_status, 200);
+
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_proceeding);
+  TEST_1(is_answer_recv(e->data->e_tags));
+  TEST_1(!is_offer_sent(e->data->e_tags));
+
+  /* Send UPDATE */
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_proceeding);
+  TEST_1(!is_answer_recv(e->data->e_tags));
+  TEST_1(is_offer_sent(e->data->e_tags));
+
+  /* The final response to the UPDATE and INVITE can be received in any order */
+  eu = event_by_type(e->next, nua_r_update);
+  ei = event_by_type(e->next, nua_r_invite);
+
+  TEST_1(e = eu); TEST_E(e->data->e_event, nua_r_update);
+  TEST(e->data->e_status, 200);
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  ustate = callstate(e->data->e_tags);
+  TEST_1(is_answer_recv(e->data->e_tags));
+  TEST_1(!is_offer_sent(e->data->e_tags));
+
+  TEST_1(e = ei); TEST_E(e->data->e_event, nua_r_invite);
+  TEST(e->data->e_status, 180);
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_proceeding); /* PROCEEDING */
+  TEST_1(!is_offer_answer_done(e->data->e_tags));
+
+  /* Final response to INVITE  */
+  TEST_1(ei = event_by_type(ei->next, nua_r_invite));
+
+  TEST_E(ei->data->e_event, nua_r_invite); TEST(ei->data->e_status, 200);
+  TEST_1(e = ei->next); TEST_E(e->data->e_event, nua_i_state);
+  istate = callstate(e->data->e_tags);
+  TEST_1(!is_offer_answer_done(e->data->e_tags));
+
+  if (eu == e->next) {
+    /* 200 OK to UPDATE is received after 200 OK to INVITE */
+    TEST(ustate, nua_callstate_ready);
+    TEST(istate, nua_callstate_completing);
+  }
+  else {
+    /* 200 OK to UPDATE is received before 200 OK to INVITE */
+    TEST(ustate, nua_callstate_proceeding);
+    TEST(istate, nua_callstate_ready);
+  }
+
+  free_events_in_list(ctx, a->events);
+
+  /*
+   Server transitions:
+   INIT -(S1)-> RECEIVED: nua_i_invite, nua_i_state
+   RECEIVED -(S2a)-> EARLY: nua_respond(), nua_i_state
+   EARLY -(S3b)-> COMPLETED: nua_respond(), nua_i_state
+   COMPLETED -(S4)-> READY: nua_i_ack, nua_i_state
+  */
+  TEST_1(e = b->events->head); TEST_E(e->data->e_event, nua_i_invite);
+  TEST(e->data->e_status, 100);
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_received); /* RECEIVED */
+
+  /* Responded with 183 Session Progress */
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_early); /* EARLY */
+  TEST_1(is_answer_sent(e->data->e_tags));
+
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_prack);
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_early); /* EARLY */
+  TEST_1(is_offer_recv(e->data->e_tags));
+  TEST_1(is_answer_sent(e->data->e_tags));
+
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_update);
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_early); /* EARLY */
+  TEST_1(is_offer_recv(e->data->e_tags));
+  TEST_1(is_answer_sent(e->data->e_tags));
+
+  /* Responded with 180 Ringing */
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_early); /* EARLY */
+  TEST_1(!is_offer_answer_done(e->data->e_tags));
+
+  /* Responded with 200 OK */
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_completed); /* COMPLETED */
+  TEST_1(!is_offer_answer_done(e->data->e_tags));
+
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_ack);
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_ready); /* READY */
+  TEST_1(!is_offer_answer_done(e->data->e_tags));
+  TEST_1(!e->next);
+  free_events_in_list(ctx, b->events);
+
+  if (print_headings)
+    printf("TEST NUA-10.4.1: PASSED\n");
+
+  if (print_headings)
+    printf("TEST NUA-10.4.2: terminate call\n");
+
+  BYE(b, b_call, b_call->nh, TAG_END());
+  run_ab_until(ctx, -1, until_terminated, -1, until_terminated);
+
+  /* B transitions:
+   READY --(T2)--> TERMINATING: nua_bye()
+   TERMINATING --(T3)--> TERMINATED: nua_r_bye, nua_i_state
+  */
+  TEST_1(e = b->events->head); TEST_E(e->data->e_event, nua_r_bye);
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_terminated); /* TERMINATED */
+  TEST_1(!e->next);
+  free_events_in_list(ctx, b->events);
+
+  /* A: READY -(T1)-> TERMINATED: nua_i_bye, nua_i_state */
+  TEST_1(e = a->events->head); TEST_E(e->data->e_event, nua_i_bye);
+  TEST(e->data->e_status, 200);
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_terminated); /* TERMINATED */
+  TEST_1(!e->next);
+  free_events_in_list(ctx, a->events);
+
+  nua_handle_destroy(a_call->nh), a_call->nh = NULL;
+  nua_handle_destroy(b_call->nh), b_call->nh = NULL;
+
+  if (print_headings)
+    printf("TEST NUA-10.4.2: PASSED\n");
+
+  END();
+}
+
+/*
+ X  ringing_updated2  ep
+ |-------INVITE------>|
+ |       (sdp)        |
+ |<----100 Trying-----|
+ |                    |
+ |<-------183---------|
+ |       (sdp)        |
+ |-------PRACK------->|
+ |       (sdp)        |
+ |<-------200---------|
+ |       (sdp)        |
+ |                    |
+ |-------UPDATE------>|
+ |       (sdp)        |
+ |<-------200---------|
+ |       (sdp)        |
+ |                    |
+ |<------UPDATE-------|
+ |       (sdp)        |
+ |--------200-------->|
+ |       (sdp)        |
+ |                    |
+<using  acccept_pracked>
+ |                    |
+ |<-------180---------|
+ |-------PRACK------->|
+ |<-------200---------|
+ |                    |
+ |<------200 OK-------|
+ |--------ACK-------->|
+ |                    |
+*/
+int ringing_updated2(CONDITION_PARAMS)
+{
+  if (!(check_handle(ep, call, nh, SIP_500_INTERNAL_SERVER_ERROR)))
+    return 0;
+
+  save_event_in_list(ctx, event, ep, call);
+
+  switch (event) {
+  case nua_i_update:
+    if (200 <= status && status < 300) {
+      UPDATE(ep, call, nh, TAG_END());
+    }
+    return 0;
+  case nua_r_update:
+    if (200 <= status && status < 300) {
+      RESPOND(ep, call, nh, SIP_180_RINGING, 
+	      SIPTAG_REQUIRE_STR("100rel"),
+	      TAG_END());
+      ep->next_condition = accept_pracked;
+    }
+    else if (300 <= status) {
+      RESPOND(ep, call, nh, SIP_500_INTERNAL_SERVER_ERROR, TAG_END());
+    }
+    return 0;
+  default:
+    break;
+  }
+
+  switch (callstate(tags)) {
+  case nua_callstate_received:
+    RESPOND(ep, call, nh, SIP_183_SESSION_PROGRESS,
+	    SIPTAG_REQUIRE_STR("100rel"),
+	    TAG_IF(call->sdp, SOATAG_USER_SDP_STR(call->sdp)),
+	    TAG_END());
+    return 0;
+  case nua_callstate_early:
+    return 0;
+  case nua_callstate_ready:
+    return 1;
+  case nua_callstate_terminated:
+    if (call)
+      nua_handle_destroy(call->nh), call->nh = NULL;
+    return 1;
+  default:
+    return 0;
+  }
+}
+
+int test_update_by_uas(struct context *ctx)
+{
+  BEGIN();
+
+  struct endpoint *a = &ctx->a,  *b = &ctx->b;
+  struct call *a_call = a->call, *b_call = b->call;
+  struct event *e, *ep, *ei;
+  sip_t *sip;
+
+  /* -------------------------------------------------------------------- */
+
+  if (print_headings)
+    printf("TEST NUA-10.5.1: Call with dual UPDATE\n");
+
+/* Test for update by UAS.
+
+   A			B
+   |-------INVITE------>|
+   |<----100 Trying-----|
+   |			|
+   |<-------183---------|
+   |-------PRACK------->|
+   |<-------200---------|
+   |			|
+   |-------UPDATE------>|
+ +------------------------+
+ | |<-------200---------| |
+ | |			| |
+ | |<------UPDATE-------| |
+ +------------------------+
+   |--------200-------->|
+   |			|
+   |<-------180---------|
+   |-------PRACK------->|
+   |<-------200---------|
+   |			|
+   |<------200 OK-------|
+   |--------ACK-------->|
+   |			|
+   |<-------BYE---------|
+   |-------200 OK-------|
+   |			|
+
+ Note that the 200 OK to UPDATE from A and UPDATE from B may be re-ordered
+ In that case, A will respond with 500/Retry-After and B will retry UPDATE.
+ See do {} while () loop below.
+*/
+
+  a_call->sdp = "m=audio 5008 RTP/AVP 8";
+  b_call->sdp = "m=audio 5010 RTP/AVP 0 8";
+
+  nua_set_params(ctx->a.nua,
+		 NUTAG_EARLY_MEDIA(1),
+		 SIPTAG_SUPPORTED_STR("100rel, precondition"),
+		 TAG_END());
+  run_a_until(ctx, nua_r_set_params, until_final_response);
+
+  nua_set_params(ctx->b.nua,
+		 NUTAG_EARLY_MEDIA(0),
+		 SIPTAG_SUPPORTED_STR("100rel, precondition, timer"),
+		 TAG_END());
+  run_b_until(ctx, nua_r_set_params, until_final_response);
+
+  TEST_1(a_call->nh = nua_handle(a->nua, a_call, SIPTAG_TO(b->to), TAG_END()));
+
+  INVITE(a, a_call, a_call->nh,
+	 TAG_IF(!ctx->proxy_tests, NUTAG_URL(b->contact->m_url)),
+	 SOATAG_USER_SDP_STR(a_call->sdp),
+	 SIPTAG_SUPPORTED_STR("100rel"),
+	 SIPTAG_REQUIRE_STR("precondition"),
+	 TAG_END());
+
+  run_ab_until(ctx, -1, until_ready, -1, ringing_updated2);
+
+  /* Client transitions:
+     INIT -(C1)-> CALLING: nua_invite(), nua_i_state
+     CALLING -(C2)-> PROCEEDING: nua_r_invite, nua_i_state
+     PROCEEDING: nua_r_prack, nua_i_state
+     PROCEEDING: nua_r_update, nua_i_state
+     PROCEEDING: nua_i_update, nua_i_state
+     PROCEEDING -(C3+C4)-> READY: nua_r_invite, nua_i_state
+  */
+  TEST_1(e = a->events->head); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_calling); /* CALLING */
+  TEST_1(is_offer_sent(e->data->e_tags));
+
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_r_invite);
+  TEST(e->data->e_status, 183);
+
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_proceeding);
+  TEST_1(is_answer_recv(e->data->e_tags));
+  TEST_1(is_offer_sent(e->data->e_tags));
+
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_r_prack);
+  TEST(e->data->e_status, 200);
+
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_proceeding);
+  TEST_1(is_answer_recv(e->data->e_tags));
+  TEST_1(!is_offer_sent(e->data->e_tags));
+
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_proceeding);
+  TEST_1(!is_answer_recv(e->data->e_tags));
+  TEST_1(is_offer_sent(e->data->e_tags));
+
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_r_update);
+  TEST(e->data->e_status, 200);
+
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_proceeding);
+  TEST_1(is_answer_recv(e->data->e_tags));
+  TEST_1(!is_offer_sent(e->data->e_tags));
+
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_update);
+  TEST(e->data->e_status, 200);
+
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_proceeding);
+  TEST_1(is_offer_recv(e->data->e_tags));
+  TEST_1(is_answer_sent(e->data->e_tags));
+
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_r_invite);
+  TEST(e->data->e_status, 180);
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_proceeding); /* PROCEEDING */
+  TEST_1(!is_offer_answer_done(e->data->e_tags));
+
+  ei = event_by_type(e->next, nua_r_invite);
+  ep = event_by_type(e->next, nua_r_prack);
+  if (!ep) {
+    run_a_until(ctx, -1, save_until_final_response);
+    ep = event_by_type(e->next, nua_r_prack);
+  }
+
+  TEST_1(e = ep); TEST_E(e->data->e_event, nua_r_prack);
+  TEST(e->data->e_status, 200);
+  /* Does not have effect on call state */
+
+  TEST_1(e = ei); TEST_E(e->data->e_event, nua_r_invite);
+  TEST(e->data->e_status, 200);
+  if (ctx->proxy_tests) {
+    TEST_1(sip = sip_object(e->data->e_msg));
+    TEST_1(sip->sip_session_expires);
+    TEST_S(sip->sip_session_expires->x_refresher, "uas");
+    TEST_1(!sip_has_supported(sip->sip_require, "timer"));
+  }
+
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_ready); /* READY */
+  TEST_1(!is_offer_answer_done(e->data->e_tags));
+  TEST_1(!e->next || !ep->next);
+  free_events_in_list(ctx, a->events);
+
+  /*
+   Server transitions:
+   INIT -(S1)-> RECEIVED: nua_i_invite, nua_i_state
+   RECEIVED -(S2a)-> EARLY: nua_respond(183), nua_i_state
+   EARLY --> EARLY: nua_i_prack, nua_i_state
+   EARLY --> EARLY: nua_i_update, nua_i_state
+   EARLY --> EARLY: nua_update(), nua_i_state
+   EARLY --> EARLY: nua_r_update, nua_i_state
+   EARLY --> EARLY: nua_respond(180), nua_i_state
+   EARLY --> EARLY: nua_i_prack, nua_i_state
+   EARLY -(S3b)-> COMPLETED: nua_respond(200), nua_i_state
+   COMPLETED -(S4)-> READY: nua_i_ack, nua_i_state
+  */
+  TEST_1(e = b->events->head); TEST_E(e->data->e_event, nua_i_invite);
+  TEST(e->data->e_status, 100);
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_received); /* RECEIVED */
+
+  /* Responded with 183 Session Progress */
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(e->data->e_status, 183);
+  TEST(callstate(e->data->e_tags), nua_callstate_early); /* EARLY */
+  TEST_1(is_answer_sent(e->data->e_tags));
+
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_prack);
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_early); /* EARLY */
+  TEST_1(is_offer_recv(e->data->e_tags));
+  TEST_1(is_answer_sent(e->data->e_tags));
+
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_update);
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_early); /* EARLY */
+  TEST_1(is_offer_recv(e->data->e_tags));
+  TEST_1(is_answer_sent(e->data->e_tags));
+
+  /* sent UPDATE */
+  do {
+    TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+    TEST(callstate(e->data->e_tags), nua_callstate_early); /* EARLY */
+    TEST_1(is_offer_sent(e->data->e_tags)); 
+    TEST_1(!is_offer_recv(e->data->e_tags));
+    TEST_1(!is_answer_sent(e->data->e_tags));
+    TEST_1(!is_answer_recv(e->data->e_tags));
+
+    TEST_1(e = e->next); TEST_E(e->data->e_event, nua_r_update);
+    if (e->data->e_status == 100) {
+      TEST_1(sip = sip_object(e->data->e_msg));
+      TEST(sip->sip_status->st_status, 500); TEST_1(sip->sip_retry_after);
+    }
+  } while (e->data->e_status == 100);
+  TEST(e->data->e_status, 200);
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_early); /* EARLY */
+  TEST_1(!is_offer_sent(e->data->e_tags)); /* XXX */
+  TEST_1(!is_offer_recv(e->data->e_tags));
+  TEST_1(!is_answer_sent(e->data->e_tags));
+  TEST_1(is_answer_recv(e->data->e_tags));
+
+  /* Responded with 180 Ringing */
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(e->data->e_status, 180);
+  TEST(callstate(e->data->e_tags), nua_callstate_early); /* EARLY */
+  TEST_1(!is_offer_answer_done(e->data->e_tags));
+
+  /* 180 PRACKed */
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_prack);
+  /* Does not have effect on call state */
+
+  /* Responded with 200 OK */
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(e->data->e_status, 200);
+  TEST(callstate(e->data->e_tags), nua_callstate_completed); /* COMPLETED */
+  TEST_1(!is_offer_answer_done(e->data->e_tags));
+
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_ack);
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_ready); /* READY */
+  TEST_1(!is_offer_answer_done(e->data->e_tags));
+  TEST_1(!e->next);
+  free_events_in_list(ctx, b->events);
+
+  if (print_headings)
+    printf("TEST NUA-10.5.1: PASSED\n");
+
+  if (print_headings)
+    printf("TEST NUA-10.5.2: terminate call\n");
+
+  BYE(b, b_call, b_call->nh, TAG_END());
+  run_ab_until(ctx, -1, until_terminated, -1, until_terminated);
+
+  /* B transitions:
+   READY --(T2)--> TERMINATING: nua_bye()
+   TERMINATING --(T3)--> TERMINATED: nua_r_bye, nua_i_state
+  */
+  TEST_1(e = b->events->head); TEST_E(e->data->e_event, nua_r_bye);
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_terminated); /* TERMINATED */
+  TEST_1(!e->next);
+  free_events_in_list(ctx, b->events);
+
+  /* A: READY -(T1)-> TERMINATED: nua_i_bye, nua_i_state */
+  TEST_1(e = a->events->head); TEST_E(e->data->e_event, nua_i_bye);
+  TEST(e->data->e_status, 200);
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_terminated); /* TERMINATED */
+  TEST_1(!e->next);
+  free_events_in_list(ctx, a->events);
+
+  nua_handle_destroy(a_call->nh), a_call->nh = NULL;
+  nua_handle_destroy(b_call->nh), b_call->nh = NULL;
+
+  if (print_headings)
+    printf("TEST NUA-10.5.2: PASSED\n");
+
+  END();
+}
+
+int test_update_failure(struct context *ctx)
+{
+  BEGIN();
+#if 0
+  struct endpoint *a = &ctx->a,  *b = &ctx->b;
+  struct call *a_call = a->call, *b_call = b->call;
+  struct event *e, *eu, *ei;
+  enum nua_callstate ustate, istate;
+
+  if (print_headings)
+    printf("TEST NUA-10.4.1: UPDATE failure terminating session\n");
+
+/* Test UPDATE failing:
+
+   A			B
+   |-------INVITE------>|
+   |<----100 Trying-----|
+   |			|
+   |<-------183---------|
+   |-------PRACK------->|
+   |<-------200---------|
+   |			|
+   |<-------180---------|
+   |			|
+ +------------------------+
+   |<------200 OK-------|
+   |-------UPDATE------>|
+   |			|
+   |<-------481---------|
+   |			|
+   |--------ACK-------->|
+   |			|
+   |<-------BYE---------|
+   |-------200 OK-------|
+   |			|
+
+*/
+
+  a_call->sdp = "m=audio 5008 RTP/AVP 8";
+  b_call->sdp = "m=audio 5010 RTP/AVP 0 8";
+
+  nua_set_params(ctx->a.nua,
+		 NUTAG_EARLY_MEDIA(1),
+		 SIPTAG_SUPPORTED_STR("100rel, precondition, timer"),
+		 TAG_END());
+  run_a_until(ctx, nua_r_set_params, until_final_response);
+
+  nua_set_params(ctx->b.nua,
+		 NUTAG_EARLY_MEDIA(1),
+		 NUTAG_ONLY183_100REL(1),
+		 SIPTAG_SUPPORTED_STR("100rel, precondition, timer"),
+		 NUTAG_APPL_METHOD("UPDATE"),
+		 TAG_END());
+  run_b_until(ctx, nua_r_set_params, until_final_response);
+
+  TEST_1(a_call->nh = nua_handle(a->nua, a_call, SIPTAG_TO(b->to), TAG_END()));
+
+  INVITE(a, a_call, a_call->nh,
+	 TAG_IF(!ctx->proxy_tests, NUTAG_URL(b->contact->m_url)),
+	 SOATAG_USER_SDP_STR(a_call->sdp),
+	 SIPTAG_SUPPORTED_STR("100rel"),
+	 TAG_END());
+
+  run_ab_until(ctx, -1, save_until_final_response, -1, until_pracked);
+
+  /* Client transitions:
+     INIT -(C1)-> CALLING: nua_invite(), nua_i_state
+     CALLING -(C2)-> PROCEEDING: nua_r_invite, nua_i_state
+     PROCEEDING -(C3+C4)-> READY: nua_r_invite, nua_i_state
+  */
+  TEST_1(e = a->events->head); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_calling); /* CALLING */
+  TEST_1(is_offer_sent(e->data->e_tags));
+
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_r_invite);
+  TEST(e->data->e_status, 183);
+
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_proceeding);
+  TEST_1(is_answer_recv(e->data->e_tags));
+  /* Offer is sent in PRACK */
+  TEST_1(is_offer_sent(e->data->e_tags));
+
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_r_prack);
+  TEST(e->data->e_status, 200);
+
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_proceeding);
+  TEST_1(is_answer_recv(e->data->e_tags));
+  TEST_1(!is_offer_sent(e->data->e_tags));
+
+  /* Send UPDATE */
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_proceeding);
+  TEST_1(!is_answer_recv(e->data->e_tags));
+  TEST_1(is_offer_sent(e->data->e_tags));
+
+  /* The final response to the UPDATE and INVITE can be received in any order */
+  eu = event_by_type(e->next, nua_r_update);
+  ei = event_by_type(e->next, nua_r_invite);
+
+  TEST_1(e = eu); TEST_E(e->data->e_event, nua_r_update);
+  TEST(e->data->e_status, 200);
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  ustate = callstate(e->data->e_tags);
+  TEST_1(is_answer_recv(e->data->e_tags));
+  TEST_1(!is_offer_sent(e->data->e_tags));
+
+  TEST_1(e = ei); TEST_E(e->data->e_event, nua_r_invite);
+  TEST(e->data->e_status, 180);
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_proceeding); /* PROCEEDING */
+  TEST_1(!is_offer_answer_done(e->data->e_tags));
+
+  /* Final response to INVITE  */
+  TEST_1(ei = event_by_type(ei->next, nua_r_invite));
+
+  TEST_E(ei->data->e_event, nua_r_invite); TEST(ei->data->e_status, 200);
+  TEST_1(e = ei->next); TEST_E(e->data->e_event, nua_i_state);
+  istate = callstate(e->data->e_tags);
+  TEST_1(!is_offer_answer_done(e->data->e_tags));
+
+  if (eu == e->next) {
+    /* 200 OK to UPDATE is received after 200 OK to INVITE */
+    TEST(ustate, nua_callstate_ready);
+    TEST(istate, nua_callstate_completing);
+  }
+  else {
+    /* 200 OK to UPDATE is received before 200 OK to INVITE */
+    TEST(ustate, nua_callstate_proceeding);
+    TEST(istate, nua_callstate_ready);
+  }
+
+  free_events_in_list(ctx, a->events);
+
+  /*
+   Server transitions:
+   INIT -(S1)-> RECEIVED: nua_i_invite, nua_i_state
+   RECEIVED -(S2a)-> EARLY: nua_respond(), nua_i_state
+   EARLY -(S3b)-> COMPLETED: nua_respond(), nua_i_state
+   COMPLETED -(S4)-> READY: nua_i_ack, nua_i_state
+  */
+  TEST_1(e = b->events->head); TEST_E(e->data->e_event, nua_i_invite);
+  TEST(e->data->e_status, 100);
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_received); /* RECEIVED */
+
+  /* Responded with 183 Session Progress */
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_early); /* EARLY */
+  TEST_1(is_answer_sent(e->data->e_tags));
+
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_prack);
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_early); /* EARLY */
+  TEST_1(is_offer_recv(e->data->e_tags));
+  TEST_1(is_answer_sent(e->data->e_tags));
+
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_update);
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_early); /* EARLY */
+  TEST_1(is_offer_recv(e->data->e_tags));
+  TEST_1(is_answer_sent(e->data->e_tags));
+
+  /* Responded with 180 Ringing */
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_early); /* EARLY */
+  TEST_1(!is_offer_answer_done(e->data->e_tags));
+
+  /* Responded with 200 OK */
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_completed); /* COMPLETED */
+  TEST_1(!is_offer_answer_done(e->data->e_tags));
+
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_ack);
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_ready); /* READY */
+  TEST_1(!is_offer_answer_done(e->data->e_tags));
+  TEST_1(!e->next);
+  free_events_in_list(ctx, b->events);
+
+  if (print_headings)
+    printf("TEST NUA-10.4.1: PASSED\n");
+
+  if (print_headings)
+    printf("TEST NUA-10.4.2: terminate call\n");
+
+  BYE(b, b_call, b_call->nh, TAG_END());
+  run_ab_until(ctx, -1, until_terminated, -1, until_terminated);
+
+  /* B transitions:
+   READY --(T2)--> TERMINATING: nua_bye()
+   TERMINATING --(T3)--> TERMINATED: nua_r_bye, nua_i_state
+  */
+  TEST_1(e = b->events->head); TEST_E(e->data->e_event, nua_r_bye);
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_terminated); /* TERMINATED */
+  TEST_1(!e->next);
+  free_events_in_list(ctx, b->events);
+
+  /* A: READY -(T1)-> TERMINATED: nua_i_bye, nua_i_state */
+  TEST_1(e = a->events->head); TEST_E(e->data->e_event, nua_i_bye);
+  TEST(e->data->e_status, 200);
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_terminated); /* TERMINATED */
+  TEST_1(!e->next);
+  free_events_in_list(ctx, a->events);
+
+  nua_handle_destroy(a_call->nh), a_call->nh = NULL;
+  nua_handle_destroy(b_call->nh), b_call->nh = NULL;
+
+  if (print_headings)
+    printf("TEST NUA-10.4.2: PASSED\n");
+#endif
+  END();
+}
+ 
+int cancel_when_pracked(CONDITION_PARAMS);
+int alert_call(CONDITION_PARAMS);
+
+int test_180rel_cancel1(struct context *ctx)
+{
+  BEGIN();
+
+  struct endpoint *a = &ctx->a,  *b = &ctx->b;
+  struct call *a_call = a->call, *b_call = b->call;
+  struct event *e;
+
+  if (print_headings)
+    printf("TEST NUA-10.6: CANCEL after PRACK\n");
+
+/* Test for 100rel:
+
+   A			B
+   |-------INVITE------>|
+   |<----100 Trying-----|
+   |			|
+   |<-------180---------|
+   |-------PRACK------->|
+   |<-------200---------|
+   |			|
+   |------CANCEL------->|
+   |<------200 OK-------|
+   |			|
+   |<-------487---------|
+   |--------ACK-------->|
+   |			|
+
+*/
+
+  a_call->sdp = "m=audio 5008 RTP/AVP 8";
+  b_call->sdp = "m=audio 5010 RTP/AVP 0 8";
+
+  nua_set_params(ctx->a.nua,
+		 NUTAG_EARLY_MEDIA(1),
+		 NUTAG_ONLY183_100REL(0),
+		 TAG_END());
+  run_a_until(ctx, nua_r_set_params, until_final_response);
+
+  nua_set_params(ctx->b.nua,
+		 NUTAG_EARLY_MEDIA(1),
+		 NUTAG_ONLY183_100REL(0),
+		 TAG_END());
+  run_b_until(ctx, nua_r_set_params, until_final_response);
+
+  TEST_1(a_call->nh = nua_handle(a->nua, a_call, SIPTAG_TO(b->to), TAG_END()));
+
+  INVITE(a, a_call, a_call->nh,
+	 TAG_IF(!ctx->proxy_tests, NUTAG_URL(b->contact->m_url)),
+	 SOATAG_USER_SDP_STR(a_call->sdp),
+	 TAG_END());
+
+  run_ab_until(ctx, -1, cancel_when_pracked, -1, alert_call);
+
+  /* Client transitions:
+     INIT -(C1)-> CALLING: nua_invite(), nua_i_state
+     CALLING -(C2)-> PROCEEDING: nua_r_invite, nua_i_state, nua_r_prack
+     PROCEEDING -(C3+C4)-> TERMINATED: nua_r_invite, nua_i_state
+  */
+  TEST_1(e = a->events->head); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_calling); /* CALLING */
+  TEST_1(is_offer_sent(e->data->e_tags));
+
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_r_invite);
+  TEST(e->data->e_status, 180);
+
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_proceeding);
+  TEST_1(is_answer_recv(e->data->e_tags));
+  TEST_1(!is_offer_sent(e->data->e_tags));
+
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_r_prack);
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_r_cancel);
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_r_invite);
+  TEST(e->data->e_status, 487);
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_terminated);
+  TEST_1(!e->next);
+
+  free_events_in_list(ctx, a->events);
+
+  /*
+   Server transitions:
+   INIT -(S1)-> RECEIVED: nua_i_invite, nua_i_state
+   RECEIVED -(S2a)-> EARLY: nua_respond(), nua_i_state
+   Option A:
+   EARLY -(S10)-> TERMINATED: nua_i_cancel, nua_i_state
+   Option B:
+   EARLY -(S3b)-> COMPLETED: nua_respond(), nua_i_state
+   COMPLETED -(S4)-> READY: nua_i_ack, nua_i_state
+   READY -(T1)-> TERMINATED: nua_i_bye, nua_i_state   
+  */
+  TEST_1(e = b->events->head); TEST_E(e->data->e_event, nua_i_invite);
+  TEST(e->data->e_status, 100);
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_received); /* RECEIVED */
+  TEST_1(is_offer_recv(e->data->e_tags));
+
+  /* Responded with 180 Ringing */
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_early); /* EARLY */
+  TEST_1(is_answer_sent(e->data->e_tags));
+
+  /* 180 is PRACKed */
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_prack);
+  /* Does not have effect on call state */
+
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_cancel);
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_terminated); /* TERMINATED */
+
+  free_events_in_list(ctx, b->events);
+
+  nua_handle_destroy(a_call->nh), a_call->nh = NULL;
+  nua_handle_destroy(b_call->nh), b_call->nh = NULL;
+
+  if (print_headings)
+    printf("TEST NUA-10.6: PASSED\n");
+  
+  END();
+}
+
+int cancel_when_pracked(CONDITION_PARAMS)
+{
+  if (!(check_handle(ep, call, nh, SIP_500_INTERNAL_SERVER_ERROR)))
+    return 0;
+
+  save_event_in_list(ctx, event, ep, call);
+
+  if (event == nua_r_prack)
+    CANCEL(ep, call, nh, TAG_END());
+
+  switch (callstate(tags)) {
+  case nua_callstate_proceeding:
+    return 0;
+  case nua_callstate_ready:
+    return 1;
+  case nua_callstate_terminated:
+    return 1;
+  default:
+    return 0;
+  }
+}
+
+int test_180rel_cancel2(struct context *ctx)
+{
+  BEGIN();
+
+  struct endpoint *a = &ctx->a,  *b = &ctx->b;
+  struct call *a_call = a->call, *b_call = b->call;
+  struct event *e, *ep, *ec;
+
+  if (print_headings)
+    printf("TEST NUA-10.7: CANCEL after 100rel 180\n");
+
+/* Test for 100rel:
+
+   A			B
+   |-------INVITE------>|
+   |<----100 Trying-----|
+   |			|
+   |<-------180---------|
+   |-------PRACK------->|
+   |<-------200---------|
+   |			|
+   |------CANCEL------->|
+   |<------200 OK-------|
+   |			|
+   |<-------487---------|
+   |--------ACK-------->|
+   |			|
+
+*/
+
+  a_call->sdp = "m=audio 5008 RTP/AVP 8";
+  b_call->sdp = "m=audio 5010 RTP/AVP 0 8";
+
+  nua_set_params(ctx->a.nua,
+		 NUTAG_EARLY_MEDIA(1),
+		 NUTAG_ONLY183_100REL(0),
+		 TAG_END());
+  run_a_until(ctx, nua_r_set_params, until_final_response);
+
+  nua_set_params(ctx->b.nua,
+		 NUTAG_EARLY_MEDIA(1),
+		 NUTAG_ONLY183_100REL(0),
+		 TAG_END());
+  run_b_until(ctx, nua_r_set_params, until_final_response);
+
+  TEST_1(a_call->nh = nua_handle(a->nua, a_call, SIPTAG_TO(b->to), TAG_END()));
+
+  INVITE(a, a_call, a_call->nh,
+	 TAG_IF(!ctx->proxy_tests, NUTAG_URL(b->contact->m_url)),
+	 SOATAG_USER_SDP_STR(a_call->sdp),
+	 TAG_END());
+
+  run_ab_until(ctx, -1, cancel_when_ringing, -1, accept_pracked2);
+
+  /* Client transitions:
+     INIT -(C1)-> CALLING: nua_invite(), nua_i_state
+     CALLING -(C2)-> PROCEEDING: nua_r_invite, nua_i_state, nua_r_prack
+     PROCEEDING -(C3+C4)-> TERMINATED: nua_r_invite, nua_i_state
+  */
+  TEST_1(e = a->events->head); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_calling); /* CALLING */
+  TEST_1(is_offer_sent(e->data->e_tags));
+
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_r_invite);
+  TEST(e->data->e_status, 180);
+
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_proceeding);
+  TEST_1(is_answer_recv(e->data->e_tags));
+  TEST_1(!is_offer_sent(e->data->e_tags));
+
+#define NEXT_SKIP(x) \
+  do { TEST_1(e = e->next); } \
+  while (x);
+
+  NEXT_SKIP(e->data->e_event == nua_r_prack ||
+	    e->data->e_event == nua_r_cancel ||
+	    e->data->e_event == nua_i_state);
+
+  TEST_E(e->data->e_event, nua_r_invite);
+  if (e->data->e_status == 487) {
+    TEST(e->data->e_status, 487);
+    TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+    TEST(callstate(e->data->e_tags), nua_callstate_terminated);
+    if (e->next)
+      NEXT_SKIP(e->data->e_event == nua_r_prack || e->data->e_event == nua_r_cancel);
+    TEST_1(!e->next);
+  }
+  else {
+    TEST(e->data->e_status, 200);
+    TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+    TEST(callstate(e->data->e_tags), nua_callstate_ready);
+
+    BYE(a, a_call, a_call->nh, TAG_END());
+    run_ab_until(ctx, -1, until_terminated, -1, until_terminated);
+
+    NEXT_SKIP(e->data->e_event == nua_r_prack || e->data->e_event == nua_r_cancel);
+    TEST_E(e->data->e_event, nua_r_bye);
+    TEST(e->data->e_status, 200);
+    TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+    TEST(callstate(e->data->e_tags), nua_callstate_terminated); /* TERMINATED */
+    TEST_1(!e->next);
+  }
+
+  free_events_in_list(ctx, a->events);
+
+  /*
+   Server transitions:
+   INIT -(S1)-> RECEIVED: nua_i_invite, nua_i_state
+   RECEIVED -(S2a)-> EARLY: nua_respond(), nua_i_state
+   Option A:
+   EARLY -(S10)-> TERMINATED: nua_i_cancel, nua_i_state
+   Option B:
+   EARLY -(S3b)-> COMPLETED: nua_respond(), nua_i_state
+   COMPLETED -(S4)-> READY: nua_i_ack, nua_i_state
+   READY -(T1)-> TERMINATED: nua_i_bye, nua_i_state   
+  */
+  TEST_1(e = b->events->head); TEST_E(e->data->e_event, nua_i_invite);
+  TEST(e->data->e_status, 100);
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_received); /* RECEIVED */
+  TEST_1(is_offer_recv(e->data->e_tags));
+
+  /* Responded with 180 Ringing */
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_early); /* EARLY */
+  TEST_1(is_answer_sent(e->data->e_tags));
+
+  ec = event_by_type(e->next, nua_i_cancel);
+
+  if (ec) {
+    TEST_1(e = ec); TEST_E(e->data->e_event, nua_i_cancel);
+    TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+    TEST(callstate(e->data->e_tags), nua_callstate_terminated); /* TERMINATED */
+  }
+  else {
+    /* 180 is PRACKed, PRACK does not have effect on call state */
+    ep = event_by_type(e->next, nua_i_prack);
+    if (ep) e = ep;
+    /* Responded with 200 OK */
+    TEST_1(e = event_by_type(e->next, nua_i_state));
+    TEST_E(e->data->e_event, nua_i_state);
+    TEST(callstate(e->data->e_tags), nua_callstate_completed); /* COMPLETED */
+    TEST_1(!is_offer_answer_done(e->data->e_tags));
+    TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_ack);
+    TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+    TEST(callstate(e->data->e_tags), nua_callstate_ready); /* READY */
+    TEST_1(!is_offer_answer_done(e->data->e_tags));
+    TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_bye);
+    TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+    TEST(callstate(e->data->e_tags), nua_callstate_terminated); /* TERMINATED */
+  }
+
+  free_events_in_list(ctx, b->events);
+
+  nua_handle_destroy(a_call->nh), a_call->nh = NULL;
+  nua_handle_destroy(b_call->nh), b_call->nh = NULL;
+
+  if (print_headings)
+    printf("TEST NUA-10.7: PASSED\n");
+  
+  END();
+}
+
+int redirect_pracked(CONDITION_PARAMS);
+
+int test_180rel_redirected(struct context *ctx)
+{
+  BEGIN();
+
+  struct endpoint *a = &ctx->a,  *b = &ctx->b;
+  struct call *a_call = a->call, *b_call = b->call;
+  struct event *e, *ep, *ei;
+  sip_t *sip;
+
+  if (print_headings)
+    printf("TEST NUA-10.8.1: Call with 100rel and 180\n");
+
+/* Test for 100rel:
+
+   A			B
+   |-------INVITE------>|
+   |<----100 Trying-----|
+   |			|
+   |<-------180---------|
+   |-------PRACK------->|
+   |<-------200---------|
+   |			|
+   |<----302 Moved------|
+   |--------ACK-------->|
+   |			|
+   |-------INVITE------>|
+   |<----100 Trying-----|
+   |			|
+   |<-------180---------|
+   |-------PRACK------->|
+   |<-------200---------|
+   |			|
+   |<------200 OK-------|
+   |--------ACK-------->|
+   |			|
+   |<-------BYE---------|
+   |-------200 OK-------|
+   |			|
+
+*/
+
+  a_call->sdp = "m=audio 5008 RTP/AVP 8";
+  b_call->sdp = "m=audio 5010 RTP/AVP 0 8";
+
+  nua_set_params(ctx->a.nua,
+		 NUTAG_EARLY_MEDIA(1),
+		 TAG_END());
+  run_a_until(ctx, nua_r_set_params, until_final_response);
+
+  nua_set_params(ctx->b.nua,
+		 NUTAG_EARLY_MEDIA(1),
+		 NUTAG_SESSION_TIMER(180),
+		 TAG_END());
+  run_b_until(ctx, nua_r_set_params, until_final_response);
+
+  TEST_1(a_call->nh = nua_handle(a->nua, a_call, SIPTAG_TO(b->to), TAG_END()));
+
+  INVITE(a, a_call, a_call->nh,
+	 TAG_IF(!ctx->proxy_tests, NUTAG_URL(b->contact->m_url)),
+	 SOATAG_USER_SDP_STR(a_call->sdp),
+	 TAG_END());
+
+  run_ab_until(ctx, -1, until_ready, -1, redirect_pracked);
+
+  /* Client transitions:
+     INIT -(C1)-> CALLING: nua_invite(), nua_i_state
+     CALLING -(C2)-> PROCEEDING: nua_r_invite, nua_i_state, nua_r_prack
+     PROCEEDING->(redirected)->CALLING: nua_r_invite, nua_i_state
+     CALLING -(C2)-> PROCEEDING: nua_r_invite, nua_i_state, nua_r_prack
+     PROCEEDING -(C3+C4)-> READY: nua_r_invite, nua_i_state
+  */
+  TEST_1(e = a->events->head); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_calling); /* CALLING */
+  TEST_1(is_offer_sent(e->data->e_tags));
+
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_r_invite);
+  TEST(e->data->e_status, 180);
+
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_proceeding);
+  TEST_1(is_answer_recv(e->data->e_tags));
+  TEST_1(!is_offer_sent(e->data->e_tags));
+
+  ei = event_by_type(e->next, nua_r_invite); /* 302 */
+  ep = event_by_type(e->next, nua_r_prack); /* 200 */
+  if (!ep) {
+    run_a_until(ctx, -1, save_until_final_response);
+    ep = event_by_type(e->next, nua_r_prack);
+  }
+
+  TEST_1(e = ep); TEST_E(e->data->e_event, nua_r_prack);
+  TEST(e->data->e_status, 200);
+
+  TEST_1(e = ei); TEST_E(e->data->e_event, nua_r_invite);
+  TEST(e->data->e_status, 100);
+  TEST_1(sip = sip_object(e->data->e_msg));
+  TEST(sip->sip_status->st_status, 302);
+
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_calling); /* CALLING */
+
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_r_invite);
+  TEST(e->data->e_status, 180);
+
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_proceeding);
+  TEST_1(is_answer_recv(e->data->e_tags));
+  TEST_1(!is_offer_sent(e->data->e_tags));
+
+  ei = event_by_type(e->next, nua_r_invite);
+  ep = event_by_type(e->next, nua_r_prack);
+  if (!ep) {
+    run_a_until(ctx, -1, save_until_final_response);
+    ep = event_by_type(e->next, nua_r_prack);
+  }
+
+  TEST_1(e = ep); TEST_E(e->data->e_event, nua_r_prack);
+  TEST(e->data->e_status, 200);
+
+  TEST_1(e = ei); TEST_E(e->data->e_event, nua_r_invite);
+  TEST(e->data->e_status, 200);
+
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_ready); /* READY */
+  TEST_1(!is_offer_answer_done(e->data->e_tags));
+
+  TEST_1(!e->next || !ep->next);
+  free_events_in_list(ctx, a->events);
+
+  /*
+   Server transitions:
+   INIT -(S1)-> RECEIVED: nua_i_invite, nua_i_state
+   RECEIVED -(S2a)-> EARLY: nua_respond(), nua_i_state
+   EARLY -(S3b)-> COMPLETED: nua_respond(), nua_i_state
+   COMPLETED -(S4)-> READY: nua_i_ack, nua_i_state
+  */
+  TEST_1(e = b->events->head); TEST_E(e->data->e_event, nua_i_invite);
+  TEST(e->data->e_status, 100);
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_received); /* RECEIVED */
+  TEST_1(is_offer_recv(e->data->e_tags));
+
+  /* Responded with 180 Ringing */
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_early); /* EARLY */
+  TEST_1(is_answer_sent(e->data->e_tags));
+
+  /* 180 is PRACKed */
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_prack);
+  /* 302 terminates call */
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_terminated); /* Terminated */
+
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_invite);
+  TEST(e->data->e_status, 100);
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_received); /* RECEIVED */
+  TEST_1(is_offer_recv(e->data->e_tags));
+
+  /* Responded with 180 Ringing */
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_early); /* EARLY */
+  TEST_1(is_answer_sent(e->data->e_tags));
+
+  /* 180 is PRACKed */
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_prack);
+  /* Does not have effect on call state */
+
+  /* Respond with 200 OK */
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_completed); /* COMPLETED */
+  TEST_1(!is_offer_answer_done(e->data->e_tags));
+
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_ack);
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_ready); /* READY */
+  TEST_1(!is_offer_answer_done(e->data->e_tags));
+  TEST_1(!e->next);
+  free_events_in_list(ctx, b->events);
+
+  if (print_headings)
+    printf("TEST NUA-10.8.1: PASSED\n");
+
+  if (print_headings)
+    printf("TEST NUA-10.8.2: terminate call\n");
+
+  BYE(b, b_call, b_call->nh, TAG_END());
+  run_ab_until(ctx, -1, until_terminated, -1, until_terminated);
+
+  /* B transitions:
+   READY --(T2)--> TERMINATING: nua_bye()
+   TERMINATING --(T3)--> TERMINATED: nua_r_bye, nua_i_state
+  */
+  TEST_1(e = b->events->head); TEST_E(e->data->e_event, nua_r_bye);
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_terminated); /* TERMINATED */
+  TEST_1(!e->next);
+  free_events_in_list(ctx, b->events);
+
+  /* A: READY -(T1)-> TERMINATED: nua_i_bye, nua_i_state */
+  TEST_1(e = a->events->head); TEST_E(e->data->e_event, nua_i_bye);
+  TEST(e->data->e_status, 200);
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_terminated); /* TERMINATED */
+  TEST_1(!e->next);
+  free_events_in_list(ctx, a->events);
+
+  nua_handle_destroy(a_call->nh), a_call->nh = NULL;
+  nua_handle_destroy(b_call->nh), b_call->nh = NULL;
+
+  if (print_headings)
+    printf("TEST NUA-10.8.2: PASSED\n");
+  
+  END();
+}
+
+int redirect_pracked(CONDITION_PARAMS)
+{
+  if (!(check_handle(ep, call, nh, SIP_500_INTERNAL_SERVER_ERROR)))
+    return 0;
+
+  save_event_in_list(ctx, event, ep, call);
+
+  if (event == nua_i_prack) {
+    if (!ep->flags.bit0) {
+      sip_contact_t m[1];
+      
+      ep->flags.bit0 = 1;
+
+      *m = *ep->contact;
+      m->m_url->url_user = "302";
+      RESPOND(ep, call, nh, SIP_302_MOVED_TEMPORARILY, SIPTAG_CONTACT(m), TAG_END());
+      return 0;
+    }
+    else {
+      RESPOND(ep, call, nh, SIP_200_OK,
+	      TAG_IF(call->sdp, SOATAG_USER_SDP_STR(call->sdp)),
+	      TAG_END());
+    }
+  }
+
+
+  switch (callstate(tags)) {
+  case nua_callstate_received:
+    RESPOND(ep, call, nh, SIP_180_RINGING,
+	    TAG_IF(call->sdp, SOATAG_USER_SDP_STR(call->sdp)),
+	    TAG_END());
+    return 0;
+  case nua_callstate_ready:
+    return 1;
+  case nua_callstate_terminated:
+    if (call)
+      nua_handle_destroy(call->nh), call->nh = NULL;
+    return 0;
+  default:
+    return 0;
+  }
+}
+
+int test_100rel(struct context *ctx)
+{
+  int retval = 0;
+
+  retval = test_180rel(ctx); RETURN_ON_SINGLE_FAILURE(retval);
+  retval = test_prack_auth(ctx); RETURN_ON_SINGLE_FAILURE(retval);
+  retval = test_183rel(ctx); RETURN_ON_SINGLE_FAILURE(retval);
+  retval = test_preconditions(ctx); RETURN_ON_SINGLE_FAILURE(retval);
+  retval = test_preconditions2(ctx); RETURN_ON_SINGLE_FAILURE(retval);
+  retval = test_update_by_uas(ctx); RETURN_ON_SINGLE_FAILURE(retval);
+  retval = test_update_failure(ctx); RETURN_ON_SINGLE_FAILURE(retval);
+  retval = test_180rel_cancel1(ctx); RETURN_ON_SINGLE_FAILURE(retval);
+  retval = test_180rel_cancel2(ctx); RETURN_ON_SINGLE_FAILURE(retval);
+  retval = test_180rel_redirected(ctx); RETURN_ON_SINGLE_FAILURE(retval);
+
+  nua_set_params(ctx->a.nua,
+		 NUTAG_EARLY_MEDIA(0),
+		 SIPTAG_SUPPORTED(ctx->a.supported),
+		 TAG_END());
+  run_a_until(ctx, nua_r_set_params, until_final_response);
+
+  nua_set_params(ctx->b.nua,
+		 NUTAG_EARLY_MEDIA(0),
+		 NUTAG_ONLY183_100REL(0),
+		 SIPTAG_SUPPORTED(ctx->b.supported),
+		 TAG_END());
+  run_b_until(ctx, nua_r_set_params, until_final_response);
+
+  nua_set_params(ctx->c.nua,
+		 NUTAG_EARLY_MEDIA(0),
+		 NUTAG_ONLY183_100REL(0),
+		 SIPTAG_SUPPORTED(ctx->c.supported),
+		 TAG_END());
+  run_c_until(ctx, nua_r_set_params, until_final_response);
+
+  return retval;
+}

Added: freeswitch/trunk/libs/sofia-sip/tests/test_basic_call.c
==============================================================================
--- (empty file)
+++ freeswitch/trunk/libs/sofia-sip/tests/test_basic_call.c	Wed May 14 15:10:54 2008
@@ -0,0 +1,1999 @@
+/*
+ * This file is part of the Sofia-SIP package
+ *
+ * Copyright (C) 2005 Nokia Corporation.
+ *
+ * Contact: Pekka Pessi <pekka.pessi at nokia.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA
+ *
+ */
+
+/**@CFILE test_nua_basic_call.c
+ * @brief Test basic call.
+ *
+ * @author Pekka Pessi <Pekka.Pessi at nokia.com>
+ * @author Martti Mela <Martti Mela at nokia.com>
+ *
+ * @date Created: Wed Aug 17 12:12:12 EEST 2005 ppessi
+ */
+
+#include "config.h"
+
+#include "test_nua.h"
+#include <sofia-sip/su_tag_class.h>
+
+#if HAVE_FUNC
+#elif HAVE_FUNCTION
+#define __func__ __FUNCTION__
+#else
+#define __func__ "test_basic_call"
+#endif
+
+/* ======================================================================== */
+
+int until_terminated(CONDITION_PARAMS)
+{
+  if (!check_handle(ep, call, nh, SIP_500_INTERNAL_SERVER_ERROR))
+    return 0;
+
+  save_event_in_list(ctx, event, ep, call);
+
+  return event == nua_i_state && callstate(tags) == nua_callstate_terminated;
+}
+
+/*
+ X     accept_call    ep
+ |                    |
+ |-------INVITE------>|
+ |<----100 Trying-----|
+ |                    |
+ |<----180 Ringing----|
+ |                    |
+ |<--------200--------|
+ |---------ACK------->|
+*/
+int accept_call(CONDITION_PARAMS)
+{
+  if (!(check_handle(ep, call, nh, SIP_500_INTERNAL_SERVER_ERROR)))
+    return 0;
+
+  save_event_in_list(ctx, event, ep, call);
+
+  switch (callstate(tags)) {
+  case nua_callstate_received:
+    RESPOND(ep, call, nh, SIP_180_RINGING, 
+	    TAG_END());
+    return 0;
+  case nua_callstate_early:
+    RESPOND(ep, call, nh, SIP_200_OK,
+	    TAG_IF(call->sdp, SOATAG_USER_SDP_STR(call->sdp)),
+	    TAG_END());
+    return 0;
+  case nua_callstate_ready:
+    return 1;
+  case nua_callstate_terminated:
+    if (call)
+      nua_handle_destroy(call->nh), call->nh = NULL;
+    return 1;
+  default:
+    return 0;
+  }
+}
+
+
+/*
+ X     accept_call    ep
+ |                    |
+ |-------INVITE------>|
+ |<----100 Trying-----|
+ |                    |
+ |<----180 Ringing----|
+ |                    |
+ |<--------200--------|
+ |---------ACK------->|
+*/
+int accept_call_with_early_sdp(CONDITION_PARAMS)
+{
+  if (!(check_handle(ep, call, nh, SIP_500_INTERNAL_SERVER_ERROR)))
+    return 0;
+
+  save_event_in_list(ctx, event, ep, call);
+
+  switch (callstate(tags)) {
+  case nua_callstate_received:
+    RESPOND(ep, call, nh, SIP_180_RINGING, 
+	    TAG_IF(call->sdp, SOATAG_USER_SDP_STR(call->sdp)),
+	    NUTAG_M_DISPLAY("Bob"),
+	    NUTAG_M_USERNAME("b+b"),
+	    TAG_END());
+    return 0;
+  case nua_callstate_early:
+    RESPOND(ep, call, nh, SIP_200_OK,
+	    TAG_IF(call->sdp, SOATAG_USER_SDP_STR(call->sdp)),
+	    TAG_END());
+    return 0;
+  case nua_callstate_ready:
+    return 1;
+  case nua_callstate_terminated:
+    if (call)
+      nua_handle_destroy(call->nh), call->nh = NULL;
+    return 1;
+  default:
+    return 0;
+  }
+}
+
+
+/*
+ X      INVITE
+ |                    |
+ |-------INVITE------>|
+ |<--------200--------|
+ |---------ACK------->|
+*/
+int until_ready(CONDITION_PARAMS)
+{
+  if (!(check_handle(ep, call, nh, SIP_500_INTERNAL_SERVER_ERROR)))
+    return 0;
+
+  save_event_in_list(ctx, event, ep, call);
+
+  switch (callstate(tags)) {
+  case nua_callstate_ready:
+    return 1;
+  case nua_callstate_terminated:
+    if (call)
+      nua_handle_destroy(call->nh), call->nh = NULL;
+    return 1;
+  default:
+    return 0;
+  }
+}
+
+/* ======================================================================== */
+
+/* Basic call:
+
+   A			B
+   |-------INVITE------>|
+   |<----100 Trying-----|
+   |			|
+   |<----180 Ringing----|
+   |			|
+   |<------200 OK-------|
+   |--------ACK-------->|
+   |			|
+   |<-------BYE---------|
+   |-------200 OK------>|
+   |			|
+
+   Client transitions:
+   INIT -(C1)-> CALLING -(C2a)-> PROCEEDING -(C3+C4)-> READY
+   Server transitions:
+   INIT -(S1)-> RECEIVED -(S2a)-> EARLY -(S3b)-> COMPLETED -(S4)-> READY
+
+   B sends BYE:
+   READY -(T2)-> TERMINATING -(T3)-> TERMINATED
+   A receives BYE:
+   READY -(T1)-> TERMINATED
+
+   See @page nua_call_model in nua.docs for more information
+*/
+
+int test_basic_call_1(struct context *ctx)
+{
+  BEGIN();
+
+  struct endpoint *a = &ctx->a,  *b = &ctx->b;
+  struct call *a_call = a->call, *b_call = b->call;
+  struct event *e;
+  sip_t *sip;
+  sip_replaces_t *repa, *repb;
+  nua_handle_t *nh;
+
+  static int once = 0;
+  sip_time_t se, min_se;
+
+  if (print_headings)
+    printf("TEST NUA-3.1: Basic call\n");
+
+  /* Disable session timer from proxy */
+  test_proxy_get_session_timer(ctx->p, &se, &min_se); 
+  test_proxy_set_session_timer(ctx->p, 0, 0); 
+
+  a_call->sdp = "m=audio 5008 RTP/AVP 8";
+  b_call->sdp = "m=audio 5010 RTP/AVP 0 8";
+
+  TEST_1(a_call->nh = nua_handle(a->nua, a_call, SIPTAG_TO(b->to), TAG_END()));
+
+  TEST_1(!nua_handle_has_active_call(a_call->nh));
+  TEST_1(!nua_handle_has_call_on_hold(a_call->nh));
+
+  INVITE(a, a_call, a_call->nh,
+	 TAG_IF(!ctx->proxy_tests, NUTAG_URL(b->contact->m_url)),
+	 SOATAG_USER_SDP_STR(a_call->sdp),
+	 NUTAG_M_USERNAME("a+a"),
+	 NUTAG_M_DISPLAY("Alice"),
+	 TAG_END());
+
+  run_ab_until(ctx, -1, until_ready, -1, accept_call_with_early_sdp);
+
+  TEST_1(nua_handle_has_active_call(a_call->nh));
+  TEST_1(!nua_handle_has_call_on_hold(a_call->nh));
+
+  TEST_1(nua_handle_has_active_call(b_call->nh));
+  TEST_1(!nua_handle_has_call_on_hold(b_call->nh));
+
+  TEST_1(repa = nua_handle_make_replaces(a_call->nh, nua_handle_home(a_call->nh), 0));
+  TEST_1(repb = nua_handle_make_replaces(b_call->nh, nua_handle_home(b_call->nh), 0));
+
+  TEST_S(repa->rp_call_id, repb->rp_call_id);
+
+  TEST_1(!nua_handle_by_replaces(a->nua, repa));
+  TEST_1(!nua_handle_by_replaces(b->nua, repb));
+
+  TEST_1(nh = nua_handle_by_replaces(a->nua, repb));
+  TEST_P(nh, a_call->nh);
+  nua_handle_unref(nh);
+
+  TEST_1(nh = nua_handle_by_replaces(b->nua, repa));
+  TEST_P(nh, b_call->nh);
+  nua_handle_unref(nh);
+
+  /* Client transitions:
+     INIT -(C1)-> CALLING: nua_invite(), nua_i_state
+     CALLING -(C2)-> PROCEEDING: nua_r_invite, nua_i_state
+     PROCEEDING -(C3+C4)-> READY: nua_r_invite, nua_i_state
+  */
+  TEST_1(e = a->events->head); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_calling); /* CALLING */
+  TEST_1(is_offer_sent(e->data->e_tags));
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_r_invite);
+  TEST(e->data->e_status, 180);
+  TEST_1(sip = sip_object(e->data->e_msg));
+  TEST_S(sip->sip_call_id->i_id, repb->rp_call_id);
+  TEST_S(sip->sip_from->a_tag, repb->rp_to_tag);
+  TEST_S(sip->sip_to->a_tag, repb->rp_from_tag);
+  TEST_1(sip->sip_payload);
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_proceeding); /* PROCEEDING */
+  TEST_1(is_answer_recv(e->data->e_tags));
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_r_invite);
+  TEST(e->data->e_status, 200);
+  TEST_1(sip = sip_object(e->data->e_msg));
+  TEST_1(sip->sip_payload);
+  TEST_1(sip->sip_contact);
+  TEST_S(sip->sip_contact->m_display, "Bob");
+  TEST_S(sip->sip_contact->m_url->url_user, "b+b");
+  if (!once) {
+    /* The session expiration is not used by default. */
+    TEST_1(sip->sip_session_expires == NULL);
+  }
+  /* Test that B uses application-specific contact */
+  if (ctx->proxy_tests)
+    TEST_1(sip->sip_contact->m_url->url_user);
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_ready); /* READY */
+  TEST_1(!e->next);
+  free_events_in_list(ctx, a->events);
+
+  /*
+   Server transitions:
+   INIT -(S1)-> RECEIVED: nua_i_invite, nua_i_state
+   RECEIVED -(S2a)-> EARLY: nua_respond(), nua_i_state
+   EARLY -(S3b)-> COMPLETED: nua_respond(), nua_i_state
+   COMPLETED -(S4)-> READY: nua_i_ack, nua_i_state
+  */
+  TEST_1(e = b->events->head); TEST_E(e->data->e_event, nua_i_invite);
+  TEST(e->data->e_status, 100);
+  TEST_1(sip = sip_object(e->data->e_msg));
+  TEST_1(sip->sip_contact);
+  TEST_S(sip->sip_contact->m_display, "Alice");
+  TEST_S(sip->sip_contact->m_url->url_user, "a+a");
+  if (!once++) {
+    /* The Session-Expires header is not used by default. */
+    TEST_1(sip->sip_session_expires == NULL);
+  }
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_received); /* RECEIVED */
+  TEST_1(is_offer_recv(e->data->e_tags));
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_early); /* EARLY */
+  TEST_1(is_answer_sent(e->data->e_tags));
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_completed); /* COMPLETED */
+  TEST_1(is_answer_sent(e->data->e_tags));
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_ack);
+  TEST_1(sip = sip_object(e->data->e_msg));
+  TEST_S(sip->sip_call_id->i_id, repa->rp_call_id);
+  TEST_S(sip->sip_from->a_tag, repa->rp_from_tag);
+  TEST_S(sip->sip_to->a_tag, repa->rp_to_tag);
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_ready); /* READY */
+  TEST_1(!e->next);
+  free_events_in_list(ctx, b->events);
+
+  BYE(b, b_call, b_call->nh, TAG_END());
+  run_ab_until(ctx, -1, until_terminated, -1, until_terminated);
+
+  /* B transitions:
+   READY --(T2)--> TERMINATING: nua_bye()
+   TERMINATING --(T3)--> TERMINATED: nua_r_bye, nua_i_state
+  */
+  TEST_1(e = b->events->head);  TEST_E(e->data->e_event, nua_r_bye);
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_terminated); /* TERMINATED */
+  TEST_1(!e->next);
+  free_events_in_list(ctx, b->events);
+
+  TEST_1(!nua_handle_has_active_call(b_call->nh));
+
+  /* A transitions:
+     READY -(T1)-> TERMINATED: nua_i_bye, nua_i_state
+  */
+  TEST_1(e = a->events->head); TEST_E(e->data->e_event, nua_i_bye);
+  TEST(e->data->e_status, 200);
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_terminated); /* TERMINATED */
+  TEST_1(!e->next);
+  free_events_in_list(ctx, a->events);
+
+  TEST_1(!nua_handle_has_active_call(a_call->nh));
+
+  nua_handle_destroy(a_call->nh), a_call->nh = NULL;
+  nua_handle_destroy(b_call->nh), b_call->nh = NULL;
+
+  test_proxy_set_session_timer(ctx->p, se, min_se); 
+
+  if (print_headings)
+    printf("TEST NUA-3.1: PASSED\n");
+
+  END();
+}
+
+/*
+  accept_early_answer
+ X                    ep
+ |                    |
+ |-------INVITE------>|
+ |<----100 Trying-----|
+ |                    |
+ |<----180 Ringing----|
+ |                    |
+ |<--------200--------|
+ |---------ACK------->|
+*/
+int accept_early_answer(CONDITION_PARAMS)
+{
+  if (!(check_handle(ep, call, nh, SIP_500_INTERNAL_SERVER_ERROR)))
+    return 0;
+
+  save_event_in_list(ctx, event, ep, call);
+
+  switch (callstate(tags)) {
+  case nua_callstate_received:
+    RESPOND(ep, call, nh, SIP_180_RINGING,
+	    NUTAG_EARLY_ANSWER(1),
+	    TAG_IF(call->sdp, SOATAG_USER_SDP_STR(call->sdp)),
+	    TAG_END());
+    return 0;
+  case nua_callstate_early:
+    RESPOND(ep, call, nh, SIP_200_OK,
+	    TAG_IF(call->sdp, SOATAG_USER_SDP_STR(call->sdp)),
+	    TAG_END());
+    return 0;
+  case nua_callstate_ready:
+    return 1;
+  case nua_callstate_terminated:
+    if (call)
+      nua_handle_destroy(call->nh), call->nh = NULL;
+    return 1;
+  default:
+    return 0;
+  }
+}
+
+int test_basic_call_2(struct context *ctx)
+{
+  BEGIN();
+
+  struct endpoint *a = &ctx->a,  *b = &ctx->b;
+  struct call *a_call = a->call, *b_call = b->call;
+  struct event *e;
+  sip_t const *sip;
+
+  if (print_headings)
+    printf("TEST NUA-3.2: Basic call with SDP in 180\n");
+
+  a_call->sdp = "m=audio 5008 RTP/AVP 8";
+  b_call->sdp = "m=audio 5010 RTP/AVP 0 8";
+
+  TEST_1(a_call->nh = nua_handle(a->nua, a_call, 
+				 SIPTAG_TO_STR("<sip:b at x.org>"),
+				 TAG_END()));
+
+  TEST_1(!nua_handle_has_active_call(a_call->nh));
+  TEST_1(!nua_handle_has_call_on_hold(a_call->nh));
+
+  INVITE(a, a_call, a_call->nh,
+	 NUTAG_URL(b->contact->m_url),
+	 SOATAG_USER_SDP_STR(a_call->sdp),
+	 NUTAG_ALLOW("INFO"),
+	 TAG_END());
+
+  run_ab_until(ctx, -1, until_ready, -1, accept_early_answer);
+
+  /* Client transitions:
+     INIT -(C1)-> CALLING: nua_invite(), nua_i_state
+     CALLING -(C2)-> PROCEEDING: nua_r_invite, nua_i_state
+     PROCEEDING -(C3+C4)-> READY: nua_r_invite, nua_i_state
+  */
+  TEST_1(e = a->events->head); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_calling); /* CALLING */
+  TEST_1(is_offer_sent(e->data->e_tags));
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_r_invite);
+  TEST(e->data->e_status, 180);
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_proceeding); /* PROCEEDING */
+  TEST_1(is_answer_recv(e->data->e_tags));
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_r_invite);
+  TEST(e->data->e_status, 200);
+  TEST_1(sip = sip_object(e->data->e_msg));
+  TEST_1(sip->sip_content_type); 
+  TEST_S(sip->sip_content_type->c_type, "application/sdp");
+  TEST_1(sip->sip_payload);	/* there is sdp in 200 OK */
+  TEST_1(sip->sip_contact);
+#if nomore
+  /* Test that B does not use application-specific contact */
+  TEST_1(!sip->sip_contact->m_url->url_user);
+#else
+  /* sf.net bug #1816647: Outbound contact does not make it to dialogs */
+  /* Now we use first registered contact if aor does not match */
+  if (ctx->proxy_tests)
+    TEST_S(sip->sip_contact->m_url->url_user, "b");
+#endif
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_ready); /* READY */
+  TEST_1(!is_answer_recv(e->data->e_tags)); /* but it is ignored */
+  TEST_1(!e->next);
+  free_events_in_list(ctx, a->events);
+
+  TEST_1(nua_handle_has_active_call(a_call->nh));
+  TEST_1(!nua_handle_has_call_on_hold(a_call->nh));
+
+  /*
+   Server transitions:
+   INIT -(S1)-> RECEIVED: nua_i_invite, nua_i_state
+   RECEIVED -(S2a)-> EARLY: nua_respond(), nua_i_state
+   EARLY -(S3b)-> COMPLETED: nua_respond(), nua_i_state
+   COMPLETED -(S4)-> READY: nua_i_ack, nua_i_state
+  */
+  TEST_1(e = b->events->head); TEST_E(e->data->e_event, nua_i_invite);
+  TEST(e->data->e_status, 100);
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_received); /* RECEIVED */
+  TEST_1(is_offer_recv(e->data->e_tags));
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_early); /* EARLY */
+  TEST_1(is_answer_sent(e->data->e_tags));
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_completed); /* COMPLETED */
+  TEST_1(is_answer_sent(e->data->e_tags));
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_ack);
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_ready); /* READY */
+  TEST_1(!e->next);
+  free_events_in_list(ctx, b->events);
+
+  TEST_1(nua_handle_has_active_call(b_call->nh));
+  TEST_1(!nua_handle_has_call_on_hold(b_call->nh));
+
+  /* Send a NOTIFY from B to A */
+  if (print_headings)
+    printf("TEST NUA-3.2.2: send a NOTIFY within a dialog\n");
+
+  /* Make A to accept NOTIFY */
+  nua_set_params(a->nua, NUTAG_APPL_METHOD("NOTIFY"), TAG_END());
+  run_a_until(ctx, nua_r_set_params, until_final_response);
+
+  NOTIFY(b, b_call, b_call->nh,
+	 NUTAG_NEWSUB(1),
+	 SIPTAG_SUBJECT_STR("NUA-3.2.2"),
+	 SIPTAG_EVENT_STR("message-summary"),
+	 SIPTAG_CONTENT_TYPE_STR("application/simple-message-summary"),
+	 SIPTAG_PAYLOAD_STR("Messages-Waiting: no"),
+	 TAG_END());
+
+  run_ab_until(ctx, -1, accept_notify, -1, save_until_final_response);
+
+  /* Notifier events: nua_r_notify */
+  TEST_1(e = b->events->head); TEST_E(e->data->e_event, nua_r_notify);
+  TEST(e->data->e_status, 200);
+  TEST_1(tl_find(e->data->e_tags, nutag_substate));
+  TEST(tl_find(e->data->e_tags, nutag_substate)->t_value,
+       nua_substate_terminated);
+
+  /* watcher events: nua_i_notify */
+  TEST_1(e = a->events->head); TEST_E(e->data->e_event, nua_i_notify);
+  TEST_1(sip = sip_object(e->data->e_msg));
+  TEST_1(sip->sip_subscription_state);
+  TEST_S(sip->sip_subscription_state->ss_substate, "terminated");
+  TEST_1(tl_find(e->data->e_tags, nutag_substate));
+  TEST(tl_find(e->data->e_tags, nutag_substate)->t_value, 
+       nua_substate_terminated);
+  TEST_1(!e->next);
+
+  free_events_in_list(ctx, a->events);
+  free_events_in_list(ctx, b->events);
+
+  if (print_headings)
+    printf("TEST NUA-3.2.2: PASSED\n");
+
+  INFO(b, b_call, b_call->nh, TAG_END());
+  BYE(b, b_call, b_call->nh, TAG_END());
+  INFO(b, b_call, b_call->nh, TAG_END());
+
+  run_ab_until(ctx, -1, until_terminated, -1, until_terminated);
+
+  while (!b->events->head || /* r_info */
+	 !b->events->head->next || /* r_bye */
+	 !b->events->head->next->next || /* i_state */
+	 !b->events->head->next->next->next) /* r_info */
+    run_ab_until(ctx, -1, save_events, -1, save_until_final_response);
+
+  /* B transitions:
+   nua_info()
+   READY --(T2)--> TERMINATING: nua_bye()
+   nua_r_info with 200
+   TERMINATING --(T3)--> TERMINATED: nua_r_bye, nua_i_state
+   nua_r_info with 481/900
+  */
+  TEST_1(e = b->events->head); TEST_E(e->data->e_event, nua_r_info);
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_r_bye);
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_terminated); /* TERMINATED */
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_r_info);
+  TEST_1(e->data->e_status >= 900 || e->data->e_status == 481);
+  TEST_1(!e->next);
+  free_events_in_list(ctx, b->events);
+
+  TEST_1(!nua_handle_has_active_call(b_call->nh));
+
+  /* A transitions:
+     nua_i_info
+     READY -(T1)-> TERMINATED: nua_i_bye, nua_i_state
+  */
+  TEST_1(e = a->events->head); TEST_E(e->data->e_event, nua_i_info);
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_bye);
+  TEST(e->data->e_status, 200);
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_terminated); /* TERMINATED */
+  TEST_1(!e->next);
+  free_events_in_list(ctx, a->events);
+
+  BYE(a, a_call, a_call->nh, TAG_END());
+  run_a_until(ctx, -1, save_until_final_response);
+
+  TEST_1(e = a->events->head); TEST_E(e->data->e_event, nua_r_bye);
+  TEST_1(e->data->e_status >= 900);
+  TEST_1(!e->next);
+  free_events_in_list(ctx, a->events);
+
+  nua_handle_destroy(a_call->nh), a_call->nh = NULL;
+  nua_handle_destroy(b_call->nh), b_call->nh = NULL;
+
+  if (print_headings)
+    printf("TEST NUA-3.2: PASSED\n");
+
+  END();
+}
+
+/* ====================================================================== */
+
+/* Basic calls with soa disabled */
+
+int accept_call_no_media(CONDITION_PARAMS)
+{
+  if (!(check_handle(ep, call, nh, SIP_500_INTERNAL_SERVER_ERROR)))
+    return 0;
+
+  save_event_in_list(ctx, event, ep, call);
+
+  switch (callstate(tags)) {
+  case nua_callstate_received:
+    RESPOND(ep, call, nh, SIP_180_RINGING, 
+	    NUTAG_MEDIA_ENABLE(0),
+	    TAG_END());
+    return 0;
+  case nua_callstate_early:
+    RESPOND(ep, call, nh, SIP_200_OK,
+	    TAG_IF(call->sdp, SIPTAG_CONTENT_TYPE_STR("application/sdp")),
+	    TAG_IF(call->sdp, SIPTAG_PAYLOAD_STR(call->sdp)),
+	    TAG_END());
+    return 0;
+  case nua_callstate_ready:
+    return 1;
+  case nua_callstate_terminated:
+    if (call)
+      nua_handle_destroy(call->nh), call->nh = NULL;
+    return 1;
+  default:
+    return 0;
+  }
+}
+
+int test_basic_call_3(struct context *ctx)
+{
+  BEGIN();
+
+  struct endpoint *a = &ctx->a,  *b = &ctx->b;
+  struct call *a_call = a->call, *b_call = b->call;
+  struct event *e;
+  sip_t const *sip;
+
+  if (print_headings)
+    printf("TEST NUA-3.3: Basic call with media disabled\n");
+
+  a_call->sdp = "v=0\r\n"
+    "o=- 1 1 IN IP4 127.0.0.1\r\n"
+    "s=-\r\n"
+    "c=IN IP4 127.0.0.1\r\n"
+    "t=0 0\r\n"
+    "m=audio 5008 RTP/AVP 8\r\n";
+
+  b_call->sdp = 
+    "v=0\r\n"
+    "o=- 2 1 IN IP4 127.0.0.1\r\n"
+    "s=-\r\n"
+    "c=IN IP4 127.0.0.1\r\n"
+    "t=0 0\r\n"
+    "m=audio 5010 RTP/AVP 0 8\r\n";
+
+  TEST_1(a_call->nh = nua_handle(a->nua, a_call, 
+				 SIPTAG_TO_STR("<sip:b at x.org>"),
+				 TAG_END()));
+
+  TEST_1(!nua_handle_has_active_call(a_call->nh));
+  TEST_1(!nua_handle_has_call_on_hold(a_call->nh));
+
+  INVITE(a, a_call, a_call->nh,
+	 NUTAG_MEDIA_ENABLE(0),
+	 NUTAG_URL(b->contact->m_url),
+	 SIPTAG_CONTENT_TYPE_STR("application/sdp"),
+	 SIPTAG_PAYLOAD_STR(a_call->sdp),
+	 TAG_END());
+
+  run_ab_until(ctx, -1, until_ready, -1, accept_call_no_media);
+
+  /* Client transitions:
+     INIT -(C1)-> CALLING: nua_invite(), nua_i_state
+     CALLING -(C2)-> PROCEEDING: nua_r_invite, nua_i_state
+     PROCEEDING -(C3+C4)-> READY: nua_r_invite, nua_i_state
+  */
+  TEST_1(e = a->events->head); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_calling); /* CALLING */
+  TEST_1(is_offer_sent(e->data->e_tags));
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_r_invite);
+  TEST(e->data->e_status, 180);
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_proceeding); /* PROCEEDING */
+  TEST_1(!is_answer_recv(e->data->e_tags));
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_r_invite);
+  TEST(e->data->e_status, 200);
+  TEST_1(sip = sip_object(e->data->e_msg));
+  TEST_1(sip->sip_content_type); 
+  TEST_S(sip->sip_content_type->c_type, "application/sdp");
+  TEST_1(sip->sip_payload);	/* there is sdp in 200 OK */
+  TEST_1(sip->sip_contact);
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_ready); /* READY */
+  TEST_1(is_answer_recv(e->data->e_tags));
+  TEST_1(!e->next);
+  free_events_in_list(ctx, a->events);
+
+  TEST_1(nua_handle_has_active_call(a_call->nh));
+  TEST_1(!nua_handle_has_call_on_hold(a_call->nh));
+
+  /*
+   Server transitions:
+   INIT -(S1)-> RECEIVED: nua_i_invite, nua_i_state
+   RECEIVED -(S2a)-> EARLY: nua_respond(), nua_i_state
+   EARLY -(S3b)-> COMPLETED: nua_respond(), nua_i_state
+   COMPLETED -(S4)-> READY: nua_i_ack, nua_i_state
+  */
+  TEST_1(e = b->events->head); TEST_E(e->data->e_event, nua_i_invite);
+  TEST(e->data->e_status, 100);
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_received); /* RECEIVED */
+  TEST_1(is_offer_recv(e->data->e_tags));
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_early); /* EARLY */
+  TEST_1(!is_answer_sent(e->data->e_tags));
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_completed); /* COMPLETED */
+  TEST_1(is_answer_sent(e->data->e_tags));
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_ack);
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_ready); /* READY */
+  TEST_1(!e->next);
+  free_events_in_list(ctx, b->events);
+
+  TEST_1(nua_handle_has_active_call(b_call->nh));
+  TEST_1(!nua_handle_has_call_on_hold(b_call->nh));
+
+  BYE(b, b_call, b_call->nh, TAG_END());
+
+  run_ab_until(ctx, -1, until_terminated, -1, until_terminated);
+
+  /* B transitions:
+   READY --(T2)--> TERMINATING: nua_bye()
+   TERMINATING --(T3)--> TERMINATED: nua_r_bye, nua_i_state
+  */
+  TEST_1(e = b->events->head); TEST_E(e->data->e_event, nua_r_bye);
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_terminated); /* TERMINATED */
+  TEST_1(!e->next);
+  free_events_in_list(ctx, b->events);
+
+  TEST_1(!nua_handle_has_active_call(b_call->nh));
+
+  /* A transitions:
+     nua_i_info
+     READY -(T1)-> TERMINATED: nua_i_bye, nua_i_state
+  */
+  TEST_1(e = a->events->head); TEST_E(e->data->e_event, nua_i_bye);
+  TEST(e->data->e_status, 200);
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_terminated); /* TERMINATED */
+  TEST_1(!e->next);
+  free_events_in_list(ctx, a->events);
+
+  nua_handle_destroy(a_call->nh), a_call->nh = NULL;
+  nua_handle_destroy(b_call->nh), b_call->nh = NULL;
+
+  if (print_headings)
+    printf("TEST NUA-3.3: PASSED\n");
+
+  END();
+}
+
+int ack_when_completing_no_media(CONDITION_PARAMS)
+{
+  if (!(check_handle(ep, call, nh, SIP_500_INTERNAL_SERVER_ERROR)))
+    return 0;
+
+  save_event_in_list(ctx, event, ep, call);
+
+  switch (callstate(tags)) {
+  case nua_callstate_completing:
+    ACK(ep, call, nh, 
+	TAG_IF(call->sdp, SIPTAG_CONTENT_TYPE_STR("application/sdp")),
+	TAG_IF(call->sdp, SIPTAG_PAYLOAD_STR(call->sdp)),
+	TAG_END());
+    return 0;
+  case nua_callstate_ready:
+    return 1;
+  case nua_callstate_terminated:
+    if (call)
+      nua_handle_destroy(call->nh), call->nh = NULL;
+    return 1;
+  default:
+    return 0;
+  }
+}
+
+int accept_call_no_media2(CONDITION_PARAMS)
+{
+  if (!(check_handle(ep, call, nh, SIP_500_INTERNAL_SERVER_ERROR)))
+    return 0;
+
+  save_event_in_list(ctx, event, ep, call);
+
+  switch (callstate(tags)) {
+  case nua_callstate_received:
+    RESPOND(ep, call, nh, SIP_180_RINGING, 
+	    NUTAG_MEDIA_ENABLE(0),
+	    TAG_IF(call->sdp, SIPTAG_CONTENT_TYPE_STR("application/sdp")),
+	    TAG_IF(call->sdp, SIPTAG_PAYLOAD_STR(call->sdp)),
+	    TAG_END());
+    return 0;
+  case nua_callstate_early:
+    RESPOND(ep, call, nh, SIP_200_OK,
+	    TAG_IF(call->sdp, SIPTAG_CONTENT_TYPE_STR("application/sdp")),
+	    TAG_IF(call->sdp, SIPTAG_PAYLOAD_STR(call->sdp)),
+	    TAG_END());
+    return 0;
+  case nua_callstate_ready:
+    return 1;
+  case nua_callstate_terminated:
+    if (call)
+      nua_handle_destroy(call->nh), call->nh = NULL;
+    return 1;
+  default:
+    return 0;
+  }
+}
+
+/* Media disabled, offer/answer in 200 OK/ACK */
+int test_basic_call_4(struct context *ctx)
+{
+  BEGIN();
+
+  struct endpoint *a = &ctx->a,  *b = &ctx->b;
+  struct call *a_call = a->call, *b_call = b->call;
+  struct event *e;
+  sip_t const *sip;
+
+  if (print_headings)
+    printf("TEST NUA-3.4: 3pcc call with media disabled\n");
+
+  a_call->sdp = "v=0\r\n"
+    "o=- 1 1 IN IP4 127.0.0.1\r\n"
+    "s=-\r\n"
+    "c=IN IP4 127.0.0.1\r\n"
+    "t=0 0\r\n"
+    "m=audio 5008 RTP/AVP 8\r\n";
+
+  b_call->sdp = 
+    "v=0\r\n"
+    "o=- 2 1 IN IP4 127.0.0.1\r\n"
+    "s=-\r\n"
+    "c=IN IP4 127.0.0.1\r\n"
+    "t=0 0\r\n"
+    "m=audio 5010 RTP/AVP 0 8\r\n";
+
+  TEST_1(a_call->nh = nua_handle(a->nua, a_call, 
+				 SIPTAG_TO_STR("<sip:b at x.org>"),
+				 TAG_END()));
+
+  TEST_1(!nua_handle_has_active_call(a_call->nh));
+  TEST_1(!nua_handle_has_call_on_hold(a_call->nh));
+
+  INVITE(a, a_call, a_call->nh,
+	 NUTAG_MEDIA_ENABLE(0),
+	 NUTAG_URL(b->contact->m_url),
+	 NUTAG_AUTOACK(0),
+	 TAG_END());
+
+  run_ab_until(ctx, -1, ack_when_completing_no_media,
+	       -1, accept_call_no_media2);
+
+  /* Client transitions:
+     INIT -(C1)-> CALLING: nua_invite(), nua_i_state
+     CALLING -(C2)-> PROCEEDING: nua_r_invite, nua_i_state
+     PROCEEDING -(C3+C4)-> READY: nua_r_invite, nua_i_state
+  */
+  TEST_1(e = a->events->head); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_calling); /* CALLING */
+  TEST_1(!is_offer_sent(e->data->e_tags));
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_r_invite);
+  TEST(e->data->e_status, 180);
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_proceeding); /* PROCEEDING */
+  TEST_1(!is_answer_recv(e->data->e_tags));
+  TEST_1(is_offer_recv(e->data->e_tags));
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_r_invite);
+  TEST(e->data->e_status, 200);
+  TEST_1(sip = sip_object(e->data->e_msg));
+  TEST_1(sip->sip_content_type); 
+  TEST_S(sip->sip_content_type->c_type, "application/sdp");
+  TEST_1(sip->sip_payload);	/* there is sdp in 200 OK */
+  TEST_1(sip->sip_contact);
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_completing); /* COMPLETING */
+  TEST_1(is_offer_recv(e->data->e_tags));
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_ready); /* READY */
+  TEST_1(is_answer_sent(e->data->e_tags));
+  TEST_1(!e->next);
+  free_events_in_list(ctx, a->events);
+
+  TEST_1(nua_handle_has_active_call(a_call->nh));
+  TEST_1(!nua_handle_has_call_on_hold(a_call->nh));
+
+  /*
+   Server transitions:
+   INIT -(S1)-> RECEIVED: nua_i_invite, nua_i_state
+   RECEIVED -(S2a)-> EARLY: nua_respond(), nua_i_state
+   EARLY -(S3b)-> COMPLETED: nua_respond(), nua_i_state
+   COMPLETED -(S4)-> READY: nua_i_ack, nua_i_state
+  */
+  TEST_1(e = b->events->head); TEST_E(e->data->e_event, nua_i_invite);
+  TEST(e->data->e_status, 100);
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_received); /* RECEIVED */
+  TEST_1(!is_offer_recv(e->data->e_tags));
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_early); /* EARLY */
+  TEST_1(!is_answer_sent(e->data->e_tags));
+  TEST_1(is_offer_sent(e->data->e_tags));
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_completed); /* COMPLETED */
+  TEST_1(is_offer_sent(e->data->e_tags));
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_ack);
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_ready); /* READY */
+  TEST_1(is_answer_recv(e->data->e_tags));
+  TEST_1(!e->next);
+  free_events_in_list(ctx, b->events);
+
+  TEST_1(nua_handle_has_active_call(b_call->nh));
+  TEST_1(!nua_handle_has_call_on_hold(b_call->nh));
+
+  BYE(b, b_call, b_call->nh, TAG_END());
+
+  run_ab_until(ctx, -1, until_terminated, -1, until_terminated);
+
+  /* B transitions:
+   READY --(T2)--> TERMINATING: nua_bye()
+   TERMINATING --(T3)--> TERMINATED: nua_r_bye, nua_i_state
+  */
+  TEST_1(e = b->events->head); TEST_E(e->data->e_event, nua_r_bye);
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_terminated); /* TERMINATED */
+  TEST_1(!e->next);
+  free_events_in_list(ctx, b->events);
+
+  TEST_1(!nua_handle_has_active_call(b_call->nh));
+
+  /* A transitions:
+     nua_i_info
+     READY -(T1)-> TERMINATED: nua_i_bye, nua_i_state
+  */
+  TEST_1(e = a->events->head); TEST_E(e->data->e_event, nua_i_bye);
+  TEST(e->data->e_status, 200);
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_terminated); /* TERMINATED */
+  TEST_1(!e->next);
+  free_events_in_list(ctx, a->events);
+
+  nua_handle_destroy(a_call->nh), a_call->nh = NULL;
+  nua_handle_destroy(b_call->nh), b_call->nh = NULL;
+
+  if (print_headings)
+    printf("TEST NUA-3.4: PASSED\n");
+
+  END();
+}
+
+int change_uri_in_ack(CONDITION_PARAMS)
+{
+  if (!(check_handle(ep, call, nh, SIP_500_INTERNAL_SERVER_ERROR)))
+    return 0;
+
+  save_event_in_list(ctx, event, ep, call);
+
+  switch (callstate(tags)) {
+  case nua_callstate_completing:
+    ACK(ep, call, nh,
+	SIPTAG_FROM_STR("sip:anonymous at org.invalid"),
+	SIPTAG_TO_STR("sip:anonymous at net.invalid"),
+	TAG_END());
+    return 0;
+  case nua_callstate_ready:
+    return 1;
+  case nua_callstate_terminated:
+    if (call)
+      nua_handle_destroy(call->nh), call->nh = NULL;
+    return 1;
+  default:
+    return 0;
+  }
+}
+
+/* Test changing from/to within dialog */
+/* Test that a proper Contact gets selected in response 
+ * regardless of the To URI. 
+ */
+int test_basic_call_5(struct context *ctx)
+{
+  BEGIN();
+
+  struct endpoint *a = &ctx->a,  *b = &ctx->b;
+  struct call *a_call = a->call, *b_call = b->call;
+  struct event *e;
+  sip_t const *sip;
+
+  if (print_headings)
+    printf("TEST NUA-3.5: test changing From/To URL in ACK\n");
+
+  a_call->sdp = "m=audio 5008 RTP/AVP 8";
+  b_call->sdp = "m=audio 5010 RTP/AVP 0 8";
+
+  TEST_1(a_call->nh = nua_handle(a->nua, a_call, 
+				 SIPTAG_TO_STR("<sips:b at x.org>"),
+				 TAG_END()));
+
+  TEST_1(!nua_handle_has_active_call(a_call->nh));
+  TEST_1(!nua_handle_has_call_on_hold(a_call->nh));
+
+  INVITE(a, a_call, a_call->nh,
+	 NUTAG_URL(b->contact->m_url),
+	 SOATAG_USER_SDP_STR(a_call->sdp),
+	 NUTAG_AUTOACK(0),
+	 TAG_END());
+
+  run_ab_until(ctx, -1, change_uri_in_ack, -1, accept_call);
+
+  /* Client transitions:
+     INIT -(C1)-> CALLING: nua_invite(), nua_i_state
+     CALLING -(C2)-> PROCEEDING: nua_r_invite, nua_i_state
+     PROCEEDING -(C3+C4)-> READY: nua_r_invite, nua_i_state
+  */
+  TEST_1(e = a->events->head); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_calling); /* CALLING */
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_r_invite);
+  TEST(e->data->e_status, 180);
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_proceeding); /* PROCEEDING */
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_r_invite);
+  TEST(e->data->e_status, 200);
+  TEST_1(sip = sip_object(e->data->e_msg));
+  TEST_1(sip->sip_contact);
+  if (ctx->proxy_tests)		/* Use Contact from registration? */
+    TEST_S(sip->sip_contact->m_url->url_user, "b");
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_completing); /* COMPLETING */
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_ready); /* READY */
+  TEST_1(!e->next);
+  free_events_in_list(ctx, a->events);
+
+  TEST_1(nua_handle_has_active_call(a_call->nh));
+  TEST_1(!nua_handle_has_call_on_hold(a_call->nh));
+
+  /*
+   Server transitions:
+   INIT -(S1)-> RECEIVED: nua_i_invite, nua_i_state
+   RECEIVED -(S2a)-> EARLY: nua_respond(), nua_i_state
+   EARLY -(S3b)-> COMPLETED: nua_respond(), nua_i_state
+   COMPLETED -(S4)-> READY: nua_i_ack, nua_i_state
+  */
+  TEST_1(e = b->events->head); TEST_E(e->data->e_event, nua_i_invite);
+  TEST(e->data->e_status, 100);
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_received); /* RECEIVED */
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_early); /* EARLY */
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_completed); /* COMPLETED */
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_ack);
+  TEST_1(sip = sip_object(e->data->e_msg));
+  TEST_S(sip->sip_to->a_url->url_user, "anonymous"); 
+  TEST_S(sip->sip_from->a_url->url_user, "anonymous"); 
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_ready); /* READY */
+  TEST_1(!e->next);
+  free_events_in_list(ctx, b->events);
+
+  TEST_1(nua_handle_has_active_call(b_call->nh));
+  TEST_1(!nua_handle_has_call_on_hold(b_call->nh));
+
+  BYE(b, b_call, b_call->nh, TAG_END());
+
+  run_ab_until(ctx, -1, until_terminated, -1, until_terminated);
+
+  /* B transitions:
+   READY --(T2)--> TERMINATING: nua_bye()
+   TERMINATING --(T3)--> TERMINATED: nua_r_bye, nua_i_state
+  */
+  TEST_1(e = b->events->head); TEST_E(e->data->e_event, nua_r_bye);
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_terminated); /* TERMINATED */
+  TEST_1(!e->next);
+  free_events_in_list(ctx, b->events);
+
+  TEST_1(!nua_handle_has_active_call(b_call->nh));
+
+  /* A transitions:
+     nua_i_info
+     READY -(T1)-> TERMINATED: nua_i_bye, nua_i_state
+  */
+  TEST_1(e = a->events->head); TEST_E(e->data->e_event, nua_i_bye);
+  TEST(e->data->e_status, 200);
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_terminated); /* TERMINATED */
+  TEST_1(!e->next);
+  free_events_in_list(ctx, a->events);
+
+  nua_handle_destroy(a_call->nh), a_call->nh = NULL;
+  nua_handle_destroy(b_call->nh), b_call->nh = NULL;
+
+  if (print_headings)
+    printf("TEST NUA-3.5: PASSED\n");
+
+  END();
+}
+
+/* ======================================================================== */
+
+/* Call with media upgrade:
+
+   A			B
+   |-------INVITE------>|
+   |   with audio only	|
+   |<----100 Trying-----|
+   |			|
+   |<----180 Ringing----|
+   |			|
+   |<------200 OK-------|
+   |--------ACK-------->|
+   |			|
+   |<------INVITE-------|
+   |	with video	|
+   |-----100 Trying---->|
+   |			|
+   |-------200 OK------>|
+   |	with video	|
+   |<-------ACK---------|
+   |			|
+   |<-------BYE---------|
+   |-------200 OK------>|
+   |			|
+
+   Client transitions:
+   INIT -(C1)-> CALLING -(C2a)-> PROCEEDING -(C3+C4)-> READY
+   Server transitions:
+   INIT -(S1)-> RECEIVED -(S2a)-> EARLY -(S3b)-> COMPLETED -(S4)-> READY
+
+   B sends BYE:
+   READY -(T2)-> TERMINATING -(T3)-> TERMINATED
+   A receives BYE:
+   READY -(T1)-> TERMINATED
+
+   See @page nua_call_model in nua.docs for more information
+*/
+int accept_upgrade(CONDITION_PARAMS);
+
+int test_video_call_1(struct context *ctx)
+{
+  BEGIN();
+
+  struct endpoint *a = &ctx->a,  *b = &ctx->b;
+  struct call *a_call = a->call, *b_call = b->call;
+  struct event *e;
+  sip_t *sip;
+  sdp_session_t *b_sdp;
+  sdp_media_t *m, b_video[1];
+  sdp_rtpmap_t *rm, b_h261[1];
+
+  sip_time_t se, min_se;
+
+  if (print_headings)
+    printf("TEST NUA-3.6: Basic call\n");
+
+  /* Disable session timer from proxy */
+  test_proxy_get_session_timer(ctx->p, &se, &min_se); 
+  test_proxy_set_session_timer(ctx->p, 0, 0); 
+
+  a_call->sdp = "m=audio 5008 RTP/AVP 8";
+  b_call->sdp = "m=audio 5010 RTP/AVP 0 8";
+
+  TEST_1(a_call->nh = nua_handle(a->nua, a_call, SIPTAG_TO(b->to), TAG_END()));
+
+  TEST_1(!nua_handle_has_active_call(a_call->nh));
+  TEST_1(!nua_handle_has_call_on_hold(a_call->nh));
+
+  INVITE(a, a_call, a_call->nh,
+	 TAG_IF(!ctx->proxy_tests, NUTAG_URL(b->contact->m_url)),
+	 SOATAG_USER_SDP_STR(a_call->sdp),
+	 NUTAG_AUTOANSWER(0),
+	 TAG_END());
+
+  run_ab_until(ctx, -1, until_ready, -1, accept_call_with_early_sdp);
+
+  TEST_1(nua_handle_has_active_call(a_call->nh));
+  TEST_1(!nua_handle_has_call_on_hold(a_call->nh));
+
+  TEST_1(nua_handle_has_active_call(b_call->nh));
+  TEST_1(!nua_handle_has_call_on_hold(b_call->nh));
+
+  /* Client transitions:
+     INIT -(C1)-> CALLING: nua_invite(), nua_i_state
+     CALLING -(C2)-> PROCEEDING: nua_r_invite, nua_i_state
+     PROCEEDING -(C3+C4)-> READY: nua_r_invite, nua_i_state
+  */
+  TEST_1(e = a->events->head); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_calling); /* CALLING */
+  TEST_1(is_offer_sent(e->data->e_tags));
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_r_invite);
+  TEST(e->data->e_status, 180);
+  TEST_1(sip = sip_object(e->data->e_msg));
+  TEST_1(sip->sip_payload);
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_proceeding); /* PROCEEDING */
+  TEST_1(is_answer_recv(e->data->e_tags));
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_r_invite);
+  TEST(e->data->e_status, 200);
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_ready); /* READY */
+  TEST_1(!e->next);
+  free_events_in_list(ctx, a->events);
+
+  /*
+   Server transitions:
+   INIT -(S1)-> RECEIVED: nua_i_invite, nua_i_state
+   RECEIVED -(S2a)-> EARLY: nua_respond(), nua_i_state
+   EARLY -(S3b)-> COMPLETED: nua_respond(), nua_i_state
+   COMPLETED -(S4)-> READY: nua_i_ack, nua_i_state
+  */
+  TEST_1(e = b->events->head); TEST_E(e->data->e_event, nua_i_invite);
+  TEST(e->data->e_status, 100);
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_received); /* RECEIVED */
+  TEST_1(is_offer_recv(e->data->e_tags));
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_early); /* EARLY */
+  TEST_1(is_answer_sent(e->data->e_tags));
+  TEST_1(tl_find(e->data->e_tags, soatag_local_sdp));
+  TEST_1(b_sdp = sdp_session_dup(nua_handle_home(a_call->nh),
+				 (sdp_session_t *)
+				 tl_find(e->data->e_tags, soatag_local_sdp)
+				 ->t_value));
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_completed); /* COMPLETED */
+  TEST_1(is_answer_sent(e->data->e_tags));
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_ack);
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_ready); /* READY */
+  TEST_1(!e->next);
+  free_events_in_list(ctx, b->events);
+
+  a_call->sdp =
+    "m=audio 5008 RTP/AVP 8\n"
+    "m=video 5014 RTP/AVP 34 31\n";
+  
+  m = memset(b_video, 0, sizeof b_video);    
+  m->m_size = sizeof *m;
+  m->m_session = b_sdp;
+  m->m_type = sdp_media_video, m->m_type_name = "video";
+  m->m_port = 5016;
+  m->m_proto = sdp_proto_rtp; m->m_proto_name = "RTP/AVP";
+  m->m_rtpmaps = memset(rm = b_h261, 0, sizeof b_h261);
+  rm->rm_size = sizeof *rm;
+  rm->rm_pt = 31; rm->rm_encoding = "h261"; rm->rm_rate = 90000; 
+
+  b_sdp->sdp_media->m_next = m;
+
+  INVITE(b, b_call, b_call->nh,
+	 SOATAG_USER_SDP(b_sdp),
+	 TAG_END());
+  run_ab_until(ctx, -1, accept_upgrade, -1, until_ready);
+
+  TEST_1(e = b->events->head); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_calling); /* CALLING */
+  TEST_1(tl_find(e->data->e_tags, soatag_local_sdp_str));
+  TEST_1(strstr((char *)
+		tl_find(e->data->e_tags, soatag_local_sdp_str)->t_value,
+		"m=video"));
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_r_invite);
+  TEST(e->data->e_status, 200);
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST_1(is_answer_recv(e->data->e_tags));
+  TEST(callstate(e->data->e_tags), nua_callstate_ready); /* READY */
+  TEST_1(!e->next);
+  free_events_in_list(ctx, b->events);
+
+  TEST_1(e = a->events->head); TEST_E(e->data->e_event, nua_i_invite);
+  TEST(e->data->e_status, 100);
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_received); /* RECEIVED */
+  TEST_1(is_offer_recv(e->data->e_tags));
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_completed); /* COMPLETED */
+  TEST_1(is_answer_sent(e->data->e_tags));
+  TEST_1(tl_find(e->data->e_tags, soatag_local_sdp_str));
+  TEST_1(strstr((char *)
+		tl_find(e->data->e_tags, soatag_local_sdp_str)->t_value,
+		"m=video"));
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_ack);
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_ready); /* READY */
+  TEST_1(!e->next);
+  free_events_in_list(ctx, a->events);
+
+  BYE(b, b_call, b_call->nh, TAG_END());
+  run_ab_until(ctx, -1, until_terminated, -1, until_terminated);
+
+  /* B transitions:
+   READY --(T2)--> TERMINATING: nua_bye()
+   TERMINATING --(T3)--> TERMINATED: nua_r_bye, nua_i_state
+  */
+  TEST_1(e = b->events->head);  TEST_E(e->data->e_event, nua_r_bye);
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_terminated); /* TERMINATED */
+  TEST_1(!e->next);
+  free_events_in_list(ctx, b->events);
+
+  TEST_1(!nua_handle_has_active_call(b_call->nh));
+
+  /* A transitions:
+     READY -(T1)-> TERMINATED: nua_i_bye, nua_i_state
+  */
+  TEST_1(e = a->events->head); TEST_E(e->data->e_event, nua_i_bye);
+  TEST(e->data->e_status, 200);
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_terminated); /* TERMINATED */
+  TEST_1(!e->next);
+  free_events_in_list(ctx, a->events);
+
+  TEST_1(!nua_handle_has_active_call(a_call->nh));
+
+  nua_handle_destroy(a_call->nh), a_call->nh = NULL;
+  nua_handle_destroy(b_call->nh), b_call->nh = NULL;
+
+  test_proxy_set_session_timer(ctx->p, se, min_se); 
+
+  if (print_headings)
+    printf("TEST NUA-3.6: PASSED\n");
+
+  END();
+}
+
+int accept_upgrade(CONDITION_PARAMS)
+{
+  if (!(check_handle(ep, call, nh, SIP_500_INTERNAL_SERVER_ERROR)))
+    return 0;
+
+  save_event_in_list(ctx, event, ep, call);
+
+  if (event == nua_i_invite && status < 200) {
+    RESPOND(ep, call, nh, SIP_200_OK,
+	    TAG_IF(call->sdp, SOATAG_USER_SDP_STR(call->sdp)),
+	    TAG_END());
+    return 0;
+  }
+
+  switch (callstate(tags)) {
+  case nua_callstate_ready:
+    return 1;
+  case nua_callstate_terminated:
+    if (call)
+      nua_handle_destroy(call->nh), call->nh = NULL;
+    return 1;
+  default:
+    return 0;
+  }
+}
+
+/* Basic call and re-INVITE with user-specified Contact:
+
+   A			B
+   |-------INVITE------>|
+   |<----100 Trying-----|
+   |			|
+   |<----180 Ringing----|
+   |			|
+   |<------200 OK-------|
+   |--------ACK-------->|
+   |			|
+   |-----re-INVITE----->|
+   |<------200 OK-------|
+   |--------ACK-------->|
+   |			|
+   |<-------BYE---------|
+   |-------200 OK------>|
+   |			|
+
+   Client transitions:
+   INIT -(C1)-> CALLING -(C2a)-> PROCEEDING -(C3+C4)-> READY
+   Server transitions:
+   INIT -(S1)-> RECEIVED -(S2a)-> EARLY -(S3b)-> COMPLETED -(S4)-> READY
+
+   Both client and server save Contact from nua_invite() and nua_respond(),
+   respectively.
+
+   INIT -(C1)-> CALLING -(C3a+C4)-> READY
+   INIT -(S3c)-> COMPLETED -(S4)-> READY
+
+   Both client and server use saved Contact.
+
+   B sends BYE:
+   READY -(T2)-> TERMINATING -(T3)-> TERMINATED
+   A receives BYE:
+   READY -(T1)-> TERMINATED
+
+   See @page nua_call_model in nua.docs for more information
+*/
+
+static sip_contact_t *contact_for_b;
+
+int accept_call_with_contact(CONDITION_PARAMS)
+{
+  if (!(check_handle(ep, call, nh, SIP_500_INTERNAL_SERVER_ERROR)))
+    return 0;
+
+  save_event_in_list(ctx, event, ep, call);
+
+  switch (callstate(tags)) {
+  case nua_callstate_received:
+    RESPOND(ep, call, nh, SIP_180_RINGING, 
+	    TAG_IF(call->sdp, SOATAG_USER_SDP_STR(call->sdp)),
+	    SIPTAG_CONTACT(contact_for_b),
+	    TAG_END());
+    return 0;
+  case nua_callstate_early:
+    RESPOND(ep, call, nh, SIP_200_OK,
+	    TAG_IF(call->sdp, SOATAG_USER_SDP_STR(call->sdp)),
+	    SIPTAG_CONTACT(contact_for_b),
+	    TAG_END());
+    return 0;
+  case nua_callstate_ready:
+    return 1;
+  case nua_callstate_terminated:
+    if (call)
+      nua_handle_destroy(call->nh), call->nh = NULL;
+    return 1;
+  default:
+    return 0;
+  }
+}
+
+int test_basic_call_6(struct context *ctx)
+{
+  BEGIN();
+
+  struct endpoint *a = &ctx->a,  *b = &ctx->b;
+  struct call *a_call = a->call, *b_call = b->call;
+  struct event *e;
+  sip_route_t *r, rb[1];
+  sip_t *sip;
+
+  sip_contact_t ma[1], mb[1];
+
+  if (print_headings)
+    printf("TEST NUA-3.6: Basic call\n");
+
+  a_call->sdp = "m=audio 5008 RTP/AVP 8";
+  b_call->sdp = "m=audio 5010 RTP/AVP 0 8";
+
+  TEST_1(a_call->nh = nua_handle(a->nua, a_call, SIPTAG_TO(b->to), TAG_END()));
+
+  TEST_1(!nua_handle_has_active_call(a_call->nh));
+  TEST_1(!nua_handle_has_call_on_hold(a_call->nh));
+
+  *ma = *a->contact;
+  ma->m_display = "Alice B.";
+  ma->m_url->url_user = "a++a";
+
+  *mb = *b->contact;
+  mb->m_display = "Bob A.";
+  mb->m_url->url_user = "b++b";
+
+  contact_for_b = mb;
+  
+  sip_route_init(rb)->r_url[0] = b->contact->m_url[0];
+  rb->r_url->url_user = "bob+0";
+  url_param_add(nua_handle_home(a_call->nh), rb->r_url, "lr");
+  
+  INVITE(a, a_call, a_call->nh,
+	 NUTAG_URL("sip:bob at example.org"), /* Expanded by proxy */
+	 SIPTAG_ROUTE_STR("B2 <sip:bob+2 at example.org>;bar=foo"), /* Last in list */
+	 NUTAG_INITIAL_ROUTE(ctx->lr), /* Removed by proxy (if any) */
+	 NUTAG_INITIAL_ROUTE(rb), /* Used to route request to b (not removed)  */
+	 NUTAG_INITIAL_ROUTE_STR("B1 <sip:bob+1 at example.org;lr>;foo=bar"), /* Next in list */
+	 SOATAG_USER_SDP_STR(a_call->sdp),
+	 SIPTAG_CONTACT(ma),
+	 TAG_END());
+
+  run_ab_until(ctx, -1, until_ready, -1, accept_call_with_contact);
+
+  TEST_1(nua_handle_has_active_call(a_call->nh));
+  TEST_1(!nua_handle_has_call_on_hold(a_call->nh));
+
+  TEST_1(nua_handle_has_active_call(b_call->nh));
+  TEST_1(!nua_handle_has_call_on_hold(b_call->nh));
+
+  /* Client transitions:
+     INIT -(C1)-> CALLING: nua_invite(), nua_i_state
+     CALLING -(C2)-> PROCEEDING: nua_r_invite, nua_i_state
+     PROCEEDING -(C3+C4)-> READY: nua_r_invite, nua_i_state
+  */
+  TEST_1(e = a->events->head); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_calling); /* CALLING */
+  TEST_1(is_offer_sent(e->data->e_tags));
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_r_invite);
+  TEST(e->data->e_status, 180);
+  TEST_1(sip = sip_object(e->data->e_msg));
+  TEST_1(sip->sip_payload);
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_proceeding); /* PROCEEDING */
+  TEST_1(is_answer_recv(e->data->e_tags));
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_r_invite);
+  TEST(e->data->e_status, 200);
+  TEST_1(sip = sip_object(e->data->e_msg));
+  TEST_1(sip->sip_payload);
+  TEST_1(sip->sip_contact);
+  TEST_S(sip->sip_contact->m_display, "Bob A.");
+  TEST_S(sip->sip_contact->m_url->url_user, "b++b");
+  /* Test that B uses application-specific contact */
+  TEST_1(sip->sip_contact->m_url->url_user);
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_ready); /* READY */
+  TEST_1(!e->next);
+  free_events_in_list(ctx, a->events);
+
+  /*
+   Server transitions:
+   INIT -(S1)-> RECEIVED: nua_i_invite, nua_i_state
+   RECEIVED -(S2a)-> EARLY: nua_respond(), nua_i_state
+   EARLY -(S3b)-> COMPLETED: nua_respond(), nua_i_state
+   COMPLETED -(S4)-> READY: nua_i_ack, nua_i_state
+  */
+  TEST_1(e = b->events->head); TEST_E(e->data->e_event, nua_i_invite);
+  TEST(e->data->e_status, 100);
+  TEST_1(sip = sip_object(e->data->e_msg));
+  TEST_1(sip->sip_contact);
+  TEST_S(sip->sip_contact->m_display, "Alice B.");
+  TEST_S(sip->sip_contact->m_url->url_user, "a++a");
+  TEST_1(r = sip->sip_route);
+  TEST_S(r->r_url->url_user, "bob+0");
+  TEST_1(r = r->r_next);
+  TEST_S(r->r_url->url_user, "bob+1");
+  TEST_1(r = r->r_next);
+  TEST_S(r->r_url->url_user, "bob+2");
+  TEST_1(!r->r_next);
+
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_received); /* RECEIVED */
+  TEST_1(is_offer_recv(e->data->e_tags));
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_early); /* EARLY */
+  TEST_1(is_answer_sent(e->data->e_tags));
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_completed); /* COMPLETED */
+  TEST_1(is_answer_sent(e->data->e_tags));
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_ack);
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_ready); /* READY */
+  TEST_1(!e->next);
+  free_events_in_list(ctx, b->events);
+
+  /* re-INVITE */
+  INVITE(a, a_call, a_call->nh, TAG_END());
+  run_ab_until(ctx, -1, until_ready, -1, until_ready);
+
+  TEST_1(e = a->events->head); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_calling); /* CALLING */
+  TEST_1(is_offer_sent(e->data->e_tags));
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_r_invite);
+  TEST(e->data->e_status, 200);
+  TEST_1(sip = sip_object(e->data->e_msg));
+  TEST_1(sip->sip_contact);
+  TEST_S(sip->sip_contact->m_display, "Bob A.");
+  TEST_S(sip->sip_contact->m_url->url_user, "b++b");
+  /* Test that B uses application-specific contact */
+  TEST_1(sip->sip_contact->m_url->url_user);
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_ready); /* READY */
+  TEST_1(!e->next);
+  free_events_in_list(ctx, a->events);
+  
+  TEST_1(e = b->events->head); TEST_E(e->data->e_event, nua_i_invite);
+  TEST(e->data->e_status, 200);
+  TEST_1(sip = sip_object(e->data->e_msg));
+  TEST_1(sip->sip_contact);
+  TEST_S(sip->sip_contact->m_display, "Alice B.");
+  TEST_S(sip->sip_contact->m_url->url_user, "a++a");
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_completed); /* COMPLETED */
+  TEST_1(is_answer_sent(e->data->e_tags));
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_ack);
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_ready); /* READY */
+  TEST_1(!e->next);
+  free_events_in_list(ctx, b->events);
+
+  BYE(b, b_call, b_call->nh, TAG_END());
+  run_ab_until(ctx, -1, until_terminated, -1, until_terminated);
+
+  /* B transitions:
+   READY --(T2)--> TERMINATING: nua_bye()
+   TERMINATING --(T3)--> TERMINATED: nua_r_bye, nua_i_state
+  */
+  TEST_1(e = b->events->head);  TEST_E(e->data->e_event, nua_r_bye);
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_terminated); /* TERMINATED */
+  TEST_1(!e->next);
+  free_events_in_list(ctx, b->events);
+
+  TEST_1(!nua_handle_has_active_call(b_call->nh));
+
+  /* A transitions:
+     READY -(T1)-> TERMINATED: nua_i_bye, nua_i_state
+  */
+  TEST_1(e = a->events->head); TEST_E(e->data->e_event, nua_i_bye);
+  TEST(e->data->e_status, 200);
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_terminated); /* TERMINATED */
+  TEST_1(!e->next);
+  free_events_in_list(ctx, a->events);
+
+  TEST_1(!nua_handle_has_active_call(a_call->nh));
+
+  nua_handle_destroy(a_call->nh), a_call->nh = NULL;
+  nua_handle_destroy(b_call->nh), b_call->nh = NULL;
+
+  if (print_headings)
+    printf("TEST NUA-3.6: PASSED\n");
+
+  END();
+}
+
+/* Terminate call with 408:
+
+   A			B
+   |-------INVITE------>|
+   |<----100 Trying-----|
+   |			|
+   |<----180 Ringing----|
+   |			|
+   |<------200 OK-------|
+   |--------ACK-------->|
+   |			|
+   |--------INFO------->|
+   |<--------408--------|
+   |			|
+   |<-------BYE---------|
+   |--------487-------->|
+
+   Client transitions:
+   INIT -(C1)-> CALLING -(C2a)-> PROCEEDING -(C3+C4)-> READY
+   Server transitions:
+   INIT -(S1)-> RECEIVED -(S2a)-> EARLY -(S3b)-> COMPLETED -(S4)-> READY
+
+   A send INFO:
+   READY -(T1)-> TERMINATED
+
+   B sends BYE:
+   READY -(T2)-> TERMINATING -(T3)-> TERMINATED
+
+   See @page nua_call_model in nua.docs for more information
+*/
+int reject_method(CONDITION_PARAMS);
+int reject_info(CONDITION_PARAMS);
+
+int test_basic_call_7(struct context *ctx)
+{
+  BEGIN();
+
+  struct endpoint *a = &ctx->a,  *b = &ctx->b;
+  struct call *a_call = a->call, *b_call = b->call;
+  struct event *e;
+  sip_t *sip;
+  sip_replaces_t *repa, *repb;
+  nua_handle_t *nh;
+
+  if (print_headings)
+    printf("TEST NUA-3.7.1: Release dialog with error response (RFC 5057)\n");
+
+  a_call->sdp = "m=audio 5008 RTP/AVP 8";
+  b_call->sdp = "m=audio 5010 RTP/AVP 0 8";
+
+  TEST_1(a_call->nh = nua_handle(a->nua, a_call, SIPTAG_TO(b->to), TAG_END()));
+
+  TEST_1(!nua_handle_has_active_call(a_call->nh));
+  TEST_1(!nua_handle_has_call_on_hold(a_call->nh));
+
+  INVITE(a, a_call, a_call->nh,
+	 TAG_IF(!ctx->proxy_tests, NUTAG_URL(b->contact->m_url)),
+	 SOATAG_USER_SDP_STR(a_call->sdp),
+	 TAG_END());
+
+  run_ab_until(ctx, -1, until_ready, -1, accept_call_with_early_sdp);
+
+  TEST_1(nua_handle_has_active_call(a_call->nh));
+  TEST_1(!nua_handle_has_call_on_hold(a_call->nh));
+
+  TEST_1(nua_handle_has_active_call(b_call->nh));
+  TEST_1(!nua_handle_has_call_on_hold(b_call->nh));
+
+  TEST_1(repa = nua_handle_make_replaces(a_call->nh, nua_handle_home(a_call->nh), 0));
+  TEST_1(repb = nua_handle_make_replaces(b_call->nh, nua_handle_home(b_call->nh), 0));
+
+  TEST_S(repa->rp_call_id, repb->rp_call_id);
+
+  TEST_1(!nua_handle_by_replaces(a->nua, repa));
+  TEST_1(!nua_handle_by_replaces(b->nua, repb));
+
+  TEST_1(nh = nua_handle_by_replaces(a->nua, repb));
+  TEST_P(nh, a_call->nh);
+  nua_handle_unref(nh);
+
+  TEST_1(nh = nua_handle_by_replaces(b->nua, repa));
+  TEST_P(nh, b_call->nh);
+  nua_handle_unref(nh);
+
+  /* Client transitions:
+     INIT -(C1)-> CALLING: nua_invite(), nua_i_state
+     CALLING -(C2)-> PROCEEDING: nua_r_invite, nua_i_state
+     PROCEEDING -(C3+C4)-> READY: nua_r_invite, nua_i_state
+  */
+  TEST_1(e = a->events->head); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_calling); /* CALLING */
+  TEST_1(is_offer_sent(e->data->e_tags));
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_r_invite);
+  TEST(e->data->e_status, 180);
+  TEST_1(sip = sip_object(e->data->e_msg));
+  TEST_S(sip->sip_call_id->i_id, repb->rp_call_id);
+  TEST_S(sip->sip_from->a_tag, repb->rp_to_tag);
+  TEST_S(sip->sip_to->a_tag, repb->rp_from_tag);
+  TEST_1(sip->sip_payload);
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_proceeding); /* PROCEEDING */
+  TEST_1(is_answer_recv(e->data->e_tags));
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_r_invite);
+  TEST(e->data->e_status, 200);
+  TEST_1(sip = sip_object(e->data->e_msg));
+  TEST_1(sip->sip_payload);
+  TEST_1(sip->sip_contact);
+  TEST_S(sip->sip_contact->m_display, "Bob");
+  TEST_S(sip->sip_contact->m_url->url_user, "b+b");
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_ready); /* READY */
+  TEST_1(!e->next);
+  free_events_in_list(ctx, a->events);
+
+  /*
+   Server transitions:
+   INIT -(S1)-> RECEIVED: nua_i_invite, nua_i_state
+   RECEIVED -(S2a)-> EARLY: nua_respond(), nua_i_state
+   EARLY -(S3b)-> COMPLETED: nua_respond(), nua_i_state
+   COMPLETED -(S4)-> READY: nua_i_ack, nua_i_state
+  */
+  TEST_1(e = b->events->head); TEST_E(e->data->e_event, nua_i_invite);
+  TEST(e->data->e_status, 100);
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_received); /* RECEIVED */
+  TEST_1(is_offer_recv(e->data->e_tags));
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_early); /* EARLY */
+  TEST_1(is_answer_sent(e->data->e_tags));
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_completed); /* COMPLETED */
+  TEST_1(is_answer_sent(e->data->e_tags));
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_ack);
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_ready); /* READY */
+  TEST_1(!e->next);
+  free_events_in_list(ctx, b->events);
+
+  /* Make B to process HUMPPA at application level */
+  nua_set_hparams(b_call->nh, NUTAG_APPL_METHOD("HUMPPA"), 
+		  NUTAG_ALLOW("HUMPPA"),
+		  TAG_END());
+  run_b_until(ctx, nua_r_set_params, until_final_response);
+
+  METHOD(a, a_call, a_call->nh, NUTAG_METHOD("HUMPPA"), TAG_END());
+
+  run_ab_until(ctx, -1, until_terminated, -1, reject_method);
+
+  TEST_1(e = a->events->head);  TEST_E(e->data->e_event, nua_r_method);
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_terminated); /* TERMINATED */
+  TEST_1(!e->next);
+  free_events_in_list(ctx, a->events);
+
+  TEST_1(!nua_handle_has_active_call(a_call->nh));
+
+  TEST_1(e = b->events->head); TEST_E(e->data->e_event, nua_i_method);
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_terminated); /* TERMINATED */
+  TEST_1(!e->next);
+  free_events_in_list(ctx, b->events);
+
+  TEST_1(!nua_handle_has_active_call(b_call->nh));
+
+  nua_handle_destroy(a_call->nh), a_call->nh = NULL;
+  nua_handle_destroy(b_call->nh), b_call->nh = NULL;
+
+  if (print_headings)
+    printf("TEST NUA-3.7.1: PASSED\n");
+
+  if (print_headings)
+    printf("TEST NUA-3.7.2: Release dialog usage with error response (RFC 5057)\n");
+
+  TEST_1(a_call->nh = nua_handle(a->nua, a_call, SIPTAG_TO(b->to), TAG_END()));
+
+  TEST_1(!nua_handle_has_active_call(a_call->nh));
+  TEST_1(!nua_handle_has_call_on_hold(a_call->nh));
+
+  INVITE(a, a_call, a_call->nh,
+	 TAG_IF(!ctx->proxy_tests, NUTAG_URL(b->contact->m_url)),
+	 SOATAG_USER_SDP_STR(a_call->sdp),
+	 TAG_END());
+
+  run_ab_until(ctx, -1, until_ready, -1, accept_call_with_early_sdp);
+
+  TEST_1(nua_handle_has_active_call(a_call->nh));
+  TEST_1(!nua_handle_has_call_on_hold(a_call->nh));
+
+  TEST_1(nua_handle_has_active_call(b_call->nh));
+  TEST_1(!nua_handle_has_call_on_hold(b_call->nh));
+
+  TEST_1(repa = nua_handle_make_replaces(a_call->nh, nua_handle_home(a_call->nh), 0));
+  TEST_1(repb = nua_handle_make_replaces(b_call->nh, nua_handle_home(b_call->nh), 0));
+
+  TEST_S(repa->rp_call_id, repb->rp_call_id);
+
+  TEST_1(!nua_handle_by_replaces(a->nua, repa));
+  TEST_1(!nua_handle_by_replaces(b->nua, repb));
+
+  TEST_1(nh = nua_handle_by_replaces(a->nua, repb));
+  TEST_P(nh, a_call->nh);
+  nua_handle_unref(nh);
+
+  TEST_1(nh = nua_handle_by_replaces(b->nua, repa));
+  TEST_P(nh, b_call->nh);
+  nua_handle_unref(nh);
+
+  /* Client transitions:
+     INIT -(C1)-> CALLING: nua_invite(), nua_i_state
+     CALLING -(C2)-> PROCEEDING: nua_r_invite, nua_i_state
+     PROCEEDING -(C3+C4)-> READY: nua_r_invite, nua_i_state
+  */
+  TEST_1(e = a->events->head); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_calling); /* CALLING */
+  TEST_1(is_offer_sent(e->data->e_tags));
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_r_invite);
+  TEST(e->data->e_status, 180);
+  TEST_1(sip = sip_object(e->data->e_msg));
+  TEST_S(sip->sip_call_id->i_id, repb->rp_call_id);
+  TEST_S(sip->sip_from->a_tag, repb->rp_to_tag);
+  TEST_S(sip->sip_to->a_tag, repb->rp_from_tag);
+  TEST_1(sip->sip_payload);
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_proceeding); /* PROCEEDING */
+  TEST_1(is_answer_recv(e->data->e_tags));
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_r_invite);
+  TEST(e->data->e_status, 200);
+  TEST_1(sip = sip_object(e->data->e_msg));
+  TEST_1(sip->sip_payload);
+  TEST_1(sip->sip_contact);
+  TEST_S(sip->sip_contact->m_display, "Bob");
+  TEST_S(sip->sip_contact->m_url->url_user, "b+b");
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_ready); /* READY */
+  TEST_1(!e->next);
+  free_events_in_list(ctx, a->events);
+
+  /*
+   Server transitions:
+   INIT -(S1)-> RECEIVED: nua_i_invite, nua_i_state
+   RECEIVED -(S2a)-> EARLY: nua_respond(), nua_i_state
+   EARLY -(S3b)-> COMPLETED: nua_respond(), nua_i_state
+   COMPLETED -(S4)-> READY: nua_i_ack, nua_i_state
+  */
+  TEST_1(e = b->events->head); TEST_E(e->data->e_event, nua_i_invite);
+  TEST(e->data->e_status, 100);
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_received); /* RECEIVED */
+  TEST_1(is_offer_recv(e->data->e_tags));
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_early); /* EARLY */
+  TEST_1(is_answer_sent(e->data->e_tags));
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_completed); /* COMPLETED */
+  TEST_1(is_answer_sent(e->data->e_tags));
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_ack);
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_ready); /* READY */
+  TEST_1(!e->next);
+  free_events_in_list(ctx, b->events);
+
+  /* Let A allow INFO  */
+  nua_set_params(a->nua, NUTAG_ALLOW("INFO"), TAG_END());
+  /* Make B to process INFO at application level */
+  nua_set_hparams(b_call->nh, NUTAG_APPL_METHOD("INFO"), 
+		  NUTAG_ALLOW("INFO"),
+		  TAG_END());
+  run_ab_until(ctx, nua_r_set_params, NULL, nua_r_set_params, NULL);
+
+  INFO(a, a_call, a_call->nh, TAG_END());
+
+  run_ab_until(ctx, -1, until_terminated, -1, reject_info);
+
+  TEST_1(e = a->events->head);  TEST_E(e->data->e_event, nua_r_info);
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_terminated); /* TERMINATED */
+  TEST_1(!e->next);
+  free_events_in_list(ctx, a->events);
+
+  TEST_1(!nua_handle_has_active_call(a_call->nh));
+
+  TEST_1(e = b->events->head); TEST_E(e->data->e_event, nua_i_info);
+  TEST_1(!e->next);
+  free_events_in_list(ctx, b->events);
+
+  TEST_1(nua_handle_has_active_call(b_call->nh));
+
+  INFO(b, b_call, b_call->nh, TAG_END());
+  run_b_until(ctx, -1, until_terminated);
+
+  /* B transitions:
+   READY --(T2)--> TERMINATING: nua_bye()
+   TERMINATING --(T3)--> TERMINATED: nua_r_bye, nua_i_state
+  */
+  TEST_1(e = b->events->head);  TEST_E(e->data->e_event, nua_r_info);
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_terminated); /* TERMINATED */
+  TEST_1(!e->next);
+  free_events_in_list(ctx, b->events);
+
+  TEST_1(!nua_handle_has_active_call(b_call->nh));
+
+  nua_handle_destroy(a_call->nh), a_call->nh = NULL;
+  nua_handle_destroy(b_call->nh), b_call->nh = NULL;
+
+  if (print_headings)
+    printf("TEST NUA-3.7.2: PASSED\n");
+
+  END();
+}
+
+int reject_method(CONDITION_PARAMS)
+{
+  msg_t *current = nua_current_request(nua);
+
+  if (!(check_handle(ep, call, nh, SIP_500_INTERNAL_SERVER_ERROR)))
+    return 0;
+
+  save_event_in_list(ctx, event, ep, call);
+
+  if (event == nua_i_method) {
+    RESPOND(ep, call, nh, 
+	    SIP_604_DOES_NOT_EXIST_ANYWHERE,
+	    NUTAG_WITH(current),
+	    TAG_END());
+    return 1;
+  }
+  return 0;
+}
+
+int reject_info(CONDITION_PARAMS)
+{
+  msg_t *current = nua_current_request(nua);
+
+  if (!(check_handle(ep, call, nh, SIP_500_INTERNAL_SERVER_ERROR)))
+    return 0;
+
+  save_event_in_list(ctx, event, ep, call);
+
+  if (event == nua_i_info) {
+    RESPOND(ep, call, nh, 
+	    SIP_480_TEMPORARILY_UNAVAILABLE,
+	    NUTAG_WITH(current),
+	    TAG_END());
+    return 1;
+  }
+  return 0;
+}
+
+
+int test_basic_call(struct context *ctx)
+{
+  return 0
+    || test_basic_call_1(ctx)
+    || test_basic_call_2(ctx)
+    || test_basic_call_3(ctx)
+    || test_basic_call_4(ctx)
+    || test_basic_call_5(ctx)
+    || test_basic_call_6(ctx)
+    || test_basic_call_7(ctx)
+    || test_video_call_1(ctx)
+    ;
+}

Added: freeswitch/trunk/libs/sofia-sip/tests/test_call_hold.c
==============================================================================
--- (empty file)
+++ freeswitch/trunk/libs/sofia-sip/tests/test_call_hold.c	Wed May 14 15:10:54 2008
@@ -0,0 +1,1170 @@
+/*
+ * This file is part of the Sofia-SIP package
+ *
+ * Copyright (C) 2005 Nokia Corporation.
+ *
+ * Contact: Pekka Pessi <pekka.pessi at nokia.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA
+ *
+ */
+
+/**@CFILE test_call_hold.c
+ * @brief Test re-INVITE, call hold, un-hold.
+ *
+ * @author Pekka Pessi <Pekka.Pessi at nokia.com>
+ * @author Martti Mela <Martti Mela at nokia.com>
+ *
+ * @date Created: Wed Aug 17 12:12:12 EEST 2005 ppessi
+ */
+
+#include "config.h"
+
+#include "test_nua.h"
+#include <sofia-sip/su_tag_class.h>
+
+#if HAVE_FUNC
+#elif HAVE_FUNCTION
+#define __func__ __FUNCTION__
+#else
+#define __func__ "test_call_hold"
+#endif
+
+int complete_call(CONDITION_PARAMS);
+int until_complete(CONDITION_PARAMS);
+int invite_responded(CONDITION_PARAMS);
+static size_t remove_first_ack(void *_once, void *message, size_t len);
+
+/* ======================================================================== */
+/* test_call_hold message sequence looks like this:
+
+ A                    B
+ |                    |
+ |-------INVITE------>|
+ |<----100 Trying-----|
+ |                    |
+ |<----180 Ringing----|
+ |                    |
+ |<--------200--------|
+ |---------ACK------->|
+ :                    :
+ |--INVITE(sendonly)->|
+ |<---200(recvonly)---|
+ |---------ACK------->|
+ :                    :
+ |<-INVITE(inactive)--|
+ |----200(inactive)-->|
+ |<--------ACK--------|
+ :                    :
+ |--INVITE(recvonly)->|
+ |<---200(sendonly)---|
+ |---------ACK------->|
+ :                    :
+ |<-INVITE(sendrecv)--|
+ |----200(sendrecv)-->|
+ |<--------ACK--------|
+ :                    :
+ |--------INFO------->|
+ |<--------200--------|
+ :                    :
+ |---------BYE------->|
+ |<--------200--------|
+*/
+
+int test_call_hold(struct context *ctx)
+{
+  BEGIN();
+
+  struct endpoint *a = &ctx->a, *b = &ctx->b;
+  struct call *a_call = a->call, *b_call = b->call;
+  struct event *e;
+  sip_t *sip;
+  int zero = 0;
+  struct nat_filter *f;
+
+  a_call->sdp =
+    "m=audio 5008 RTP/AVP 0 8\n"
+    "m=video 6008 RTP/AVP 30\n";
+  b_call->sdp =
+    "m=audio 5010 RTP/AVP 8\n"
+    "a=rtcp:5011\n"
+    "m=video 6010 RTP/AVP 30\n"
+    "a=rtcp:6011\n";
+
+  TEST_1(a_call->nh = nua_handle(a->nua, a_call, SIPTAG_TO(b->to), TAG_END()));
+  INVITE(a, a_call, a_call->nh,
+	 TAG_IF(!ctx->proxy_tests, NUTAG_URL(b->contact->m_url)),
+	 SOATAG_USER_SDP_STR(a_call->sdp),
+	 TAG_END());
+
+  run_ab_until(ctx, -1, until_ready, -1, accept_call);
+
+  /*
+    Client transitions:
+    INIT -(C1)-> CALLING: nua_invite(), nua_i_state
+    CALLING -(C2)-> PROCEEDING: nua_r_invite, nua_i_state
+    PROCEEDING -(C3+C4)-> READY: nua_r_invite, nua_i_state
+  */
+  TEST_1(e = a->events->head); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_calling); /* CALLING */
+  TEST_1(is_offer_sent(e->data->e_tags));
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_r_invite);
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_proceeding); /* PROCEEDING */
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_r_invite);
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_ready); /* READY */
+  TEST_1(is_answer_recv(e->data->e_tags));
+  TEST(audio_activity(e->data->e_tags), SOA_ACTIVE_SENDRECV);
+  TEST_1(!e->next);
+  free_events_in_list(ctx, a->events);
+
+  TEST_1(nua_handle_has_active_call(a_call->nh));
+  TEST_1(!nua_handle_has_call_on_hold(a_call->nh));
+
+  /*
+   Server transitions:
+   INIT -(S1)-> RECEIVED: nua_i_invite, nua_i_state
+   RECEIVED -(S2a)-> EARLY: nua_respond(), nua_i_state
+   EARLY -(S3b)-> COMPLETED: nua_respond(), nua_i_state
+   COMPLETED -(S4)-> READY: nua_i_ack, nua_i_state
+  */
+  TEST_1(e = b->events->head); TEST_E(e->data->e_event, nua_i_invite);
+  TEST(e->data->e_status, 100);
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_received); /* RECEIVED */
+  TEST_1(is_offer_recv(e->data->e_tags));
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_early); /* EARLY */
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_completed); /* COMPLETED */
+  TEST_1(is_answer_sent(e->data->e_tags));
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_ack);
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_ready); /* READY */
+  TEST(audio_activity(e->data->e_tags), SOA_ACTIVE_SENDRECV);
+  TEST_1(!e->next);
+
+  TEST_1(nua_handle_has_active_call(b_call->nh));
+  TEST_1(!nua_handle_has_call_on_hold(b_call->nh));
+
+  free_events_in_list(ctx, b->events);
+
+  /*
+ :                    :
+ |--INVITE(sendonly)->|
+ |<---200(recvonly)---|
+ |---------ACK------->|
+ :                    :
+  */
+
+  if (print_headings)
+    printf("TEST NUA-7.1: put B on hold\n");
+
+  /* Put B on hold */
+  INVITE(a, a_call, a_call->nh, SOATAG_HOLD("audio"),
+	 SIPTAG_SUBJECT_STR("hold b"),
+	 TAG_END());
+  run_ab_until(ctx, -1, until_ready, -1, until_ready);
+
+  /* Client transitions:
+     READY -(C1)-> CALLING: nua_invite(), nua_i_state
+     CALLING -(C3a+C4)-> READY: nua_r_invite, nua_i_state
+  */
+  TEST_1(e = a->events->head); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_calling); /* CALLING */
+  TEST_1(is_offer_sent(e->data->e_tags));
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_r_invite);
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_ready); /* READY */
+  TEST_1(is_answer_recv(e->data->e_tags));
+  TEST(audio_activity(e->data->e_tags), SOA_ACTIVE_SENDONLY);
+  TEST_1(!e->next);
+
+  TEST_1(nua_handle_has_active_call(a_call->nh));
+  TEST_1(nua_handle_has_call_on_hold(a_call->nh));
+
+  free_events_in_list(ctx, a->events);
+
+  /*
+   Server transitions:
+   READY -(S3a)-> COMPLETED: nua_i_invite, <auto-answer>, nua_i_state
+   COMPLETED -(S4)-> READY: nua_i_ack, nua_i_state
+  */
+  TEST_1(e = b->events->head); TEST_E(e->data->e_event, nua_i_invite);
+  TEST(e->data->e_status, 200);
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_completed); /* COMPLETED */
+  TEST_1(is_answer_sent(e->data->e_tags));
+  TEST(audio_activity(e->data->e_tags), SOA_ACTIVE_RECVONLY);
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_ack);
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_ready); /* READY */
+  TEST(audio_activity(e->data->e_tags), SOA_ACTIVE_RECVONLY);
+  TEST_1(!e->next);
+
+  TEST_1(nua_handle_has_active_call(b_call->nh));
+  TEST_1(!nua_handle_has_call_on_hold(b_call->nh));
+
+  free_events_in_list(ctx, b->events);
+
+  if (print_headings)
+    printf("TEST NUA-7.1: PASSED\n");
+
+  /* ------------------------------------------------------------------------ */
+  /*
+ :                    :
+ |<-INVITE(inactive)--|
+ |----200(inactive)-->|
+ |<--------ACK--------|
+ :                    :
+  */
+
+  if (print_headings)
+    printf("TEST NUA-7.2: put A on hold\n");
+
+  /* Put A on hold, too. */
+  INVITE(b, b_call, b_call->nh, SOATAG_HOLD("audio"),
+	 SIPTAG_SUBJECT_STR("hold a"),
+	 TAG_END());
+  run_ab_until(ctx, -1, until_ready, -1, until_ready);
+
+  /* Client transitions:
+     READY -(C1)-> CALLING: nua_invite(), nua_i_state
+     CALLING -(C3a+C4)-> READY: nua_r_invite, nua_i_state
+  */
+  TEST_1(e = b->events->head); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_calling); /* CALLING */
+  TEST_1(is_offer_sent(e->data->e_tags));
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_r_invite);
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_ready); /* READY */
+  TEST_1(is_answer_recv(e->data->e_tags));
+  TEST(audio_activity(e->data->e_tags), SOA_ACTIVE_INACTIVE);
+  TEST(video_activity(e->data->e_tags), SOA_ACTIVE_SENDRECV);
+  TEST_1(!e->next);
+
+  TEST_1(nua_handle_has_active_call(b_call->nh));
+  TEST_1(nua_handle_has_call_on_hold(b_call->nh));
+
+  free_events_in_list(ctx, b->events);
+
+  /*
+   Server transitions:
+   READY -(S3a)-> COMPLETED: nua_i_invite, <auto-answer>, nua_i_state
+   COMPLETED -(S4)-> READY: nua_i_ack, nua_i_state
+  */
+  TEST_1(e = a->events->head); TEST_E(e->data->e_event, nua_i_invite);
+  TEST(e->data->e_status, 200);
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_completed); /* COMPLETED */
+  TEST_1(is_answer_sent(e->data->e_tags));
+  TEST(audio_activity(e->data->e_tags), SOA_ACTIVE_INACTIVE);
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_ack);
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_ready); /* READY */
+  TEST(audio_activity(e->data->e_tags), SOA_ACTIVE_INACTIVE);
+  TEST(video_activity(e->data->e_tags), SOA_ACTIVE_SENDRECV);
+  TEST_1(!e->next);
+
+  TEST_1(nua_handle_has_active_call(a_call->nh));
+  TEST_1(nua_handle_has_call_on_hold(a_call->nh));
+
+  free_events_in_list(ctx, a->events);
+
+  if (print_headings)
+    printf("TEST NUA-7.2: PASSED\n");
+
+  /* ------------------------------------------------------------------------ */
+  /*
+ :                    :
+ |--INVITE(recvonly)->|
+ |<---200(sendonly)---|
+ |---------ACK------->|
+ :                    :
+  */
+
+  if (print_headings)
+    printf("TEST NUA-7.3: resume B\n");
+
+  /* Resume B from hold */
+  INVITE(a, a_call, a_call->nh, SOATAG_HOLD(NULL),
+	 SIPTAG_SUBJECT_STR("resume b"),
+	 TAG_END());
+  run_ab_until(ctx, -1, until_ready, -1, until_ready);
+
+  /* Client transitions:
+     READY -(C1)-> CALLING: nua_invite(), nua_i_state
+     CALLING -(C3a+C4)-> READY: nua_r_invite, nua_i_state
+  */
+  TEST_1(e = a->events->head); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_calling); /* CALLING */
+  TEST_1(is_offer_sent(e->data->e_tags));
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_r_invite);
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_ready); /* READY */
+  TEST_1(is_answer_recv(e->data->e_tags));
+  TEST(audio_activity(e->data->e_tags), SOA_ACTIVE_RECVONLY);
+  TEST(video_activity(e->data->e_tags), SOA_ACTIVE_SENDRECV);
+  TEST_1(!e->next);
+  free_events_in_list(ctx, a->events);
+
+  TEST_1(nua_handle_has_active_call(a_call->nh));
+  TEST_1(!nua_handle_has_call_on_hold(a_call->nh));
+
+  /*
+   Server transitions:
+   READY -(S3a)-> COMPLETED: nua_i_invite, <auto-answer>, nua_i_state
+   COMPLETED -(S4)-> READY: nua_i_ack, nua_i_state
+  */
+  TEST_1(e = b->events->head); TEST_E(e->data->e_event, nua_i_invite);
+  TEST(e->data->e_status, 200);
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_completed); /* COMPLETED */
+  TEST_1(is_answer_sent(e->data->e_tags));
+  TEST(audio_activity(e->data->e_tags), SOA_ACTIVE_SENDONLY);
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_ack);
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_ready); /* READY */
+  TEST(audio_activity(e->data->e_tags), SOA_ACTIVE_SENDONLY);
+  TEST(video_activity(e->data->e_tags), SOA_ACTIVE_SENDRECV);
+  TEST_1(!e->next);
+
+  TEST_1(nua_handle_has_active_call(b_call->nh));
+  TEST_1(nua_handle_has_call_on_hold(b_call->nh));
+
+  free_events_in_list(ctx, b->events);
+
+  if (print_headings)
+    printf("TEST NUA-7.3: PASSED\n");
+
+  /* ------------------------------------------------------------------------ */
+  /*
+ :                    :
+ |<-INVITE(sendrecv)--|
+ |----200(sendrecv)-->|
+ |<--------ACK--------|
+ :                    :
+  */
+
+  if (print_headings)
+    printf("TEST NUA-7.4: resume A\n");
+
+  /* Resume A on hold, too. */
+  INVITE(b, b_call, b_call->nh, SOATAG_HOLD(""),
+	 SIPTAG_SUBJECT_STR("TEST NUA-7.4: resume A"),
+	 TAG_END());
+  run_ab_until(ctx, -1, until_ready, -1, until_ready);
+
+  /* Client transitions:
+     READY -(C1)-> CALLING: nua_invite(), nua_i_state
+     CALLING -(C3a+C4)-> READY: nua_r_invite, nua_i_state
+  */
+  TEST_1(e = b->events->head); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_calling); /* CALLING */
+  TEST_1(is_offer_sent(e->data->e_tags));
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_r_invite);
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_ready); /* READY */
+  TEST_1(is_answer_recv(e->data->e_tags));
+  TEST(audio_activity(e->data->e_tags), SOA_ACTIVE_SENDRECV);
+  TEST(video_activity(e->data->e_tags), SOA_ACTIVE_SENDRECV);
+  TEST_1(!e->next);
+  free_events_in_list(ctx, b->events);
+
+  TEST_1(nua_handle_has_active_call(a_call->nh));
+  TEST_1(!nua_handle_has_call_on_hold(a_call->nh));
+
+  /*
+   Server transitions:
+   READY -(S3a)-> COMPLETED: nua_i_invite, <auto-answer>, nua_i_state
+   COMPLETED -(S4)-> READY: nua_i_ack, nua_i_state
+  */
+  TEST_1(e = a->events->head); TEST_E(e->data->e_event, nua_i_invite);
+  TEST(e->data->e_status, 200);
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_completed); /* COMPLETED */
+  TEST_1(is_answer_sent(e->data->e_tags));
+  TEST(audio_activity(e->data->e_tags), SOA_ACTIVE_SENDRECV);
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_ack);
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_ready); /* READY */
+  TEST(audio_activity(e->data->e_tags), SOA_ACTIVE_SENDRECV);
+  TEST(video_activity(e->data->e_tags), SOA_ACTIVE_SENDRECV);
+  TEST_1(!e->next);
+
+  TEST_1(nua_handle_has_active_call(b_call->nh));
+  TEST_1(!nua_handle_has_call_on_hold(b_call->nh));
+
+  free_events_in_list(ctx, a->events);
+
+  if (print_headings)
+    printf("TEST NUA-7.4: PASSED\n");
+
+  /* ---------------------------------------------------------------------- */
+  /*
+ A                    B
+ |--------INFO------->|
+ |<--------200--------|
+   */
+  if (print_headings)
+    printf("TEST NUA-7.5: send INFO\n");
+
+  INFO(a, a_call, a_call->nh, TAG_END());
+  run_a_until(ctx, -1, save_until_final_response);
+  /* XXX - B should get a  nua_i_info event with 405 */
+
+  /* A sent INFO, receives 405 */
+  TEST_1(e = a->events->head);  TEST_E(e->data->e_event, nua_r_info);
+  TEST(e->data->e_status, 405);
+  TEST_1(!e->next);
+  free_events_in_list(ctx, a->events);
+
+#if 0				/* XXX */
+  /* B received INFO */
+  TEST_1(e = b->events->head);  TEST_E(e->data->e_event, nua_i_info);
+  TEST(e->data->e_status, 405);
+  TEST_1(!e->next);
+  free_events_in_list(ctx, b->events);
+#endif
+
+  /* Add INFO to allowed methods */
+  nua_set_hparams(b_call->nh, NUTAG_ALLOW("INFO, PUBLISH"), TAG_END());
+  run_b_until(ctx, nua_r_set_params, until_final_response);
+
+  INFO(a, a_call, a_call->nh, TAG_END());
+  run_ab_until(ctx, -1, save_until_final_response, -1, save_until_received);
+
+  /* A sent INFO, receives 200 */
+  TEST_1(e = a->events->head);  TEST_E(e->data->e_event, nua_r_info);
+  TEST(e->data->e_status, 200);
+  TEST_1(!e->next);
+  free_events_in_list(ctx, a->events);
+
+  /* B received INFO */
+  TEST_1(e = b->events->head);  TEST_E(e->data->e_event, nua_i_info);
+  TEST(e->data->e_status, 200);
+  TEST_1(!e->next);
+  free_events_in_list(ctx, b->events);
+
+  if (print_headings)
+    printf("TEST NUA-7.5: PASSED\n");
+
+  /* ------------------------------------------------------------------------ */
+  /*
+ :                    :
+ |<------INVITE-------|
+ |--------200-------->|
+ |<--------ACK--------|
+ :                    :
+  */
+
+  if (print_headings)
+    printf("TEST NUA-7.6.1: re-INVITE without auto-ack\n");
+
+  /* Turn off auto-ack */
+  nua_set_hparams(b_call->nh, NUTAG_AUTOACK(0), TAG_END());
+  run_b_until(ctx, nua_r_set_params, until_final_response);
+
+  INVITE(b, b_call, b_call->nh, SOATAG_HOLD(""),
+	 SIPTAG_SUBJECT_STR("TEST NUA-7.6: re-INVITE without auto-ack"),
+	 TAG_END());
+  run_ab_until(ctx, -1, until_complete, -1, complete_call);
+
+  /* Client transitions:
+     READY -(C1)-> CALLING: nua_invite(), nua_i_state
+     CALLING -(C3a)-> COMPLETING: nua_r_invite, nua_i_state
+     COMPLETING -(C4)-> READY: nua_ack(), nua_i_state
+  */
+  TEST_1(e = b->events->head); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_calling); /* CALLING */
+  TEST_1(is_offer_sent(e->data->e_tags));
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_r_invite);
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_completing); /* COMPLETING */
+  TEST_1(is_answer_recv(e->data->e_tags));
+  TEST(audio_activity(e->data->e_tags), SOA_ACTIVE_SENDRECV);
+  TEST(video_activity(e->data->e_tags), SOA_ACTIVE_SENDRECV);
+  TEST_1(!e->next);
+
+  free_events_in_list(ctx, b->events);
+
+  /*
+   Server transitions:
+   READY -(S3a)-> COMPLETED: nua_i_invite, <auto-answer>, nua_i_state
+   COMPLETED -(S4)-> READY: nua_i_ack, nua_i_state
+  */
+  TEST_1(e = a->events->head); TEST_E(e->data->e_event, nua_i_invite);
+  TEST(e->data->e_status, 200);
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_completed); /* COMPLETED */
+  TEST_1(is_answer_sent(e->data->e_tags));
+  TEST(audio_activity(e->data->e_tags), SOA_ACTIVE_SENDRECV);
+  TEST_1(!e->next);
+  free_events_in_list(ctx, a->events);
+
+  ACK(b, b_call, b_call->nh, TAG_END());
+
+  run_ab_until(ctx, -1, until_ready, -1, until_ready);
+
+  TEST_1(e = b->events->head); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_ready); /* READY */
+  TEST_1(!e->next);
+  free_events_in_list(ctx, b->events);
+  
+  TEST_1(e = a->events->head); TEST_E(e->data->e_event, nua_i_ack);
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_ready); /* READY */
+  TEST(audio_activity(e->data->e_tags), SOA_ACTIVE_SENDRECV);
+  TEST(video_activity(e->data->e_tags), SOA_ACTIVE_SENDRECV);
+  TEST_1(!e->next);
+  free_events_in_list(ctx, a->events);
+
+  if (print_headings)
+    printf("TEST NUA-7.6.1: PASSED\n");
+
+  /* ------------------------------------------------------------------------ */
+  /*
+ :                    :
+ |<------INVITE-------|
+ |--------200-------->|
+ |<--------ACK--------|
+ :                    :
+  */
+
+  if (ctx->proxy_tests && ctx->nat) {
+  if (print_headings)
+    printf("TEST NUA-7.6.2: almost overlapping re-INVITE\n");
+
+  /* Turn off auto-ack */
+  nua_set_hparams(b_call->nh, NUTAG_AUTOACK(0), TAG_END());
+  run_b_until(ctx, nua_r_set_params, until_final_response);
+
+  INVITE(b, b_call, b_call->nh, SOATAG_HOLD("#"),
+	 SIPTAG_SUBJECT_STR("TEST NUA-7.6.2: re-INVITE"),
+	 TAG_END());
+  run_ab_until(ctx, -1, until_complete, -1, complete_call);
+
+  /* Client transitions:
+     READY -(C1)-> CALLING: nua_invite(), nua_i_state
+     CALLING -(C3a)-> COMPLETING: nua_r_invite, nua_i_state
+     COMPLETING -(C4)-> READY: nua_ack(), nua_i_state
+  */
+  TEST_1(e = b->events->head); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_calling); /* CALLING */
+  TEST_1(is_offer_sent(e->data->e_tags));
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_r_invite);
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_completing); /* COMPLETING */
+  TEST_1(is_answer_recv(e->data->e_tags));
+  TEST(audio_activity(e->data->e_tags), SOA_ACTIVE_INACTIVE);
+  TEST(video_activity(e->data->e_tags), SOA_ACTIVE_INACTIVE);
+  TEST_1(!e->next);
+
+  free_events_in_list(ctx, b->events);
+
+  /*
+   Server transitions:
+   READY -(S3a)-> COMPLETED: nua_i_invite, <auto-answer>, nua_i_state
+   COMPLETED -(S4)-> READY: nua_i_ack, nua_i_state
+  */
+  TEST_1(e = a->events->head); TEST_E(e->data->e_event, nua_i_invite);
+  TEST(e->data->e_status, 200);
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_completed); /* COMPLETED */
+  TEST_1(is_answer_sent(e->data->e_tags));
+  TEST(audio_activity(e->data->e_tags), SOA_ACTIVE_INACTIVE);
+  TEST_1(!e->next);
+  free_events_in_list(ctx, a->events);
+
+  f = test_nat_add_filter(ctx->nat, remove_first_ack, &zero, nat_inbound);
+
+  ACK(b, b_call, b_call->nh, TAG_END());
+  INVITE(b, b_call, b_call->nh, SOATAG_HOLD("*"),
+	 SIPTAG_SUBJECT_STR("TEST NUA-7.6.2: almost overlapping re-INVITE"),
+	 NUTAG_AUTOACK(1),
+	 TAG_END());
+
+  run_ab_until(ctx, -1, until_ready, -1, invite_responded);
+
+  TEST_1(e = b->events->head); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_ready); /* READY */
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_calling); /* CALLING */
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_r_invite);
+  TEST(e->data->e_status, 100);
+  TEST_1(sip = sip_object(e->data->e_msg));
+  TEST_1(sip->sip_retry_after);
+  TEST_1(!e->next);
+  free_events_in_list(ctx, b->events);
+  
+  TEST_1(e = a->events->head); TEST_E(e->data->e_event, nua_i_ack);
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_ready); /* READY */
+  TEST(audio_activity(e->data->e_tags), SOA_ACTIVE_INACTIVE);
+  TEST(video_activity(e->data->e_tags), SOA_ACTIVE_INACTIVE);
+  TEST_1(!e->next);
+  free_events_in_list(ctx, a->events);
+
+  test_nat_remove_filter(ctx->nat, f);
+
+  if (print_headings)
+    printf("TEST NUA-7.6.2: PASSED\n");
+  }
+  
+
+  /* ---------------------------------------------------------------------- */
+  /*
+ A                    B
+ |---------BYE------->|
+ |<--------200--------|
+   */
+
+  if (print_headings)
+    printf("TEST NUA-7.6.3: terminate call\n");
+
+  BYE(a, a_call, a_call->nh, TAG_END());
+  run_ab_until(ctx, -1, until_terminated, -1, until_terminated);
+
+  /*
+   Transitions of A:
+   READY --(T2)--> TERMINATING: nua_bye()
+   TERMINATING --(T3)--> TERMINATED: nua_r_bye, nua_i_state
+  */
+  TEST_1(e = a->events->head); TEST_E(e->data->e_event, nua_r_bye);
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_terminated); /* TERMINATED */
+  TEST_1(!e->next);
+  free_events_in_list(ctx, a->events);
+
+  /* Transitions of B:
+     READY -(T1)-> TERMINATED: nua_i_bye, nua_i_state
+  */
+  TEST_1(e = b->events->head); TEST_E(e->data->e_event, nua_i_bye);
+  TEST(e->data->e_status, 200);
+  if (ctx->proxy_tests && ctx->nat) {
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_r_invite);
+  TEST(e->data->e_status, 481);
+  }
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_terminated); /* TERMINATED */
+  TEST_1(!e->next);
+  free_events_in_list(ctx, b->events);
+
+  if (print_headings)
+    printf("TEST NUA-7.6.3: PASSED\n");
+
+  nua_handle_destroy(a_call->nh), a_call->nh = NULL;
+  nua_handle_destroy(b_call->nh), b_call->nh = NULL;
+
+  END();
+}
+
+/*
+ INVITE without auto-ack
+ X
+ |                    |
+ |-------INVITE------>|
+ |<--------200--------|
+ |                    |
+ |---------ACK------->|
+*/
+int complete_call(CONDITION_PARAMS)
+{
+  if (!(check_handle(ep, call, nh, SIP_500_INTERNAL_SERVER_ERROR)))
+    return 0;
+
+  save_event_in_list(ctx, event, ep, call);
+
+  switch (callstate(tags)) {
+  case nua_callstate_completing:
+    return 1;
+  case nua_callstate_ready:
+    return 1;
+  case nua_callstate_terminated:
+    if (call)
+      nua_handle_destroy(call->nh), call->nh = NULL;
+    return 1;
+  default:
+    return 0;
+  }
+}
+
+/*
+ X      INVITE
+ |                    |
+ |-------INVITE------>|
+ |<--------200--------|
+*/
+int until_complete(CONDITION_PARAMS)
+{
+  if (!(check_handle(ep, call, nh, SIP_500_INTERNAL_SERVER_ERROR)))
+    return 0;
+
+  save_event_in_list(ctx, event, ep, call);
+
+  switch (callstate(tags)) {
+  case nua_callstate_completed:
+  case nua_callstate_ready:
+    return 1;
+  case nua_callstate_terminated:
+    if (call)
+      nua_handle_destroy(call->nh), call->nh = NULL;
+    return 1;
+  default:
+    return 0;
+  }
+}
+
+static size_t remove_first_ack(void *_once, void *message, size_t len)
+{
+  int *once = _once;
+  
+  if (*once)
+    return len;
+
+  if (strncmp("ACK ", message, 4) == 0) {
+    printf("FILTERING %.*s\n", strcspn(message, "\r\n"), (char *)message);
+    *once = 1;
+    return 0;
+  }
+
+  return len;
+}
+
+/* ======================================================================== */
+/* test_reinvite message sequence looks like this:
+
+ A                    B
+ |                    |
+ |-------INVITE------>|
+ |<----100 Trying-----|
+ |                    |
+ |<----180 Ringing----|
+ |                    |
+ |<--------200--------|
+ |---------ACK------->|
+ :                    :
+ |<----re-INVITE------|
+ |<-------BYE---------|
+ |--------200-------->|
+ |-----487-INVITE---->|
+ |<--------ACK--------|
+*/
+
+int accept_no_save(CONDITION_PARAMS);
+int ringing_until_terminated(CONDITION_PARAMS);
+int bye_when_ringing(CONDITION_PARAMS);
+
+int test_reinvite(struct context *ctx)
+{
+  BEGIN();
+
+  struct endpoint *a = &ctx->a, *b = &ctx->b;
+  struct call *a_call = a->call, *b_call = b->call;
+
+  if (print_headings)
+    printf("TEST NUA-7.7: Test re-INVITE and BYE\n");
+
+  a_call->sdp = "m=audio 5008 RTP/AVP 0 8\n";
+  b_call->sdp = "m=audio 5010 RTP/AVP 8\n";
+
+  TEST_1(a_call->nh = 
+	 nua_handle(a->nua, a_call, 
+		    SIPTAG_FROM_STR("Alice <sip:alice at example.com>"),
+		    SIPTAG_TO(b->to),
+		    NUTAG_AUTOANSWER(0),
+		    TAG_END()));
+  INVITE(a, a_call, a_call->nh,
+	 TAG_IF(!ctx->proxy_tests, NUTAG_URL(b->contact->m_url)),
+	 SOATAG_USER_SDP_STR(a_call->sdp),
+	 TAG_END());
+
+  run_ab_until(ctx, -1, accept_no_save, -1, accept_no_save);
+
+  TEST_1(nua_handle_has_active_call(a_call->nh));
+  TEST_1(nua_handle_has_active_call(b_call->nh));
+
+  free_events_in_list(ctx, a->events);
+  free_events_in_list(ctx, b->events);
+
+/*
+ A                    B
+ |<----re-INVITE------|
+ |<------CANCEL-------|
+ |<-------BYE---------|
+ |-----200-CANCEL---->|
+ |------200-BYE------>|
+ |-----487-INVITE---->|
+ |<--------ACK--------|
+*/
+
+  /* re-INVITE A, send BYE after receiving 180 */
+  INVITE(b, b_call, b_call->nh, 
+	 SIPTAG_SUBJECT_STR("re-INVITE"),
+	 TAG_END());
+  /* Run until both a and b has terminated their call */
+  run_ab_until(ctx, -1, ringing_until_terminated, -1, bye_when_ringing);
+  
+#if notyet
+  struct event *e;
+
+  /* XXX - check events later - now we are happy that calls get terminated  */
+  /* Client events:
+   READY -(C1)-> CALLING: nua_invite(), nua_i_state
+   CALLING --(C2)--> PROCEEDING: nua_r_invite, nua_i_state, nua_bye()
+   PROCEEDING--((C3a+C4)-> READY: nua_r_invite, nua_i_state
+   READY --(T2)--> TERMINATING: nua_bye()
+   TERMINATING --(T3)--> TERMINATED: nua_r_bye, nua_i_state
+  */
+  TEST_1(e = b->events->head); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_calling); /* CALLING */
+  TEST_1(is_offer_sent(e->data->e_tags));
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_r_invite);
+  TEST(e->data->e_status, 180);
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_terminating); /* READY */
+  /* Now we can receive events, in any possible order */
+  /* XXX */
+  TEST_1(e = e->next); 
+  
+  TEST_1(!nua_handle_has_active_call(a_call->nh));
+
+  /*
+   Server transitions:
+   READY --(T2)--> CTERMINATING: nua_bye()
+   TERMINATING --(T3)--> TERMINATED: nua_r_bye, nua_i_state
+   READY -(S3a)-> COMPLETED: nua_i_invite, <auto-answer>, nua_i_state
+   COMPLETED -(S4)-> READY: nua_i_ack, nua_i_state
+  */
+  TEST_1(e = b->events->head); TEST_E(e->data->e_event, nua_i_invite);
+  TEST(e->data->e_status, 200);
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_completed); /* COMPLETED */
+  TEST_1(is_answer_sent(e->data->e_tags));
+  TEST(audio_activity(e->data->e_tags), SOA_ACTIVE_RECVONLY);
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_ack);
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_ready); /* READY */
+  TEST(audio_activity(e->data->e_tags), SOA_ACTIVE_RECVONLY);
+  TEST_1(!e->next);
+
+  TEST_1(nua_handle_has_active_call(b_call->nh));
+  TEST_1(!nua_handle_has_call_on_hold(b_call->nh));
+
+#endif
+
+  if (print_headings)
+    printf("TEST NUA-7.7: PASSED\n");
+
+  free_events_in_list(ctx, a->events);
+  free_events_in_list(ctx, b->events);
+
+  nua_handle_destroy(a_call->nh), a_call->nh = NULL;
+  nua_handle_destroy(b_call->nh), b_call->nh = NULL;
+
+  END();
+}
+
+/*
+ Accept INVITE 
+ X
+ |                    |
+ |-------INVITE------>|
+ |<--------200--------|
+ |                    |
+ |---------ACK------->|
+*/
+int accept_no_save(CONDITION_PARAMS)
+{
+  if (!(check_handle(ep, call, nh, SIP_500_INTERNAL_SERVER_ERROR)))
+    return 0;
+
+  switch (callstate(tags)) {
+  case nua_callstate_received:
+    RESPOND(ep, call, nh, SIP_200_OK,
+	    TAG_IF(call->sdp, SOATAG_USER_SDP_STR(call->sdp)),
+	    TAG_END());
+    return 0;
+  case nua_callstate_ready:
+    return 1;
+  case nua_callstate_terminated:
+    if (call)
+      nua_handle_destroy(call->nh), call->nh = NULL;
+    return 1;
+  default:
+    return 0;
+  }
+}
+
+int ringing_until_terminated(CONDITION_PARAMS)
+{
+  if (!(check_handle(ep, call, nh, SIP_500_INTERNAL_SERVER_ERROR)))
+    return 0;
+
+  save_event_in_list(ctx, event, ep, call);
+
+  switch (callstate(tags)) {
+  case nua_callstate_received:
+    RESPOND(ep, call, nh, SIP_180_RINGING, TAG_END());
+    return 0;
+  case nua_callstate_ready:
+    return 0;
+  case nua_callstate_terminated:
+    if (call)
+      nua_handle_destroy(call->nh), call->nh = NULL;
+    return 1;
+  default:
+    return 0;
+  }
+}
+
+/* ======================================================================== */
+
+int accept_and_attempt_reinvite(CONDITION_PARAMS);
+int until_ready2(CONDITION_PARAMS);
+
+/* test_reinvite2 message sequence looks like this:
+
+ A                    B
+ |                    |
+ |-------INVITE------>|
+ |<----100 Trying-----|
+ |                    |
+ |<----180 Ringing----|
+ |                    |
+ |           /-INVITE-|
+ |           \---900->|
+ |                    |
+ |<--------200--------|
+ |---------ACK------->|
+ :                    :
+ :   queue INVITE     :
+ :                    :
+ |-----re-INVITE----->|
+ |<--------200--------|
+ |---------ACK------->|
+ |-----re-INVITE----->|
+ |<--------200--------|
+ |---------ACK------->|
+ :                    :
+ :        glare       :
+ :                    :
+ |-----re-INVITE----->|
+ |<----re-INVITE------|
+ |<--------491--------|
+ |---------491------->|
+ |---------ACK------->|
+ |<--------ACK--------|
+ :                    :
+ |---------BYE------->|
+ |<--------200--------|
+*/
+
+int test_reinvite2(struct context *ctx)
+{
+  BEGIN();
+
+  struct endpoint *a = &ctx->a, *b = &ctx->b;
+  struct call *a_call = a->call, *b_call = b->call;
+  struct event *e;
+
+  if (print_headings)
+    printf("TEST NUA-7.8.1: Test re-INVITE glare\n");
+
+  a_call->sdp = "m=audio 5008 RTP/AVP 0 8\n";
+  b_call->sdp = "m=audio 5010 RTP/AVP 8\n";
+
+  TEST_1(a_call->nh = 
+	 nua_handle(a->nua, a_call, 
+		    SIPTAG_FROM_STR("Alice <sip:alice at example.com>"),
+		    SIPTAG_TO(b->to),
+		    TAG_END()));
+  INVITE(a, a_call, a_call->nh,
+	 TAG_IF(!ctx->proxy_tests, NUTAG_URL(b->contact->m_url)),
+	 SOATAG_USER_SDP_STR(a_call->sdp),
+	 TAG_END());
+
+  run_ab_until(ctx, -1, until_ready, -1, accept_and_attempt_reinvite);
+
+  TEST_1(nua_handle_has_active_call(a_call->nh));
+  TEST_1(nua_handle_has_active_call(b_call->nh));
+
+  free_events_in_list(ctx, a->events);
+  free_events_in_list(ctx, b->events);
+
+  /* Check that we can queue INVITEs */
+  INVITE(a, a_call, a_call->nh, TAG_END());
+  INVITE(a, a_call, a_call->nh, TAG_END());
+
+  run_ab_until(ctx, -1, until_ready2, -1, until_ready2);
+
+  free_events_in_list(ctx, a->events);
+  free_events_in_list(ctx, b->events);
+
+  /* Check that INVITE glare works */
+  INVITE(a, a_call, a_call->nh, TAG_END());
+  INVITE(b, b_call, b_call->nh, TAG_END());
+
+  a->flags.n = 0, b->flags.n = 0;
+  run_ab_until(ctx, -1, until_ready2, -1, until_ready2);
+
+  free_events_in_list(ctx, a->events);
+  free_events_in_list(ctx, b->events);
+
+  if (print_headings)
+    printf("TEST NUA-7.8.1: PASSED\n");
+
+
+
+  /* ---------------------------------------------------------------------- */
+  /*
+ A                    B
+ |---------BYE------->|
+ |<--------200--------|
+   */
+
+  if (print_headings)
+    printf("TEST NUA-7.8.2: terminate call\n");
+
+  BYE(a, a_call, a_call->nh, TAG_END());
+  run_ab_until(ctx, -1, until_terminated, -1, until_terminated);
+
+  /*
+   Transitions of A:
+   READY --(T2)--> TERMINATING: nua_bye()
+   TERMINATING --(T3)--> TERMINATED: nua_r_bye, nua_i_state
+  */
+  TEST_1(e = a->events->head); TEST_E(e->data->e_event, nua_r_bye);
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_terminated); /* TERMINATED */
+  TEST_1(!e->next);
+  free_events_in_list(ctx, a->events);
+
+  /* Transitions of B:
+     READY -(T1)-> TERMINATED: nua_i_bye, nua_i_state
+  */
+  TEST_1(e = b->events->head); TEST_E(e->data->e_event, nua_i_bye);
+  TEST(e->data->e_status, 200);
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_terminated); /* TERMINATED */
+  TEST_1(!e->next);
+  free_events_in_list(ctx, b->events);
+
+  if (print_headings)
+    printf("TEST NUA-7.8.2: PASSED\n");
+
+  nua_handle_destroy(a_call->nh), a_call->nh = NULL;
+  nua_handle_destroy(b_call->nh), b_call->nh = NULL;
+
+  END();
+}
+
+int accept_and_attempt_reinvite(CONDITION_PARAMS)
+{
+  if (!(check_handle(ep, call, nh, SIP_500_INTERNAL_SERVER_ERROR)))
+    return 0;
+
+  save_event_in_list(ctx, event, ep, call);
+
+  if (event == nua_i_prack) {
+    INVITE(ep, call, nh, TAG_END());
+    RESPOND(ep, call, nh, SIP_200_OK,
+	    TAG_IF(call->sdp, SOATAG_USER_SDP_STR(call->sdp)),
+	    TAG_END());
+  }
+  else switch (callstate(tags)) {
+  case nua_callstate_received:
+    RESPOND(ep, call, nh, SIP_180_RINGING, 
+	    SIPTAG_REQUIRE_STR("100rel"),
+	    TAG_END());
+    return 0;
+  case nua_callstate_early:
+    return 0;
+  case nua_callstate_ready:
+    return 1;
+  case nua_callstate_terminated:
+    if (call)
+      nua_handle_destroy(call->nh), call->nh = NULL;
+    return 1;
+  default:
+    return 0;
+  }
+  return 0;
+}
+
+/*
+ X      INVITE
+ |                    |
+ |-------INVITE------>|
+ |<--------200--------|
+ |---------ACK------->|
+*/
+int until_ready2(CONDITION_PARAMS)
+{
+  if (!(check_handle(ep, call, nh, SIP_500_INTERNAL_SERVER_ERROR)))
+    return 0;
+
+  save_event_in_list(ctx, event, ep, call);
+
+  if (event == nua_r_invite && status == 491) {
+    if (ep == &ctx->a && ++ctx->b.flags.n >= 2) ctx->b.running = 0;
+    if (ep == &ctx->b && ++ctx->a.flags.n >= 2) ctx->a.running = 0;
+  }
+
+  switch (callstate(tags)) {
+  case nua_callstate_ready:
+    return ++ep->flags.n >= 2;
+  case nua_callstate_terminated:
+    if (call)
+      nua_handle_destroy(call->nh), call->nh = NULL;
+    return 1;
+  default:
+    return 0;
+  }
+}
+
+int invite_responded(CONDITION_PARAMS)
+{
+  if (!(check_handle(ep, call, nh, SIP_500_INTERNAL_SERVER_ERROR)))
+    return 0;
+
+  save_event_in_list(ctx, event, ep, call);
+
+  return event == nua_r_invite && status >= 100;
+}
+
+
+int test_reinvites(struct context *ctx)
+{
+  int retval = 0;
+
+  if (print_headings)
+    printf("TEST NUA-7: Test call hold and re-INVITEs\n");
+
+  retval = test_call_hold(ctx);
+  
+  if (retval == 0)
+    retval = test_reinvite(ctx);
+
+  if (retval == 0)
+    retval = test_reinvite2(ctx);
+
+  if (print_headings && retval == 0)
+    printf("TEST NUA-7: PASSED\n");
+
+  return retval;
+}

Added: freeswitch/trunk/libs/sofia-sip/tests/test_call_reject.c
==============================================================================
--- (empty file)
+++ freeswitch/trunk/libs/sofia-sip/tests/test_call_reject.c	Wed May 14 15:10:54 2008
@@ -0,0 +1,1689 @@
+/*
+ * This file is part of the Sofia-SIP package
+ *
+ * Copyright (C) 2005 Nokia Corporation.
+ *
+ * Contact: Pekka Pessi <pekka.pessi at nokia.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA
+ *
+ */
+
+/**@CFILE test_call_reject.c
+ * @brief NUA-4 tests: call reject cases
+ *
+ * @author Pekka Pessi <Pekka.Pessi at nokia.com>
+ * @author Martti Mela <Martti Mela at nokia.com>
+ *
+ * @date Created: Wed Aug 17 12:12:12 EEST 2005 ppessi
+ */
+
+#include "config.h"
+
+#include "test_nua.h"
+#include <sofia-sip/su_tag_class.h>
+
+#if HAVE_FUNC
+#elif HAVE_FUNCTION
+#define __func__ __FUNCTION__
+#else
+#define __func__ "test_reject"
+#endif
+
+/* ======================================================================== */
+/*
+ A      reject-1      B
+ |                    |
+ |-------INVITE------>|
+ |<----100 Trying-----|
+ |                    |
+ |<--------486--------|
+ |---------ACK------->|
+*/
+int reject_1(CONDITION_PARAMS)
+{
+  if (!(check_handle(ep, call, nh, SIP_500_INTERNAL_SERVER_ERROR)))
+    return 0;
+
+  save_event_in_list(ctx, event, ep, call);
+
+  switch (callstate(tags)) {
+  case nua_callstate_received:
+    RESPOND(ep, call, nh, SIP_486_BUSY_HERE, TAG_END());
+    return 0;
+  case nua_callstate_terminated:
+    if (call)
+      nua_handle_destroy(call->nh), call->nh = NULL;
+    return 1;
+  default:
+    return 0;
+  }
+}
+
+
+int test_reject_a(struct context *ctx)
+{
+  BEGIN();
+
+  struct endpoint *a = &ctx->a, *b = &ctx->b;
+  struct call *a_call = a->call, *b_call = b->call;
+  struct event *e;
+  sip_t const *sip;
+
+  if (print_headings)
+    printf("TEST NUA-4.1: reject before ringing\n");
+
+  /*
+   A      reject-1      B
+   |			|
+   |-------INVITE------>|
+   |<----100 Trying-----|
+   |			|
+   |<--------486--------|
+   |---------ACK------->|
+  */
+
+  a_call->sdp = "m=audio 5008 RTP/AVP 8";
+  b_call->sdp = "m=audio 5010 RTP/AVP 0 8";
+
+  TEST_1(a_call->nh = nua_handle(a->nua, a_call, SIPTAG_TO(b->to), TAG_END()));
+  INVITE(a, a_call, a_call->nh,
+	 TAG_IF(!ctx->proxy_tests, NUTAG_URL(b->contact->m_url)),
+	 SIPTAG_SUBJECT_STR("reject-1"),
+	 SIPTAG_CONTACT_STR("sip:a at 127.0.0.1"),
+	 SOATAG_USER_SDP_STR(a_call->sdp),
+	 TAG_END());
+  run_ab_until(ctx, -1, until_terminated, -1, reject_1);
+
+  /*
+   Client transitions in reject-1:
+   INIT -(C1)-> CALLING -(C6a)-> TERMINATED
+  */
+  TEST_1(e = a->events->head); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_calling); /* CALLING */
+  TEST_1(is_offer_sent(e->data->e_tags));
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_r_invite);
+  TEST(e->data->e_status, 486);
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_terminated); /* TERMINATED */
+  TEST_1(!e->next);
+
+  /*
+   Server transitions in reject-1:
+   INIT -(S1)-> RECEIVED -(S6a)-> TERMINATED
+  */
+  TEST_1(e = b->events->head); TEST_E(e->data->e_event, nua_i_invite);
+  TEST(e->data->e_status, 100);
+  TEST_1(sip = sip_object(e->data->e_msg));
+  TEST_1(sip->sip_contact); TEST_1(!sip->sip_contact->m_next); 
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_received); /* RECEIVED */
+  TEST_1(is_offer_recv(e->data->e_tags));
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_terminated); /* TERMINATED */
+  TEST_1(!e->next);
+
+  free_events_in_list(ctx, a->events);
+  nua_handle_destroy(a_call->nh), a_call->nh = NULL;
+  free_events_in_list(ctx, b->events);
+  nua_handle_destroy(b_call->nh), b_call->nh = NULL;
+
+  if (print_headings)
+    printf("TEST NUA-4.1: PASSED\n");
+
+  END();
+}
+
+/*
+ A      reject-2      B
+ |                    |
+ |-------INVITE------>|
+ |<----100 Trying-----|
+ |                    |
+ |<----180 Ringing----|
+ |                    |
+ |<--------602--------|
+ |---------ACK------->|
+*/
+int reject_2(CONDITION_PARAMS)
+{
+  if (!(check_handle(ep, call, nh, SIP_500_INTERNAL_SERVER_ERROR)))
+    return 0;
+
+  save_event_in_list(ctx, event, ep, call);
+
+  switch (callstate(tags)) {
+  case nua_callstate_received:
+    RESPOND(ep, call, nh, SIP_180_RINGING, TAG_END());
+    return 0;
+  case nua_callstate_early:
+    RESPOND(ep, call, nh, 602, "Rejected 2", TAG_END());
+    return 0;
+  case nua_callstate_terminated:
+    if (call)
+      nua_handle_destroy(call->nh), call->nh = NULL;
+    return 1;
+  default:
+    return 0;
+  }
+}
+
+int test_reject_b(struct context *ctx)
+{
+  BEGIN();
+
+  struct endpoint *a = &ctx->a, *b = &ctx->b;
+  struct call *a_call = a->call, *b_call = b->call;
+  struct event *e;
+
+  /* ------------------------------------------------------------------------ */
+  /*
+   A      reject-2      B
+   |			|
+   |-------INVITE------>|
+   |<----100 Trying-----|
+   |			|
+   |<----180 Ringing----|
+   |			|
+   |<--------602--------|
+   |---------ACK------->|
+  */
+
+  if (print_headings)
+    printf("TEST NUA-4.2: reject after ringing\n");
+
+  a_call->sdp = "m=audio 5008 RTP/AVP 8";
+  b_call->sdp = "m=audio 5010 RTP/AVP 0 8";
+
+  /* Make call reject-2 */
+  TEST_1(a_call->nh = nua_handle(a->nua, a_call, SIPTAG_TO(b->to), TAG_END()));
+  INVITE(a, a_call, a_call->nh,
+	 TAG_IF(!ctx->proxy_tests, NUTAG_URL(b->contact->m_url)),
+	 SIPTAG_SUBJECT_STR("reject-2"),
+	 SOATAG_USER_SDP_STR(a_call->sdp),
+	 TAG_END());
+  run_ab_until(ctx, -1, until_terminated, -1, reject_2);
+
+  /*
+   Client transitions in reject-2:
+   INIT -(C1)-> CALLING -(C2)-> PROCEEDING -(C6b)-> TERMINATED
+  */
+  TEST_1(e = a->events->head); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_calling); /* CALLING */
+  TEST_1(is_offer_sent(e->data->e_tags));
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_r_invite);
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_proceeding); /* PROCEEDING */
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_r_invite);
+  TEST(e->data->e_status, 602);
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_terminated); /* TERMINATED */
+  TEST_1(!e->next);
+
+  /*
+   Server transitions in reject-2:
+   INIT -(S1)-> RECEIVED -(S2)-> EARLY -(S6a)-> TERMINATED
+  */
+  TEST_1(e = b->events->head); TEST_E(e->data->e_event, nua_i_invite);
+  TEST(e->data->e_status, 100);
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_received); /* RECEIVED */
+  TEST_1(is_offer_recv(e->data->e_tags));
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_early); /* EARLY */
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_terminated); /* TERMINATED */
+  TEST_1(!e->next);
+
+  free_events_in_list(ctx, a->events);
+  nua_handle_destroy(a_call->nh), a_call->nh = NULL;
+  free_events_in_list(ctx, b->events);
+  nua_handle_destroy(b_call->nh), b_call->nh = NULL;
+
+  if (print_headings)
+    printf("TEST NUA-4.2: PASSED\n");
+
+  END();
+}
+
+/* ------------------------------------------------------------------------ */
+
+int reject_302(CONDITION_PARAMS), reject_305(CONDITION_PARAMS);
+int reject_500_retry_after(CONDITION_PARAMS);
+int redirect_always(CONDITION_PARAMS);
+int reject_604(CONDITION_PARAMS);
+
+/*
+ A     reject-302     B
+ |                    |
+ |-------INVITE------>|
+ |<----100 Trying-----|
+ |<-----302 Other-----|
+ |--------ACK-------->|
+ |                    |
+ |-------INVITE------>|
+ |<----100 Trying-----|
+ |<--305 Use Proxy----|
+ |--------ACK-------->|
+ |                    |
+ |-------INVITE------>|
+ |<----100 Trying-----|
+ |<----500 Retry------|
+ |--------ACK-------->|
+ |                    |
+ |-------INVITE------>|
+ |<----100 Trying-----|
+ |                    |
+ |<----180 Ringing----|
+ |                    |
+ |<---604 Nowhere-----|
+ |--------ACK-------->|
+*/
+
+int reject_302(CONDITION_PARAMS)
+{
+  if (!(check_handle(ep, call, nh, SIP_500_INTERNAL_SERVER_ERROR)))
+    return 0;
+
+  save_event_in_list(ctx, event, ep, call);
+
+  switch (callstate(tags)) {
+  case nua_callstate_received:
+    {
+      sip_contact_t m[1];
+      *m = *ep->contact;
+      m->m_url->url_user = "302";
+      RESPOND(ep, call, nh, SIP_302_MOVED_TEMPORARILY,
+	      SIPTAG_CONTACT(m), TAG_END());
+    }
+    return 0;
+  case nua_callstate_terminated:
+    if (call)
+      nua_handle_destroy(call->nh), call->nh = NULL;
+    ep->next_condition = reject_305;
+    return 0;
+  default:
+    return 0;
+  }
+}
+
+int reject_305(CONDITION_PARAMS)
+{
+  if (!(check_handle(ep, call, nh, SIP_500_INTERNAL_SERVER_ERROR)))
+    return 0;
+
+  save_event_in_list(ctx, event, ep, call);
+
+  switch (callstate(tags)) {
+  case nua_callstate_received:
+    {
+      sip_contact_t m[1];
+      *m = *ep->contact;
+      m->m_url->url_user = "305";
+      m->m_url->url_params = "lr=1";
+      RESPOND(ep, call, nh, SIP_305_USE_PROXY, SIPTAG_CONTACT(m), TAG_END());
+    }
+    return 0;
+  case nua_callstate_terminated:
+    if (call)
+      nua_handle_destroy(call->nh), call->nh = NULL;
+    ep->next_condition = reject_500_retry_after;
+    return 0;
+  default:
+    return 0;
+  }
+}
+
+int reject_500_retry_after(CONDITION_PARAMS)
+{
+  if (!(check_handle(ep, call, nh, SIP_500_INTERNAL_SERVER_ERROR)))
+    return 0;
+
+  save_event_in_list(ctx, event, ep, call);
+
+  if (event == nua_i_invite) {
+    sip_retry_after_t af[1];
+    sip_retry_after_init(af)->af_delta = 1;
+    RESPOND(ep, call, nh, 500, "Retry After", SIPTAG_RETRY_AFTER(af), TAG_END());
+  }
+  else if (event == nua_i_state) switch (callstate(tags)) {
+  case nua_callstate_terminated:
+    if (call)
+      nua_handle_destroy(call->nh), call->nh = NULL;
+    ep->next_condition = reject_604;
+    break;
+  default:
+    break;
+  }
+
+  return 0;
+}
+
+int reject_604(CONDITION_PARAMS)
+{
+  if (!(check_handle(ep, call, nh, SIP_500_INTERNAL_SERVER_ERROR)))
+    return 0;
+
+  save_event_in_list(ctx, event, ep, call);
+
+  switch (callstate(tags)) {
+  case nua_callstate_received:
+    RESPOND(ep, call, nh, SIP_180_RINGING, TAG_END());
+    return 0;
+  case nua_callstate_early:
+    RESPOND(ep, call, nh, SIP_604_DOES_NOT_EXIST_ANYWHERE, TAG_END());
+    return 0;
+  case nua_callstate_terminated:
+    if (call)
+      nua_handle_destroy(call->nh), call->nh = NULL;
+    return 1;
+  default:
+    return 0;
+  }
+}
+
+int redirect_always(CONDITION_PARAMS)
+{
+  if (!(check_handle(ep, call, nh, SIP_500_INTERNAL_SERVER_ERROR)))
+    return 0;
+
+  if (event == nua_i_invite) {
+    char user[30];
+    sip_contact_t m[1];
+    *m = *ep->contact;
+    snprintf(user, sizeof user, "user-%u", ep->flags.n++);
+    m->m_url->url_user = user;
+    RESPOND(ep, call, nh, SIP_302_MOVED_TEMPORARILY,
+	    SIPTAG_CONTACT(m), TAG_END());
+    nua_handle_destroy(nh);
+    call->nh = NULL;
+    return 1;
+  }
+
+  return 0;
+}
+
+
+int test_reject_302(struct context *ctx)
+{
+  BEGIN();
+
+  struct endpoint *a = &ctx->a, *b = &ctx->b;
+  struct call *a_call = a->call, *b_call = b->call;
+  struct event *e;
+  sip_t const *sip;
+
+  /* Make call reject-3 */
+  if (print_headings)
+    printf("TEST NUA-4.3: redirect then reject\n");
+
+  a_call->sdp = "m=audio 5008 RTP/AVP 8";
+  b_call->sdp = "m=audio 5010 RTP/AVP 0 8";
+
+  TEST_1(a_call->nh = nua_handle(a->nua, a_call, SIPTAG_TO(b->to), TAG_END()));
+  INVITE(a, a_call, a_call->nh,
+	 TAG_IF(!ctx->proxy_tests, NUTAG_URL(b->contact->m_url)),
+	 SIPTAG_SUBJECT_STR("reject-3"),
+	 SOATAG_USER_SDP_STR(a_call->sdp),
+	 TAG_END());
+  run_ab_until(ctx, -1, until_terminated, -1, reject_302);
+
+  /*
+   A      reject-3      B
+   |                    |
+   |-------INVITE------>|
+   |<----100 Trying-----|
+   |                    |
+   |<-----302 Other-----|
+   |--------ACK-------->|
+   |                    |
+   |-------INVITE------>|
+   |<----100 Trying-----|
+   |<---305 Use Proxy---|
+   |--------ACK-------->|
+   |                    |
+   |-------INVITE------>|
+   |<----100 Trying-----|
+   |<-----500 Retry-----|
+   |--------ACK-------->|
+   |                    |
+   |                    |
+   |-------INVITE------>|
+   |<----100 Trying-----|
+   |                    |
+   |<----180 Ringing----|
+   |                    |
+   |<---604 Nowhere-----|
+   |--------ACK-------->|
+  */
+
+  /*
+   Client transitions in reject-3:
+   INIT -(C1)-> PROCEEDING -(C6a)-> TERMINATED/INIT
+   INIT -(C1)-> CALLING -(C2)-> PROCEEDING -(C6b)-> TERMINATED
+  */
+
+  TEST_1(e = a->events->head); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_calling); /* CALLING */
+  TEST_1(is_offer_sent(e->data->e_tags));
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_r_invite);
+  TEST(e->data->e_status, 100);
+  TEST(sip_object(e->data->e_msg)->sip_status->st_status, 302);
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_calling); /* CALLING */
+  TEST_1(is_offer_sent(e->data->e_tags));
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_r_invite);
+  TEST(e->data->e_status, 100);
+  TEST(sip_object(e->data->e_msg)->sip_status->st_status, 305);
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_calling); /* CALLING */
+  TEST_1(is_offer_sent(e->data->e_tags));
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_r_invite);
+  TEST(e->data->e_status, 100);
+  TEST(sip_object(e->data->e_msg)->sip_status->st_status, 500);
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_calling); /* CALLING */
+  TEST_1(is_offer_sent(e->data->e_tags));
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_r_invite);
+  TEST(e->data->e_status, 180);
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_proceeding); /* PROCEEDING */
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_r_invite);
+  TEST(e->data->e_status, 604);
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_terminated); /* TERMINATED */
+  TEST_1(!e->next);
+
+  /*
+   Server transitions:
+   INIT -(S1)-> RECEIVED -(S6a)-> TERMINATED/INIT
+   INIT -(S1)-> RECEIVED -(S2)-> EARLY -(S6b)-> TERMINATED
+  */
+  TEST_1(e = b->events->head); TEST_E(e->data->e_event, nua_i_invite);
+  TEST(e->data->e_status, 100);
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_received); /* RECEIVED */
+  TEST_1(is_offer_recv(e->data->e_tags));
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_terminated); /* TERMINATED */
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_invite);
+  TEST(e->data->e_status, 100);
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_received); /* RECEIVED */
+  TEST_1(is_offer_recv(e->data->e_tags));
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_terminated); /* TERMINATED */
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_invite);
+  TEST(e->data->e_status, 100);
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_received); /* RECEIVED */
+  TEST_1(is_offer_recv(e->data->e_tags));
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_terminated); /* TERMINATED */
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_invite);
+  TEST(e->data->e_status, 100);
+  TEST_1(sip = sip_object(e->data->e_msg));
+  TEST_1(sip->sip_request);
+  TEST_S(sip->sip_request->rq_url->url_user, "302");
+  TEST_1(sip->sip_route);
+  TEST_S(sip->sip_route->r_url->url_user, "305");
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_received); /* RECEIVED */
+  TEST_1(is_offer_recv(e->data->e_tags));
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_early); /* EARLY */
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_terminated); /* TERMINATED */
+  TEST_1(!e->next);
+
+  free_events_in_list(ctx, a->events);
+  nua_handle_destroy(a_call->nh), a_call->nh = NULL;
+  free_events_in_list(ctx, b->events);
+  nua_handle_destroy(b_call->nh), b_call->nh = NULL;
+
+  if (print_headings)
+    printf("TEST NUA-4.3: PASSED\n");
+
+  if (print_headings)
+    printf("TEST NUA-4.3.1: redirect until retry count is exceeded\n");
+
+  TEST_1(a_call->nh = nua_handle(a->nua, a_call, SIPTAG_TO(b->to), TAG_END()));
+  INVITE(a, a_call, a_call->nh,
+	 TAG_IF(!ctx->proxy_tests, NUTAG_URL(b->contact->m_url)),
+	 SIPTAG_SUBJECT_STR("redirect always"),
+	 SOATAG_USER_SDP_STR(a_call->sdp),
+	 TAG_END());
+  run_ab_until(ctx, -1, until_terminated, -1, redirect_always);
+
+  free_events_in_list(ctx, a->events);
+  nua_handle_destroy(a_call->nh), a_call->nh = NULL;
+  free_events_in_list(ctx, b->events);
+  nua_handle_destroy(b_call->nh), b_call->nh = NULL;
+
+  if (print_headings)
+    printf("TEST NUA-4.3: PASSED\n");
+
+  END();
+}
+
+/* ------------------------------------------------------------------------ */
+
+/* Reject call with 407, then 401 */
+
+int reject_407(CONDITION_PARAMS);
+int reject_401(CONDITION_PARAMS);
+int authenticate_call(CONDITION_PARAMS);
+int reject_403(CONDITION_PARAMS);
+
+/*
+ A     reject-401     B
+ |                    |
+ |-------INVITE------>|
+ |<----100 Trying-----|
+ |<--------407--------|
+ |---------ACK------->|
+ |                    |
+ |-------INVITE------>|
+ |<----100 Trying-----|
+ |                    |
+ |<----180 Ringing----|
+ |                    |
+ |<--------401--------|
+ |---------ACK------->|
+ |                    |
+ |-------INVITE------>|
+ |<----100 Trying-----|
+ |<-------403---------|
+ |--------ACK-------->|
+*/
+
+int reject_407(CONDITION_PARAMS)
+{
+  if (!(check_handle(ep, call, nh, SIP_500_INTERNAL_SERVER_ERROR)))
+    return 0;
+
+  save_event_in_list(ctx, event, ep, call);
+
+  switch (callstate(tags)) {
+  case nua_callstate_received:
+    RESPOND(ep, call, nh, SIP_407_PROXY_AUTH_REQUIRED,
+	    SIPTAG_PROXY_AUTHENTICATE_STR("Digest realm=\"test_nua\", "
+					  "nonce=\"nsdhfuds\", algorithm=MD5, "
+					  "qop=\"auth-int\""),
+	    TAG_END());
+    return 0;
+  case nua_callstate_terminated:
+    if (call)
+      nua_handle_destroy(call->nh), call->nh = NULL;
+    ep->next_condition = reject_401;
+    return 0;
+  default:
+    return 0;
+  }
+}
+
+int reject_401(CONDITION_PARAMS)
+{
+  if (!(check_handle(ep, call, nh, SIP_500_INTERNAL_SERVER_ERROR)))
+    return 0;
+
+  save_event_in_list(ctx, event, ep, call);
+
+  switch (callstate(tags)) {
+  case nua_callstate_received:
+    RESPOND(ep, call, nh, SIP_180_RINGING, TAG_END());
+    return 0;
+  case nua_callstate_early:
+    RESPOND(ep, call, nh, SIP_401_UNAUTHORIZED,
+	    SIPTAG_WWW_AUTHENTICATE_STR("Digest realm=\"test_nua\", "
+					"nonce=\"nsdhfuds\", algorithm=MD5, "
+					"qop=\"auth\""),
+	    TAG_END());
+    return 0;
+  case nua_callstate_terminated:
+    if (call)
+      nua_handle_destroy(call->nh), call->nh = NULL;
+    ep->next_condition = reject_403;
+    return 0;
+  default:
+    return 0;
+  }
+}
+
+int reject_403(CONDITION_PARAMS)
+{
+  if (!(check_handle(ep, call, nh, SIP_500_INTERNAL_SERVER_ERROR)))
+    return 0;
+
+  save_event_in_list(ctx, event, ep, call);
+
+  switch (callstate(tags)) {
+  case nua_callstate_received:
+    RESPOND(ep, call, nh, SIP_403_FORBIDDEN, TAG_END());
+    return 0;
+  case nua_callstate_terminated:
+    if (call)
+      nua_handle_destroy(call->nh), call->nh = NULL;
+    ep->next_condition = NULL;
+    return 1;
+  default:
+    return 0;
+  }
+}
+
+int authenticate_call(CONDITION_PARAMS)
+{
+  if (!(check_handle(ep, call, nh, SIP_500_INTERNAL_SERVER_ERROR)))
+    return 0;
+
+  save_event_in_list(ctx, event, ep, call);
+
+  if (event == nua_r_invite && status == 401) {
+    AUTHENTICATE(ep, call, nh, NUTAG_AUTH("Digest:\"test_nua\":jaska:secret"),
+		 SIPTAG_SUBJECT_STR("Got 401"),
+		 TAG_END());
+    return 0;
+  }
+
+  if (event == nua_r_invite && status == 407) {
+    AUTHENTICATE(ep, call, nh, NUTAG_AUTH("Digest:\"test_nua\":erkki:secret"),
+		 SIPTAG_SUBJECT_STR("Got 407"),
+		 TAG_END());
+    return 0;
+  }
+
+  switch (callstate(tags)) {
+  case nua_callstate_terminated:
+    if (call)
+      nua_handle_destroy(call->nh), call->nh = NULL;
+    return 1;
+  default:
+    return 0;
+  }
+}
+
+int test_reject_401(struct context *ctx)
+{
+  BEGIN();
+
+  struct endpoint *a = &ctx->a, *b = &ctx->b;
+  struct call *a_call = a->call, *b_call = b->call;
+  struct event const *e;
+  sip_t const *sip;
+
+  if (print_headings)
+    printf("TEST NUA-4.4: challenge then reject\n");
+
+  a_call->sdp = "m=audio 5008 RTP/AVP 8";
+  b_call->sdp = "m=audio 5010 RTP/AVP 0 8";
+
+  TEST_1(a_call->nh = nua_handle(a->nua, a_call, SIPTAG_TO(b->to), TAG_END()));
+  INVITE(a, a_call, a_call->nh,
+	 TAG_IF(!ctx->proxy_tests, NUTAG_URL(b->contact->m_url)),
+	 SIPTAG_SUBJECT_STR("reject-401"),
+	 SOATAG_USER_SDP_STR(a_call->sdp),
+	 TAG_END());
+  run_ab_until(ctx, -1, authenticate_call, -1, reject_407);
+
+  /*
+   Client transitions in reject-3:
+   INIT -(C1)-> CALLING -(C2)-> PROCEEDING -(C6b)-> TERMINATED/INIT
+   INIT -(C1)-> CALLING -(C6a)-> TERMINATED
+  */
+  TEST_1(e = a->events->head); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_calling); /* CALLING */
+  TEST_1(is_offer_sent(e->data->e_tags));
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_r_invite);
+  TEST(sip_object(e->data->e_msg)->sip_status->st_status, 407);
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_calling); /* CALLING */
+  TEST_1(is_offer_sent(e->data->e_tags));
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_r_invite);
+  TEST(e->data->e_status, 180);
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_proceeding); /* PROCEEDING */
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_r_invite);
+  TEST(e->data->e_status, 401);
+  TEST(sip_object(e->data->e_msg)->sip_status->st_status, 401);
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_calling); /* CALLING */
+  TEST_1(is_offer_sent(e->data->e_tags));
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_r_invite);
+  TEST(e->data->e_status, 403);
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_terminated); /* TERMINATED */
+  TEST_1(!e->next);
+
+  free_events_in_list(ctx, a->events);
+
+  /*
+   Server transitions:
+   INIT -(S1)-> RECEIVED -(S6a)-> TERMINATED/INIT
+   INIT -(S1)-> RECEIVED -(S2)-> EARLY -(S6b)-> TERMINATED/INIT
+   INIT -(S1)-> RECEIVED -(S6a)-> TERMINATED
+  */
+  TEST_1(e = b->events->head); TEST_E(e->data->e_event, nua_i_invite);
+  TEST_1(sip = sip_object(e->data->e_msg));
+  TEST_1(sip->sip_subject);
+  TEST_S(sip->sip_subject->g_value, "reject-401");
+  TEST(e->data->e_status, 100);
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_received); /* RECEIVED */
+  TEST_1(is_offer_recv(e->data->e_tags));
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_terminated); /* TERMINATED */
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_invite);
+  TEST(e->data->e_status, 100);
+  TEST_1(sip = sip_object(e->data->e_msg));
+  TEST_1(sip->sip_proxy_authorization);
+  /* Ensure that nua_authenticate() tags get added to the request */
+  TEST_1(sip->sip_subject);
+  TEST_S(sip->sip_subject->g_value, "Got 407");
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_received); /* RECEIVED */
+  TEST_1(is_offer_recv(e->data->e_tags));
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_early); /* EARLY */
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_terminated); /* TERMINATED */
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_invite);
+  TEST_1(sip = sip_object(e->data->e_msg));
+  TEST_1(sip->sip_subject);
+  TEST_S(sip->sip_subject->g_value, "Got 401");
+  TEST_1(sip->sip_authorization);
+  TEST_1(sip->sip_proxy_authorization);
+  TEST(e->data->e_status, 100);
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_received); /* RECEIVED */
+  TEST_1(is_offer_recv(e->data->e_tags));
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_terminated); /* TERMINATED */
+  TEST_1(!e->next);
+
+  free_events_in_list(ctx, b->events);
+
+  nua_handle_destroy(a_call->nh), a_call->nh = NULL;
+  nua_handle_destroy(b_call->nh), b_call->nh = NULL;
+
+  if (print_headings)
+    printf("TEST NUA-4.4: PASSED\n");
+
+  END();
+}
+
+/* ------------------------------------------------------------------------ */
+
+/* Reject call with 401 and bad challenge */
+
+/*
+ A   reject-401-aka   B
+ |                    |
+ |-------INVITE------>|
+ |<----100 Trying-----|
+ |<--------401--------|
+ |---------ACK------->|
+*/
+
+int reject_401_aka(CONDITION_PARAMS)
+{
+  if (!(check_handle(ep, call, nh, SIP_500_INTERNAL_SERVER_ERROR)))
+    return 0;
+
+  save_event_in_list(ctx, event, ep, call);
+
+  switch (callstate(tags)) {
+  case nua_callstate_received:
+    RESPOND(ep, call, nh, SIP_401_UNAUTHORIZED,
+	    /* Send a challenge that we do not grok */
+	    SIPTAG_WWW_AUTHENTICATE_STR("Digest realm=\"test_nua\", "
+					"nonce=\"nsdhfuds\", algorithm=SHA0-AKAv6, "
+					"qop=\"auth\""),
+	    TAG_END());
+    return 0;
+  case nua_callstate_terminated:
+    if (call)
+      nua_handle_destroy(call->nh), call->nh = NULL;
+    return 1;
+  default:
+    return 0;
+  }
+}
+
+int test_reject_401_aka(struct context *ctx)
+{
+  BEGIN();
+
+  struct endpoint *a = &ctx->a, *b = &ctx->b;
+  struct call *a_call = a->call, *b_call = b->call;
+  struct event const *e;
+  sip_t const *sip;
+
+  if (print_headings)
+    printf("TEST NUA-4.6.1: invalid challenge \n");
+
+  a_call->sdp = "m=audio 5008 RTP/AVP 8";
+  b_call->sdp = "m=audio 5010 RTP/AVP 0 8";
+
+  TEST_1(a_call->nh = nua_handle(a->nua, a_call, SIPTAG_TO(b->to), TAG_END()));
+  INVITE(a, a_call, a_call->nh,
+	 TAG_IF(!ctx->proxy_tests, NUTAG_URL(b->contact->m_url)),
+	 SIPTAG_SUBJECT_STR("reject-401-aka"),
+	 SOATAG_USER_SDP_STR(a_call->sdp),
+	 TAG_END());
+
+  run_ab_until(ctx, -1, until_terminated, -1, reject_401_aka);
+
+  /*
+   Client transitions
+   INIT -(C1)-> CALLING -(C6a)-> TERMINATED/INIT
+  */
+  TEST_1(e = a->events->head); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_calling); /* CALLING */
+  TEST_1(is_offer_sent(e->data->e_tags));
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_r_invite);
+  TEST(sip_object(e->data->e_msg)->sip_status->st_status, 401);
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_terminated); /* TERMINATED */
+  TEST_1(!e->next);
+
+  free_events_in_list(ctx, a->events);
+
+  /*
+   Server transitions:
+   INIT -(S1)-> RECEIVED -(S6a)-> TERMINATED
+  */
+  TEST_1(e = b->events->head); TEST_E(e->data->e_event, nua_i_invite);
+  TEST_1(sip = sip_object(e->data->e_msg));
+  TEST(e->data->e_status, 100);
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_received); /* RECEIVED */
+  TEST_1(is_offer_recv(e->data->e_tags));
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_terminated); /* TERMINATED */
+  TEST_1(!e->next);
+
+  free_events_in_list(ctx, b->events);
+
+  nua_handle_destroy(a_call->nh), a_call->nh = NULL;
+  nua_handle_destroy(b_call->nh), b_call->nh = NULL;
+
+  if (print_headings)
+    printf("TEST NUA-4.6.1: PASSED\n");
+
+  END();
+}
+
+
+/* ------------------------------------------------------------------------ */
+
+/* Reject call with 401, twice */
+
+/*
+ A   reject-401-bad   B
+ |                    |
+ |-------INVITE------>|
+ |<----100 Trying-----|
+ |<--------401--------|
+ |---------ACK------->|
+ |                    |
+ |-------INVITE------>|
+ |<----100 Trying-----|
+ |<--------401--------|
+ |---------ACK------->|
+*/
+
+int reject_401_bad(CONDITION_PARAMS)
+{
+  if (!(check_handle(ep, call, nh, SIP_500_INTERNAL_SERVER_ERROR)))
+    return 0;
+
+  save_event_in_list(ctx, event, ep, call);
+
+  switch (callstate(tags)) {
+  case nua_callstate_received:
+    RESPOND(ep, call, nh, SIP_401_UNAUTHORIZED,
+	    /* Send a challenge that we do not grok */
+	    SIPTAG_WWW_AUTHENTICATE_STR("Digest realm=\"No hope\", "
+					"nonce=\"goO541ftNrw327aWpu2\", "
+					"algorithm=MD5, "
+					"qop=\"auth\""),
+	    TAG_END());
+    return 0;
+  case nua_callstate_terminated:
+    if (call)
+      nua_handle_destroy(call->nh), call->nh = NULL;
+    if (ep->flags.bit0)		/* Terminate 2 calls */
+      return 1;
+    ep->flags.bit0 = 1;
+    return 0;
+  default:
+    return 0;
+  }
+}
+
+int authenticate_bad(CONDITION_PARAMS)
+{
+  if (!(check_handle(ep, call, nh, SIP_500_INTERNAL_SERVER_ERROR)))
+    return 0;
+
+  save_event_in_list(ctx, event, ep, call);
+
+  if (event == nua_r_invite && status == 401) {
+    AUTHENTICATE(ep, call, nh, NUTAG_AUTH("Digest:\"No hope\":jaska:secret"),
+		 SIPTAG_SUBJECT_STR("Bad password"),
+		 TAG_END());
+    return 0;
+  }
+
+  switch (callstate(tags)) {
+  case nua_callstate_terminated:
+    if (call)
+      nua_handle_destroy(call->nh), call->nh = NULL;
+    return 1;
+  default:
+    return 0;
+  }
+}
+
+
+int test_reject_401_bad(struct context *ctx)
+{
+  BEGIN();
+
+  struct endpoint *a = &ctx->a, *b = &ctx->b;
+  struct call *a_call = a->call, *b_call = b->call;
+  struct event const *e;
+  sip_t const *sip;
+
+  if (print_headings)
+    printf("TEST NUA-4.6.2: bad username/password\n");
+
+  a_call->sdp = "m=audio 5008 RTP/AVP 8";
+
+  TEST_1(a_call->nh = nua_handle(a->nua, a_call, SIPTAG_TO(b->to), TAG_END()));
+  INVITE(a, a_call, a_call->nh,
+	 TAG_IF(!ctx->proxy_tests, NUTAG_URL(b->contact->m_url)),
+	 SIPTAG_SUBJECT_STR("reject-401-bad"),
+	 SOATAG_USER_SDP_STR(a_call->sdp),
+	 TAG_END());
+
+
+  run_ab_until(ctx, -1, authenticate_bad, -1, reject_401_bad);
+
+  /*
+   Client transitions
+   INIT -(C1)-> CALLING -(C6a)-> TERMINATED/INIT
+  */
+  TEST_1(e = a->events->head); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_calling); /* CALLING */
+  TEST_1(is_offer_sent(e->data->e_tags));
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_r_invite);
+  TEST(sip_object(e->data->e_msg)->sip_status->st_status, 401);
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_calling); /* CALLING */
+  TEST_1(is_offer_sent(e->data->e_tags));
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_r_invite);
+  TEST(sip_object(e->data->e_msg)->sip_status->st_status, 401);
+  /* nua_authenticate() fails and INVITE returns an internal error response */
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_r_invite);
+  TEST(e->data->e_status, 904);
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_terminated); /* TERMINATED */
+  TEST_1(!e->next); 
+
+  free_events_in_list(ctx, a->events);
+
+  /*
+   Server transitions:
+   INIT -(S1)-> RECEIVED -(S6a)-> TERMINATED
+  */
+  TEST_1(e = b->events->head); TEST_E(e->data->e_event, nua_i_invite);
+  TEST_1(sip = sip_object(e->data->e_msg));
+  TEST(e->data->e_status, 100);
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_received); /* RECEIVED */
+  TEST_1(is_offer_recv(e->data->e_tags));
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_terminated); /* TERMINATED */
+
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_invite);
+  TEST_1(sip = sip_object(e->data->e_msg));
+  TEST(e->data->e_status, 100);
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_received); /* RECEIVED */
+  TEST_1(is_offer_recv(e->data->e_tags));
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_terminated); /* TERMINATED */
+  TEST_1(!e->next);
+
+  free_events_in_list(ctx, b->events);
+
+  nua_handle_destroy(a_call->nh), a_call->nh = NULL;
+  nua_handle_destroy(b_call->nh), b_call->nh = NULL;
+
+  if (print_headings)
+    printf("TEST NUA-4.6.2: PASSED\n");
+
+  END();
+}
+
+
+/* ---------------------------------------------------------------------- */
+
+int test_mime_negotiation(struct context *ctx)
+{
+  BEGIN();
+
+  struct endpoint *a = &ctx->a, *b = &ctx->b;
+  struct call *a_call = a->call, *b_call = b->call;
+  struct event *e;
+  sip_t const *sip;
+
+  /* Make call reject-3 */
+  if (print_headings)
+    printf("TEST NUA-4.5: check for rejections of invalid requests\n");
+
+  a_call->sdp = "m=audio 5008 RTP/AVP 8";
+  b_call->sdp = "m=audio 5010 RTP/AVP 0 8";
+
+  TEST_1(a_call->nh = nua_handle(a->nua, a_call, SIPTAG_TO(b->to), TAG_END()));
+
+  if (print_headings)
+    printf("TEST NUA-4.5.1: invalid Content-Type\n");
+
+  INVITE(a, a_call, a_call->nh,
+	 TAG_IF(!ctx->proxy_tests, NUTAG_URL(b->contact->m_url)),
+	 SIPTAG_SUBJECT_STR("reject-3"),
+	 SOATAG_USER_SDP_STR(a_call->sdp),
+	 SIPTAG_CONTENT_TYPE_STR("application/xyzzy+xml"),
+	 SIPTAG_CONTENT_DISPOSITION_STR("session;required"),
+	 SIPTAG_PAYLOAD_STR("m=audio 5008 RTP/AVP 8\n"),
+	 TAG_END());
+  run_ab_until(ctx, -1, until_terminated, -1, NULL);
+
+  /*
+   A    reject-5.1      B
+   |			|
+   |-------INVITE------>|
+   |<-------415---------|
+   |--------ACK-------->|
+  */
+
+  /*
+   Client transitions in reject-3:
+   INIT -(C1)-> CALLING -(C6a)-> TERMINATED
+  */
+
+  TEST_1(e = a->events->head); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_calling); /* CALLING */
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_r_invite);
+  TEST(e->data->e_status, 415);
+  TEST_1(sip = sip_object(e->data->e_msg));
+  TEST(sip->sip_status->st_status, 415);
+  TEST_1(sip->sip_accept);
+  TEST_S(sip->sip_accept->ac_type, "application/sdp");
+  TEST_1(sip->sip_accept_encoding);
+  /* No content-encoding is supported */
+  TEST_S(sip->sip_accept_encoding->aa_value, "");
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_terminated); /* CALLING */
+  TEST_1(!e->next);
+
+  free_events_in_list(ctx, a->events);
+
+  if (print_headings)
+    printf("TEST NUA-4.5.1: PASSED\n");
+
+  if (print_headings)
+    printf("TEST NUA-4.5.2: invalid Content-Encoding\n");
+
+  /*
+   A    reject-5.2      B
+   |			|
+   |-------INVITE------>|
+   |<-------415---------|
+   |--------ACK-------->|
+  */
+
+  INVITE(a, a_call, a_call->nh,
+	 TAG_IF(!ctx->proxy_tests, NUTAG_URL(b->contact->m_url)),
+	 SIPTAG_SUBJECT_STR("reject-5"),
+	 SOATAG_USER_SDP_STR(a_call->sdp),
+	 SIPTAG_CONTENT_ENCODING_STR("zyxxy"),
+	 TAG_END());
+  run_ab_until(ctx, -1, until_terminated, -1, NULL);
+
+  /*
+   Client transitions in reject-3:
+   INIT -(C1)-> CALLING -(C6a)-> TERMINATED
+  */
+
+  TEST_1(e = a->events->head); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_calling); /* CALLING */
+  TEST_1(is_offer_sent(e->data->e_tags));
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_r_invite);
+  TEST(e->data->e_status, 415);
+  TEST_1(sip = sip_object(e->data->e_msg));
+  TEST(sip->sip_status->st_status, 415);
+  TEST_1(sip->sip_accept);
+  TEST_S(sip->sip_accept->ac_type, "application/sdp");
+  TEST_1(sip->sip_accept_encoding);
+  /* No content-encoding is supported */
+  TEST_S(sip->sip_accept_encoding->aa_value, "");
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_terminated); /* TERMINATED */
+  TEST_1(!e->next);
+
+  free_events_in_list(ctx, a->events);
+
+  if (print_headings)
+    printf("TEST NUA-4.5.2: PASSED\n");
+
+  if (print_headings)
+    printf("TEST NUA-4.5.3: invalid Accept\n");
+
+  INVITE(a, a_call, a_call->nh,
+	 TAG_IF(!ctx->proxy_tests, NUTAG_URL(b->contact->m_url)),
+	 SIPTAG_SUBJECT_STR("reject-3"),
+	 SOATAG_USER_SDP_STR(a_call->sdp),
+	 SIPTAG_ACCEPT_STR("application/xyzzy+xml"),
+	 TAG_END());
+  run_ab_until(ctx, -1, until_terminated, -1, NULL);
+
+
+  /*
+   A    reject-5.3      B
+   |			|
+   |-------INVITE------>|
+   |<-------406---------|
+   |--------ACK-------->|
+  */
+
+  /*
+   Client transitions in reject-3:
+   INIT -(C1)-> CALLING -(C6a)-> TERMINATED
+  */
+
+  TEST_1(e = a->events->head); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_calling); /* CALLING */
+  TEST_1(is_offer_sent(e->data->e_tags));
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_r_invite);
+  TEST(e->data->e_status, 406);
+  TEST_1(sip = sip_object(e->data->e_msg));
+  TEST(sip->sip_status->st_status, 406);
+  TEST_1(sip->sip_accept);
+  TEST_S(sip->sip_accept->ac_type, "application/sdp");
+  TEST_1(sip->sip_accept_encoding);
+  /* No content-encoding is supported */
+  TEST_S(sip->sip_accept_encoding->aa_value, "");
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_terminated); /* TERMINATED */
+  TEST_1(!e->next);
+
+  free_events_in_list(ctx, a->events);
+
+  if (print_headings)
+    printf("TEST NUA-4.5.3: PASSED\n");
+
+  nua_handle_destroy(a_call->nh), a_call->nh = NULL;
+
+  free_events_in_list(ctx, b->events);
+
+  if (print_headings)
+    printf("TEST NUA-4.5: PASSED\n");
+
+  END();
+}
+
+/* ---------------------------------------------------------------------- */
+
+size_t filter_200_OK(void *arg, void *message, size_t len)
+{
+  (void)arg;
+
+  if (len >= 11 && strncasecmp(message, "SIP/2.0 200", 11) == 0)
+    return 0;
+  return len;
+}
+
+size_t filter_ACK(void *arg, void *message, size_t len)
+{
+  (void)arg;
+
+  if (len >= 7 && strncasecmp(message, "ACK sip", 7) == 0) 
+    return 0;
+  return len;
+}
+
+int call_with_bad_ack(CONDITION_PARAMS);
+int accept_call_with_bad_contact(CONDITION_PARAMS);
+
+int test_call_timeouts(struct context *ctx)
+{
+  BEGIN();
+
+  struct endpoint *a = &ctx->a, *b = &ctx->b;
+  struct call *a_call = a->call, *b_call = b->call;
+  struct event *e;
+  struct nat_filter *f, *f2;
+
+  if (print_headings)
+    printf("TEST NUA-4.7: check for error and timeout handling\n");
+
+  a_call->sdp = "m=audio 5008 RTP/AVP 8";
+  b_call->sdp = "m=audio 5010 RTP/AVP 0 8";
+
+  if (!ctx->nat)
+    goto completed_4_7_1;
+
+  if (print_headings)
+    printf("TEST NUA-4.7.1: ACK timeout (200 OK filtered)\n");
+
+  TEST_1(a_call->nh = nua_handle(a->nua, a_call, SIPTAG_TO(b->to), TAG_END()));
+
+  TEST_1(f = test_nat_add_filter(ctx->nat, filter_200_OK, NULL, nat_inbound));
+  TEST_1(f2 = test_nat_add_filter(ctx->nat, filter_200_OK,
+				  NULL, nat_outbound));
+  
+  INVITE(a, a_call, a_call->nh,
+	 TAG_IF(!ctx->proxy_tests, NUTAG_URL(b->contact->m_url)),
+	 SIPTAG_SUBJECT_STR("NUA-4.7.1"),
+	 SOATAG_USER_SDP_STR(a_call->sdp),
+	 TAG_END());
+  run_ab_until(ctx, -1, until_terminated, -1, accept_call);
+
+  /*
+ A     accept_call    B
+ |                    |
+ |-------INVITE------>|
+ |<----100 Trying-----|
+ |                    |
+ |<----180 Ringing----|
+ |                    |
+ |   X-----200--------|
+ |   X-----200--------|
+ |   X-----200--------|
+ |                    |
+ |<--------BYE--------|
+ |--------200 OK---X  |
+
+  */
+
+  /*
+    Client transitions:
+  */
+
+  TEST_1(e = a->events->head); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_calling); /* CALLING */
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_r_invite);
+  TEST(e->data->e_status, 180);
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_proceeding); /* PROCEEDING */
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_bye);
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_terminated); /* TERMINATED */
+  TEST_1(!e->next);
+
+  /*
+   Server transitions:
+   -(S1)-> RECEIVED -(S2a)-> EARLY -(S3b)-> COMPLETED -(S5)-> TERMINATING
+   -(S10)-> TERMINATED -X
+  */
+  TEST_1(e = b->events->head); TEST_E(e->data->e_event, nua_i_invite);
+  TEST(e->data->e_status, 100);
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_received); /* RECEIVED */
+  TEST_1(is_offer_recv(e->data->e_tags));
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_early); /* EARLY */
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_completed); /* COMPLETED */
+  TEST_1(is_answer_sent(e->data->e_tags));
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_error);
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_terminating); /* TERMINATING */
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_r_bye);
+  TEST(e->data->e_status, 408);
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_terminated); /* TERMINATED */
+  TEST_1(!e->next);
+
+  free_events_in_list(ctx, a->events);
+  nua_handle_destroy(a_call->nh), a_call->nh = NULL;
+  free_events_in_list(ctx, b->events);
+  nua_handle_destroy(b_call->nh), b_call->nh = NULL;
+
+  TEST_1(test_nat_remove_filter(ctx->nat, f) == 0);
+  TEST_1(test_nat_remove_filter(ctx->nat, f2) == 0);
+
+  if (print_headings)
+    printf("TEST NUA-4.7.1: PASSED\n");
+
+ completed_4_7_1:
+
+  if (!ctx->nat)
+    goto completed_4_7_2;
+
+  if (print_headings)
+    printf("TEST NUA-4.7.2: ACK timeout (ACK filtered)\n");
+
+  TEST_1(a_call->nh = nua_handle(a->nua, a_call, SIPTAG_TO(b->to), TAG_END()));
+
+  TEST_1(f = test_nat_add_filter(ctx->nat, filter_ACK, NULL, nat_outbound));
+  
+  INVITE(a, a_call, a_call->nh,
+	 TAG_IF(!ctx->proxy_tests, NUTAG_URL(b->contact->m_url)),
+	 SIPTAG_SUBJECT_STR("NUA-4.7.2"),
+	 SOATAG_USER_SDP_STR(a_call->sdp),
+	 TAG_END());
+  run_ab_until(ctx, -1, until_terminated, -1, accept_call);
+
+  /*
+ A     accept_call    B
+ |                    |
+ |-------INVITE------>|
+ |<----100 Trying-----|
+ |                    |
+ |<----180 Ringing----|
+ |                    |
+ |<--------200--------|
+ |--------ACK-----X   |
+ |                    |
+ |<--------200--------|
+ |--------ACK-----X   |
+ |                    |
+ |<--------200--------|
+ |--------ACK-----X   |
+ |                    |
+ |<--------BYE--------|
+ |--------200 OK----->|
+
+  */
+
+  /*
+  */
+
+  TEST_1(e = a->events->head); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_calling); /* CALLING */
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_r_invite);
+  TEST(e->data->e_status, 180);
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_proceeding); 
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_r_invite);
+  TEST(e->data->e_status, 200);
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_ready); /* READY */
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_bye);
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_terminated); 
+  TEST_1(!e->next);
+
+  /*
+   Server transitions:
+   -(S1)-> RECEIVED -(S2a)-> EARLY -(S3b)-> COMPLETED -(S5)-> TERMINATING
+   -(S10)-> TERMINATED -X
+  */
+  TEST_1(e = b->events->head); TEST_E(e->data->e_event, nua_i_invite);
+  TEST(e->data->e_status, 100);
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_received); /* RECEIVED */
+  TEST_1(is_offer_recv(e->data->e_tags));
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_early); /* EARLY */
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_completed); /* COMPLETED */
+  TEST_1(is_answer_sent(e->data->e_tags));
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_error);
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_terminating); /* TERMINATING */
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_r_bye);
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_terminated); /* TERMINATED */
+  TEST_1(!e->next);
+
+  free_events_in_list(ctx, a->events);
+  nua_handle_destroy(a_call->nh), a_call->nh = NULL;
+  free_events_in_list(ctx, b->events);
+  nua_handle_destroy(b_call->nh), b_call->nh = NULL;
+
+  TEST_1(test_nat_remove_filter(ctx->nat, f) == 0);
+
+  if (print_headings)
+    printf("TEST NUA-4.7.2: PASSED\n");
+
+ completed_4_7_2:
+
+  if (print_headings)
+    printf("TEST NUA-4.7.3: sending ACK fails\n");
+
+  TEST_1(a_call->nh = nua_handle(a->nua, a_call, SIPTAG_TO(b->to), TAG_END()));
+
+  INVITE(a, a_call, a_call->nh,
+	 TAG_IF(!ctx->proxy_tests, NUTAG_URL(b->contact->m_url)),
+	 SIPTAG_SUBJECT_STR("NUA-4.7.3"),
+	 SOATAG_USER_SDP_STR(a_call->sdp),
+	 NUTAG_AUTOACK(0),
+	 TAG_END());
+  run_ab_until(ctx, -1, call_with_bad_ack, -1, accept_call);
+
+  /*
+ A     accept_call    B
+ |                    |
+ |-------INVITE------>|
+ |<----100 Trying-----|
+ |                    |
+ |<----180 Ringing----|
+ |                    |
+ |<--------200--------|
+ |--ACK-X             |
+ |                    |
+ |<--------BYE--------|
+ |--------200 OK----->|
+
+  */
+
+  /*
+  */
+
+  TEST_1(e = a->events->head); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_calling); /* CALLING */
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_r_invite);
+  TEST(e->data->e_status, 180);
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_proceeding); 
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_r_invite);
+  TEST(e->data->e_status, 200);
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_completing); /* COMPLETING */
+  /* try to send ACK */
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_terminating);
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_r_bye);
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_terminated); 
+  TEST_1(!e->next);
+
+  /*
+   Server transitions:
+   -(S1)-> RECEIVED -(S2a)-> EARLY -(S3b)-> COMPLETED -(S5)-> TERMINATING
+   -(S10)-> TERMINATED -X
+  */
+  TEST_1(e = b->events->head); TEST_E(e->data->e_event, nua_i_invite);
+  TEST(e->data->e_status, 100);
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_received); /* RECEIVED */
+  TEST_1(is_offer_recv(e->data->e_tags));
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_early); /* EARLY */
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_completed); /* COMPLETED */
+  TEST_1(is_answer_sent(e->data->e_tags));
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_bye);
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_terminated); /* TERMINATED */
+  TEST_1(!e->next);
+
+  free_events_in_list(ctx, a->events);
+  nua_handle_destroy(a_call->nh), a_call->nh = NULL;
+  free_events_in_list(ctx, b->events);
+  nua_handle_destroy(b_call->nh), b_call->nh = NULL;
+
+  if (print_headings)
+    printf("TEST NUA-4.7.3: PASSED\n");
+
+  if (!ctx->nat)
+    goto completed_4_7_4;
+
+  if (print_headings)
+    printf("TEST NUA-4.7.4: 200 OK timeout after client has timed out\n");
+
+  if (ctx->expensive)
+    nua_set_params(b->nua, NTATAG_SIP_T1X64(34000), TAG_END());
+  else
+    nua_set_params(b->nua, NTATAG_SIP_T1X64(4000), TAG_END());
+  run_b_until(ctx, nua_r_set_params, until_final_response);
+
+  TEST_1(a_call->nh = nua_handle(a->nua, a_call, SIPTAG_TO(b->to), TAG_END()));
+
+  TEST_1(f = test_nat_add_filter(ctx->nat, filter_ACK, NULL, nat_outbound));
+  
+  INVITE(a, a_call, a_call->nh,
+	 TAG_IF(!ctx->proxy_tests, NUTAG_URL(b->contact->m_url)),
+	 SIPTAG_SUBJECT_STR("NUA-4.7.4"),
+	 SOATAG_USER_SDP_STR(a_call->sdp),
+	 TAG_END());
+  run_ab_until(ctx, -1, until_terminated, -1, accept_call);
+
+  /*
+ A     accept_call    B
+ |                    |
+ |-------INVITE------>|
+ |<----100 Trying-----|
+ |                    |
+ |<----180 Ringing----|
+ |                    |
+ |<--------200--------| Timer H'
+ |--------ACK-----X   X--+
+ |                    |  |
+ |<--------200--------|  |
+ |--------ACK-----X   |  |
+ |                    |  |
+ |<--------200--------|  |
+ |                    |  |
+ |<--------200--------|  |
+ |                    |  |
+ |                    |<-+
+ |<--------BYE--------|
+ |--------200 OK----->|
+
+  */
+
+  /*
+  */
+
+  TEST_1(e = a->events->head); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_calling); /* CALLING */
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_r_invite);
+  TEST(e->data->e_status, 180);
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_proceeding); 
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_r_invite);
+  TEST(e->data->e_status, 200);
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_ready); /* READY */
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_bye);
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_terminated); 
+  TEST_1(!e->next);
+
+  /*
+   Server transitions:
+   -(S1)-> RECEIVED -(S2a)-> EARLY -(S3b)-> COMPLETED -(S5)-> TERMINATING
+   -(S10)-> TERMINATED -X
+  */
+  TEST_1(e = b->events->head); TEST_E(e->data->e_event, nua_i_invite);
+  TEST(e->data->e_status, 100);
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_received); /* RECEIVED */
+  TEST_1(is_offer_recv(e->data->e_tags));
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_early); /* EARLY */
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_completed); /* COMPLETED */
+  TEST_1(is_answer_sent(e->data->e_tags));
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_error);
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_terminating); 
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_r_bye);
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_terminated); /* TERMINATED */
+  TEST_1(!e->next);
+
+  free_events_in_list(ctx, a->events);
+  nua_handle_destroy(a_call->nh), a_call->nh = NULL;
+  free_events_in_list(ctx, b->events);
+  nua_handle_destroy(b_call->nh), b_call->nh = NULL;
+
+  TEST_1(test_nat_remove_filter(ctx->nat, f) == 0);
+
+  if (print_headings)
+    printf("TEST NUA-4.7.4: PASSED\n");
+
+  nua_set_params(b->nua, NTATAG_SIP_T1X64(2000), TAG_END());
+  run_b_until(ctx, nua_r_set_params, until_final_response);
+
+ completed_4_7_4:
+
+  /* XXX - PRACK timeout, PRACK failing, media failing, re-INVITEs */
+
+  if (print_headings)
+    printf("TEST NUA-4.7: PASSED\n");
+
+  END();
+}
+
+int call_with_bad_ack(CONDITION_PARAMS)
+{
+  if (!check_handle(ep, call, nh, SIP_500_INTERNAL_SERVER_ERROR))
+    return 0;
+
+  save_event_in_list(ctx, event, ep, call);
+
+  if (event == nua_r_invite && 200 <= status && status < 300) {
+    ACK(ep, call, nh,
+	/* Syntax error - sending ACK fails, we send BYE */
+	SIPTAG_MAX_FORWARDS_STR("blue"),
+	TAG_END());
+  }
+
+  return event == nua_i_state && callstate(tags) == nua_callstate_terminated;
+}
+
+/* ---------------------------------------------------------------------- */
+
+int test_rejects(struct context *ctx)
+{
+  return
+    test_reject_401_bad(ctx) ||
+    test_reject_a(ctx) ||
+    test_reject_b(ctx) ||
+    test_reject_302(ctx) ||
+    test_reject_401(ctx) ||
+    test_mime_negotiation(ctx) ||
+    test_reject_401_aka(ctx) ||
+    test_call_timeouts(ctx) ||
+    0;
+}

Added: freeswitch/trunk/libs/sofia-sip/tests/test_cancel_bye.c
==============================================================================
--- (empty file)
+++ freeswitch/trunk/libs/sofia-sip/tests/test_cancel_bye.c	Wed May 14 15:10:54 2008
@@ -0,0 +1,1663 @@
+/*
+ * This file is part of the Sofia-SIP package
+ *
+ * Copyright (C) 2005 Nokia Corporation.
+ *
+ * Contact: Pekka Pessi <pekka.pessi at nokia.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA
+ *
+ */
+
+/**@CFILE test_cancel_bye.c
+ * @brief Test CANCEL, weird BYE and handle destroy
+ *
+ * @author Pekka Pessi <Pekka.Pessi at nokia.com>
+ * @author Martti Mela <Martti Mela at nokia.com>
+ *
+ * @date Created: Wed Aug 17 12:12:12 EEST 2005 ppessi
+ */
+
+#include "config.h"
+
+#include "test_nua.h"
+#include <sofia-sip/su_tag_class.h>
+
+#if HAVE_FUNC
+#elif HAVE_FUNCTION
+#define __func__ __FUNCTION__
+#else
+#define __func__ "test_cancel_bye"
+#endif
+
+/* ======================================================================== */
+
+/* Cancel cases:
+
+
+   A			B
+   |-------INVITE------>|
+   |<----100 Trying-----|
+   |			|
+   |------CANCEL------->|
+   |<------200 OK-------|
+   |			|
+   |<-------487---------|
+   |--------ACK-------->|
+   |			|
+   |			|
+
+   Client transitions:
+   INIT -(C1)-> CALLING -(C6a)-> TERMINATED
+
+   Server transitions:
+   INIT -(S1)-> RECEIVED -(S6a)-> TERMINATED
+
+   A			B
+   |-------INVITE------>|
+   |<----100 Trying-----|
+   |			|
+   |<----180 Ringing----|
+   |			|
+   |------CANCEL------->|
+   |<------200 OK-------|
+   |			|
+   |<-------487---------|
+   |--------ACK-------->|
+   |			|
+   |			|
+
+   Client transitions:
+   INIT -(C1)-> CALLING -(C2)-> PROCEEDING -(C6b)-> TERMINATED
+
+   Server transitions:
+   INIT -(S1)-> RECEIVED -(S2a)-> EARLY -(S6b)-> TERMINATED
+
+*/
+
+int cancel_when_calling(CONDITION_PARAMS)
+{
+  if (!(check_handle(ep, call, nh, SIP_500_INTERNAL_SERVER_ERROR)))
+    return 0;
+
+  save_event_in_list(ctx, event, ep, call);
+
+  switch (callstate(tags)) {
+  case nua_callstate_calling:
+    CANCEL(ep, call, nh,
+	   /* sf.net bug #173323 */
+	   SIPTAG_CALL_ID_STR("non-existing-call-id"),
+	   TAG_END());
+    return 0;
+  case nua_callstate_terminated:
+    return 1;
+  default:
+    return 0;
+  }
+}
+
+int bye_when_calling(CONDITION_PARAMS)
+{
+  if (!(check_handle(ep, call, nh, SIP_500_INTERNAL_SERVER_ERROR)))
+    return 0;
+
+  save_event_in_list(ctx, event, ep, call);
+
+  switch (callstate(tags)) {
+  case nua_callstate_calling:
+    BYE(ep, call, nh, TAG_END());
+    return 0;
+  case nua_callstate_terminated:
+    return 1;
+  default:
+    return 0;
+  }
+}
+
+
+int cancel_when_ringing(CONDITION_PARAMS)
+{
+  if (!(check_handle(ep, call, nh, SIP_500_INTERNAL_SERVER_ERROR)))
+    return 0;
+
+  save_event_in_list(ctx, event, ep, call);
+
+  switch (callstate(tags)) {
+  case nua_callstate_proceeding:
+    CANCEL(ep, call, nh, TAG_END());
+    return 0;
+  case nua_callstate_ready:
+    return 1;
+  case nua_callstate_terminated:
+    return 1;
+  default:
+    return 0;
+  }
+}
+
+
+int alert_call(CONDITION_PARAMS)
+{
+  if (!(check_handle(ep, call, nh, SIP_500_INTERNAL_SERVER_ERROR)))
+    return 0;
+
+  save_event_in_list(ctx, event, ep, call);
+
+  switch (callstate(tags)) {
+  case nua_callstate_received:
+    RESPOND(ep, call, nh, SIP_180_RINGING, TAG_END());
+    return 0;
+  case nua_callstate_terminated:
+    return 1;
+  default:
+    return 0;
+  }
+}
+
+int accept_after_183(CONDITION_PARAMS);
+
+int test_call_cancel(struct context *ctx)
+{
+  BEGIN();
+
+  struct endpoint *a = &ctx->a,  *b = &ctx->b;
+  struct call *a_call = a->call, *b_call = b->call;
+  struct event *e;
+
+  a_call->sdp = "m=audio 5008 RTP/AVP 8";
+  b_call->sdp = "m=audio 5010 RTP/AVP 0 8";
+
+  if (print_headings)
+    printf("TEST NUA-5.1.1: cancel call\n");
+
+  TEST_1(a_call->nh = nua_handle(a->nua, a_call, SIPTAG_TO(b->to), TAG_END()));
+
+  INVITE(a, a_call, a_call->nh,
+	 TAG_IF(!ctx->proxy_tests, NUTAG_URL(b->contact->m_url)),
+	 SOATAG_USER_SDP_STR(a_call->sdp),
+	 TAG_END());
+
+  run_ab_until(ctx, -1, cancel_when_calling, -1, until_terminated);
+
+  /* Client transitions:
+     INIT -(C1)-> CALLING: nua_invite(), nua_i_state, nua_cancel()
+     CALLING -(C6a)-> TERMINATED: nua_r_invite(487), nua_i_state
+  */
+  TEST_1(e = a->events->head); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_calling); /* CALLING */
+  TEST_1(is_offer_sent(e->data->e_tags));
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_r_cancel);
+  TEST(e->data->e_status, 200);
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_r_invite);
+  TEST(e->data->e_status, 487);
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_terminated); /* TERMINATED */
+  TEST_1(!e->next);
+
+  /*
+   Server transitions:
+   INIT -(S1)-> RECEIVED: nua_i_invite, nua_i_state
+   RECEIVED -(S6a)--> TERMINATED: nua_i_cancel, nua_i_state
+  */
+  TEST_1(e = b->events->head); TEST_E(e->data->e_event, nua_i_invite);
+  TEST(e->data->e_status, 100);
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_received); /* RECEIVED */
+  TEST_1(is_offer_recv(e->data->e_tags));
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_cancel);
+  TEST(e->data->e_status, 200);
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_terminated); /* TERMINATED */
+  TEST_1(!e->next);
+
+  free_events_in_list(ctx, a->events);
+  nua_handle_destroy(a_call->nh), a_call->nh = NULL;
+
+  free_events_in_list(ctx, b->events);
+  nua_handle_destroy(b_call->nh), b_call->nh = NULL;
+
+  if (print_headings)
+    printf("TEST NUA-5.1.1: PASSED\n");
+
+  /* ------------------------------------------------------------------------ */
+
+  if (print_headings)
+    printf("TEST NUA-5.1.2: cancel call (with nua_bye())\n");
+
+  TEST_1(a_call->nh = nua_handle(a->nua, a_call, SIPTAG_TO(b->to), TAG_END()));
+
+  INVITE(a, a_call, a_call->nh,
+	 TAG_IF(!ctx->proxy_tests, NUTAG_URL(b->contact->m_url)),
+	 SIPTAG_SUBJECT_STR("TEST NUA-5.1.2"),
+	 SOATAG_USER_SDP_STR(a_call->sdp),
+	 TAG_END());
+
+  run_ab_until(ctx, -1, bye_when_calling, -1, until_terminated);
+
+  /* Client transitions:
+     INIT -(C1)-> CALLING: nua_invite(), nua_i_state, nua_cancel()
+     CALLING -(C6a)-> TERMINATED: nua_r_invite(487), nua_i_state
+  */
+  TEST_1(e = a->events->head); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_calling); /* CALLING */
+  TEST_1(is_offer_sent(e->data->e_tags));
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_r_bye);
+  TEST(e->data->e_status, 200);
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_r_invite);
+  TEST(e->data->e_status, 487);
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_terminated); /* TERMINATED */
+  TEST_1(!e->next);
+
+  /*
+   Server transitions:
+   INIT -(S1)-> RECEIVED: nua_i_invite, nua_i_state
+   RECEIVED -(S6a)--> TERMINATED: nua_i_cancel, nua_i_state
+  */
+  TEST_1(e = b->events->head); TEST_E(e->data->e_event, nua_i_invite);
+  TEST(e->data->e_status, 100);
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_received); /* RECEIVED */
+  TEST_1(is_offer_recv(e->data->e_tags));
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_cancel);
+  TEST(e->data->e_status, 200);
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_terminated); /* TERMINATED */
+  TEST_1(!e->next);
+
+  free_events_in_list(ctx, a->events);
+  nua_handle_destroy(a_call->nh), a_call->nh = NULL;
+
+  free_events_in_list(ctx, b->events);
+  nua_handle_destroy(b_call->nh), b_call->nh = NULL;
+
+  if (print_headings)
+    printf("TEST NUA-5.1.2: PASSED\n");
+
+ /* ----------------------------------------------------------------------- */
+ 
+  if (print_headings)
+    printf("TEST NUA-5.2.1: cancel call when ringing\n");
+
+  TEST_1(a_call->nh = nua_handle(a->nua, a_call, SIPTAG_TO(b->to), TAG_END()));
+
+  INVITE(a, a_call, a_call->nh,
+	 TAG_IF(!ctx->proxy_tests, NUTAG_URL(b->contact->m_url)),
+	 SOATAG_USER_SDP_STR(a_call->sdp),
+	 /*SIPTAG_REJECT_CONTACT_STR("*;audio=FALSE"),*/
+	 TAG_END());
+
+  run_ab_until(ctx, -1, cancel_when_ringing, -1, alert_call);
+
+  /* Client transitions:
+     INIT -(C1)-> CALLING: nua_invite(), nua_i_state
+     CALLING -(C2)-> PROCEEDING: nua_r_invite(180, nua_i_state, nua_cancel()
+     PROCEEDING -(C6b)-> TERMINATED: nua_r_invite(487), nua_i_state
+  */
+  TEST_1(e = a->events->head); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_calling); /* CALLING */
+  TEST_1(is_offer_sent(e->data->e_tags));
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_r_invite);
+  TEST(e->data->e_status, 180);
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_proceeding); /* PROCEEDING */
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_r_cancel);
+  TEST(e->data->e_status, 200);
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_r_invite);
+  TEST(e->data->e_status, 487);
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_terminated); /* TERMINATED */
+  TEST_1(!e->next);
+
+  /*
+   Server transitions:
+   INIT -(S1)-> RECEIVED: nua_i_invite, nua_i_state
+   RECEIVED -(S2a)-> EARLY: nua_respond(180), nua_i_state
+   EARLY -(S6b)--> TERMINATED: nua_i_cancel, nua_i_state
+  */
+  TEST_1(e = b->events->head); TEST_E(e->data->e_event, nua_i_invite);
+  TEST(e->data->e_status, 100);
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_received); /* RECEIVED */
+  TEST_1(is_offer_recv(e->data->e_tags));
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_early); /* EARLY */
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_cancel);
+  TEST(e->data->e_status, 200);
+  /* Check for bug #1326727 */
+  TEST_1(e->data->e_msg);
+#if 0
+  TEST_1(sip_object(e->data->e_msg)->sip_reject_contact);
+  TEST_1(sip_object(e->data->e_msg)->sip_reject_contact->cp_params &&
+	 sip_object(e->data->e_msg)->sip_reject_contact->cp_params[0]);
+  TEST_S(sip_object(e->data->e_msg)->sip_reject_contact->cp_params[0],
+	 "audio=FALSE");
+#endif
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_terminated); /* TERMINATED */
+  TEST_1(!e->next);
+
+  free_events_in_list(ctx, a->events);
+  nua_handle_destroy(a_call->nh), a_call->nh = NULL;
+
+  free_events_in_list(ctx, b->events);
+  nua_handle_destroy(b_call->nh), b_call->nh = NULL;
+
+  if (print_headings)
+    printf("TEST NUA-5.2.1: PASSED\n");
+
+  /* ------------------------------------------------------------------------ */
+  if (print_headings)
+    printf("TEST NUA-5.2.2: CANCEL call when server waits for PRACK\n");
+
+  TEST_1(a_call->nh = nua_handle(a->nua, a_call, SIPTAG_TO(b->to), TAG_END()));
+
+  INVITE(a, a_call, a_call->nh,
+	 TAG_IF(!ctx->proxy_tests, NUTAG_URL(b->contact->m_url)),
+	 SOATAG_USER_SDP_STR(a_call->sdp),
+	 NUTAG_APPL_METHOD("PRACK"),
+	 /*SIPTAG_REJECT_CONTACT_STR("*;audio=FALSE"),*/
+	 TAG_END());
+
+  run_ab_until(ctx, -1, cancel_when_ringing, -1, accept_after_183);
+
+  free_events_in_list(ctx, a->events);
+  free_events_in_list(ctx, b->events);
+
+  nua_handle_destroy(a_call->nh), a_call->nh = NULL;
+  nua_handle_destroy(b_call->nh), b_call->nh = NULL;
+
+  if (print_headings)
+    printf("TEST NUA-5.2.2: PASSED\n");
+
+
+  END();
+}
+
+int accept_after_183(CONDITION_PARAMS)
+{
+  if (!(check_handle(ep, call, nh, SIP_500_INTERNAL_SERVER_ERROR)))
+    return 0;
+
+  save_event_in_list(ctx, event, ep, call);
+
+  switch (callstate(tags)) {
+  case nua_callstate_received:
+    RESPOND(ep, call, nh, SIP_183_SESSION_PROGRESS, TAG_END());
+    RESPOND(ep, call, nh, SIP_200_OK, TAG_END());
+    return 0;
+  case nua_callstate_terminated:
+    return 1;
+  default:
+    return 0;
+  }
+}
+
+
+/* ======================================================================== */
+/* Destroy call handle */
+
+int destroy_when_calling(CONDITION_PARAMS)
+{
+  if (!(check_handle(ep, call, nh, SIP_500_INTERNAL_SERVER_ERROR)))
+    return 0;
+
+  save_event_in_list(ctx, event, ep, call);
+
+  switch (callstate(tags)) {
+  case nua_callstate_calling:
+    DESTROY(ep, call, nh);
+    return 1;
+  default:
+    return 0;
+  }
+}
+
+int destroy_when_completing(CONDITION_PARAMS)
+{
+  if (!(check_handle(ep, call, nh, SIP_500_INTERNAL_SERVER_ERROR)))
+    return 0;
+
+  save_event_in_list(ctx, event, ep, call);
+
+  switch (callstate(tags)) {
+  case nua_callstate_completing:
+    DESTROY(ep, call, nh);
+    return 1;
+  case nua_callstate_ready:
+    return 1;
+  case nua_callstate_terminated:
+    if (call)
+      nua_handle_destroy(call->nh), call->nh = NULL;
+    return 1;
+  default:
+    return 0;
+  }
+}
+
+int test_call_destroy_1(struct context *ctx)
+{
+  BEGIN();
+
+  struct endpoint *a = &ctx->a,  *b = &ctx->b;
+  struct call *a_call = a->call, *b_call = b->call;
+  struct event *e;
+
+  if (print_headings)
+    printf("TEST NUA-5.3: destroy when calling\n");
+
+  TEST_1(a_call->nh = nua_handle(a->nua, a_call, SIPTAG_TO(b->to), TAG_END()));
+
+  INVITE(a, a_call, a_call->nh,
+	 TAG_IF(!ctx->proxy_tests, NUTAG_URL(b->contact->m_url)),
+	 SOATAG_USER_SDP_STR(a_call->sdp),
+	 TAG_END());
+
+  run_ab_until(ctx, -1, destroy_when_calling, -1, until_terminated);
+
+  /* Client transitions:
+     INIT -(C1)-> CALLING: nua_invite(), ...
+  */
+  TEST_1(e = a->events->head); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_calling); /* CALLING */
+  TEST_1(is_offer_sent(e->data->e_tags));
+  TEST_1(!e->next);
+
+  free_events_in_list(ctx, a->events);
+  nua_handle_destroy(a_call->nh), a_call->nh = NULL;
+
+  /*
+   Server transitions:
+   INIT -(S1)-> RECEIVED: nua_i_invite, nua_i_state
+   RECEIVED -(S6a)--> TERMINATED: nua_i_cancel, nua_i_state
+  */
+  TEST_1(e = b->events->head); TEST_E(e->data->e_event, nua_i_invite);
+  TEST(e->data->e_status, 100);
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_received); /* RECEIVED */
+  TEST_1(is_offer_recv(e->data->e_tags));
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_cancel);
+  TEST(e->data->e_status, 200);
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_terminated); /* TERMINATED */
+  TEST_1(!e->next);
+
+  free_events_in_list(ctx, b->events);
+  nua_handle_destroy(b_call->nh), b_call->nh = NULL;
+
+  if (print_headings)
+    printf("TEST NUA-5.3: PASSED\n");
+
+  END();
+}
+
+int accept_until_terminated(CONDITION_PARAMS)
+{
+  if (!(check_handle(ep, call, nh, SIP_500_INTERNAL_SERVER_ERROR)))
+    return 0;
+
+  save_event_in_list(ctx, event, ep, call);
+
+  switch (callstate(tags)) {
+  case nua_callstate_received:
+    RESPOND(ep, call, nh, SIP_180_RINGING, TAG_END());
+    return 0;
+  case nua_callstate_early:
+    RESPOND(ep, call, nh, SIP_200_OK,
+	    TAG_IF(call->sdp, SOATAG_USER_SDP_STR(call->sdp)),
+	    TAG_END());
+    return 0;
+  case nua_callstate_completed:
+  case nua_callstate_ready:
+    return 0;
+  case nua_callstate_terminated:
+    if (call)
+      nua_handle_destroy(call->nh), call->nh = NULL;
+    return 1;
+  default:
+    return 0;
+  }
+}
+
+int test_call_destroy_2(struct context *ctx)
+{
+  BEGIN();
+
+  struct endpoint *a = &ctx->a,  *b = &ctx->b;
+  struct call *a_call = a->call, *b_call = b->call;
+  struct event *e;
+
+  if (print_headings)
+    printf("TEST NUA-5.4: destroy when completing\n");
+
+  TEST_1(a_call->nh = nua_handle(a->nua, a_call, SIPTAG_TO(b->to), TAG_END()));
+
+  INVITE(a, a_call, a_call->nh,
+	 TAG_IF(!ctx->proxy_tests, NUTAG_URL(b->contact->m_url)),
+	 NUTAG_AUTOACK(0),
+	 SOATAG_USER_SDP_STR(a_call->sdp),
+	 TAG_END());
+
+  run_ab_until(ctx, -1, destroy_when_completing, -1, accept_until_terminated);
+
+  /* Client transitions:
+     INIT -(C1)-> CALLING: nua_invite(), ...
+  */
+  TEST_1(e = a->events->head); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_calling); /* CALLING */
+  TEST_1(is_offer_sent(e->data->e_tags));
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_r_invite);
+  TEST(e->data->e_status, 180);
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_proceeding); /* PROCEEDING */
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_r_invite);
+  TEST(e->data->e_status, 200);
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_completing); /* COMPLETING */
+  TEST_1(is_answer_recv(e->data->e_tags));
+  TEST_1(!e->next);
+
+  free_events_in_list(ctx, a->events);
+  nua_handle_destroy(a_call->nh), a_call->nh = NULL;
+
+  /*
+   Server transitions:
+   INIT -(S1)-> RECEIVED: nua_i_invite, nua_i_state
+   RECEIVED -(S2a)-> EARLY: nua_respond(), nua_i_state
+   EARLY -(S3b)-> COMPLETED: nua_respond(), nua_i_state
+   COMPLETED -(S4)-> READY: nua_i_ack, nua_i_state
+  */
+  TEST_1(e = b->events->head); TEST_E(e->data->e_event, nua_i_invite);
+  TEST(e->data->e_status, 100);
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_received); /* RECEIVED */
+  TEST_1(is_offer_recv(e->data->e_tags));
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_early); /* EARLY */
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_completed); /* COMPLETED */
+  TEST_1(is_answer_sent(e->data->e_tags));
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_ack);
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_ready); /* READY */
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_bye);
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_terminated); /* TERMINATED */
+  TEST_1(!e->next);
+
+  free_events_in_list(ctx, b->events);
+  nua_handle_destroy(b_call->nh), b_call->nh = NULL;
+
+  if (print_headings)
+    printf("TEST NUA-5.4: PASSED\n");
+
+  END();
+}
+
+int destroy_when_early(CONDITION_PARAMS)
+{
+  if (!(check_handle(ep, call, nh, SIP_500_INTERNAL_SERVER_ERROR)))
+    return 0;
+
+  save_event_in_list(ctx, event, ep, call);
+
+  switch (callstate(tags)) {
+  case nua_callstate_received:
+    RESPOND(ep, call, nh, SIP_180_RINGING, TAG_END());
+    return 0;
+  case nua_callstate_early:
+    if (call)
+      DESTROY(ep, call, nh), call->nh = NULL;
+    return 1;
+  case nua_callstate_completed:
+  case nua_callstate_ready:
+  case nua_callstate_terminated:
+    if (call)
+      DESTROY(ep, call, nh), call->nh = NULL;
+    return 1;
+  default:
+    return 0;
+  }
+}
+
+int test_call_destroy_3(struct context *ctx)
+{
+  BEGIN();
+
+  struct endpoint *a = &ctx->a,  *b = &ctx->b;
+  struct call *a_call = a->call, *b_call = b->call;
+  struct event *e;
+
+  if (print_headings)
+    printf("TEST NUA-5.5: destroy when early\n");
+
+  TEST_1(a_call->nh = nua_handle(a->nua, a_call, SIPTAG_TO(b->to), TAG_END()));
+
+  INVITE(a, a_call, a_call->nh,
+	 TAG_IF(!ctx->proxy_tests, NUTAG_URL(b->contact->m_url)),
+	 NUTAG_AUTOACK(0),
+	 SOATAG_USER_SDP_STR(a_call->sdp),
+	 TAG_END());
+
+  run_ab_until(ctx, -1, until_terminated, -1, destroy_when_early);
+
+  /* Client transitions:
+     INIT -(C1)-> CALLING: nua_invite(), ...
+  */
+  TEST_1(e = a->events->head); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_calling); /* CALLING */
+  TEST_1(is_offer_sent(e->data->e_tags));
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_r_invite);
+  TEST(e->data->e_status, 180);
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_proceeding); /* PROCEEDING */
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_r_invite);
+  TEST(e->data->e_status, 480);
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_terminated); /* TERMINATED */
+  TEST_1(!e->next);
+  TEST_1(!e->next);
+
+  free_events_in_list(ctx, a->events);
+  nua_handle_destroy(a_call->nh), a_call->nh = NULL;
+
+  /*
+   Server transitions:
+   INIT -(S1)-> RECEIVED: nua_i_invite, nua_i_state
+   RECEIVED -(S2a)-> EARLY: nua_respond(), nua_i_state
+   EARLY -(S3b)-> COMPLETED: nua_respond(), nua_i_state ... DESTROY
+  */
+  TEST_1(e = b->events->head); TEST_E(e->data->e_event, nua_i_invite);
+  TEST(e->data->e_status, 100);
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_received); /* RECEIVED */
+  TEST_1(is_offer_recv(e->data->e_tags));
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_early); /* EARLY */
+  TEST_1(!e->next);  
+
+  free_events_in_list(ctx, b->events);
+  nua_handle_destroy(b_call->nh), b_call->nh = NULL;
+
+  if (print_headings)
+    printf("TEST NUA-5.5: PASSED\n");
+
+  END();
+}
+
+int destroy_when_completed(CONDITION_PARAMS)
+{
+  if (!(check_handle(ep, call, nh, SIP_500_INTERNAL_SERVER_ERROR)))
+    return 0;
+
+  save_event_in_list(ctx, event, ep, call);
+
+  switch (callstate(tags)) {
+  case nua_callstate_received:
+    RESPOND(ep, call, nh, SIP_180_RINGING, TAG_END());
+    return 0;
+  case nua_callstate_early:
+    RESPOND(ep, call, nh, SIP_200_OK,
+	    TAG_IF(call->sdp, SOATAG_USER_SDP_STR(call->sdp)),
+	    TAG_END());
+    return 0;
+  case nua_callstate_completed:
+  case nua_callstate_ready:
+  case nua_callstate_terminated:
+    if (call)
+      DESTROY(ep, call, nh), call->nh = NULL;
+    return 1;
+  default:
+    return 0;
+  }
+}
+
+int test_call_destroy_4(struct context *ctx)
+{
+  BEGIN();
+
+  struct endpoint *a = &ctx->a,  *b = &ctx->b;
+  struct call *a_call = a->call, *b_call = b->call;
+  struct event *e;
+
+  if (print_headings)
+    printf("TEST NUA-5.6: destroy when completed\n");
+
+  TEST_1(a_call->nh = nua_handle(a->nua, a_call, SIPTAG_TO(b->to), TAG_END()));
+
+  INVITE(a, a_call, a_call->nh,
+	 TAG_IF(!ctx->proxy_tests, NUTAG_URL(b->contact->m_url)),
+	 NUTAG_AUTOACK(0),
+	 SOATAG_USER_SDP_STR(a_call->sdp),
+	 TAG_END());
+
+  run_ab_until(ctx, -1, until_terminated, -1, destroy_when_completed);
+
+  /* Client transitions:
+     INIT -(C1)-> CALLING: nua_invite(), ...
+  */
+  TEST_1(e = a->events->head); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_calling); /* CALLING */
+  TEST_1(is_offer_sent(e->data->e_tags));
+  TEST_1(e = e->next); if (e->data->e_event == nua_r_invite) {
+    TEST_E(e->data->e_event, nua_r_invite);
+    TEST(e->data->e_status, 180);
+    TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+    TEST(callstate(e->data->e_tags), nua_callstate_proceeding); /* PROCEEDING */
+    TEST_1(e = e->next); 
+  }
+  if (e->data->e_event == nua_r_invite) {
+    TEST_E(e->data->e_event, nua_r_invite);
+    TEST(e->data->e_status, 200);
+    TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+    TEST(callstate(e->data->e_tags), nua_callstate_completing); /* COMPLETING */
+    TEST_1(is_answer_recv(e->data->e_tags));
+    TEST_1(e = e->next); 
+  }
+  TEST_E(e->data->e_event, nua_i_bye);
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_terminated); /* TERMINATED */
+
+  free_events_in_list(ctx, a->events);
+  nua_handle_destroy(a_call->nh), a_call->nh = NULL;
+
+  /*
+   Server transitions:
+   INIT -(S1)-> RECEIVED: nua_i_invite, nua_i_state
+   RECEIVED -(S2a)-> EARLY: nua_respond(), nua_i_state
+   EARLY -(S3b)-> COMPLETED: nua_respond(), nua_i_state ... DESTROY
+  */
+  TEST_1(e = b->events->head); TEST_E(e->data->e_event, nua_i_invite);
+  TEST(e->data->e_status, 100);
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_received); /* RECEIVED */
+  TEST_1(is_offer_recv(e->data->e_tags));
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_early); /* EARLY */
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_completed); /* COMPLETED */
+  TEST_1(is_answer_sent(e->data->e_tags));
+  TEST_1(!e->next);  
+
+  free_events_in_list(ctx, b->events);
+  nua_handle_destroy(b_call->nh), b_call->nh = NULL;
+
+  if (print_headings)
+    printf("TEST NUA-5.6: PASSED\n");
+
+  END();
+}
+
+/* Destroy when one INVITE is queued. */
+int test_call_destroy_5(struct context *ctx)
+{
+  BEGIN();
+
+  struct endpoint *a = &ctx->a,  *b = &ctx->b;
+  struct call *a_call = a->call, *b_call = b->call;
+  struct event *e;
+
+  if (print_headings)
+    printf("TEST NUA-5.7: destroy when re-INVITE is queued\n");
+
+  TEST_1(a_call->nh = nua_handle(a->nua, a_call, SIPTAG_TO(b->to), TAG_END()));
+
+  INVITE(a, a_call, a_call->nh,
+	 TAG_IF(!ctx->proxy_tests, NUTAG_URL(b->contact->m_url)),
+	 NUTAG_AUTOACK(0),
+	 SOATAG_USER_SDP_STR(a_call->sdp),
+	 TAG_END());
+
+  INVITE(a, a_call, a_call->nh, TAG_END());
+
+  run_ab_until(ctx, -1, until_terminated, -1, destroy_when_completed);
+
+  /* Client transitions:
+     INIT -(C1)-> CALLING: nua_invite(), ...
+  */
+  TEST_1(e = a->events->head); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_calling); /* CALLING */
+  TEST_1(is_offer_sent(e->data->e_tags));
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_r_invite);
+  TEST(e->data->e_status, 180);
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_proceeding); /* PROCEEDING */
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_r_invite);
+  TEST(e->data->e_status, 200);
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_completing); /* COMPLETING */
+  TEST_1(is_answer_recv(e->data->e_tags));
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_bye);
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_r_invite);
+  TEST(e->data->e_status, 481);
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_terminated); /* TERMINATED */
+  TEST_1(!e->next);
+
+  free_events_in_list(ctx, a->events);
+  nua_handle_destroy(a_call->nh), a_call->nh = NULL;
+
+  /*
+   Server transitions:
+   INIT -(S1)-> RECEIVED: nua_i_invite, nua_i_state
+   RECEIVED -(S2a)-> EARLY: nua_respond(), nua_i_state
+   EARLY -(S3b)-> COMPLETED: nua_respond(), nua_i_state ... DESTROY
+  */
+  TEST_1(e = b->events->head); TEST_E(e->data->e_event, nua_i_invite);
+  TEST(e->data->e_status, 100);
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_received); /* RECEIVED */
+  TEST_1(is_offer_recv(e->data->e_tags));
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_early); /* EARLY */
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_completed); /* COMPLETED */
+  TEST_1(is_answer_sent(e->data->e_tags));
+  TEST_1(!e->next);  
+
+  free_events_in_list(ctx, b->events);
+  nua_handle_destroy(b_call->nh), b_call->nh = NULL;
+
+  if (print_headings)
+    printf("TEST NUA-5.7: PASSED\n");
+
+  END();
+}
+
+int test_call_destroy(struct context *ctx)
+{
+  struct endpoint *a = &ctx->a,  *b = &ctx->b;
+  struct call *a_call = a->call, *b_call = b->call;
+
+  a_call->sdp = "m=audio 5008 RTP/AVP 8";
+  b_call->sdp = "m=audio 5010 RTP/AVP 0 8";
+
+  return 
+    test_call_destroy_1(ctx) ||
+    test_call_destroy_2(ctx) ||
+    test_call_destroy_3(ctx) ||
+    test_call_destroy_4(ctx) ||
+    test_call_destroy_5(ctx);
+}
+
+/* ======================================================================== */
+
+/* Early BYE
+
+   A			B
+   |-------INVITE------>|
+   |<----100 Trying-----|
+   |			|
+   |<----180 Ringing----|
+   |			|
+   |--------BYE-------->|
+   |<------200 OK-------|
+   |			|
+   |<-------487---------|
+   |--------ACK-------->|
+   |			|
+   |			|
+
+   Client transitions:
+   INIT -(C1)-> CALLING -(C2)-> PROCEEDING -(8)-> TERMINATING -> TERMINATED
+
+   Server transitions:
+   INIT -(S1)-> RECEIVED -(S2a)-> EARLY -(S8)-> TERMINATED
+
+*/
+
+int bye_when_ringing(CONDITION_PARAMS)
+{
+  if (!(check_handle(ep, call, nh, SIP_500_INTERNAL_SERVER_ERROR)))
+    return 0;
+
+  save_event_in_list(ctx, event, ep, call);
+
+  switch (callstate(tags)) {
+  case nua_callstate_proceeding:
+    BYE(ep, call, nh, TAG_END());
+    return 0;
+  case nua_callstate_terminated:
+    return 1;
+  default:
+    return 0;
+  }
+}
+
+int bye_when_completing(CONDITION_PARAMS);
+
+static int ack_sent = 0;
+
+size_t count_acks(void *arg, void *message, size_t len)
+{
+  (void)arg;
+
+  if (strncasecmp(message, "ACK sip:", 8) == 0)
+    ack_sent++;
+
+  return len;
+}
+
+int test_bye_before_200(struct context *ctx)
+{
+  BEGIN();
+  struct endpoint *a = &ctx->a,  *b = &ctx->b;
+  struct call *a_call = a->call, *b_call = b->call;
+  struct event *e;
+
+  a_call->sdp = "m=audio 5008 RTP/AVP 8";
+  b_call->sdp = "m=audio 5010 RTP/AVP 0 8";
+
+  if (print_headings)
+    printf("TEST NUA-6.1: BYE call when ringing\n");
+
+  TEST_1(a_call->nh = nua_handle(a->nua, a_call, SIPTAG_TO(b->to), TAG_END()));
+
+  INVITE(a, a_call, a_call->nh,
+	 TAG_IF(!ctx->proxy_tests, NUTAG_URL(b->contact->m_url)),
+	 SOATAG_USER_SDP_STR(a_call->sdp),
+	 TAG_END());
+
+  run_ab_until(ctx, -1, bye_when_ringing, -1, alert_call);
+
+  /* Client transitions:
+     INIT -(C1)-> CALLING: nua_invite(), nua_i_state
+     CALLING -(C2)-> PROCEEDING: nua_r_invite(180, nua_i_state, nua_cancel()
+     PROCEEDING -(C6b)-> TERMINATED: nua_r_invite(487), nua_i_state
+  */
+  TEST_1(e = a->events->head); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_calling); /* CALLING */
+  TEST_1(is_offer_sent(e->data->e_tags));
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_r_invite);
+  TEST(e->data->e_status, 180);
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_proceeding); /* PROCEEDING */
+  TEST_1(e = e->next);
+  if (e->data->e_event == nua_r_bye) {
+    /* We might receive this before or after response to INVITE */
+    /* If afterwards, it will come after nua_i_state and we just ignore it */
+    TEST_E(e->data->e_event, nua_r_bye); TEST(e->data->e_status, 200);
+    TEST_1(e->data->e_msg);
+    /* Forking has not been enabled, so this should be actually a CANCEL */
+    TEST(sip_object(e->data->e_msg)->sip_cseq->cs_method, sip_method_cancel);
+    TEST_1(e = e->next);
+  }
+  TEST_E(e->data->e_event, nua_r_invite); TEST(e->data->e_status, 487);
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_terminated); /* TERMINATED */
+  TEST_1(!e->next);
+
+  /*
+   Server transitions:
+   INIT -(S1)-> RECEIVED: nua_i_invite, nua_i_state
+   RECEIVED -(S2a)-> EARLY: nua_respond(180), nua_i_state
+   EARLY -(S6b)--> TERMINATED: nua_i_cancel, nua_i_state
+  */
+  TEST_1(e = b->events->head); TEST_E(e->data->e_event, nua_i_invite);
+  TEST(e->data->e_status, 100);
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_received); /* RECEIVED */
+  TEST_1(is_offer_recv(e->data->e_tags));
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_early); /* EARLY */
+  /* Forking has not been enabled, so this should be actually a CANCEL */
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_cancel);
+  TEST(e->data->e_status, 200);
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_terminated); /* TERMINATED */
+  TEST_1(!e->next);
+
+  free_events_in_list(ctx, a->events);
+  nua_handle_destroy(a_call->nh), a_call->nh = NULL;
+
+  free_events_in_list(ctx, b->events);
+  nua_handle_destroy(b_call->nh), b_call->nh = NULL;
+
+  if (print_headings)
+    printf("TEST NUA-6.1: PASSED\n");
+
+  END();
+}
+
+
+int bye_when_completing(CONDITION_PARAMS)
+{
+  if (!(check_handle(ep, call, nh, SIP_500_INTERNAL_SERVER_ERROR)))
+    return 0;
+
+  save_event_in_list(ctx, event, ep, call);
+
+  switch (callstate(tags)) {
+  case nua_callstate_completing:
+    ack_sent = 0;
+    BYE(ep, call, nh, TAG_END());
+    return 0;
+  case nua_callstate_terminated:
+    return 1;
+  default:
+    return 0;
+  }
+}
+
+
+int test_bye_before_ack(struct context *ctx)
+{
+  BEGIN();
+  struct endpoint *a = &ctx->a,  *b = &ctx->b;
+  struct call *a_call = a->call, *b_call = b->call;
+  struct event *e;
+  struct nat_filter *f = NULL;
+
+  a_call->sdp = "m=audio 5008 RTP/AVP 8";
+  b_call->sdp = "m=audio 5010 RTP/AVP 0 8";
+
+/* Early BYE 2
+
+   A			B
+   |-------INVITE------>|
+   |<----100 Trying-----|
+   |			|
+   |<----180 Ringing----|
+   |<-------200---------|
+   |			|
+   |--------BYE-------->|
+   |<------200 OK-------|
+   |--------ACK-------->|
+   |			|
+   |			|
+*/
+  if (print_headings)
+    printf("TEST NUA-6.2: BYE call when completing\n");
+
+  if (ctx->nat)
+    TEST_1(f = test_nat_add_filter(ctx->nat, count_acks, NULL, nat_outbound));
+  ack_sent = 0;
+
+  TEST_1(a_call->nh = nua_handle(a->nua, a_call, SIPTAG_TO(b->to), TAG_END()));
+
+  INVITE(a, a_call, a_call->nh,
+	 TAG_IF(!ctx->proxy_tests, NUTAG_URL(b->contact->m_url)),
+	 SOATAG_USER_SDP_STR(a_call->sdp),
+	 NUTAG_AUTOACK(0),
+	 TAG_END());
+
+  run_ab_until(ctx, -1, bye_when_completing, -1, accept_until_terminated);
+
+  /* Client transitions:
+     INIT -(C1)-> CALLING: nua_invite(), nua_i_state
+     CALLING -(C2)-> PROCEEDING: nua_r_invite(180, nua_i_state, nua_cancel()
+     PROCEEDING -(C6b)-> TERMINATED: nua_r_invite(487), nua_i_state
+  */
+  TEST_1(e = a->events->head); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_calling);
+  TEST_1(is_offer_sent(e->data->e_tags));
+
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_r_invite);
+  TEST(e->data->e_status, 180);
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_proceeding); 
+
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_r_invite);
+  TEST(e->data->e_status, 200);
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_completing); 
+  TEST_1(e = e->next);
+
+  TEST_E(e->data->e_event, nua_r_bye); TEST(e->data->e_status, 200);
+  TEST_1(e->data->e_msg);
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_terminated); /* TERMINATED */
+  TEST_1(!e->next);
+
+  if (ctx->nat) {
+    while (ack_sent == 0)
+      su_root_step(ctx->root, 100);
+    TEST_1(ack_sent > 0);
+    TEST_1(test_nat_remove_filter(ctx->nat, f) == 0);
+  }
+
+  /*
+   Server transitions:
+   INIT -(S1)-> RECEIVED: nua_i_invite, nua_i_state
+   RECEIVED -(S2a)-> EARLY: nua_respond(180), nua_i_state
+   EARLY -(S6b)--> TERMINATED: nua_i_bye, nua_i_state
+  */
+  TEST_1(e = b->events->head); TEST_E(e->data->e_event, nua_i_invite);
+  TEST(e->data->e_status, 100);
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_received); /* RECEIVED */
+  TEST_1(is_offer_recv(e->data->e_tags));
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_early); /* EARLY */
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_completed); /* COMPLETED */
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_bye);
+  TEST(e->data->e_status, 200);
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_terminated); /* TERMINATED */
+  TEST_1(!e->next);
+
+  free_events_in_list(ctx, a->events);
+  nua_handle_destroy(a_call->nh), a_call->nh = NULL;
+
+  free_events_in_list(ctx, b->events);
+  nua_handle_destroy(b_call->nh), b_call->nh = NULL;
+
+  if (print_headings)
+    printf("TEST NUA-6.2: PASSED\n");
+
+  END();
+}
+
+int reject_reinvite_401(CONDITION_PARAMS);
+
+int test_bye_after_receiving_401(struct context *ctx)
+{
+  BEGIN();
+  struct endpoint *a = &ctx->a,  *b = &ctx->b;
+  struct call *a_call = a->call, *b_call = b->call;
+  struct event *e;
+
+  a_call->sdp = "m=audio 5008 RTP/AVP 8";
+  b_call->sdp = "m=audio 5010 RTP/AVP 0 8";
+
+/* BYE after receiving 401
+
+   A			B
+   |-------INVITE------>|
+   |<----100 Trying-----|
+   |			|
+   |<----180 Ringing----|
+   |<-------200---------|
+   |--------ACK-------->|
+   |			|
+   |<------INVITE-------|
+   |--------401-------->|
+   |<--------ACK--------|
+   |			|
+   |<--------BYE--------|
+   |------200 OK------->|
+   |			|
+*/
+  if (print_headings)
+    printf("TEST NUA-6.3: BYE after receiving 401\n");
+
+  TEST_1(a_call->nh = nua_handle(a->nua, a_call, SIPTAG_TO(b->to), TAG_END()));
+
+  INVITE(a, a_call, a_call->nh,
+	 TAG_IF(!ctx->proxy_tests, NUTAG_URL(b->contact->m_url)),
+	 SIPTAG_SUBJECT_STR("NUA-6.3"),
+	 SOATAG_USER_SDP_STR(a_call->sdp),
+	 NUTAG_AUTOANSWER(0),
+	 TAG_END());
+
+  run_ab_until(ctx, -1, until_ready, -1, accept_call);
+
+  free_events_in_list(ctx, a->events);
+
+  TEST_1(e = b->events->head); TEST_E(e->data->e_event, nua_i_invite);
+  free_events_in_list(ctx, b->events);
+
+  /* re-INVITE A. */
+  INVITE(b, b_call, b_call->nh, 
+	 SIPTAG_SUBJECT_STR("NUA-6.3 re-INVITE"),
+	 TAG_END());
+  run_ab_until(ctx, -1, reject_reinvite_401, -1, save_until_final_response);
+
+  TEST_1(nua_handle_has_active_call(a_call->nh));
+  TEST_1(nua_handle_has_active_call(b_call->nh));
+
+  free_events_in_list(ctx, a->events);
+  free_events_in_list(ctx, b->events);
+
+  BYE(b, b_call, b_call->nh, TAG_END());
+  
+  run_ab_until(ctx, -1, until_terminated, -1, until_terminated);
+
+  free_events_in_list(ctx, a->events);
+  nua_handle_destroy(a_call->nh), a_call->nh = NULL;
+
+  free_events_in_list(ctx, b->events);
+  nua_handle_destroy(b_call->nh), b_call->nh = NULL;
+
+  if (print_headings)
+    printf("TEST NUA-6.3: PASSED\n");
+
+  END();
+}
+
+int test_bye_after_sending_401(struct context *ctx)
+{
+  BEGIN();
+  struct endpoint *a = &ctx->a,  *b = &ctx->b;
+  struct call *a_call = a->call, *b_call = b->call;
+  struct event *e;
+
+  a_call->sdp = "m=audio 5008 RTP/AVP 8";
+  b_call->sdp = "m=audio 5010 RTP/AVP 0 8";
+
+/* BYE after sending 401
+
+   A			B
+   |-------INVITE------>|
+   |<----100 Trying-----|
+   |			|
+   |<----180 Ringing----|
+   |<-------200---------|
+   |--------ACK-------->|
+   |			|
+   |<------INVITE-------|
+   |--------401-------->|
+   |<--------ACK--------|
+   |			|
+   |--------BYE-------->|
+   |<------200 OK-------|
+   |			|
+*/
+  if (print_headings)
+    printf("TEST NUA-6.4.1: BYE after sending 401\n");
+
+  TEST_1(a_call->nh = nua_handle(a->nua, a_call, SIPTAG_TO(b->to), TAG_END()));
+
+  INVITE(a, a_call, a_call->nh,
+	 TAG_IF(!ctx->proxy_tests, NUTAG_URL(b->contact->m_url)),
+	 SIPTAG_SUBJECT_STR("NUA-6.4.1"),
+	 SOATAG_USER_SDP_STR(a_call->sdp),
+	 NUTAG_AUTOANSWER(0),
+	 TAG_END());
+
+  run_ab_until(ctx, -1, until_ready, -1, accept_call);
+
+  free_events_in_list(ctx, a->events);
+
+  TEST_1(e = b->events->head); TEST_E(e->data->e_event, nua_i_invite);
+  free_events_in_list(ctx, b->events);
+
+  /* re-INVITE A. */
+  INVITE(b, b_call, b_call->nh, 
+	 SIPTAG_SUBJECT_STR("NUA-6.4.1 re-INVITE"),
+	 TAG_END());
+  run_ab_until(ctx, -1, reject_reinvite_401, -1, save_until_final_response);
+
+  TEST_1(nua_handle_has_active_call(a_call->nh));
+  TEST_1(nua_handle_has_active_call(b_call->nh));
+
+  free_events_in_list(ctx, a->events);
+  free_events_in_list(ctx, b->events);
+
+  BYE(a, a_call, a_call->nh, TAG_END());
+  
+  run_ab_until(ctx, -1, until_terminated, -1, until_terminated);
+
+  free_events_in_list(ctx, a->events);
+  nua_handle_destroy(a_call->nh), a_call->nh = NULL;
+
+  free_events_in_list(ctx, b->events);
+  nua_handle_destroy(b_call->nh), b_call->nh = NULL;
+
+  if (print_headings)
+    printf("TEST NUA-6.4.1: PASSED\n");
+
+  END();
+}
+
+int test_bye_after_receiving_401_to_update(struct context *ctx)
+{
+  BEGIN();
+  struct endpoint *a = &ctx->a,  *b = &ctx->b;
+  struct call *a_call = a->call, *b_call = b->call;
+  struct event *e;
+
+  a_call->sdp = "m=audio 5008 RTP/AVP 8";
+  b_call->sdp = "m=audio 5010 RTP/AVP 0 8";
+
+/* BYE after receiving 401
+
+   A			B
+   |-------INVITE------>|
+   |<----100 Trying-----|
+   |			|
+   |<----180 Ringing----|
+   |<-------200---------|
+   |--------ACK-------->|
+   |			|
+   |<------UPDATE-------|
+   |--------401-------->|
+   |			|
+   |<--------BYE--------|
+   |------200 OK------->|
+   |			|
+*/
+  if (print_headings)
+    printf("TEST NUA-6.4.2: BYE after receiving 401 to UPDATE\n");
+
+  TEST_1(a_call->nh = nua_handle(a->nua, a_call, SIPTAG_TO(b->to), TAG_END()));
+
+  INVITE(a, a_call, a_call->nh,
+	 TAG_IF(!ctx->proxy_tests, NUTAG_URL(b->contact->m_url)),
+	 SIPTAG_SUBJECT_STR("NUA-6.4.2"),
+	 SOATAG_USER_SDP_STR(a_call->sdp),
+	 NUTAG_AUTOANSWER(0),
+	 NUTAG_APPL_METHOD("UPDATE"),
+	 TAG_END());
+
+  run_ab_until(ctx, -1, until_ready, -1, accept_call);
+
+  free_events_in_list(ctx, a->events);
+
+  TEST_1(e = b->events->head); TEST_E(e->data->e_event, nua_i_invite);
+  free_events_in_list(ctx, b->events);
+
+  /* UPDATE A. */
+  UPDATE(b, b_call, b_call->nh, 
+	 SIPTAG_SUBJECT_STR("NUA-6.4.2 UPDATE"),
+	 TAG_END());
+  BYE(b, b_call, b_call->nh, TAG_END()); /* Queued until nua_authenticate */
+  run_ab_until(ctx, -1, reject_reinvite_401, -1, save_until_final_response);
+
+  TEST_1(nua_handle_has_active_call(a_call->nh));
+  TEST_1(nua_handle_has_active_call(b_call->nh));
+
+  free_events_in_list(ctx, a->events);
+  free_events_in_list(ctx, b->events);
+
+  AUTHENTICATE(b, b_call, b_call->nh, TAG_END());
+  
+  run_ab_until(ctx, -1, until_terminated, -1, until_terminated);
+
+  free_events_in_list(ctx, a->events);
+  nua_handle_destroy(a_call->nh), a_call->nh = NULL;
+
+  free_events_in_list(ctx, b->events);
+  nua_handle_destroy(b_call->nh), b_call->nh = NULL;
+
+  if (print_headings)
+    printf("TEST NUA-6.4.2: PASSED\n");
+
+  END();
+}
+
+int reject_reinvite_401(CONDITION_PARAMS)
+{
+  void *request = nua_current_request(nua);
+
+  save_event_in_list(ctx, event, ep, call);
+
+  if (!(check_handle(ep, call, nh, SIP_500_INTERNAL_SERVER_ERROR)))
+    return 0;
+
+  if (status < 200 && (event == nua_i_invite || event == nua_i_update)) {
+    RESPOND(ep, call, nh, SIP_401_UNAUTHORIZED,
+	    NUTAG_WITH(request),
+	    SIPTAG_WWW_AUTHENTICATE_STR("Digest realm=\"test_nua\", "
+					"nonce=\"nsdhfuds\", algorithm=MD5, "
+					"qop=\"auth\""),
+	    TAG_END());
+    return 0;
+  }
+
+  if (event == nua_i_state) switch (callstate(tags)) {
+  case nua_callstate_ready:
+    return 1;
+  case nua_callstate_terminated:
+    if (call)
+      nua_handle_destroy(call->nh), call->nh = NULL;
+    return 1;
+  default:
+    return 0;
+  }
+
+  return 0;
+}
+
+int test_bye_with_407(struct context *ctx)
+{
+  BEGIN();
+  struct endpoint *a = &ctx->a,  *c = &ctx->c;
+  struct call *a_call = a->call, *c_call = c->call;
+  struct event *e;
+
+  a_call->sdp = "m=audio 5008 RTP/AVP 8";
+  c_call->sdp = "m=audio 5010 RTP/AVP 0 8";
+
+  if (!ctx->proxy_tests)
+    return 0;
+
+/* BYE after receiving 401
+
+   A			C
+   |-------INVITE------>|
+   |<----100 Trying-----|
+   |			|
+   |<----180 Ringing----|
+   |<-------200---------|
+   |--------ACK-------->|
+   |			|
+   |       |<----BYE----|
+   |       |-----407--->|
+   |<-------BYE---------|
+   |--------200-------->|
+   |			|
+*/
+  if (print_headings)
+    printf("TEST NUA-6.4.5: BYE with 407\n");
+
+  TEST_1(a_call->nh = nua_handle(a->nua, a_call, SIPTAG_TO(c->to), TAG_END()));
+
+  INVITE(a, a_call, a_call->nh,
+	 TAG_IF(!ctx->proxy_tests, NUTAG_URL(c->contact->m_url)),
+	 SIPTAG_SUBJECT_STR("NUA-6.4.2"),
+	 SOATAG_USER_SDP_STR(a_call->sdp),
+	 NUTAG_AUTOANSWER(0),
+	 NUTAG_APPL_METHOD("UPDATE"),
+	 TAG_END());
+
+  run_abc_until(ctx, -1, until_ready, -1, NULL, -1, accept_call);
+
+  free_events_in_list(ctx, a->events);
+
+  TEST_1(e = c->events->head); TEST_E(e->data->e_event, nua_i_invite);
+  free_events_in_list(ctx, c->events);
+
+  BYE(c, c_call, c_call->nh, 
+      TAG_END());
+  run_c_until(ctx, -1, save_until_final_response);
+
+  TEST_1(nua_handle_has_active_call(a_call->nh));
+  TEST_1(nua_handle_has_active_call(c_call->nh));
+
+  free_events_in_list(ctx, a->events);
+  free_events_in_list(ctx, c->events);
+
+  AUTHENTICATE(c, c_call, c_call->nh, 
+	       NUTAG_AUTH("Digest:\"test-proxy\":charlie:secret"), TAG_END());
+  
+  run_abc_until(ctx, -1, until_terminated, -1, NULL, -1, until_terminated);
+
+  free_events_in_list(ctx, a->events);
+  nua_handle_destroy(a_call->nh), a_call->nh = NULL;
+
+  free_events_in_list(ctx, c->events);
+  nua_handle_destroy(c_call->nh), c_call->nh = NULL;
+
+  if (print_headings)
+    printf("TEST NUA-6.4.5: PASSED\n");
+
+  END();
+}
+
+int test_bye_to_invalid_contact(struct context *ctx)
+{
+  BEGIN();
+  struct endpoint *a = &ctx->a,  *b = &ctx->b;
+  struct call *a_call = a->call, *b_call = b->call;
+  struct event *e;
+  sip_t *sip = NULL;
+
+  int seen_401;
+
+  a_call->sdp = "m=audio 5008 RTP/AVP 8";
+  b_call->sdp = "m=audio 5010 RTP/AVP 0 8";
+
+/* Bad Contact URI
+
+   A			B
+   |-------INVITE------>|
+   |<----100 Trying-----|
+   |			|
+   |<----180 Ringing----|
+   |<-------200---------|
+   |			|
+   |--------ACK-------->|
+   |			|
+   |<-------BYE---------|
+   |--------400-------->|
+   |			|
+   |--------BYE-------->|
+   |<------200 OK-------|
+   |			|
+*/
+  if (print_headings)
+    printf("TEST NUA-6.4.3: Test dialog with bad Contact info\n");
+
+  TEST_1(a_call->nh = nua_handle(a->nua, a_call, SIPTAG_TO(b->to), TAG_END()));
+
+  INVITE(a, a_call, a_call->nh,
+	 TAG_IF(!ctx->proxy_tests, NUTAG_URL(b->contact->m_url)),
+	 SOATAG_USER_SDP_STR(a_call->sdp),
+	 SIPTAG_CONTACT(NULL),
+	 SIPTAG_HEADER_STR("Contact: <<sip:xyzzy at com.invalid>"),
+	 TAG_END());
+
+  run_ab_until(ctx, -1, until_ready, -1, accept_call);
+
+  /* Client transitions:
+     INIT -(C1)-> CALLING: nua_invite(), nua_i_state
+     CALLING -(C2)-> PROCEEDING: nua_r_invite(180, nua_i_state, nua_cancel()
+     PROCEEDING -(C6b)-> TERMINATED: nua_r_invite(487), nua_i_state
+  */
+  TEST_1(e = a->events->head); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_calling);
+  TEST_1(is_offer_sent(e->data->e_tags));
+
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_r_invite);
+  TEST(e->data->e_status, 180);
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_proceeding); 
+
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_r_invite);
+  TEST(e->data->e_status, 200);
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_ready); 
+  TEST_1(!e->next);
+
+  /*
+   Server transitions:
+   INIT -(S1)-> RECEIVED: nua_i_invite, nua_i_state
+   RECEIVED -(S2a)-> EARLY: nua_respond(180), nua_i_state
+   EARLY -(S6b)--> TERMINATED: nua_i_cancel, nua_i_state
+  */
+  TEST_1(e = b->events->head); TEST_E(e->data->e_event, nua_i_invite);
+  TEST(e->data->e_status, 100);
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_received); /* RECEIVED */
+  TEST_1(is_offer_recv(e->data->e_tags));
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_early); /* EARLY */
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_completed); /* COMPLETED */
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_ack);
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_ready); /* READY */
+  TEST_1(!e->next);
+
+  free_events_in_list(ctx, a->events);
+  free_events_in_list(ctx, b->events);
+
+  BYE(b, b_call, b_call->nh, TAG_END());
+  
+  run_b_until(ctx, -1, until_terminated);
+
+  /* B transitions:
+   READY --(T2)--> TERMINATING: nua_bye()
+   TERMINATING --(T3)--> TERMINATED: nua_r_bye, nua_i_state
+  */
+  TEST_1(e = b->events->head);  TEST_E(e->data->e_event, nua_r_bye);
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_terminated); /* TERMINATED */
+
+  TEST_1(!nua_handle_has_active_call(b_call->nh));
+  TEST_1(nua_handle_has_active_call(a_call->nh));
+
+  nua_handle_destroy(a_call->nh), a_call->nh = NULL;
+  nua_handle_destroy(b_call->nh), b_call->nh = NULL;
+
+  if (print_headings)
+    printf("TEST NUA-6.4.3: PASSED\n");
+
+  if (!ctx->p) {
+    free_events_in_list(ctx, b->events);
+    return 0;
+  }
+
+  if (print_headings)
+    printf("TEST NUA-6.4.4: Wait for re-REGISTER after connection has been closed\n");
+
+  if (!e->next || (!e->next->next || !e->next->data->e_status != 200))
+    /* B is supposed to re-register pretty soon, wait for re-registration */
+    run_b_until(ctx, -1, save_until_final_response);
+
+  seen_401 = 0;
+
+  for (e = e->next; e; e = e->next) {
+    TEST_E(e->data->e_event, nua_r_register);
+    TEST_1(sip = sip_object(e->data->e_msg));
+
+    if (e->data->e_status == 200) {
+      TEST(e->data->e_status, 200);
+      TEST_1(seen_401);
+      TEST_1(sip->sip_contact);
+    }
+    else if (sip->sip_status && sip->sip_status->st_status == 401) {
+      seen_401 = 1;
+    }
+
+    if (!e->next)
+      break;
+  }
+  TEST_1(e);
+  TEST_S(sip->sip_contact->m_expires, "3600");
+  TEST_1(!e->next);
+  free_events_in_list(ctx, b->events);
+
+  if (print_headings)
+    printf("TEST NUA-6.4.4: PASSED\n");
+
+  END();
+}
+
+int test_early_bye(struct context *ctx)
+{
+  return 
+    test_bye_with_407(ctx) ||
+    test_bye_before_200(ctx) ||
+    test_bye_before_ack(ctx) ||
+    test_bye_after_receiving_401(ctx) ||
+    test_bye_after_sending_401(ctx) ||
+    test_bye_after_receiving_401_to_update(ctx) ||
+    test_bye_to_invalid_contact(ctx) ||
+    0;
+}

Added: freeswitch/trunk/libs/sofia-sip/tests/test_extension.c
==============================================================================
--- (empty file)
+++ freeswitch/trunk/libs/sofia-sip/tests/test_extension.c	Wed May 14 15:10:54 2008
@@ -0,0 +1,180 @@
+/*
+ * This file is part of the Sofia-SIP package
+ *
+ * Copyright (C) 2005 Nokia Corporation.
+ *
+ * Contact: Pekka Pessi <pekka.pessi at nokia.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA
+ *
+ */
+
+/**@CFILE test_extension.c
+ * @brief NUA-12: Test extension methods.
+ *
+ * @author Pekka Pessi <Pekka.Pessi at nokia.com>
+ *
+ * @date Created: Mon Nov 13 15:37:05 EET 2006
+ */
+
+#include "config.h"
+
+#include "test_nua.h"
+
+#include <sofia-sip/su_tag_class.h>
+
+#if HAVE_FUNC
+#elif HAVE_FUNCTION
+#define __func__ __FUNCTION__
+#else
+#define __func__ "test_extension"
+#endif
+
+
+int respond_to_extension(CONDITION_PARAMS)
+{
+  msg_t *with = nua_current_request(nua);
+
+  if (!(check_handle(ep, call, nh, SIP_500_INTERNAL_SERVER_ERROR)))
+    return 0;
+
+  save_event_in_list(ctx, event, ep, call);
+
+  switch (event) {
+  case nua_i_method:
+    RESPOND(ep, call, nh, SIP_200_OK,
+	    NUTAG_WITH(with),
+	    SIPTAG_SUBJECT_STR("extended"),
+	    TAG_END());
+    return 1;
+  default:
+    return 0;
+  }
+}
+
+int test_extension(struct context *ctx)
+{
+  BEGIN();
+
+  struct endpoint *a = &ctx->a,  *b = &ctx->b;
+  struct call *a_call = a->call, *b_call = b->call;
+  struct event *e;
+  sip_t const *sip;
+
+
+/* Test for EXTENSION 
+
+   A			B
+   |------EXTENSION---->|
+   |<--------501--------| (method not recognized)
+   |			|
+   |------EXTENSION---->|
+   |<-------200---------| (method allowed, responded)
+   |			|
+*/
+
+  if (print_headings)
+    printf("TEST NUA-13.1: EXTENSION\n");
+
+
+  TEST_1(a_call->nh = nua_handle(a->nua, a_call, SIPTAG_TO(b->to), TAG_END()));
+
+  /* Test first without NUTAG_METHOD() */
+  METHOD(a, a_call, a_call->nh,
+	 TAG_IF(!ctx->proxy_tests, NUTAG_URL(b->contact->m_url)),
+	 TAG_END());
+
+  run_ab_until(ctx, -1, save_until_final_response, -1, NULL);
+
+  /* Client events:
+     nua_method(), nua_r_method
+  */
+  TEST_1(e = a->events->head); TEST_E(e->data->e_event, nua_r_method);
+  TEST(e->data->e_status, 900);	/* Internal error */
+  TEST_1(!e->data->e_msg);
+  TEST_1(!e->next);
+
+  free_events_in_list(ctx, a->events);
+  nua_handle_destroy(a_call->nh), a_call->nh = NULL;
+
+  TEST_1(a_call->nh = nua_handle(a->nua, a_call, SIPTAG_TO(b->to), TAG_END()));
+
+  METHOD(a, a_call, a_call->nh,
+	 TAG_IF(!ctx->proxy_tests, NUTAG_URL(b->contact->m_url)),
+	 NUTAG_METHOD("EXTENSION"),
+	 TAG_END());
+
+  run_ab_until(ctx, -1, save_until_final_response, -1, NULL);
+
+  /* Client events:
+     nua_method(), nua_r_method
+  */
+  TEST_1(e = a->events->head); TEST_E(e->data->e_event, nua_r_method);
+  TEST(e->data->e_status, 501);
+  TEST_1(!e->next);
+
+  free_events_in_list(ctx, a->events);
+  nua_handle_destroy(a_call->nh), a_call->nh = NULL;
+
+  free_events_in_list(ctx, b->events);
+  nua_handle_destroy(b_call->nh), b_call->nh = NULL;
+
+  nua_set_params(b->nua, NUTAG_ALLOW("EXTENSION"), TAG_END());
+
+  run_b_until(ctx, nua_r_set_params, until_final_response);
+
+  TEST_1(a_call->nh = nua_handle(a->nua, a_call, SIPTAG_TO(b->to), TAG_END()));
+
+  METHOD(a, a_call, a_call->nh,
+	 TAG_IF(!ctx->proxy_tests, NUTAG_URL(b->contact->m_url)),
+	 NUTAG_METHOD("EXTENSION"),
+	 TAG_END());
+
+  run_ab_until(ctx, -1, save_until_final_response, -1, respond_to_extension);
+
+  /* Client events:
+     nua_method(), nua_r_method
+  */
+  TEST_1(e = a->events->head); TEST_E(e->data->e_event, nua_r_method);
+  TEST(e->data->e_status, 200);
+  TEST_1(sip = sip_object(e->data->e_msg));
+  TEST_1(!e->next);
+
+  /*
+   Server events:
+   nua_i_method
+  */
+  TEST_1(e = b->events->head); TEST_E(e->data->e_event, nua_i_method);
+  TEST(e->data->e_status, 100);
+  TEST_1(!e->next);
+
+  free_events_in_list(ctx, a->events);
+  nua_handle_destroy(a_call->nh), a_call->nh = NULL;
+
+  free_events_in_list(ctx, b->events);
+  nua_handle_destroy(b_call->nh), b_call->nh = NULL;
+
+  nua_set_params(b->nua,
+		 SIPTAG_ALLOW(b->allow),
+		 NUTAG_APPL_METHOD(NULL),
+		 NUTAG_APPL_METHOD(b->appl_method),
+		 TAG_END());
+  run_b_until(ctx, nua_r_set_params, until_final_response);
+
+  if (print_headings)
+    printf("TEST NUA-13.1: PASSED\n");
+  END();
+}

Added: freeswitch/trunk/libs/sofia-sip/tests/test_init.c
==============================================================================
--- (empty file)
+++ freeswitch/trunk/libs/sofia-sip/tests/test_init.c	Wed May 14 15:10:54 2008
@@ -0,0 +1,448 @@
+/*
+ * This file is part of the Sofia-SIP package
+ *
+ * Copyright (C) 2005 Nokia Corporation.
+ *
+ * Contact: Pekka Pessi <pekka.pessi at nokia.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA
+ *
+ */
+
+/**@CFILE test_init.c
+ * @brief Init nua test context 
+ *
+ * @author Pekka Pessi <Pekka.Pessi at nokia.com>
+ * @author Martti Mela <Martti Mela at nokia.com>
+ *
+ * @date Created: Wed Aug 17 12:12:12 EEST 2005 ppessi
+ */
+
+#include "config.h"
+
+#include "test_nua.h"
+
+#include <sofia-sip/tport_tag.h>
+#include <sofia-sip/auth_module.h>
+
+#if HAVE_FUNC
+#elif HAVE_FUNCTION
+#define __func__ __FUNCTION__
+#else
+#define __func__ name
+#endif
+
+static char passwd_name[] = "tmp_sippasswd.XXXXXX";
+
+static void remove_tmp(void)
+{
+  if (passwd_name[0])
+    unlink(passwd_name);
+}
+
+static char const passwd[] =
+  "alice:secret:\n"
+  "bob:secret:\n"
+  "charlie:secret:\n";
+
+int test_nua_init(struct context *ctx,
+		  int start_proxy,
+		  url_t const *o_proxy,
+		  int start_nat,
+		  tag_type_t tag, tag_value_t value, ...)
+{
+  BEGIN();
+  struct event *e;
+  sip_contact_t const *m = NULL;
+  sip_from_t const *sipaddress = NULL;
+  sip_allow_t const *allow = NULL;
+  sip_supported_t const *supported = NULL;
+  char const *appl_method = NULL;
+  url_t const *p_uri, *a_uri, *b_uri;		/* Proxy URI */
+  char const *initial_route = NULL;	/* Initial route towards internal proxy */
+  char const *a_bind, *a_bind2;
+  url_t *e_proxy = NULL;
+  int err = -1;
+  url_t b_proxy[1];
+
+  a_bind = a_bind2 = "sip:0.0.0.0:*";
+
+#if SU_HAVE_OSX_CF_API
+  if (ctx->osx_runloop)
+    ctx->root = su_root_osx_runloop_create(NULL);
+  else
+#endif
+  ctx->root = su_root_create(NULL);
+  TEST_1(ctx->root);
+
+  /* Disable threading by command line switch -s */
+  su_root_threading(ctx->root, ctx->threading);
+
+  if (start_proxy && !o_proxy) {
+    int temp;
+
+    if (print_headings)
+      printf("TEST NUA-2.1.1: init proxy P\n");
+
+#ifndef _WIN32
+    temp = mkstemp(passwd_name);
+#else
+    temp = open(passwd_name, O_WRONLY|O_CREAT|O_TRUNC, 666);
+#endif
+    TEST_1(temp != -1);
+    atexit(remove_tmp);		/* Make sure temp file is unlinked */
+
+    TEST_SIZE(write(temp, passwd, strlen(passwd)), strlen(passwd));
+
+    TEST_1(close(temp) == 0);
+
+    ctx->p = test_proxy_create(ctx->root,
+			       TAG_IF(ctx->proxy_logging, TPTAG_LOG(1)),
+			       TAG_END());
+
+    if (ctx->p) {
+      ctx->a.domain = 
+	test_proxy_add_domain(ctx->p,
+			      URL_STRING_MAKE("sip:example.com")->us_url,
+			      AUTHTAG_METHOD("Digest"),
+			      AUTHTAG_REALM("test-proxy"),
+			      AUTHTAG_OPAQUE("kuik"),
+			      AUTHTAG_DB(passwd_name),
+			      AUTHTAG_QOP("auth-int"),
+			      AUTHTAG_ALGORITHM("md5"),
+			      AUTHTAG_NEXT_EXPIRES(60),
+			      TAG_END());
+
+      ctx->b.domain = 
+	test_proxy_add_domain(ctx->p,
+			      URL_STRING_MAKE("sip:example.org")->us_url,
+			      AUTHTAG_METHOD("Digest"),
+			      AUTHTAG_REALM("test-proxy"),
+			      AUTHTAG_OPAQUE("kuik"),
+			      AUTHTAG_DB(passwd_name),
+			      AUTHTAG_QOP("auth-int"),
+			      AUTHTAG_ALGORITHM("md5"),
+			      AUTHTAG_NEXT_EXPIRES(60),
+			      TAG_END());
+
+      test_proxy_domain_set_outbound(ctx->b.domain, 1);
+
+      ctx->c.domain = 
+	test_proxy_add_domain(ctx->p,
+			      URL_STRING_MAKE("sip:example.net")->us_url,
+			      AUTHTAG_METHOD("Digest"),
+			      AUTHTAG_REALM("test-proxy"),
+			      AUTHTAG_OPAQUE("kuik"),
+			      AUTHTAG_DB(passwd_name),
+			      AUTHTAG_QOP("auth-int"),
+			      AUTHTAG_ALGORITHM("md5"),
+			      AUTHTAG_NEXT_EXPIRES(60),
+			      AUTHTAG_MAX_NCOUNT(1),
+			      AUTHTAG_ALLOW("ACK, CANCEL"),
+			      TAG_END());
+
+      test_proxy_domain_set_record_route(ctx->c.domain, 1);
+
+      ctx->proxy_tests = 1;
+    }
+
+
+    if (print_headings)
+      printf("TEST NUA-2.1.1: PASSED\n");
+  }
+
+  p_uri = a_uri = b_uri = test_proxy_uri(ctx->p);
+
+  if (o_proxy) {
+    TEST_1(e_proxy = url_hdup(ctx->home, (void *)o_proxy));
+    ctx->external_proxy = e_proxy;
+  }
+
+  if (start_nat && p_uri == NULL)
+    p_uri = e_proxy;
+
+  if (ctx->p)
+    initial_route = test_proxy_route_uri(ctx->p, &ctx->lr);
+
+  if (start_nat && p_uri != NULL) {
+    int family = 0;
+    su_sockaddr_t su[1];
+    socklen_t sulen = sizeof su;
+    char b[64];
+    size_t len;
+    ta_list ta;
+
+    if (print_headings)
+      printf("TEST NUA-2.1.2: creating test NAT\n");
+
+    /* Try to use different family than proxy. */
+    if (p_uri->url_host[0] == '[')
+      family = AF_INET;
+#if defined(SU_HAVE_IN6)
+    else
+      family = AF_INET6;
+#endif
+
+    ta_start(ta, tag, value);
+    ctx->nat = test_nat_create(ctx->root, family, ta_tags(ta));
+    ta_end(ta);
+
+    /*
+     * NAT thingy works so that we set the outgoing proxy URI to point
+     * towards its "private" address and give the real address of the proxy
+     * as its "public" address. If we use different IP families here, we may
+     * even manage to test real connectivity problems as proxy and endpoint
+     * can not talk to each other.
+     */
+
+    if (test_nat_private(ctx->nat, su, &sulen) < 0) {
+      printf("%s:%u: NUA-2.1.2: failed to get private NAT address\n",
+	     __FILE__, __LINE__);
+    }
+
+#if defined(SU_HAVE_IN6)
+    else if (su->su_family == AF_INET6) {
+      a_uri = (void *)
+	su_sprintf(ctx->home, "sip:[%s]:%u",
+		   inet_ntop(su->su_family, SU_ADDR(su), b, sizeof b),
+		   ntohs(su->su_port));
+      a_bind = "sip:[::]:*";
+    }
+#endif
+    else if (su->su_family == AF_INET) {
+      a_uri = (void *)
+	su_sprintf(ctx->home, "sip:%s:%u",
+		   inet_ntop(su->su_family, SU_ADDR(su), b, sizeof b),
+		   ntohs(su->su_port));
+    }
+
+#if defined(SU_HAVE_IN6)
+    if (p_uri->url_host[0] == '[') {
+      su->su_len = sulen = (sizeof su->su_sin6), su->su_family = AF_INET6;
+      len = strcspn(p_uri->url_host + 1, "]"); assert(len < sizeof b);
+      memcpy(b, p_uri->url_host + 1, len); b[len] = '\0';
+      inet_pton(su->su_family, b, SU_ADDR(su));
+    }
+    else {
+      su->su_len = sulen = (sizeof su->su_sin), su->su_family = AF_INET;
+      inet_pton(su->su_family, p_uri->url_host, SU_ADDR(su));
+    }
+#else
+    su->su_len = sulen = (sizeof su->su_sin), su->su_family = AF_INET;
+    inet_pton(su->su_family, p_uri->url_host, SU_ADDR(su));
+#endif
+
+    su->su_port = htons(strtoul(url_port(p_uri), NULL, 10));
+
+    if (test_nat_public(ctx->nat, su, sulen) < 0) {
+      printf("%s:%u: NUA-2.1.2: failed to set public address\n",
+	     __FILE__, __LINE__);
+      a_uri = NULL;
+    }
+
+    if (print_headings) {
+      if (ctx->nat && a_uri) {
+	printf("TEST NUA-2.1.2: PASSED\n");
+      } else {
+	printf("TEST NUA-2.1.2: FAILED\n");
+      }
+    }
+  }
+
+  if (print_headings)
+    printf("TEST NUA-2.2.1: init endpoint A\n");
+
+  if (a_uri == NULL)
+    a_uri = p_uri;
+
+  ctx->a.instance = nua_generate_instance_identifier(ctx->home);
+
+  ctx->a.nua = nua_create(ctx->root, a_callback, ctx,
+			  NUTAG_PROXY(a_uri ? a_uri : e_proxy),
+			  NUTAG_INITIAL_ROUTE_STR(initial_route),
+			  SIPTAG_FROM_STR("sip:alice at example.com"),
+			  NUTAG_URL(a_bind),
+			  TAG_IF(a_bind != a_bind2, NUTAG_SIPS_URL(a_bind2)),
+			  SOATAG_USER_SDP_STR("m=audio 5004 RTP/AVP 0 8"),
+			  NTATAG_SIP_T1X64(2000),
+			  NUTAG_INSTANCE(ctx->a.instance),
+			  TAG_IF(ctx->a.logging, TPTAG_LOG(1)),
+			  TAG_END());
+  TEST_1(ctx->a.nua);
+
+  nua_get_params(ctx->a.nua, TAG_ANY(), TAG_END());
+  run_a_until(ctx, nua_r_get_params, save_until_final_response);
+  TEST_1(e = ctx->a.specials->head);
+  err = tl_gets(e->data->e_tags,
+	            NTATAG_CONTACT_REF(m),
+	            SIPTAG_FROM_REF(sipaddress),
+	            SIPTAG_ALLOW_REF(allow),
+	            NUTAG_APPL_METHOD_REF(appl_method),
+	            SIPTAG_SUPPORTED_REF(supported),
+	            TAG_END());
+  TEST(err, 5);
+  TEST_1(m);
+  TEST_1(ctx->a.contact = sip_contact_dup(ctx->home, m));
+  TEST_1(ctx->a.to = sip_to_dup(ctx->home, sipaddress));
+  TEST_1(ctx->a.allow = sip_allow_dup(ctx->home, allow));
+  TEST_1(ctx->a.appl_method = su_strdup(ctx->home, appl_method));
+  TEST_1(ctx->a.supported = sip_supported_dup(ctx->home, supported));
+
+  free_events_in_list(ctx, ctx->a.specials);
+
+  if (print_headings)
+    printf("TEST NUA-2.2.1: PASSED\n");
+
+  if (print_headings)
+    printf("TEST NUA-2.2.2: init endpoint B\n");
+
+  ctx->b.instance = nua_generate_instance_identifier(ctx->home);
+
+  if (ctx->p) {
+    /* B uses TCP when talking with proxy */
+    *b_proxy = *b_uri;
+    b_uri = b_proxy;
+    b_proxy->url_params = "transport=tcp";
+  }
+
+  ctx->b.nua = nua_create(ctx->root, b_callback, ctx,
+			  NUTAG_PROXY(b_uri ? b_uri : e_proxy),
+			  SIPTAG_FROM_STR("sip:bob at example.org"),
+			  NUTAG_URL("sip:0.0.0.0:*"),
+			  SOATAG_USER_SDP_STR("m=audio 5006 RTP/AVP 8 0"),
+			  NUTAG_INSTANCE(ctx->b.instance),
+			  /* Quicker timeout */
+			  NTATAG_SIP_T1X64(2000),
+			  TPTAG_KEEPALIVE(100),
+			  TAG_IF(ctx->b.logging, TPTAG_LOG(1)),
+			  TAG_END());
+  TEST_1(ctx->b.nua);
+
+  nua_get_params(ctx->b.nua, TAG_ANY(), TAG_END());
+  run_b_until(ctx, nua_r_get_params, save_until_final_response);
+  TEST_1(e = ctx->b.specials->head);
+  err = tl_gets(e->data->e_tags,
+	            NTATAG_CONTACT_REF(m),
+	            SIPTAG_FROM_REF(sipaddress),
+	            SIPTAG_ALLOW_REF(allow),
+	            NUTAG_APPL_METHOD_REF(appl_method),
+	            SIPTAG_SUPPORTED_REF(supported),
+	            TAG_END());
+  TEST(err, 5); TEST_1(m);
+
+  TEST_1(ctx->b.contact = sip_contact_dup(ctx->home, m));
+  TEST_1(ctx->b.to = sip_to_dup(ctx->home, sipaddress));
+  TEST_1(ctx->b.allow = sip_allow_dup(ctx->home, allow));
+  TEST_1(ctx->b.appl_method = su_strdup(ctx->home, appl_method));
+  TEST_1(ctx->b.supported = sip_supported_dup(ctx->home, supported));
+
+  free_events_in_list(ctx, ctx->b.specials);
+
+  if (print_headings)
+    printf("TEST NUA-2.2.2: PASSED\n");
+
+  if (print_headings)
+    printf("TEST NUA-2.2.3: init endpoint C\n");
+
+  /* ctx->c.instance = nua_generate_instance_identifier(ctx->home); */
+
+  ctx->c.to = sip_from_make(ctx->home, "Charlie <sip:charlie at example.net>");
+
+  ctx->c.nua = nua_create(ctx->root, c_callback, ctx,
+			  NUTAG_PROXY(p_uri ? p_uri : e_proxy),
+			  NUTAG_URL("sip:0.0.0.0:*"),
+			  SOATAG_USER_SDP_STR("m=audio 5400 RTP/AVP 8 0"),
+			  NUTAG_INSTANCE(ctx->c.instance),
+			  TAG_IF(ctx->c.logging, TPTAG_LOG(1)),
+			  TAG_END());
+  TEST_1(ctx->c.nua);
+
+  nua_get_params(ctx->c.nua, TAG_ANY(), TAG_END());
+  run_c_until(ctx, nua_r_get_params, save_until_final_response);
+  TEST_1(e = ctx->c.specials->head);
+  err = tl_gets(e->data->e_tags,
+	            NTATAG_CONTACT_REF(m),
+	            SIPTAG_ALLOW_REF(allow),
+	            NUTAG_APPL_METHOD_REF(appl_method),
+	            SIPTAG_SUPPORTED_REF(supported),
+	            TAG_END());
+  
+  TEST(err, 4); TEST_1(m);
+  TEST_1(ctx->c.contact = sip_contact_dup(ctx->home, m));
+  TEST_1(ctx->c.allow = sip_allow_dup(ctx->home, allow));
+  TEST_1(ctx->c.appl_method = su_strdup(ctx->home, appl_method));
+  TEST_1(ctx->c.supported = sip_supported_dup(ctx->home, supported));
+  free_events_in_list(ctx, ctx->c.specials);
+
+  if (print_headings)
+    printf("TEST NUA-2.2.3: PASSED\n");
+
+  END();
+}
+
+
+/* ====================================================================== */
+
+int test_deinit(struct context *ctx)
+{
+  BEGIN();
+
+  struct call *call;
+
+  if (!ctx->threading)
+    su_root_step(ctx->root, 100);
+
+  if (ctx->a.nua) {
+    for (call = ctx->a.call; call; call = call->next)
+      nua_handle_destroy(call->nh), call->nh = NULL;
+
+    nua_shutdown(ctx->a.nua);
+    run_a_until(ctx, nua_r_shutdown, until_final_response);
+    free_events_in_list(ctx, ctx->a.events);
+    free_events_in_list(ctx, ctx->a.specials);
+    nua_destroy(ctx->a.nua), ctx->a.nua = NULL;
+  }
+
+  if (ctx->b.nua) {
+    for (call = ctx->b.call; call; call = call->next)
+      nua_handle_destroy(call->nh), call->nh = NULL;
+
+    nua_shutdown(ctx->b.nua);
+    run_b_until(ctx, nua_r_shutdown, until_final_response);
+    free_events_in_list(ctx, ctx->b.events);
+    free_events_in_list(ctx, ctx->b.specials);
+    nua_destroy(ctx->b.nua), ctx->b.nua = NULL;
+  }
+
+  if (ctx->c.nua) {
+    for (call = ctx->c.call; call; call = call->next)
+      nua_handle_destroy(call->nh), call->nh = NULL;
+
+    nua_shutdown(ctx->c.nua);
+    run_c_until(ctx, nua_r_shutdown, until_final_response);
+    free_events_in_list(ctx, ctx->c.events);
+    free_events_in_list(ctx, ctx->c.specials);
+    nua_destroy(ctx->c.nua), ctx->c.nua = NULL;
+  }
+
+  test_proxy_destroy(ctx->p), ctx->p = NULL;
+
+  test_nat_destroy(ctx->nat), ctx->nat = NULL;
+
+  su_root_destroy(ctx->root), ctx->root = NULL;
+
+  END();
+}

Added: freeswitch/trunk/libs/sofia-sip/tests/test_nat.c
==============================================================================
--- (empty file)
+++ freeswitch/trunk/libs/sofia-sip/tests/test_nat.c	Wed May 14 15:10:54 2008
@@ -0,0 +1,999 @@
+/*
+ * This file is part of the Sofia-SIP package
+ *
+ * Copyright (C) 2005 Nokia Corporation.
+ *
+ * Contact: Pekka Pessi <pekka.pessi at nokia.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA
+ *
+ */
+
+/**@CFILE test_nat.c
+ * @brief Simulated NAT for testing
+ *
+ * NAT thing works so that we set the outgoing proxy URI to point
+ * towards its "private" address and give the real address of the proxy
+ * as its "public" address. If we use different IP families here, we may
+ * even manage to test real connectivity problems as proxy and endpoint
+ * can not talk to each other.
+ *
+ * @author Pekka Pessi <Pekka.Pessi at nokia.com>
+ *
+ * @date Created: Wed Mar  8 19:54:28 EET 2006
+ */
+
+#include "config.h"
+
+struct nat;
+struct binding;
+
+#define SU_ROOT_MAGIC_T struct nat
+#define SU_WAKEUP_ARG_T struct binding
+
+#include <sofia-sip/su.h>
+#include <sofia-sip/su_errno.h>
+#include <sofia-sip/su_wait.h>
+#include <sofia-sip/su_tagarg.h>
+#include <sofia-sip/su_localinfo.h>
+#include <sofia-sip/su_log.h>
+
+#include <stdlib.h>
+#include <assert.h>
+#include <string.h>
+
+#define LIST_PROTOS(STORAGE, PREFIX, T)			 \
+STORAGE void PREFIX ##_insert(T **list, T *node),	 \
+        PREFIX ##_remove(T *node)
+
+#define LIST_BODIES(STORAGE, PREFIX, T, NEXT, PREV)	  \
+STORAGE void PREFIX ##_insert(T **list, T *node)   \
+{							 \
+  if ((node->NEXT = *list)) {				 \
+    node->PREV = node->NEXT->PREV;			 \
+    node->NEXT->PREV = &node->NEXT;			 \
+  }							 \
+  else							 \
+    node->PREV = list;					 \
+  *list = node;						 \
+}							 \
+STORAGE void PREFIX ##_remove(T *node)			 \
+{							 \
+  if (node->PREV)					 \
+    if ((*node->PREV = node->NEXT))			 \
+      node->NEXT->PREV = node->PREV;			 \
+  node->PREV = NULL;					 \
+}							 \
+extern int LIST_DUMMY_VARIABLE
+
+#include "test_nat.h"
+
+struct nat {
+  su_home_t    home[1];
+  su_root_t   *parent;
+  su_clone_r   clone;
+  tagi_t      *tags;
+
+  su_root_t   *root;
+
+  struct binding *bindings;
+
+  struct nat_filter *in_filters, *out_filters;
+
+  /* True if we act in symmetric way */
+  int symmetric;
+  /* True if we do logging */
+  int logging;
+
+  /* Everything sent to in_address will be forwarded to out_address */
+  su_sockaddr_t in_address[1], out_address[1];
+  socklen_t in_addrlen, out_addrlen;
+
+  int family;			/* Preferred private family */
+
+  /* ...but source address will be "fake" */
+  su_localinfo_t *localinfo, *private, *fake;
+
+  su_socket_t udp_socket, tcp_socket;
+  int udp_register, tcp_register;
+
+  char buffer[65536];
+};
+
+LIST_PROTOS(static, nat_binding, struct binding);
+
+struct binding
+{
+  struct binding *next, **prev;
+  struct nat *nat;		/* backpointer */
+  int socktype, protocol;
+  su_socket_t in_socket, out_socket;
+  int in_register, out_register;
+  int in_closed, out_closed;
+  char in_name[64], out_name[64];
+};
+
+static struct binding *nat_binding_new(struct nat *nat,
+				       char const *protoname,
+				       int socktype, int protocol, 
+				       int connected,
+				       su_socket_t in_socket,
+				       su_sockaddr_t *from,
+				       socklen_t fromlen);
+static void nat_binding_destroy(struct binding *);
+
+static int binding_init(struct binding *b,
+			char const *protoname,
+			int connected,
+			su_localinfo_t *li,
+			su_sockaddr_t *from,
+			socklen_t fromlen);
+
+static void flush_bindings(struct nat *nat);
+static int invalidate_bindings(void *nat);
+
+static int new_udp(struct nat *, su_wait_t *wait, struct binding *dummy);
+static int udp_in_to_out(struct nat *, su_wait_t *wait, struct binding *);
+static int udp_out_to_in(struct nat *, su_wait_t *wait, struct binding *);
+
+static int new_tcp(struct nat *, su_wait_t *wait, struct binding *dummy);
+static int tcp_in_to_out(struct nat *, su_wait_t *wait, struct binding *);
+static int tcp_out_to_in(struct nat *, su_wait_t *wait, struct binding *);
+
+static int invalidate_binding(struct binding *b);
+
+LIST_PROTOS(static, nat_filter, struct nat_filter);
+
+struct nat_filter
+{
+  struct nat_filter *next, **prev;
+  size_t (*condition)(void *arg, void *message, size_t len);
+  void *arg;
+};
+
+/* nat entry point */
+static int
+test_nat_init(su_root_t *root, struct nat *nat)
+{
+  su_localinfo_t *li, hints[1] = {{ 0 }};
+  int error;
+  unsigned port = 0, port0 = 0;
+  su_sockaddr_t su[1];
+  socklen_t sulen;
+  su_wait_t wait[1];
+
+  nat->root = root;
+  nat->udp_socket = INVALID_SOCKET, nat->tcp_socket = INVALID_SOCKET;
+
+  tl_gets(nat->tags, 
+	  TESTNATTAG_SYMMETRIC_REF(nat->symmetric),
+	  TESTNATTAG_LOGGING_REF(nat->logging),
+	  TAG_END());
+
+  hints->li_scope = LI_SCOPE_HOST | LI_SCOPE_SITE | LI_SCOPE_GLOBAL;
+
+  error = su_getlocalinfo(hints, &nat->localinfo);
+  if (error) {
+    fprintf(stderr, "test_nat: su_getlocalinfo: %s\n", su_gli_strerror(error));
+    return -1;
+  }
+
+  /* We must have two different IP addresses. */
+  if (!nat->localinfo || !nat->localinfo->li_next) {
+    fprintf(stderr, "test_nat: only one IP address available\n");
+    return -1;
+  }
+
+  for (li = nat->localinfo; li; li = li->li_next) {
+    if (nat->family == 0 || nat->family == li->li_family)
+      break;
+  }
+  if (li == NULL)
+    li = nat->localinfo;
+
+  memcpy(su, li->li_addr, sulen = li->li_addrlen);
+  memset(SU_ADDR(su), 0, SU_ADDRLEN(su));
+
+  nat->private = li;
+
+  /* Bind TCP and UDP to same port */
+  for (;;) {
+    nat->udp_socket = su_socket(li->li_family, SOCK_DGRAM, IPPROTO_UDP);
+    if (nat->udp_socket == INVALID_SOCKET)
+      return -1;
+
+    if (bind(nat->udp_socket, (void *)su, sulen) < 0) {
+      if (port0 == 0) {
+	su_perror("nat: bind(udp_socket)");
+	return -1;
+      }
+
+      fprintf(stderr, "test_nat: port %u: %s\n",
+	      port, su_strerror(su_errno()));
+      su_close(nat->udp_socket);
+
+      nat->udp_socket = INVALID_SOCKET;
+
+      if (++port > 65535)
+	port = 1024;
+      if (port == port0) {
+	fprintf(stderr, "test_nat: could not find free port pairt\n");
+	return -1;
+      }
+
+      continue;
+    }
+
+    if (getsockname(nat->udp_socket, (void *)su, &sulen) < 0) {
+      su_perror("nat: getsockname(udp_socket)");
+      return -1;
+    }
+
+    if (port0 == 0) {
+      port = port0 = ntohs(su->su_port);
+      if (port0 == 0) {
+	fprintf(stderr, "test_nat: bind did not return port\n");
+	return -1;
+      }
+    }
+
+    nat->tcp_socket = su_socket(li->li_family, SOCK_STREAM, IPPROTO_TCP);
+    if (nat->tcp_socket == INVALID_SOCKET)
+      return -1;
+
+    if (bind(nat->tcp_socket, (void *)su, sulen) < 0) {
+      su_close(nat->tcp_socket);
+      nat->tcp_socket = INVALID_SOCKET;
+
+      fprintf(stderr, "test_nat: port %u: %s\n",
+	      port, su_strerror(su_errno()));
+
+      if (++port > 65535)
+	port = 1024;
+      if (port == port0) {
+	fprintf(stderr, "test_nat: could not find free port pair\n");
+	return -1;
+      }
+
+      continue;
+    }
+
+    break;
+  }
+
+  memcpy(nat->in_address, li->li_addr, nat->in_addrlen = li->li_addrlen);
+  nat->in_address->su_port = su->su_port;
+
+  if (su_setreuseaddr(nat->udp_socket, 1) < 0) {
+    su_perror("nat: su_setreuseaddr(udp_socket)");
+    return -1;
+  }
+
+  if (listen(nat->tcp_socket, 5) < 0) {
+    su_perror("nat: listen(tcp_socket)");
+    return -1;
+  }
+
+  if (su_wait_create(wait, nat->udp_socket, SU_WAIT_IN) < 0) {
+    su_perror("nat: su_wait_create");
+    return -1;
+  }
+
+  nat->udp_register = su_root_register(root, wait, new_udp, NULL, 0);
+  if (nat->udp_register < 0) {
+    su_perror("nat: su_root_register");
+    return -1;
+  }
+
+  if (su_wait_create(wait, nat->tcp_socket, SU_WAIT_IN) < 0) {
+    su_perror("nat: su_wait_create");
+    return -1;
+  }
+
+  nat->tcp_register = su_root_register(root, wait, new_tcp, NULL, 0);
+  if (nat->tcp_register < 0) {
+    su_perror("nat: su_root_register");
+    return -1;
+  }
+
+  return 0;
+}
+
+static void
+test_nat_deinit(su_root_t *root, struct nat *nat)
+{
+  flush_bindings(nat);
+
+  if (nat->tcp_register)
+    su_root_deregister(root, nat->tcp_register);
+  if (nat->udp_register)
+    su_root_deregister(root, nat->udp_register);
+
+  if (nat->udp_socket != INVALID_SOCKET)
+    su_close(nat->udp_socket);
+  if (nat->tcp_socket != INVALID_SOCKET)
+    su_close(nat->tcp_socket);
+
+  su_freelocalinfo(nat->localinfo);
+
+  free(nat->tags);
+}
+
+struct nat *test_nat_create(su_root_t *root,
+			    int family,
+			    tag_type_t tag, tag_value_t value, ...)
+{
+  struct nat *nat = su_home_new(sizeof *nat);
+
+  if (nat) {
+    ta_list ta;
+
+    nat->parent = root;
+    nat->family = family;
+
+    ta_start(ta, tag, value);
+    nat->tags = tl_llist(ta_tags(ta));
+    ta_end(ta);
+
+    if (su_clone_start(root,
+		       nat->clone,
+		       nat,
+		       test_nat_init,
+		       test_nat_deinit) == -1)
+      su_home_unref(nat->home), nat = NULL;
+  }
+
+  return nat;
+}
+
+void test_nat_destroy(struct nat *nat)
+{
+  if (nat) {
+    su_clone_wait(nat->parent, nat->clone);
+    su_home_unref(nat->home);
+  }
+}
+
+/** Get "private" address. */
+int test_nat_private(struct nat *nat, void *address, socklen_t *return_addrlen)
+{
+  if (nat == NULL || address == NULL || return_addrlen == NULL)
+    return su_seterrno(EFAULT);
+
+  if (*return_addrlen < nat->in_addrlen)
+    return su_seterrno(EINVAL);
+
+  memcpy(address, nat->in_address, *return_addrlen = nat->in_addrlen);
+
+  return 0;
+}
+
+/** Set "public" address. */
+int test_nat_public(struct nat *nat, void const *address, int addrlen)
+{
+  su_sockaddr_t const *su = address;
+  su_localinfo_t *li;
+
+  if (nat == NULL)
+    return su_seterrno(EFAULT);
+
+  if (address == NULL) {
+    nat->fake = NULL;
+    return 0;
+  }
+
+  if ((size_t)addrlen > sizeof nat->out_address)
+    return su_seterrno(EINVAL);
+
+  for (li = nat->localinfo; li; li = li->li_next) {
+    if (li != nat->private &&
+	li->li_scope == LI_SCOPE_HOST &&
+	li->li_family == su->su_family)
+      break;
+  }
+
+  if (li == NULL)
+    for (li = nat->localinfo; li; li = li->li_next) {
+      if (li != nat->private && li->li_family == su->su_family)
+	break;
+    }
+
+  if (li == NULL)
+    return su_seterrno(EADDRNOTAVAIL);
+
+  su_clone_pause(nat->clone);
+  memcpy(nat->out_address, address, nat->out_addrlen = addrlen);
+  nat->fake = li;
+  su_clone_resume(nat->clone);
+
+  return 0;
+}
+
+int test_nat_flush(struct nat *nat)
+{
+  if (nat == NULL)
+    return su_seterrno(EFAULT);
+
+  return su_task_execute(su_clone_task(nat->clone), 
+			 invalidate_bindings, nat, NULL);
+}
+
+/* ====================================================================== */
+
+struct binding *nat_binding_new(struct nat *nat,
+				char const *protoname,
+				int socktype,
+				int protocol,
+				int connected,
+				su_socket_t in_socket,
+				su_sockaddr_t *from,
+				socklen_t fromlen)
+{
+  struct binding *b;
+
+  if (nat->fake == NULL) {	/* Xyzzy */
+    fprintf(stderr, "test_nat: fake address missing\n");
+    su_close(in_socket);
+    return NULL;
+  }
+
+  b = su_zalloc(nat->home, sizeof *b);
+  if (b == NULL) {
+    su_perror("nat_binding_new: su_zalloc");
+    su_close(in_socket);
+    return 0;
+  }
+
+  b->nat = nat;
+  b->socktype = socktype;
+  b->protocol = protocol;
+  b->in_socket = in_socket, b->out_socket = INVALID_SOCKET;
+  b->in_register = -1, b->out_register = -1;
+
+  if (binding_init(b, protoname, connected, nat->fake, from, fromlen) < 0)
+    nat_binding_destroy(b), b = NULL;
+
+  return b;
+}
+
+static int binding_init(struct binding *b,
+			char const *protoname,
+			int connected,
+			su_localinfo_t *li,
+			su_sockaddr_t *from,
+			socklen_t fromlen)
+{
+  struct nat *nat = b->nat;
+  su_socket_t out_socket;
+  su_sockaddr_t addr[1];
+  socklen_t addrlen = (sizeof addr);
+  char ipname[64];
+  su_wait_t wait[1];
+  su_wakeup_f in_to_out, out_to_in;
+
+  if (b->socktype == SOCK_STREAM)
+    in_to_out = tcp_in_to_out, out_to_in = tcp_out_to_in;    
+  else
+    in_to_out = udp_in_to_out, out_to_in = udp_out_to_in;    
+  
+  if (b->in_socket == INVALID_SOCKET) {
+    int in_socket;
+
+    in_socket = su_socket(from->su_family, b->socktype, b->protocol);
+    if (in_socket == INVALID_SOCKET) {
+      su_perror("nat_binding_new: socket");
+      return -1;
+    }
+    b->in_socket = in_socket;
+    if (su_setreuseaddr(in_socket, 1) < 0) {
+      su_perror("nat_binding_new: su_setreuseaddr(in_socket)");
+      return -1;
+    }
+    if (bind(in_socket, (void *)nat->in_address, nat->in_addrlen) < 0) {
+      su_perror("nat_binding_new: bind(in_socket)");
+      return -1;
+    }
+    if (connect(in_socket, (void *)from, fromlen) < 0) {
+      su_perror("nat_binding_new: connect(in_socket)");
+      return -1;
+    }
+  }
+
+  out_socket = su_socket(li->li_family, b->socktype, b->protocol);
+  if (out_socket == INVALID_SOCKET) {
+    su_perror("nat_binding_new: socket");
+    return -1;
+  }
+  b->out_socket = out_socket;
+
+  if (bind(out_socket, (void *)li->li_addr, li->li_addrlen) < 0) {
+    su_perror("nat_binding_new: bind(to)");
+    return -1;
+  }
+
+  if (connected)
+    if (connect(out_socket, (void *)nat->out_address, nat->out_addrlen) < 0) {
+      su_perror("nat_binding_new: connect(to)");
+      return -1;
+    }
+
+  getpeername(b->in_socket, (void *)addr, &addrlen);
+  inet_ntop(addr->su_family, SU_ADDR(addr), ipname, sizeof ipname);
+  snprintf(b->in_name, sizeof b->in_name,
+	   addr->su_family == AF_INET6 ? "[%s]:%u" : "%s:%u",
+	   ipname, ntohs(addr->su_port));
+
+  getsockname(out_socket, (void *)addr, &addrlen);
+  inet_ntop(addr->su_family, SU_ADDR(addr), ipname, sizeof ipname);
+  snprintf(b->out_name, sizeof b->out_name,
+	   addr->su_family == AF_INET6 ? "[%s]:%u" : "%s:%u",
+	   ipname, ntohs(addr->su_port));
+
+  if (su_wait_create(wait, b->in_socket, SU_WAIT_IN) < 0) {
+    su_perror("nat_binding_new: su_wait_create");
+    return -1;
+  }
+  b->in_register = su_root_register(nat->root, wait, in_to_out, b, 0);
+  if (b->in_register < 0) {
+    su_perror("nat_binding_new: su_root_register");
+    su_wait_destroy(wait); 
+    return -1;
+  }
+
+  if (su_wait_create(wait, out_socket, SU_WAIT_IN) < 0) {
+    su_perror("nat_binding_new: su_wait_create");
+    return -1;
+  }
+  b->out_register = su_root_register(nat->root, wait, out_to_in, b, 0);
+  if (b->out_register < 0) {
+    su_perror("nat_binding_new: su_root_register");
+    su_wait_destroy(wait);
+    return -1;
+  }
+
+  nat_binding_insert(&nat->bindings, b);
+
+  if (nat->logging)
+    printf("nat: new %s binding %s <=> %s\n",
+	   protoname, b->in_name, b->out_name);
+
+  return 0;
+}
+
+static void nat_binding_destroy(struct binding *b)
+{
+  nat_binding_remove(b);
+  if (b->in_register != -1)
+    su_root_deregister(b->nat->root, b->in_register);
+  if (b->out_register != -1)
+    su_root_deregister(b->nat->root, b->out_register);
+  su_close(b->in_socket), su_close(b->out_socket);
+}
+
+static void flush_bindings(struct nat *nat)
+{
+  struct binding *b;
+
+  for (b = nat->bindings; b; b = b->next) {
+    if (b->in_register)
+      su_root_deregister(nat->root, b->in_register);
+    su_close(b->in_socket);
+    if (b->out_register)
+      su_root_deregister(nat->root, b->out_register);
+    su_close(b->out_socket);
+  }
+}
+
+static int invalidate_bindings(void *arg)
+{
+  struct nat *nat = arg;
+  struct binding *b;
+
+  for (b = nat->bindings; b; b = b->next) {
+    invalidate_binding(b);
+  }
+  return 0;
+}
+
+#if 0
+static struct binding *nat_binding_find(struct nat *nat,
+					su_sockaddr_t *from, 
+					int fromlen)
+{
+  char name[64], ipname[64];
+  size_t namelen;
+  struct binding *b;
+				       
+  inet_ntop(from->su_family, SU_ADDR(from), ipname, sizeof ipname);
+  snprintf(name, sizeof name,
+	   from->su_family == AF_INET6 ? "[%s]:%u" : "%s:%u",
+	   ipname, ntohs(from->su_port));
+  namelen = strlen(name) + 1;
+
+  for (b = nat->bindings; b; b = b->next) {
+    if (memcmp(name, b->in_name, namelen) == 0)
+      return b;
+  }
+
+  if (b == NULL)
+    b = nat_binding_new(nat, "UDP", SOCK_DGRAM, IPPROTO_UDP, nat->symmetric, 
+			INVALID_SOCKET, from, fromlen);
+
+  return b;
+}
+#endif
+
+/* ====================================================================== */
+
+LIST_BODIES(static, nat_binding, struct binding, next, prev);
+
+/* ====================================================================== */
+
+static int new_udp(struct nat *nat, su_wait_t *wait, struct binding *dummy)
+{
+  int events;
+  su_sockaddr_t from[1];
+  socklen_t fromlen = (sizeof from);
+  struct binding *b;
+  ssize_t n, m;
+
+  events = su_wait_events(wait, nat->udp_socket);
+
+  n = su_recvfrom(nat->udp_socket, nat->buffer, sizeof nat->buffer, 0,
+		  from, &fromlen);
+  if (n < 0) {
+    su_perror("new_udp: recvfrom");
+    return 0;
+  }
+
+  b = nat_binding_new(nat, "UDP", SOCK_DGRAM, IPPROTO_UDP, nat->symmetric, 
+		      INVALID_SOCKET, from, fromlen);
+  if (b == NULL)
+    return 0;
+
+  if (nat->symmetric)
+    m = su_send(b->out_socket, nat->buffer, n, 0);
+  else
+    m = su_sendto(b->out_socket, nat->buffer, n, 0, 
+		  nat->out_address, nat->out_addrlen);
+
+  if (nat->logging)
+    printf("nat: udp out %d/%d %s => %s\n",
+	   (int)m, (int)n, b->in_name, b->out_name);
+
+  return 0;
+}
+
+static int udp_in_to_out(struct nat *nat, su_wait_t *wait, struct binding *b)
+{
+  int events;
+  ssize_t n, m;
+  size_t len, filtered;
+  struct nat_filter *f;
+
+  events = su_wait_events(wait, b->in_socket);
+
+  n = su_recv(b->in_socket, nat->buffer, sizeof nat->buffer, 0);
+  if (n == -1) {
+    su_perror("udp_in_to_out: recv");
+    return 0;
+  }
+
+  len = (size_t)n;
+
+  for (f = nat->out_filters; f; f = f->next) {
+    filtered = f->condition(f->arg, nat->buffer, len);
+    if (filtered != len) {
+      if (nat->logging)
+	printf("nat: udp filtered "MOD_ZU" from %s => "MOD_ZU" to %s\n",
+	       len, b->in_name, filtered, b->out_name);
+      if (filtered == 0)
+	return 0;
+      len = filtered;
+    }
+  }
+
+  if (nat->symmetric)
+    m = su_send(b->out_socket, nat->buffer, len, 0);
+  else
+    m = su_sendto(b->out_socket, nat->buffer, len, 0,
+		  nat->out_address, nat->out_addrlen);
+
+  if (nat->logging)
+    printf("nat: udp out %d/%d %s => %s\n",
+	   (int)m, (int)n, b->in_name, b->out_name);
+
+  return 0;
+}
+
+static int udp_out_to_in(struct nat *nat, su_wait_t *wait, struct binding *b)
+{
+  int events;
+  ssize_t n, m;
+  size_t len, filtered;
+  struct nat_filter *f;
+
+  events = su_wait_events(wait, b->out_socket);
+
+  n = su_recv(b->out_socket, nat->buffer, sizeof nat->buffer, 0);
+  if (n < 0) {
+    su_perror("udp_out_to_out: recv");
+    return 0;
+  }
+
+  len = (size_t)n;
+
+  for (f = nat->in_filters; f; f = f->next) {
+    filtered = f->condition(f->arg, nat->buffer, len);
+    if (filtered != len) {
+      if (nat->logging)
+	printf("nat: udp filtered "MOD_ZU" from %s => "MOD_ZU" to %s\n",
+	       len, b->out_name, filtered, b->in_name);
+      if (filtered == 0)
+	return 0;
+      len = filtered;
+    }
+  }
+
+  m = su_send(b->in_socket, nat->buffer, n, 0);
+
+  if (nat->logging)
+    printf("nat: udp in %d/%d %s => %s\n",
+	   (int)m, (int)n, b->out_name, b->in_name);
+
+  return 0;
+}
+
+/* ====================================================================== */
+
+static int new_tcp(struct nat *nat, su_wait_t *wait, struct binding *dummy)
+{
+  int events;
+  su_socket_t in_socket;
+  su_sockaddr_t from[1];
+  socklen_t fromlen = (sizeof from);
+  struct binding *b;
+
+  events = su_wait_events(wait, nat->tcp_socket);
+
+  in_socket = accept(nat->tcp_socket, (void *)from, &fromlen);
+  if (in_socket == INVALID_SOCKET) {
+    su_perror("new_tcp: accept");
+    return 0;
+  }
+
+  b = nat_binding_new(nat, "TCP", SOCK_STREAM, IPPROTO_TCP, 1,
+		      in_socket, from, fromlen);
+  if (b == NULL)
+    return 0;
+
+  return 0;
+}
+
+static int tcp_in_to_out(struct nat *nat, su_wait_t *wait, struct binding *b)
+{
+  int events;
+  ssize_t n, m, o;
+
+  events = su_wait_events(wait, b->in_socket);
+
+  n = su_recv(b->in_socket, nat->buffer, sizeof nat->buffer, 0);
+  if (n < 0) {
+    su_perror("tcp_in_to_out: recv");
+    return 0;
+  }
+
+  if (n == 0) {
+    if (nat->logging)
+      printf("nat: tcp out FIN %s => %s\n", b->in_name, b->out_name);
+    shutdown(b->out_socket, 1);
+    su_root_eventmask(nat->root, b->in_register, b->in_socket, 0);
+    b->in_closed = 1;
+    if (b->out_closed && b->in_closed)
+      nat_binding_destroy(b);
+    return 0;
+  }
+
+  for (m = 0; m < n; m += o) {
+    o = su_send(b->out_socket, nat->buffer + m, n - m, 0);
+    if (o < 0) {
+      su_perror("tcp_in_to_out: send");
+      break;
+    }
+  }
+
+  if (nat->logging)
+    printf("nat: tcp out %d/%d %s => %s\n",
+	   (int)m, (int)n, b->in_name, b->out_name);
+
+  return 0;
+}
+
+static int tcp_out_to_in(struct nat *nat, su_wait_t *wait, struct binding *b)
+{
+  int events;
+  ssize_t n, m, o;
+
+  events = su_wait_events(wait, b->out_socket);
+
+  n = su_recv(b->out_socket, nat->buffer, sizeof nat->buffer, 0);
+  if (n < 0) {
+    su_perror("tcp_out_to_in: recv");
+    return 0;
+  }
+
+  if (n == 0) {
+    if (nat->logging)
+      printf("nat: tcp out FIN %s => %s\n", b->out_name, b->in_name);
+    shutdown(b->in_socket, 1);
+    su_root_eventmask(nat->root, b->in_register, b->out_socket, 0);
+    b->out_closed = 1;
+    if (b->out_closed && b->in_closed)
+      nat_binding_destroy(b);
+    return 0;
+  }
+
+  for (m = 0; m < n; m += o) {
+    o = su_send(b->in_socket, nat->buffer + m, n - m, 0);
+    if (o < 0) {
+      if (su_errno() != EPIPE)
+	su_perror("tcp_in_to_out: send");
+      break;
+    }
+  }
+
+  if (nat->logging)
+    printf("nat: tcp in %d/%d %s => %s\n",
+	   (int)m, (int)n, b->out_name, b->in_name);
+
+  return 0;
+}
+
+static int invalidate_binding(struct binding *b)
+{
+  struct nat *nat = b->nat;
+  su_sockaddr_t addr[1];
+  socklen_t addrlen = (sizeof addr);
+  su_socket_t out;
+  int out_register;
+  su_wait_t wout[1];
+  char name[64];
+
+  out = su_socket(nat->fake->li_family, b->socktype, 0);
+  if (out == INVALID_SOCKET) {
+    su_perror("new_udp: socket");
+    return -1;
+  }
+  if (bind(out, (void *)nat->fake->li_addr, nat->fake->li_addrlen) < 0) {
+    su_perror("new_udp: bind(to)");
+    su_close(out);
+    return -1;
+  }
+
+  if (nat->symmetric)
+    if (connect(out, (void *)nat->out_address, nat->out_addrlen) < 0) {
+      su_perror("new_udp: connect(to)");
+      su_close(out);
+      return -1;
+    }
+
+  if (su_wait_create(wout, out, SU_WAIT_IN) < 0) {
+    su_perror("new_udp: su_wait_create");
+    su_close(out);
+    return -1;
+  }
+
+  if (b->socktype == SOCK_DGRAM)
+    out_register = su_root_register(nat->root, wout, udp_out_to_in, b, 0);
+  else
+    out_register = su_root_register(nat->root, wout, tcp_out_to_in, b, 0);
+
+  if (out_register < 0) {
+    su_perror("new_udp: su_root_register");
+    su_wait_destroy(wout);
+    su_close(out);
+    return -1;
+  }
+
+  su_root_deregister(nat->root, b->out_register);
+  su_close(b->out_socket);
+
+  b->out_socket = out;
+  b->out_register = out_register;
+
+  getsockname(out, (void *)addr, &addrlen);
+  inet_ntop(addr->su_family, SU_ADDR(addr), name, sizeof name);
+  snprintf(b->out_name, sizeof b->out_name,
+	   addr->su_family == AF_INET6 ? "[%s]:%u" : "%s:%u",
+	   name, ntohs(addr->su_port));
+
+  if (nat->logging)
+    printf("nat: flushed binding %s <=> %s\n", b->in_name, b->out_name);
+
+  return 0;
+}
+
+LIST_BODIES(static, nat_filter, struct nat_filter, next, prev);
+
+struct args {
+  struct nat *nat;
+  struct nat_filter *f;
+  int outbound;
+};
+
+int execute_nat_filter_insert(void *_args)
+{
+  struct args *a = (struct args *)_args;
+  if (a->outbound)
+    nat_filter_insert(&a->nat->out_filters, a->f);
+  else
+    nat_filter_insert(&a->nat->in_filters, a->f);
+  return 0;
+}
+
+int execute_nat_filter_remove(void *_args)
+{
+  struct args *a = (struct args *)_args;
+  nat_filter_remove(a->f);
+  return 0;
+}
+
+struct nat_filter *test_nat_add_filter(struct nat *nat,
+				       size_t (*condition)(void *arg,
+							   void *message,
+							   size_t len),
+				       void *arg,
+				       int outbound)
+{
+  struct args a[1];
+
+  if (nat == NULL)
+    return su_seterrno(EFAULT), NULL;
+
+  a->nat = nat;
+  a->f = su_zalloc(nat->home, sizeof *a->f);
+  a->outbound = outbound;
+
+  if (a->f) {
+    a->f->condition = condition;
+    a->f->arg = arg;
+    if (su_task_execute(su_clone_task(nat->clone),
+			execute_nat_filter_insert, a, NULL) < 0)
+      su_free(nat->home, a->f), a->f = NULL;
+  }
+
+  return a->f;
+}
+
+
+int test_nat_remove_filter(struct nat *nat,
+			   struct nat_filter *filter)
+{
+  struct args a[1];
+
+  if (nat == NULL)
+    return su_seterrno(EFAULT);
+
+  a->nat = nat;
+  a->f = filter;
+  
+  if (su_task_execute(su_clone_task(nat->clone),
+		      execute_nat_filter_remove, a, NULL) < 0)
+    return -1;
+
+  su_free(nat->home, filter);
+  return 0;
+}

Added: freeswitch/trunk/libs/sofia-sip/tests/test_nat.h
==============================================================================
--- (empty file)
+++ freeswitch/trunk/libs/sofia-sip/tests/test_nat.h	Wed May 14 15:10:54 2008
@@ -0,0 +1,74 @@
+/*
+ * This file is part of the Sofia-SIP package
+ *
+ * Copyright (C) 2005 Nokia Corporation.
+ *
+ * Contact: Pekka Pessi <pekka.pessi at nokia.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA
+ *
+ */
+
+#ifndef TEST_NAT_H
+#define TEST_NAT_H
+
+#include <sofia-sip/su_wait.h>
+#include <sofia-sip/nta.h>
+
+SOFIA_BEGIN_DECLS
+
+struct nat;
+struct nat_filter;
+
+struct nat *test_nat_create(su_root_t *, int family, 
+			    tag_type_t, tag_value_t, ...);
+
+void test_nat_destroy(struct nat *);
+
+int test_nat_private(struct nat *nat, void *address, socklen_t *return_addrlen);
+int test_nat_public(struct nat *nat, void const *address, int addrlen);
+
+int test_nat_flush(struct nat *nat);
+
+struct nat_filter *test_nat_add_filter(struct nat *nat,
+				       size_t (*condition)(void *arg,
+							   void *message,
+							   size_t len),
+				       void *arg,
+				       int outbound);
+
+enum { nat_inbound, nat_outbound };
+
+int test_nat_remove_filter(struct nat *nat,
+			   struct nat_filter *filter);
+
+/* Tags */
+
+/** If true, act as symmetric nat. */
+#define TESTNATTAG_SYMMETRIC(x) testnattag_symmetric, tag_bool_v((x))
+#define TESTNATTAG_SYMMETRIC_REF(x) testnattag_symmetric_ref, tag_bool_vr(&(x))
+extern tag_typedef_t testnattag_symmetric;
+extern tag_typedef_t testnattag_symmetric_ref;
+
+/** If true, print information about connections. */
+#define TESTNATTAG_LOGGING(x) testnattag_logging, tag_bool_v((x))
+#define TESTNATTAG_LOGGING_REF(x) testnattag_logging_ref, tag_bool_vr(&(x))
+extern tag_typedef_t testnattag_logging;
+extern tag_typedef_t testnattag_logging_ref;
+
+SOFIA_END_DECLS
+
+#endif

Added: freeswitch/trunk/libs/sofia-sip/tests/test_nat_tags.c
==============================================================================
--- (empty file)
+++ freeswitch/trunk/libs/sofia-sip/tests/test_nat_tags.c	Wed May 14 15:10:54 2008
@@ -0,0 +1,40 @@
+/*
+ * This file is part of the Sofia-SIP package
+ *
+ * Copyright (C) 2005 Nokia Corporation.
+ *
+ * Contact: Pekka Pessi <pekka.pessi at nokia.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA
+ *
+ */
+
+/**@CFILE test_nat_tags.c
+ * @brief Tags for simulated NAT
+ *
+ * @author Pekka Pessi <Pekka.Pessi at nokia.com>
+ *
+ * @date Created: Wed Mar  8 19:54:28 EET 2006
+ */
+
+#include "config.h"
+
+#include "test_nat.h"
+
+tag_typedef_t testnattag_symmetric = BOOLTAG_TYPEDEF(symmetric);
+tag_typedef_t testnattag_symmetric_ref = REFTAG_TYPEDEF(testnattag_symmetric);
+tag_typedef_t testnattag_logging = BOOLTAG_TYPEDEF(symmetric);
+tag_typedef_t testnattag_logging_ref = REFTAG_TYPEDEF(testnattag_logging);

Added: freeswitch/trunk/libs/sofia-sip/tests/test_nua.c
==============================================================================
--- (empty file)
+++ freeswitch/trunk/libs/sofia-sip/tests/test_nua.c	Wed May 14 15:10:54 2008
@@ -0,0 +1,397 @@
+/*
+ * This file is part of the Sofia-SIP package
+ *
+ * Copyright (C) 2005 Nokia Corporation.
+ *
+ * Contact: Pekka Pessi <pekka.pessi at nokia.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA
+ *
+ */
+
+/**@CFILE test_nua.c
+ * @brief High-level tester for Sofia SIP User Agent Engine
+ *
+ * @author Pekka Pessi <Pekka.Pessi at nokia.com>
+ * @author Martti Mela <Martti Mela at nokia.com>
+ *
+ * @date Created: Wed Aug 17 12:12:12 EEST 2005 ppessi
+ */
+
+#include "config.h"
+
+#include "test_nua.h"
+
+#if HAVE_ALARM
+#include <signal.h>
+#endif
+
+#if defined(_WIN32)
+#include <fcntl.h>
+#endif
+
+SOFIAPUBVAR su_log_t nua_log[];
+SOFIAPUBVAR su_log_t soa_log[];
+SOFIAPUBVAR su_log_t nea_log[];
+SOFIAPUBVAR su_log_t nta_log[];
+SOFIAPUBVAR su_log_t tport_log[];
+SOFIAPUBVAR su_log_t su_log_default[];
+
+char const name[] = "test_nua";
+int print_headings = 1;
+int tstflags = 0;
+
+#if HAVE_FUNC
+#elif HAVE_FUNCTION
+#define __func__ __FUNCTION__
+#else
+#define __func__ name
+#endif
+
+#if HAVE_ALARM
+static RETSIGTYPE sig_alarm(int s)
+{
+  fprintf(stderr, "%s: FAIL! test timeout!\n", name);
+  if (tstflags & tst_abort)
+    abort();
+  exit(1);
+}
+#endif
+
+static char const options_usage[] =
+  "   -v | --verbose    be verbose\n"
+  "   -q | --quiet      be quiet\n"
+  "   -a | --abort      abort on error\n" 
+  "   -s                use only single thread\n"
+  "   -l level          set logging level (0 by default)\n"
+  "   -e | --events     print nua events\n"
+  "   -A                print nua events for A\n"
+  "   -B                print nua events for B\n"
+  "   -C                print nua events for C\n"
+  "   --log=a           log messages for A\n"
+  "   --log=b           log messages for B\n"
+  "   --log=c           log messages for C\n"
+  "   --log=proxy       log messages for proxy\n"
+  "   --attach          print pid, wait for a debugger to be attached\n"
+  "   --no-proxy        do not use internal proxy\n"
+  "   --no-nat          do not use internal \"nat\"\n"
+  "   --symmetric       run internal \"nat\" in symmetric mode\n"
+  "   -N                print events from internal \"nat\"\n"
+  "   --loop            loop main tests for ever\n"
+  "   --no-alarm        don't ask for guard ALARM\n"
+  "   -p uri            specify uri of outbound proxy (implies --no-proxy)\n"
+  "   --proxy-tests     run tests involving proxy, too\n"
+#if SU_HAVE_OSX_CF_API /* If compiled with CoreFoundation events */
+  "   --osx-runloop     use OSX CoreFoundation runloop instead of poll() loop\n"
+#endif
+  "   -k                do not exit after first error\n"
+  ;
+
+void usage(int exitcode)
+{
+  fprintf(stderr, "usage: %s OPTIONS\n   where OPTIONS are\n%s",
+	    name, options_usage);
+  exit(exitcode);
+}
+
+int main(int argc, char *argv[])
+{
+  int retval = 0;
+  int i, o_quiet = 0, o_attach = 0, o_alarm = 1, o_loop = 0;
+  int o_events_init = 0, o_events_a = 0, o_events_b = 0, o_events_c = 0;
+  int o_iproxy = 1, o_inat = 1;
+  int o_inat_symmetric = 0, o_inat_logging = 0, o_expensive = 0;
+  url_t const *o_proxy = NULL;
+  int level = 0;
+
+  struct context ctx[1] = {{{ SU_HOME_INIT(ctx) }}};
+
+#if HAVE_OPEN_C
+  dup2(1, 2);
+#endif
+  
+  if (getenv("EXPENSIVE_CHECKS"))
+    o_expensive = 1;
+
+  ctx->threading = 1;
+  ctx->quit_on_single_failure = 1;
+
+  endpoint_init(ctx, &ctx->a, 'a');
+  endpoint_init(ctx, &ctx->b, 'b');
+  endpoint_init(ctx, &ctx->c, 'c');
+
+  for (i = 1; argv[i]; i++) {
+    if (strcmp(argv[i], "-v") == 0 || strcmp(argv[i], "--verbose") == 0)
+      tstflags |= tst_verbatim;
+    else if (strcmp(argv[i], "-a") == 0 || strcmp(argv[i], "--abort") == 0)
+      tstflags |= tst_abort;
+    else if (strcmp(argv[i], "-q") == 0 || strcmp(argv[i], "--quiet") == 0)
+      tstflags &= ~tst_verbatim, o_quiet = 1;
+    else if (strcmp(argv[i], "-k") == 0)
+      ctx->quit_on_single_failure = 0;
+    else if (strncmp(argv[i], "-l", 2) == 0) {
+      char *rest = NULL;
+
+      if (argv[i][2])
+	level = strtol(argv[i] + 2, &rest, 10);
+      else if (argv[i + 1])
+	level = strtol(argv[i + 1], &rest, 10), i++;
+      else
+	level = 3, rest = "";
+
+      if (rest == NULL || *rest)
+	usage(1);
+
+      su_log_set_level(nua_log, level);
+      su_log_soft_set_level(soa_log, level);
+      su_log_soft_set_level(nea_log, level);
+      su_log_soft_set_level(nta_log, level);
+      su_log_soft_set_level(tport_log, level);
+    }
+    else if (strcmp(argv[i], "-e") == 0 || strcmp(argv[i], "--events") == 0) {
+      o_events_init = o_events_a = o_events_b = o_events_c = 1;
+    }
+    else if (strcmp(argv[i], "-I") == 0) {
+      o_events_init = 1;
+    }
+    else if (strcmp(argv[i], "-A") == 0) {
+      o_events_a = 1;
+    }
+    else if (strcmp(argv[i], "-B") == 0) {
+      o_events_b = 1;
+    }
+    else if (strcmp(argv[i], "-C") == 0) {
+      o_events_c = 1;
+    }
+    else if (strcmp(argv[i], "-s") == 0) {
+      ctx->threading = 0;
+    }
+    else if (strcmp(argv[i], "--attach") == 0) {
+      o_attach = 1;
+    }
+    else if (strncmp(argv[i], "-p", 2) == 0) {
+      if (argv[i][2])
+	o_proxy = URL_STRING_MAKE(argv[i] + 2)->us_url;
+      else if (!argv[++i] || argv[i][0] == '-')
+	usage(1);
+      else
+	o_proxy = URL_STRING_MAKE(argv[i])->us_url;
+    }
+    else if (strcmp(argv[i], "--proxy-tests") == 0) {
+      ctx->proxy_tests = 1;
+    }
+    else if (strcmp(argv[i], "--no-proxy") == 0) {
+      o_iproxy = 0;
+    }
+    else if (strcmp(argv[i], "--no-nat") == 0) {
+      o_inat = 0;
+    }
+    else if (strcmp(argv[i], "--nat") == 0) {
+      o_inat = 1;
+    }
+    else if (strcmp(argv[i], "--symmetric") == 0) {
+      o_inat_symmetric = 1;
+    }
+    else if (strcmp(argv[i], "-N") == 0) {
+      o_inat_logging = 1;
+    }
+    else if (strcmp(argv[i], "--expensive") == 0) {
+      o_expensive = 1;
+    }
+    else if (strcmp(argv[i], "--no-alarm") == 0) {
+      o_alarm = 0;
+    }
+    else if (strcmp(argv[i], "--loop") == 0) {
+      o_alarm = 0, o_loop = 1;
+    }
+    else if (strcmp(argv[i], "--print-tags") == 0) {
+      ctx->print_tags = 1;
+    }
+    else if (strcmp(argv[i], "--tags=a") == 0) {
+      ctx->a.print_tags = 1;
+    }
+    else if (strcmp(argv[i], "--tags=b") == 0) {
+      ctx->b.print_tags = 1;
+    }
+    else if (strcmp(argv[i], "--tags=c") == 0) {
+      ctx->c.print_tags = 1;
+    }
+    else if (strcmp(argv[i], "--log=a") == 0) {
+      ctx->a.logging = 1;
+    }
+    else if (strcmp(argv[i], "--log=b") == 0) {
+      ctx->b.logging = 1;
+    }
+    else if (strcmp(argv[i], "--log=c") == 0) {
+      ctx->c.logging = 1;
+    }
+    else if (strcmp(argv[i], "--log=proxy") == 0) {
+      ctx->proxy_logging = 1;
+    }
+#if SU_HAVE_OSX_CF_API /* If compiled with CoreFoundation events */
+    else if (strcmp(argv[i], "--osx-runloop") == 0) {
+      ctx->osx_runloop = 1;
+    }
+#endif
+    else if (strcmp(argv[i], "-") == 0) {
+      i++; break;
+    }
+    else if (argv[i][0] != '-') {
+      break;
+    }
+    else {
+      fprintf(stderr, "test_nua: unknown argument \"%s\"\n\n", argv[i]);
+      usage(1);
+    }
+  }
+
+  if (o_attach) {
+    char line[10], *l;
+    printf("%s: pid %lu\n", name, (unsigned long)getpid());
+    printf("<Press RETURN to continue>\n");
+    l = fgets(line, sizeof line, stdin);
+  }
+#if HAVE_ALARM
+  else if (o_alarm) {
+    signal(SIGALRM, sig_alarm);
+    if (o_expensive) {
+      printf("%s: extending timeout to %u because expensive tests\n",
+	     name, 240);
+      alarm(240);
+    }
+    else {
+      alarm(120);
+    }
+  }
+#endif
+
+#if HAVE_OPEN_C
+  tstflags |= tst_verbatim;
+  level = 9;
+  o_inat = 1; /* No NATs */
+  ctx->threading = 1;
+  ctx->quit_on_single_failure = 1;
+  su_log_soft_set_level(nua_log, level);
+  su_log_soft_set_level(soa_log, level);
+  su_log_soft_set_level(su_log_default, level);
+  su_log_soft_set_level(nea_log, level);
+  su_log_soft_set_level(nta_log, level);
+  su_log_soft_set_level(tport_log, level);
+  setenv("SU_DEBUG", "9", 1);
+  setenv("NUA_DEBUG", "9", 1);
+  setenv("NTA_DEBUG", "9", 1);
+  setenv("TPORT_DEBUG", "9", 1);
+  o_events_a = o_events_b = 1;
+#endif
+
+  su_init();
+
+  if (!(TSTFLAGS & tst_verbatim)) {
+    if (level == 0 && !o_quiet)
+      level = 1;
+    su_log_soft_set_level(nua_log, level);
+    su_log_soft_set_level(soa_log, level);
+    su_log_soft_set_level(su_log_default, level);
+    su_log_soft_set_level(nea_log, level);
+    su_log_soft_set_level(nta_log, level);
+    su_log_soft_set_level(tport_log, level);
+  }
+
+  if (!o_quiet || (TSTFLAGS & tst_verbatim)
+      || o_events_a || o_events_b || o_events_c)
+    print_headings = 1;
+
+#if !HAVE_OPEN_C
+#define SINGLE_FAILURE_CHECK()						\
+  do { fflush(stdout);							\
+    if (retval && ctx->quit_on_single_failure) {			\
+      su_deinit(); return retval; }					\
+  } while(0)
+#else
+#define SINGLE_FAILURE_CHECK()						\
+  do { fflush(stdout);							\
+    if (retval && ctx->quit_on_single_failure) {			\
+      su_deinit(); sleep(10); return retval; }					\
+  } while(0)
+#endif
+
+  ctx->a.printer = o_events_init ? print_event : NULL;
+
+  retval |= test_nua_api_errors(ctx); SINGLE_FAILURE_CHECK();
+  
+  retval |= test_tag_filter(); SINGLE_FAILURE_CHECK();
+
+  retval |= test_nua_params(ctx); SINGLE_FAILURE_CHECK();
+
+  retval |= test_nua_destroy(ctx); SINGLE_FAILURE_CHECK();
+
+  retval |= test_stack_errors(ctx); SINGLE_FAILURE_CHECK();
+
+  retval |= test_nua_init(ctx, o_iproxy, o_proxy, o_inat,
+			  TESTNATTAG_SYMMETRIC(o_inat_symmetric),
+			  TESTNATTAG_LOGGING(o_inat_logging),
+			  TAG_END());
+
+  ctx->expensive = o_expensive;
+
+  if (retval == 0) {
+    ctx->a.printer = o_events_a ? print_event : NULL;
+    if (o_events_b)
+      ctx->b.printer = print_event;
+    if (o_events_c)
+      ctx->c.printer = print_event;
+
+    retval |= test_register(ctx);
+
+    if (retval == 0)
+      retval |= test_connectivity(ctx);
+
+    if (retval == 0 && o_inat)
+      retval |= test_nat_timeout(ctx);
+
+    while (retval == 0) {
+      retval |= test_basic_call(ctx); SINGLE_FAILURE_CHECK();
+      retval |= test_rejects(ctx); SINGLE_FAILURE_CHECK();
+      retval |= test_call_cancel(ctx); SINGLE_FAILURE_CHECK();
+      retval |= test_call_destroy(ctx); SINGLE_FAILURE_CHECK();
+      retval |= test_early_bye(ctx); SINGLE_FAILURE_CHECK();
+      retval |= test_offer_answer(ctx); SINGLE_FAILURE_CHECK();
+      retval |= test_reinvites(ctx); SINGLE_FAILURE_CHECK();
+      retval |= test_session_timer(ctx); SINGLE_FAILURE_CHECK();
+      retval |= test_refer(ctx); SINGLE_FAILURE_CHECK();
+      retval |= test_100rel(ctx); SINGLE_FAILURE_CHECK();
+      retval |= test_simple(ctx); SINGLE_FAILURE_CHECK();
+      retval |= test_events(ctx); SINGLE_FAILURE_CHECK();
+      retval |= test_extension(ctx); SINGLE_FAILURE_CHECK();
+      if (!o_loop)
+	break;
+    }
+
+    if (ctx->proxy_tests && (retval == 0 || !ctx->p))
+      retval |= test_unregister(ctx); SINGLE_FAILURE_CHECK();
+  }
+  retval |= test_deinit(ctx);
+
+  su_home_deinit(ctx->home);
+
+  su_deinit();
+
+#if HAVE_OPEN_C
+  sleep(7);
+#endif
+
+  return retval;
+}

Added: freeswitch/trunk/libs/sofia-sip/tests/test_nua.h
==============================================================================
--- (empty file)
+++ freeswitch/trunk/libs/sofia-sip/tests/test_nua.h	Wed May 14 15:10:54 2008
@@ -0,0 +1,364 @@
+/*
+ * This file is part of the Sofia-SIP package
+ *
+ * Copyright (C) 2005 Nokia Corporation.
+ *
+ * Contact: Pekka Pessi <pekka.pessi at nokia.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA
+ *
+ */
+
+/**@@internal
+ * @file test_nua.h
+ * @brief High-level tester framework for Sofia SIP User Agent Engine
+ *
+ * @author Pekka Pessi <Pekka.Pessi at nokia.com>
+ * @author Martti Mela <Martti Mela at nokia.com>
+ *
+ * @date Created: Wed Aug 17 12:12:12 EEST 2005 ppessi
+ */
+
+#ifndef TEST_NUA_H
+#define TEST_NUA_H
+
+struct context;
+#define NUA_MAGIC_T struct context
+
+struct call;
+#define NUA_HMAGIC_T struct call
+
+#include "sofia-sip/nua.h"
+#include "sofia-sip/sip_status.h"
+
+#include <sofia-sip/sdp.h>
+#include <sofia-sip/sip_header.h>
+
+#include <sofia-sip/su_log.h>
+#include <sofia-sip/su_tagarg.h>
+#include <sofia-sip/su_tag_io.h>
+#include <sofia-sip/nua_tag.h>
+
+#if __APPLE_CC__
+#include <sofia-sip/su_osx_runloop.h>
+#endif
+
+#include "test_proxy.h"
+#include "test_nat.h"
+#include <sofia-sip/auth_module.h>
+
+#include <stddef.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <assert.h>
+#include <unistd.h>
+
+extern char const name[];
+
+extern int print_headings;
+extern int tstflags;
+#define TSTFLAGS tstflags
+
+#include <sofia-sip/tstdef.h>
+
+#define TEST_E(a, b) TEST_S(nua_event_name(a), nua_event_name(b))
+
+#define NONE ((void*)-1)
+
+struct endpoint;
+
+typedef
+int condition_function(nua_event_t event,
+		       int status, char const *phrase,
+		       nua_t *nua, struct context *ctx,
+		       struct endpoint *ep,
+		       nua_handle_t *nh, struct call *call,
+		       sip_t const *sip,
+		       tagi_t tags[]);
+
+typedef
+void printer_function(nua_event_t event,
+		      char const *operation,
+		      int status, char const *phrase,
+		      nua_t *nua, struct context *ctx,
+		      struct endpoint *ep,
+		      nua_handle_t *nh, struct call *call,
+		      sip_t const *sip,
+		      tagi_t tags[]);
+
+struct proxy_transaction;
+struct registration_entry;
+
+enum { event_is_extra, event_is_normal, event_is_special };
+
+struct eventlist
+{
+  nua_event_t kind;
+  struct event *head, **tail;
+};
+
+struct event 
+{
+  struct event *next, **prev;
+  struct call *call;
+  nua_saved_event_t saved_event[1];
+  nua_event_data_t const *data;
+};
+
+
+struct context
+{
+  su_home_t home[1];
+  su_root_t *root;
+
+  int threading, proxy_tests, expensive, quit_on_single_failure, osx_runloop;
+  int print_tags;
+
+  url_t *external_proxy;
+
+  int proxy_logging;
+
+  struct endpoint {
+    char name[4];
+    struct context *ctx;	/* Backpointer */
+
+    int logging;
+    int print_tags;
+
+    int running;
+
+    struct domain *domain;
+    condition_function *next_condition;
+    nua_event_t next_event, last_event;
+    nua_t *nua;
+    sip_contact_t *contact;
+    sip_from_t *to;
+
+    sip_allow_t *allow;
+    char const *appl_method;
+    sip_supported_t *supported;
+
+    printer_function *printer;
+
+    char const *instance;
+
+    /* Per-call stuff */
+    struct call {
+      struct call *next;
+      nua_handle_t *nh;
+      char const *sdp;
+      struct eventlist *events;
+    } call[1], reg[1];
+
+    int (*is_special)(nua_event_t e);
+
+    /* Normal events are saved here */
+    struct eventlist events[1];
+    /* Special events are saved here */
+    struct eventlist specials[1];
+
+    /* State flags for complex scenarios */
+    struct {
+      unsigned n;
+      unsigned bit0:1, bit1:1, bit2:1, bit3:1;
+      unsigned bit4:1, bit5:1, bit6:1, bit7:1;
+      unsigned :0;
+    } flags;
+    /* Accross-run state information */
+    struct {
+      unsigned n;
+    } state;
+  } a, b, c;
+
+  struct proxy *p;
+  sip_route_t const *lr;
+  struct nat *nat;
+};
+
+#define RETURN_ON_SINGLE_FAILURE(retval)			  \
+  do {								  \
+    fflush(stdout);						  \
+    if (retval && ctx->quit_on_single_failure) { return retval; } \
+  } while(0)
+
+
+int save_event_in_list(struct context *,
+		       nua_event_t nevent,
+		       struct endpoint *,
+		       struct call *);
+void free_events_in_list(struct context *,
+			 struct eventlist *);
+void free_event_in_list(struct context *ctx,
+			struct eventlist *list,
+			struct event *e);
+
+struct event *event_by_type(struct event *e, nua_event_t);
+size_t count_events(struct event const *e);
+
+#define CONDITION_PARAMS			\
+  nua_event_t event,				\
+  int status, char const *phrase,		\
+  nua_t *nua, struct context *ctx,		\
+  struct endpoint *ep,				\
+  nua_handle_t *nh, struct call *call,		\
+  sip_t const *sip,				\
+  tagi_t tags[]
+
+int save_events(CONDITION_PARAMS);
+int until_final_response(CONDITION_PARAMS);
+int save_until_final_response(CONDITION_PARAMS);
+int save_until_received(CONDITION_PARAMS);
+int save_until_special(CONDITION_PARAMS);
+
+int until_terminated(CONDITION_PARAMS);
+int until_ready(CONDITION_PARAMS);
+int accept_call(CONDITION_PARAMS);
+int cancel_when_ringing(CONDITION_PARAMS);
+
+int accept_notify(CONDITION_PARAMS);
+
+void a_callback(nua_event_t event,
+		int status, char const *phrase,
+		nua_t *nua, struct context *ctx,
+		nua_handle_t *nh, struct call *call,
+		sip_t const *sip,
+		tagi_t tags[]);
+void b_callback(nua_event_t event,
+		int status, char const *phrase,
+		nua_t *nua, struct context *ctx,
+		nua_handle_t *nh, struct call *call,
+		sip_t const *sip,
+		tagi_t tags[]);
+void c_callback(nua_event_t event,
+		int status, char const *phrase,
+		nua_t *nua, struct context *ctx,
+		nua_handle_t *nh, struct call *call,
+		sip_t const *sip,
+		tagi_t tags[]);
+
+void run_abc_until(struct context *ctx,
+		   nua_event_t a_event, condition_function *a_condition,
+		   nua_event_t b_event, condition_function *b_condition,
+		   nua_event_t c_event, condition_function *c_condition);
+
+void run_ab_until(struct context *ctx,
+		  nua_event_t a_event, condition_function *a_condition,
+		  nua_event_t b_event, condition_function *b_condition);
+
+void run_bc_until(struct context *ctx,
+		  nua_event_t b_event, condition_function *b_condition,
+		  nua_event_t c_event, condition_function *c_condition);
+
+int run_a_until(struct context *, nua_event_t, condition_function *);
+int run_b_until(struct context *, nua_event_t, condition_function *);
+int run_c_until(struct context *, nua_event_t, condition_function *);
+
+typedef int operation_f(struct endpoint *ep, struct call *call, 
+			nua_handle_t *nh, tag_type_t tag, tag_value_t value,
+			...);
+
+operation_f INVITE, ACK, BYE, CANCEL, AUTHENTICATE, UPDATE, INFO, PRACK,
+  REFER, MESSAGE, METHOD, OPTIONS, PUBLISH, UNPUBLISH, REGISTER, UNREGISTER,
+  SUBSCRIBE, UNSUBSCRIBE, NOTIFY, NOTIFIER, TERMINATE, AUTHORIZE;
+
+int RESPOND(struct endpoint *ep,
+	    struct call *call,
+	    nua_handle_t *nh,
+	    int status, char const *phrase,
+	    tag_type_t tag, tag_value_t value,
+	    ...);
+
+int DESTROY(struct endpoint *ep,
+	    struct call *call,
+	    nua_handle_t *nh);
+
+struct call *check_handle(struct endpoint *ep,
+			  struct call *call,
+			  nua_handle_t *nh,
+			  int status, char const *phrase);
+
+int is_special(nua_event_t e);
+int callstate(tagi_t const *tags);
+int is_offer_sent(tagi_t const *tags);
+int is_answer_sent(tagi_t const *tags);
+int is_offer_recv(tagi_t const *tags);
+int is_answer_recv(tagi_t const *tags);
+int is_offer_answer_done(tagi_t const *tags);
+int audio_activity(tagi_t const *tags);
+int video_activity(tagi_t const *tags);
+
+void print_event(nua_event_t event,
+		 char const *operation,
+		 int status, char const *phrase,
+		 nua_t *nua, struct context *ctx,
+		 struct endpoint *ep,
+		 nua_handle_t *nh, struct call *call,
+		 sip_t const *sip,
+		 tagi_t tags[]);
+
+su_inline
+void eventlist_init(struct eventlist *list)
+{
+  list->tail = &list->head;
+}
+
+su_inline
+void call_init(struct call *call)
+{
+}
+
+void endpoint_init(struct context *ctx, struct endpoint *e, char id);
+
+int test_nua_init(struct context *ctx,
+		  int start_proxy,
+		  url_t const *o_proxy,
+		  int start_nat,
+		  tag_type_t tag, tag_value_t value, ...);
+
+int test_deinit(struct context *ctx);
+
+int test_nua_api_errors(struct context *ctx);
+int test_nua_destroy(struct context *ctx);
+int test_stack_errors(struct context *ctx);
+int test_tag_filter(void);
+int test_nua_params(struct context *ctx);
+
+int test_register(struct context *ctx);
+int test_connectivity(struct context *ctx);
+int test_nat_timeout(struct context *ctx);
+int test_unregister(struct context *ctx);
+
+int test_basic_call(struct context *ctx);
+int test_offer_answer(struct context *ctx);
+int test_rejects(struct context *ctx);
+int test_mime_negotiation(struct context *ctx);
+int test_call_timeouts(struct context *ctx);
+int test_reject_401_aka(struct context *ctx);
+int test_call_cancel(struct context *ctx);
+int test_call_destroy(struct context *ctx);
+int test_early_bye(struct context *ctx);
+int test_call_hold(struct context *ctx);
+int test_reinvites(struct context *ctx);
+int test_session_timer(struct context *ctx);
+int test_refer(struct context *ctx);
+int test_100rel(struct context *ctx);
+int test_simple(struct context *ctx);
+int test_events(struct context *ctx);
+
+int test_extension(struct context *ctx);
+
+#endif

Added: freeswitch/trunk/libs/sofia-sip/tests/test_nua_api.c
==============================================================================
--- (empty file)
+++ freeswitch/trunk/libs/sofia-sip/tests/test_nua_api.c	Wed May 14 15:10:54 2008
@@ -0,0 +1,460 @@
+/*
+ * This file is part of the Sofia-SIP package
+ *
+ * Copyright (C) 2005 Nokia Corporation.
+ *
+ * Contact: Pekka Pessi <pekka.pessi at nokia.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA
+ *
+ */
+
+/**@CFILE test_nua_api.c
+ * @brief NUA API tester.
+ *
+ * @author Pekka Pessi <Pekka.Pessi at nokia.com>
+ * @author Martti Mela <Martti Mela at nokia.com>
+ *
+ * @date Created: Wed Aug 17 12:12:12 EEST 2005 ppessi
+ */
+
+#include "config.h"
+
+#include "test_nua.h"
+#include "sofia-sip/tport_tag.h"
+
+#if HAVE_FUNC
+#elif HAVE_FUNCTION
+#define __func__ __FUNCTION__
+#else
+#define __func__ "test_nua_api_errors"
+#endif
+
+/* ------------------------------------------------------------------------ */
+/* API tests */
+
+SOFIAPUBVAR su_log_t nua_log[];
+
+int check_set_status(int status, char const *phrase)
+{
+  return status == 200 && strcmp(phrase, sip_200_OK) == 0;
+}
+
+int test_nua_api_errors(struct context *ctx)
+{
+  BEGIN();
+
+  /* Invoke every API function with invalid arguments */
+
+  int level;
+
+  int status; char const *phrase;
+
+  if (print_headings)
+    printf("TEST NUA-1.0: test API\n");
+
+  /* This is a nasty macro. Test it. */
+#define SET_STATUS1(x) ((status = x), status), (phrase = ((void)x))
+  TEST_1(check_set_status(SET_STATUS1(SIP_200_OK)));
+  TEST(status, 200); TEST_S(phrase, sip_200_OK);
+
+  su_log_init(nua_log);
+  level = nua_log->log_level;
+  if (!(tstflags & tst_verbatim))
+    su_log_set_level(nua_log, 0);  /* Log at level 0 by default */
+
+  TEST_1(!nua_create(NULL, NULL, NULL, TAG_END()));
+  TEST_VOID(nua_shutdown(NULL));
+  TEST_VOID(nua_destroy(NULL));
+  TEST_VOID(nua_set_params(NULL, TAG_END()));
+  TEST_VOID(nua_get_params(NULL, TAG_END()));
+  TEST_1(!nua_default(NULL));
+  TEST_1(!nua_handle(NULL, NULL, TAG_END()));
+  TEST_VOID(nua_handle_destroy(NULL));
+  TEST_VOID(nua_handle_bind(NULL, NULL));
+  TEST_1(!nua_handle_has_invite(NULL));
+  TEST_1(!nua_handle_has_subscribe(NULL));
+  TEST_1(!nua_handle_has_register(NULL));
+  TEST_1(!nua_handle_has_active_call(NULL));
+  TEST_1(!nua_handle_has_call_on_hold(NULL));
+  TEST_1(!nua_handle_has_events(NULL));
+  TEST_1(!nua_handle_has_registrations(NULL));
+  TEST_1(!nua_handle_remote(NULL));
+  TEST_1(!nua_handle_local(NULL));
+  TEST_S(nua_event_name(-1), "NUA_UNKNOWN");
+  TEST_VOID(nua_register(NULL, TAG_END()));
+  TEST_VOID(nua_unregister(NULL, TAG_END()));
+  TEST_VOID(nua_invite(NULL, TAG_END()));
+  TEST_VOID(nua_ack(NULL, TAG_END()));
+  TEST_VOID(nua_prack(NULL, TAG_END()));
+  TEST_VOID(nua_options(NULL, TAG_END()));
+  TEST_VOID(nua_publish(NULL, TAG_END()));
+  TEST_VOID(nua_message(NULL, TAG_END()));
+  TEST_VOID(nua_chat(NULL, TAG_END()));
+  TEST_VOID(nua_info(NULL, TAG_END()));
+  TEST_VOID(nua_subscribe(NULL, TAG_END()));
+  TEST_VOID(nua_unsubscribe(NULL, TAG_END()));
+  TEST_VOID(nua_notify(NULL, TAG_END()));
+  TEST_VOID(nua_notifier(NULL, TAG_END()));
+  TEST_VOID(nua_terminate(NULL, TAG_END()));
+  TEST_VOID(nua_refer(NULL, TAG_END()));
+  TEST_VOID(nua_update(NULL, TAG_END()));
+  TEST_VOID(nua_bye(NULL, TAG_END()));
+  TEST_VOID(nua_cancel(NULL, TAG_END()));
+  TEST_VOID(nua_authenticate(NULL, TAG_END()));
+  TEST_VOID(nua_redirect(NULL, TAG_END()));
+  TEST_VOID(nua_respond(NULL, 0, "", TAG_END()));
+
+  TEST_1(!nua_handle_home(NULL));
+  TEST_1(!nua_save_event(NULL, NULL));
+  TEST_1(!nua_event_data(NULL));
+  TEST_VOID(nua_destroy_event(NULL));
+
+  {
+    nua_saved_event_t event[1];
+
+    memset(event, 0, sizeof event);
+
+    TEST_1(!nua_save_event(NULL, event));
+    TEST_1(!nua_event_data(event));
+    TEST_VOID(nua_destroy_event(event));
+  }
+
+  su_log_set_level(nua_log, level);
+
+  if (print_headings)
+    printf("TEST NUA-1.0: PASSED\n");
+
+  END();
+}
+
+/* ======================================================================== */
+
+void destroy_callback(nua_event_t event,
+		      int status, char const *phrase,
+		      nua_t *nua, struct context *ctx,
+		      nua_handle_t *nh, struct call *call,
+		      sip_t const *sip,
+		      tagi_t tags[])
+{
+  if (status >= 200) {
+    nua_destroy(ctx->a.nua), ctx->a.nua = NULL;
+    su_root_break(ctx->root);
+  }
+}
+
+/* Test different nua_destroy() corner cases */
+int test_nua_destroy(struct context *ctx)
+{
+  BEGIN();
+  
+  struct endpoint *a = &ctx->a;
+
+  TEST_1(ctx->root = su_root_create(NULL));
+
+#if 0
+  a->nua = nua_create(ctx->root, destroy_callback, ctx,
+		      NUTAG_URL("sip:0.0.0.0:*"),
+		      TAG_IF(ctx->a.logging, TPTAG_LOG(1)),
+		      TAG_END());
+  TEST_1(a->nua);
+
+  nua_get_params(a->nua, TAG_ANY(), TAG_END());
+
+  su_root_run(ctx->root);
+
+  TEST_1(a->nua == NULL);
+#endif
+
+  a->nua = nua_create(ctx->root, destroy_callback, ctx,
+		      NUTAG_URL("sip:0.0.0.0:*"),
+		      TAG_IF(ctx->a.logging, TPTAG_LOG(1)),
+		      TAG_END());
+  TEST_1(a->nua);
+
+  nua_shutdown(a->nua);
+
+  su_root_run(ctx->root);
+
+  TEST_1(a->nua == NULL);
+
+  su_root_destroy(ctx->root), ctx->root = NULL;
+
+  END();
+}
+
+/* ======================================================================== */
+
+int test_byecancel_without_invite(struct context *ctx)
+{
+  BEGIN();
+
+  struct endpoint *a = &ctx->a, *b = &ctx->b;
+  struct call *a_call = a->call;
+  struct event *e;
+
+  int internal_error = 900;
+
+  if (print_headings)
+    printf("TEST NUA-1.2.1: CANCEL without INVITE\n");
+
+  TEST_1(a_call->nh = nua_handle(a->nua, a_call, SIPTAG_TO(b->to), TAG_END()));
+
+  CANCEL(a, a_call, a_call->nh, TAG_END());
+
+  run_a_until(ctx, -1, save_until_final_response);
+
+  TEST_1(e = a->events->head); TEST_E(e->data->e_event, nua_r_cancel);
+  TEST(e->data->e_status, 481);
+  TEST_1(!e->next);
+
+  free_events_in_list(ctx, a->events);
+  nua_handle_destroy(a_call->nh), a_call->nh = NULL;
+
+  if (print_headings)
+    printf("TEST NUA-1.2.1: PASSED\n");
+
+  /* -BYE without INVITE--------------------------------------------------- */
+
+  if (print_headings)
+    printf("TEST NUA-1.2.2: BYE without INVITE\n");
+
+  a_call->nh = nua_handle(a->nua, a_call, SIPTAG_TO(b->to), TAG_END());
+  TEST_1(a_call->nh);
+
+  BYE(a, a_call, a_call->nh, TAG_END());
+
+  run_a_until(ctx, -1, save_until_final_response);
+
+  TEST_1(e = a->events->head); TEST_E(e->data->e_event, nua_r_bye);
+  TEST(e->data->e_status, internal_error);
+  TEST_1(!e->next);
+
+  free_events_in_list(ctx, a->events);
+  nua_handle_destroy(a_call->nh), a_call->nh = NULL;
+
+  if (print_headings)
+    printf("TEST NUA-1.2.2: PASSED\n");
+
+  END();
+}
+
+
+int test_unregister_without_register(struct context *ctx)
+{
+  BEGIN();
+
+  struct endpoint *a = &ctx->a, *b = &ctx->b;
+  struct call *a_call = a->call;
+  struct event *e;
+
+  /* -Un-register without REGISTER--------------------------------------- */
+
+  if (print_headings)
+    printf("TEST NUA-1.2.3: unregister without register\n");
+
+  a_call->nh = nua_handle(a->nua, a_call, SIPTAG_TO(a->to), TAG_END());
+  TEST_1(a_call->nh);
+
+  UNREGISTER(a, a_call, a_call->nh, TAG_END());
+
+  run_a_until(ctx, -1, save_until_final_response);
+
+  TEST_1(e = a->events->head); TEST_E(e->data->e_event, nua_r_unregister);
+  if (e->data->e_status == 401)
+    TEST(e->data->e_status, 401);
+  else
+    TEST(e->data->e_status, 406);
+  TEST_1(!e->next);
+
+  free_events_in_list(ctx, a->events);
+  nua_handle_destroy(a_call->nh), a_call->nh = NULL;
+
+  if (print_headings)
+    printf("TEST NUA-1.2.3: PASSED\n");
+
+  /* -Un-publish without publish--------------------------------------- */
+
+  if (print_headings)
+    printf("TEST NUA-1.2.4: unpublish without publish\n");
+
+  a_call->nh = nua_handle(a->nua, a_call, SIPTAG_TO(b->to), TAG_END());
+  TEST_1(a_call->nh);
+
+  UNPUBLISH(a, a_call, a_call->nh, TAG_END());
+
+  run_a_until(ctx, -1, save_until_final_response);
+
+  TEST_1(e = a->events->head); TEST_E(e->data->e_event, nua_r_unpublish);
+  TEST(e->data->e_status, 404);
+  TEST_1(!e->next);
+
+  free_events_in_list(ctx, a->events);
+  nua_handle_destroy(a_call->nh), a_call->nh = NULL;
+
+  if (print_headings)
+    printf("TEST NUA-1.2.4: PASSED\n");
+  
+  END();
+}
+
+/* -terminate without notifier--------------------------------------- */
+
+int test_terminate_without_notifier(struct context *ctx)
+{
+  BEGIN();
+
+  struct endpoint *a = &ctx->a, *b = &ctx->b;
+  struct call *a_call = a->call;
+  struct event *e;
+
+  int internal_error = 900;
+
+  if (print_headings)
+    printf("TEST NUA-1.2.5: terminate without notifier\n");
+
+  TEST_1(a_call->nh = nua_handle(a->nua, a_call, SIPTAG_TO(b->to), TAG_END()));
+
+  TERMINATE(a, a_call, a_call->nh, TAG_END());
+
+  run_a_until(ctx, -1, save_until_final_response);
+
+  TEST_1(e = a->events->head); TEST_E(e->data->e_event, nua_r_terminate);
+  TEST(e->data->e_status, internal_error);
+  TEST_1(!e->next);
+
+  free_events_in_list(ctx, a->events);
+
+  AUTHORIZE(a, a_call, a_call->nh, TAG_END());
+
+  run_a_until(ctx, -1, save_until_final_response);
+
+  TEST_1(e = a->events->head); TEST_E(e->data->e_event, nua_r_authorize);
+  TEST(e->data->e_status, internal_error);
+  TEST_1(!e->next);
+
+  free_events_in_list(ctx, a->events);
+
+  nua_handle_destroy(a_call->nh), a_call->nh = NULL;
+
+  if (print_headings)
+    printf("TEST NUA-1.2.5: PASSED\n");
+
+  END();
+}
+
+int destroy_on_503(CONDITION_PARAMS)
+{
+  save_event_in_list(ctx, event, ep, call);
+
+  if (status == 503) {
+    assert(nh == call->nh);
+    nua_handle_destroy(call->nh), call->nh = NULL;
+  }
+
+  return
+    nua_r_set_params <= event && event < nua_i_network_changed
+    && status >= 200;
+}
+
+
+int test_register_503(struct context *ctx)
+{
+  BEGIN();
+
+  struct endpoint *a = &ctx->a;
+  struct call *a_reg = a->reg;
+  struct event *e;
+
+/* REGISTER test
+
+   A
+   |------REGISTER--\
+   |<-------503-----/
+   |			
+
+*/
+
+  if (print_headings)
+    printf("TEST NUA-1.2.6: REGISTER with bad domain\n");
+
+  TEST_1(a_reg->nh = nua_handle(a->nua, a_reg, TAG_END()));
+
+  REGISTER(a, a_reg, a_reg->nh, 
+	   NUTAG_REGISTRAR(URL_STRING_MAKE("sip:bad.domain")),
+	   SIPTAG_TO_STR("sip:lissu at bad.domain"),
+	   TAG_END());
+  run_a_until(ctx, -1, destroy_on_503);
+
+  TEST_1(e = a->events->head);
+  TEST_E(e->data->e_event, nua_r_register);
+  TEST(e->data->e_status, 503);
+  TEST_1(!e->next);
+  free_events_in_list(ctx, a->events);
+
+  TEST_1(a_reg->nh = nua_handle(a->nua, a_reg, TAG_END()));
+
+  REGISTER(a, a_reg, a_reg->nh, 
+	   NUTAG_REGISTRAR(URL_STRING_MAKE("sip:bad.domain")),
+	   SIPTAG_TO_STR("sip:lissu at bad.domain"),
+	   TAG_END());
+  nua_handle_destroy(a_reg->nh), a_reg->nh = NULL;
+
+  if (print_headings)
+    printf("TEST NUA-1.2.6: PASSED\n");
+
+  END();
+}
+
+int test_stack_errors(struct context *ctx)
+{
+  BEGIN();
+
+  struct endpoint *a = &ctx->a;
+
+  if (print_headings)
+    printf("TEST NUA-1.2: Stack error handling\n");
+
+  TEST_1(ctx->root == NULL);
+  TEST_1(ctx->root = su_root_create(NULL));
+
+  a->nua = nua_create(ctx->root, a_callback, ctx,
+		      NUTAG_URL("sip:0.0.0.0:*"),
+		      TAG_IF(ctx->a.logging, TPTAG_LOG(1)),
+		      TAG_END());
+  TEST_1(a->nua);
+
+  TEST(test_byecancel_without_invite(ctx), 0);
+  if (ctx->proxy_tests)
+    TEST(test_unregister_without_register(ctx), 0);
+  TEST(test_terminate_without_notifier(ctx), 0);
+  TEST(test_register_503(ctx), 0);
+  TEST(test_register_503(ctx), 0);
+
+  nua_shutdown(a->nua);
+
+  run_a_until(ctx, -1, until_final_response);
+  
+  TEST_VOID(nua_destroy(a->nua));
+
+  su_root_destroy(ctx->root), ctx->root = NULL;
+	 
+  if (print_headings)
+    printf("TEST NUA-1.2: PASSED\n");
+
+  END();
+}
+

Added: freeswitch/trunk/libs/sofia-sip/tests/test_nua_params.c
==============================================================================
--- (empty file)
+++ freeswitch/trunk/libs/sofia-sip/tests/test_nua_params.c	Wed May 14 15:10:54 2008
@@ -0,0 +1,656 @@
+/*
+ * This file is part of the Sofia-SIP package
+ *
+ * Copyright (C) 2005 Nokia Corporation.
+ *
+ * Contact: Pekka Pessi <pekka.pessi at nokia.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA
+ *
+ */
+
+/**@CFILE test_nua_params.c
+ * @brief Test NUA parameter handling.
+ *
+ * @author Pekka Pessi <Pekka.Pessi at nokia.com>
+ * @author Martti Mela <Martti Mela at nokia.com>
+ *
+ * @date Created: Wed Aug 17 12:12:12 EEST 2005 ppessi
+ */
+
+#include "config.h"
+
+#include "test_nua.h"
+#include <sofia-sip/su_tag_class.h>
+
+#if HAVE_FUNC
+#elif HAVE_FUNCTION
+#define __func__ __FUNCTION__
+#else
+#define __func__ "test_nua_params"
+#endif
+
+sip_route_t *GLOBAL_ROUTE;
+
+int test_tag_filter(void)
+{
+  BEGIN();
+
+#undef TAG_NAMESPACE
+#define TAG_NAMESPACE "test"
+  tag_typedef_t tag_a = STRTAG_TYPEDEF(a);
+#define TAG_A(s)      tag_a, tag_str_v((s))
+  tag_typedef_t tag_b = STRTAG_TYPEDEF(b);
+#define TAG_B(s)      tag_b, tag_str_v((s))
+
+  tagi_t filter[2] = {{ NUTAG_ANY() }, { TAG_END() }};
+
+  tagi_t *lst, *result;
+
+  lst = tl_list(TAG_A("X"),
+		TAG_SKIP(2),
+		NUTAG_URL((void *)"urn:foo"),
+		TAG_B("Y"),
+		NUTAG_URL((void *)"urn:bar"),
+		TAG_NULL());
+
+  TEST_1(lst);
+
+  result = tl_afilter(NULL, filter, lst);
+
+  TEST_1(result);
+  TEST_P(result[0].t_tag, nutag_url);
+  TEST_P(result[1].t_tag, nutag_url);
+
+  tl_vfree(lst);
+  free(result);
+
+  END();
+}
+
+int test_nua_params(struct context *ctx)
+{
+  BEGIN();
+
+  char const Alice[] = "Alice <sip:a at wonderland.org>";
+  sip_from_t const *from;
+  su_home_t tmphome[SU_HOME_AUTO_SIZE(16384)];
+  nua_handle_t *nh;
+  struct event *e;
+  tagi_t const *t;
+  int n;
+
+  su_home_auto(tmphome, sizeof(tmphome));
+
+  if (print_headings)
+    printf("TEST NUA-1.1: PARAMETERS\n");
+
+#if SU_HAVE_OSX_CF_API
+  if (ctx->osx_runloop)
+    ctx->root = su_root_osx_runloop_create(NULL);
+  else
+#endif
+  ctx->root = su_root_create(NULL);
+  TEST_1(ctx->root);
+
+  su_root_threading(ctx->root, ctx->threading);
+
+  ctx->a.nua = nua_create(ctx->root, a_callback, ctx,
+			  SIPTAG_FROM_STR("sip:alice at example.com"),
+			  NUTAG_URL("sip:0.0.0.0:*;transport=udp"),
+			  TAG_END());
+
+  TEST_1(ctx->a.nua);
+
+  nua_get_params(ctx->a.nua, TAG_ANY(), TAG_END());
+  run_a_until(ctx, nua_r_get_params, save_until_final_response);
+
+  TEST_1(e = ctx->a.specials->head);
+  TEST_E(e->data->e_event, nua_r_get_params);
+  for (n = 0, t = e->data->e_tags; t; n++, t = tl_next(t))
+    ;
+  TEST_1(n > 32);
+  free_events_in_list(ctx, ctx->a.specials);
+
+  nh = nua_handle(ctx->a.nua, NULL, TAG_END()); TEST_1(nh);
+  nua_handle_unref(nh);
+
+  nh = nua_handle(ctx->a.nua, NULL, TAG_END()); TEST_1(nh);
+  nua_handle_destroy(nh);
+
+  from = sip_from_make(tmphome, Alice);
+
+  nh = nua_handle(ctx->a.nua, NULL, TAG_END());
+
+  nua_set_hparams(nh, NUTAG_INVITE_TIMER(90), TAG_END());
+  run_a_until(ctx, nua_r_set_params, until_final_response);
+
+  /* Modify all pointer values */
+  nua_set_params(ctx->a.nua,
+		 SIPTAG_FROM_STR(Alice),
+
+		 NUTAG_MEDIA_ENABLE(0),
+		 NUTAG_SOA_NAME("test"),
+
+		 NUTAG_REGISTRAR("sip:openlaboratory.net"),
+
+		 SIPTAG_SUPPORTED_STR("test"),
+		 SIPTAG_ALLOW_STR("DWIM, OPTIONS, INFO"),
+		 NUTAG_APPL_METHOD(NULL),
+		 NUTAG_APPL_METHOD("INVITE, REGISTER, PUBLISH, SUBSCRIBE"),
+		 SIPTAG_ALLOW_EVENTS_STR("reg"),
+		 SIPTAG_USER_AGENT_STR("test_nua/1.0"),
+
+		 SIPTAG_ORGANIZATION_STR("Open Laboratory"),
+		 
+		 NUTAG_M_DISPLAY("XXX"),
+		 NUTAG_M_USERNAME("xxx"),
+		 NUTAG_M_PARAMS("user=ip"),
+		 NUTAG_M_FEATURES("language=\"fi\""),
+		 NUTAG_INSTANCE("urn:uuid:3eb007b1-6d7f-472e-8b64-29e482795da8"),
+		 NUTAG_OUTBOUND("bar"),
+
+		 NUTAG_INITIAL_ROUTE(NULL),
+		 NUTAG_INITIAL_ROUTE(sip_route_make(tmphome, "<sip:tst at example.net;lr>")),
+		 NUTAG_INITIAL_ROUTE_STR("<sip:str1 at example.net;lr>"),
+		 NUTAG_INITIAL_ROUTE_STR("sip:str2 at example.net;lr=foo"),
+		 NUTAG_INITIAL_ROUTE_STR(NULL),
+
+		 TAG_END());
+
+  run_a_until(ctx, nua_r_set_params, until_final_response);
+
+  /* Modify everything from their default value */
+  nua_set_params(ctx->a.nua,
+		 SIPTAG_FROM(from),
+		 NUTAG_RETRY_COUNT(9),
+		 NUTAG_MAX_SUBSCRIPTIONS(6),
+
+		 NUTAG_ENABLEINVITE(0),
+		 NUTAG_AUTOALERT(1),
+		 NUTAG_EARLY_MEDIA(1),
+		 NUTAG_AUTOANSWER(1),
+		 NUTAG_AUTOACK(0),
+		 NUTAG_INVITE_TIMER(60),
+
+		 NUTAG_SESSION_TIMER(600),
+		 NUTAG_MIN_SE(35),
+		 NUTAG_SESSION_REFRESHER(nua_remote_refresher),
+		 NUTAG_UPDATE_REFRESH(1),
+
+		 NUTAG_ENABLEMESSAGE(0),
+		 NUTAG_ENABLEMESSENGER(1),
+		 /* NUTAG_MESSAGE_AUTOANSWER(0), */
+
+		 NUTAG_CALLEE_CAPS(1),
+		 NUTAG_MEDIA_FEATURES(1),
+		 NUTAG_SERVICE_ROUTE_ENABLE(0),
+		 NUTAG_PATH_ENABLE(0),
+		 NUTAG_AUTH_CACHE(nua_auth_cache_challenged),
+		 NUTAG_REFER_EXPIRES(333),
+		 NUTAG_REFER_WITH_ID(0),
+		 NUTAG_SUBSTATE(nua_substate_pending),
+		 NUTAG_SUB_EXPIRES(3700),
+
+		 NUTAG_KEEPALIVE(66),
+		 NUTAG_KEEPALIVE_STREAM(33),
+
+		 NUTAG_INSTANCE("urn:uuid:97701ad9-39df-1229-1083-dbc0a85f029c"),
+		 NUTAG_M_DISPLAY("Joe"),
+		 NUTAG_M_USERNAME("joe"),
+		 NUTAG_M_PARAMS("user=phone"),
+		 NUTAG_M_FEATURES("language=\"en\""),
+		 NUTAG_OUTBOUND("foo"),
+		 SIPTAG_SUPPORTED(sip_supported_make(tmphome, "foo")),
+		 NUTAG_SUPPORTED("foo, bar"),
+		 SIPTAG_SUPPORTED_STR(",baz,"),
+
+		 SIPTAG_ALLOW_STR("OPTIONS"),
+		 SIPTAG_ALLOW(sip_allow_make(tmphome, "INFO")),
+		 NUTAG_ALLOW("ACK, INFO"),
+
+		 NUTAG_APPL_METHOD("NOTIFY"),
+
+		 SIPTAG_ALLOW_EVENTS_STR("reg"),
+		 SIPTAG_ALLOW_EVENTS(sip_allow_events_make(tmphome, "presence")),
+		 NUTAG_ALLOW_EVENTS("presence.winfo"),
+
+		 NUTAG_INITIAL_ROUTE(NULL),
+		 NUTAG_INITIAL_ROUTE(sip_route_make(nua_handle_home(nh), "<sip:1 at example.com;lr>")),
+		 NUTAG_INITIAL_ROUTE_STR("<sip:2 at example.com;lr>"),
+		 /* Check for sip_route_fix() */
+		 NUTAG_INITIAL_ROUTE_STR("sip:3 at example.com;lr=foo"),
+		 NUTAG_INITIAL_ROUTE_STR(NULL),
+
+		 SIPTAG_USER_AGENT(sip_user_agent_make(tmphome, "test_nua")),
+
+		 SIPTAG_ORGANIZATION(sip_organization_make(tmphome, "Pussy Galore's Flying Circus")),
+
+		 NUTAG_MEDIA_ENABLE(0),
+		 NUTAG_REGISTRAR(url_hdup(tmphome, (url_t *)"sip:sip.wonderland.org")),
+
+		 TAG_END());
+
+  run_a_until(ctx, nua_r_set_params, until_final_response);
+
+  /* Modify something... */
+  nua_set_params(ctx->a.nua,
+		 NUTAG_RETRY_COUNT(5),
+		 TAG_END());
+  run_a_until(ctx, nua_r_set_params, until_final_response);
+
+  {
+    sip_from_t const *from = NONE;
+    char const *from_str = "NONE";
+
+    unsigned retry_count = (unsigned)-1;
+    unsigned max_subscriptions = (unsigned)-1;
+
+    char const *soa_name = "NONE";
+    int media_enable = -1;
+    int invite_enable = -1;
+    int auto_alert = -1;
+    int early_media = -1;
+    int only183_100rel = -1;
+    int auto_answer = -1;
+    int auto_ack = -1;
+    unsigned invite_timeout = (unsigned)-1;
+
+    unsigned session_timer = (unsigned)-1;
+    unsigned min_se = (unsigned)-1;
+    int refresher = -1;
+    int update_refresh = -1;
+
+    int message_enable = -1;
+    int win_messenger_enable = -1;
+    int message_auto_respond = -1;
+
+    int callee_caps = -1;
+    int media_features = -1;
+    int service_route_enable = -1;
+    int path_enable = -1;
+    int auth_cache = -1;
+    unsigned refer_expires = (unsigned)-1;
+    int refer_with_id = -1;
+    unsigned sub_expires = (unsigned)-1;
+    int substate = -1;
+
+    sip_allow_t const *allow = NONE;
+    char const *allow_str = "NONE";
+    char const *appl_method = "NONE";
+    sip_allow_events_t const *allow_events = NONE;
+    char const *allow_events_str = "NONE";
+    sip_supported_t const *supported = NONE;
+    char const *supported_str = "NONE";
+    sip_user_agent_t const *user_agent = NONE;
+    char const *user_agent_str = "NONE";
+    char const *ua_name = "NONE";
+    sip_organization_t const *organization = NONE;
+    char const *organization_str = "NONE";
+
+    sip_route_t const *initial_route = NONE;
+    char const *initial_route_str = NONE;
+
+    char const *outbound = "NONE";
+    char const *m_display = "NONE";
+    char const *m_username = "NONE";
+    char const *m_params = "NONE";
+    char const *m_features = "NONE";
+    char const *instance = "NONE";
+    
+    url_string_t const *registrar = NONE;
+    unsigned keepalive = (unsigned)-1, keepalive_stream = (unsigned)-1;
+
+    nua_get_params(ctx->a.nua, TAG_ANY(), TAG_END());
+    run_a_until(ctx, nua_r_get_params, save_until_final_response);
+
+    TEST_1(e = ctx->a.specials->head);
+    TEST_E(e->data->e_event, nua_r_get_params);
+
+    n = tl_gets(e->data->e_tags,
+	       	SIPTAG_FROM_REF(from),
+	       	SIPTAG_FROM_STR_REF(from_str),
+
+	       	NUTAG_RETRY_COUNT_REF(retry_count),
+	       	NUTAG_MAX_SUBSCRIPTIONS_REF(max_subscriptions),
+
+		NUTAG_SOA_NAME_REF(soa_name),
+		NUTAG_MEDIA_ENABLE_REF(media_enable),
+	       	NUTAG_ENABLEINVITE_REF(invite_enable),
+	       	NUTAG_AUTOALERT_REF(auto_alert),
+	       	NUTAG_EARLY_MEDIA_REF(early_media),
+		NUTAG_ONLY183_100REL_REF(only183_100rel),
+	       	NUTAG_AUTOANSWER_REF(auto_answer),
+	       	NUTAG_AUTOACK_REF(auto_ack),
+	       	NUTAG_INVITE_TIMER_REF(invite_timeout),
+
+	       	NUTAG_SESSION_TIMER_REF(session_timer),
+	       	NUTAG_MIN_SE_REF(min_se),
+	       	NUTAG_SESSION_REFRESHER_REF(refresher),
+	       	NUTAG_UPDATE_REFRESH_REF(update_refresh),
+
+	       	NUTAG_ENABLEMESSAGE_REF(message_enable),
+	       	NUTAG_ENABLEMESSENGER_REF(win_messenger_enable),
+	       	/* NUTAG_MESSAGE_AUTOANSWER(message_auto_respond), */
+
+	       	NUTAG_CALLEE_CAPS_REF(callee_caps),
+	       	NUTAG_MEDIA_FEATURES_REF(media_features),
+	       	NUTAG_SERVICE_ROUTE_ENABLE_REF(service_route_enable),
+	       	NUTAG_PATH_ENABLE_REF(path_enable),
+	       	NUTAG_AUTH_CACHE_REF(auth_cache),
+	       	NUTAG_REFER_EXPIRES_REF(refer_expires),
+	       	NUTAG_REFER_WITH_ID_REF(refer_with_id),
+	       	NUTAG_SUBSTATE_REF(substate),
+		NUTAG_SUB_EXPIRES_REF(sub_expires),
+
+	       	SIPTAG_SUPPORTED_REF(supported),
+	       	SIPTAG_SUPPORTED_STR_REF(supported_str),
+	       	SIPTAG_ALLOW_REF(allow),
+	       	SIPTAG_ALLOW_STR_REF(allow_str),
+		NUTAG_APPL_METHOD_REF(appl_method),
+		SIPTAG_ALLOW_EVENTS_REF(allow_events),
+		SIPTAG_ALLOW_EVENTS_STR_REF(allow_events_str),
+	       	SIPTAG_USER_AGENT_REF(user_agent),
+	       	SIPTAG_USER_AGENT_STR_REF(user_agent_str),
+		NUTAG_USER_AGENT_REF(ua_name),
+
+	       	SIPTAG_ORGANIZATION_REF(organization),
+	       	SIPTAG_ORGANIZATION_STR_REF(organization_str),
+
+		NUTAG_INITIAL_ROUTE_REF(initial_route),
+		NUTAG_INITIAL_ROUTE_STR_REF(initial_route_str),
+
+	       	NUTAG_REGISTRAR_REF(registrar),
+		NUTAG_KEEPALIVE_REF(keepalive),
+		NUTAG_KEEPALIVE_STREAM_REF(keepalive_stream),
+
+		NUTAG_OUTBOUND_REF(outbound),
+		NUTAG_M_DISPLAY_REF(m_display),
+		NUTAG_M_USERNAME_REF(m_username),
+		NUTAG_M_PARAMS_REF(m_params),
+		NUTAG_M_FEATURES_REF(m_features),
+		NUTAG_INSTANCE_REF(instance),
+
+		TAG_END());
+    TEST(n, 51);
+
+    TEST_S(sip_header_as_string(tmphome, (void *)from), Alice);
+    TEST_S(from_str, Alice);
+
+    TEST(retry_count, 5);
+    TEST(max_subscriptions, 6);
+
+    TEST_S(soa_name, "test");
+    TEST(media_enable, 0);
+    TEST(invite_enable, 0);
+    TEST(auto_alert, 1);
+    TEST(early_media, 1);
+    TEST(auto_answer, 1);
+    TEST(auto_ack, 0);
+    TEST(invite_timeout, 60);
+
+    TEST(session_timer, 600);
+    TEST(min_se, 35);
+    TEST(refresher, nua_remote_refresher);
+    TEST(update_refresh, 1);
+
+    TEST(message_enable, 0);
+    TEST(win_messenger_enable, 1);
+    TEST(message_auto_respond, -1); /* XXX */
+
+    TEST(callee_caps, 1);
+    TEST(media_features, 1);
+    TEST(service_route_enable, 0);
+    TEST(path_enable, 0);
+    TEST(auth_cache, nua_auth_cache_challenged);
+    TEST(refer_expires, 333);
+    TEST(refer_with_id, 0);
+    TEST(substate, nua_substate_pending);
+    TEST(sub_expires, 3700);
+
+    TEST_S(sip_header_as_string(tmphome, (void *)allow), "OPTIONS, INFO, ACK");
+    TEST_S(allow_str, "OPTIONS, INFO, ACK");
+    TEST_S(appl_method, "INVITE, REGISTER, PUBLISH, SUBSCRIBE, NOTIFY");
+    TEST_S(sip_header_as_string(tmphome, (void *)allow_events), 
+	   "reg, presence, presence.winfo");
+    TEST_S(allow_events_str, "reg, presence, presence.winfo");
+    TEST_S(sip_header_as_string(tmphome, (void *)supported), 
+	   "foo, bar, baz");
+    TEST_S(supported_str, "foo, bar, baz");
+    TEST_S(sip_header_as_string(tmphome, (void *)user_agent), "test_nua");
+    TEST_S(user_agent_str, "test_nua");
+    TEST_S(sip_header_as_string(tmphome, (void *)organization),
+	   "Pussy Galore's Flying Circus");
+    TEST_S(organization_str, "Pussy Galore's Flying Circus");
+
+    TEST_1(initial_route); TEST_1(initial_route != (void *)-1);
+    TEST_S(initial_route->r_url->url_user, "1");
+    TEST_1(url_has_param(initial_route->r_url, "lr"));
+    TEST_1(initial_route->r_next);
+    TEST_S(initial_route->r_next->r_url->url_user, "2");
+    TEST_1(url_has_param(initial_route->r_next->r_url, "lr"));
+    TEST_1(initial_route->r_next->r_next);
+    TEST_S(initial_route->r_next->r_next->r_url->url_user, "3");
+    TEST_1(url_has_param(initial_route->r_next->r_next->r_url, "lr"));
+    TEST_1(!initial_route->r_next->r_next->r_next);
+
+    TEST_S(url_as_string(tmphome, registrar->us_url),
+	   "sip:sip.wonderland.org");
+    TEST(keepalive, 66);
+    TEST(keepalive_stream, 33);
+
+    TEST_S(instance, "urn:uuid:97701ad9-39df-1229-1083-dbc0a85f029c");
+    TEST_S(m_display, "Joe");
+    TEST_S(m_username, "joe");
+    TEST_S(m_params, "user=phone");
+    { char const *expect_m_features = "language=\"en\"";
+    TEST_S(m_features, expect_m_features); }
+    TEST_S(outbound, "foo");
+
+    free_events_in_list(ctx, ctx->a.specials);
+  }
+
+  /* Test that only those tags that have been set per handle are returned by nua_get_hparams() */
+
+  {
+    sip_from_t const *from = NONE;
+    char const *from_str = "NONE";
+
+    unsigned retry_count = (unsigned)-1;
+    unsigned max_subscriptions = (unsigned)-1;
+
+    int invite_enable = -1;
+    int auto_alert = -1;
+    int early_media = -1;
+    int auto_answer = -1;
+    int auto_ack = -1;
+    unsigned invite_timeout = (unsigned)-1;
+
+    unsigned session_timer = (unsigned)-1;
+    unsigned min_se = (unsigned)-1;
+    int refresher = -1;
+    int update_refresh = -1;
+
+    int message_enable = -1;
+    int win_messenger_enable = -1;
+    int message_auto_respond = -1;
+
+    int callee_caps = -1;
+    int media_features = -1;
+    int service_route_enable = -1;
+    int path_enable = -1;
+    int auth_cache = -1;
+    unsigned refer_expires = (unsigned)-1;
+    int refer_with_id = -1;
+    int substate = -1;
+    unsigned sub_expires = (unsigned)-1;
+
+    sip_allow_t const *allow = NONE;
+    char const   *allow_str = "NONE";
+    sip_supported_t const *supported = NONE;
+    char const *supported_str = "NONE";
+    sip_user_agent_t const *user_agent = NONE;
+    char const *user_agent_str = "NONE";
+    sip_organization_t const *organization = NONE;
+    char const *organization_str = "NONE";
+
+    sip_route_t *initial_route = NONE;
+    char const *initial_route_str = "NONE";
+
+    url_string_t const *registrar = NONE;
+
+    char const *outbound = "NONE";
+    char const *m_display = "NONE";
+    char const *m_username = "NONE";
+    char const *m_params = "NONE";
+    char const *m_features = "NONE";
+    char const *instance = "NONE";
+
+    int n;
+    struct event *e;
+
+    nua_get_hparams(nh, TAG_ANY(), TAG_END());
+    run_a_until(ctx, nua_r_get_params, save_until_final_response);
+
+    TEST_1(e = ctx->a.events->head);
+    TEST_E(e->data->e_event, nua_r_get_params);
+
+    n = tl_gets(e->data->e_tags,
+	       	SIPTAG_FROM_REF(from),
+	       	SIPTAG_FROM_STR_REF(from_str),
+
+	       	NUTAG_RETRY_COUNT_REF(retry_count),
+	       	NUTAG_MAX_SUBSCRIPTIONS_REF(max_subscriptions),
+
+	       	NUTAG_ENABLEINVITE_REF(invite_enable),
+	       	NUTAG_AUTOALERT_REF(auto_alert),
+	       	NUTAG_EARLY_MEDIA_REF(early_media),
+	       	NUTAG_AUTOANSWER_REF(auto_answer),
+	       	NUTAG_AUTOACK_REF(auto_ack),
+	       	NUTAG_INVITE_TIMER_REF(invite_timeout),
+
+	       	NUTAG_SESSION_TIMER_REF(session_timer),
+	       	NUTAG_MIN_SE_REF(min_se),
+	       	NUTAG_SESSION_REFRESHER_REF(refresher),
+	       	NUTAG_UPDATE_REFRESH_REF(update_refresh),
+
+	       	NUTAG_ENABLEMESSAGE_REF(message_enable),
+	       	NUTAG_ENABLEMESSENGER_REF(win_messenger_enable),
+	       	/* NUTAG_MESSAGE_AUTOANSWER(message_auto_respond), */
+
+	       	NUTAG_CALLEE_CAPS_REF(callee_caps),
+	       	NUTAG_MEDIA_FEATURES_REF(media_features),
+	       	NUTAG_SERVICE_ROUTE_ENABLE_REF(service_route_enable),
+	       	NUTAG_PATH_ENABLE_REF(path_enable),
+		NUTAG_AUTH_CACHE_REF(auth_cache),
+	       	NUTAG_SUBSTATE_REF(substate),
+	       	NUTAG_SUB_EXPIRES_REF(sub_expires),
+
+	       	SIPTAG_SUPPORTED_REF(supported),
+	       	SIPTAG_SUPPORTED_STR_REF(supported_str),
+	       	SIPTAG_ALLOW_REF(allow),
+	       	SIPTAG_ALLOW_STR_REF(allow_str),
+	       	SIPTAG_USER_AGENT_REF(user_agent),
+	       	SIPTAG_USER_AGENT_STR_REF(user_agent_str),
+
+	       	SIPTAG_ORGANIZATION_REF(organization),
+	       	SIPTAG_ORGANIZATION_STR_REF(organization_str),
+
+		NUTAG_OUTBOUND_REF(outbound),
+		NUTAG_M_DISPLAY_REF(m_display),
+		NUTAG_M_USERNAME_REF(m_username),
+		NUTAG_M_PARAMS_REF(m_params),
+		NUTAG_M_FEATURES_REF(m_features),
+		NUTAG_INSTANCE_REF(instance),
+
+	       	NUTAG_REGISTRAR_REF(registrar),
+
+		TAG_END());
+    TEST(n, 3);
+
+    TEST(invite_timeout, 90);
+
+    TEST_1(from != NULL && from != NONE);
+    TEST_1(strcmp(from_str, "NONE"));
+
+    /* Nothing else should be set */
+    TEST(retry_count, (unsigned)-1);
+    TEST(max_subscriptions, (unsigned)-1);
+
+    TEST(invite_enable, -1);
+    TEST(auto_alert, -1);
+    TEST(early_media, -1);
+    TEST(auto_answer, -1);
+    TEST(auto_ack, -1);
+
+    TEST(session_timer, (unsigned)-1);
+    TEST(min_se, (unsigned)-1);
+    TEST(refresher, -1);
+    TEST(update_refresh, -1);
+
+    TEST(message_enable, -1);
+    TEST(win_messenger_enable, -1);
+    TEST(message_auto_respond, -1); /* XXX */
+
+    TEST(callee_caps, -1);
+    TEST(media_features, -1);
+    TEST(service_route_enable, -1);
+    TEST(path_enable, -1);
+    TEST(auth_cache, -1);
+    TEST(refer_expires, (unsigned)-1);
+    TEST(refer_with_id, -1);
+    TEST(substate, -1);
+    TEST(sub_expires, -1);
+
+    TEST_P(allow, NONE);
+    TEST_S(allow_str, "NONE");
+    TEST_P(supported, NONE);
+    TEST_S(supported_str, "NONE");
+    TEST_P(user_agent, NONE);
+    TEST_S(user_agent_str, "NONE");
+    TEST_P(organization, NONE);
+    TEST_S(organization_str, "NONE");
+
+    TEST_1(initial_route == (void *)-1);
+    TEST_S(initial_route_str, "NONE");
+
+    TEST_S(outbound, "NONE");
+    TEST_S(m_display, "NONE");
+    TEST_S(m_username, "NONE");
+    TEST_S(m_params, "NONE");
+    TEST_S(m_features, "NONE");
+    TEST_S(instance, "NONE");
+
+    TEST_P(registrar->us_url, NONE);
+
+    free_events_in_list(ctx, ctx->a.events);
+  }
+
+  nua_handle_destroy(nh);
+
+  nua_shutdown(ctx->a.nua);
+  run_a_until(ctx, nua_r_shutdown, until_final_response);
+  nua_destroy(ctx->a.nua), ctx->a.nua = NULL;
+
+  su_root_destroy(ctx->root), ctx->root = NULL;
+
+  su_home_deinit(tmphome);
+
+  if (print_headings)
+    printf("TEST NUA-1.1: PASSED\n");
+
+  END();
+}

Added: freeswitch/trunk/libs/sofia-sip/tests/test_offer_answer.c
==============================================================================
--- (empty file)
+++ freeswitch/trunk/libs/sofia-sip/tests/test_offer_answer.c	Wed May 14 15:10:54 2008
@@ -0,0 +1,479 @@
+/*
+ * This file is part of the Sofia-SIP package
+ *
+ * Copyright (C) 2005 Nokia Corporation.
+ *
+ * Contact: Pekka Pessi <pekka.pessi at nokia.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA
+ *
+ */
+
+/**@CFILE test_offer_answer.c
+ * @brief Test offer/answer failures.
+ *
+ * @author Pekka Pessi <Pekka.Pessi at nokia.com>
+ *
+ * @date Created: Wed Aug 17 12:12:12 EEST 2005 ppessi
+ */
+
+#include "config.h"
+
+#include "test_nua.h"
+#include <sofia-sip/su_tag_class.h>
+
+#if HAVE_FUNC
+#elif HAVE_FUNCTION
+#define __func__ __FUNCTION__
+#else
+#define __func__ "test_offer_answer"
+#endif
+
+
+/* ======================================================================== */
+
+/* Send offer in INVITE, no answer:
+
+			   A			B
+          --nua_invite()-->|                    |
+                           |-------INVITE------>|
+          <--nua_i_state---|                    |
+                           |<----100 Trying-----|
+                           |                    |
+                           |<--------180--------|
+          <--nua_r_invite--|                    |
+          <--nua_i_state---|                    |
+                           |<------200 OK-------|
+          <--nua_r_invite--|                    |
+                           |--------ACK-------->|
+                           |--------BYE-------->|
+          <--nua_i_state---|                    |
+                           |<-------200 OK------|
+          <--nua_r_bye-----|                    |
+          <--nua_i_state---|                    |
+
+   Client transitions:
+   INIT -(C1)-> CALLING -(C2a)-> PROCEEDING -(C3+C4)-> 
+   TERMINATING -> TERMINATED
+   Server transitions:
+   INIT -(S1)-> RECEIVED -(S2a)-> EARLY -(S3b)-> COMPLETED -(S4)-> READY
+   -> TERMINATED
+
+*/
+
+int no_media_terminated(CONDITION_PARAMS);
+
+int test_no_answer_1(struct context *ctx)
+{
+  BEGIN();
+
+  struct endpoint *a = &ctx->a,  *b = &ctx->b;
+  struct call *a_call = a->call, *b_call = b->call;
+  struct event *e;
+  sip_t *sip;
+
+  if (print_headings)
+    printf("TEST NUA-6.5: No SDP answer from callee\n");
+
+  a_call->sdp = "m=audio 5008 RTP/AVP 8";
+  b_call->sdp = NULL;
+
+  TEST_1(a_call->nh = nua_handle(a->nua, a_call, SIPTAG_TO(b->to), TAG_END()));
+
+  TEST_1(!nua_handle_has_active_call(a_call->nh));
+  TEST_1(!nua_handle_has_call_on_hold(a_call->nh));
+
+  INVITE(a, a_call, a_call->nh,
+	 TAG_IF(!ctx->proxy_tests, NUTAG_URL(b->contact->m_url)),
+	 SOATAG_USER_SDP_STR(a_call->sdp),
+	 NUTAG_M_USERNAME("a+a"),
+	 NUTAG_M_DISPLAY("Alice"),
+	 TAG_END());
+
+  run_ab_until(ctx, -1, until_terminated, -1, no_media_terminated);
+
+  TEST_1(!nua_handle_has_active_call(a_call->nh));
+  TEST_1(!nua_handle_has_call_on_hold(a_call->nh));
+
+  TEST_1(!nua_handle_has_active_call(b_call->nh));
+  TEST_1(!nua_handle_has_call_on_hold(b_call->nh));
+
+  /* Client transitions:
+     INIT -(C1)-> CALLING: nua_invite(), nua_i_state
+     CALLING -(C3)-> PROCEEDING: nua_r_invite, nua_i_state 
+     PROCEEDING -(C3a+C5)-> TERMINATING: nua_r_invite, nua_i_state 
+     TERMINATING -(C12)-> TERMINATED: nua_r_bye, nua_i_state
+  */
+  TEST_1(e = a->events->head); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_calling); /* CALLING */
+  TEST_1(is_offer_sent(e->data->e_tags));
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_r_invite);
+  TEST(e->data->e_status, 180);
+  TEST_1(sip = sip_object(e->data->e_msg));
+  TEST_1(!sip->sip_payload);
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_proceeding); /* PROCEEDING */
+  TEST_1(!is_answer_recv(e->data->e_tags));
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_r_invite);
+  TEST(e->data->e_status, 200);
+  TEST_1(sip = sip_object(e->data->e_msg));
+  TEST_1(!sip->sip_payload);
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_media_error);
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_terminating); /* TERMINATING */
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_r_bye);
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_terminated); /* TERMINATED */
+  TEST_1(!e->next);
+  free_events_in_list(ctx, a->events);
+
+  /*
+   Server transitions:
+   INIT -(S1)-> RECEIVED: nua_i_invite, nua_i_state
+   RECEIVED -(S2a)-> EARLY: nua_respond(), nua_i_state
+   EARLY -(S3b)-> COMPLETED: nua_respond(), nua_i_state
+   COMPLETED -(S4)-> READY: nua_i_ack, nua_i_state
+   READY -(T1)-> TERMINATED: nua_i_bye, nua_i_state
+  */
+  TEST_1(e = b->events->head); TEST_E(e->data->e_event, nua_i_invite);
+  TEST(e->data->e_status, 100);
+  TEST_1(sip = sip_object(e->data->e_msg));
+  TEST_1(sip->sip_contact);
+  TEST_S(sip->sip_contact->m_display, "Alice");
+  TEST_S(sip->sip_contact->m_url->url_user, "a+a");
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_received); /* RECEIVED */
+  TEST_1(is_offer_recv(e->data->e_tags));
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_early); /* EARLY */
+  TEST_1(!is_answer_sent(e->data->e_tags));
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_completed); /* COMPLETED */
+  TEST_1(!is_answer_sent(e->data->e_tags));
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_ack);
+  TEST_1(sip = sip_object(e->data->e_msg));
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_ready); /* READY */
+  TEST_1(!is_answer_recv(e->data->e_tags));
+  TEST_1(!is_offer_recv(e->data->e_tags));
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_bye);
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_terminated); /* TERMINATED */
+  TEST_1(!e->next);
+
+  free_events_in_list(ctx, a->events);
+  free_events_in_list(ctx, b->events);
+
+  TEST_1(!nua_handle_has_active_call(a_call->nh));
+  TEST_1(!nua_handle_has_active_call(b_call->nh));
+
+  nua_handle_destroy(a_call->nh), a_call->nh = NULL;
+  nua_handle_destroy(b_call->nh), b_call->nh = NULL;
+
+  if (print_headings)
+    printf("TEST NUA-6.5: PASSED\n");
+
+  END();
+}
+
+int no_media_terminated(CONDITION_PARAMS)
+{
+  if (!(check_handle(ep, call, nh, SIP_500_INTERNAL_SERVER_ERROR)))
+    return 0;
+
+  save_event_in_list(ctx, event, ep, call);
+
+  switch (callstate(tags)) {
+  case nua_callstate_received:
+    RESPOND(ep, call, nh, SIP_180_RINGING, 
+	    NUTAG_MEDIA_ENABLE(0),
+	    TAG_END());
+    return 0;
+  case nua_callstate_early:
+    RESPOND(ep, call, nh, SIP_200_OK,
+	    NUTAG_MEDIA_ENABLE(0),
+	    TAG_END());
+    return 0;
+  case nua_callstate_terminated:
+    if (call)
+      nua_handle_destroy(call->nh), call->nh = NULL;
+    return 1;
+  default:
+    return 0;
+  }
+}
+
+int accept_call_until_terminated(CONDITION_PARAMS);
+
+/* No offer in INVITE, offer in 200 OK, no answer in ACK */
+int test_no_answer_2(struct context *ctx)
+{
+  BEGIN();
+
+  struct endpoint *a = &ctx->a,  *b = &ctx->b;
+  struct call *a_call = a->call, *b_call = b->call;
+  struct event *e;
+  sip_t const *sip;
+
+  if (print_headings)
+    printf("TEST NUA-6.6: No SDP offer from caller\n");
+
+  a_call->sdp = "v=0\r\n"
+    "o=- 1 1 IN IP4 127.0.0.1\r\n"
+    "s=-\r\n"
+    "c=IN IP4 127.0.0.1\r\n"
+    "t=0 0\r\n"
+    "m=audio 5008 RTP/AVP 8\r\n";
+
+  b_call->sdp = 
+    "v=0\r\n"
+    "o=- 2 1 IN IP4 127.0.0.1\r\n"
+    "s=-\r\n"
+    "c=IN IP4 127.0.0.1\r\n"
+    "t=0 0\r\n"
+    "m=audio 5010 RTP/AVP 0 8\r\n";
+
+  TEST_1(a_call->nh = nua_handle(a->nua, a_call, 
+				 SIPTAG_TO_STR("<sip:b at x.org>"),
+				 TAG_END()));
+
+  TEST_1(!nua_handle_has_active_call(a_call->nh));
+  TEST_1(!nua_handle_has_call_on_hold(a_call->nh));
+
+  INVITE(a, a_call, a_call->nh,
+	 NUTAG_MEDIA_ENABLE(0),
+	 NUTAG_URL(b->contact->m_url),
+	 NUTAG_AUTOACK(1),
+	 TAG_END());
+
+  run_ab_until(ctx, -1, until_terminated,
+	       -1, accept_call_until_terminated);
+
+  /* Client transitions:
+     INIT -(C1)-> CALLING: nua_invite(), nua_i_state
+     CALLING -(C2)-> PROCEEDING: nua_r_invite, nua_i_state
+     PROCEEDING -(C3+C4)-> READY: nua_r_invite, nua_i_state
+  */
+  TEST_1(e = a->events->head); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_calling); /* CALLING */
+  TEST_1(!is_offer_sent(e->data->e_tags));
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_r_invite);
+  TEST(e->data->e_status, 180);
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_proceeding); /* PROCEEDING */
+  TEST_1(!is_answer_recv(e->data->e_tags));
+  TEST_1(!is_offer_recv(e->data->e_tags));
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_r_invite);
+  TEST(e->data->e_status, 200);
+  TEST_1(sip = sip_object(e->data->e_msg));
+  TEST_1(sip->sip_content_type); 
+  TEST_S(sip->sip_content_type->c_type, "application/sdp");
+  TEST_1(sip->sip_payload);	/* there is sdp in 200 OK */
+  TEST_1(sip->sip_contact);
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_ready); /* READY */
+  TEST_1(!is_answer_sent(e->data->e_tags));
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_bye);
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_terminated); /* READY */
+  TEST_1(!e->next);
+  free_events_in_list(ctx, a->events);
+
+  TEST_1(!nua_handle_has_active_call(a_call->nh));
+  TEST_1(!nua_handle_has_call_on_hold(a_call->nh));
+
+  /*
+   Server transitions:
+   INIT -(S1)-> RECEIVED: nua_i_invite, nua_i_state
+   RECEIVED -(S2a)-> EARLY: nua_respond(), nua_i_state
+   EARLY -(S3b)-> COMPLETED: nua_respond(), nua_i_state
+   COMPLETED -(S4)-> READY: nua_i_ack, nua_i_state
+  */
+  TEST_1(e = b->events->head); TEST_E(e->data->e_event, nua_i_invite);
+  TEST(e->data->e_status, 100);
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_received); /* RECEIVED */
+  TEST_1(!is_offer_recv(e->data->e_tags));
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_early); /* EARLY */
+  TEST_1(!is_answer_sent(e->data->e_tags));
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_completed); /* COMPLETED */
+  TEST_1(is_offer_sent(e->data->e_tags));
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_ack);
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_media_error);
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_terminating); /* TERMINATING */
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_r_bye);
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_terminated); /* TERMINATED */
+  TEST_1(!e->next);
+  free_events_in_list(ctx, b->events);
+
+  TEST_1(!nua_handle_has_active_call(b_call->nh));
+  TEST_1(!nua_handle_has_call_on_hold(b_call->nh));
+
+  nua_handle_destroy(a_call->nh), a_call->nh = NULL;
+  nua_handle_destroy(b_call->nh), b_call->nh = NULL;
+
+  if (print_headings)
+    printf("TEST NUA-6.6: PASSED\n");
+
+  END();
+}
+
+int accept_call_until_terminated(CONDITION_PARAMS)
+{
+  if (!(check_handle(ep, call, nh, SIP_500_INTERNAL_SERVER_ERROR)))
+    return 0;
+
+  save_event_in_list(ctx, event, ep, call);
+
+  switch (callstate(tags)) {
+  case nua_callstate_received:
+    RESPOND(ep, call, nh, SIP_180_RINGING, 
+	    TAG_END());
+    return 0;
+  case nua_callstate_early:
+    RESPOND(ep, call, nh, SIP_200_OK,
+	    NUTAG_MEDIA_ENABLE(1),
+	    TAG_IF(call->sdp, SOATAG_USER_SDP_STR(call->sdp)),
+	    TAG_END());
+    return 0;
+  case nua_callstate_terminated:
+    if (call)
+      nua_handle_destroy(call->nh), call->nh = NULL;
+    return 1;
+  default:
+    return 0;
+  }
+}
+
+
+/* No offer in INVITE, no user SDP in 200 OK */
+int test_missing_user_sdp(struct context *ctx)
+{
+  BEGIN();
+
+  struct endpoint *a = &ctx->a,  *b = &ctx->b;
+  struct call *a_call = a->call, *b_call = b->call;
+  struct event *e;
+
+  if (print_headings)
+    printf("TEST NUA-6.6.2: No SDP offer from caller\n");
+
+  a_call->sdp = "v=0\r\n"
+    "o=- 1 1 IN IP4 127.0.0.1\r\n"
+    "s=-\r\n"
+    "c=IN IP4 127.0.0.1\r\n"
+    "t=0 0\r\n"
+    "m=audio 5008 RTP/AVP 8\r\n";
+
+  b_call->sdp = NULL;
+
+  nua_set_params(b->nua, NUTAG_MEDIA_ENABLE(0), TAG_END());
+  run_b_until(ctx, nua_r_set_params, until_final_response);
+
+  TEST_1(a_call->nh = nua_handle(a->nua, a_call, 
+				 SIPTAG_TO_STR("<sip:b at x.org>"),
+				 TAG_END()));
+
+  TEST_1(!nua_handle_has_active_call(a_call->nh));
+  TEST_1(!nua_handle_has_call_on_hold(a_call->nh));
+
+  INVITE(a, a_call, a_call->nh,
+	 NUTAG_MEDIA_ENABLE(0),
+	 NUTAG_URL(b->contact->m_url),
+	 NUTAG_AUTOACK(1),
+	 TAG_END());
+
+  run_ab_until(ctx, -1, until_terminated,
+	       -1, accept_call_until_terminated);
+
+  /* Client transitions:
+     INIT -(C1)-> CALLING: nua_invite(), nua_i_state
+     CALLING -(C2)-> PROCEEDING: nua_r_invite, nua_i_state
+     PROCEEDING -(C3+C4)-> READY: nua_r_invite, nua_i_state
+  */
+  TEST_1(e = a->events->head); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_calling); /* CALLING */
+  TEST_1(!is_offer_sent(e->data->e_tags));
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_r_invite);
+  TEST(e->data->e_status, 180);
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_proceeding); /* PROCEEDING */
+  TEST_1(!is_answer_recv(e->data->e_tags));
+  TEST_1(!is_offer_recv(e->data->e_tags));
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_r_invite);
+  TEST(e->data->e_status, 500);
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_terminated); /* READY */
+  TEST_1(!e->next);
+  free_events_in_list(ctx, a->events);
+
+  TEST_1(!nua_handle_has_active_call(a_call->nh));
+  TEST_1(!nua_handle_has_call_on_hold(a_call->nh));
+
+  /*
+   Server transitions:
+   INIT -(S1)-> RECEIVED: nua_i_invite, nua_i_state
+   RECEIVED -(S2a)-> EARLY: nua_respond(), nua_i_state
+   EARLY -(S3b)-> COMPLETED: nua_respond(), nua_i_state
+   COMPLETED -(S4)-> READY: nua_i_ack, nua_i_state
+  */
+  TEST_1(e = b->events->head); TEST_E(e->data->e_event, nua_i_invite);
+  TEST(e->data->e_status, 100);
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_received); /* RECEIVED */
+  TEST_1(!is_offer_recv(e->data->e_tags));
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_early); /* EARLY */
+  TEST_1(!is_answer_sent(e->data->e_tags));
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_error);
+  TEST(e->data->e_status, 500);
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_terminated); /* COMPLETED */
+  TEST_1(!e->next);
+  free_events_in_list(ctx, b->events);
+
+  TEST_1(!nua_handle_has_active_call(b_call->nh));
+  TEST_1(!nua_handle_has_call_on_hold(b_call->nh));
+
+  nua_handle_destroy(a_call->nh), a_call->nh = NULL;
+  nua_handle_destroy(b_call->nh), b_call->nh = NULL;
+
+  nua_set_params(b->nua,
+		 NUTAG_MEDIA_ENABLE(1), 
+		 SOATAG_USER_SDP_STR("m=audio 5006 RTP/AVP 8 0"),
+		 TAG_END());
+  run_b_until(ctx, nua_r_set_params, until_final_response);
+
+  if (print_headings)
+    printf("TEST NUA-6.6.2: PASSED\n");
+
+  END();
+}
+
+
+
+int test_offer_answer(struct context *ctx)
+{
+  return
+    test_no_answer_1(ctx) ||
+    test_no_answer_2(ctx) ||
+    test_missing_user_sdp(ctx) ||
+    0;
+}

Added: freeswitch/trunk/libs/sofia-sip/tests/test_ops.c
==============================================================================
--- (empty file)
+++ freeswitch/trunk/libs/sofia-sip/tests/test_ops.c	Wed May 14 15:10:54 2008
@@ -0,0 +1,566 @@
+/*
+ * This file is part of the Sofia-SIP package
+ *
+ * Copyright (C) 2005 Nokia Corporation.
+ *
+ * Contact: Pekka Pessi <pekka.pessi at nokia.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA
+ *
+ */
+
+/**@CFILE test_ops.c
+ * @brief High-level test framework for Sofia SIP User Agent Engine
+ *
+ * @author Pekka Pessi <Pekka.Pessi at nokia.com>
+ * @author Martti Mela <Martti Mela at nokia.com>
+ *
+ * @date Created: Wed Aug 17 12:12:12 EEST 2005 ppessi
+ */
+
+#include "config.h"
+
+#include "test_nua.h"
+
+#if HAVE_FUNC
+#elif HAVE_FUNCTION
+#define __func__ __FUNCTION__
+#else
+#define __func__ name
+#endif
+
+int save_events(CONDITION_PARAMS)
+{
+  return save_event_in_list(ctx, event, ep, call) == event_is_normal;
+}
+
+int until_final_response(CONDITION_PARAMS)
+{ 
+  return status >= 200;
+}
+
+int save_until_final_response(CONDITION_PARAMS)
+{
+  save_event_in_list(ctx, event, ep, call);
+  return
+    nua_r_set_params <= event && event < nua_i_network_changed
+    && status >= 200;
+}
+
+/** Save events.
+ *
+ * Terminate when a event is saved.
+ */
+int save_until_received(CONDITION_PARAMS)
+{
+  return save_event_in_list(ctx, event, ep, call) == event_is_normal;
+}
+
+/** Save events until nua_i_outbound is received.  */
+int save_until_special(CONDITION_PARAMS)
+{
+  return save_event_in_list(ctx, event, ep, call) == event_is_special;
+}
+
+/* Return call state from event tag list */
+int callstate(tagi_t const *tags)
+{
+  tagi_t const *ti = tl_find(tags, nutag_callstate);
+  return ti ? ti->t_value : -1;
+}
+
+/* Return true if offer is sent */
+int is_offer_sent(tagi_t const *tags)
+{
+  tagi_t const *ti = tl_find(tags, nutag_offer_sent);
+  return ti ? ti->t_value : 0;
+}
+
+/* Return true if answer is sent */
+int is_answer_sent(tagi_t const *tags)
+{
+  tagi_t const *ti = tl_find(tags, nutag_answer_sent);
+  return ti ? ti->t_value : 0;
+}
+
+/* Return true if offer is recv */
+int is_offer_recv(tagi_t const *tags)
+{
+  tagi_t const *ti = tl_find(tags, nutag_offer_recv);
+  return ti ? ti->t_value : 0;
+}
+
+/* Return true if answer is recv */
+int is_answer_recv(tagi_t const *tags)
+{
+  tagi_t const *ti = tl_find(tags, nutag_answer_recv);
+  return ti ? ti->t_value : 0;
+}
+
+/* Return true if offer/answer is sent/recv */
+int is_offer_answer_done(tagi_t const *tags)
+{
+  tagi_t const *ti;
+
+  return 
+    ((ti = tl_find(tags, nutag_answer_recv)) && ti->t_value) ||
+    ((ti = tl_find(tags, nutag_offer_sent)) && ti->t_value) ||
+    ((ti = tl_find(tags, nutag_offer_recv)) && ti->t_value) ||
+    ((ti = tl_find(tags, nutag_answer_sent)) && ti->t_value);
+}
+
+/* Return audio state from event tag list */
+int audio_activity(tagi_t const *tags)
+{
+  tagi_t const *ti = tl_find(tags, soatag_active_audio);
+  return ti ? ti->t_value : -1;
+}
+
+/* Return video state from event tag list */
+int video_activity(tagi_t const *tags)
+{
+  tagi_t const *ti = tl_find(tags, soatag_active_video);
+  return ti ? ti->t_value : -1;
+}
+
+void print_event(nua_event_t event,
+		 char const *operation,
+		 int status, char const *phrase,
+		 nua_t *nua, struct context *ctx,
+		 struct endpoint *ep,
+		 nua_handle_t *nh, struct call *call,
+		 sip_t const *sip,
+		 tagi_t tags[])
+{
+  tagi_t const *t;
+  static su_nanotime_t started = 0;
+  su_nanotime_t now;
+  char timestamp[32];
+
+  su_nanotime(&now);
+
+  if (started == 0) started = now;
+
+  now -= started; now /= 1000000;
+
+  snprintf(timestamp, sizeof timestamp, "%03u.%03u",
+	   (unsigned)(now / 1000), (unsigned)(now % 1000));
+
+  if (event == nua_i_state) {
+    fprintf(stderr, "%s %s.nua(%p): event %s %s\n", timestamp,
+	    ep->name, (void *)nh, nua_event_name(event),
+	    nua_callstate_name(callstate(tags)));
+  }
+  else if ((int)event >= nua_r_set_params) {
+    t = tl_find(tags, nutag_substate);
+    if (t) {
+      fprintf(stderr, "%s %s.nua(%p): event %s status %u %s (%s)\n", timestamp,
+	      ep->name, (void*)nh, nua_event_name(event), status, phrase,
+	      nua_substate_name(t->t_value));
+    }
+    else {
+      fprintf(stderr, "%s %s.nua(%p): event %s status %u %s\n", timestamp,
+	      ep->name, (void *)nh, nua_event_name(event), status, phrase);
+    }
+  }
+  else if (event == nua_i_notify) {
+    t = tl_find(tags, nutag_substate);
+    fprintf(stderr, "%s %s.nua(%p): event %s %s (%s)\n", timestamp,
+	    ep->name, (void *)nh, nua_event_name(event), phrase,
+	    nua_substate_name(t ? t->t_value : 0));
+  }
+  else if ((int)event >= nua_i_bye || 
+	   event == nua_i_invite || event == nua_i_cancel ||
+	   event == nua_i_ack) {
+    fprintf(stderr, "%s %s.nua(%p): event %s %03d %s\n", timestamp,
+	    ep->name, (void *)nh, nua_event_name(event), status, phrase);
+  }
+  else if ((int)event >= 0) {
+    fprintf(stderr, "%s %s.nua(%p): event %s %s\n", timestamp,
+	    ep->name, (void *)nh, nua_event_name(event), phrase);
+  }
+  else if (status > 0) {
+    fprintf(stderr, "%s %s.nua(%p): call %s() with status %u %s\n", timestamp,
+	    ep->name, (void *)nh, operation, status, phrase);
+  }
+  else {
+    t = tl_find(tags, siptag_subject_str);
+    if (t && t->t_value) {
+      char const *subject = (char const *)t->t_value;
+      fprintf(stderr, "%s %s.nua(%p): call %s() \"%s\"\n", timestamp,
+	      ep->name, (void *)nh, operation, subject);
+    }
+    else
+      fprintf(stderr, "%s %s.nua(%p): call %s()\n", timestamp,
+	      ep->name, (void *)nh, operation);
+  }
+
+  if (tags && 
+      ((tstflags & tst_verbatim) || ctx->print_tags || ep->print_tags))
+    tl_print(stderr, "", tags);
+}
+
+void ep_callback(nua_event_t event,
+		 int status, char const *phrase,
+		 nua_t *nua, struct context *ctx,
+		 struct endpoint *ep,
+		 nua_handle_t *nh, struct call *call,
+		 sip_t const *sip,
+		 tagi_t tags[])
+{
+  if (ep->printer)
+    ep->printer(event, "", status, phrase, nua, ctx, ep, nh, call, sip, tags);
+
+  if (call == NULL && nh) {
+    for (call = ep->call; call; call = call->next) {
+      if (!call->nh)
+	break;
+      if (nh == call->nh)
+	break;
+    }
+
+    if (call && call->nh == NULL) {
+      call->nh = nh;
+      nua_handle_bind(nh, call);
+    }
+  }
+
+  if ((ep->next_condition == NULL ||
+       ep->next_condition(event, status, phrase,
+			  nua, ctx, ep, nh, call, sip, tags))
+      &&
+      (ep->next_event == -1 || ep->next_event == event))
+    ep->running = 0;
+
+  ep->last_event = event;
+
+  if (call == NULL && nh)
+    nua_handle_destroy(nh);
+}
+
+void a_callback(nua_event_t event,
+		int status, char const *phrase,
+		nua_t *nua, struct context *ctx,
+		nua_handle_t *nh, struct call *call,
+		sip_t const *sip,
+		tagi_t tags[])
+{
+  ep_callback(event, status, phrase, nua, ctx, &ctx->a, nh, call, sip, tags);
+}
+
+void b_callback(nua_event_t event,
+		int status, char const *phrase,
+		nua_t *nua, struct context *ctx,
+		nua_handle_t *nh, struct call *call,
+		sip_t const *sip,
+		tagi_t tags[])
+{
+  ep_callback(event, status, phrase, nua, ctx, &ctx->b, nh, call, sip, tags);
+}
+
+void c_callback(nua_event_t event,
+		int status, char const *phrase,
+		nua_t *nua, struct context *ctx,
+		nua_handle_t *nh, struct call *call,
+		sip_t const *sip,
+		tagi_t tags[])
+{
+  ep_callback(event, status, phrase, nua, ctx, &ctx->c, nh, call, sip, tags);
+}
+
+void run_abc_until(struct context *ctx,
+		   nua_event_t a_event, condition_function *a_condition,
+		   nua_event_t b_event, condition_function *b_condition,
+		   nua_event_t c_event, condition_function *c_condition)
+{
+  struct endpoint *a = &ctx->a, *b = &ctx->b, *c = &ctx->c;
+
+  a->next_event = a_event;
+  a->next_condition = a_condition;
+  a->last_event = -1;
+  a->running = a_condition != NULL && a_condition != save_events;
+  a->running |= a_event != -1;
+  memset(&a->flags, 0, sizeof a->flags);
+
+  b->next_event = b_event;
+  b->next_condition = b_condition;
+  b->last_event = -1;
+  b->running = b_condition != NULL && b_condition != save_events;
+  b->running |= b_event != -1;
+  memset(&b->flags, 0, sizeof b->flags);
+
+  c->next_event = c_event;
+  c->next_condition = c_condition;
+  c->last_event = -1;
+  c->running = c_condition != NULL && c_condition != save_events;
+  c->running |= c_event != -1;
+  memset(&c->flags, 0, sizeof c->flags);
+
+  for (; a->running || b->running || c->running;) {
+    su_root_step(ctx->root, 100);
+  }
+}
+
+void run_ab_until(struct context *ctx,
+		  nua_event_t a_event, condition_function *a_condition,
+		  nua_event_t b_event, condition_function *b_condition)
+{
+  run_abc_until(ctx, a_event, a_condition, b_event, b_condition, -1, NULL);
+}
+
+void run_bc_until(struct context *ctx,
+		  nua_event_t b_event, condition_function *b_condition,
+		  nua_event_t c_event, condition_function *c_condition)
+{
+  run_abc_until(ctx, -1, NULL, b_event, b_condition, c_event, c_condition);
+}
+
+int run_a_until(struct context *ctx,
+		nua_event_t a_event,
+		condition_function *a_condition)
+{
+  run_abc_until(ctx, a_event, a_condition, -1, NULL, -1, NULL);
+  return ctx->a.last_event;
+}
+
+int run_b_until(struct context *ctx,
+		nua_event_t b_event,
+		condition_function *b_condition)
+{
+  run_abc_until(ctx, -1, NULL, b_event, b_condition, -1, NULL);
+  return ctx->b.last_event;
+}
+
+int run_c_until(struct context *ctx,
+		nua_event_t event,
+		condition_function *condition)
+{
+  run_abc_until(ctx, -1, NULL, -1, NULL, event, condition);
+  return ctx->c.last_event;
+}
+
+#define OPERATION(X, x)	   \
+int X(struct endpoint *ep, \
+      struct call *call, nua_handle_t *nh, \
+      tag_type_t tag, tag_value_t value, \
+      ...) \
+{ \
+  ta_list ta; \
+  ta_start(ta, tag, value); \
+\
+  if (ep->printer) \
+    ep->printer(-1, "nua_" #x, 0, "", ep->nua, ep->ctx, ep, \
+		nh, call, NULL, ta_args(ta)); \
+\
+  nua_##x(nh, ta_tags(ta)); \
+\
+  ta_end(ta); \
+  return 0; \
+} extern int dummy
+
+OPERATION(INVITE, invite);
+OPERATION(ACK, ack);
+OPERATION(BYE, bye);
+OPERATION(CANCEL, cancel);
+OPERATION(AUTHENTICATE, authenticate);
+OPERATION(UPDATE, update);
+OPERATION(INFO, info);
+OPERATION(PRACK, prack);
+OPERATION(REFER, refer);
+OPERATION(MESSAGE, message);
+OPERATION(METHOD, method);
+OPERATION(OPTIONS, options);
+OPERATION(PUBLISH, publish);
+OPERATION(UNPUBLISH, unpublish);
+OPERATION(REGISTER, register);
+OPERATION(UNREGISTER, unregister);
+OPERATION(SUBSCRIBE, subscribe);
+OPERATION(UNSUBSCRIBE, unsubscribe);
+OPERATION(NOTIFY, notify);
+OPERATION(NOTIFIER, notifier);
+OPERATION(TERMINATE, terminate);
+OPERATION(AUTHORIZE, authorize);
+
+/* Respond via endpoint and handle */
+int RESPOND(struct endpoint *ep,
+	    struct call *call,
+	    nua_handle_t *nh,
+	    int status, char const *phrase,
+	    tag_type_t tag, tag_value_t value,
+	    ...)
+{
+  ta_list ta;
+
+  ta_start(ta, tag, value);
+
+  if (ep->printer)
+    ep->printer(-1, "nua_respond", status, phrase, ep->nua, ep->ctx, ep,
+		nh, call, NULL, ta_args(ta));
+
+  nua_respond(nh, status, phrase, ta_tags(ta));
+  ta_end(ta);
+
+  return 0;
+}
+
+/* Destroy a handle */
+int DESTROY(struct endpoint *ep,
+	    struct call *call,
+	    nua_handle_t *nh)
+{
+  if (ep->printer)
+    ep->printer(-1, "nua_handle_destroy", 0, "", ep->nua, ep->ctx, ep,
+		nh, call, NULL, NULL);
+
+  nua_handle_destroy(nh);
+
+  if (call->nh == nh)
+    call->nh = NULL;
+
+  return 0;
+}
+
+
+/* Reject all but currently used handles */
+struct call *check_handle(struct endpoint *ep,
+			  struct call *call,
+			  nua_handle_t *nh,
+			  int status, char const *phrase)
+{
+  if (call)
+    return call;
+
+  if (status)
+    RESPOND(ep, call, nh, status, phrase, TAG_END());
+
+  nua_handle_destroy(nh);
+  return NULL;
+}
+
+/* Save nua event in call-specific list */
+int save_event_in_list(struct context *ctx,
+		       nua_event_t nevent,
+		       struct endpoint *ep,
+		       struct call *call)
+
+{
+  struct eventlist *list;
+  struct event *e;
+  int action = ep->is_special(nevent);
+
+  if (action == event_is_extra)
+    return 0;
+  else if (action == event_is_special || call == NULL)
+    list = ep->specials;
+  else if (call->events)
+    list = call->events;
+  else
+    list = ep->events;
+
+  e = su_zalloc(ctx->home, sizeof *e);
+
+  if (!e) { perror("su_zalloc"), abort(); }
+
+  if (!nua_save_event(ep->nua, e->saved_event)) {
+    su_free(ctx->home, e);
+    return -1;
+  }
+
+  *(e->prev = list->tail) = e; list->tail = &e->next;
+
+  e->call = call;
+  e->data = nua_event_data(e->saved_event);
+
+  return action;
+}
+
+/* Free nua events from endpoint list */
+void free_events_in_list(struct context *ctx,
+			 struct eventlist *list)
+{
+  struct event *e;
+
+  while ((e = list->head)) {
+    if ((*e->prev = e->next))
+      e->next->prev = e->prev;
+    nua_destroy_event(e->saved_event);
+    su_free(ctx->home, e);
+  }
+
+  list->tail = &list->head;
+}
+
+void free_event_in_list(struct context *ctx,
+			struct eventlist *list,
+			struct event *e)
+{
+  if (e) {
+    if ((*e->prev = e->next))
+      e->next->prev = e->prev;
+    nua_destroy_event(e->saved_event);
+    su_free(ctx->home, e);
+
+    if (list->head == NULL)
+      list->tail = &list->head;
+  }
+}			      
+
+struct event *event_by_type(struct event *e, nua_event_t etype)
+{
+  for (; e; e = e->next) {
+    if (e->data->e_event == etype)
+      break;
+  }
+
+  return e;
+}
+
+size_t count_events(struct event const *e)
+{
+  size_t n;
+
+  for (n = 0; e; e = e->next)
+    n++;
+
+  return n;
+}
+
+
+int is_special(nua_event_t e)
+{
+  if (e == nua_i_active || e == nua_i_terminated)
+    return event_is_extra;
+  if (e == nua_i_outbound)
+    return event_is_special;
+
+  return event_is_normal;
+}
+
+void
+endpoint_init(struct context *ctx, struct endpoint *e, char id)
+{
+  e->name[0] = id;
+  e->ctx = ctx;
+
+  e->is_special = is_special;
+
+  call_init(e->call);
+  call_init(e->reg);
+  eventlist_init(e->events);
+  eventlist_init(e->specials);
+}
+
+void nolog(void *stream, char const *fmt, va_list ap) {}

Added: freeswitch/trunk/libs/sofia-sip/tests/test_proxy.c
==============================================================================
--- (empty file)
+++ freeswitch/trunk/libs/sofia-sip/tests/test_proxy.c	Wed May 14 15:10:54 2008
@@ -0,0 +1,1604 @@
+/*
+ * This file is part of the Sofia-SIP package
+ *
+ * Copyright (C) 2005 Nokia Corporation.
+ *
+ * Contact: Pekka Pessi <pekka.pessi at nokia.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA
+ *
+ */
+
+/**@CFILE test_proxy.c
+ * @brief Extremely simple proxy and registrar for testing nua
+ *
+ * @author Pekka Pessi <Pekka.Pessi at nokia.com>
+ *
+ * @date Created: Thu Nov  3 22:49:46 EET 2005
+ */
+
+#include "config.h"
+
+#include <string.h>
+
+struct proxy;
+struct domain;
+union proxy_or_domain;
+struct proxy_tr;
+struct client_tr;
+struct registration_entry;
+struct binding;
+
+#define SU_ROOT_MAGIC_T struct proxy
+#define NTA_LEG_MAGIC_T union proxy_or_domain
+#define NTA_OUTGOING_MAGIC_T struct client_tr
+#define NTA_INCOMING_MAGIC_T struct proxy_tr
+#define SU_TIMER_ARG_T struct proxy_tr
+
+#include <sofia-sip/su_wait.h>
+#include <sofia-sip/nta.h>
+#include <sofia-sip/sip_header.h>
+#include <sofia-sip/sip_status.h>
+#include <sofia-sip/sip_util.h>
+#include <sofia-sip/auth_module.h>
+#include <sofia-sip/su_tagarg.h>
+#include <sofia-sip/msg_addr.h>
+#include <sofia-sip/hostdomain.h>
+#include <sofia-sip/tport.h>
+#include <sofia-sip/nta_tport.h>
+
+#include <stdlib.h>
+#include <assert.h>
+
+#define LIST_PROTOS(STORAGE, PREFIX, T)			 \
+STORAGE void PREFIX ##_insert(T **list, T *node),	 \
+  PREFIX ##_remove(T *node)
+
+#define LIST_BODIES(STORAGE, PREFIX, T, NEXT, PREV)	  \
+STORAGE void PREFIX ##_insert(T **list, T *node)   \
+{							 \
+  if ((node->NEXT = *list)) {				 \
+    node->PREV = node->NEXT->PREV;			 \
+    node->NEXT->PREV = &node->NEXT;			 \
+  }							 \
+  else							 \
+    node->PREV = list;					 \
+  *list = node;						 \
+}							 \
+STORAGE void PREFIX ##_remove(T *node)			 \
+{							 \
+  if (node->PREV)					 \
+    if ((*node->PREV = node->NEXT))			 \
+      node->NEXT->PREV = node->PREV;			 \
+  node->PREV = NULL;					 \
+}							 \
+extern int LIST_DUMMY_VARIABLE
+
+#include "test_proxy.h"
+#include <sofia-sip/auth_module.h>
+
+struct proxy {
+  su_home_t    home[1];
+  void        *magic;
+  su_root_t   *parent;
+  su_clone_r   clone;
+  tagi_t      *tags;
+
+  su_root_t   *root;
+ 
+  nta_agent_t *agent;
+  url_t const *uri;
+  sip_route_t *lr;
+  char const *lr_str;
+  url_t const *rr_uri;
+
+  nta_leg_t *defleg;
+
+  sip_contact_t *transport_contacts;
+
+  struct proxy_tr *stateless;
+  struct proxy_tr *transactions;
+  struct proxy_tr *invite_waiting;
+
+  struct domain *domains;
+
+  struct {
+    unsigned t1x64;
+    sip_time_t session_expires, min_se;
+  } prefs;
+}; 
+
+struct domain {
+  su_home_t home[1];
+  void *magic;
+  struct proxy *proxy;
+  struct domain *next, **prev;
+
+  url_t *uri;
+
+  nta_leg_t *rleg, *uleg;
+
+  auth_mod_t *auth;
+  struct registration_entry *entries;
+
+  struct {
+    sip_time_t min_expires, expires, max_expires;
+    int outbound_tcp;		/**< Use inbound TCP connection as outbound */
+    char const *authorize;	/**< Authorization realm to use */
+    int record_route;
+  } prefs;  
+
+  tagi_t *tags;
+};
+
+LIST_PROTOS(static, domain, struct domain);
+static int _domain_init(void *_d);
+static int  domain_init(struct domain *domain);
+static void domain_destroy(struct domain *domain);
+
+LIST_BODIES(static, domain, struct domain, next, prev);
+
+LIST_PROTOS(static, registration_entry, struct registration_entry);
+static struct registration_entry *registration_entry_new(struct domain *,
+							 url_t const *);
+static void registration_entry_destroy(struct registration_entry *e);
+
+struct registration_entry
+{
+  struct registration_entry *next, **prev;
+  struct domain *domain;	/* backpointer */
+  url_t *aor;			/* address-of-record */
+  struct binding *bindings;	/* list of bindings */
+  sip_contact_t *contacts;
+};
+
+struct binding
+{
+  struct binding *next, **prev;
+  sip_contact_t *contact;	/* binding */
+  sip_time_t registered, expires; /* When registered and when expires */
+  sip_call_id_t *call_id;
+  uint32_t cseq;
+  tport_t *tport;		/**< Reference to tport */
+};
+
+static struct binding *binding_new(su_home_t *home, 
+				   sip_contact_t *contact,
+				   tport_t *tport,
+				   sip_call_id_t const *call_id,
+				   uint32_t cseq,
+				   sip_time_t registered, 
+				   sip_time_t expires);
+static void binding_destroy(su_home_t *home, struct binding *b);
+static int binding_is_active(struct binding const *b)
+{
+  return
+    b->expires > sip_now() && 
+    (b->tport == NULL || tport_is_clear_to_send(b->tport));
+}
+
+LIST_PROTOS(static, proxy_tr, struct proxy_tr);
+struct proxy_tr *proxy_tr_new(struct proxy *);
+static void proxy_tr_timeout(struct proxy_tr *t);
+static void proxy_tr_destroy(struct proxy_tr *t);
+
+struct proxy_tr
+{
+  struct proxy_tr *next, **prev;
+
+  struct proxy *proxy;		/**< Backpointer to proxy */
+
+  struct domain *origin;	/**< Originating domain */
+  struct domain *domain;	/**< Destination domain */
+
+  sip_time_t now;		/**< When received */
+
+  nta_incoming_t *server;	/**< server transaction */
+  msg_t *msg;			/**< request message */
+  sip_t *sip;			/**< request headers */
+
+  sip_method_t method;		/**< request method */
+  char const *method_name;
+  int status;			/**< best status */
+  url_t *target;		/**< request-URI */
+
+  struct client_tr *clients;	/**< Client transactions */
+
+  struct registration_entry *entry;
+				/**< Registration entry */
+
+  auth_mod_t *am;		/**< Authentication module */
+  auth_status_t *as;		/**< Authentication status */
+  char const *realm;		/**< Authentication realm to use */
+  unsigned use_auth;		/**< Authentication method (401/407) to use */
+
+  su_timer_t *timer;		/**< Timer */
+
+  unsigned rr:1;
+};
+
+LIST_PROTOS(static, client_tr, struct client_tr);
+
+struct client_tr
+{
+  struct client_tr *next, **prev;
+  struct proxy_tr *t;
+
+  int status;			/* response status */
+  sip_request_t *rq;		/* request line */
+  msg_t *msg;			/* request message */
+  sip_t *sip;			/* request headers */
+  nta_outgoing_t *client;	/* transaction */
+};
+
+LIST_BODIES(static, client_tr, struct client_tr, next, prev);
+
+static sip_contact_t *create_transport_contacts(struct proxy *p);
+
+union proxy_or_domain { struct proxy proxy[1]; struct domain domain[1]; };
+
+static int proxy_request(union proxy_or_domain *proxy,
+			 nta_leg_t *leg,
+			 nta_incoming_t *irq,
+			 sip_t const *sip);
+
+static int domain_request(union proxy_or_domain *domain,
+			  nta_leg_t *leg,
+			  nta_incoming_t *irq,
+			  sip_t const *sip);
+
+static int proxy_response(struct client_tr *client,
+			   nta_outgoing_t *orq,
+			   sip_t const *sip);
+
+static int close_tports(void *proxy);
+
+static auth_challenger_t registrar_challenger[1];
+static auth_challenger_t proxy_challenger[1];
+
+/* Proxy entry point */
+static int 
+test_proxy_init(su_root_t *root, struct proxy *proxy)
+{
+  struct proxy_tr *t;
+  struct client_tr *c;
+
+  auth_challenger_t _proxy_challenger[1] = 
+  {{ 
+      SIP_407_PROXY_AUTH_REQUIRED,
+      sip_proxy_authenticate_class,
+      sip_proxy_authentication_info_class
+    }};
+
+  auth_challenger_t _registrar_challenger[1] = 
+  {{ 
+      SIP_401_UNAUTHORIZED,
+      sip_www_authenticate_class,
+      sip_authentication_info_class
+    }};
+
+  *proxy_challenger = *_proxy_challenger;
+  *registrar_challenger = *_registrar_challenger;
+
+  proxy->root = root;
+
+  proxy->agent = nta_agent_create(root,
+				  URL_STRING_MAKE("sip:0.0.0.0:*"),
+				  NULL, NULL,
+				  NTATAG_UA(0),
+				  NTATAG_CANCEL_487(0),
+				  NTATAG_SERVER_RPORT(1),
+				  NTATAG_CLIENT_RPORT(1),
+				  TAG_NEXT(proxy->tags));
+
+  if (!proxy->agent)
+    return -1;
+
+  proxy->transport_contacts = create_transport_contacts(proxy);
+
+  proxy->defleg = nta_leg_tcreate(proxy->agent,
+				  proxy_request,
+				  (union proxy_or_domain *)proxy,
+				  NTATAG_NO_DIALOG(1),
+				  TAG_END());
+
+  proxy->prefs.session_expires = 180;
+  proxy->prefs.min_se = 90;
+  proxy->prefs.t1x64 = 64 * 500;
+
+  nta_agent_get_params(proxy->agent,
+		       NTATAG_SIP_T1X64_REF(proxy->prefs.t1x64),
+		       TAG_END());
+
+  if (!proxy->defleg) 
+    return -1;
+  /* if (!proxy->example_net || !proxy->example_org || !proxy->example_com)
+     return -1; */
+
+  /* Create stateless client */
+  t = su_zalloc(proxy->home, sizeof *t);
+  c = su_zalloc(proxy->home, sizeof *c); 
+
+  if (!t || !c)
+    return -1;
+
+  proxy->stateless = t;
+  t->proxy = proxy;
+  c->t = t, client_tr_insert(&t->clients, c);
+  t->server = nta_incoming_default(proxy->agent);
+  c->client = nta_outgoing_default(proxy->agent, proxy_response, c);
+
+  if (!c->client || !t->server)
+    return -1;
+
+  proxy->uri = nta_agent_contact(proxy->agent)->m_url;
+  proxy->lr_str = su_sprintf(proxy->home, "<" URL_PRINT_FORMAT ";lr>", URL_PRINT_ARGS(proxy->uri));
+  proxy->lr = sip_route_make(proxy->home, proxy->lr_str);
+
+  if (!proxy->lr)
+    return -1;
+  				  
+  return 0;
+}
+
+static void
+test_proxy_deinit(su_root_t *root, struct proxy *proxy)
+{
+  struct proxy_tr *t;
+
+  while (proxy->transactions)
+    proxy_tr_destroy(proxy->transactions);
+
+  if ((t = proxy->stateless)) {
+    proxy->stateless = NULL;
+    proxy_tr_destroy(t);
+  }
+
+  while (proxy->domains)
+    domain_destroy(proxy->domains);
+
+  nta_agent_destroy(proxy->agent);
+
+  free(proxy->tags);
+}
+
+/* Create test proxy object */
+struct proxy *test_proxy_create(su_root_t *root,
+				tag_type_t tag, tag_value_t value, ...)
+{
+  struct proxy *p = su_home_new(sizeof *p);
+
+  if (p) {
+    ta_list ta;
+
+    p->magic = test_proxy_create;
+
+    p->parent = root;
+
+    ta_start(ta, tag, value);
+    p->tags = tl_llist(ta_tags(ta));
+    ta_end(ta);
+    
+    if (su_clone_start(root,
+		       p->clone,
+		       p,
+		       test_proxy_init,
+		       test_proxy_deinit) == -1)
+      su_home_unref(p->home), p = NULL;
+  }
+
+  return p;
+}
+
+/* Destroy the proxy object */
+void test_proxy_destroy(struct proxy *p)
+{
+  if (p) {
+    su_clone_wait(p->parent, p->clone);
+    su_home_unref(p->home);
+  }
+}
+
+/* Return the proxy URI */
+url_t const *test_proxy_uri(struct proxy const *p)
+{
+  return p ? p->uri : NULL;
+}
+
+/* Return the proxy route URI */
+char const *test_proxy_route_uri(struct proxy const *p,
+				 sip_route_t const **return_route)
+{
+  if (p == NULL)
+    return NULL;
+
+  if (return_route)
+    *return_route = p->lr;
+
+  return p->lr_str;
+}
+
+struct _set_logging {
+  struct proxy *p;
+  int logging;
+};
+ 
+static int _set_logging(void *_a)
+{
+  struct _set_logging *a = _a;
+  return nta_agent_set_params(a->p->agent, TPTAG_LOG(a->logging), TAG_END());
+}
+
+void test_proxy_set_logging(struct proxy *p, int logging)
+{
+  if (p) {
+    struct _set_logging a[1] = {{ p, logging }};
+    su_task_execute(su_clone_task(p->clone), _set_logging, a, NULL);
+  }
+}
+
+void test_proxy_domain_set_expiration(struct domain *d,
+				      sip_time_t min_expires, 
+				      sip_time_t expires, 
+				      sip_time_t max_expires)
+{
+  if (d) {
+    d->prefs.min_expires = min_expires;
+    d->prefs.expires = expires;
+    d->prefs.max_expires = max_expires;
+  }
+}
+
+void test_proxy_domain_get_expiration(struct domain *d,
+				      sip_time_t *return_min_expires,
+				      sip_time_t *return_expires,
+				      sip_time_t *return_max_expires)
+{
+  if (d) {
+    if (return_min_expires) *return_min_expires = d->prefs.min_expires;
+    if (return_expires) *return_expires = d->prefs.expires;
+    if (return_max_expires) *return_max_expires = d->prefs.max_expires;
+  }
+}
+
+void test_proxy_set_session_timer(struct proxy *p,
+				  sip_time_t session_expires, 
+				  sip_time_t min_se)
+{
+  if (p) {
+    p->prefs.session_expires = session_expires;
+    p->prefs.min_se = min_se;
+  }
+}
+
+void test_proxy_get_session_timer(struct proxy *p,
+				  sip_time_t *return_session_expires,
+				  sip_time_t *return_min_se)
+{
+  if (p) {
+    if (return_session_expires)
+      *return_session_expires = p->prefs.session_expires;
+    if (return_min_se) *return_min_se = p->prefs.min_se;
+  }
+}
+
+void test_proxy_domain_set_outbound(struct domain *d,
+				    int use_outbound)
+{
+  if (d) {
+    d->prefs.outbound_tcp = use_outbound;
+  }
+}
+
+void test_proxy_domain_get_outbound(struct domain *d,
+				    int *return_use_outbound)
+{
+  if (d) {
+    if (return_use_outbound)
+      *return_use_outbound = d->prefs.outbound_tcp;
+  }
+}
+
+void test_proxy_domain_set_record_route(struct domain *d,
+					int use_record_route)
+{
+  if (d) {
+    d->prefs.record_route = use_record_route;
+  }
+}
+
+void test_proxy_domain_get_record_route(struct domain *d,
+					int *return_use_record_route)
+{
+  if (d) {
+    if (return_use_record_route)
+      *return_use_record_route = d->prefs.record_route;
+  }
+}
+
+int test_proxy_domain_set_authorize(struct domain *d, 
+				     char const *realm)
+{
+  if (d) {
+    if (realm) {
+      realm = su_strdup(d->home, realm);
+      if (!realm)
+	return -1;
+    }
+
+    d->prefs.authorize = realm;
+
+    return 0;
+  }
+  return -1;
+}
+
+int test_proxy_domain_get_authorize(struct domain *d,
+				     char const **return_realm)
+{
+  if (d) {
+    if (return_realm) {
+      *return_realm = d->prefs.authorize;
+      return 0;
+    }
+  }
+  return -1;
+}
+
+int test_proxy_close_tports(struct proxy *p)
+{
+  if (p) {
+    int retval = -EPROTO;
+
+    su_task_execute(su_clone_task(p->clone), close_tports, p, &retval);
+
+    if (retval < 0)
+      return errno = -retval, -1;
+    else
+      return 0;
+  }
+  return errno = EFAULT, -1;
+}
+
+/* ---------------------------------------------------------------------- */
+
+struct domain *test_proxy_add_domain(struct proxy *p,
+				     url_t const *uri,
+				     tag_type_t tag, tag_value_t value, ...)
+{
+  struct domain *d;
+
+  if (p == NULL || uri == NULL)
+    return NULL;
+
+  d = su_home_clone(p->home, sizeof *d);
+
+  if (d) {
+    ta_list ta;
+    int init = 0;
+
+    ta_start(ta, tag, value);
+
+    d->magic = domain_init;
+
+    d->proxy = p;
+    d->uri = url_hdup(d->home, uri);
+    d->tags = tl_adup(d->home, ta_args(ta));
+
+    d->prefs.min_expires = 300;
+    d->prefs.expires = 3600;
+    d->prefs.max_expires = 36000;
+    d->prefs.outbound_tcp = 0;
+    d->prefs.authorize = NULL;
+
+    if (d->uri && d->tags && 
+	!su_task_execute(su_clone_task(p->clone), _domain_init, d, &init)) {
+      if (init == 0)
+	/* OK */;
+      else
+	d = NULL;
+    }
+    else 
+      su_home_unref(d->home);
+  }
+
+  return d;
+}
+
+static int _domain_init(void *_d)
+{
+  return domain_init(_d);
+}
+
+static int domain_init(struct domain *d)
+{
+  struct proxy *p = d->proxy;
+  url_t uri[1];
+
+  *uri = *d->uri;
+
+  d->auth = auth_mod_create(p->root, TAG_NEXT(d->tags));
+
+  /* Leg for URIs without userpart */
+  d->rleg = nta_leg_tcreate(d->proxy->agent,
+			    domain_request,
+			    (union proxy_or_domain *)d,
+			    NTATAG_NO_DIALOG(1),
+			    URLTAG_URL(uri),
+			    TAG_END());
+
+  /* Leg for URIs with wildcard userpart */
+  uri->url_user = "%";
+  d->uleg = nta_leg_tcreate(d->proxy->agent,
+			    domain_request,
+			    (union proxy_or_domain *)d,
+			    NTATAG_NO_DIALOG(1),
+			    URLTAG_URL(uri),
+			    TAG_END());
+
+  if (d->auth && d->rleg && d->uleg) {
+    domain_insert(&p->domains, d);
+    return 0;
+  }
+
+  domain_destroy(d);
+
+  return -1;
+}
+
+static void domain_destroy(struct domain *d)
+{
+  while (d->entries)
+    registration_entry_destroy(d->entries);
+
+  nta_leg_destroy(d->rleg), d->rleg = NULL;
+  nta_leg_destroy(d->uleg), d->uleg = NULL;
+  auth_mod_destroy(d->auth), d->auth = NULL;
+
+  domain_remove(d);
+
+  su_home_unref(d->home);
+}
+
+/* ---------------------------------------------------------------------- */
+
+static sip_contact_t *create_transport_contacts(struct proxy *p)
+{
+  su_home_t *home = p->home;
+  sip_via_t *v;
+  sip_contact_t *retval = NULL, **mm = &retval;
+
+  if (!p->agent)
+    return NULL;
+
+  for (v = nta_agent_via(p->agent); v; v = v->v_next) {
+    char const *proto = v->v_protocol;
+
+    if (v->v_next && 
+	strcasecmp(v->v_host, v->v_next->v_host) == 0 &&
+	str0cmp(v->v_port, v->v_next->v_port) == 0 &&
+	((proto == sip_transport_udp &&
+	  v->v_next->v_protocol == sip_transport_tcp) ||
+	 (proto == sip_transport_tcp &&
+	  v->v_next->v_protocol == sip_transport_udp)))
+      /* We have udp/tcp pair, insert URL without tport parameter */
+      *mm = sip_contact_create_from_via_with_transport(home, v, NULL, NULL);
+    if (*mm) mm = &(*mm)->m_next;
+
+    *mm = sip_contact_create_from_via_with_transport(home, v, NULL, proto);
+
+    if (*mm) mm = &(*mm)->m_next;
+  }
+
+  return retval;
+}
+
+/* ---------------------------------------------------------------------- */
+
+static int proxy_tr_with(struct proxy *proxy,
+			 struct domain *domain,
+			 nta_incoming_t *irq,
+			 sip_t const *sip,
+			 int (*process)(struct proxy_tr *));
+static int proxy_transaction(struct proxy_tr *t);
+static int respond_transaction(struct proxy_tr *t,
+			       int status, char const *phrase,
+			       tag_type_t tag, tag_value_t value,
+			       ...);
+static int validate_transaction(struct proxy_tr *t);
+static int originating_transaction(struct proxy_tr *t);
+static int challenge_transaction(struct proxy_tr *t);
+static int session_timers(struct proxy_tr *t);
+static int incoming_transaction(struct proxy_tr *t);
+static int target_transaction(struct proxy_tr *t,
+			      url_t const *target,
+			      tport_t *tport);
+static int process_register(struct proxy_tr *t);
+static int process_options(struct proxy_tr *t);
+
+static int proxy_ack_cancel(struct proxy_tr *t,
+			    nta_incoming_t *irq,
+			    sip_t const *sip);
+
+static struct registration_entry *
+registration_entry_find(struct domain const *domain, url_t const *uri);
+
+static int proxy_request(union proxy_or_domain *pod,
+			 nta_leg_t *leg,
+			 nta_incoming_t *irq,
+			 sip_t const *sip)
+{
+  assert(pod->proxy->magic = test_proxy_init);
+
+  return proxy_tr_with(pod->proxy, NULL, irq, sip, proxy_transaction);
+}
+
+static int domain_request(union proxy_or_domain *pod,
+			  nta_leg_t *leg,
+			  nta_incoming_t *irq,
+			  sip_t const *sip)
+{
+  int (*process)(struct proxy_tr *) = NULL;
+  sip_method_t method = sip->sip_request->rq_method;
+  
+  assert(pod->domain->magic = domain_init);
+
+  if (leg == pod->domain->uleg)
+    process = proxy_transaction;
+  else if (method == sip_method_register)
+    process = process_register;
+  else if (method == sip_method_options) 
+    process = process_options;
+
+  if (process == NULL)
+    return 501;			/* Not implemented */
+
+  return proxy_tr_with(pod->domain->proxy, pod->domain, irq, sip, process);
+}
+
+static int proxy_tr_with(struct proxy *proxy,
+			 struct domain *domain,
+			 nta_incoming_t *irq,
+			 sip_t const *sip,
+			 int (*process)(struct proxy_tr *))
+{
+  struct proxy_tr *t = NULL;
+  int status = 500;
+
+  assert(proxy->magic = test_proxy_init);
+
+  t = proxy_tr_new(proxy);
+  if (t) {
+    t->proxy = proxy, t->domain = domain, t->server = irq;
+    t->msg = nta_incoming_getrequest(irq);
+    t->sip = sip_object(t->msg);
+
+    t->method = sip->sip_request->rq_method;
+    t->method_name = sip->sip_request->rq_method_name;
+    t->target = sip->sip_request->rq_url;
+    t->now = nta_incoming_received(irq, NULL);
+
+    if (t->method != sip_method_ack && t->method != sip_method_cancel)
+      nta_incoming_bind(irq, proxy_ack_cancel, t);
+
+    if (domain && domain->prefs.record_route)
+      t->rr = 1;
+
+    if (process(t) < 200)
+      return 0;
+
+    proxy_tr_destroy(t);
+  }
+  else {
+    nta_incoming_treply(irq, SIP_500_INTERNAL_SERVER_ERROR, TAG_END());
+  }
+
+  return status;
+}
+
+/** Forward request */
+static int proxy_transaction(struct proxy_tr *t)
+{
+  if (originating_transaction(t))
+    return t->status;
+
+  if (validate_transaction(t))
+    return t->status;
+
+  if (session_timers(t))
+    return t->status;
+
+  if (t->domain)
+    return incoming_transaction(t);
+
+  return target_transaction(t, t->target, NULL);
+}
+
+static int respond_transaction(struct proxy_tr *t,
+			       int status, char const *phrase,
+			       tag_type_t tag, tag_value_t value,
+			       ...)
+{
+  ta_list ta;
+  void *info = NULL, *response = NULL;
+
+  ta_start(ta, tag, value);
+
+  if (t->as)
+    info = t->as->as_info, response = t->as->as_response;
+  
+  if (nta_incoming_treply(t->server, t->status = status, phrase, 
+			  SIPTAG_HEADER(info),
+			  SIPTAG_HEADER(response),
+			  ta_tags(ta)) < 0)
+    t->status = status = 500;
+
+  ta_end(ta);
+  
+  return status;
+}
+
+static int originating_transaction(struct proxy_tr *t)
+{
+  struct domain *o;
+  char const *host;
+
+  host = t->sip->sip_from->a_url->url_host;
+  if (!host)
+    return 0;
+
+  for (o = t->proxy->domains; o; o = o->next)
+    if (strcasecmp(host, o->uri->url_host) == 0)
+      break;
+
+  t->origin = o;
+
+  if (o && o->auth && o->prefs.authorize) {
+    t->am = o->auth;
+    t->realm = o->prefs.authorize;
+    t->use_auth = 407;
+  }
+
+  if (o && o->prefs.record_route)
+    t->rr = 1;
+
+  return 0;
+}
+
+static int validate_transaction(struct proxy_tr *t)
+{
+  sip_max_forwards_t *mf;
+
+  mf = t->sip->sip_max_forwards;
+
+  if (mf && mf->mf_count <= 1) {
+    if (t->method == sip_method_options)
+      return process_options(t);
+
+    return respond_transaction(t, SIP_483_TOO_MANY_HOPS, TAG_END());
+  }
+
+  /* Remove our routes */
+  while (t->sip->sip_route && 
+	 url_has_param(t->sip->sip_route->r_url, "lr") && 
+	 (url_cmp(t->proxy->lr->r_url, t->sip->sip_route->r_url) == 0 ||
+	  url_cmp(t->proxy->rr_uri, t->sip->sip_route->r_url) == 0)) {
+    sip_route_remove(t->msg, t->sip);
+    /* add record-route also to the forwarded request  */
+  }
+
+  if (t->use_auth)
+    return challenge_transaction(t);
+  
+  return 0;
+}
+
+static int session_timers(struct proxy_tr *t)
+{
+  sip_t *sip = t->sip;
+  sip_session_expires_t *x = NULL, x0[1];
+  sip_min_se_t *min_se = NULL, min_se0[1];
+  char const *require = NULL;
+
+  if (t->method == sip_method_invite) {
+    if (t->proxy->prefs.min_se) {
+      if (!sip->sip_min_se || 
+	  sip->sip_min_se->min_delta < t->proxy->prefs.min_se) {
+	min_se = sip_min_se_init(min_se0);
+	min_se->min_delta = t->proxy->prefs.min_se;
+      }
+
+      if (sip->sip_session_expires
+	  && sip->sip_session_expires->x_delta < t->proxy->prefs.min_se
+	  && sip_has_supported(sip->sip_supported, "timer")) {
+	if (min_se == NULL)
+	  min_se = sip->sip_min_se; assert(min_se);
+	return respond_transaction(t, SIP_422_SESSION_TIMER_TOO_SMALL,
+				   SIPTAG_MIN_SE(min_se),
+				   TAG_END());
+      }
+    }
+
+    if (t->proxy->prefs.session_expires) {
+      if (!sip->sip_session_expires ||
+	  sip->sip_session_expires->x_delta > t->proxy->prefs.session_expires) {
+	x = sip_session_expires_init(x0);
+	x->x_delta = t->proxy->prefs.session_expires;
+	if (!sip_has_supported(sip->sip_supported, "timer"))
+	  require = "timer";
+      }
+    }
+
+    if (x || min_se || require)
+      sip_add_tl(t->msg, t->sip,
+		 SIPTAG_REQUIRE_STR(require),
+		 SIPTAG_MIN_SE(min_se),
+		 SIPTAG_SESSION_EXPIRES(x),
+		 TAG_END());
+  }
+
+  return 0;
+}
+
+static int incoming_transaction(struct proxy_tr *t)
+{
+  struct registration_entry *e;
+  struct binding *b;
+
+#if 0
+  if (sip->sip_request->rq_method == sip_method_register) 
+    return process_register(proxy, irq, sip);
+#endif
+
+  t->entry = e = registration_entry_find(t->domain, t->target);
+  if (e == NULL)
+    return respond_transaction(t, SIP_404_NOT_FOUND, TAG_END());
+
+  for (b = e->bindings; b; b = b->next) {
+    if (binding_is_active(b)) 
+      target_transaction(t, b->contact->m_url, b->tport);
+
+    if (t->clients)		/* XXX - enable forking */
+      break;
+  }
+
+  if (t->clients != NULL)
+    return 0;
+
+  return respond_transaction(t, SIP_480_TEMPORARILY_UNAVAILABLE, TAG_END());
+}
+
+static int target_transaction(struct proxy_tr *t,
+			      url_t const *target,
+			      tport_t *tport)
+{
+  struct client_tr *c = su_zalloc(t->proxy->home, sizeof *c);
+  int stateless = t->method == sip_method_ack;
+
+  if (c == NULL)
+    return 500;
+
+  c->t = t;
+  c->msg = msg_copy(t->msg);
+  c->sip = sip_object(c->msg);
+
+  if (c->msg)
+    c->rq = sip_request_create(msg_home(c->msg),
+			       t->method, t->method_name,
+			       (url_string_t *)target,
+			       NULL);
+
+  msg_header_insert(c->msg, (msg_pub_t *)c->sip, (msg_header_t *)c->rq);
+
+  if (t->rr) {
+    sip_record_route_t rr[1];
+
+    if (t->proxy->rr_uri) {
+      *sip_record_route_init(rr)->r_url = *t->proxy->rr_uri;
+      msg_header_add_dup(c->msg, (msg_pub_t *)c->sip, (msg_header_t *)rr);
+    }
+    else if (t->proxy->lr) {
+      *sip_record_route_init(rr)->r_url = *t->proxy->lr->r_url;
+      msg_header_add_dup(c->msg, (msg_pub_t *)c->sip, (msg_header_t *)rr);
+    }
+  }
+
+  if (c->rq)
+    /* Forward request */
+    c->client = nta_outgoing_mcreate(t->proxy->agent,
+				     proxy_response, c,
+				     NULL,
+				     msg_ref_create(c->msg),
+				     NTATAG_TPORT(tport),
+				     NTATAG_STATELESS(stateless),
+				     TAG_END());
+
+  if (!c->client) {
+    msg_destroy(c->msg);
+    su_free(t->proxy->home, c);
+    return 500;
+  }
+
+  client_tr_insert(&t->clients, c);
+
+  return stateless ? 200 : 0;
+}
+
+static int challenge_transaction(struct proxy_tr *t)
+{
+  auth_status_t *as;
+  sip_t *sip = t->sip;
+
+  assert(t->am);
+
+  t->as = as = auth_status_new(t->proxy->home);
+  if (!as)
+    return respond_transaction(t, SIP_500_INTERNAL_SERVER_ERROR, TAG_END());
+
+  as->as_method = sip->sip_request->rq_method_name;
+  as->as_source = msg_addrinfo(t->msg);
+  as->as_realm = t->realm;
+
+  as->as_user_uri = sip->sip_from->a_url;
+  as->as_display = sip->sip_from->a_display;
+
+  if (sip->sip_payload)
+    as->as_body = sip->sip_payload->pl_data,
+      as->as_bodylen = sip->sip_payload->pl_len;
+
+  if (t->use_auth == 401)
+    auth_mod_check_client(t->am, as, sip->sip_authorization,
+			  registrar_challenger);
+  else
+    auth_mod_check_client(t->am, as, sip->sip_proxy_authorization,
+			  proxy_challenger);
+
+  if (as->as_status)
+    return respond_transaction(t, as->as_status, as->as_phrase, TAG_END());
+
+  if (as->as_match)
+    msg_header_remove(t->msg, (msg_pub_t *)sip, as->as_match);
+
+  return 0;
+}		      
+
+int proxy_ack_cancel(struct proxy_tr *t,
+		     nta_incoming_t *irq,
+		     sip_t const *sip)
+{
+  struct client_tr *c;
+  int status;
+
+  if (sip == NULL) {		/* timeout */
+    proxy_tr_destroy(t);	
+    return 0;
+  }
+
+  if (sip->sip_request->rq_method != sip_method_cancel)
+    return 500;
+
+  status = 200;
+
+  for (c = t->clients; c; c = c->next) {
+    if (c->client && c->status < 200)
+      /*
+       * We don't care about response to CANCEL (or ACK)
+       * so we give NULL as callback pointer (and nta immediately 
+       * destroys transaction object or marks it disposable)
+       */
+      if (nta_outgoing_tcancel(c->client, NULL, NULL, TAG_END()) == NULL)
+	status = 500;
+  }
+
+  return status;
+}
+
+int proxy_response(struct client_tr *c,
+		   nta_outgoing_t *client,
+		   sip_t const *sip)
+{
+  int final, timeout = 0;
+
+  assert(c->t);
+
+  if (sip) {
+    msg_t *response = nta_outgoing_getresponse(client);
+    if (c->t->method == sip_method_invite)
+      final = sip->sip_status->st_status >= 300, 
+	timeout = sip->sip_status->st_status >= 200;
+    else
+      final = sip->sip_status->st_status >= 200;
+    sip_via_remove(response, sip_object(response));
+    nta_incoming_mreply(c->t->server, response);
+  }
+  else {
+    int status = nta_outgoing_status(c->client);
+    char const *phrase;
+
+    if (status < 300 || status > 699)
+      status = 500;
+    phrase = sip_status_phrase(status);
+    respond_transaction(c->t, status, phrase, TAG_END());
+    final = 1;
+  }
+
+  if (final)
+    proxy_tr_destroy(c->t);
+  else if (timeout)
+    proxy_tr_timeout(c->t);
+
+  return 0;
+}
+
+int proxy_late_response(struct client_tr *c,
+			nta_outgoing_t *client,
+			sip_t const *sip)
+{
+  assert(c->t);
+
+  if (sip &&
+      sip->sip_status->st_status >= 200 && 
+      sip->sip_status->st_status < 300) {
+    msg_t *response = nta_outgoing_getresponse(client);
+    sip_via_remove(response, sip_object(response));
+    nta_incoming_mreply(c->t->server, response);
+  }
+
+  return 0;
+}
+
+static void proxy_tr_remove_late(su_root_magic_t *magic,
+				 su_timer_t *timer,
+				 struct proxy_tr *t)
+{
+  proxy_tr_destroy(t);
+}
+
+
+/** Proxy only late responses 
+ *
+ * Keeping the invite transactions live
+ */
+static void proxy_tr_timeout(struct proxy_tr *t)
+{
+  struct client_tr *c;
+
+  if (t == t->proxy->stateless)
+    return;
+
+  for (c = t->clients; c; c = c->next) {
+    if (c->client && c->status < 300) {
+      nta_outgoing_bind(c->client, proxy_late_response, c);
+      if (c->status < 200) {
+	nta_outgoing_tcancel(c->client, NULL, NULL, TAG_END());
+      }
+    }
+  }
+
+  t->timer = su_timer_create(su_root_task(t->proxy->root), t->proxy->prefs.t1x64);
+  if (su_timer_set(t->timer, proxy_tr_remove_late, t) < 0) {
+    proxy_tr_destroy(t);
+  }
+}
+
+struct proxy_tr *
+proxy_tr_new(struct proxy *proxy)
+{
+  struct proxy_tr *t;
+
+  t = su_zalloc(proxy->home, sizeof *t);
+  if (t) {
+    t->proxy = proxy;
+    proxy_tr_insert(&proxy->transactions, t);
+  }
+  return t;
+}
+
+static
+void proxy_tr_destroy(struct proxy_tr *t)
+{
+  struct client_tr *c;
+
+  if (t == t->proxy->stateless)
+    return;
+
+  proxy_tr_remove(t);
+
+  if (t->as)
+    su_home_unref(t->as->as_home), t->as = NULL;
+  
+  while (t->clients) {
+    client_tr_remove(c = t->clients);
+    nta_outgoing_destroy(c->client), c->client = NULL;
+    msg_destroy(c->msg), c->msg = NULL;
+    su_free(t->proxy->home, c);
+  }
+
+  su_timer_destroy(t->timer), t->timer = NULL;
+
+  msg_destroy(t->msg);
+
+  nta_incoming_destroy(t->server);
+
+  su_free(t->proxy->home, t);
+}
+
+LIST_BODIES(static, proxy_tr, struct proxy_tr, next, prev);
+
+/* ---------------------------------------------------------------------- */
+
+static int process_options(struct proxy_tr *t)
+{
+  return respond_transaction(t, SIP_200_OK,
+			     SIPTAG_CONTACT(t->proxy->transport_contacts),
+			     TAG_END());
+}
+
+/* ---------------------------------------------------------------------- */
+
+static int check_received_contact(struct proxy_tr *t);
+static int validate_contacts(struct proxy_tr *t);
+static int check_out_of_order(struct proxy_tr *t);
+static int update_bindings(struct proxy_tr *t);
+
+int process_register(struct proxy_tr *t)
+{
+  /* This is before authentication because we want to be bug-compatible */
+  if (check_received_contact(t))
+    return t->status;
+
+  if (t->domain->auth) {
+    t->am = t->domain->auth, t->use_auth = 401;
+    if (t->domain->prefs.authorize)
+      t->realm = t->domain->prefs.authorize;
+    if (challenge_transaction(t))
+      return t->status;
+  }
+
+  if (validate_contacts(t))
+    return t->status;
+
+  t->entry = registration_entry_find(t->domain, t->sip->sip_to->a_url);
+
+  if (check_out_of_order(t))
+    return t->status;
+  
+  return update_bindings(t);
+}
+
+static int check_received_contact(struct proxy_tr *t)
+{
+  sip_t *sip = t->sip;
+  sip_contact_t *m = sip->sip_contact;
+  sip_via_t *v = sip->sip_via;
+
+  if (m && v && v->v_received && m->m_url->url_host
+      && strcasecmp(v->v_received, m->m_url->url_host) 
+      && host_is_ip_address(m->m_url->url_host))
+    return respond_transaction(t, 406, "Unacceptable Contact", TAG_END());
+
+  return 0;
+}
+
+/* Validate expiration times */
+static int validate_contacts(struct proxy_tr *t)
+{
+  sip_contact_t const *m = t->sip->sip_contact;
+  sip_expires_t const *ex = t->sip->sip_expires;
+  sip_date_t const *date = t->sip->sip_date;
+  sip_time_t expires;
+
+  if (m && m->m_url->url_type == url_any) {
+    if (!ex || ex->ex_delta || ex->ex_time || m->m_next)
+      return respond_transaction(t, SIP_400_BAD_REQUEST, TAG_END());
+    return 0;
+  }
+
+  for (; m; m = m->m_next) {
+    expires = sip_contact_expires(m, ex, date, t->domain->prefs.expires, t->now);
+    
+    if (expires > 0 && expires < t->domain->prefs.min_expires) {
+      sip_min_expires_t me[1];
+
+      sip_min_expires_init(me)->me_delta = t->domain->prefs.min_expires;
+
+      return respond_transaction(t, SIP_423_INTERVAL_TOO_BRIEF,
+				 SIPTAG_MIN_EXPIRES(me),
+				 TAG_END());
+    }
+  }
+
+  return 0;
+}
+
+/** Check for out-of-order register request */
+static int check_out_of_order(struct proxy_tr *t)
+{
+  struct binding const *b;
+  sip_call_id_t const *id = t->sip->sip_call_id;
+  uint32_t cseq = t->sip->sip_cseq->cs_seq;
+  sip_contact_t *m;
+
+  if (t->entry == NULL || !t->sip->sip_contact)
+    return 0;
+
+  /* RFC 3261 subsection 10.3 step 6 and step 7 (p. 66): */
+  /* Check for reordered register requests */
+  for (b = t->entry->bindings; b; b = b->next) {
+    if (binding_is_active(b) &&
+	strcmp(id->i_id, b->call_id->i_id) == 0 &&
+	cseq <= b->cseq) {
+      for (m = t->sip->sip_contact; m; m = m->m_next) {
+	if (m->m_url->url_type == url_any ||
+	    url_cmp_all(m->m_url, b->contact->m_url) == 0)
+	  return respond_transaction(t, SIP_500_INTERNAL_SERVER_ERROR,
+				     TAG_END());
+      }
+    }
+  }
+
+  return 0;
+}
+
+static struct registration_entry *
+registration_entry_find(struct domain const *d, url_t const *uri)
+{
+  struct registration_entry *e;
+
+  /* Our routing table */
+  for (e = d->entries; e; e = e->next) {
+    if (url_cmp(uri, e->aor) == 0)
+      return e;
+  }
+
+  return NULL;
+}
+
+static struct registration_entry *
+registration_entry_new(struct domain *d, url_t const *aor)
+{
+  struct registration_entry *e;
+
+  if (d == NULL)
+    return NULL;
+
+  e = su_zalloc(d->home, sizeof *e); 
+  if (!e) 
+    return NULL;
+
+  e->domain = d;
+  e->aor = url_hdup(d->home, aor);
+  if (!e->aor) {
+    su_free(d->home, e);
+    return NULL;
+  }
+
+  registration_entry_insert(&d->entries, e);
+
+  return e;
+}
+
+static void
+registration_entry_destroy(struct registration_entry *e)
+{
+  if (e) {
+    registration_entry_remove(e);
+    su_free(e->domain->home, e->aor);
+    while (e->bindings)
+      binding_destroy(e->domain->home, e->bindings);
+    msg_header_free(e->domain->home, (void *)e->contacts);
+    su_free(e->domain->home, e);
+  }
+}
+
+sip_contact_t *entry_contacts(struct registration_entry *entry)
+{
+  return entry ? entry->contacts : NULL;
+}
+
+LIST_BODIES(static, registration_entry, struct registration_entry, next, prev);
+
+/* ---------------------------------------------------------------------- */
+/* Bindings */
+
+static
+struct binding *binding_new(su_home_t *home, 
+			    sip_contact_t *contact,
+			    tport_t *tport,
+			    sip_call_id_t const *call_id,
+			    uint32_t cseq,
+			    sip_time_t registered, 
+			    sip_time_t expires)
+{
+  struct binding *b;
+  
+  b = su_zalloc(home, sizeof *b);
+
+  if (b) {
+    sip_contact_t m[1];
+    *m = *contact; m->m_next = NULL;
+
+    b->contact = sip_contact_dup(home, m);
+    b->tport = tport_ref(tport);
+    b->call_id = sip_call_id_dup(home, call_id);
+    b->cseq = cseq;
+    b->registered = registered;
+    b->expires = expires;
+
+    if (!b->contact || !b->call_id)
+      binding_destroy(home, b), b = NULL;
+
+    if (b)
+      msg_header_remove_param(b->contact->m_common, "expires");
+  }
+  
+  return b;
+}
+
+static
+void binding_destroy(su_home_t *home, struct binding *b)
+{
+  if (b->prev) {
+    if ((*b->prev = b->next))
+      b->next->prev = b->prev;
+  }
+  msg_header_free(home, (void *)b->contact);
+  msg_header_free(home, (void *)b->call_id);
+  tport_unref(b->tport);
+  su_free(home, b);
+}
+
+static int update_bindings(struct proxy_tr *t)
+{
+  struct domain *d = t->domain;
+  struct binding *b, *old, *next, *last, *bindings = NULL, **bb = &bindings;
+  sip_contact_t *m;
+  sip_call_id_t const *id = t->sip->sip_call_id;
+  uint32_t cseq = t->sip->sip_cseq->cs_seq;
+  sip_expires_t *ex = t->sip->sip_expires;
+  sip_date_t *date = t->sip->sip_date;
+  sip_time_t expires;
+  tport_t *tport = NULL;
+  sip_contact_t *contacts = NULL, **mm = &contacts; 
+  void *tbf;
+
+  if (t->sip->sip_contact == NULL) {
+    if (t->entry)
+      contacts = t->entry->contacts;
+    goto ok200;
+  }
+
+  if (t->entry == NULL)
+    t->entry = registration_entry_new(d, t->sip->sip_to->a_url);
+  if (t->entry == NULL)
+    return respond_transaction(t, SIP_500_INTERNAL_SERVER_ERROR, TAG_END());
+
+  if (d->prefs.outbound_tcp && 
+      str0casecmp(t->sip->sip_via->v_protocol, sip_transport_tcp) == 0)
+    tport = nta_incoming_transport(t->proxy->agent, t->server, NULL);
+
+  /* Create new bindings */
+  for (m = t->sip->sip_contact; m; m = m->m_next) {
+    if (m->m_url->url_type == url_any)
+      break;
+    
+    expires = sip_contact_expires(m, ex, date, d->prefs.expires, t->now);
+
+    if (expires > d->prefs.max_expires)
+      expires = d->prefs.max_expires;
+
+    msg_header_remove_param(m->m_common, "expires");
+
+    b = binding_new(d->home, m, tport, id, cseq, t->now, t->now + expires);
+    if (!b)
+      break;
+
+    *bb = b, b->prev = bb, bb = &b->next;
+  }
+
+  tport_unref(tport);
+
+  last = NULL;
+
+  if (m == NULL) {
+    /* Merge new bindings with old ones */
+    for (old = t->entry->bindings; old; old = next) {
+      next = old->next;
+
+      for (b = bindings; b != last; b = b->next) {
+	if (url_cmp_all(old->contact->m_url, b->contact->m_url) != 0) 
+	  continue;
+
+	if (strcmp(old->call_id->i_id, b->call_id->i_id) == 0) {
+	  b->registered = old->registered;
+	}
+	binding_destroy(d->home, old);
+	break;
+      }
+    }
+
+    for (bb = &t->entry->bindings; *bb; bb = &(*bb)->next)
+      ;
+
+    if ((*bb = bindings))
+      bindings->prev = bb;
+  }
+  else if (m->m_url->url_type == url_any) {
+    /* Unregister all */
+    for (b = t->entry->bindings; b; b = b->next) {
+      b->expires = t->now;
+    }
+  }
+  else {
+    /* Infernal error */
+
+    for (old = bindings; old; old = next) {
+      next = old->next;
+      binding_destroy(d->home, old);
+    }
+
+    return respond_transaction(t, SIP_500_INTERNAL_SERVER_ERROR, TAG_END());
+  }
+
+  for (b = t->entry->bindings; b; b = b->next) {
+    char const *expires;
+
+    if (b->expires <= t->now)
+      continue;
+
+    *mm = sip_contact_copy(d->home, b->contact);
+    if (*mm) {
+      expires = su_sprintf(d->home, "expires=%u", 
+			   (unsigned)(b->expires - t->now));
+      msg_header_add_param(d->home, (*mm)->m_common, expires);
+      mm = &(*mm)->m_next;
+    }
+  }
+
+  tbf = t->entry->contacts;
+  t->entry->contacts = contacts;
+  msg_header_free(d->home, tbf);
+
+ ok200:
+  return respond_transaction(t, SIP_200_OK,
+			     SIPTAG_CONTACT(contacts),
+			     TAG_END());
+}
+
+/* ---------------------------------------------------------------------- */
+
+static int close_tports(void *_proxy)
+{
+  struct proxy *p = _proxy;
+  struct domain *d;
+  struct registration_entry *e;
+  struct binding *b;
+  
+  /* Close all outbound transports */
+  for (d = p->domains; d; d = d->next) {
+    for (e = d->entries; e; e = e->next) {
+      for (b = e->bindings; b; b = b->next) {
+	if (b->tport) {
+	  tport_shutdown(b->tport, 1);
+	  tport_unref(b->tport);
+	  b->tport = NULL;
+	}
+      }
+    }
+  }
+  
+  return 0;
+}

Added: freeswitch/trunk/libs/sofia-sip/tests/test_proxy.h
==============================================================================
--- (empty file)
+++ freeswitch/trunk/libs/sofia-sip/tests/test_proxy.h	Wed May 14 15:10:54 2008
@@ -0,0 +1,87 @@
+/*
+ * This file is part of the Sofia-SIP package
+ *
+ * Copyright (C) 2005 Nokia Corporation.
+ *
+ * Contact: Pekka Pessi <pekka.pessi at nokia.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA
+ *
+ */
+
+#ifndef TEST_PROXY_H
+#define TEST_PROXY_H
+
+#include <sofia-sip/su_wait.h>
+#include <sofia-sip/nta.h>
+
+SOFIA_BEGIN_DECLS
+
+struct proxy;
+struct domain;
+
+struct proxy *test_proxy_create(su_root_t *, tag_type_t, tag_value_t, ...);
+
+void test_proxy_destroy(struct proxy *);
+
+url_t const *test_proxy_uri(struct proxy const *);
+
+char const *test_proxy_route_uri(struct proxy const *p,
+				 sip_route_t const **return_route);
+
+struct domain *test_proxy_add_domain(struct proxy *,
+				     url_t const *domain,
+				     tag_type_t, tag_value_t, ...);
+
+void test_proxy_set_logging(struct proxy *, int logging);
+
+void test_proxy_domain_set_expiration(struct domain *,
+				      sip_time_t min_expires, 
+				      sip_time_t expires, 
+				      sip_time_t max_expires);
+
+void test_proxy_domain_get_expiration(struct domain *,
+				      sip_time_t *return_min_expires, 
+				      sip_time_t *return_expires, 
+				      sip_time_t *return_max_expires);
+
+void test_proxy_set_session_timer(struct proxy *p,
+				  sip_time_t session_expires, 
+				  sip_time_t min_se);
+
+void test_proxy_get_session_timer(struct proxy *p,
+				  sip_time_t *return_session_expires,
+				  sip_time_t *return_min_se);
+
+int test_proxy_domain_set_authorize(struct domain *, char const *realm);
+int test_proxy_domain_get_authorize(struct domain *,
+				    char const **return_realm);
+
+void test_proxy_domain_set_outbound(struct domain *d,
+				    int use_outbound);
+void test_proxy_domain_get_outbound(struct domain *d,
+				    int *return_use_outbound);
+
+void test_proxy_domain_set_record_route(struct domain *d,
+					int use_record_route);
+void test_proxy_domain_get_record_route(struct domain *d,
+					int *return_use_record_route);
+
+int test_proxy_close_tports(struct proxy *p);
+
+SOFIA_END_DECLS
+
+#endif

Added: freeswitch/trunk/libs/sofia-sip/tests/test_refer.c
==============================================================================
--- (empty file)
+++ freeswitch/trunk/libs/sofia-sip/tests/test_refer.c	Wed May 14 15:10:54 2008
@@ -0,0 +1,852 @@
+/*
+ * This file is part of the Sofia-SIP package
+ *
+ * Copyright (C) 2005 Nokia Corporation.
+ *
+ * Contact: Pekka Pessi <pekka.pessi at nokia.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA
+ *
+ */
+
+/**@CFILE test_nua_re_invite.c
+ * @brief Test re_inviteing, outbound, nat traversal.
+ *
+ * @author Pekka Pessi <Pekka.Pessi at nokia.com>
+ * @author Martti Mela <Martti Mela at nokia.com>
+ *
+ * @date Created: Wed Aug 17 12:12:12 EEST 2005 ppessi
+ */
+
+#include "config.h"
+
+#include "test_nua.h"
+#include <sofia-sip/su_tag_class.h>
+
+#if HAVE_FUNC
+#elif HAVE_FUNCTION
+#define __func__ __FUNCTION__
+#else
+#define __func__ "test_call_hold"
+#endif
+
+/* ======================================================================== */
+/* NUA-9 tests: REFER */
+
+int test_refer0(struct context *ctx, char const *tests,
+		int refer_with_id, int notify_by_appl);
+int notify_until_terminated(CONDITION_PARAMS);
+int test_challenge_refer(struct context *ctx);
+
+int test_refer(struct context *ctx)
+{
+  /* test twice, once without id and once with id */
+  return
+    test_challenge_refer(ctx) ||
+    test_refer0(ctx, "NUA-9.1", 0, 0) ||
+    test_refer0(ctx, "NUA-9.2", 1, 0) ||
+    test_refer0(ctx, "NUA-9.3", 0, 1) ||
+    test_refer0(ctx, "NUA-9.4", 1, 1);
+}
+
+/* Referred call:
+
+   A			B
+   |			|
+   |-------INVITE------>|
+   |<----100 Trying-----|
+   |			|
+   |<----180 Ringing----|
+   |			|
+   |<------200 OK-------|
+   |--------ACK-------->|
+   |			|
+   |<------REFER--------|
+   |-------200 OK------>|			C
+  [|-------NOTIFY------>|]			|
+  [|<------200 OK-------|]			|
+   |			|			|
+   |			|			|
+   |<-----SUBSCRIBE-----|                       |
+   |-------200 OK------>|			|
+   |			|			|
+   |			|			|
+   |-----------------INVITE-------------------->|
+   |			|			|
+   |<------------------180----------------------|
+   |-------NOTIFY------>|			|
+   |<------200 OK-------|			|
+   |			|			|
+   |<------------------200----------------------|
+   |-------NOTIFY------>|			|
+   |<------200 OK-------|			|
+   |-------------------ACK--------------------->|
+   |			|			|
+   |--------BYE-------->|			|
+   |<------200 OK-------|			|
+   |			X			|
+   |			 			|
+   |-------------------BYE--------------------->|
+   |<------------------200----------------------|
+   |						|
+
+*/
+
+int test_refer0(struct context *ctx, char const *tests,
+		int refer_with_id, int notify_by_appl)
+{
+  BEGIN();
+
+  struct endpoint *a = &ctx->a,  *b = &ctx->b, *c = &ctx->c;
+  struct call *a_call = a->call, *b_call = b->call, *c_call = c->call;
+  struct call *a_refer, *a_c2, *b_refer;
+  struct eventlist *a_revents, *b_revents;
+  struct event *e, *notify_e;
+  sip_t const *sip;
+  sip_event_t const *a_event, *b_event;
+  sip_refer_to_t const *refer_to;
+  sip_referred_by_t const *referred_by;
+
+  sip_refer_to_t r0[1];
+  sip_to_t to[1];
+
+  su_home_t tmphome[SU_HOME_AUTO_SIZE(16384)];
+
+  su_home_auto(tmphome, sizeof(tmphome));
+
+  if (print_headings)
+    printf("TEST %s: REFER: refer A to C%s%s%s\n", tests,
+	   refer_with_id ? " with Event id" : "",
+	   refer_with_id && !notify_by_appl ? " and" : "",
+	   !notify_by_appl ? " nua generating the NOTIFYs" : "");
+
+  if (print_headings)
+    printf("TEST %s.1: REFER: make a call between A and B\n", tests);
+
+  /* Do (not) include id with first implicit Event: refer */
+  nua_set_params(ctx->a.nua, NUTAG_REFER_WITH_ID(refer_with_id), TAG_END());
+  run_a_until(ctx, nua_r_set_params, until_final_response);
+
+  if (refer_with_id) {
+    TEST_1(a_refer = calloc(1, (sizeof *a_refer) + (sizeof *a_refer->events)));
+    call_init(a_refer);
+    a_refer->events = (void *)(a_refer + 1);
+    eventlist_init(a_refer->events);
+
+    a_call->next = a_refer;
+    a_revents = a_refer->events;
+
+    TEST_1(b_refer = calloc(1, (sizeof *b_refer) + (sizeof *b_refer->events)));
+    call_init(b_refer);
+    b_refer->events = (void *)(b_refer + 1);
+    eventlist_init(b_refer->events);
+
+    b_call->next = b_refer;
+    b_revents = b_refer->events;
+  }
+  else {
+    a_refer = a_call, b_refer = b_call;
+    a_revents = a->events, b_revents = b->events;
+  }
+
+  TEST_1(a_c2 = calloc(1, (sizeof *a_c2) + (sizeof *a_c2->events)));
+  call_init(a_c2);
+  a_c2->events = (void *)(a_c2 + 1);
+  eventlist_init(a_c2->events);
+
+  a_call->sdp = "m=audio 5008 RTP/AVP 8";
+  b_call->sdp = "m=audio 5010 RTP/AVP 0 8";
+  a_c2->sdp   = "m=audio 5012 RTP/AVP 8";
+  c_call->sdp = "m=audio 5014 RTP/AVP 0 8";
+
+  TEST_1(a_call->nh = nua_handle(a->nua, a_call, SIPTAG_TO(b->to), TAG_END()));
+
+  INVITE(a, a_call, a_call->nh,
+	 TAG_IF(!ctx->proxy_tests, NUTAG_URL(b->contact->m_url)),
+	 SOATAG_USER_SDP_STR(a_call->sdp),
+	 TAG_END());
+
+  run_ab_until(ctx, -1, until_ready, -1, accept_call);
+
+  /* Client transitions:
+     INIT -(C1)-> CALLING: nua_invite(), nua_i_state
+     CALLING -(C2)-> PROCEEDING: nua_r_invite, nua_i_state
+     PROCEEDING -(C3+C4)-> READY: nua_r_invite, nua_i_state
+  */
+  TEST_1(e = a->events->head); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_calling); /* CALLING */
+  TEST_1(is_offer_sent(e->data->e_tags));
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_r_invite);
+  TEST(e->data->e_status, 180);
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_proceeding); /* PROCEEDING */
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_r_invite);
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_ready); /* READY */
+  TEST_1(is_answer_recv(e->data->e_tags));
+  TEST_1(!e->next);
+  free_events_in_list(ctx, a->events);
+
+  /*
+   Server transitions:
+   INIT -(S1)-> RECEIVED: nua_i_invite, nua_i_state
+   RECEIVED -(S2a)-> EARLY: nua_respond(), nua_i_state
+   EARLY -(S3b)-> COMPLETED: nua_respond(), nua_i_state
+   COMPLETED -(S4)-> READY: nua_i_ack, nua_i_state
+  */
+  TEST_1(e = b->events->head); TEST_E(e->data->e_event, nua_i_invite);
+  TEST(e->data->e_status, 100);
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_received); /* RECEIVED */
+  TEST_1(is_offer_recv(e->data->e_tags));
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_early); /* EARLY */
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_completed); /* COMPLETED */
+  TEST_1(is_answer_sent(e->data->e_tags));
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_ack);
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_ready); /* READY */
+  TEST_1(!e->next);
+  free_events_in_list(ctx, b->events);
+
+  if (print_headings)
+    printf("TEST %s.1: PASSED\n", tests);
+
+  /* ---------------------------------------------------------------------- */
+  /* REFER (initial NOTIFY is no more sent unless REFER creates a new dialog)
+   A                    B
+   |<------REFER--------|
+   |-------200 OK------>|
+  [|-------NOTIFY------>|]			|
+  [|<------200 OK-------|]			|
+   */
+
+  if (print_headings)
+    printf("TEST %s.2: B refers A to C\n", tests);
+
+  if (b_refer != b_call)
+    TEST_1(b_refer->nh = 
+	   nua_handle(b->nua, b_refer, SIPTAG_TO(a->to), TAG_END()));
+
+  *sip_refer_to_init(r0)->r_url = *c->contact->m_url;
+  r0->r_url->url_headers = "subject=referred";
+  r0->r_display = "C";
+
+  REFER(b, b_refer, b_refer->nh, SIPTAG_REFER_TO(r0),
+	TAG_IF(!ctx->proxy_tests && b_refer != b_call,
+	       NUTAG_URL(a->contact->m_url)),
+	TAG_END());
+  run_ab_until(ctx, -1, save_until_received,
+	       -1, save_until_final_response);
+
+  /*
+    Events in A:
+    nua_i_refer
+  */
+  TEST_1(e = a_revents->head); TEST_E(e->data->e_event, nua_i_refer);
+  TEST(e->data->e_status, 202);
+  a_event = NULL;
+  TEST(tl_gets(e->data->e_tags,
+	       NUTAG_REFER_EVENT_REF(a_event),
+	       TAG_END()), 1);
+  TEST_1(a_event); TEST_1(a_event = sip_event_dup(tmphome, a_event));
+  TEST_1(sip = sip_object(e->data->e_msg));
+  TEST_1(sip->sip_refer_to);
+  TEST_1(refer_to = sip_refer_to_dup(tmphome, sip->sip_refer_to));
+  TEST_1(sip->sip_referred_by);
+  TEST_1(referred_by = sip_referred_by_dup(tmphome, sip->sip_referred_by));
+
+  /*
+     Events in B after nua_refer():
+     nua_r_refer
+  */
+  TEST_1(e = b_revents->head); TEST_E(e->data->e_event, nua_r_refer);
+  TEST(e->data->e_status, 100);
+  TEST(tl_gets(e->data->e_tags,
+	       NUTAG_REFER_EVENT_REF(b_event),
+	       TAG_END()), 1);
+  TEST_1(b_event); TEST_1(b_event->o_id);
+  TEST_1(b_event = sip_event_dup(tmphome, b_event));
+
+  notify_e = NULL;
+
+  TEST_1(e = e->next);
+  if (e->data->e_event == nua_i_notify) {
+    notify_e = e;
+    TEST_1(e = e->next);
+  }
+  TEST_E(e->data->e_event, nua_r_refer);
+  TEST(e->data->e_status, 202);
+  TEST_1(sip = sip_object(e->data->e_msg));
+  TEST_SIZE(strtoul(b_event->o_id, NULL, 10), sip->sip_cseq->cs_seq);
+
+  if (a_refer != a_call) {
+    while (!notify_e) {
+      for (e = b_revents->head; e; e = e->next) {
+	if (e->data->e_event == nua_i_notify) {
+	  notify_e = e;
+	  break;
+	}
+      }
+      if (!notify_e)
+	run_ab_until(ctx, -1, save_until_received, nua_i_notify, save_events);
+    }
+
+    if (a_revents->head->next == NULL)
+      run_a_until(ctx, -1, save_until_received);
+
+    TEST_1(e = a_revents->head->next); TEST_E(e->data->e_event, nua_r_notify);
+    TEST_1(!e->next);
+
+    TEST_1(e = notify_e);
+    TEST_E(e->data->e_event, nua_i_notify);
+    TEST(e->data->e_status, 200);
+    TEST_1(sip = sip_object(e->data->e_msg));
+    TEST_1(sip->sip_event);
+    if (refer_with_id)
+      TEST_S(sip->sip_event->o_id, b_event->o_id);
+    TEST_1(sip->sip_subscription_state);
+    TEST_S(sip->sip_subscription_state->ss_substate, "pending");
+    TEST_1(sip->sip_payload && sip->sip_payload->pl_data);
+    TEST_M(sip->sip_payload->pl_data, "SIP/2.0 100 Trying\r\n",
+	   sip->sip_payload->pl_len);
+  }
+
+  free_events_in_list(ctx, a_revents);
+  free_events_in_list(ctx, b_revents);
+
+  if (print_headings)
+    printf("TEST %s.2: PASSED\n", tests);
+
+#if 0
+  /* ---------------------------------------------------------------------- */
+  /*
+   A                    B
+   |<-----SUBSCRIBE-----|
+   |-------200 OK------>|
+   |-------NOTIFY------>|			|
+   |<------200 OK-------|			|
+   */
+
+  if (print_headings)
+    printf("TEST %s.3: extend expiration time for implied subscription\n", tests);
+
+  SUBSCRIBE(b, b_call, b_call->nh,
+	    SIPTAG_EVENT(b_event),
+	    SIPTAG_EXPIRES_STR("3600"),
+	    TAG_END());
+  run_ab_until(ctx, -1, save_until_final_response,
+	       -1, save_until_final_response);
+
+  /*
+    Events in A:
+    nua_i_subscribe, nua_r_notify
+  */
+  TEST_1(e = a->events->head); 
+  if (e->data->e_event == nua_r_notify)
+    TEST_1(e = e->next);
+  TEST_E(e->data->e_event, nua_i_subscribe);
+  TEST(e->data->e_status, 202);
+  TEST_1(sip = sip_object(e->data->e_msg));
+  TEST_1(sip->sip_event);
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_r_notify);
+  TEST_1(!e->next);
+  free_events_in_list(ctx, a->events);
+
+  /*
+     Events in B after nua_subscribe():
+     nua_r_subscribe, nua_i_notify
+  */
+  TEST_1(e = b->events->head); 
+  if (e->data->e_event == nua_i_notify) {
+  TEST(e->data->e_status, 200);
+  TEST_1(sip = sip_object(e->data->e_msg));
+  TEST_1(sip->sip_event);
+  if (refer_with_id)
+    TEST_S(sip->sip_event->o_id, b_event->o_id);
+  TEST_1(sip->sip_subscription_state);
+  TEST_S(sip->sip_subscription_state->ss_substate, "pending");
+  TEST_1(sip->sip_payload && sip->sip_payload->pl_data);
+  TEST_M(sip->sip_payload->pl_data, "SIP/2.0 100 Trying\r\n",
+	 sip->sip_payload->pl_len);
+  TEST_1(e = e->next);
+  }
+  TEST_E(e->data->e_event, nua_r_subscribe);
+  TEST(e->data->e_status, 202);
+  if (!e->next)
+    run_b_until(ctx, -1, save_until_received);
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_notify);
+  TEST(e->data->e_status, 200);
+  TEST_1(sip = sip_object(e->data->e_msg));
+  TEST_1(sip->sip_event);
+  if (refer_with_id)
+    TEST_S(sip->sip_event->o_id, b_event->o_id);
+  TEST_1(sip->sip_subscription_state);
+  TEST_S(sip->sip_subscription_state->ss_substate, "pending");
+  TEST_1(!e->next);
+  free_events_in_list(ctx, b->events);
+
+  if (print_headings)
+    printf("TEST %s.3: PASSED\n", tests);
+#endif
+
+  /* ---------------------------------------------------------------------- */
+  /*
+   A                    B                       C
+   |			|			|
+   |-----------------INVITE-------------------->|
+   |			|			|
+  XXX			|			|
+   | 			|			|
+   |<------------------180----------------------|
+   |-------NOTIFY------>|			|
+   |<------200 OK-------|			|
+   | 			|			|
+  XXX			|			|
+   |			|			|
+   |<------------------200----------------------|
+   |-------NOTIFY------>|			|
+   |<------200 OK-------|			|
+   |-------------------ACK--------------------->|
+   */
+
+  if (print_headings)
+    printf("TEST %s.4: A invites C\n", tests);
+
+  *sip_to_init(to)->a_url = *refer_to->r_url;
+  to->a_display = refer_to->r_display;
+
+  a_refer->next = a_c2;
+
+  TEST_1(a_c2->nh = nua_handle(a->nua, a_c2, SIPTAG_TO(to), TAG_END()));
+
+  INVITE(a, a_c2, a_c2->nh, /* NUTAG_URL(refer_to->r_url), */
+	 TAG_IF(!notify_by_appl, NUTAG_REFER_EVENT(a_event)),
+	 TAG_IF(!notify_by_appl, NUTAG_NOTIFY_REFER(a_refer->nh)),
+	 SOATAG_USER_SDP_STR(a_c2->sdp),
+	 SIPTAG_REFERRED_BY(referred_by),
+	 TAG_END());
+
+  run_abc_until(ctx,
+		-1, notify_by_appl ? notify_until_terminated : until_ready,
+		-1, save_until_received,
+		-1, accept_call);
+
+  /* Wait until both NOTIFY has been responded */
+  while (a_revents->head == NULL || a_revents->head->next == NULL)
+    run_ab_until(ctx, -1, save_until_received, -1, save_events);
+  while (b_revents->head == NULL || b_revents->head->next == NULL)
+    run_ab_until(ctx, -1, save_events, -1, save_until_received);
+
+  /* Client A transitions:
+     INIT -(C1)-> CALLING: nua_invite(), nua_i_state
+     CALLING -(C2+C4)-> PROCEEDING: nua_r_invite, nua_i_state
+     nua_r_notify
+     PROCEEDING -(C3+C4)-> READY: nua_r_invite, nua_i_state
+     nua_r_notify
+  */
+  TEST_1(e = a_c2->events->head); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_calling); /* CALLING */
+  TEST_1(is_offer_sent(e->data->e_tags));
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_r_invite);
+  TEST(e->data->e_status, 180);
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_proceeding); /* PROCEEDING */
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_r_invite);
+  TEST(e->data->e_status, 200);
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_ready); /* READY */
+  TEST_1(is_answer_recv(e->data->e_tags));
+  TEST_1(!e->next);
+  free_events_in_list(ctx, a_c2->events);
+
+  TEST_1(e = a_revents->head); TEST_E(e->data->e_event, nua_r_notify);
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_r_notify);
+  if (a_refer == a_call && notify_by_appl) {
+    free_event_in_list(ctx, a_revents, a_revents->head);
+    free_event_in_list(ctx, a_revents, a_revents->head);
+  }
+  else {
+    TEST_1(!e->next);
+    free_events_in_list(ctx, a_revents);
+  }
+
+  /*
+     Events in B after nua_refer():
+     nua_i_notify
+  */
+  TEST_1(e = b_revents->head); TEST_E(e->data->e_event, nua_i_notify);
+  TEST(e->data->e_status, 200);
+  TEST_1(sip = sip_object(e->data->e_msg));
+  TEST_1(sip->sip_subscription_state);
+  TEST_S(sip->sip_subscription_state->ss_substate, "active");
+  TEST_1(sip->sip_payload && sip->sip_payload->pl_data);
+  TEST_M(sip->sip_payload->pl_data, "SIP/2.0 180 Ringing\r\n", sip->sip_payload->pl_len);
+  TEST_1(sip->sip_event);
+  if (refer_with_id)
+    TEST_S(sip->sip_event->o_id, b_event->o_id);
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_notify);
+  TEST(e->data->e_status, 200);
+  TEST_1(sip = sip_object(e->data->e_msg));
+  TEST_1(sip->sip_subscription_state);
+  TEST_S(sip->sip_subscription_state->ss_substate, "terminated");
+  TEST_1(sip->sip_payload && sip->sip_payload->pl_data);
+  TEST_M(sip->sip_payload->pl_data, "SIP/2.0 200 OK\r\n", sip->sip_payload->pl_len);
+  TEST_1(sip->sip_event);
+  if (refer_with_id)
+    TEST_S(sip->sip_event->o_id, b_event->o_id);
+  if (b_refer == b_call && notify_by_appl) {
+    free_event_in_list(ctx, b_revents, b_revents->head);
+    free_event_in_list(ctx, b_revents, b_revents->head);
+  }
+  else {
+    TEST_1(!e->next);
+    free_events_in_list(ctx, b_revents);
+  }
+
+  /*
+   C transitions:
+   INIT -(S1)-> RECEIVED: nua_i_invite, nua_i_state
+   RECEIVED -(S2a)-> EARLY: nua_respond(), nua_i_state
+   EARLY -(S3b)-> COMPLETED: nua_respond(), nua_i_state
+   COMPLETED -(S4)-> READY: nua_i_ack, nua_i_state
+  */
+  TEST_1(e = c->events->head); TEST_E(e->data->e_event, nua_i_invite);
+  TEST(e->data->e_status, 100);
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_received); /* RECEIVED */
+  TEST_1(is_offer_recv(e->data->e_tags));
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_early); /* EARLY */
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_completed); /* COMPLETED */
+  TEST_1(is_answer_sent(e->data->e_tags));
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_ack);
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_ready); /* READY */
+  TEST_1(!e->next);
+  free_events_in_list(ctx, c->events);
+
+  if (print_headings)
+    printf("TEST %s.4: PASSED\n", tests);
+
+  /* ---------------------------------------------------------------------- */
+  /*
+ A                    B
+ |---------BYE------->|
+ |<--------200--------|
+   */
+
+  if (print_headings)
+    printf("TEST %s.5.1: terminate call between A and B\n", tests);
+
+  if (notify_by_appl) {
+    if (!a->events->head || !a->events->head->next)
+      run_ab_until(ctx, -1, until_terminated, -1, save_events);
+    if (!b->events->head || !b->events->head->next)
+      run_ab_until(ctx, -1, save_events, -1, until_terminated);
+  }
+  else {
+    BYE(a, a_call, a_call->nh, TAG_END());
+    run_ab_until(ctx, -1, until_terminated, -1, until_terminated);
+  }
+
+  /*
+    Transitions of A:
+    READY --(T2)--> TERMINATING: nua_bye()
+    TERMINATING --(T3)--> TERMINATED: nua_r_bye, nua_i_state
+  */
+  TEST_1(e = a->events->head); TEST_E(e->data->e_event, nua_r_bye);
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_terminated); /* TERMINATED */
+  TEST_1(!e->next);
+  free_events_in_list(ctx, a->events);
+  
+  /* Transitions of B:
+     READY -(T1)-> TERMINATED: nua_i_bye, nua_i_state
+  */
+  TEST_1(e = b->events->head); TEST_E(e->data->e_event, nua_i_bye);
+  TEST(e->data->e_status, 200);
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_terminated); /* TERMINATED */
+  TEST_1(!e->next);
+  free_events_in_list(ctx, b->events);
+
+  if (print_headings)
+    printf("TEST %s.5.1: PASSED\n", tests);
+
+  nua_handle_destroy(a_call->nh), a_call->nh = NULL;
+  nua_handle_destroy(b_call->nh), b_call->nh = NULL;
+
+
+  /* ---------------------------------------------------------------------- */
+  /*
+   A                                            C
+   |-------------------BYE--------------------->|
+   |<------------------200----------------------|
+   */
+
+  if (print_headings)
+    printf("TEST %s.5.2: terminate call between A and C\n", tests);
+
+  BYE(a, a_c2, a_c2->nh, TAG_END());
+  run_abc_until(ctx, -1, until_terminated, -1, NULL, -1, until_terminated);
+
+  /*
+   Transitions of A:
+   READY --(T2)--> TERMINATING: nua_bye()
+   TERMINATING --(T3)--> TERMINATED: nua_r_bye, nua_i_state
+  */
+  TEST_1(e = a_c2->events->head); TEST_E(e->data->e_event, nua_r_bye);
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_terminated); /* TERMINATED */
+  TEST_1(!e->next);
+  free_events_in_list(ctx, a_c2->events);
+
+  /* Transitions of B:
+     READY -(T1)-> TERMINATED: nua_i_bye, nua_i_state
+  */
+  TEST_1(e = c->events->head); TEST_E(e->data->e_event, nua_i_bye);
+  TEST(e->data->e_status, 200);
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_terminated); /* TERMINATED */
+  TEST_1(!e->next);
+  free_events_in_list(ctx, c->events);
+
+  if (print_headings)
+    printf("TEST %s.5.2: PASSED\n", tests);
+
+  nua_handle_destroy(c_call->nh), c_call->nh = NULL;
+
+  nua_handle_destroy(a_c2->nh), a_c2->nh = NULL;
+  a_refer->next = NULL; free(a_c2);
+
+  if (a_refer != a_call) {
+    nua_handle_destroy(a_refer->nh), a_refer->nh = NULL;
+    a_call->next = NULL; free(a_refer);
+  }
+
+  if (b_refer != b_call) {
+    nua_handle_destroy(b_refer->nh), b_refer->nh = NULL;
+    b_call->next = NULL; free(b_refer);
+  }
+
+  if (print_headings)
+    printf("TEST %s: PASSED\n", tests);
+
+  su_home_deinit(tmphome);
+
+  END();
+}
+
+
+/*
+ X      INVITE
+ |                    |
+ |-----------------INVITE-------------------->|
+ |                    |                       |
+ |                    |                       |
+ |<------------------200----------------------|
+ |-------NOTIFY------>|			      |
+ |--------BYE-------->|			      |
+ |-------------------ACK--------------------->|
+
+*/
+int notify_until_terminated(CONDITION_PARAMS)
+{
+  if (!(check_handle(ep, call, nh, SIP_500_INTERNAL_SERVER_ERROR)))
+    return 0;
+
+  save_event_in_list(ctx, event, ep, call);
+
+  if (event == nua_r_invite) {
+    sip_status_t *st = sip->sip_status;
+    sip_payload_t *pl;
+    struct call *r_call;
+
+    if (!nua_handle_has_events(ep->call->nh))
+      r_call = ep->call->next;
+    else
+      r_call = ep->call;
+
+    assert(nua_handle_has_events(r_call->nh));
+
+    pl = sip_payload_format(NULL, "SIP/2.0 %u %s\r\n", 
+			    st->st_status, st->st_phrase);
+
+    NOTIFY(ep, r_call, r_call->nh,
+	   SIPTAG_CONTENT_TYPE_STR("message/sipfrag"),
+	   SIPTAG_PAYLOAD(pl),
+	   NUTAG_SUBSTATE(st->st_status >= 200
+			  ? nua_substate_terminated
+			  : nua_substate_active),
+	   TAG_END());
+
+    su_free(NULL, pl);
+
+    if (st->st_status >= 200)
+      BYE(ep, ep->call, ep->call->nh, TAG_END());
+
+    return 0;
+  }
+
+  if (call != ep->call)
+    return 0;
+
+  switch (callstate(tags)) {
+  case nua_callstate_terminated:
+    return 1;
+  default:
+    return 0;
+  }
+}
+
+int authenticate_refer(CONDITION_PARAMS);
+int reject_refer_after_notified(CONDITION_PARAMS);
+
+int test_challenge_refer(struct context *ctx)
+{
+  BEGIN();
+
+  struct endpoint *a = &ctx->a, *b = &ctx->b, *c = &ctx->c;
+  struct call *a_call = a->call, *c_call = c->call;
+  struct event *e;
+  sip_t const *sip;
+
+  sip_refer_to_t r0[1];
+
+  if (!ctx->proxy_tests)
+    return 0;
+
+  if (print_headings)
+    printf("TEST NUA-9.0.1: challenge REFER\n");
+
+  nua_set_params(ctx->a.nua, NUTAG_APPL_METHOD("REFER"), TAG_END());
+  run_a_until(ctx, nua_r_set_params, until_final_response);
+
+  *sip_refer_to_init(r0)->r_url = *b->contact->m_url;
+  r0->r_url->url_headers = "subject=referred";
+  r0->r_display = "B";
+
+  TEST_1(c_call->nh = nua_handle(c->nua, c_call, SIPTAG_TO(a->to), TAG_END()));
+
+  REFER(c, c_call, c_call->nh,
+	SIPTAG_FROM(c->to),
+	SIPTAG_REFER_TO(r0),
+	TAG_END());
+
+  run_abc_until(ctx, -1, reject_refer_after_notified, -1, NULL, -1, authenticate_refer);
+
+  /*
+    Events in A:
+    nua_i_refer
+  */
+  TEST_1(e = a->events->head); TEST_E(e->data->e_event, nua_i_refer);
+  TEST(e->data->e_status, 100);
+  TEST_1(sip = sip_object(e->data->e_msg));
+  TEST_1(sip->sip_refer_to);
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_r_notify);
+  TEST(e->data->e_status, 200);
+  TEST_1(!e->next); 
+  /*
+     Events in C after nua_refer():
+     nua_r_refer
+  */
+  TEST_1(e = c->events->head); TEST_E(e->data->e_event, nua_r_refer);
+  TEST(e->data->e_status, 100);
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_r_refer);
+  TEST(e->data->e_status, 407);
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_r_refer);
+  TEST(e->data->e_status, 100);
+
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_notify);
+  TEST(e->data->e_status, 200);
+
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_r_refer);
+  TEST(e->data->e_status, 480);
+
+  TEST_1(!e->next);
+  
+  free_events_in_list(ctx, a->events);
+  free_events_in_list(ctx, c->events);
+
+  nua_handle_destroy(a_call->nh), a_call->nh = NULL;
+  nua_handle_destroy(c_call->nh), c_call->nh = NULL;
+
+  nua_set_params(ctx->a.nua,
+		 NUTAG_APPL_METHOD(NULL),
+		 NUTAG_APPL_METHOD("INVITE, REGISTER, PUBLISH, SUBSCRIBE"),
+		 TAG_END());
+  run_a_until(ctx, nua_r_set_params, until_final_response);
+
+  if (print_headings)
+    printf("TEST NUA-9.0.1: PASSED\n");
+
+  END();
+}
+
+int reject_refer_after_notified(CONDITION_PARAMS)
+{
+  if (!(check_handle(ep, call, nh, SIP_500_INTERNAL_SERVER_ERROR)))
+    return 0;
+
+  save_event_in_list(ctx, event, ep, call);
+
+  if (event == nua_i_refer) {
+  }
+
+  if (event == nua_r_notify) {
+    /* Respond to refer only after initial notify has been responded */
+    struct eventlist *list;
+    struct event *e;
+
+    if (call->events)
+      list = call->events;
+    else
+      list = ep->events;
+
+    for (e = list->head; e; e = e->next)
+      if (e->data->e_event == nua_i_refer)
+	break;
+
+    if (e) {
+      RESPOND(ep, call, nh, SIP_480_TEMPORARILY_UNAVAILABLE,
+	      NUTAG_WITH(e->data->e_msg),
+	      TAG_END());
+      return 1;
+    }
+    return 0;
+  }
+
+  return 0;
+}
+
+int authenticate_refer(CONDITION_PARAMS)
+{
+  if (!(check_handle(ep, call, nh, SIP_500_INTERNAL_SERVER_ERROR)))
+    return 0;
+
+  save_event_in_list(ctx, event, ep, call);
+
+  if (status == 401 || status == 407) {
+    AUTHENTICATE(ep, call, nh,
+		 NUTAG_AUTH("Digest:\"test-proxy\":charlie:secret"),
+		 TAG_END());
+  }
+
+  return status == 480;
+}

Added: freeswitch/trunk/libs/sofia-sip/tests/test_register.c
==============================================================================
--- (empty file)
+++ freeswitch/trunk/libs/sofia-sip/tests/test_register.c	Wed May 14 15:10:54 2008
@@ -0,0 +1,1043 @@
+/*
+ * This file is part of the Sofia-SIP package
+ *
+ * Copyright (C) 2005 Nokia Corporation.
+ *
+ * Contact: Pekka Pessi <pekka.pessi at nokia.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA
+ *
+ */
+
+/**@CFILE test_register.c
+ * @brief Test registering, outbound, nat traversal.
+ *
+ * @author Pekka Pessi <Pekka.Pessi at nokia.com>
+ * @author Martti Mela <Martti Mela at nokia.com>
+ *
+ * @date Created: Wed Aug 17 12:12:12 EEST 2005 ppessi
+ */
+
+#include "config.h"
+
+#include "test_nua.h"
+#include <sofia-sip/su_tag_class.h>
+
+#if HAVE_FUNC
+#elif HAVE_FUNCTION
+#define __func__ __FUNCTION__
+#else
+#define __func__ "test_register"
+#endif
+
+/* ======================================================================== */
+/* Test REGISTER */
+
+int test_clear_registrations(struct context *ctx);
+int test_outbound_cases(struct context *ctx);
+int test_register_a(struct context *ctx);
+int test_register_b(struct context *ctx);
+int test_register_c(struct context *ctx);
+int test_register_refresh(struct context *ctx);
+
+int test_register_to_proxy(struct context *ctx)
+{
+  return
+    test_clear_registrations(ctx) ||
+    test_outbound_cases(ctx) ||
+    test_register_a(ctx) ||
+    test_register_b(ctx) ||
+    test_register_c(ctx) ||
+    test_register_refresh(ctx);
+}
+
+int test_clear_registrations(struct context *ctx)
+{
+  BEGIN();
+
+  struct endpoint *a = &ctx->a,  *b = &ctx->b, *c = &ctx->c;
+  struct call *a_reg = a->reg, *b_reg = b->reg, *c_reg = c->reg;
+
+  if (print_headings)
+    printf("TEST NUA-2.3.0.1: un-REGISTER a\n");
+
+  TEST_1(a_reg->nh = nua_handle(a->nua, a_reg, TAG_END()));
+  UNREGISTER(a, a_reg, a_reg->nh, SIPTAG_TO(a->to), 
+	     SIPTAG_CONTACT_STR("*"),
+	     TAG_END());
+  run_a_until(ctx, -1, until_final_response);  
+  AUTHENTICATE(a, a_reg, a_reg->nh,
+	       NUTAG_AUTH("Digest:\"test-proxy\":alice:secret"), TAG_END());
+  run_a_until(ctx, -1, until_final_response);
+  nua_handle_destroy(a_reg->nh);
+
+  if (print_headings)
+    printf("TEST NUA-2.3.0.1: PASSED\n");
+
+  if (print_headings)
+    printf("TEST NUA-2.3.0.2: un-REGISTER b\n");
+
+  TEST_1(b_reg->nh = nua_handle(b->nua, b_reg, TAG_END()));
+  UNREGISTER(b, b_reg, b_reg->nh, SIPTAG_TO(b->to), 
+	     SIPTAG_CONTACT_STR("*"),
+	     TAG_END());
+  run_b_until(ctx, -1, until_final_response);  
+  AUTHENTICATE(b, b_reg, b_reg->nh,
+	       NUTAG_AUTH("Digest:\"test-proxy\":bob:secret"), TAG_END());
+  run_b_until(ctx, -1, until_final_response);
+  nua_handle_destroy(b_reg->nh);
+
+  if (print_headings)
+    printf("TEST NUA-2.3.0.2: PASSED\n");
+
+  if (print_headings)
+    printf("TEST NUA-2.3.0.3: un-REGISTER c\n");
+
+  TEST_1(c_reg->nh = nua_handle(c->nua, c_reg, TAG_END()));
+  UNREGISTER(c, c_reg, c_reg->nh,
+	     SIPTAG_FROM(c->to), SIPTAG_TO(c->to),
+	     SIPTAG_CONTACT_STR("*"),
+	     TAG_END());
+  run_c_until(ctx, -1, until_final_response);  
+  AUTHENTICATE(c, c_reg, c_reg->nh,
+	       NUTAG_AUTH("Digest:\"test-proxy\":charlie:secret"), TAG_END());
+  run_c_until(ctx, -1, until_final_response);
+  nua_handle_destroy(c_reg->nh);
+
+  if (print_headings)
+    printf("TEST NUA-2.3.0.3: PASSED\n");
+
+  END();
+}
+
+int test_outbound_cases(struct context *ctx)
+{
+  BEGIN();
+
+#if 0
+
+  struct endpoint *a = &ctx->a, *x;
+  struct call *a_reg = a->reg;
+  struct event *e;
+  sip_t const *sip;
+  sip_contact_t m[1];
+
+/* REGISTER test
+
+   A			R
+   |------REGISTER----->|
+   |<-------401---------|
+   |------REGISTER----->|
+   |<-------200---------|
+   |			|
+
+*/
+
+  if (print_headings)
+    printf("TEST NUA-2.3.1: REGISTER a\n");
+
+  test_proxy_domain_set_expiration(ctx->a.domain, 5, 5, 10);
+
+  TEST_1(a_reg->nh = nua_handle(a->nua, a_reg, TAG_END()));
+
+  sip_contact_init(m);
+  m->m_display = "Lissu";
+  *m->m_url = *a->contact->m_url;
+  m->m_url->url_user = "a";
+  m->m_url->url_params = "transport=udp";
+
+  REGISTER(a, a_reg, a_reg->nh, SIPTAG_TO(a->to),
+	   NUTAG_OUTBOUND("use-rport no-options-keepalive"),
+	   SIPTAG_CONTACT(m),
+	   TAG_END());
+  run_a_until(ctx, -1, save_until_final_response);
+
+  TEST_1(e = a->events->head);
+  TEST_E(e->data->e_event, nua_r_register);
+  TEST(e->data->e_status, 401);
+  TEST_1(sip = sip_object(e->data->e_msg));
+  TEST(sip->sip_status->st_status, 401);
+  TEST_1(!sip->sip_contact);
+  TEST_1(!e->next);
+  free_events_in_list(ctx, a->events);
+
+  AUTHENTICATE(a, a_reg, a_reg->nh,
+	       NUTAG_AUTH("Digest:\"test-proxy\":alice:secret"), TAG_END());
+  run_a_until(ctx, -1, save_until_final_response);
+
+  TEST_1(e = a->events->head);
+  TEST_E(e->data->e_event, nua_r_register);
+  TEST(e->data->e_status, 200);
+  TEST_1(sip = sip_object(e->data->e_msg));
+  TEST_1(sip->sip_contact);
+  TEST_S(sip->sip_contact->m_display, "Lissu");
+  TEST_S(sip->sip_contact->m_url->url_user, "a");
+  TEST_1(strstr(sip->sip_contact->m_url->url_params, "transport=udp"));
+
+  if (ctx->nat) {
+    TEST_1(e = a->specials->head);
+  }
+
+  test_proxy_domain_set_expiration(ctx->a.domain, 600, 3600, 36000);
+
+  if (print_headings)
+    printf("TEST NUA-2.3.1: PASSED\n");
+
+  if (print_headings)
+    printf("TEST NUA-2.3.4: refresh REGISTER\n");
+
+  if (!ctx->p) {
+    free_events_in_list(ctx, a->events);
+    return 0;
+  }
+
+  /* Wait for A to refresh its registrations */
+
+  /*
+   * Avoid race condition: if X has already refreshed registration
+   * with expiration time of 3600 seconds, do not wait for new refresh
+   */
+  a->next_condition = save_until_final_response;
+
+  for (x = a; x; x = NULL) {
+    for (e = x->events->head; e; e = e->next) {
+      if (e->data->e_event == nua_r_register &&
+	  e->data->e_status == 200 &&
+	  (sip = sip_object(e->data->e_msg)) &&
+	  sip->sip_contact &&
+	  sip->sip_contact->m_expires &&
+	  strcmp(sip->sip_contact->m_expires, "3600") == 0) {
+	x->next_condition = NULL;
+	break;
+      }
+    }
+  }
+
+  run_a_until(ctx, -1, a->next_condition);
+  
+  for (e = a->events->head; e; e = e->next) {
+    TEST_E(e->data->e_event, nua_r_register);
+    TEST(e->data->e_status, 200);
+    TEST_1(sip = sip_object(e->data->e_msg));
+    TEST_1(sip->sip_contact);
+    if (!e->next)
+      break;
+  }
+  TEST_1(e);
+  TEST_1(sip = sip_object(e->data->e_msg));
+  TEST_S(sip->sip_contact->m_expires, "3600");
+  TEST_1(!e->next);
+  free_events_in_list(ctx, a->events);
+
+  if (print_headings)
+    printf("TEST NUA-2.3.4: PASSED\n");
+
+  TEST_1(0);
+
+#endif
+
+  END();
+}
+
+int test_register_a(struct context *ctx)
+{
+  BEGIN();
+
+  struct endpoint *a = &ctx->a;
+  struct call *a_reg = a->reg;
+  struct event *e;
+  sip_t const *sip;
+  sip_cseq_t cseq[1];
+
+/* REGISTER test
+
+   A			R
+   |------REGISTER----->|
+   |<-------401---------|
+   |------REGISTER----->|
+   |<-------200---------|
+   |			|
+
+*/
+
+  if (print_headings)
+    printf("TEST NUA-2.3.1: REGISTER a\n");
+
+  test_proxy_domain_set_expiration(ctx->a.domain, 5, 5, 10);
+
+  TEST_1(a_reg->nh = nua_handle(a->nua, a_reg, TAG_END()));
+
+  sip_cseq_init(cseq)->cs_seq = 12;
+  cseq->cs_method = sip_method_register;
+  cseq->cs_method_name = sip_method_name_register;
+
+  REGISTER(a, a_reg, a_reg->nh, SIPTAG_TO(a->to),
+	   NUTAG_OUTBOUND("natify options-keepalive validate"),
+	   NUTAG_KEEPALIVE(1000),
+	   NUTAG_M_DISPLAY("A&A"),
+	   NUTAG_M_USERNAME("a"),
+	   NUTAG_M_PARAMS("foo=bar"),
+	   NUTAG_M_FEATURES("q=0.9"),
+	   SIPTAG_CSEQ(cseq),
+	   TAG_END());
+  run_a_until(ctx, -1, save_until_final_response);
+
+  TEST_1(e = a->events->head);
+  TEST_1(sip = sip_object(e->data->e_msg));
+  if (ctx->nat && e->data->e_status == 100) {
+    TEST_E(e->data->e_event, nua_r_register);
+    TEST(e->data->e_status, 100);
+    TEST(sip->sip_status->st_status, 406);
+    /* Check that CSeq included in tags is actually used in the request */
+    TEST(sip->sip_cseq->cs_seq, 13);
+    TEST_1(!sip->sip_contact);
+    TEST_1(e = e->next);
+    TEST_1(sip = sip_object(e->data->e_msg));
+    TEST(sip->sip_cseq->cs_seq, 14);
+  }
+  else {
+    /* Check that CSeq included in tags is actually used in the request */
+    TEST(sip->sip_cseq->cs_seq, 13);
+  }
+  TEST_E(e->data->e_event, nua_r_register);
+  TEST(e->data->e_status, 401);
+  TEST(sip->sip_status->st_status, 401);
+  TEST_1(!sip->sip_contact);
+  TEST_1(!e->next);
+  free_events_in_list(ctx, a->events);
+
+  AUTHENTICATE(a, a_reg, a_reg->nh,
+	       NUTAG_AUTH("Digest:\"test-proxy\":alice:secret"), TAG_END());
+  run_a_until(ctx, -1, save_until_final_response);
+
+  TEST_1(e = a->events->head);
+  TEST_E(e->data->e_event, nua_r_register);
+  TEST(e->data->e_status, 200);
+  TEST_1(sip = sip_object(e->data->e_msg));
+  TEST_1(sip->sip_contact);
+  { char const *expect_m_display = "\"A&A\"";
+    /* VC does not dig \" with TEST_S() */
+  TEST_S(sip->sip_contact->m_display, expect_m_display); }
+  TEST_S(sip->sip_contact->m_url->url_user, "a");
+  TEST_1(strstr(sip->sip_contact->m_url->url_params, "foo=bar"));
+  TEST_S(sip->sip_contact->m_q, "0.9");
+
+  if (ctx->nat) {
+    TEST_1(e = a->specials->head);
+  }
+
+  test_proxy_domain_set_expiration(ctx->a.domain, 600, 3600, 36000);
+
+  if (print_headings)
+    printf("TEST NUA-2.3.1: PASSED\n");
+
+  END();
+}
+
+int test_register_b(struct context *ctx)
+{
+  BEGIN();
+
+  struct endpoint  *b = &ctx->b;
+  struct call *b_reg = b->reg;
+  struct event *e;
+  sip_t const *sip;
+
+  if (print_headings)
+    printf("TEST NUA-2.3.2: REGISTER b\n");
+
+  test_proxy_domain_set_expiration(ctx->b.domain, 5, 5, 10);
+
+  TEST_1(b_reg->nh = nua_handle(b->nua, b_reg, TAG_END()));
+
+  /* Test application-supplied contact */
+  {
+    sip_contact_t m[1];
+    sip_contact_init(m)->m_url[0] = b->contact->m_url[0];
+
+    m->m_display = "B";
+    m->m_url->url_user = "b";
+
+    /* Include "tcp" transport parameter in Contact */
+    if (ctx->p)
+      m->m_url->url_params = "transport=tcp";
+
+    REGISTER(b, b_reg, b_reg->nh, SIPTAG_TO(b->to), 
+	     SIPTAG_CONTACT(m),
+	     /* Do not include credentials unless challenged */
+	     NUTAG_AUTH_CACHE(nua_auth_cache_challenged),
+	     TAG_END());
+  }
+  run_ab_until(ctx, -1, save_events, -1, save_until_final_response);
+
+  TEST_1(e = b->events->head);
+  TEST_E(e->data->e_event, nua_r_register);
+  TEST_1(sip = sip_object(e->data->e_msg));
+  TEST(e->data->e_status, 401);
+  TEST(sip->sip_status->st_status, 401);
+  TEST_1(!sip->sip_contact);
+  TEST_1(!e->next);
+  free_events_in_list(ctx, b->events);
+
+  AUTHENTICATE(b, b_reg, b_reg->nh,
+	       NUTAG_AUTH("Digest:\"test-proxy\":bob:secret"), TAG_END());
+  run_ab_until(ctx, -1, save_events, -1, save_until_final_response);
+
+  TEST_1(e = b->events->head);
+  TEST_E(e->data->e_event, nua_r_register);
+  TEST(e->data->e_status, 200);
+  TEST_1(sip = sip_object(e->data->e_msg));
+  TEST_1(sip->sip_contact);
+  TEST_S(sip->sip_contact->m_display, "B");
+  TEST_S(sip->sip_contact->m_url->url_user, "b");
+  free_events_in_list(ctx, b->events);
+
+  test_proxy_domain_set_expiration(ctx->b.domain, 600, 3600, 36000);
+
+  if (print_headings)
+    printf("TEST NUA-2.3.2: PASSED\n");
+
+  END();
+}
+
+int test_register_c(struct context *ctx)
+{
+  BEGIN();
+
+  struct endpoint *c = &ctx->c;
+  struct call *c_reg = c->reg;
+  struct event *e;
+  sip_t const *sip;
+
+  if (print_headings)
+    printf("TEST NUA-2.3.3: REGISTER c\n");
+
+  test_proxy_domain_set_expiration(ctx->c.domain, 600, 3600, 36000);
+  test_proxy_domain_set_authorize(ctx->c.domain, "test-proxy-0");
+
+  TEST_1(c_reg->nh = nua_handle(c->nua, c_reg, TAG_END()));
+
+  REGISTER(c, c_reg, c_reg->nh, SIPTAG_TO(c->to),
+	   SIPTAG_FROM(c->to),
+	   NUTAG_OUTBOUND(NULL),
+	   NUTAG_M_DISPLAY("C"),
+	   NUTAG_M_USERNAME("c"),
+	   NUTAG_M_PARAMS("c=1"),
+	   NUTAG_M_FEATURES("q=0.987;expires=5"),
+	   NUTAG_CALLEE_CAPS(1),
+	   SIPTAG_EXPIRES_STR("5"), /* Test 423 negotiation */
+	   TAG_END());
+  run_abc_until(ctx, -1, save_events, -1, save_events, 
+		-1, save_until_final_response);
+
+  TEST_1(e = c->events->head);
+  TEST_E(e->data->e_event, nua_r_register);
+  TEST_1(sip = sip_object(e->data->e_msg));
+  TEST(e->data->e_status, 401);
+  TEST(sip->sip_status->st_status, 401);
+  TEST_1(!sip->sip_contact);
+  TEST_1(!e->next);
+  free_events_in_list(ctx, c->events);
+
+  AUTHENTICATE(c, c_reg, c_reg->nh,
+	       NUTAG_AUTH("Digest:\"test-proxy-0\":charlie:secret"), TAG_END());
+  run_abc_until(ctx, -1, save_events, -1, save_events, 
+		-1, save_until_final_response);
+
+  TEST_1(e = c->events->head);
+  TEST_E(e->data->e_event, nua_r_register);
+  TEST(e->data->e_status, 100);
+  TEST_1(sip = sip_object(e->data->e_msg));
+  TEST(sip->sip_status->st_status, 423);
+  TEST_1(e = e->next);
+  if (e->data->e_status == 100 && e->data->e_event == nua_r_register) {
+    TEST_1(sip = sip_object(e->data->e_msg));
+    TEST(sip->sip_status->st_status, 401);
+    TEST_1(e = e->next);
+  }
+  TEST(e->data->e_status, 200); TEST_E(e->data->e_event, nua_r_register);
+  TEST_1(sip = sip_object(e->data->e_msg));
+  TEST_1(sip->sip_contact);
+  TEST_S(sip->sip_contact->m_display, "C");
+  TEST_S(sip->sip_contact->m_url->url_user, "c");
+  TEST_1(strstr(sip->sip_contact->m_url->url_params, "c=1"));
+  TEST_S(sip->sip_contact->m_q, "0.987");
+  TEST_1(msg_header_find_param(sip->sip_contact->m_common, "methods="));
+  TEST_1(!e->next);
+  free_events_in_list(ctx, c->events);
+
+  if (print_headings)
+    printf("TEST NUA-2.3.3: PASSED\n");
+
+  END();
+}
+
+int test_register_refresh(struct context *ctx)
+{
+  BEGIN();
+
+  struct endpoint *a = &ctx->a,  *b = &ctx->b, *x;
+  struct event *e;
+  sip_t const *sip;
+  int seen_401;
+
+  if (print_headings)
+    printf("TEST NUA-2.3.4: refresh REGISTER\n");
+
+  if (!ctx->p) {
+    free_events_in_list(ctx, a->events);
+    free_events_in_list(ctx, b->events);
+    return 0;
+  }
+
+  /* Wait for A and B to refresh their registrations */
+
+  /*
+   * Avoid race condition: if X has already refreshed registration
+   * with expiration time of 3600 seconds, do not wait for new refresh
+   */
+  a->next_condition = save_until_final_response;
+  b->next_condition = save_until_final_response;
+
+  for (x = a; x; x = x == a ? b : NULL) {
+    for (e = x->events->head; e; e = e->next) {
+      if (e->data->e_event == nua_r_register &&
+	  e->data->e_status == 200 &&
+	  (sip = sip_object(e->data->e_msg)) &&
+	  sip->sip_contact &&
+	  sip->sip_contact->m_expires &&
+	  strcmp(sip->sip_contact->m_expires, "3600") == 0) {
+	x->next_condition = NULL;
+	break;
+      }
+    }
+  }
+
+  run_ab_until(ctx, -1, a->next_condition, -1, b->next_condition);
+  
+  for (e = a->events->head; e; e = e->next) {
+    TEST_E(e->data->e_event, nua_r_register);
+    TEST(e->data->e_status, 200);
+    TEST_1(sip = sip_object(e->data->e_msg));
+    TEST_1(sip->sip_contact);
+    if (!e->next)
+      break;
+  }
+  TEST_1(e);
+  TEST_1(sip = sip_object(e->data->e_msg));
+  TEST_S(sip->sip_contact->m_expires, "3600");
+  TEST_1(!e->next);
+  free_events_in_list(ctx, a->events);
+
+  seen_401 = 0;
+
+  for (e = b->events->head; e; e = e->next) {
+    TEST_E(e->data->e_event, nua_r_register);
+    TEST_1(sip = sip_object(e->data->e_msg));
+
+    if (e->data->e_status == 200) {
+      TEST(e->data->e_status, 200);
+      TEST_1(seen_401);
+      TEST_1(sip->sip_contact);
+    }
+    else if (sip->sip_status && sip->sip_status->st_status == 401) {
+      seen_401 = 1;
+    }
+
+    if (!e->next)
+      break;
+  }
+  TEST_1(e);
+  TEST_S(sip->sip_contact->m_expires, "3600");
+  TEST_1(!e->next);
+  free_events_in_list(ctx, b->events);
+
+  if (print_headings)
+    printf("TEST NUA-2.3.4: PASSED\n");
+
+  if (!ctx->p)
+    return 0;
+
+  if (print_headings)
+    printf("TEST NUA-2.3.5: re-REGISTER when TCP connection is closed\n");
+
+  test_proxy_close_tports(ctx->p);
+
+  run_b_until(ctx, -1, save_until_final_response);
+
+  TEST_1(e = b->events->head);
+  TEST_E(e->data->e_event, nua_r_register);
+  if (e->data->e_status == 100)
+    TEST_1(e = e->next);
+  TEST_1(sip = sip_object(e->data->e_msg));
+  TEST_1(sip->sip_contact);
+  TEST_S(sip->sip_contact->m_expires, "3600");
+  TEST_1(!e->next);
+
+  free_events_in_list(ctx, b->events);
+
+  if (print_headings)
+    printf("TEST NUA-2.3.5: PASSED\n");
+
+  END();
+}
+
+int registrar_299(CONDITION_PARAMS)
+{
+  msg_t *request = nua_current_request(nua);
+
+  save_event_in_list(ctx, event, ep, ep->call);
+
+  if (event == nua_i_register) {
+    RESPOND(ep, call, nh, 299, "YES", NUTAG_WITH(request), TAG_END());
+    return 1;
+  }
+
+  return 0;
+}
+
+int test_register_to_c(struct context *ctx)
+{
+  BEGIN();
+
+  struct endpoint *b = &ctx->b, *c = &ctx->c;
+  struct call *b_call = b->call, *c_call = c->call;
+  struct event *e;
+  sip_t const *sip;
+
+  if (print_headings)
+    printf("TEST NUA-2.6.1: REGISTER b to c\n");
+
+  nua_set_params(ctx->c.nua,
+		 NUTAG_ALLOW("REGISTER"),
+		 TAG_END());
+  run_c_until(ctx, nua_r_set_params, until_final_response);
+
+  TEST_1(b_call->nh = nua_handle(b->nua, b_call, TAG_END()));
+
+  REGISTER(b, b_call, b_call->nh, 
+	   NUTAG_REGISTRAR((url_string_t *)c->contact->m_url),
+	   SIPTAG_TO(b->to),
+	   NUTAG_OUTBOUND(NULL),
+	   SIPTAG_CONTACT_STR(NULL),
+	   TAG_END());
+  run_bc_until(ctx, -1, save_until_final_response, -1, registrar_299);
+
+  TEST_1(e = b->events->head);
+  TEST_E(e->data->e_event, nua_r_register);
+  TEST(e->data->e_status, 299);
+  TEST_1(sip = sip_object(e->data->e_msg));
+  TEST_1(!sip->sip_contact);
+
+  free_events_in_list(ctx, b->events);
+  nua_handle_destroy(b_call->nh), b_call->nh = NULL;
+
+  TEST_1(e = c->events->head);
+  TEST_E(e->data->e_event, nua_i_register);
+  TEST(e->data->e_status, 100);
+  TEST_1(sip = sip_object(e->data->e_msg));
+  TEST_1(!sip->sip_contact);
+
+  free_events_in_list(ctx, c->events);
+  nua_handle_destroy(c_call->nh), c_call->nh = NULL;
+
+  if (print_headings)
+    printf("TEST NUA-2.6.1: PASSED\n");
+
+  END();
+}
+
+
+int test_register(struct context *ctx)
+{
+  if (test_register_to_c(ctx))
+    return 1;
+
+  if (ctx->proxy_tests)
+    if (test_register_to_proxy(ctx)) return 1;
+
+  return 0;
+}
+
+
+int test_connectivity(struct context *ctx)
+{
+  if (!ctx->proxy_tests)
+    return 0;			/* No proxy */
+
+  BEGIN();
+
+  struct endpoint *a = &ctx->a,  *b = &ctx->b, *c = &ctx->c;
+  struct call *a_call = a->call, *b_call = b->call, *c_call = c->call;
+  struct event *e;
+  sip_t const *sip;
+
+  /* Connectivity test using OPTIONS */
+
+  if (print_headings)
+    printf("TEST NUA-2.4.1: OPTIONS from A to B\n");
+
+  TEST_1(a_call->nh = nua_handle(a->nua, a_call, SIPTAG_TO(b->to), TAG_END()));
+
+  OPTIONS(a, a_call, a_call->nh,
+	  TAG_IF(!ctx->proxy_tests, NUTAG_URL(b->contact->m_url)),
+	  NUTAG_ALLOW("OPTIONS"),
+	  TAG_END());
+
+  run_ab_until(ctx, -1, save_until_final_response, -1, save_until_received);
+
+  /* Client events: nua_options(), nua_r_options */
+  TEST_1(e = a->events->head); TEST_E(e->data->e_event, nua_r_options);
+  TEST(e->data->e_status, 200);
+  TEST_1(sip = sip_object(e->data->e_msg));
+  TEST_1(sip->sip_allow); TEST_1(sip->sip_accept); TEST_1(sip->sip_supported);
+  /* TEST_1(sip->sip_content_type); */
+  /* TEST_1(sip->sip_payload); */
+  TEST_1(!e->next);
+
+  free_events_in_list(ctx, a->events);
+  nua_handle_destroy(a_call->nh), a_call->nh = NULL;
+
+  /* Server events: nua_i_options */
+  TEST_1(e = b->events->head); TEST_E(e->data->e_event, nua_i_options);
+  TEST(e->data->e_status, 200);
+  TEST_1(!e->next);
+
+  free_events_in_list(ctx, b->events);
+  nua_handle_destroy(b_call->nh), b_call->nh = NULL;
+
+  if (print_headings)
+    printf("TEST NUA-2.4.1: PASSED\n");
+
+  if (print_headings)
+    printf("TEST NUA-2.4.2: OPTIONS from B to C\n");
+
+  TEST_1(b_call->nh = nua_handle(b->nua, b_call, SIPTAG_TO(c->to), TAG_END()));
+
+  OPTIONS(b, b_call, b_call->nh,
+	  TAG_IF(!ctx->proxy_tests, NUTAG_URL(c->contact->m_url)),
+	  TAG_END());
+
+  run_abc_until(ctx, -1, NULL,
+		-1, save_until_final_response,
+		-1, save_until_received);
+
+  /* Client events: nua_options(), nua_r_options */
+  TEST_1(e = b->events->head); TEST_E(e->data->e_event, nua_r_options);
+  TEST(e->data->e_status, 200);
+  TEST_1(sip = sip_object(e->data->e_msg));
+  TEST_1(sip->sip_allow); TEST_1(sip->sip_accept); TEST_1(sip->sip_supported);
+  /* TEST_1(sip->sip_content_type); */
+  /* TEST_1(sip->sip_payload); */
+  TEST_1(!e->next);
+
+  free_events_in_list(ctx, b->events);
+  nua_handle_destroy(b_call->nh), b_call->nh = NULL;
+
+  /* Server events: nua_i_options */
+  TEST_1(e = c->events->head); TEST_E(e->data->e_event, nua_i_options);
+  TEST(e->data->e_status, 200);
+  TEST_1(!e->next);
+
+  free_events_in_list(ctx, c->events);
+  nua_handle_destroy(c_call->nh), c_call->nh = NULL;
+
+  if (print_headings)
+    printf("TEST NUA-2.4.2: PASSED\n");
+
+  if (print_headings)
+    printf("TEST NUA-2.4.3: OPTIONS from C to A\n");
+
+  TEST_1(c_call->nh = nua_handle(c->nua, c_call, SIPTAG_TO(a->to), TAG_END()));
+
+  OPTIONS(c, c_call, c_call->nh,
+	  SIPTAG_FROM(c->to),
+	  TAG_IF(!ctx->proxy_tests, NUTAG_URL(a->contact->m_url)),
+	  TAG_END());
+
+  if (ctx->proxy_tests) {
+    run_abc_until(ctx, -1, NULL, -1, NULL, -1, save_until_final_response);
+
+    /* Client events: nua_options(), nua_r_options */
+    TEST_1(e = c->events->head); TEST_E(e->data->e_event, nua_r_options);
+    TEST(e->data->e_status, 407);
+    TEST_1(!e->next);
+
+    free_events_in_list(ctx, c->events);
+
+    /* Sneakily change the realm */  
+
+    TEST(test_proxy_domain_set_authorize(ctx->c.domain, "test-proxy"), 0);
+
+    AUTHENTICATE(c, c_call, c_call->nh,
+		 NUTAG_AUTH("Digest:\"test-proxy-0\":charlie:secret"),
+		 TAG_END());
+
+    run_abc_until(ctx, -1, NULL, -1, NULL, -1, save_until_final_response);
+
+    /* Client events: nua_options(), nua_r_options */
+    TEST_1(e = c->events->head); TEST_E(e->data->e_event, nua_r_options);
+    TEST(e->data->e_status, 407);
+    TEST_1(!e->next);
+
+    free_events_in_list(ctx, c->events);
+
+    AUTHENTICATE(c, c_call, c_call->nh,
+		 NUTAG_AUTH("Digest:\"test-proxy\":charlie:secret"),
+		 TAG_END());
+  }
+
+  run_abc_until(ctx, -1, save_until_received,
+		-1, NULL,
+		-1, save_until_final_response);
+
+  /* Client events: nua_options(), nua_r_options */
+  TEST_1(e = c->events->head); TEST_E(e->data->e_event, nua_r_options);
+  TEST(e->data->e_status, 200);
+  TEST_1(sip = sip_object(e->data->e_msg));
+  TEST_1(sip->sip_allow); TEST_1(sip->sip_accept); TEST_1(sip->sip_supported);
+  /* TEST_1(sip->sip_content_type); */
+  /* TEST_1(sip->sip_payload); */
+  TEST_1(!e->next);
+
+  free_events_in_list(ctx, c->events);
+  nua_handle_destroy(c_call->nh), c_call->nh = NULL;
+
+  /* Server events: nua_i_options */
+  TEST_1(e = a->events->head); TEST_E(e->data->e_event, nua_i_options);
+  TEST(e->data->e_status, 200);
+  TEST_1(!e->next);
+
+  free_events_in_list(ctx, a->events);
+  nua_handle_destroy(a_call->nh), a_call->nh = NULL;
+
+  if (print_headings)
+    printf("TEST NUA-2.4.3: PASSED\n");
+
+  END();
+}
+
+int test_nat_timeout(struct context *ctx)
+{
+  if (!ctx->proxy_tests || !ctx->nat)
+    return 0;			/* No proxy */
+
+  BEGIN();
+
+  struct endpoint *a = &ctx->a,  *b = &ctx->b;
+  struct call *a_call = a->call, *b_call = b->call;
+  struct event *e;
+  sip_t const *sip;
+
+  /* Test what happens when NAT bindings go away */
+
+  if (print_headings)
+    printf("TEST NUA-2.5.1: NAT binding change\n");
+
+  free_events_in_list(ctx, a->specials);
+
+  test_nat_flush(ctx->nat);	/* Break our connections */
+
+  /* Run until we get final response to REGISTER */
+  run_a_until(ctx, -1, save_until_final_response);
+
+  TEST_1(e = a->specials->head);
+  TEST_E(e->data->e_event, nua_i_outbound);
+  TEST(e->data->e_status, 102);
+  TEST_S(e->data->e_phrase, "NAT binding changed");
+  TEST_1(!e->next);
+
+  free_events_in_list(ctx, a->specials);
+
+  TEST_1(e = a->events->head);
+  TEST_E(e->data->e_event, nua_r_register);
+  TEST(e->data->e_status, 200);
+  TEST_1(!e->next);
+
+  free_events_in_list(ctx, a->events);
+
+  if (print_headings)
+    printf("TEST NUA-2.5.1: PASSED\n");
+  
+  if (print_headings)
+    printf("TEST NUA-2.5.2: OPTIONS from B to A\n");
+
+  TEST_1(b_call->nh = nua_handle(b->nua, b_call, SIPTAG_TO(a->to), TAG_END()));
+
+  OPTIONS(b, b_call, b_call->nh, TAG_END());
+
+  run_ab_until(ctx, -1, save_until_received,
+	       -1, save_until_final_response);
+
+  /* Client events: nua_options(), nua_r_options */
+  TEST_1(e = b->events->head); TEST_E(e->data->e_event, nua_r_options);
+  TEST(e->data->e_status, 200);
+  TEST_1(sip = sip_object(e->data->e_msg));
+  TEST_1(sip->sip_allow); TEST_1(sip->sip_accept); TEST_1(sip->sip_supported);
+  /* TEST_1(sip->sip_content_type); */
+  /* TEST_1(sip->sip_payload); */
+  TEST_1(!e->next);
+
+  free_events_in_list(ctx, b->events);
+  nua_handle_destroy(b_call->nh), b_call->nh = NULL;
+
+  /* Server events: nua_i_options */
+  TEST_1(e = a->events->head); TEST_E(e->data->e_event, nua_i_options);
+  TEST(e->data->e_status, 200);
+  TEST_1(!e->next);
+
+  free_events_in_list(ctx, a->events);
+  nua_handle_destroy(a_call->nh), a_call->nh = NULL;
+
+  if (print_headings)
+    printf("TEST NUA-2.5.2: PASSED\n");
+
+  END();
+}
+
+int test_unregister(struct context *ctx)
+{
+  if (!ctx->proxy_tests)
+    return 0;			/* No proxy */
+
+  BEGIN();
+
+  struct endpoint *a = &ctx->a,  *b = &ctx->b, *c = &ctx->c;
+  struct event *e;
+  sip_t const *sip;
+
+/* un-REGISTER test
+
+   A			B
+   |----un-REGISTER---->|
+   |<-------200---------|
+   |			|
+
+*/
+  if (print_headings)
+    printf("TEST NUA-13.1: un-REGISTER a\n");
+
+  if (a->reg->nh) {
+    free_events_in_list(ctx, a->events);
+    UNREGISTER(a, NULL, a->reg->nh, TAG_END());
+    run_a_until(ctx, -1, save_until_final_response);
+    TEST_1(e = a->events->head);
+    TEST_E(e->data->e_event, nua_r_unregister);
+    if (e->data->e_status == 100) {
+      TEST_1(e = e->next);
+      TEST_E(e->data->e_event, nua_r_unregister);
+    }
+    TEST(e->data->e_status, 200);
+    TEST_1(sip = sip_object(e->data->e_msg));
+    TEST_1(!sip->sip_contact);
+    TEST_1(!e->next);
+    free_events_in_list(ctx, a->events);
+    nua_handle_destroy(a->reg->nh), a->reg->nh = NULL;
+  }
+
+  if (print_headings)
+    printf("TEST NUA-13.1: PASSED\n");
+
+  if (print_headings)
+    printf("TEST NUA-13.2: un-REGISTER b\n");
+
+  if (b->reg->nh) {
+    free_events_in_list(ctx, b->events);
+    UNREGISTER(b, NULL, b->reg->nh, TAG_END());
+    run_b_until(ctx, -1, save_until_final_response);
+    TEST_1(e = b->events->head);
+    TEST_E(e->data->e_event, nua_r_unregister);
+    if (e->data->e_status == 100) {
+      TEST_1(e = e->next);
+      TEST_E(e->data->e_event, nua_r_unregister);
+    }
+    TEST(e->data->e_status, 200);
+    TEST_1(sip = sip_object(e->data->e_msg));
+    TEST_1(!sip->sip_contact);
+    TEST_1(!e->next);
+    free_events_in_list(ctx, b->events);
+    nua_handle_destroy(b->reg->nh), b->reg->nh = NULL;
+  }
+  if (print_headings)
+    printf("TEST NUA-13.2: PASSED\n");
+
+  if (print_headings)
+    printf("TEST NUA-13.3: un-REGISTER c\n");
+
+  /* Unregister using another handle */
+  free_events_in_list(ctx, c->events);
+  TEST_1(c->call->nh = nua_handle(c->nua, c->call, TAG_END()));
+  UNREGISTER(c, c->call, c->call->nh, SIPTAG_TO(c->to), SIPTAG_FROM(c->to),
+	     NUTAG_M_DISPLAY("C"),
+	     NUTAG_M_USERNAME("c"),
+	     NUTAG_M_PARAMS("c=1"),
+	     TAG_END());
+  run_c_until(ctx, -1, save_until_final_response);
+
+  TEST_1(e = c->events->head);
+  TEST_E(e->data->e_event, nua_r_unregister);
+  TEST_1(sip = sip_object(e->data->e_msg));
+  TEST(e->data->e_status, 401);
+  TEST(sip->sip_status->st_status, 401);
+  TEST_1(!sip->sip_contact);
+  TEST_1(!e->next);
+  free_events_in_list(ctx, c->events);
+
+  AUTHENTICATE(c, c->call, c->call->nh,
+	       NUTAG_AUTH("Digest:\"test-proxy\":charlie:secret"), TAG_END());
+  run_c_until(ctx, -1, save_until_final_response);
+
+  TEST_1(e = c->events->head);
+  TEST_E(e->data->e_event, nua_r_unregister);
+  TEST(e->data->e_status, 200);
+  TEST_1(sip = sip_object(e->data->e_msg));
+  TEST_1(!sip->sip_contact);
+  TEST_1(!e->next);
+  free_events_in_list(ctx, c->events);
+  nua_handle_destroy(c->call->nh), c->call->nh = NULL;
+
+  if (c->reg->nh) {
+    UNREGISTER(c, NULL, c->reg->nh, TAG_END());
+    run_c_until(ctx, -1, save_until_final_response);
+    TEST_1(e = c->events->head);
+    TEST_E(e->data->e_event, nua_r_unregister);
+    if (e->data->e_status == 100) {
+      TEST_1(e = e->next);
+      TEST_E(e->data->e_event, nua_r_unregister);
+    }
+    if (e->data->e_status == 401) {
+      TEST_1(!e->next);
+      free_events_in_list(ctx, c->events);
+      AUTHENTICATE(c, NULL, c->reg->nh,
+		   NUTAG_AUTH("Digest:\"test-proxy\":charlie:secret"), TAG_END());
+      run_c_until(ctx, -1, save_until_final_response);
+      TEST_1(e = c->events->head);
+      TEST_E(e->data->e_event, nua_r_unregister);
+    }
+    TEST(e->data->e_status, 200);
+    TEST_1(sip = sip_object(e->data->e_msg));
+    TEST_1(sip->sip_from->a_url->url_user);
+    TEST_1(!sip->sip_contact);
+    TEST_1(!e->next);
+    free_events_in_list(ctx, c->events);
+    nua_handle_destroy(c->reg->nh), c->reg->nh = NULL;
+  }
+
+  if (print_headings)
+    printf("TEST NUA-13.3: PASSED\n");
+
+  END();
+}

Added: freeswitch/trunk/libs/sofia-sip/tests/test_session_timer.c
==============================================================================
--- (empty file)
+++ freeswitch/trunk/libs/sofia-sip/tests/test_session_timer.c	Wed May 14 15:10:54 2008
@@ -0,0 +1,434 @@
+/*
+ * This file is part of the Sofia-SIP package
+ *
+ * Copyright (C) 2005 Nokia Corporation.
+ *
+ * Contact: Pekka Pessi <pekka.pessi at nokia.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA
+ *
+ */
+
+/**@CFILE test_session_timer.c
+ * @brief NUA-8 tests: Session timer, UPDATE
+ *
+ * @author Pekka Pessi <Pekka.Pessi at nokia.com>
+ * @author Martti Mela <Martti Mela at nokia.com>
+ *
+ * @date Created: Wed Aug 17 12:12:12 EEST 2005 ppessi
+ */
+
+#include "config.h"
+
+#include "test_nua.h"
+#include <sofia-sip/su_tag_class.h>
+
+#if HAVE_FUNC
+#elif HAVE_FUNCTION
+#define __func__ __FUNCTION__
+#else
+#define __func__ "test_session_timer"
+#endif
+
+/* ======================================================================== */
+
+static size_t remove_update(void *a, void *message, size_t len);
+
+int test_session_timer(struct context *ctx)
+{
+  BEGIN();
+
+  struct endpoint *a = &ctx->a,  *b = &ctx->b;
+  struct call *a_call = a->call, *b_call = b->call;
+  struct event *e;
+  sip_t const *sip;
+
+/* Session timer test:
+
+   A	      P		B
+   |-------INVITE------>|
+   |<----100 Trying-----|
+   |			|
+   |<----180 Ringing----|
+   |			|
+   |<------200 OK-------|
+   |------------------->|
+   |			|
+   |			|
+   |--INVITE->| 	|
+   |<--422----|		|
+   |---ACK--->|		|
+
+*/
+
+  if (print_headings)
+    printf("TEST NUA-8.1.1: Session timers\n");
+
+  a_call->sdp = "m=audio 5008 RTP/AVP 8";
+  b_call->sdp = "m=audio 5010 RTP/AVP 0 8";
+
+  /* We negotiate session timer of 6 second */
+  /* Disable session timer from proxy */
+  test_proxy_set_session_timer(ctx->p, 0, 0); 
+
+  nua_set_params(ctx->b.nua,
+		 NUTAG_SESSION_REFRESHER(nua_any_refresher),
+		 NUTAG_MIN_SE(1),
+		 NUTAG_SESSION_TIMER(6),
+		 NTATAG_SIP_T1X64(8000),
+		 TAG_END());
+
+  run_b_until(ctx, nua_r_set_params, until_final_response);
+
+  TEST_1(a_call->nh = nua_handle(a->nua, a_call, SIPTAG_TO(b->to), TAG_END()));
+
+  INVITE(a, a_call, a_call->nh,
+	 TAG_IF(!ctx->proxy_tests, NUTAG_URL(b->contact->m_url)),
+	 SOATAG_USER_SDP_STR(a_call->sdp),
+	 SIPTAG_SUPPORTED_STR("100rel"),
+	 TAG_END());
+
+  run_ab_until(ctx, -1, until_ready, -1, accept_call);
+
+  /* Client transitions:
+     INIT -(C1)-> CALLING: nua_i_state
+     CALLING -(C2)-> PROCEEDING: nua_r_invite, nua_i_state
+     PROCEEDING -(C3+C4)-> READY: nua_r_invite, nua_i_state
+  */
+  TEST_1(e = a->events->head); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_calling); /* CALLING */
+  TEST_1(is_offer_sent(e->data->e_tags));
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_r_invite);
+  TEST(e->data->e_status, 180);
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_proceeding); /* PROCEEDING */
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_r_invite);
+  TEST(e->data->e_status, 200);
+  TEST_1(sip = sip_object(e->data->e_msg));
+  TEST_1(sip->sip_session_expires);
+  TEST_S(sip->sip_session_expires->x_refresher, "uas");
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_ready); /* READY */
+  TEST_1(is_answer_recv(e->data->e_tags));
+  TEST_1(!e->next);
+  free_events_in_list(ctx, a->events);
+
+  /*
+   Server transitions:
+   INIT -(S1)-> RECEIVED: nua_i_invite, nua_i_state
+   RECEIVED -(S2a)-> EARLY: nua_respond(), nua_i_state
+   EARLY -(S3b)-> COMPLETED: nua_respond(), nua_i_state
+   COMPLETED -(S4)-> READY: nua_i_ack, nua_i_state
+  */
+  TEST_1(e = b->events->head); TEST_E(e->data->e_event, nua_i_invite);
+  TEST(e->data->e_status, 100);
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_received); /* RECEIVED */
+  TEST_1(is_offer_recv(e->data->e_tags));
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_early); /* EARLY */
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_completed); /* COMPLETED */
+  TEST_1(is_answer_sent(e->data->e_tags));
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_ack);
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_ready); /* READY */
+  TEST_1(!e->next);
+  free_events_in_list(ctx, b->events);
+
+  if (print_headings)
+    printf("TEST NUA-8.1.1: PASSED\n");
+
+  if (ctx->expensive) {
+    nua_set_hparams(a_call->nh,
+		    NUTAG_SUPPORTED("timer"),
+		    NUTAG_MIN_SE(1),
+		    NUTAG_SESSION_TIMER(5),
+		    TAG_END());
+    run_a_until(ctx, nua_r_set_params, until_final_response);
+
+    if (print_headings)
+      printf("TEST NUA-8.1.2: Wait for refresh using INVITE\n");
+
+    run_ab_until(ctx, -1, until_ready, -1, until_ready);
+
+    free_events_in_list(ctx, a->events);
+    free_events_in_list(ctx, b->events);
+
+    if (print_headings)
+      printf("TEST NUA-8.1.2: PASSED\n");
+
+    nua_set_hparams(b_call->nh,
+		    NUTAG_UPDATE_REFRESH(1),
+		    TAG_END());
+    run_b_until(ctx, nua_r_set_params, until_final_response);
+
+    if (print_headings)
+      printf("TEST NUA-8.1.3: Wait for refresh using UPDATE\n");
+
+    run_ab_until(ctx, -1, until_ready, -1, until_ready);
+
+    TEST_1(e = a->events->head); TEST_E(e->data->e_event, nua_i_update);
+
+    free_events_in_list(ctx, a->events);
+    free_events_in_list(ctx, b->events);
+
+    if (print_headings)
+      printf("TEST NUA-8.1.3: PASSED\n");
+
+    if (ctx->nat) {
+      struct nat_filter *f;
+
+      if (print_headings)
+	printf("TEST NUA-8.1.4: filter UPDATE, wait until session expires\n");
+
+      f = test_nat_add_filter(ctx->nat, remove_update, NULL, nat_inbound);
+      TEST_1(f);
+
+      run_ab_until(ctx, -1, until_terminated, -1, until_terminated);
+      
+      TEST_1(e = a->events->head); TEST_E(e->data->e_event, nua_r_bye);
+
+      free_events_in_list(ctx, a->events);
+      free_events_in_list(ctx, b->events);
+
+      nua_handle_destroy(a_call->nh), a_call->nh = NULL;
+      nua_handle_destroy(b_call->nh), b_call->nh = NULL;
+
+      test_nat_remove_filter(ctx->nat, f);
+
+      if (print_headings)
+	printf("TEST NUA-8.1.4: PASSED\n");
+    }
+  }
+
+  if (b_call->nh) {
+    if (print_headings)
+      printf("TEST NUA-8.1.9: Terminate first session timer call\n");
+
+    BYE(b, b_call, b_call->nh, TAG_END());
+    run_ab_until(ctx, -1, until_terminated, -1, until_terminated);
+    free_events_in_list(ctx, a->events);
+    free_events_in_list(ctx, b->events);
+
+    nua_handle_destroy(a_call->nh), a_call->nh = NULL;
+    nua_handle_destroy(b_call->nh), b_call->nh = NULL;
+
+    if (print_headings)
+      printf("TEST NUA-8.1.9: PASSED\n");
+  }
+
+  /*
+   |			|
+   |-------INVITE------>|
+   |<-------422---------|
+   |--------ACK-------->|
+   |			|
+   |-------INVITE------>|
+   |<----100 Trying-----|
+   |			|
+   |<----180 Ringing----|
+   |			|
+   |<------200 OK-------|
+   |--------ACK-------->|
+   |			|
+   |<-------BYE---------|
+   |-------200 OK-------|
+   |			|
+  */
+
+  if (print_headings)
+    printf("TEST NUA-8.2: Session timers negotiation\n");
+
+  test_proxy_set_session_timer(ctx->p, 180, 90);
+
+  nua_set_params(a->nua,
+		 NUTAG_SUPPORTED("timer"),
+		 TAG_END());
+
+  run_a_until(ctx, nua_r_set_params, until_final_response);
+
+  nua_set_params(b->nua,
+		 NUTAG_AUTOANSWER(0),
+		 NUTAG_MIN_SE(120),
+		 NTATAG_SIP_T1X64(2000),
+		 TAG_END());
+
+  run_b_until(ctx, nua_r_set_params, until_final_response);
+
+  TEST_1(a_call->nh = nua_handle(a->nua, a_call, SIPTAG_TO(b->to),
+				 TAG_END()));
+
+  INVITE(a, a_call, a_call->nh,
+	 TAG_IF(!ctx->proxy_tests, NUTAG_URL(b->contact->m_url)),
+	 SOATAG_USER_SDP_STR(a_call->sdp),
+	 SIPTAG_SUPPORTED_STR("100rel, timer"),
+	 NUTAG_SESSION_TIMER(4),
+	 NUTAG_MIN_SE(3),
+	 TAG_END());
+
+  run_ab_until(ctx, -1, until_ready, -1, accept_call);
+
+  /* Client transitions:
+     INIT -(C1)-> CALLING: nua_invite(), nua_i_state
+     CALLING -(C6a)-> (TERMINATED/INIT): nua_r_invite
+     when testing with proxy, second 422 when call reaches UAS:
+       (INIT) -(C1)-> CALLING: nua_i_state
+       CALLING -(C6a)-> (TERMINATED/INIT): nua_r_invite
+     (INIT) -(C1)-> CALLING: nua_i_state
+     CALLING -(C2)-> PROCEEDING: nua_r_invite, nua_i_state
+     PROCEEDING -(C3+C4)-> READY: nua_r_invite, nua_i_state
+  */
+  TEST_1(e = a->events->head); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_calling); /* CALLING */
+  TEST_1(is_offer_sent(e->data->e_tags));
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_r_invite);
+  TEST(e->data->e_status, 100);
+  TEST(sip_object(e->data->e_msg)->sip_status->st_status, 422);
+
+  if (ctx->proxy_tests) {
+    TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+    TEST(callstate(e->data->e_tags), nua_callstate_calling); /* CALLING */
+    TEST_1(is_offer_sent(e->data->e_tags));
+    TEST_1(e = e->next); TEST_E(e->data->e_event, nua_r_invite);
+    TEST(e->data->e_status, 100);
+    TEST(sip_object(e->data->e_msg)->sip_status->st_status, 422);
+  }
+
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_calling); /* CALLING */
+  TEST_1(is_offer_sent(e->data->e_tags));
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_r_invite);
+  TEST(e->data->e_status, 180);
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_proceeding); /* PROCEEDING */
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_r_invite);
+  TEST(e->data->e_status, 200);
+  TEST_1(sip = sip_object(e->data->e_msg));
+  TEST_1(sip->sip_session_expires);
+  TEST(sip->sip_session_expires->x_delta, 120);
+  TEST_S(sip->sip_session_expires->x_refresher, "uac");
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_ready); /* READY */
+  TEST_1(is_answer_recv(e->data->e_tags));
+  TEST_1(!e->next);
+  free_events_in_list(ctx, a->events);
+
+  /*
+   Server transitions:
+   INIT -(S1)-> RECEIVED: nua_i_invite, nua_i_state
+   RECEIVED -(S2a)-> EARLY: nua_respond(), nua_i_state
+   EARLY -(S3b)-> COMPLETED: nua_respond(), nua_i_state
+   COMPLETED -(S4)-> READY: nua_i_ack, nua_i_state
+  */
+  TEST_1(e = b->events->head); TEST_E(e->data->e_event, nua_i_invite);
+  TEST(e->data->e_status, 100);
+  TEST_1(sip = sip_object(e->data->e_msg));
+  TEST_1(sip->sip_session_expires);
+  TEST_1(!sip->sip_session_expires->x_refresher);
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_received); /* RECEIVED */
+  TEST_1(is_offer_recv(e->data->e_tags));
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_early); /* EARLY */
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_completed); /* COMPLETED */
+  TEST_1(is_answer_sent(e->data->e_tags));
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_ack);
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_ready); /* READY */
+  TEST_1(!e->next);
+  free_events_in_list(ctx, b->events);
+
+  if (print_headings)
+    printf("TEST NUA-8.2: PASSED\n");
+
+  if (print_headings)
+    printf("TEST NUA-8.3: UPDATE with session timer headers\n");
+
+  UPDATE(b, b_call, b_call->nh, TAG_END());
+  run_ab_until(ctx, -1, until_ready, -1, until_ready);
+
+  /* Events from B (who sent UPDATE) */
+  TEST_1(e = b->events->head); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_calling); /* CALLING */
+  TEST_1(is_offer_sent(e->data->e_tags));
+  if (!e->next)
+    run_b_until(ctx, -1, until_ready);
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_r_update);
+  TEST(e->data->e_status, 200);
+  TEST_1(sip = sip_object(e->data->e_msg));
+  TEST_1(sip->sip_session_expires);
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_ready); /* READY */
+  TEST_1(is_answer_recv(e->data->e_tags));
+  TEST_1(!e->next);
+  free_events_in_list(ctx, b->events);
+
+  /* Events from A (who received UPDATE) */
+  TEST_1(e = a->events->head); TEST_E(e->data->e_event, nua_i_update);
+  TEST(e->data->e_status, 200);
+  TEST_1(sip = sip_object(e->data->e_msg));
+  TEST_1(sip->sip_session_expires);
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_ready); /* READY */
+  TEST_1(is_offer_recv(e->data->e_tags));
+  TEST_1(is_answer_sent(e->data->e_tags));
+  TEST_1(!e->next);
+  free_events_in_list(ctx, a->events);
+
+  BYE(b, b_call, b_call->nh, TAG_END());
+  run_ab_until(ctx, -1, until_terminated, -1, until_terminated);
+
+  /* B transitions:
+   READY --(T2)--> TERMINATING: nua_bye()
+   TERMINATING --(T3)--> TERMINATED: nua_r_bye, nua_i_state
+  */
+  TEST_1(e = b->events->head); TEST_E(e->data->e_event, nua_r_bye);
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_terminated); /* TERMINATED */
+  TEST_1(!e->next);
+  free_events_in_list(ctx, b->events);
+
+  /* A: READY -(T1)-> TERMINATED: nua_i_bye, nua_i_state */
+  TEST_1(e = a->events->head); TEST_E(e->data->e_event, nua_i_bye);
+  TEST(e->data->e_status, 200);
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_terminated); /* TERMINATED */
+  TEST_1(!e->next);
+  free_events_in_list(ctx, a->events);
+
+  nua_handle_destroy(a_call->nh), a_call->nh = NULL;
+  nua_handle_destroy(b_call->nh), b_call->nh = NULL;
+
+  if (print_headings)
+    printf("TEST NUA-8.3: PASSED\n");
+
+  END();
+}
+
+static
+size_t remove_update(void *a, void *message, size_t len)
+{
+  (void)a;
+
+  if (strncmp("UPDATE ", message, 7) == 0) {
+    return 0;
+  }
+
+  return len;
+}
+

Added: freeswitch/trunk/libs/sofia-sip/tests/test_simple.c
==============================================================================
--- (empty file)
+++ freeswitch/trunk/libs/sofia-sip/tests/test_simple.c	Wed May 14 15:10:54 2008
@@ -0,0 +1,2099 @@
+/*
+ * This file is part of the Sofia-SIP package
+ *
+ * Copyright (C) 2005 Nokia Corporation.
+ *
+ * Contact: Pekka Pessi <pekka.pessi at nokia.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA
+ *
+ */
+
+/**@CFILE test_nua_simple.c
+ * @brief NUA-11: Test SIMPLE methods: MESSAGE, PUBLISH and SUBSCRIBE/NOTIFY.
+ *
+ * @author Pekka Pessi <Pekka.Pessi at nokia.com>
+ * @author Martti Mela <Martti Mela at nokia.com>
+ *
+ * @date Created: Wed Aug 17 12:12:12 EEST 2005 ppessi
+ */
+
+#include "config.h"
+
+#include "test_nua.h"
+#include <sofia-sip/su_tag_class.h>
+
+#if HAVE_FUNC
+#elif HAVE_FUNCTION
+#define __func__ __FUNCTION__
+#else
+#define __func__ "test_simple"
+#endif
+
+extern int accept_request(CONDITION_PARAMS);
+
+int save_until_nth_final_response(CONDITION_PARAMS);
+int accept_n_notifys(CONDITION_PARAMS);
+
+int test_message(struct context *ctx)
+{
+  BEGIN();
+
+  struct endpoint *a = &ctx->a,  *b = &ctx->b;
+  struct call *a_call = a->call, *b_call = b->call;
+  struct event *e;
+  sip_t const *sip;
+  url_t url[1];
+
+/* Message test
+
+   A			B
+   |-------MESSAGE----->|
+   |<-------200---------|
+   |			|
+
+*/
+  if (print_headings)
+    printf("TEST NUA-11.1.1: MESSAGE\n");
+
+  if (ctx->proxy_tests)
+    *url = *b->to->a_url;
+  else
+    *url = *b->contact->m_url;
+
+  /* Test that query part is included in request sent to B */
+  url->url_headers = "organization=United%20Testers";
+
+  TEST_1(a_call->nh = nua_handle(a->nua, a_call, TAG_END()));
+
+  MESSAGE(a, a_call, a_call->nh,
+	  NUTAG_URL(url),
+	  SIPTAG_SUBJECT_STR("NUA-11.1.1"),
+	  SIPTAG_CONTENT_TYPE_STR("text/plain"),
+	  SIPTAG_PAYLOAD_STR("Hello hellO!\n"),
+	  TAG_END());
+
+  run_ab_until(ctx, -1, save_until_final_response, -1, save_until_received);
+
+  /* Client events:
+     nua_message(), nua_r_message
+  */
+  TEST_1(e = a->events->head); TEST_E(e->data->e_event, nua_r_message);
+  TEST(e->data->e_status, 200);
+  TEST_1(!e->next);
+
+  /*
+   Server events:
+   nua_i_message
+  */
+  TEST_1(e = b->events->head); TEST_E(e->data->e_event, nua_i_message);
+  TEST(e->data->e_status, 200);
+  TEST_1(sip = sip_object(e->data->e_msg));
+  TEST_1(sip->sip_subject && sip->sip_subject->g_string);
+  TEST_S(sip->sip_subject->g_string, "NUA-11.1.1");
+  TEST_1(sip->sip_organization);
+  TEST_S(sip->sip_organization->g_string, "United Testers");
+  TEST_1(!e->next);
+
+  free_events_in_list(ctx, a->events);
+  nua_handle_destroy(a_call->nh), a_call->nh = NULL;
+
+  free_events_in_list(ctx, b->events);
+  nua_handle_destroy(b_call->nh), b_call->nh = NULL;
+
+  if (print_headings)
+    printf("TEST NUA-11.1.1: PASSED\n");
+
+/* MESSAGE as application method
+
+   A			B
+   |-------MESSAGE----->|
+   |<-------202---------|
+   |			|
+*/
+
+  if (print_headings)
+    printf("TEST NUA-11.1.2: MESSAGE\n");
+
+  nua_set_params(b->nua, NUTAG_APPL_METHOD("MESSAGE"), TAG_END());
+  run_b_until(ctx, nua_r_set_params, until_final_response);
+
+  TEST_1(a_call->nh = nua_handle(a->nua, a_call, TAG_END()));
+
+  MESSAGE(a, a_call, a_call->nh,
+	  NUTAG_URL(url),
+	  SIPTAG_SUBJECT_STR("NUA-11.1.2"),
+	  SIPTAG_CONTENT_TYPE_STR("text/plain"),
+	  SIPTAG_PAYLOAD_STR("Hello hellO!\n"),
+	  TAG_END());
+
+  run_ab_until(ctx, -1, save_until_final_response, -1, accept_request);
+
+  /* Client events:
+     nua_message(), nua_r_message
+  */
+  TEST_1(e = a->events->head); TEST_E(e->data->e_event, nua_r_message);
+  TEST(e->data->e_status, 202);
+  TEST_1(sip = sip_object(e->data->e_msg));
+  TEST_1(sip_user_agent(sip)); 
+  TEST_S(sip_user_agent(sip)->g_value, "007");
+  TEST_1(!e->next);
+
+  /*
+   Server events:
+   nua_i_message
+  */
+  TEST_1(e = b->events->head); TEST_E(e->data->e_event, nua_i_message);
+  TEST(e->data->e_status, 100);
+  TEST_1(sip = sip_object(e->data->e_msg));
+  TEST_1(sip->sip_subject && sip->sip_subject->g_string);
+  TEST_S(sip->sip_subject->g_string, "NUA-11.1.2");
+  TEST_1(sip->sip_organization);
+  TEST_S(sip->sip_organization->g_string, "United Testers");
+  TEST_1(!e->next);
+
+  free_events_in_list(ctx, a->events);
+  nua_handle_destroy(a_call->nh), a_call->nh = NULL;
+
+  free_events_in_list(ctx, b->events);
+  nua_handle_destroy(b_call->nh), b_call->nh = NULL;
+
+  if (print_headings)
+    printf("TEST NUA-11.1.2: PASSED\n");
+
+
+/* Message test
+
+   A
+   |-------MESSAGE--\
+   |<---------------/
+   |--------200-----\
+   |<---------------/
+   |
+
+*/
+  if (print_headings)
+    printf("TEST NUA-11.2: MESSAGE to myself\n");
+
+  TEST_1(a_call->nh = nua_handle(a->nua, a_call, SIPTAG_TO(a->to), TAG_END()));
+
+  MESSAGE(a, a_call, a_call->nh,
+	  /* We cannot reach us by using our contact! */
+	  NUTAG_URL(!ctx->p && !ctx->proxy_tests ? a->contact->m_url : NULL),
+	  SIPTAG_SUBJECT_STR("NUA-11.2"),
+	  SIPTAG_CONTENT_TYPE_STR("text/plain"),
+	  SIPTAG_PAYLOAD_STR("Hello hellO!\n"),
+	  TAG_END());
+
+  run_a_until(ctx, -1, save_until_final_response);
+
+  /* Events:
+     nua_message(), nua_i_message, nua_r_message
+  */
+  TEST_1(e = a->specials->head);
+  while (e->data->e_event == nua_i_outbound)
+    e = e->next;
+  TEST_E(e->data->e_event, nua_i_message);
+  TEST(e->data->e_status, 200);
+  TEST_1(sip = sip_object(e->data->e_msg));
+  TEST_1(sip->sip_subject && sip->sip_subject->g_string);
+  TEST_S(sip->sip_subject->g_string, "NUA-11.2");
+
+  TEST_1(e = a->events->head); TEST_E(e->data->e_event, nua_r_message);
+  TEST(e->data->e_status, 200);
+  TEST_1(!e->next);
+
+  free_events_in_list(ctx, a->events);
+  free_events_in_list(ctx, a->specials);
+  nua_handle_destroy(a_call->nh), a_call->nh = NULL;
+
+  if (print_headings)
+    printf("TEST NUA-11.2: PASSED\n");
+
+  END();
+}
+
+int accept_request(CONDITION_PARAMS)
+{
+  msg_t *with = nua_current_request(nua);
+  
+  if (!(check_handle(ep, call, nh, SIP_500_INTERNAL_SERVER_ERROR)))
+    return 0;
+
+  save_event_in_list(ctx, event, ep, call);
+
+  if (status < 200) {
+    RESPOND(ep, call, nh, SIP_202_ACCEPTED,
+	    NUTAG_WITH(with),
+	    SIPTAG_USER_AGENT_STR("007"),
+	    TAG_END());
+    return 1;
+  }
+
+  return 0;
+}
+
+char const *test_etag = "tagtag";
+
+int respond_with_etag(CONDITION_PARAMS)
+{
+  msg_t *with = nua_current_request(nua);
+
+  if (!(check_handle(ep, call, nh, SIP_500_INTERNAL_SERVER_ERROR)))
+    return 1;
+
+  save_event_in_list(ctx, event, ep, call);
+
+  switch (event) {
+    char const *etag;
+  case nua_i_publish:
+    etag = sip->sip_if_match ? sip->sip_if_match->g_value : NULL;
+    if (sip->sip_if_match && (etag == NULL || strcmp(etag, test_etag))) {
+      RESPOND(ep, call, nh, SIP_412_PRECONDITION_FAILED,
+	      NUTAG_WITH(with),
+	      TAG_END());
+    } 
+    else {
+      RESPOND(ep, call, nh, SIP_200_OK,
+	      NUTAG_WITH(with),
+	      SIPTAG_ETAG_STR(test_etag),
+	      SIPTAG_EXPIRES_STR("3600"),
+	      SIPTAG_EXPIRES(sip->sip_expires),	/* overrides 3600 */
+	      TAG_END());
+    }
+    return 1;
+  default:
+    return 0;
+  }
+}
+
+static int close_handle(CONDITION_PARAMS)
+{
+  if (call->nh == nh)
+    call->nh = NULL;
+  nua_handle_destroy(nh);
+  return 1;
+}
+
+int test_publish(struct context *ctx)
+{
+  BEGIN();
+
+  struct endpoint *a = &ctx->a,  *b = &ctx->b;
+  struct call *a_call = a->call, *b_call = b->call;
+  struct event *e;
+  sip_t const *sip;
+
+
+/* PUBLISH test
+
+   A			B
+   |-------PUBLISH----->|
+   |<-------405---------| (method not allowed by default)
+   |			|
+   |-------PUBLISH----->|
+   |<-------501---------| (no events allowed)
+   |			|
+   |-------PUBLISH----->|
+   |<-------489---------| (event not allowed by default)
+   |			|
+   |-------PUBLISH----->|
+   |<-------200---------| (event allowed, responded)
+   |			|
+   |-----un-PUBLISH---->|
+   |<-------200---------| (event allowed, responded)
+
+*/
+  if (print_headings)
+    printf("TEST NUA-11.3: PUBLISH\n");
+
+  TEST_1(a_call->nh = nua_handle(a->nua, a_call, SIPTAG_TO(b->to), TAG_END()));
+
+  PUBLISH(a, a_call, a_call->nh,
+	  TAG_IF(!ctx->proxy_tests, NUTAG_URL(b->contact->m_url)),
+	  SIPTAG_EVENT_STR("presence"),
+	  SIPTAG_CONTENT_TYPE_STR("text/urllist"),
+	  SIPTAG_PAYLOAD_STR("sip:example.com\n"),
+	  TAG_END());
+
+  run_ab_until(ctx, -1, save_until_final_response, -1, NULL);
+
+  /* Client events:
+     nua_publish(), nua_r_publish
+  */
+  TEST_1(e = a->events->head); TEST_E(e->data->e_event, nua_r_publish);
+  TEST(e->data->e_status, 405);
+  TEST_1(!e->next);
+
+  free_events_in_list(ctx, a->events);
+  nua_handle_destroy(a_call->nh), a_call->nh = NULL;
+
+  free_events_in_list(ctx, b->events);
+  nua_handle_destroy(b_call->nh), b_call->nh = NULL;
+
+  nua_set_params(b->nua, NUTAG_ALLOW("PUBLISH"), TAG_END());
+
+  run_b_until(ctx, nua_r_set_params, until_final_response);
+
+  TEST_1(a_call->nh = nua_handle(a->nua, a_call, SIPTAG_TO(b->to), TAG_END()));
+
+  PUBLISH(a, a_call, a_call->nh,
+	  TAG_IF(!ctx->proxy_tests, NUTAG_URL(b->contact->m_url)),
+	  SIPTAG_EVENT_STR("presence"),
+	  SIPTAG_CONTENT_TYPE_STR("text/urllist"),
+	  SIPTAG_PAYLOAD_STR("sip:example.com\n"),
+	  TAG_END());
+
+  run_a_until(ctx, -1, save_until_final_response);
+
+  /* Client events:
+     nua_publish(), nua_r_publish
+  */
+  TEST_1(e = a->events->head); TEST_E(e->data->e_event, nua_r_publish);
+  TEST(e->data->e_status, 501);	/* Not implemented */
+  TEST_1(!e->next);
+
+  free_events_in_list(ctx, a->events);
+  nua_handle_destroy(a_call->nh), a_call->nh = NULL;
+
+  /* Allow presence event */
+
+  nua_set_params(b->nua, NUTAG_ALLOW_EVENTS("presence"), TAG_END());
+
+  run_b_until(ctx, nua_r_set_params, until_final_response);
+
+  TEST_1(a_call->nh = nua_handle(a->nua, a_call, SIPTAG_TO(b->to), TAG_END()));
+
+  PUBLISH(a, a_call, a_call->nh,
+	  TAG_IF(!ctx->proxy_tests, NUTAG_URL(b->contact->m_url)),
+	  SIPTAG_EVENT_STR("reg"),
+	  SIPTAG_CONTENT_TYPE_STR("text/urllist"),
+	  SIPTAG_PAYLOAD_STR("sip:example.com\n"),
+	  TAG_END());
+
+  run_a_until(ctx, -1, save_until_final_response);
+
+  /* Client events:
+     nua_publish(), nua_r_publish
+  */
+  TEST_1(e = a->events->head); TEST_E(e->data->e_event, nua_r_publish);
+  TEST(e->data->e_status, 489);	/* Bad Event */
+  TEST_1(!e->next);
+
+  free_events_in_list(ctx, a->events);
+  nua_handle_destroy(a_call->nh), a_call->nh = NULL;
+
+  TEST_1(a_call->nh = nua_handle(a->nua, a_call, SIPTAG_TO(b->to), TAG_END()));
+
+  PUBLISH(a, a_call, a_call->nh,
+	  TAG_IF(!ctx->proxy_tests, NUTAG_URL(b->contact->m_url)),
+	  SIPTAG_EVENT_STR("presence"),
+	  SIPTAG_CONTENT_TYPE_STR("text/urllist"),
+	  SIPTAG_PAYLOAD_STR("sip:example.com\n"),
+	  SIPTAG_EXPIRES_STR("5"),
+	  TAG_END());
+
+  run_ab_until(ctx, -1, save_until_final_response, -1, respond_with_etag);
+
+  /* Client events:
+     nua_publish(), nua_r_publish
+  */
+  TEST_1(e = a->events->head); TEST_E(e->data->e_event, nua_r_publish);
+  TEST(e->data->e_status, 200);
+  TEST_1(sip = sip_object(e->data->e_msg));
+  TEST_1(sip->sip_etag);
+  TEST_S(sip->sip_etag->g_string, test_etag);
+  TEST_1(!e->next);
+
+  /*
+   Server events:
+   nua_i_publish
+  */
+  TEST_1(e = b->events->head); TEST_E(e->data->e_event, nua_i_publish);
+  TEST(e->data->e_status, 100);
+  TEST_1(!e->next);
+
+  free_events_in_list(ctx, a->events);
+  free_events_in_list(ctx, b->events);
+  nua_handle_destroy(b_call->nh), b_call->nh = NULL;
+
+  if (!ctx->expensive && 0)
+    goto skip_republish;
+
+  run_ab_until(ctx, -1, save_until_final_response, -1, respond_with_etag); 
+
+  /* Client events: nua_r_publish
+  */
+  TEST_1(e = a->events->head); TEST_E(e->data->e_event, nua_r_publish);
+  TEST(e->data->e_status, 200);
+  TEST_1(sip = sip_object(e->data->e_msg));
+  TEST_1(sip->sip_etag);
+  TEST_S(sip->sip_etag->g_string, test_etag);
+  TEST_1(!e->next);
+
+  free_events_in_list(ctx, a->events);
+ 
+  /*
+   Server events:
+   nua_i_publish
+  */
+  TEST_1(e = b->events->head); TEST_E(e->data->e_event, nua_i_publish);
+  TEST(e->data->e_status, 100);
+  TEST_1(sip = sip_object(e->data->e_msg));
+  TEST_1(sip = sip_object(e->data->e_msg));
+  TEST_1(sip->sip_if_match);
+  TEST_S(sip->sip_if_match->g_string, "tagtag");
+  TEST_1(!sip->sip_content_type);
+  TEST_1(!sip->sip_payload);
+  TEST_1(!e->next);
+
+  free_events_in_list(ctx, b->events);
+  nua_handle_destroy(b_call->nh), b_call->nh = NULL;
+
+ skip_republish:
+
+  UNPUBLISH(a, a_call, a_call->nh, TAG_END());
+
+  run_ab_until(ctx, -1, save_until_final_response, -1, respond_with_etag);
+
+  /* Client events:
+     nua_publish(), nua_r_publish
+  */
+  TEST_1(e = a->events->head); TEST_E(e->data->e_event, nua_r_unpublish);
+  TEST(e->data->e_status, 200);
+  TEST_1(!e->next);
+
+  /*
+   Server events:
+   nua_i_publish
+  */
+  TEST_1(e = b->events->head); TEST_E(e->data->e_event, nua_i_publish);
+  TEST(e->data->e_status, 100);
+  TEST_1(!e->next);
+
+  free_events_in_list(ctx, a->events);
+  free_events_in_list(ctx, b->events);
+
+  nua_handle_destroy(a_call->nh), a_call->nh = NULL;
+  nua_handle_destroy(b_call->nh), b_call->nh = NULL;
+
+  TEST_1(a_call->nh = nua_handle(a->nua, a_call, SIPTAG_TO(b->to), TAG_END()));
+
+  /* Let server close handle without responding to PUBLISH */ 
+  PUBLISH(a, a_call, a_call->nh,
+	  TAG_IF(!ctx->proxy_tests, NUTAG_URL(b->contact->m_url)),
+	  SIPTAG_EVENT_STR("presence"),
+	  SIPTAG_CONTENT_TYPE_STR("text/urllist"),
+	  SIPTAG_PAYLOAD_STR("sip:example.com\n"),
+	  TAG_END());
+
+  run_ab_until(ctx, -1, save_until_final_response, -1, close_handle);
+
+  /* Client events:
+     nua_publish(), nua_r_publish
+  */
+  TEST_1(e = a->events->head); TEST_E(e->data->e_event, nua_r_publish);
+  TEST(e->data->e_status, 500);
+  TEST_1(!e->next);
+
+  free_events_in_list(ctx, a->events);
+
+  nua_handle_destroy(a_call->nh), a_call->nh = NULL;
+
+  /* No Event header */
+
+  TEST_1(a_call->nh = nua_handle(a->nua, a_call, SIPTAG_TO(b->to), TAG_END()));
+
+  PUBLISH(a, a_call, a_call->nh,
+	  TAG_IF(!ctx->proxy_tests, NUTAG_URL(b->contact->m_url)),
+	  SIPTAG_CONTENT_TYPE_STR("text/urllist"),
+	  SIPTAG_PAYLOAD_STR("sip:example.com\n"),
+	  TAG_END());
+
+  run_ab_until(ctx, -1, save_until_final_response, -1, save_events);
+
+  /* Client events:
+     nua_publish(), nua_r_publish
+  */
+  TEST_1(e = a->events->head); TEST_E(e->data->e_event, nua_r_publish);
+  TEST(e->data->e_status, 489);
+  TEST_1(!e->next);
+
+  free_events_in_list(ctx, a->events);
+
+  /*
+   Server events: nothing
+  */
+  TEST_1(!b->events->head);
+
+  nua_handle_destroy(a_call->nh), a_call->nh = NULL;
+
+  if (print_headings)
+    printf("TEST NUA-11.3: PASSED\n");
+
+  END();
+}
+
+static char const presence_open[] =
+    "<?xml version='1.0' encoding='UTF-8'?>\n"
+    "<presence xmlns='urn:ietf:params:xml:ns:cpim-pidf' \n"
+    "   entity='pres:bob at example.org'>\n"
+    "  <tuple id='ksac9udshce'>\n"
+    "    <status><basic>open</basic></status>\n"
+    "    <contact priority='1.0'>sip:bob at example.org</contact>\n"
+    "  </tuple>\n"
+    "</presence>\n";
+
+static char const presence_closed[] =
+    "<?xml version='1.0' encoding='UTF-8'?>\n"
+    "<presence xmlns='urn:ietf:params:xml:ns:cpim-pidf' \n"
+    "   entity='pres:bob at example.org'>\n"
+    "  <tuple id='ksac9udshce'>\n"
+    "    <status><basic>closed</basic></status>\n"
+    "  </tuple>\n"
+    "</presence>\n";
+
+
+int accept_and_notify_twice(CONDITION_PARAMS)
+{
+  msg_t *with = nua_current_request(nua);
+
+  if (!(check_handle(ep, call, nh, SIP_500_INTERNAL_SERVER_ERROR)))
+    return 0;
+
+  save_event_in_list(ctx, event, ep, call);
+
+  switch (event) {
+  case nua_i_subscribe:
+    if (status < 200) {
+      NOTIFY(ep, call, nh, 
+	     SIPTAG_EVENT(sip->sip_event),
+	     SIPTAG_CONTENT_TYPE_STR("application/pidf+xml"),
+	     SIPTAG_PAYLOAD_STR(presence_closed),
+	     NUTAG_SUBSTATE(nua_substate_pending),
+	     TAG_END());
+      NOTIFY(ep, call, nh,
+	     SIPTAG_EVENT(sip->sip_event),
+	     SIPTAG_CONTENT_TYPE_STR("application/pidf+xml"),
+	     SIPTAG_PAYLOAD_STR(presence_open),
+	     NUTAG_SUBSTATE(nua_substate_active),
+	     TAG_END());
+      RESPOND(ep, call, nh, SIP_202_ACCEPTED,
+	      NUTAG_WITH(with),
+	      SIPTAG_EXPIRES_STR("360"),
+	      TAG_END());
+    }
+    return 0;
+
+  case nua_r_notify:
+    return status >= 200 &&
+      tl_find(tags, nutag_substate)->t_value == nua_substate_active;
+
+  default:
+    return 0;
+  }
+}
+
+int save_until_responded_and_notified_twice(CONDITION_PARAMS)
+{
+  save_event_in_list(ctx, event, ep, call);
+
+  if (event == nua_i_notify) {
+    if (ep->flags.bit0)
+      ep->flags.bit1 = 1;
+    ep->flags.bit0 = 1;
+  }
+
+  if (event == nua_r_subscribe || event == nua_r_unsubscribe) {
+    if (status >= 300)
+      return 1;
+    else if (status >= 200) {
+      ep->flags.bit2 = 1;
+    }
+  }
+
+  return ep->flags.bit0 && ep->flags.bit1 && ep->flags.bit2;
+}
+
+
+int accept_and_notify(CONDITION_PARAMS)
+{
+  msg_t *with = nua_current_request(nua);
+
+  if (!(check_handle(ep, call, nh, SIP_500_INTERNAL_SERVER_ERROR)))
+    return 0;
+
+  save_event_in_list(ctx, event, ep, call);
+
+  switch (event) {
+  case nua_i_subscribe:
+    if (status < 200) {
+      int fetch = sip->sip_expires && sip->sip_expires->ex_delta == 0;
+
+      RESPOND(ep, call, nh, SIP_202_ACCEPTED,
+	      NUTAG_WITH(with),
+	      SIPTAG_EXPIRES_STR("360"),
+	      SIPTAG_EXPIRES(sip->sip_expires),
+	      TAG_END());
+
+      NOTIFY(ep, call, nh, 
+	     SIPTAG_EVENT(sip->sip_event),
+	     SIPTAG_CONTENT_TYPE_STR("application/pidf+xml"),
+	     SIPTAG_PAYLOAD_STR(presence_closed),
+	     NUTAG_SUBSTATE(fetch
+			    ? nua_substate_pending
+			    : nua_substate_terminated),
+	     TAG_END());
+
+    }
+    return 0;
+
+  case nua_r_notify:
+    return status >= 200;
+
+  default:
+    return 0;
+  }
+}
+
+int save_and_notify(CONDITION_PARAMS)
+{
+  if (!(check_handle(ep, call, nh, SIP_500_INTERNAL_SERVER_ERROR)))
+    return 0;
+
+  save_event_in_list(ctx, event, ep, call);
+
+  switch (event) {
+  case nua_i_subscribe:
+    NOTIFY(ep, call, nh, 
+	   SIPTAG_EVENT(sip->sip_event),
+	   SIPTAG_CONTENT_TYPE_STR("application/pidf+xml"),
+	   SIPTAG_PAYLOAD_STR(presence_closed),
+	   NUTAG_SUBSTATE(nua_substate_active),
+	   TAG_END());
+    return 0;
+
+  case nua_r_notify:
+    return status >= 200;
+
+  default:
+    return 0;
+  }
+}
+
+extern int save_until_notified_and_responded(CONDITION_PARAMS);
+extern int save_until_notified(CONDITION_PARAMS);
+
+int test_subscribe_notify(struct context *ctx)
+{
+  BEGIN();
+
+  struct endpoint *a = &ctx->a,  *b = &ctx->b;
+  struct call *a_call = a->call, *b_call = b->call;
+  struct event *e, *en1, *en2, *es;
+  sip_t const *sip;
+  tagi_t const *n_tags, *r_tags;
+
+  if (print_headings)
+    printf("TEST NUA-11.4: notifier server using nua_notify()\n");
+
+  if (print_headings)
+    printf("TEST NUA-11.4.1: establishing subscription\n");
+
+  TEST_1(a_call->nh = nua_handle(a->nua, a_call, SIPTAG_TO(b->to), TAG_END()));
+
+  SUBSCRIBE(a, a_call, a_call->nh, NUTAG_URL(b->contact->m_url),
+	    SIPTAG_EVENT_STR("presence"),
+	    SIPTAG_EXPIRES_STR("333"),
+	    SIPTAG_ACCEPT_STR("application/xpidf, application/pidf+xml"),
+	    TAG_END());
+
+  run_ab_until(ctx, -1, save_until_responded_and_notified_twice,
+	       -1, accept_and_notify_twice);
+
+  /* Client events:
+     nua_subscribe(), nua_i_notify/nua_r_subscribe/nua_i_notify
+  */
+  for (en1 = en2 = es = NULL, e = a->events->head; e; e = e->next) {
+    if (en1 == NULL && e->data->e_event == nua_i_notify)
+      en1 = e;
+    else if (en2 == NULL && e->data->e_event == nua_i_notify)
+      en2 = e;
+    else if (e->data->e_event == nua_r_subscribe)
+      es = e;
+    else
+      TEST_1(!e);
+  }
+
+  TEST_1(e = en1);
+  TEST_E(e->data->e_event, nua_i_notify);
+  TEST_1(tl_find(e->data->e_tags, nutag_substate));
+  TEST(tl_find(e->data->e_tags, nutag_substate)->t_value, nua_substate_pending);
+  TEST_1(sip = sip_object(e->data->e_msg));
+  TEST_1(sip->sip_event); TEST_S(sip->sip_event->o_type, "presence");
+  TEST_1(sip->sip_content_type);
+  TEST_S(sip->sip_content_type->c_type, "application/pidf+xml");
+  TEST_1(sip->sip_subscription_state);
+  TEST_S(sip->sip_subscription_state->ss_substate, "pending");
+  TEST_1(sip->sip_subscription_state->ss_expires);
+
+  TEST_1(e = es); TEST_E(e->data->e_event, nua_r_subscribe);
+  TEST_1(e->data->e_status == 202 || e->data->e_status == 200);
+  TEST_1(tl_find(e->data->e_tags, nutag_substate));
+  r_tags = tl_find(e->data->e_tags, nutag_substate);
+  if (es == a->events->head) {
+    TEST(r_tags->t_value, nua_substate_embryonic);
+  }
+  else if (es == a->events->head->next) {
+    TEST_1(r_tags->t_value == nua_substate_pending);
+  }
+  else {
+    TEST_1(r_tags->t_value == nua_substate_active);
+  }
+  TEST_1(sip = sip_object(e->data->e_msg));
+  TEST_1(sip->sip_expires);
+  TEST_1(sip->sip_expires->ex_delta <= 333);
+
+  free_events_in_list(ctx, a->events);
+
+  /* Server events: nua_i_subscribe, nua_r_notify */
+  TEST_1(e = b->events->head);
+  TEST_E(e->data->e_event, nua_i_subscribe);
+  TEST_E(e->data->e_status, 100);
+  TEST_1(sip = sip_object(e->data->e_msg));
+
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_r_notify);
+  r_tags = e->data->e_tags;
+  TEST_1(tl_find(r_tags, nutag_substate));
+  TEST(tl_find(r_tags, nutag_substate)->t_value, nua_substate_pending);
+
+  free_events_in_list(ctx, b->events);
+
+  if (print_headings)
+    printf("TEST NUA-11.4.1: PASSED\n");
+
+  /* ---------------------------------------------------------------------- */
+
+/* NOTIFY with updated content
+
+   A			B
+   |                    |
+   |<------NOTIFY-------|
+   |-------200 OK------>|
+   |                    |
+*/
+  if (print_headings)
+    printf("TEST NUA-11.4.2: send NOTIFY\n");
+
+  /* Update presence data */
+
+  NOTIFY(b, b_call, b_call->nh,
+	 NUTAG_SUBSTATE(nua_substate_active),
+	 SIPTAG_EVENT_STR("presence"),
+	 SIPTAG_CONTENT_TYPE_STR("application/pidf+xml"),
+	 SIPTAG_PAYLOAD_STR(presence_open),
+	 TAG_END());
+
+  run_ab_until(ctx, -1, save_until_notified,
+	       -1, save_until_final_response);
+
+  /* subscriber events:
+     nua_i_notify
+  */
+  TEST_1(e = a->events->head); TEST_E(e->data->e_event, nua_i_notify);
+  TEST_1(sip = sip_object(e->data->e_msg));
+  TEST_1(sip->sip_event); TEST_S(sip->sip_event->o_type, "presence");
+  TEST_1(sip->sip_content_type);
+  TEST_S(sip->sip_content_type->c_type, "application/pidf+xml");
+  TEST_1(sip->sip_subscription_state);
+  TEST_S(sip->sip_subscription_state->ss_substate, "active");
+  TEST_1(sip->sip_subscription_state->ss_expires);
+  n_tags = e->data->e_tags;
+  TEST_1(tl_find(n_tags, nutag_substate));
+  TEST(tl_find(n_tags, nutag_substate)->t_value, nua_substate_active);
+  TEST_1(!e->next);
+  free_events_in_list(ctx, a->events);
+
+  /* Notifier events: nua_r_notify */
+  TEST_1(e = b->events->head); TEST_E(e->data->e_event, nua_r_notify);
+  r_tags = e->data->e_tags;
+  TEST_1(tl_find(r_tags, nutag_substate));
+  TEST(tl_find(r_tags, nutag_substate)->t_value, nua_substate_active);
+
+  free_events_in_list(ctx, b->events);
+
+  if (print_headings)
+    printf("TEST NUA-11.4.2: PASSED\n");
+
+  /* ---------------------------------------------------------------------- */
+
+/* Re-SUBSCRIBE
+
+   A			B
+   |                    |
+   |<------NOTIFY-------|
+   |-------200 OK------>|
+   |                    |
+*/
+  if (print_headings)
+    printf("TEST NUA-11.4.3: re-SUBSCRIBE\n");
+
+  /* Set default expiration time */
+  nua_set_hparams(b_call->nh, NUTAG_SUB_EXPIRES(365), TAG_END());
+  run_b_until(ctx, nua_r_set_params, until_final_response);
+
+  SUBSCRIBE(a, a_call, a_call->nh, NUTAG_URL(b->contact->m_url),
+	    SIPTAG_EVENT_STR("presence"),
+	    SIPTAG_EXPIRES_STR("3600"),
+	    SIPTAG_ACCEPT_STR("application/xpidf, application/pidf+xml"),
+	    TAG_END());
+
+  run_ab_until(ctx, -1, save_until_notified_and_responded,
+	       -1, save_until_final_response);
+
+  /* Client events:
+     nua_subscribe(), nua_i_notify/nua_r_subscribe
+  */
+  for (en1 = en2 = es = NULL, e = a->events->head; e; e = e->next) {
+    if (en1 == NULL && e->data->e_event == nua_i_notify)
+      en1 = e;
+    else if (e->data->e_event == nua_r_subscribe)
+      es = e;
+    else
+      TEST_1(!e);
+  }
+  TEST_1(e = en1);
+  TEST_E(e->data->e_event, nua_i_notify);
+  TEST_1(sip = sip_object(e->data->e_msg));
+  TEST_1(sip->sip_event); TEST_S(sip->sip_event->o_type, "presence");
+  TEST_1(sip->sip_content_type);
+  TEST_S(sip->sip_content_type->c_type, "application/pidf+xml");
+  TEST_1(sip->sip_subscription_state);
+  TEST_S(sip->sip_subscription_state->ss_substate, "active");
+  TEST_1(sip->sip_subscription_state->ss_expires);
+  n_tags = e->data->e_tags;
+  TEST_1(tl_find(n_tags, nutag_substate));
+  TEST(tl_find(n_tags, nutag_substate)->t_value, nua_substate_active);
+
+  TEST_1(e = es); TEST_E(e->data->e_event, nua_r_subscribe);
+  TEST_1(tl_find(e->data->e_tags, nutag_substate));
+  TEST(tl_find(e->data->e_tags, nutag_substate)->t_value, nua_substate_active);
+  TEST_1(sip = sip_object(e->data->e_msg));
+  TEST_1(sip->sip_expires);
+  TEST_1(sip->sip_expires->ex_delta <= 365);
+
+  free_events_in_list(ctx, a->events);
+
+  /* Server events: nua_i_subscribe, nua_r_notify */
+  TEST_1(e = b->events->head);
+  TEST_E(e->data->e_event, nua_i_subscribe);
+  TEST_E(e->data->e_status, 100);
+  TEST_1(sip = sip_object(e->data->e_msg));
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_r_notify);
+  r_tags = e->data->e_tags;
+  TEST_1(tl_find(r_tags, nutag_substate));
+  TEST(tl_find(r_tags, nutag_substate)->t_value, nua_substate_active);
+
+  free_events_in_list(ctx, b->events);
+
+  if (print_headings)
+    printf("TEST NUA-11.4.3: PASSED\n");
+
+  /* ---------------------------------------------------------------------- */
+
+/* un-SUBSCRIBE
+
+   A			B
+   |                    |
+   |------SUBSCRIBE---->|
+   |<--------202--------|
+   |<------NOTIFY-------|
+   |-------200 OK------>|
+   |                    |
+*/
+  if (print_headings)
+    printf("TEST NUA-11.4.4: un-SUBSCRIBE\n");
+
+  UNSUBSCRIBE(a, a_call, a_call->nh, TAG_END());
+
+  run_ab_until(ctx, -1, save_until_final_response,
+	       -1, save_until_final_response);
+
+  /* Client events:
+     nua_unsubscribe(), nua_i_notify/nua_r_unsubscribe
+  */
+  for (en1 = en2 = es = NULL, e = a->events->head; e; e = e->next) {
+    if (en1 == NULL && e->data->e_event == nua_i_notify)
+      en1 = e;
+    else if (e->data->e_event == nua_r_unsubscribe)
+      es = e;
+    else
+      TEST_1(!e);
+  }
+  if (en1) {
+    TEST_1(e = en1); TEST_E(e->data->e_event, nua_i_notify);
+    TEST_1(sip = sip_object(e->data->e_msg));
+    TEST_1(sip->sip_event);
+    TEST_1(sip->sip_subscription_state);
+    TEST_S(sip->sip_subscription_state->ss_substate, "terminated");
+    TEST_1(!sip->sip_subscription_state->ss_expires);
+    n_tags = e->data->e_tags;
+    TEST_1(tl_find(n_tags, nutag_substate));
+    TEST(tl_find(n_tags, nutag_substate)->t_value, nua_substate_terminated);
+  }
+
+  TEST_1(e = es); TEST_E(e->data->e_event, nua_r_unsubscribe);
+  TEST(e->data->e_status, 200);
+  r_tags = e->data->e_tags;
+  TEST_1(tl_find(r_tags, nutag_substate));
+  TEST(tl_find(r_tags, nutag_substate)->t_value, nua_substate_terminated);
+  TEST_1(sip = sip_object(e->data->e_msg));
+  TEST_1(sip->sip_expires);
+  TEST_1(sip->sip_expires->ex_delta == 0);
+
+  free_events_in_list(ctx, a->events);
+
+  /* Notifier events: nua_r_notify */
+  TEST_1(e = b->events->head);
+  TEST_E(e->data->e_event, nua_i_subscribe);
+  TEST(e->data->e_status, 200);
+  TEST_1(sip = sip_object(e->data->e_msg));
+  TEST_1(tl_find(e->data->e_tags, nutag_substate));
+  TEST(tl_find(e->data->e_tags, nutag_substate)->t_value, 
+       nua_substate_terminated);
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_r_notify);
+  TEST_1(e->data->e_status >= 200);
+  r_tags = e->data->e_tags;
+  TEST_1(tl_find(r_tags, nutag_substate));
+  TEST(tl_find(r_tags, nutag_substate)->t_value, nua_substate_terminated);
+
+  free_events_in_list(ctx, b->events);
+
+  nua_handle_destroy(a_call->nh), a_call->nh = NULL;
+  nua_handle_destroy(b_call->nh), b_call->nh = NULL;
+
+  if (print_headings)
+    printf("TEST NUA-11.4.4: PASSED\n");
+
+  if (print_headings)
+    printf("TEST NUA-11.4: PASSED\n");
+
+  END();
+}
+
+/* ---------------------------------------------------------------------- */
+/* Subscriber gracefully terminates dialog upon 483 */
+
+static
+size_t change_status_to_483(void *a, void *message, size_t len);
+int save_until_notified_and_responded_twice(CONDITION_PARAMS);
+int save_until_notify_responded_twice(CONDITION_PARAMS);
+int accept_subscription_until_terminated(CONDITION_PARAMS);
+
+int test_subscribe_notify_graceful(struct context *ctx)
+{
+  if (!ctx->nat)
+    return 0;
+
+  BEGIN();
+
+  struct endpoint *a = &ctx->a,  *b = &ctx->b;
+  struct call *a_call = a->call, *b_call = b->call;
+  struct event *e, *en1, *en2, *es;
+  sip_t const *sip;
+  tagi_t const *n_tags, *r_tags;
+  struct nat_filter *f;
+
+  if (print_headings)
+    printf("TEST NUA-11.5.1: establishing subscription\n");
+
+  TEST_1(a_call->nh = nua_handle(a->nua, a_call, SIPTAG_TO(b->to), TAG_END()));
+
+  SUBSCRIBE(a, a_call, a_call->nh, NUTAG_URL(b->contact->m_url),
+	    SIPTAG_EVENT_STR("presence"),
+	    SIPTAG_ACCEPT_STR("application/xpidf, application/pidf+xml"),
+	    TAG_END());
+
+  run_ab_until(ctx, -1, save_until_notified_and_responded,
+	       -1, accept_and_notify);
+
+  /* Client events:
+     nua_subscribe(), nua_i_notify/nua_r_subscribe
+  */
+  for (en1 = en2 = es = NULL, e = a->events->head; e; e = e->next) {
+    if (en1 == NULL && e->data->e_event == nua_i_notify)
+      en1 = e;
+    else if (es == NULL && e->data->e_event == nua_r_subscribe)
+      es = e;
+    else
+      TEST_1(!e);
+  }
+  TEST_1(e = en1); TEST_E(e->data->e_event, nua_i_notify);
+  TEST_1(sip = sip_object(e->data->e_msg));
+  TEST_1(sip->sip_event); TEST_S(sip->sip_event->o_type, "presence");
+  TEST_1(sip->sip_content_type);
+  TEST_S(sip->sip_content_type->c_type, "application/pidf+xml");
+  TEST_1(sip->sip_subscription_state);
+  TEST_S(sip->sip_subscription_state->ss_substate, "pending");
+  TEST_1(sip->sip_subscription_state->ss_expires);
+  n_tags = e->data->e_tags;
+  TEST_1(tl_find(n_tags, nutag_substate));
+  TEST(tl_find(n_tags, nutag_substate)->t_value, nua_substate_pending);
+
+  TEST_1(e = es); TEST_E(e->data->e_event, nua_r_subscribe);
+  r_tags = e->data->e_tags;
+  TEST_1(tl_find(r_tags, nutag_substate));
+  if (es == a->events->head)
+    TEST(tl_find(r_tags, nutag_substate)->t_value, nua_substate_embryonic);
+  else
+    TEST(tl_find(r_tags, nutag_substate)->t_value, nua_substate_pending);
+  free_events_in_list(ctx, a->events);
+
+  /* Server events: nua_i_subscribe, nua_r_notify */
+  TEST_1(e = b->events->head);
+  TEST_E(e->data->e_event, nua_i_subscribe);
+  TEST_E(e->data->e_status, 100);
+  TEST_1(sip = sip_object(e->data->e_msg));
+
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_r_notify);
+  r_tags = e->data->e_tags;
+  TEST_1(tl_find(r_tags, nutag_substate));
+  TEST(tl_find(r_tags, nutag_substate)->t_value, nua_substate_pending);
+
+  free_events_in_list(ctx, b->events);
+
+  if (print_headings)
+    printf("TEST NUA-11.5.1: PASSED\n");
+
+  if (print_headings)
+    printf("TEST NUA-11.5.2: terminate gracefully upon 483\n");
+
+  TEST_1(f = test_nat_add_filter(ctx->nat,
+				 change_status_to_483, NULL,
+				 nat_inbound));
+
+  SUBSCRIBE(a, a_call, a_call->nh, TAG_END());
+
+  run_ab_until(ctx, -1, save_until_notified_and_responded_twice, 
+	       -1, accept_subscription_until_terminated);
+
+#if 0
+  /* Client events:
+     nua_unsubscribe(), nua_i_notify/nua_r_unsubscribe
+  */
+  TEST_1(e = a->events->head);
+  if (e->data->e_event == nua_i_notify) {
+    TEST_E(e->data->e_event, nua_i_notify);
+    n_tags = e->data->e_tags;
+    TEST_1(sip = sip_object(e->data->e_msg));
+    TEST_1(sip->sip_event);
+    TEST_1(sip->sip_subscription_state);
+    TEST_S(sip->sip_subscription_state->ss_substate, "terminated");
+    TEST_1(!sip->sip_subscription_state->ss_expires);
+    TEST_1(tl_find(n_tags, nutag_substate));
+    TEST(tl_find(n_tags, nutag_substate)->t_value, nua_substate_terminated);
+    TEST_1(e = e->next); TEST_E(e->data->e_event, nua_r_unsubscribe);
+    TEST(e->data->e_status, 200);
+    r_tags = e->data->e_tags;
+    TEST_1(tl_find(r_tags, nutag_substate));
+    TEST(tl_find(r_tags, nutag_substate)->t_value, nua_substate_terminated);
+  }
+  else {
+    TEST_E(e->data->e_event, nua_r_unsubscribe);
+    TEST(e->data->e_status, 200);
+    r_tags = e->data->e_tags;
+    TEST_1(tl_find(r_tags, nutag_substate));
+    TEST(tl_find(r_tags, nutag_substate)->t_value, nua_substate_terminated);
+  }
+  TEST_1(!e->next);
+
+  /* Notifier events: nua_r_notify */
+  TEST_1(e = b->events->head);
+  TEST_E(e->data->e_event, nua_i_subscribe);
+  TEST(e->data->e_status, 200);
+  TEST_1(sip = sip_object(e->data->e_msg));
+  TEST_1(tl_find(e->data->e_tags, nutag_substate));
+  TEST(tl_find(e->data->e_tags, nutag_substate)->t_value, 
+       nua_substate_terminated);
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_r_notify);
+  TEST_1(e->data->e_status >= 200);
+  r_tags = e->data->e_tags;
+  TEST_1(tl_find(r_tags, nutag_substate));
+  TEST(tl_find(r_tags, nutag_substate)->t_value, nua_substate_terminated);
+#endif
+
+  free_events_in_list(ctx, a->events);
+  free_events_in_list(ctx, b->events);
+
+  nua_handle_destroy(a_call->nh), a_call->nh = NULL;
+  nua_handle_destroy(b_call->nh), b_call->nh = NULL;
+
+  test_nat_remove_filter(ctx->nat, f);
+
+  if (print_headings)
+    printf("TEST NUA-11.5.2: PASSED\n");
+
+  END();
+}
+
+static
+size_t change_status_to_483(void *a, void *message, size_t len)
+{
+  (void)a;
+
+  if (strncmp("SIP/2.0 2", message, 9) == 0) {
+    memcpy(message, "SIP/2.0 483", 11);
+  }
+  return len;
+}
+
+int save_until_notified_and_responded_twice(CONDITION_PARAMS)
+{
+  save_event_in_list(ctx, event, ep, call);
+
+  if (event == nua_i_notify) {
+    if (ep->flags.bit0)
+      ep->flags.bit1 = 1;
+    ep->flags.bit0 = 1;
+  }
+
+  if (event == nua_r_subscribe || event == nua_r_unsubscribe) {
+    if (status >= 300)
+      return 1;
+    else if (status >= 200) {
+      if (ep->flags.bit2)
+	ep->flags.bit3 = 1;
+      ep->flags.bit2 = 1;
+    }
+  }
+
+  return ep->flags.bit0 && ep->flags.bit1 && ep->flags.bit2 && ep->flags.bit3;
+}
+
+int save_until_notify_responded_twice(CONDITION_PARAMS)
+{
+  save_event_in_list(ctx, event, ep, call);
+
+  if (event == nua_r_notify) {
+    if (ep->flags.bit0)
+      ep->flags.bit1 = 1;
+    ep->flags.bit0 = 1;
+  }
+
+  return ep->flags.bit0 && ep->flags.bit1;
+}
+
+/* ---------------------------------------------------------------------- */
+
+/*
+ * When incoming SUBSCRIBE, send NOTIFY,
+ * 200 OK SUBSCRIBE when NOTIFY has been responded.
+ */
+int notify_and_accept(CONDITION_PARAMS)
+{
+  if (!(check_handle(ep, call, nh, SIP_500_INTERNAL_SERVER_ERROR)))
+    return 0;
+
+  save_event_in_list(ctx, event, ep, call);
+
+  switch (event) {
+  case nua_i_subscribe:
+    if (status < 200) {
+      NOTIFY(ep, call, nh,
+	     SIPTAG_EVENT(sip->sip_event),
+	     SIPTAG_CONTENT_TYPE_STR("application/pidf+xml"),
+	     SIPTAG_PAYLOAD_STR(presence_closed),
+	     TAG_END());
+    }
+    return 0;
+
+  case nua_r_notify:
+    if (status >= 200) {
+      struct event *e;
+      for (e = ep->events->head; e; e = e->next) {
+	if (e->data->e_event == nua_i_subscribe) {
+	  RESPOND(ep, call, nh, SIP_200_OK,
+		  NUTAG_WITH(e->data->e_msg),
+		  TAG_END());
+	  break;
+	}
+      }
+      return 1;
+    }
+
+  default:
+    return 0;
+  }
+}
+
+int test_event_fetch(struct context *ctx)
+{
+  BEGIN();
+
+  struct endpoint *a = &ctx->a,  *b = &ctx->b;
+  struct call *a_call = a->call, *b_call = b->call;
+  struct event *e, *en1, *en2, *es;
+  sip_t const *sip;
+  tagi_t const *r_tags;
+
+  if (print_headings)
+    printf("TEST NUA-11.6.1: event fetch using nua_notify()\n");
+
+  TEST_1(a_call->nh = nua_handle(a->nua, a_call, SIPTAG_TO(b->to), TAG_END()));
+
+/* Fetch 1:
+
+   A			 B
+   |                     |
+   |------SUBSCRIBE----->|
+   |     Expires: 0      |
+   |<---------202--------|
+   |                     |
+   |<-------NOTIFY-------|
+   | S-State: terminated |
+   |-------200 OK------->|
+   |                     |
+*/
+
+  SUBSCRIBE(a, a_call, a_call->nh, NUTAG_URL(b->contact->m_url),
+	    SIPTAG_EVENT_STR("presence"),
+	    SIPTAG_EXPIRES_STR("0"),
+	    SIPTAG_ACCEPT_STR("application/xpidf, application/pidf+xml"),
+	    TAG_END());
+
+  run_ab_until(ctx, -1, save_until_notified_and_responded,
+	       -1, accept_and_notify);
+
+  /* Client events:
+     nua_subscribe(), nua_i_notify/nua_r_subscribe/nua_i_notify
+  */
+  for (en1 = en2 = es = NULL, e = a->events->head; e; e = e->next) {
+    if (en1 == NULL && e->data->e_event == nua_i_notify)
+      en1 = e;
+    else if (en2 == NULL && e->data->e_event == nua_i_notify)
+      en2 = e;
+    else if (e->data->e_event == nua_r_subscribe)
+      es = e;
+    else
+      TEST_1(!e);
+  }
+
+  TEST_1(e = en1);
+  TEST_E(e->data->e_event, nua_i_notify);
+  TEST_1(tl_find(e->data->e_tags, nutag_substate));
+  TEST(tl_find(e->data->e_tags, nutag_substate)->t_value, nua_substate_terminated);
+  TEST_1(sip = sip_object(e->data->e_msg));
+  TEST_1(sip->sip_event); TEST_S(sip->sip_event->o_type, "presence");
+  TEST_1(sip->sip_content_type);
+  TEST_S(sip->sip_content_type->c_type, "application/pidf+xml");
+  TEST_1(sip->sip_subscription_state);
+  TEST_S(sip->sip_subscription_state->ss_substate, "terminated");
+
+  TEST_1(e = es); TEST_E(e->data->e_event, nua_r_subscribe);
+  TEST_1(e->data->e_status == 202 || e->data->e_status == 200);
+  TEST_1(tl_find(e->data->e_tags, nutag_substate));
+
+  if (es == a->events->head)
+    TEST(tl_find(e->data->e_tags, nutag_substate)->t_value, nua_substate_embryonic);
+  else
+    TEST(tl_find(e->data->e_tags, nutag_substate)->t_value, nua_substate_terminated);
+  TEST_1(sip = sip_object(e->data->e_msg));
+  TEST_1(sip->sip_expires);
+  TEST_1(sip->sip_expires->ex_delta == 0);
+
+  free_events_in_list(ctx, a->events);
+
+  /* Server events: nua_i_subscribe, nua_r_notify */
+  TEST_1(e = b->events->head);
+  TEST_E(e->data->e_event, nua_i_subscribe);
+  TEST_E(e->data->e_status, 100);
+  TEST_1(sip = sip_object(e->data->e_msg));
+
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_r_notify);
+  r_tags = e->data->e_tags;
+  TEST_1(tl_find(r_tags, nutag_substate));
+  TEST(tl_find(r_tags, nutag_substate)->t_value, nua_substate_terminated);
+
+  free_events_in_list(ctx, b->events);
+
+  if (print_headings)
+    printf("TEST NUA-11.6.1: PASSED\n");
+
+  nua_handle_destroy(a_call->nh), a_call->nh = NULL;
+  nua_handle_destroy(b_call->nh), b_call->nh = NULL;
+
+
+  if (print_headings)
+    printf("TEST NUA-11.6.2: event fetch, NOTIFY comes before 202\n");
+
+  TEST_1(a_call->nh = nua_handle(a->nua, a_call, SIPTAG_TO(b->to), TAG_END()));
+
+/* Fetch 2:
+
+   A			 B
+   |                     |
+   |------SUBSCRIBE----->|
+   |     Expires: 0      |
+   |<-------NOTIFY-------|
+   | S-State: terminated |
+   |-------200 OK------->|
+   |                     |
+   |<---------202--------|
+   |                     |
+*/
+
+  SUBSCRIBE(a, a_call, a_call->nh, NUTAG_URL(b->contact->m_url),
+	    SIPTAG_EVENT_STR("presence"),
+	    SIPTAG_EXPIRES_STR("0"),
+	    SIPTAG_ACCEPT_STR("application/xpidf, application/pidf+xml"),
+	    TAG_END());
+
+  run_ab_until(ctx, -1, save_until_notified_and_responded,
+	       -1, notify_and_accept);
+
+  /* Client events:
+     nua_subscribe(), nua_i_notify/nua_r_subscribe/nua_i_notify
+  */
+  for (en1 = en2 = es = NULL, e = a->events->head; e; e = e->next) {
+    if (en1 == NULL && e->data->e_event == nua_i_notify)
+      en1 = e;
+    else if (en2 == NULL && e->data->e_event == nua_i_notify)
+      en2 = e;
+    else if (e->data->e_event == nua_r_subscribe)
+      es = e;
+    else
+      TEST_1(!e);
+  }
+
+  TEST_1(e = en1);
+  TEST_E(e->data->e_event, nua_i_notify);
+  TEST_1(tl_find(e->data->e_tags, nutag_substate));
+  TEST(tl_find(e->data->e_tags, nutag_substate)->t_value, nua_substate_terminated);
+  TEST_1(sip = sip_object(e->data->e_msg));
+  TEST_1(sip->sip_event); TEST_S(sip->sip_event->o_type, "presence");
+  TEST_1(sip->sip_content_type);
+  TEST_S(sip->sip_content_type->c_type, "application/pidf+xml");
+  TEST_1(sip->sip_subscription_state);
+  TEST_S(sip->sip_subscription_state->ss_substate, "terminated");
+
+  TEST_1(e = es); TEST_E(e->data->e_event, nua_r_subscribe);
+  TEST_1(e->data->e_status == 202 || e->data->e_status == 200);
+  TEST_1(tl_find(e->data->e_tags, nutag_substate));
+
+  if (es == a->events->head)
+    TEST(tl_find(e->data->e_tags, nutag_substate)->t_value, nua_substate_embryonic);
+  else
+    TEST(tl_find(e->data->e_tags, nutag_substate)->t_value, nua_substate_terminated);
+  TEST_1(sip = sip_object(e->data->e_msg));
+  TEST_1(sip->sip_expires);
+  TEST_1(sip->sip_expires->ex_delta == 0);
+
+  free_events_in_list(ctx, a->events);
+
+  /* Server events: nua_i_subscribe, nua_r_notify */
+  TEST_1(e = b->events->head);
+  TEST_E(e->data->e_event, nua_i_subscribe);
+  TEST_E(e->data->e_status, 100);
+  TEST_1(sip = sip_object(e->data->e_msg));
+
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_r_notify);
+  r_tags = e->data->e_tags;
+  TEST_1(tl_find(r_tags, nutag_substate));
+  TEST(tl_find(r_tags, nutag_substate)->t_value, nua_substate_terminated);
+
+  free_events_in_list(ctx, b->events);
+
+  if (print_headings)
+    printf("TEST NUA-11.6.2: PASSED\n");
+
+  nua_handle_destroy(a_call->nh), a_call->nh = NULL;
+  nua_handle_destroy(b_call->nh), b_call->nh = NULL;
+
+  END();
+}
+
+/* ---------------------------------------------------------------------- */
+/* Unsolicited NOTIFY */
+
+int accept_notify(CONDITION_PARAMS);
+
+int test_newsub_notify(struct context *ctx)
+{
+  BEGIN();
+
+  struct endpoint *a = &ctx->a,  *b = &ctx->b;
+  struct call *a_call = a->call, *b_call = b->call;
+  struct event *e;
+  sip_t const *sip;
+  sip_call_id_t *i;
+  tagi_t const *n_tags, *r_tags;
+
+#if 0
+
+  if (print_headings)
+    printf("TEST NUA-11.7.1: rejecting NOTIFY without subscription locally\n");
+
+  TEST_1(a_call->nh = nua_handle(a->nua, a_call, SIPTAG_TO(b->to), TAG_END()));
+
+  NOTIFY(a, a_call, a_call->nh, NUTAG_URL(b->contact->m_url),
+	 SIPTAG_SUBJECT_STR("NUA-11.7.1"),
+	 SIPTAG_EVENT_STR("message-summary"),
+	 SIPTAG_CONTENT_TYPE_STR("application/simple-message-summary"),
+	 SIPTAG_PAYLOAD_STR("Messages-Waiting: yes"),
+	 TAG_END());
+
+  run_a_until(ctx, -1, save_until_final_response);
+
+  /* Client events:
+     nua_notify(), nua_r_notify
+  */
+  TEST_1(e = a->events->head);
+  TEST_E(e->data->e_event, nua_r_notify);
+  TEST(e->data->e_status, 481);
+  TEST_1(!e->data->e_msg);
+  TEST_1(!e->next);
+  free_events_in_list(ctx, a->events);
+
+  if (print_headings)
+    printf("TEST NUA-11.7.1: PASSED\n");
+
+  if (print_headings)
+    printf("TEST NUA-11.7.2: rejecting NOTIFY without subscription\n");
+
+  TEST_1(i = sip_call_id_create(nua_handle_home(a_call->nh), NULL));
+
+  NOTIFY(a, a_call, a_call->nh, NUTAG_URL(b->contact->m_url),
+	 NUTAG_NEWSUB(1),
+	 SIPTAG_SUBJECT_STR("NUA-11.7.2 first"),
+	 SIPTAG_FROM_STR("<sip:alice at example.com>;tag=nua-11.7.2"),
+	 SIPTAG_CALL_ID(i),
+	 SIPTAG_CSEQ_STR("1 NOTIFY"),
+	 SIPTAG_EVENT_STR("message-summary"),
+	 SIPTAG_CONTENT_TYPE_STR("application/simple-message-summary"),
+	 SIPTAG_PAYLOAD_STR("Messages-Waiting: yes"),
+	 TAG_END());
+
+  run_a_until(ctx, -1, save_until_final_response);
+
+  /* Client events:
+     nua_notify(), nua_r_notify
+  */
+  TEST_1(e = a->events->head);
+  TEST_E(e->data->e_event, nua_r_notify);
+  TEST(e->data->e_status, 481);
+  TEST_1(e->data->e_msg);
+  TEST_1(!e->next);
+
+  free_events_in_list(ctx, a->events);
+  
+  /* 2nd NOTIFY using same dialog */
+  /* Check that server really discards the dialog */
+
+  NOTIFY(a, a_call, a_call->nh, NUTAG_URL(b->contact->m_url),
+	 NUTAG_NEWSUB(1),
+	 SIPTAG_SUBJECT_STR("NUA-11.7.2 second"),
+	 SIPTAG_FROM_STR("<sip:alice at example.com>;tag=nua-11.7.2"),
+	 SIPTAG_CALL_ID(i),
+	 SIPTAG_CSEQ_STR("2 NOTIFY"),
+	 SIPTAG_EVENT_STR("message-summary"),
+	 SIPTAG_CONTENT_TYPE_STR("application/simple-message-summary"),
+	 SIPTAG_PAYLOAD_STR("Messages-Waiting: yes"),
+	 TAG_END());
+
+  run_a_until(ctx, -1, save_until_final_response);
+
+  /* Client events:
+     nua_notify(), nua_r_notify
+  */
+  TEST_1(e = a->events->head);
+  TEST_E(e->data->e_event, nua_r_notify);
+  TEST(e->data->e_status, 481);
+  TEST_1(e->data->e_msg);
+  TEST_1(!e->next);
+
+  free_events_in_list(ctx, a->events);
+
+  if (print_headings)
+    printf("TEST NUA-11.7.2: PASSED\n");
+
+  /* ---------------------------------------------------------------------- */
+
+  if (print_headings)
+    printf("TEST NUA-11.7.3: accept NOTIFY\n");
+
+  nua_set_params(b->nua, NUTAG_APPL_METHOD("NOTIFY"), TAG_END());
+  run_b_until(ctx, nua_r_set_params, until_final_response);
+
+  NOTIFY(a, a_call, a_call->nh,
+	 NUTAG_URL(b->contact->m_url),
+	 NUTAG_NEWSUB(1),
+	 SIPTAG_SUBJECT_STR("NUA-11.7.3"),
+	 SIPTAG_EVENT_STR("message-summary"),
+	 SIPTAG_CONTENT_TYPE_STR("application/simple-message-summary"),
+	 SIPTAG_PAYLOAD_STR("Messages-Waiting: yes"),
+	 TAG_END());
+
+  run_ab_until(ctx, -1, save_until_final_response, -1, accept_notify);
+
+  /* Notifier events: nua_r_notify */
+  TEST_1(e = a->events->head); TEST_E(e->data->e_event, nua_r_notify);
+  TEST(e->data->e_status, 200);
+  r_tags = e->data->e_tags;
+  TEST_1(tl_find(r_tags, nutag_substate));
+  TEST(tl_find(r_tags, nutag_substate)->t_value, nua_substate_terminated);
+
+  /* subscriber events:
+     nua_i_notify
+  */
+  TEST_1(e = b->events->head); TEST_E(e->data->e_event, nua_i_notify);
+  TEST_1(sip = sip_object(e->data->e_msg));
+  TEST_1(sip->sip_subscription_state);
+  TEST_S(sip->sip_subscription_state->ss_substate, "terminated");
+  n_tags = e->data->e_tags;
+  TEST_1(tl_find(n_tags, nutag_substate));
+  TEST(tl_find(n_tags, nutag_substate)->t_value, nua_substate_terminated);
+  TEST_1(!e->next);
+
+  free_events_in_list(ctx, a->events);
+  free_events_in_list(ctx, b->events);
+
+  if (print_headings)
+    printf("TEST NUA-11.7.3: PASSED\n");
+
+  nua_handle_destroy(a_call->nh), a_call->nh = NULL;
+  nua_handle_destroy(b_call->nh), b_call->nh = NULL;
+
+#else
+  (void)i;
+  nua_set_params(b->nua, NUTAG_APPL_METHOD("NOTIFY"), TAG_END());
+  run_b_until(ctx, nua_r_set_params, until_final_response);
+#endif
+
+  /* ---------------------------------------------------------------------- */
+
+  if (print_headings)
+    printf("TEST NUA-11.7.4: multiple unsolicited NOTIFYs\n");
+
+  TEST_1(a_call->nh = nua_handle(a->nua, a_call, SIPTAG_TO(b->to), TAG_END()));
+
+  NOTIFY(a, a_call, a_call->nh,
+	 NUTAG_URL(b->contact->m_url),
+	 NUTAG_NEWSUB(1),
+	 SIPTAG_EXPIRES_STR("10"),
+	 SIPTAG_SUBJECT_STR("NUA-11.7.4aa"),
+	 SIPTAG_CONTENT_TYPE_STR("application/simple-message-summary"),
+	 SIPTAG_PAYLOAD_STR("Messages-Waiting: no"),
+	 TAG_END());
+
+  NOTIFY(a, a_call, a_call->nh,
+	 NUTAG_URL(b->contact->m_url),
+	 NUTAG_NEWSUB(1),
+	 SIPTAG_SUBSCRIPTION_STATE_STR("active; expires=333"),
+	 SIPTAG_SUBJECT_STR("NUA-11.7.4a"),
+	 SIPTAG_EVENT_STR("message-summary"),
+	 SIPTAG_CONTENT_TYPE_STR("application/simple-message-summary"),
+	 SIPTAG_PAYLOAD_STR("Messages-Waiting: no"),
+	 TAG_END());
+
+  NOTIFY(a, a_call, a_call->nh,
+	 NUTAG_URL(b->contact->m_url),
+	 NUTAG_NEWSUB(1),
+	 SIPTAG_SUBSCRIPTION_STATE_STR("active; expires=3000"),
+	 SIPTAG_SUBJECT_STR("NUA-11.7.4b"),
+	 SIPTAG_EVENT_STR("message-summary"),
+	 SIPTAG_CONTENT_TYPE_STR("application/simple-message-summary"),
+	 SIPTAG_PAYLOAD_STR("Messages-Waiting: yes"),
+	 TAG_END());
+
+  NOTIFY(a, a_call, a_call->nh,
+	 NUTAG_URL(b->contact->m_url),
+	 NUTAG_NEWSUB(1),
+	 SIPTAG_SUBSCRIPTION_STATE_STR("terminated"),
+	 SIPTAG_SUBJECT_STR("NUA-11.7.4c"),
+	 SIPTAG_EVENT_STR("message-summary"),
+	 SIPTAG_CONTENT_TYPE_STR("application/simple-message-summary"),
+	 SIPTAG_PAYLOAD_STR("Messages-Waiting: yes"),
+	 TAG_END());
+
+  a->state.n = 4;
+  b->state.n = 4;
+
+  run_ab_until(ctx, -1, save_until_nth_final_response, 
+	       -1, accept_n_notifys);
+
+  /* Notifier events: nua_r_notify nua_r_notify */
+  TEST_1(e = a->events->head); TEST_E(e->data->e_event, nua_r_notify);
+  TEST(e->data->e_status, 200);
+  r_tags = e->data->e_tags;
+  TEST_1(tl_find(r_tags, nutag_substate));
+  TEST(tl_find(r_tags, nutag_substate)->t_value, nua_substate_terminated);
+
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_r_notify);
+  TEST(e->data->e_status, 200);
+  r_tags = e->data->e_tags;
+  TEST_1(tl_find(r_tags, nutag_substate));
+  TEST(tl_find(r_tags, nutag_substate)->t_value, nua_substate_active);
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_r_notify);
+  TEST(e->data->e_status, 200);
+  r_tags = e->data->e_tags;
+  TEST_1(tl_find(r_tags, nutag_substate));
+  TEST(tl_find(r_tags, nutag_substate)->t_value, nua_substate_active);
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_r_notify);
+  TEST(e->data->e_status, 200);
+  r_tags = e->data->e_tags;
+  TEST_1(tl_find(r_tags, nutag_substate));
+  TEST(tl_find(r_tags, nutag_substate)->t_value, nua_substate_terminated);
+  TEST_1(!e->next);
+
+  /* subscriber events:
+     nua_i_notify
+  */
+  TEST_1(e = b->events->head); TEST_E(e->data->e_event, nua_i_notify);
+  TEST_1(sip = sip_object(e->data->e_msg));
+  TEST_1(sip->sip_subscription_state);
+  TEST_S(sip->sip_subscription_state->ss_substate, "terminated");
+  n_tags = e->data->e_tags;
+  TEST_1(tl_find(n_tags, nutag_substate));
+  TEST(tl_find(n_tags, nutag_substate)->t_value, nua_substate_terminated);
+
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_notify);
+  TEST_1(sip = sip_object(e->data->e_msg));
+  TEST_1(sip->sip_subscription_state);
+  TEST_S(sip->sip_subscription_state->ss_substate, "active");
+  n_tags = e->data->e_tags;
+  TEST_1(tl_find(n_tags, nutag_substate));
+  TEST(tl_find(n_tags, nutag_substate)->t_value, nua_substate_active);
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_notify);
+  TEST_1(sip = sip_object(e->data->e_msg));
+  TEST_1(sip->sip_subscription_state);
+  TEST_S(sip->sip_subscription_state->ss_substate, "active");
+  n_tags = e->data->e_tags;
+  TEST_1(tl_find(n_tags, nutag_substate));
+  TEST(tl_find(n_tags, nutag_substate)->t_value, nua_substate_active);
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_notify);
+  TEST_1(sip = sip_object(e->data->e_msg));
+  TEST_1(sip->sip_subscription_state);
+  TEST_S(sip->sip_subscription_state->ss_substate, "terminated");
+  n_tags = e->data->e_tags;
+  TEST_1(tl_find(n_tags, nutag_substate));
+  TEST(tl_find(n_tags, nutag_substate)->t_value, nua_substate_terminated);
+  TEST_1(!e->next);
+
+  free_events_in_list(ctx, a->events);
+  free_events_in_list(ctx, b->events);
+
+  nua_handle_destroy(a_call->nh), a_call->nh = NULL;
+  nua_handle_destroy(b_call->nh), b_call->nh = NULL;
+
+  if (print_headings)
+    printf("TEST NUA-11.7.4: PASSED\n");
+
+  /* ---------------------------------------------------------------------- */
+
+  if (print_headings)
+    printf("TEST NUA-11.7.5: multiple unsolicited NOTIFYs\n");
+
+  TEST_1(a_call->nh = nua_handle(a->nua, a_call, SIPTAG_TO(b->to), TAG_END()));
+
+  NOTIFY(a, a_call, a_call->nh,
+	 NUTAG_URL(b->contact->m_url),
+	 NUTAG_NEWSUB(1),
+	 SIPTAG_SUBSCRIPTION_STATE_STR("active; expires=333"),
+	 SIPTAG_SUBJECT_STR("NUA-11.7.5a"),
+	 SIPTAG_EVENT_STR("message-summary"),
+	 SIPTAG_CONTENT_TYPE_STR("application/simple-message-summary"),
+	 SIPTAG_PAYLOAD_STR("Messages-Waiting: no"),
+	 TAG_END());
+
+  NOTIFY(a, a_call, a_call->nh,
+	 NUTAG_URL(b->contact->m_url),
+	 NUTAG_NEWSUB(1),
+	 SIPTAG_SUBSCRIPTION_STATE_STR("active; expires=3000"),
+	 SIPTAG_SUBJECT_STR("NUA-11.7.5b"),
+	 SIPTAG_EVENT_STR("message-summary"),
+	 SIPTAG_CONTENT_TYPE_STR("application/simple-message-summary"),
+	 SIPTAG_PAYLOAD_STR("Messages-Waiting: yes"),
+	 TAG_END());
+
+  NOTIFY(a, a_call, a_call->nh,
+	 NUTAG_URL(b->contact->m_url),
+	 NUTAG_NEWSUB(1),
+	 SIPTAG_SUBSCRIPTION_STATE_STR("terminated"),
+	 SIPTAG_SUBJECT_STR("NUA-11.7.5c"),
+	 SIPTAG_EVENT_STR("message-summary"),
+	 SIPTAG_CONTENT_TYPE_STR("application/simple-message-summary"),
+	 SIPTAG_PAYLOAD_STR("Messages-Waiting: yes"),
+	 TAG_END());
+
+  a->state.n = 3;
+  b->state.n = 3;
+
+  run_ab_until(ctx, -1, save_until_nth_final_response, 
+	       -1, accept_n_notifys);
+
+  /* Notifier events: nua_r_notify nua_r_notify */
+  TEST_1(e = a->events->head); TEST_E(e->data->e_event, nua_r_notify);
+  TEST(e->data->e_status, 200);
+  r_tags = e->data->e_tags;
+  TEST_1(tl_find(r_tags, nutag_substate));
+  TEST(tl_find(r_tags, nutag_substate)->t_value, nua_substate_active);
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_r_notify);
+  TEST(e->data->e_status, 200);
+  r_tags = e->data->e_tags;
+  TEST_1(tl_find(r_tags, nutag_substate));
+  TEST(tl_find(r_tags, nutag_substate)->t_value, nua_substate_active);
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_r_notify);
+  TEST(e->data->e_status, 200);
+  r_tags = e->data->e_tags;
+  TEST_1(tl_find(r_tags, nutag_substate));
+  TEST(tl_find(r_tags, nutag_substate)->t_value, nua_substate_terminated);
+  TEST_1(!e->next);
+
+  /* subscriber events:
+     nua_i_notify
+  */
+  TEST_1(e = b->events->head); TEST_E(e->data->e_event, nua_i_notify);
+  TEST_1(sip = sip_object(e->data->e_msg));
+  TEST_1(sip->sip_subscription_state);
+  TEST_S(sip->sip_subscription_state->ss_substate, "active");
+  n_tags = e->data->e_tags;
+  TEST_1(tl_find(n_tags, nutag_substate));
+  TEST(tl_find(n_tags, nutag_substate)->t_value, nua_substate_active);
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_notify);
+  TEST_1(sip = sip_object(e->data->e_msg));
+  TEST_1(sip->sip_subscription_state);
+  TEST_S(sip->sip_subscription_state->ss_substate, "active");
+  n_tags = e->data->e_tags;
+  TEST_1(tl_find(n_tags, nutag_substate));
+  TEST(tl_find(n_tags, nutag_substate)->t_value, nua_substate_active);
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_notify);
+  TEST_1(sip = sip_object(e->data->e_msg));
+  TEST_1(sip->sip_subscription_state);
+  TEST_S(sip->sip_subscription_state->ss_substate, "terminated");
+  n_tags = e->data->e_tags;
+  TEST_1(tl_find(n_tags, nutag_substate));
+  TEST(tl_find(n_tags, nutag_substate)->t_value, nua_substate_terminated);
+  TEST_1(!e->next);
+
+  free_events_in_list(ctx, a->events);
+  free_events_in_list(ctx, b->events);
+
+  nua_handle_destroy(a_call->nh), a_call->nh = NULL;
+  nua_handle_destroy(b_call->nh), b_call->nh = NULL;
+
+  if (print_headings)
+    printf("TEST NUA-11.7.5: PASSED\n");
+
+  /* ---------------------------------------------------------------------- */
+
+#if 0
+
+  if (print_headings)
+    printf("TEST NUA-11.7.6: unsolicited NOTIFY handle destroyed\n");
+
+  TEST_1(a_call->nh = nua_handle(a->nua, a_call, SIPTAG_TO(b->to), TAG_END()));
+
+  NOTIFY(a, a_call, a_call->nh,
+	 NUTAG_URL(b->contact->m_url),
+	 NUTAG_NEWSUB(1),
+	 SIPTAG_SUBSCRIPTION_STATE_STR("active; expires=333"),
+	 SIPTAG_SUBJECT_STR("NUA-11.7.6a"),
+	 SIPTAG_EVENT_STR("message-summary"),
+	 SIPTAG_CONTENT_TYPE_STR("application/simple-message-summary"),
+	 SIPTAG_PAYLOAD_STR("Messages-Waiting: no"),
+	 TAG_END());
+
+  NOTIFY(a, a_call, a_call->nh,
+	 NUTAG_URL(b->contact->m_url),
+	 NUTAG_NEWSUB(1),
+	 SIPTAG_SUBSCRIPTION_STATE_STR("active; expires=3000"),
+	 SIPTAG_SUBJECT_STR("NUA-11.7.6b"),
+	 SIPTAG_EVENT_STR("message-summary"),
+	 SIPTAG_CONTENT_TYPE_STR("application/simple-message-summary"),
+	 SIPTAG_PAYLOAD_STR("Messages-Waiting: yes"),
+	 TAG_END());
+
+  nua_handle_destroy(a_call->nh), a_call->nh = NULL;
+
+  a->state.n = 3;
+  b->state.n = 3;
+
+  run_b_until(ctx, -1, accept_n_notifys);
+
+  /* subscriber events:
+     nua_i_notify
+  */
+  TEST_1(e = b->events->head); TEST_E(e->data->e_event, nua_i_notify);
+  TEST_1(sip = sip_object(e->data->e_msg));
+  TEST_1(sip->sip_subscription_state);
+  TEST_S(sip->sip_subscription_state->ss_substate, "active");
+  n_tags = e->data->e_tags;
+  TEST_1(tl_find(n_tags, nutag_substate));
+  TEST(tl_find(n_tags, nutag_substate)->t_value, nua_substate_active);
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_notify);
+  TEST_1(sip = sip_object(e->data->e_msg));
+  TEST_1(sip->sip_subscription_state);
+  TEST_S(sip->sip_subscription_state->ss_substate, "active");
+  n_tags = e->data->e_tags;
+  TEST_1(tl_find(n_tags, nutag_substate));
+  TEST(tl_find(n_tags, nutag_substate)->t_value, nua_substate_active);
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_notify);
+  TEST_1(sip = sip_object(e->data->e_msg));
+  TEST_1(sip->sip_subscription_state);
+  TEST_S(sip->sip_subscription_state->ss_substate, "terminated");
+  n_tags = e->data->e_tags;
+  TEST_1(tl_find(n_tags, nutag_substate));
+  TEST(tl_find(n_tags, nutag_substate)->t_value, nua_substate_terminated);
+  TEST_1(!e->next);
+
+  free_events_in_list(ctx, b->events);
+
+  if (print_headings)
+    printf("TEST NUA-11.7.6: PASSED\n");
+
+  nua_handle_destroy(b_call->nh), b_call->nh = NULL;
+
+#endif
+
+  if (print_headings)
+    printf("TEST NUA-11.7: PASSED\n");
+
+  END();
+}
+
+/**Terminate when received notify. 
+ * Respond to NOTIFY with 200 OK if it has not been responded.
+ * Save events (except nua_i_active or terminated).
+ */
+int accept_notify(CONDITION_PARAMS)
+{
+  if (event == nua_i_notify && status < 200)
+    RESPOND(ep, call, nh, SIP_200_OK, 
+	    NUTAG_WITH_THIS(ep->nua),
+	    TAG_END());
+
+  save_event_in_list(ctx, event, ep, call);
+
+  return event == nua_i_notify;
+}
+
+int save_until_nth_final_response(CONDITION_PARAMS)
+{
+  save_event_in_list(ctx, event, ep, call);
+
+  if (nua_r_set_params <= event && event < nua_i_network_changed
+      && status >= 200) {
+    if (ep->state.n > 0) 
+      ep->state.n--;
+    return ep->state.n == 0;
+  }
+
+  return 0;
+}
+
+int accept_n_notifys(CONDITION_PARAMS)
+{
+  tagi_t const *substate = tl_find(tags, nutag_substate);
+
+  if (event == nua_i_notify && status < 200)
+    RESPOND(ep, call, nh, SIP_200_OK, 
+	    NUTAG_WITH_THIS(ep->nua),
+	    TAG_END());
+
+  save_event_in_list(ctx, event, ep, call);
+
+  if (event != nua_i_notify)
+    return 0;
+
+  if (ep->state.n > 0) 
+    ep->state.n--;
+
+  if (ep->state.n == 0) 
+    return 1;
+
+  if (substate && substate->t_value == nua_substate_terminated) {
+    if (call && call->nh == nh) {
+      call->nh = NULL;
+      nua_handle_destroy(nh);
+    }
+  }
+
+  return 0;
+}
+
+/* ======================================================================== */
+
+int save_until_subscription_terminated(CONDITION_PARAMS);
+int accept_subscription_until_terminated(CONDITION_PARAMS);
+
+/* Timeout subscription */
+int test_subscription_timeout(struct context *ctx)
+{
+  BEGIN();
+
+  struct endpoint *a = &ctx->a,  *b = &ctx->b;
+  struct call *a_call = a->call, *b_call = b->call;
+  struct event *e, *en, *es;
+  sip_t const *sip;
+  tagi_t const *n_tags, *r_tags;
+
+  if (print_headings)
+    printf("TEST NUA-11.8: subscribe and wait until subscription times out\n");
+
+  TEST_1(a_call->nh = nua_handle(a->nua, a_call, SIPTAG_TO(b->to), TAG_END()));
+
+  METHOD(a, a_call, a_call->nh,
+	 NUTAG_METHOD("SUBSCRIBE"),
+	 NUTAG_URL(b->contact->m_url),
+	 SIPTAG_EVENT_STR("presence"),
+	 SIPTAG_ACCEPT_STR("application/xpidf, application/pidf+xml"),
+	 SIPTAG_EXPIRES_STR("2"),
+	 NUTAG_APPL_METHOD("NOTIFY"),
+	 NUTAG_DIALOG(2),
+	 TAG_END());
+
+  run_ab_until(ctx,
+	       -1, save_until_subscription_terminated,
+	       -1, accept_subscription_until_terminated);
+
+  /* Client events:
+     nua_method(), nua_i_notify/nua_r_method, nua_i_notify
+  */
+  TEST_1(en = event_by_type(a->events->head, nua_i_notify));
+  TEST_1(es = event_by_type(a->events->head, nua_r_method));
+
+  TEST_1(e = en); TEST_E(e->data->e_event, nua_i_notify);
+  TEST_1(sip = sip_object(e->data->e_msg));
+  n_tags = e->data->e_tags;
+
+  TEST_1(e = es); TEST_E(e->data->e_event, nua_r_method);
+  r_tags = e->data->e_tags;
+
+  TEST_1(sip->sip_event); TEST_S(sip->sip_event->o_type, "presence");
+  TEST_1(sip->sip_content_type);
+  TEST_S(sip->sip_content_type->c_type, "application/pidf+xml");
+  TEST_1(sip->sip_subscription_state);
+  TEST_S(sip->sip_subscription_state->ss_substate, "pending");
+  TEST_1(sip->sip_subscription_state->ss_expires);
+  TEST_1(tl_find(n_tags, nutag_substate));
+  TEST(tl_find(n_tags, nutag_substate)->t_value, nua_substate_pending);
+
+  if (es->next == en)
+    e = en->next;
+  else
+    e = es->next;
+
+  TEST_1(e); TEST_E(e->data->e_event, nua_i_notify);
+  n_tags = e->data->e_tags;
+  TEST_1(tl_find(n_tags, nutag_substate));
+  TEST(tl_find(n_tags, nutag_substate)->t_value, nua_substate_terminated);
+  TEST_1(sip = sip_object(e->data->e_msg));
+  TEST_1(sip->sip_event);
+  TEST_1(sip->sip_subscription_state);
+  TEST_S(sip->sip_subscription_state->ss_substate, "terminated");
+  TEST_1(!sip->sip_subscription_state->ss_expires);
+  TEST_1(!e->next);
+
+  free_events_in_list(ctx, a->events);
+
+  /* Server events: nua_i_subscribe, nua_r_notify */
+  TEST_1(e = b->events->head);
+  TEST_E(e->data->e_event, nua_i_subscribe);
+  TEST_E(e->data->e_status, 100);
+  TEST_1(sip = sip_object(e->data->e_msg));
+
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_r_notify);
+  r_tags = e->data->e_tags;
+  TEST_1(tl_find(r_tags, nutag_substate));
+  TEST(tl_find(r_tags, nutag_substate)->t_value, nua_substate_pending);
+
+  /* Notifier events: 2nd nua_r_notify */
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_r_notify);
+  TEST_1(e->data->e_status >= 200);
+  r_tags = e->data->e_tags;
+  TEST_1(tl_find(r_tags, nutag_substate));
+  TEST(tl_find(r_tags, nutag_substate)->t_value, nua_substate_terminated);
+
+  free_events_in_list(ctx, b->events);
+
+  nua_handle_destroy(a_call->nh), a_call->nh = NULL;
+  nua_handle_destroy(b_call->nh), b_call->nh = NULL;
+
+  if (print_headings)
+    printf("TEST NUA-11.8: PASSED\n");
+
+  END();
+}
+
+int save_until_subscription_terminated(CONDITION_PARAMS)
+{
+  void *with = nua_current_request(nua);
+
+  save_event_in_list(ctx, event, ep, call);
+
+  if (event == nua_i_notify) {
+    if (status < 200)
+      RESPOND(ep, call, nh, SIP_200_OK, NUTAG_WITH(with), TAG_END());
+
+    tags = tl_find(tags, nutag_substate);
+    return tags && tags->t_value == nua_substate_terminated;
+  }
+
+  return 0;
+}
+
+int accept_subscription_until_terminated(CONDITION_PARAMS)
+{
+  void *with = nua_current_request(nua);
+
+  save_event_in_list(ctx, event, ep, call);
+
+  if (event == nua_i_subscribe && status < 200) {
+    RESPOND(ep, call, nh, SIP_202_ACCEPTED,
+	    NUTAG_WITH(with),
+	    SIPTAG_EXPIRES_STR("360"),
+	    TAG_END());
+    NOTIFY(ep, call, nh, 
+	   SIPTAG_EVENT(sip->sip_event),
+	   SIPTAG_CONTENT_TYPE_STR("application/pidf+xml"),
+	   SIPTAG_PAYLOAD_STR(presence_closed),
+	   NUTAG_SUBSTATE(nua_substate_pending),
+	   TAG_END());
+  }
+  else if (event == nua_r_notify) {
+    tags = tl_find(tags, nutag_substate);
+    return tags && tags->t_value == nua_substate_terminated;
+  }
+
+  return 0;
+}
+
+
+/* ======================================================================== */
+/* Test simple methods: MESSAGE, PUBLISH, SUBSCRIBE/NOTIFY */
+
+int test_simple(struct context *ctx)
+{
+  return 0
+    || test_message(ctx)
+    || test_publish(ctx)
+    || test_subscribe_notify(ctx)
+    || test_event_fetch(ctx)
+    || test_subscribe_notify_graceful(ctx)
+    || test_newsub_notify(ctx)
+    || test_subscription_timeout(ctx)
+    ;
+}

Added: freeswitch/trunk/libs/sofia-sip/tests/test_sip_events.c
==============================================================================
--- (empty file)
+++ freeswitch/trunk/libs/sofia-sip/tests/test_sip_events.c	Wed May 14 15:10:54 2008
@@ -0,0 +1,601 @@
+/*
+ * This file is part of the Sofia-SIP package
+ *
+ * Copyright (C) 2005 Nokia Corporation.
+ *
+ * Contact: Pekka Pessi <pekka.pessi at nokia.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA
+ *
+ */
+
+/**@CFILE test_sip_events.c
+ * @brief NUA-12 tests: SUBSCRIBE/NOTIFY.
+ *
+ * @author Pekka Pessi <Pekka.Pessi at nokia.com>
+ * @author Martti Mela <Martti Mela at nokia.com>
+ *
+ * @date Created: Wed Aug 17 12:12:12 EEST 2005 ppessi
+ */
+
+#include "config.h"
+
+#include "test_nua.h"
+#include <sofia-sip/su_tag_class.h>
+#include <sofia-sip/nea.h>
+
+#if !HAVE_MEMMEM
+void *memmem(const void *haystack, size_t haystacklen,
+	     const void *needle, size_t needlelen);
+#endif
+
+#if HAVE_FUNC
+#elif HAVE_FUNCTION
+#define __func__ __FUNCTION__
+#else
+#define __func__ "test_sip_events"
+#endif
+
+/* ======================================================================== */
+/* Test events methods: SUBSCRIBE/NOTIFY */
+
+/**Terminate until received notify.
+ * Save events (except nua_i_active or terminated).
+ */
+int save_until_notified(CONDITION_PARAMS)
+{
+  save_event_in_list(ctx, event, ep, call);
+  return event == nua_i_notify;
+}
+
+int save_until_notified_and_responded(CONDITION_PARAMS)
+{
+  save_event_in_list(ctx, event, ep, call);
+
+  if (event == nua_i_notify) ep->flags.bit0 = 1;
+  if (event == nua_r_subscribe || event == nua_r_unsubscribe) {
+    if (status == 407) {
+      AUTHENTICATE(ep, call, nh,
+		   NUTAG_AUTH("Digest:\"test-proxy\":charlie:secret"),
+		   TAG_END());
+    }
+    else if (status >= 300) 
+      return 1;
+    else if (status >= 200)
+      ep->flags.bit1 = 1;
+  }
+
+  return ep->flags.bit0 && ep->flags.bit1;
+}
+
+
+int save_until_subscription(CONDITION_PARAMS)
+{
+  save_event_in_list(ctx, event, ep, call);
+  return event == nua_i_subscription;
+}
+
+
+int test_events(struct context *ctx)
+{
+  BEGIN();
+
+  struct endpoint *a = &ctx->a,  *b = &ctx->b;
+  struct call *a_call = a->call, *b_call = b->call;
+  struct event *e, *en, *es;
+  sip_t const *sip;
+  tagi_t const *t, *n_tags, *r_tags;
+  url_t b_url[1];
+  enum nua_substate substate;
+  nea_sub_t *sub = NULL;
+
+  char const open[] =
+    "<?xml version='1.0' encoding='UTF-8'?>\n"
+    "<presence xmlns='urn:ietf:params:xml:ns:cpim-pidf' \n"
+    "   entity='pres:bob at example.org'>\n"
+    "  <tuple id='ksac9udshce'>\n"
+    "    <status><basic>open</basic></status>\n"
+    "    <contact priority='1.0'>sip:bob at example.org</contact>\n"
+    "  </tuple>\n"
+    "</presence>\n";
+
+  char const closed[] =
+    "<?xml version='1.0' encoding='UTF-8'?>\n"
+    "<presence xmlns='urn:ietf:params:xml:ns:cpim-pidf' \n"
+    "   entity='pres:bob at example.org'>\n"
+    "  <tuple id='ksac9udshce'>\n"
+    "    <status><basic>closed</basic></status>\n"
+    "  </tuple>\n"
+    "</presence>\n";
+
+
+/* SUBSCRIBE test
+
+   A			B
+   |------SUBSCRIBE---->|
+   |<--------405--------|
+   |			|
+
+*/
+  if (print_headings)
+    printf("TEST NUA-12.1: SUBSCRIBE without notifier\n");
+
+  nua_set_params(b->nua, SIPTAG_ALLOW_EVENTS(NULL), TAG_END());
+  run_b_until(ctx, nua_r_set_params, until_final_response);
+
+  TEST_1(a_call->nh = nua_handle(a->nua, a_call, SIPTAG_TO(b->to), TAG_END()));
+
+  SUBSCRIBE(a, a_call, a_call->nh, NUTAG_URL(b->contact->m_url),
+	    SIPTAG_EVENT_STR("presence"),
+	    TAG_END());
+
+  run_ab_until(ctx, -1, save_until_final_response,
+	       -1, NULL /* XXX save_until_received */);
+
+  /* Client events:
+     nua_subscribe(), nua_r_subscribe
+  */
+  TEST_1(e = a->events->head); TEST_E(e->data->e_event, nua_r_subscribe);
+  TEST(e->data->e_status, 489);
+  TEST_1(!e->next);
+
+#if 0				/* XXX */
+  /*
+   Server events:
+   nua_i_subscribe
+  */
+  TEST_1(e = b->events->head);
+  TEST_E(e->data->e_event, nua_i_subscribe);
+  TEST(e->data->e_status, 405);
+  TEST_1(sip = sip_object(e->data->e_msg));
+  TEST_1(sip->sip_event);
+  TEST_S(sip->sip_event->o_type, "presence");
+  TEST_1(!e->next);
+#endif
+
+  free_events_in_list(ctx, a->events);
+  free_events_in_list(ctx, b->events);
+  nua_handle_destroy(b_call->nh), b_call->nh = NULL;
+
+  if (print_headings)
+    printf("TEST NUA-12.1: PASSED\n");
+
+  /* ---------------------------------------------------------------------- */
+
+/* SUBSCRIBE test using notifier and establishing subscription
+
+   A			B
+   |                    |
+   |------SUBSCRIBE---->|
+   |<--------202--------|
+   |<------NOTIFY-------|
+   |-------200 OK------>|
+   |                    |
+*/
+
+  if (print_headings)
+    printf("TEST NUA-12.2: using notifier and establishing subscription\n");
+
+  TEST_1(b_call->nh = nua_handle(b->nua, b_call, TAG_END()));
+
+  *b_url = *b->contact->m_url;
+
+  NOTIFIER(b, b_call, b_call->nh,
+	   NUTAG_URL(b_url),
+	   SIPTAG_EVENT_STR("presence"),
+	   SIPTAG_CONTENT_TYPE_STR("application/pidf+xml"),
+	   SIPTAG_PAYLOAD_STR(closed),
+	   NEATAG_THROTTLE(1),
+	   TAG_END());
+  run_b_until(ctx, nua_r_notifier, until_final_response);
+
+  SUBSCRIBE(a, a_call, a_call->nh, NUTAG_URL(b->contact->m_url),
+	    SIPTAG_EVENT_STR("presence"),
+	    SIPTAG_ACCEPT_STR("application/xpidf, application/pidf+xml"),
+	    TAG_END());
+
+  run_ab_until(ctx, -1, save_until_notified_and_responded,
+	       -1, save_until_received);
+
+  /* Client events:
+     nua_subscribe(), nua_i_notify/nua_r_subscribe
+  */
+  TEST_1(en = event_by_type(a->events->head, nua_i_notify));
+  TEST_1(es = event_by_type(a->events->head, nua_r_subscribe));
+
+  TEST_1(e = es); TEST_E(e->data->e_event, nua_r_subscribe);
+  r_tags = e->data->e_tags;
+  TEST_1(tl_find(r_tags, nutag_substate));
+  if (es->next == en) {
+    TEST_1(200 <= e->data->e_status && e->data->e_status < 300);
+    TEST(tl_find(r_tags, nutag_substate)->t_value, nua_substate_embryonic);
+  }
+  else {
+    TEST_1(200 <= e->data->e_status && e->data->e_status < 300);
+    TEST(tl_find(r_tags, nutag_substate)->t_value, nua_substate_active);
+  }
+
+  TEST_1(e = en); TEST_E(e->data->e_event, nua_i_notify);
+  TEST_1(sip = sip_object(e->data->e_msg));
+  n_tags = e->data->e_tags;
+
+  TEST_1(sip->sip_event); TEST_S(sip->sip_event->o_type, "presence");
+  TEST_1(sip->sip_content_type);
+  TEST_S(sip->sip_content_type->c_type, "application/pidf+xml");
+  TEST_1(sip->sip_subscription_state);
+  TEST_S(sip->sip_subscription_state->ss_substate, "active");
+  TEST_1(sip->sip_subscription_state->ss_expires);
+  TEST_1(tl_find(n_tags, nutag_substate));
+  TEST(tl_find(n_tags, nutag_substate)->t_value, nua_substate_active);
+  TEST_1(!en->next || !es->next);
+  free_events_in_list(ctx, a->events);
+
+  /* XXX --- Do not check server side events */
+  free_events_in_list(ctx, b->events);
+
+  if (print_headings)
+    printf("TEST NUA-12.2: PASSED\n");
+
+  /* ---------------------------------------------------------------------- */
+
+/* NOTIFY with updated content
+
+   A			B
+   |                    |
+   |<------NOTIFY-------|
+   |-------200 OK------>|
+   |                    |
+*/
+  if (print_headings)
+    printf("TEST NUA-12.3: update notifier\n");
+
+  /* Update presence data */
+
+  NOTIFIER(b, b_call, b_call->nh,
+	   SIPTAG_EVENT_STR("presence"),
+	   SIPTAG_CONTENT_TYPE_STR("application/pidf+xml"),
+	   SIPTAG_PAYLOAD_STR(open),
+	   TAG_END());
+
+  run_ab_until(ctx, -1, save_until_notified, -1, save_until_received);
+
+  /* subscriber events:
+     nua_i_notify
+  */
+  TEST_1(e = a->events->head); TEST_E(e->data->e_event, nua_i_notify);
+  TEST_1(sip = sip_object(e->data->e_msg));
+  TEST_1(sip->sip_event); TEST_S(sip->sip_event->o_type, "presence");
+  TEST_1(sip->sip_content_type);
+  TEST_S(sip->sip_content_type->c_type, "application/pidf+xml");
+  TEST_1(sip->sip_subscription_state);
+  TEST_S(sip->sip_subscription_state->ss_substate, "active");
+  TEST_1(sip->sip_subscription_state->ss_expires);
+  n_tags = e->data->e_tags;
+  TEST_1(tl_find(n_tags, nutag_substate));
+  TEST(tl_find(n_tags, nutag_substate)->t_value,
+       nua_substate_active);
+  TEST_1(!e->next);
+  free_events_in_list(ctx, a->events);
+
+  /* XXX --- Do not check server side events */
+  free_events_in_list(ctx, b->events);
+
+  if (print_headings)
+    printf("TEST NUA-12.3: PASSED\n");
+
+  /* ---------------------------------------------------------------------- */
+
+/* un-SUBSCRIBE
+
+   A			B
+   |                    |
+   |------SUBSCRIBE---->|
+   |<--------202--------|
+   |<------NOTIFY-------|
+   |-------200 OK------>|
+   |                    |
+*/
+  if (print_headings)
+    printf("TEST NUA-12.5: un-SUBSCRIBE\n");
+
+  UNSUBSCRIBE(a, a_call, a_call->nh, TAG_END());
+
+  run_ab_until(ctx, -1, save_until_final_response,
+	       -1, save_until_subscription);
+
+  /* Client events:
+     nua_unsubscribe(), nua_i_notify/nua_r_unsubscribe
+  */
+  TEST_1(e = a->events->head);
+  if (e->data->e_event == nua_i_notify) {
+    TEST_E(e->data->e_event, nua_i_notify);
+    TEST_1(sip = sip_object(e->data->e_msg));
+    n_tags = e->data->e_tags;
+    TEST_1(sip->sip_event);
+    TEST_1(sip->sip_subscription_state);
+    TEST_S(sip->sip_subscription_state->ss_substate, "terminated");
+    TEST_1(!sip->sip_subscription_state->ss_expires);
+    TEST_1(tl_find(n_tags, nutag_substate));
+    TEST(tl_find(n_tags, nutag_substate)->t_value, nua_substate_terminated);
+    TEST_1(e = e->next);
+  }
+  TEST_E(e->data->e_event, nua_r_unsubscribe);
+  TEST_1(tl_find(e->data->e_tags, nutag_substate));
+  TEST(tl_find(e->data->e_tags, nutag_substate)->t_value,
+       nua_substate_terminated);
+  /* Currently, NOTIFY is dropped after successful response to unsubscribe */
+  /* But we don't really care.. */
+  /* TEST_1(!e->next); */
+  free_events_in_list(ctx, a->events);
+
+  /* Server events: nua_i_subscription with terminated status */
+  TEST_1(e = b->events->head);
+  TEST_E(e->data->e_event, nua_i_subscription);
+  TEST(tl_gets(e->data->e_tags,
+               NEATAG_SUB_REF(sub),
+               NUTAG_SUBSTATE_REF(substate),
+               TAG_END()), 2);
+  TEST_1(sub);
+  TEST(substate, nua_substate_terminated);
+  TEST_1(!e->next);
+
+  free_events_in_list(ctx, b->events);
+
+  if (print_headings)
+    printf("TEST NUA-12.5: PASSED\n");
+
+  /* ---------------------------------------------------------------------- */
+/* Fetch event, SUBSCRIBE with expires: 0
+
+   A			B
+   |                    |
+   |------SUBSCRIBE---->|
+   |<--------202--------|
+   |<------NOTIFY-------|
+   |-------200 OK------>|
+   |                    |
+*/
+  if (print_headings)
+    printf("TEST NUA-12.5.1: event fetch\n");
+
+  SUBSCRIBE(a, a_call, a_call->nh, NUTAG_URL(b->contact->m_url),
+	    SIPTAG_EVENT_STR("presence"),
+	    SIPTAG_ACCEPT_STR("application/pidf+xml"),
+	    SIPTAG_EXPIRES_STR("0"),
+	    TAG_END());
+
+  run_ab_until(ctx, -1, save_until_notified_and_responded,
+	       -1, save_until_subscription);
+
+  /* Client events:
+     nua_subscribe(), nua_i_notify/nua_r_subscribe
+  */
+  TEST_1(en = event_by_type(a->events->head, nua_i_notify));
+  TEST_1(es = event_by_type(a->events->head, nua_r_subscribe));
+
+  e = es; TEST_E(e->data->e_event, nua_r_subscribe);
+  TEST_1(t = tl_find(e->data->e_tags, nutag_substate));
+  TEST_1(t->t_value == nua_substate_pending ||
+	 t->t_value == nua_substate_terminated ||
+	 t->t_value == nua_substate_embryonic);
+
+  e = en; TEST_E(e->data->e_event, nua_i_notify);
+  TEST_1(sip = sip_object(e->data->e_msg));
+  n_tags = e->data->e_tags;
+
+  TEST_1(sip->sip_event); TEST_S(sip->sip_event->o_type, "presence");
+  TEST_1(sip->sip_content_type);
+  TEST_S(sip->sip_content_type->c_type, "application/pidf+xml");
+  TEST_1(sip->sip_payload && sip->sip_payload->pl_data);
+  TEST_1(sip->sip_subscription_state);
+  TEST_S(sip->sip_subscription_state->ss_substate, "terminated");
+  TEST_1(tl_find(n_tags, nutag_substate));
+  TEST(tl_find(n_tags, nutag_substate)->t_value,
+       nua_substate_terminated);
+  TEST_1(!en->next || !es->next);
+  free_events_in_list(ctx, a->events);
+
+  /*
+   Server events:
+   nua_i_subscription
+  */
+  TEST_1(e = b->events->head);
+  TEST_E(e->data->e_event, nua_i_subscription);
+  TEST(tl_gets(e->data->e_tags, NEATAG_SUB_REF(sub), TAG_END()), 1);
+  TEST_1(sub);
+  TEST_1(!e->next);
+
+  free_events_in_list(ctx, b->events);
+
+  if (print_headings)
+    printf("TEST NUA-12.4.1: PASSED\n");
+
+
+  /* ---------------------------------------------------------------------- */
+/* 2nd SUBSCRIBE with event id
+
+   A			B
+   |                    |
+   |------SUBSCRIBE---->|
+   |<--------202--------|
+   |<------NOTIFY-------|
+   |-------200 OK------>|
+   |                    |
+*/
+  /* XXX - we should do this before unsubscribing first one */
+  if (print_headings)
+    printf("TEST NUA-12.4.2: establishing 2nd subscription\n");
+
+   NOTIFIER(b, b_call, b_call->nh,
+	    SIPTAG_EVENT_STR("presence"),
+	    SIPTAG_CONTENT_TYPE_STR("application/xpidf+xml"),
+	    SIPTAG_PAYLOAD_STR(open),
+	    NEATAG_THROTTLE(1),
+	    NUTAG_SUBSTATE(nua_substate_pending),
+	    TAG_END());
+  run_b_until(ctx, nua_r_notifier, until_final_response);
+
+  NOTIFIER(b, b_call, b_call->nh,
+	   SIPTAG_EVENT_STR("presence"),
+	   SIPTAG_CONTENT_TYPE_STR("application/xpidf+xml"),
+	   SIPTAG_PAYLOAD_STR(closed),
+	   NEATAG_THROTTLE(1),
+	   NEATAG_FAKE(1),
+	   NUTAG_SUBSTATE(nua_substate_pending),
+	   TAG_END());
+  run_b_until(ctx, nua_r_notifier, until_final_response);
+
+  SUBSCRIBE(a, a_call, a_call->nh, NUTAG_URL(b->contact->m_url),
+	    SIPTAG_EVENT_STR("presence;id=1"),
+	    SIPTAG_ACCEPT_STR("application/xpidf+xml"),
+	    TAG_END());
+
+  run_ab_until(ctx, -1, save_until_notified_and_responded,
+	       -1, save_until_subscription);
+
+  /* Client events:
+     nua_subscribe(), nua_i_notify/nua_r_subscribe
+  */
+  TEST_1(en = event_by_type(a->events->head, nua_i_notify));
+  TEST_1(es = event_by_type(a->events->head, nua_r_subscribe));
+
+  e = es; TEST_E(e->data->e_event, nua_r_subscribe);
+  TEST_1(t = tl_find(e->data->e_tags, nutag_substate));
+  TEST_1(t->t_value == nua_substate_pending ||
+	 t->t_value == nua_substate_embryonic);
+
+  e = en; TEST_E(e->data->e_event, nua_i_notify);
+  TEST_1(sip = sip_object(e->data->e_msg));
+  n_tags = e->data->e_tags;
+
+  TEST_1(sip->sip_event); TEST_S(sip->sip_event->o_type, "presence");
+  TEST_S(sip->sip_event->o_id, "1");
+  TEST_1(sip->sip_content_type);
+  TEST_S(sip->sip_content_type->c_type, "application/xpidf+xml");
+  TEST_1(sip->sip_payload && sip->sip_payload->pl_data);
+  /* Check that we really got "fake" content */
+  TEST_1(memmem(sip->sip_payload->pl_data, sip->sip_payload->pl_len,
+		"<basic>closed</basic>", strlen("<basic>closed</basic>")));
+  TEST_1(sip->sip_subscription_state);
+  TEST_S(sip->sip_subscription_state->ss_substate, "pending");
+  TEST_1(sip->sip_subscription_state->ss_expires);
+  TEST_1(tl_find(n_tags, nutag_substate));
+  TEST(tl_find(n_tags, nutag_substate)->t_value,
+       nua_substate_pending);
+  TEST_1(!en->next || !es->next);
+  free_events_in_list(ctx, a->events);
+
+  /*
+   Server events:
+   nua_i_subscription
+  */
+  TEST_1(e = b->events->head);
+  TEST_E(e->data->e_event, nua_i_subscription);
+  TEST(tl_gets(e->data->e_tags, NEATAG_SUB_REF(sub), TAG_END()), 1);
+  TEST_1(sub);
+  TEST_1(!e->next);
+
+  free_events_in_list(ctx, b->events);
+
+  /* Authorize user A */
+  AUTHORIZE(b, b_call, b_call->nh,
+	    NUTAG_SUBSTATE(nua_substate_active),
+	    NEATAG_SUB(sub),
+	    NEATAG_FAKE(0),
+	    TAG_END());
+
+  run_ab_until(ctx, -1, save_until_notified,
+	       -1, save_until_final_response);
+
+  /* subscriber events:
+     nua_i_notify with NUTAG_SUBSTATE(nua_substate_active)
+  */
+  TEST_1(e = a->events->head); TEST_E(e->data->e_event, nua_i_notify);
+  TEST_1(sip = sip_object(e->data->e_msg));
+  TEST_1(sip->sip_event); TEST_S(sip->sip_event->o_type, "presence");
+  TEST_1(sip->sip_content_type);
+  TEST_S(sip->sip_content_type->c_type, "application/xpidf+xml");
+  TEST_1(sip->sip_payload && sip->sip_payload->pl_data);
+  /* Check that we really got real content */
+  TEST_1(memmem(sip->sip_payload->pl_data, sip->sip_payload->pl_len,
+		"<basic>open</basic>", strlen("<basic>open</basic>")));
+  TEST_1(sip->sip_subscription_state);
+  TEST_S(sip->sip_subscription_state->ss_substate, "active");
+  TEST_1(sip->sip_subscription_state->ss_expires);
+  n_tags = e->data->e_tags;
+  TEST_1(tl_find(n_tags, nutag_substate));
+  TEST(tl_find(n_tags, nutag_substate)->t_value, nua_substate_active);
+  TEST_1(!e->next);
+  free_events_in_list(ctx, a->events);
+
+  /*
+   Server events:
+   nua_r_authorize
+  */
+  TEST_1(e = b->events->head);
+  TEST_E(e->data->e_event, nua_r_authorize);
+  TEST_1(!e->next);
+
+  free_events_in_list(ctx, b->events);
+
+  if (print_headings)
+    printf("TEST NUA-12.4: PASSED\n");
+
+  /* ---------------------------------------------------------------------- */
+
+/* NOTIFY terminating subscription
+
+   A			B
+   |                    |
+   |<------NOTIFY-------|
+   |-------200 OK------>|
+   |                    |
+*/
+
+  if (print_headings)
+    printf("TEST NUA-12.6: terminate notifier\n");
+
+  TERMINATE(b, b_call, b_call->nh, TAG_END());
+
+  run_ab_until(ctx, -1, save_until_notified, -1, until_final_response);
+
+  /* Client events:
+     nua_i_notify
+  */
+  TEST_1(e = a->events->head); TEST_E(e->data->e_event, nua_i_notify);
+  TEST_1(sip = sip_object(e->data->e_msg));
+  TEST_1(sip->sip_event); TEST_S(sip->sip_event->o_type, "presence");
+  TEST_S(sip->sip_event->o_id, "1");
+  TEST_1(sip->sip_subscription_state);
+  TEST_S(sip->sip_subscription_state->ss_substate, "terminated");
+  TEST_1(!sip->sip_subscription_state->ss_expires);
+  n_tags = e->data->e_tags;
+  TEST_1(tl_find(n_tags, nutag_substate));
+  TEST(tl_find(n_tags, nutag_substate)->t_value, nua_substate_terminated);
+  TEST_1(!e->next);
+  free_events_in_list(ctx, a->events);
+
+  if (print_headings)
+    printf("TEST NUA-12.6: PASSED\n");
+
+  /* ---------------------------------------------------------------------- */
+
+
+  nua_handle_destroy(a_call->nh), a_call->nh = NULL;
+  nua_handle_destroy(b_call->nh), b_call->nh = NULL;
+
+  END();			/* test_events */
+}



More information about the Freeswitch-svn mailing list