/*
 * This file is part of the Sofia-SIP package
 *
 * Copyright (C) 2008 Nokia Corporation.
 *
 * Contact: Pekka Pessi <pekka.pessi@nokia.com>
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public License
 * as published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA
 *
 */

/**@CFILE check_session.c
 *
 * @brief NUA module tests for SIP session handling
 *
 * @author Pekka Pessi <Pekka.Pessi@nokia.com>
 *
 * @copyright (C) 2008 Nokia Corporation.
 */

#include "config.h"

#undef NDEBUG

#include "check_nua.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_string.h>
#include <sofia-sip/su_tag_io.h>

#include <stdlib.h>
#include <string.h>
#include <assert.h>

/* define XXX as 1 in order to see all failing test cases */
#ifndef XXX
#define XXX (0)
#endif

/* ====================================================================== */

static nua_t *nua;
static soa_session_t *soa = NULL;
static struct dialog *dialog = NULL;

#define CRLF "\r\n"

static void call_setup(void)
{
  nua = s2_nua_setup("call",
		     SIPTAG_ORGANIZATION_STR("Pussy Galore's Flying Circus"),
                     NUTAG_OUTBOUND("no-options-keepalive, no-validate"),
		     TAG_END());

  soa = soa_create(NULL, s2base->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_thread_setup(void)
{
  s2_nua_thread = 1;
  call_setup();
}

static void call_teardown(void)
{
  s2_teardown_started("call");

  mark_point();

  s2_register_teardown();

  if (s2->shutdown == 0) {
    mark_point();
    nua_shutdown(nua);
    fail_unless(s2_check_event(nua_r_shutdown, 200));
  }

  mark_point();

  s2_nua_teardown();
}

static void
add_call_fixtures(TCase *tc, int threading)
{
  if (threading)
    tcase_add_checked_fixture(tc, call_thread_setup, call_teardown);
  else
    tcase_add_checked_fixture(tc, call_setup, call_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_sip_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_sip_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_sip_wait_for_request(SIP_METHOD_INVITE);
}

static uint32_t s2_rseq;

static struct message *
respond_with_100rel(struct message *invite,
		    struct dialog *d,
		    int with_sdp,
		    int status, char const *phrase,
		    tag_type_t tag, tag_value_t value, ...)
{
  ta_list ta;
  sip_rseq_t rs[1];

  assert(100 < status && status < 200);

  sip_rseq_init(rs);
  rs->rs_response = ++s2_rseq;

  ta_start(ta, tag, value);

  if (with_sdp) {
    respond_with_sdp(
      invite, dialog, status, phrase,
      SIPTAG_REQUIRE_STR("100rel"),
      SIPTAG_RSEQ(rs),
      ta_tags(ta));
  }
  else {
    s2_sip_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));

  return s2_sip_wait_for_request(SIP_METHOD_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, 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_sip_free_message(invite);
  fail_unless(s2_check_event(nua_r_invite, 200));
  fail_unless(s2_check_callstate(nua_callstate_ready));
  fail_unless(s2_sip_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;

  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);

  s2_free_event(invite);

  response = s2_sip_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_sip_wait_for_response(180, SIP_METHOD_INVITE);
  fail_if(!response);
  s2_sip_update_dialog(dialog, response);
  process_answer(response);
  s2_sip_free_message(response);

  nua_respond(nh, SIP_200_OK, TAG_END());

  fail_unless(s2_check_callstate(nua_callstate_completed));

  response = s2_sip_wait_for_response(200, SIP_METHOD_INVITE);

  fail_if(!response);
  s2_sip_update_dialog(dialog, response);
  s2_sip_free_message(response);

  fail_if(s2_sip_request_to(dialog, SIP_METHOD_ACK, NULL, 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_sip_wait_for_request(SIP_METHOD_BYE);
  fail_if(!bye);
  s2_sip_respond_to(bye, dialog, SIP_200_OK, TAG_END());
  s2_sip_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_sip_wait_for_request(SIP_METHOD_BYE);
  fail_if(!bye);
  s2_sip_respond_to(bye, dialog, SIP_407_PROXY_AUTH_REQUIRED,
		SIPTAG_PROXY_AUTHENTICATE_STR(s2_auth_digest_str),
		TAG_END());
  s2_sip_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_sip_wait_for_request(SIP_METHOD_BYE);
  fail_if(!bye);
  s2_sip_respond_to(bye, dialog, SIP_200_OK, TAG_END());
  s2_sip_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_sip_wait_for_request(SIP_METHOD_CANCEL);
  fail_if(!cancel);
  s2_sip_respond_to(cancel, dialog, SIP_200_OK, TAG_END());
  s2_sip_free_message(cancel);
  fail_unless(s2_check_event(nua_r_cancel, 200));

  s2_sip_respond_to(invite, dialog, SIP_487_REQUEST_CANCELLED, TAG_END());
  fail_unless(s2_sip_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_sip_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_sip_check_response(200, SIP_METHOD_BYE));
}

/* ====================================================================== */
/* 2 - Call cases */

/* 2.1 - Basic call cases */

START_TEST(call_2_1_1)
{
  nua_handle_t *nh;

  s2_case("2.1.1", "Basic call",
	  "NUA sends INVITE, NUA sends BYE");

  nh = nua_handle(nua, NULL, SIPTAG_TO(s2sip->aor), TAG_END());

  invite_by_nua(nh, TAG_END());

  bye_by_nua(nh, TAG_END());

  nua_handle_destroy(nh);
}
END_TEST


START_TEST(call_2_1_2_1)
{
  nua_handle_t *nh;

  s2_case("2.1.2.1", "Basic call",
	  "NUA sends INVITE, NUA receives BYE");

  nh = nua_handle(nua, NULL, SIPTAG_TO(s2sip->aor), TAG_END());

  invite_by_nua(nh, TAG_END());

  bye_to_nua(nh, TAG_END());

  nua_handle_destroy(nh);
}
END_TEST

START_TEST(call_2_1_2_2)
{
  nua_handle_t *nh;

  s2_case("2.1.2.2", "Basic call over TCP",
	  "NUA sends INVITE, NUA receives BYE");

  nh = nua_handle(nua, NULL, SIPTAG_TO(s2sip->aor),
		  TAG_END());

  invite_by_nua(nh,
		NUTAG_PROXY(s2sip->tcp.contact->m_url),
		TAG_END());

  bye_to_nua(nh, TAG_END());

  nua_handle_destroy(nh);
}
END_TEST


START_TEST(call_2_1_3_1)
{
  nua_handle_t *nh;

  s2_case("2.1.3.1", "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_2_1_3_2)
{
  nua_handle_t *nh;

  s2_case("2.1.3.2", "Incoming call over TCP",
	  "NUA receives INVITE and BYE");

  dialog->tport = s2sip->tcp.tport;

  nh = invite_to_nua(TAG_END());

  bye_to_nua(nh, TAG_END());

  nua_handle_destroy(nh);
}
END_TEST


START_TEST(call_2_1_4)
{
  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_2_1_5)
{
  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;

  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);

  s2_free_event(invite);

  response = s2_sip_wait_for_response(100, SIP_METHOD_INVITE);
  fail_if(!response);

  nua_respond(nh, SIP_180_RINGING,
	      /* Dialog-specific proxy is saved */
	      NUTAG_PROXY(s2sip->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_sip_wait_for_response(180, SIP_METHOD_INVITE);
  fail_if(!response);
  s2_sip_update_dialog(dialog, response);
  process_answer(response);
  s2_sip_free_message(response);

  nua_respond(nh, SIP_200_OK, TAG_END());

  fail_unless(s2_check_callstate(nua_callstate_completed));

  response = s2_sip_wait_for_response(200, SIP_METHOD_INVITE);

  fail_if(!response);
  s2_sip_update_dialog(dialog, response);
  s2_sip_free_message(response);

  fail_if(s2_sip_request_to(dialog, SIP_METHOD_ACK, NULL, 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_sip_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_sip_respond_to(bye, dialog, SIP_200_OK, TAG_END());
  s2_sip_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


START_TEST(call_2_1_7)
{
  nua_handle_t *nh, *nh2;
  sip_replaces_t *replaces;

  s2_case("2.1.7", "Call lookup",
	  "Test dialog and call-id lookup");

  nh = nua_handle(nua, NULL, SIPTAG_TO(s2sip->aor), TAG_END());

  invite_by_nua(nh, TAG_END());

  nh2 = nua_handle_by_call_id(nua, dialog->call_id->i_id);
  fail_if(!nh2); fail_if(nh != nh2); nua_handle_unref(nh2);

  replaces = sip_replaces_format(NULL, "%s;from-tag=%s;to-tag=%s",
				 dialog->call_id->i_id,
				 dialog->local->a_tag,
				 dialog->remote->a_tag);
  fail_if(!replaces);

  nh2 = nua_handle_by_replaces(nua, replaces);
  fail_if(!nh2); fail_if(nh != nh2); nua_handle_unref(nh2);

  msg_header_free_all(NULL, (msg_header_t *)replaces);

  bye_by_nua(nh, TAG_END());

  nua_handle_destroy(nh);
}
END_TEST


START_TEST(call_2_1_8)
{
  nua_handle_t *nh;
  struct message *invite, *ack;

  s2_case("2.1.8", "Call using NUTAG_PROXY()",
	  "Test handle-specific NUTAG_PROXY().");

  nh = nua_handle(nua, NULL, SIPTAG_TO(s2sip->aor), TAG_END());

  invite = invite_sent_by_nua(
    nh, SOATAG_USER_SDP_STR("m=audio 5004 RTP/AVP 0 8"),
    NUTAG_PROXY(s2sip->tcp.contact->m_url), TAG_END());

  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_sip_free_message(invite);
  fail_unless(s2_check_event(nua_r_invite, 200));
  fail_unless(s2_check_callstate(nua_callstate_ready));
  ack = s2_sip_wait_for_request(SIP_METHOD_ACK);
  fail_unless(ack && tport_is_tcp(ack->tport));

  bye_by_nua(nh, TAG_END());

  nua_handle_destroy(nh);
}
END_TEST


TCase *invite_tcase(int threading)
{
  TCase *tc = tcase_create("2.1 - Basic INVITE");
  add_call_fixtures(tc, threading);
  {
    tcase_add_test(tc, call_2_1_1);
    tcase_add_test(tc, call_2_1_2_1);
    tcase_add_test(tc, call_2_1_2_2);
    tcase_add_test(tc, call_2_1_3_1);
    tcase_add_test(tc, call_2_1_3_2);
    tcase_add_test(tc, call_2_1_4);
    tcase_add_test(tc, call_2_1_5);
    tcase_add_test(tc, call_2_1_6);
    tcase_add_test(tc, call_2_1_7);
    tcase_add_test(tc, call_2_1_8);
  }
  return tc;
}

/* ---------------------------------------------------------------------- */
/* 2.2 - Call CANCEL cases */

START_TEST(cancel_2_2_1)
{
  nua_handle_t *nh;
  struct message *invite, *cancel;

  s2_case("2.2.1", "Cancel call",
	  "NUA is caller, NUA sends CANCEL immediately");

  nh = nua_handle(nua, NULL, SIPTAG_TO(s2sip->aor), 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_sip_wait_for_request(SIP_METHOD_INVITE);
  fail_if(!invite);
  fail_if(s2sip->received != NULL);
  s2_sip_respond_to(invite, dialog, SIP_100_TRYING, TAG_END());
  cancel = s2_sip_wait_for_request(SIP_METHOD_CANCEL);
  fail_if(!cancel);
  s2_sip_respond_to(invite, dialog, SIP_487_REQUEST_CANCELLED, TAG_END());
  s2_sip_respond_to(cancel, dialog, SIP_200_OK, TAG_END());

  fail_unless(s2_sip_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_2_2_2)
{
  nua_handle_t *nh;
  struct message *invite;

  s2_case("2.2.2", "Canceled call",
	  "NUA is caller, NUA sends CANCEL after receiving 100");

  nh = nua_handle(nua, NULL, SIPTAG_TO(s2sip->aor), 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_sip_wait_for_request(SIP_METHOD_INVITE);
  process_offer(invite);
  s2_sip_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_2_2_3)
{
  nua_handle_t *nh;
  struct message *invite;

  s2_case("2.2.3", "Canceled call",
	  "NUA is caller, NUA sends CANCEL after receiving 180");

  nh = nua_handle(nua, NULL, SIPTAG_TO(s2sip->aor), 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_sip_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_2_2_4)
{
  nua_handle_t *nh;
  struct message *invite, *cancel;

  s2_case("2.2.4", "Cancel and 200 OK glare",
	  "NUA is caller, NUA sends CANCEL after receiving 180 "
	  "but UAS already sent 200 OK.");

  nh = nua_handle(nua, NULL, SIPTAG_TO(s2sip->aor), 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_sip_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_sip_wait_for_request(SIP_METHOD_CANCEL);
  fail_if(!cancel);

  respond_with_sdp(invite, dialog, SIP_200_OK, TAG_END());

  s2_sip_respond_to(cancel, dialog, SIP_481_NO_TRANSACTION, TAG_END());
  s2_sip_free_message(cancel);
  fail_unless(s2_check_event(nua_r_cancel, 481));

  fail_unless(s2_sip_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


START_TEST(cancel_2_2_5)
{
  nua_handle_t *nh;
  struct message *invite, *cancel, *bye;

  s2_case(
    "2.2.5", "Cancel and 200 OK glare",
    "NUA is caller, "
    "NUA uses nua_bye() to send CANCEL after receiving 180\n"
    "but UAS already sent 200 OK.\n"
    "Test case checks that NUA really sends BYE after nua_bye() is called\n");

  nh = nua_handle(nua, NULL, SIPTAG_TO(s2sip->aor), TAG_END());

  nua_invite(nh, SOATAG_USER_SDP_STR("m=audio 5004 RTP/AVP 0 8"),
	     NUTAG_AUTOACK(0),
	     TAG_END());
  fail_unless(s2_check_callstate(nua_callstate_calling));

  invite = s2_sip_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_bye(nh, TAG_END());
  cancel = s2_sip_wait_for_request(SIP_METHOD_CANCEL);
  fail_if(!cancel);

  respond_with_sdp(invite, dialog, SIP_200_OK, TAG_END());

  s2_sip_respond_to(cancel, dialog, SIP_481_NO_TRANSACTION, TAG_END());
  s2_sip_free_message(cancel);
  fail_unless(s2_check_event(nua_r_cancel, 481));

  fail_unless(s2_sip_check_request(SIP_METHOD_ACK));

  fail_unless(s2_check_callstate(nua_callstate_terminating));

  bye = s2_sip_wait_for_request(SIP_METHOD_BYE);
  fail_if(!bye);
  s2_sip_respond_to(bye, dialog, SIP_200_OK, TAG_END());
  s2_sip_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


START_TEST(cancel_2_2_6)
{
  nua_handle_t *nh;
  struct event *invite;
  struct message *response;

  s2_case("2.2.6", "Cancel call",
	  "NUA is callee, sends 100, 180, INVITE gets canceled");

  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);

  s2_free_event(invite);

  response = s2_sip_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_sip_wait_for_response(180, SIP_METHOD_INVITE);
  fail_if(!response);
  s2_sip_update_dialog(dialog, response);
  process_answer(response);
  s2_sip_free_message(response);

  fail_if(s2_sip_request_to(dialog, SIP_METHOD_CANCEL, NULL, TAG_END()));
  fail_unless(s2_check_event(nua_i_cancel, 200));
  fail_unless(s2_check_callstate(nua_callstate_terminated));

  response = s2_sip_wait_for_response(200, SIP_METHOD_CANCEL);
  fail_if(!response);
  s2_sip_free_message(response);

  response = s2_sip_wait_for_response(487, SIP_METHOD_INVITE);
  fail_if(s2_sip_request_to(dialog, SIP_METHOD_ACK, NULL,
			SIPTAG_VIA(sip_object(dialog->invite)->sip_via),
			TAG_END()));

  nua_handle_destroy(nh);
}
END_TEST


START_TEST(cancel_2_2_7)
{
  nua_handle_t *nh;
  struct event *invite;
  struct message *response;
  char const *via = "SIP/2.0/UDP host.in.invalid;rport";

  s2_case("2.2.7", "Call gets canceled",
	  "NUA is callee, sends 100, 180, INVITE gets canceled. "
	  "Using RFC 2543 dialog and transaction matching.");

  soa_generate_offer(soa, 1, NULL);
  request_with_sdp(dialog, SIP_METHOD_INVITE, NULL,
		   SIPTAG_VIA_STR(via),
		   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);

  s2_free_event(invite);

  response = s2_sip_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_sip_wait_for_response(180, SIP_METHOD_INVITE);
  fail_if(!response);
  s2_sip_update_dialog(dialog, response);
  process_answer(response);
  s2_sip_free_message(response);

  fail_if(s2_sip_request_to(dialog, SIP_METHOD_CANCEL, NULL, TAG_END()));
  fail_unless(s2_check_event(nua_i_cancel, 200));
  fail_unless(s2_check_callstate(nua_callstate_terminated));

  response = s2_sip_wait_for_response(200, SIP_METHOD_CANCEL);
  fail_if(!response);
  s2_sip_free_message(response);

  response = s2_sip_wait_for_response(487, SIP_METHOD_INVITE);
  fail_if(s2_sip_request_to(dialog, SIP_METHOD_ACK, NULL,
			SIPTAG_VIA(sip_object(dialog->invite)->sip_via),
			TAG_END()));

  nua_handle_destroy(nh);
}
END_TEST


START_TEST(cancel_2_2_8)
{
  nua_handle_t *nh;
  struct message *invite, *cancel;
  int timeout;

  s2_case("2.2.8", "CANCEL and INVITE times out",
	  "NUA is caller, NUA sends CANCEL after receiving 180 "
	  "but UAS never responds.");

  nh = nua_handle(nua, NULL, SIPTAG_TO(s2sip->aor), 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_sip_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_sip_wait_for_request(SIP_METHOD_CANCEL);
  s2_sip_free_message(cancel);
  fail_if(!cancel);

  /* Now, time out both CANCEL and INVITE */
  for (timeout = 0; timeout < 34; timeout++) {
    s2_nua_fast_forward(1, s2base->root);
    cancel = s2_sip_next_request(SIP_METHOD_CANCEL);
    if (cancel)
      s2_sip_free_message(cancel);
  }

  fail_unless(s2_check_event(nua_r_cancel, 408));
  fail_unless(s2_check_event(nua_r_invite, 408));
  fail_unless(s2_check_callstate(nua_callstate_terminated));
  nua_handle_destroy(nh);
}
END_TEST

START_TEST(cancel_2_2_9)
{
  nua_handle_t *nh;
  struct message *invite, *cancel;
  int timeout;

  s2_case("2.2.9", "CANCEL a RFC2543 UA",
	  "NUA is caller, NUA sends CANCEL after receiving 180, "
	  "UAS sends 200 OK to CANCEL but no response to INVITE.");

  nh = nua_handle(nua, NULL, SIPTAG_TO(s2sip->aor), 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_sip_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_sip_wait_for_request(SIP_METHOD_CANCEL);
  fail_if(!cancel);
  s2_sip_respond_to(cancel, dialog, SIP_200_OK, TAG_END());
  s2_sip_free_message(cancel);

  /* Time out INVITE */
  for (timeout = 0; timeout < 34; timeout++) {
    s2_nua_fast_forward(1, s2base->root);
  }

  fail_unless(s2_check_event(nua_r_invite, 408));
  fail_unless(s2_check_callstate(nua_callstate_terminated));
  nua_handle_destroy(nh);
}
END_TEST

START_TEST(cancel_2_2_10)
{
  nua_handle_t *nh;
  struct message *invite, *cancel;
  struct event *event;
  int timeout;

  s2_case("2.2.10", "CANCEL and INVITE times out",
	  "NUA is caller, NUA sends CANCEL after receiving 180 "
	  "but UAS never responds.");

  nh = nua_handle(nua, NULL, SIPTAG_TO(s2sip->aor), 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_sip_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_sip_wait_for_request(SIP_METHOD_CANCEL);
  s2_sip_free_message(cancel);
  fail_if(!cancel);

  nua_cancel(nh, TAG_END());
  cancel = s2_sip_wait_for_request(SIP_METHOD_CANCEL);
  fail_if(!cancel);
  s2_sip_respond_to(cancel, dialog, SIP_200_OK, TAG_END());
  s2_sip_free_message(cancel);

  /* emulate network gone bad below
     zap registration handle here
     so that s2_register_teardown() does not hang
  */
  s2->registration->nh = NULL;

  nua_set_params(s2->nua, NUTAG_SHUTDOWN_EVENTS(1), TAG_END());
  fail_unless(s2_check_event(nua_r_set_params, 200));

  nua_shutdown(s2->nua);
  event = s2_wait_for_event(nua_r_shutdown, 100);
  fail_unless(event != NULL);
  s2_free_event(event);

  /* Time out */
  for (timeout = 0; timeout < 34; timeout++) {
    s2_nua_fast_forward(5, s2base->root);
    nua_shutdown(s2->nua);
    event = s2_wait_for_event(nua_r_shutdown, 0);
    fail_unless(event != NULL);
    if (event->data->e_status >= 200)
      break;
  }

  s2->shutdown = 200;
}
END_TEST



TCase *cancel_tcase(int threading)
{
  TCase *tc = tcase_create("2.2 - CANCEL");
  add_call_fixtures(tc, threading);

  tcase_add_test(tc, cancel_2_2_1);
  tcase_add_test(tc, cancel_2_2_2);
  tcase_add_test(tc, cancel_2_2_3);
  tcase_add_test(tc, cancel_2_2_4);
  if (XXX) tcase_add_test(tc, cancel_2_2_5);
  tcase_add_test(tc, cancel_2_2_6);
  tcase_add_test(tc, cancel_2_2_7);
  tcase_add_test(tc, cancel_2_2_8);
  tcase_add_test(tc, cancel_2_2_9);
  tcase_add_test(tc, cancel_2_2_10);

  return tc;
}


/* ---------------------------------------------------------------------- */
/* 2.3 - Session timers */

/* Wait for invite from NUA */
static struct message *
invite_timer_round(nua_handle_t *nh,
		   char const *session_expires,
		   sip_record_route_t *rr)
{
  struct message *invite, *ack;

  fail_unless(s2_check_callstate(nua_callstate_calling));
  invite = s2_sip_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"),
    SIPTAG_RECORD_ROUTE(rr),
    TAG_END());
  s2_sip_free_message(invite);
  fail_unless(s2_check_event(nua_r_invite, 200));
  fail_unless(s2_check_callstate(nua_callstate_ready));
  ack = s2_sip_wait_for_request(SIP_METHOD_ACK);
  if (rr == NULL)
    s2_sip_free_message(ack);
  return ack;
}

START_TEST(call_2_3_1)
{
  nua_handle_t *nh;
  sip_record_route_t rr[1];
  struct message *ack;

  sip_record_route_init(rr);
  *rr->r_url = *s2sip->contact->m_url;
  rr->r_url->url_user = "record";
  rr->r_url->url_params = "lr";

  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_nua_fast_forward(300, s2base->root);
  ack = invite_timer_round(nh, "300;refresher=uac", rr);
  fail_if(ack->sip->sip_route &&
	  su_strmatch(ack->sip->sip_route->r_url->url_user, "record"));
  s2_nua_fast_forward(300, s2base->root);
  invite_timer_round(nh, "300;refresher=uac", NULL);

  bye_by_nua(nh, TAG_END());

  nua_handle_destroy(nh);
}
END_TEST

START_TEST(call_2_3_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_nua_fast_forward(300, s2base->root);
  invite_timer_round(nh, "300", NULL);
  s2_nua_fast_forward(300, s2base->root);
  invite_timer_round(nh, "300", NULL);

  bye_by_nua(nh, TAG_END());

  nua_handle_destroy(nh);
}
END_TEST



TCase *session_timer_tcase(int threading)
{
  TCase *tc = tcase_create("2.3 - Session timers");
  add_call_fixtures(tc, threading);
  {
    tcase_add_test(tc, call_2_3_1);
    tcase_add_test(tc, call_2_3_2);
  }
  return tc;
}

/* ====================================================================== */
/* 2.4 - 100rel */

START_TEST(call_2_4_1)
{
  nua_handle_t *nh;
  struct message *invite, *prack, *ack;
  int with_sdp;
  sip_record_route_t rr[1];

  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.");

  sip_record_route_init(rr);
  *rr->r_url = *s2sip->contact->m_url;
  rr->r_url->url_user = "record";
  rr->r_url->url_params = "lr";

  nh = nua_handle(nua, NULL, SIPTAG_TO(s2sip->aor), 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, with_sdp = 1,
			      SIP_183_SESSION_PROGRESS,
			      SIPTAG_RECORD_ROUTE(rr),
			      TAG_END());
  s2_sip_respond_to(prack, dialog, SIP_200_OK, TAG_END());
  s2_sip_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, with_sdp = 0,
			      SIP_180_RINGING,
			      TAG_END());
  fail_unless(prack->sip->sip_route != NULL);
  fail_unless(su_strmatch(prack->sip->sip_route->r_url->url_user, "record"));

  s2_sip_respond_to(prack, dialog, SIP_200_OK, TAG_END());
  s2_sip_free_message(prack), prack = NULL;
  fail_unless(s2_check_callstate(nua_callstate_proceeding));
  fail_unless(s2_check_event(nua_r_prack, 200));

  /* Change the record-route */
  rr->r_url->url_user = "record2";
  s2_sip_respond_to(invite, dialog, SIP_200_OK,
		SIPTAG_RECORD_ROUTE(rr),
		TAG_END());
  s2_sip_free_message(invite);
  fail_unless(s2_check_event(nua_r_invite, 200));
  fail_unless(s2_check_callstate(nua_callstate_ready));
  ack = s2_sip_wait_for_request(SIP_METHOD_ACK);
  fail_if(!ack);
  fail_unless(su_strmatch(ack->sip->sip_route->r_url->url_user, "record2"));
  s2_sip_free_message(ack);

  bye_to_nua(nh, TAG_END());

  nua_handle_destroy(nh);
}
END_TEST

START_TEST(call_2_4_2)
{
  nua_handle_t *nh;
  struct message *invite, *prack;
  int with_sdp;

  s2_case("2.4.2", "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(s2sip->aor), 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, with_sdp = 0,
			      SIP_183_SESSION_PROGRESS,
			      TAG_END());
  s2_sip_respond_to(prack, dialog, SIP_200_OK, TAG_END());
  s2_sip_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, with_sdp = 0,
			      SIP_180_RINGING,
			      TAG_END());
  s2_sip_respond_to(prack, dialog, SIP_200_OK, TAG_END());
  s2_sip_free_message(prack), prack = NULL;
  fail_unless(s2_check_callstate(nua_callstate_proceeding));
  fail_unless(s2_check_event(nua_r_prack, 200));

  s2_sip_respond_to(invite, dialog, SIP_200_OK, TAG_END());
  s2_sip_free_message(invite);
  fail_unless(s2_check_event(nua_r_invite, 200));
  fail_unless(s2_check_callstate(nua_callstate_ready));
  fail_unless(s2_sip_check_request(SIP_METHOD_ACK));

  bye_to_nua(nh, TAG_END());

  nua_handle_destroy(nh);
}
END_TEST

TCase *invite_100rel_tcase(int threading)
{
  TCase *tc = tcase_create("2.4 - INVITE with 100rel");
  add_call_fixtures(tc, threading);
  {
    tcase_add_test(tc, call_2_4_1);
    tcase_add_test(tc, call_2_4_2);
  }
  return tc;
}

/* ====================================================================== */
/* 2.5 - Call with preconditions */

START_TEST(call_2_5_1)
{
  nua_handle_t *nh;
  struct message *invite, *prack, *update;
  int with_sdp;

  s2_case("2.5.1", "Call with preconditions",
	  "NUA sends INVITE, "
	  "receives 183, sends PRACK, receives 200 for it, "
	  "sends UPDATE, receives 200 for it, "
	  "receives 180, sends PRACK, receives 200 for it, "
          "receives 200, send ACK.");

  nh = nua_handle(nua, NULL, SIPTAG_TO(s2sip->aor), TAG_END());

  invite = invite_sent_by_nua(
    nh, SOATAG_USER_SDP_STR("m=audio 5004 RTP/AVP 0 8"),
    SIPTAG_REQUIRE_STR("precondition"),
    TAG_END());
  process_offer(invite);

  prack = respond_with_100rel(invite, dialog, with_sdp = 1,
			      SIP_183_SESSION_PROGRESS,
			      TAG_END());
  fail_unless(s2_check_callstate(nua_callstate_proceeding));
  process_offer(prack);
  respond_with_sdp(
    prack, dialog, SIP_200_OK,
    SIPTAG_REQUIRE_STR("100rel"),
    TAG_END());
  s2_sip_free_message(prack), prack = NULL;
  fail_unless(s2_check_event(nua_r_prack, 200));
  fail_unless(s2_check_callstate(nua_callstate_proceeding));

  update = s2_sip_wait_for_request(SIP_METHOD_UPDATE);
  /* UPDATE sent by stack, stack sends event for it */
  fail_unless(s2_check_callstate(nua_callstate_proceeding));

  process_offer(update);
  respond_with_sdp(
    update, dialog, SIP_200_OK,
    TAG_END());
  s2_sip_free_message(update), update = NULL;

  fail_unless(s2_check_event(nua_r_update, 200));
  fail_unless(s2_check_callstate(nua_callstate_proceeding));

  prack = respond_with_100rel(invite, dialog, with_sdp = 0,
			      SIP_180_RINGING,
			      TAG_END());
  s2_sip_respond_to(prack, dialog, SIP_200_OK, TAG_END());
  s2_sip_free_message(prack), prack = NULL;
  fail_unless(s2_check_callstate(nua_callstate_proceeding));
  fail_unless(s2_check_event(nua_r_prack, 200));

  s2_sip_respond_to(invite, dialog, SIP_200_OK, TAG_END());
  s2_sip_free_message(invite);
  fail_unless(s2_check_event(nua_r_invite, 200));
  fail_unless(s2_check_callstate(nua_callstate_ready));
  fail_unless(s2_sip_check_request(SIP_METHOD_ACK));

  bye_to_nua(nh, TAG_END());

  nua_handle_destroy(nh);
}
END_TEST

START_TEST(call_2_5_2)
{
  nua_handle_t *nh;
  struct message *invite, *prack, *update;
  sip_rseq_t rs[1];
  sip_rack_t rack[1];

  s2_case("2.5.2", "Call with preconditions - send 200 w/ ongoing PRACK ",
	  "NUA sends INVITE, "
	  "receives 183, sends PRACK, "
          "receives 200 to INVITE, "
	  "receives 200 to PRACK, "
	  "sends ACK, "
	  "sends UPDATE, "
	  "receives 200 to UPDATE.");

  nh = nua_handle(nua, NULL, SIPTAG_TO(s2sip->aor), TAG_END());

  invite = invite_sent_by_nua(
    nh,
    SOATAG_USER_SDP_STR("m=audio 5004 RTP/AVP 0 8"),
    SIPTAG_REQUIRE_STR("precondition"),
    NUTAG_APPL_METHOD("PRACK"),
    TAG_END());
  process_offer(invite);

  sip_rseq_init(rs)->rs_response = ++s2_rseq;
  respond_with_sdp(
    invite, dialog, SIP_183_SESSION_PROGRESS,
    SIPTAG_REQUIRE_STR("100rel"),
    SIPTAG_RSEQ(rs),
    TAG_END());
  fail_unless(s2_check_event(nua_r_invite, 183));
  fail_unless(s2_check_callstate(nua_callstate_proceeding));

  sip_rack_init(rack)->ra_response = s2_rseq;
  rack->ra_cseq = invite->sip->sip_cseq->cs_seq;
  rack->ra_method = invite->sip->sip_cseq->cs_method;
  rack->ra_method_name = invite->sip->sip_cseq->cs_method_name;

  nua_prack(nh, SIPTAG_RACK(rack), TAG_END());
  prack = s2_sip_wait_for_request(SIP_METHOD_PRACK);
  process_offer(prack);

  s2_sip_respond_to(invite, dialog, SIP_200_OK, TAG_END());
  s2_sip_free_message(invite);
  fail_unless(s2_check_event(nua_r_invite, 200));
  fail_unless(s2_check_callstate(nua_callstate_completing));

  respond_with_sdp(
    prack, dialog, SIP_200_OK,
    TAG_END());
  s2_sip_free_message(prack), prack = NULL;
  fail_unless(s2_check_event(nua_r_prack, 200));
  fail_unless(s2_check_callstate(nua_callstate_ready));

  fail_unless(s2_sip_check_request(SIP_METHOD_ACK));

  update = s2_sip_wait_for_request(SIP_METHOD_UPDATE);
  /* UPDATE sent by stack, stack sends event for it */
  fail_unless(s2_check_callstate(nua_callstate_calling));

  process_offer(update);
  respond_with_sdp(
    update, dialog, SIP_200_OK,
    TAG_END());
  s2_sip_free_message(update), update = NULL;

  fail_unless(s2_check_event(nua_r_update, 200));
  fail_unless(s2_check_callstate(nua_callstate_ready));

  bye_to_nua(nh, TAG_END());

  nua_handle_destroy(nh);
}
END_TEST

START_TEST(call_2_5_3)
{
  nua_handle_t *nh;
  struct message *invite, *prack, *update;
  sip_rseq_t rs[1];
  sip_rack_t rack[1];

  s2_case("2.5.3", "Call with preconditions - send 200 w/ ongoing UPDATE ",
	  "NUA sends INVITE, "
	  "receives 183, sends PRACK, receives 200 to PRACK, "
	  "sends UPDATE, "
          "receives 200 to INVITE, "
	  "receives 200 to UPDATE, "
	  "sends ACK.");

  nh = nua_handle(nua, NULL, SIPTAG_TO(s2sip->aor), TAG_END());

  invite = invite_sent_by_nua(
    nh,
    SOATAG_USER_SDP_STR("m=audio 5004 RTP/AVP 0 8"),
    SIPTAG_REQUIRE_STR("precondition"),
    NUTAG_APPL_METHOD("PRACK"),
    TAG_END());
  process_offer(invite);

  sip_rseq_init(rs)->rs_response = ++s2_rseq;
  respond_with_sdp(
    invite, dialog, SIP_183_SESSION_PROGRESS,
    SIPTAG_REQUIRE_STR("100rel"),
    SIPTAG_RSEQ(rs),
    TAG_END());
  fail_unless(s2_check_event(nua_r_invite, 183));
  fail_unless(s2_check_callstate(nua_callstate_proceeding));

  sip_rack_init(rack)->ra_response = s2_rseq;
  rack->ra_cseq = invite->sip->sip_cseq->cs_seq;
  rack->ra_method = invite->sip->sip_cseq->cs_method;
  rack->ra_method_name = invite->sip->sip_cseq->cs_method_name;

  nua_prack(nh, SIPTAG_RACK(rack), TAG_END());
  prack = s2_sip_wait_for_request(SIP_METHOD_PRACK);
  process_offer(prack);
  respond_with_sdp(
    prack, dialog, SIP_200_OK,
    TAG_END());
  s2_sip_free_message(prack), prack = NULL;
  fail_unless(s2_check_event(nua_r_prack, 200));
  fail_unless(s2_check_callstate(nua_callstate_proceeding));

  update = s2_sip_wait_for_request(SIP_METHOD_UPDATE);
  /* UPDATE sent by stack, stack sends event for it */
  fail_unless(s2_check_callstate(nua_callstate_proceeding));

  s2_sip_respond_to(invite, dialog, SIP_200_OK, TAG_END());
  s2_sip_free_message(invite);
  fail_unless(s2_check_event(nua_r_invite, 200));
  fail_unless(s2_check_callstate(nua_callstate_completing));

  process_offer(update);
  respond_with_sdp(
    update, dialog, SIP_200_OK,
    TAG_END());
  s2_sip_free_message(update), update = NULL;

  fail_unless(s2_check_event(nua_r_update, 200));
  fail_unless(s2_check_callstate(nua_callstate_ready));
  fail_unless(s2_sip_check_request(SIP_METHOD_ACK));

  bye_to_nua(nh, TAG_END());

  nua_handle_destroy(nh);
}
END_TEST

TCase *invite_precondition_tcase(int threading)
{
  TCase *tc = tcase_create("2.5 - Call with preconditions");
  add_call_fixtures(tc, threading);
  {
    tcase_add_test(tc, call_2_5_1);
    tcase_add_test(tc, call_2_5_2);
    tcase_add_test(tc, call_2_5_3);
  }
  return tc;
}

/* ====================================================================== */
/* 2.6 - Re-INVITEs */

START_TEST(call_2_6_1)
{
  nua_handle_t *nh;
  struct message *invite, *ack;
  int i;

  s2_case("2.6.1", "Queued re-INVITEs",
	  "NUA receives INVITE, "
	  "sends re-INVITE twice, "
	  "sends BYE.");

  nh = invite_to_nua(TAG_END());

  nua_invite(nh, TAG_END());
  nua_invite(nh, TAG_END());

  for (i = 0; i < 2; i++) {
    fail_unless(s2_check_callstate(nua_callstate_calling));

    invite = s2_sip_wait_for_request(SIP_METHOD_INVITE);
    fail_if(!invite);
    process_offer(invite);
    respond_with_sdp(invite, dialog, SIP_200_OK, TAG_END());
    s2_sip_free_message(invite);

    ack = s2_sip_wait_for_request(SIP_METHOD_ACK);
    fail_if(!ack);
    s2_sip_free_message(ack);

    fail_unless(s2_check_event(nua_r_invite, 200));
    fail_unless(s2_check_callstate(nua_callstate_ready));
  }

  bye_by_nua(nh, TAG_END());

  nua_handle_destroy(nh);
}
END_TEST

START_TEST(call_2_6_2)
{
  nua_handle_t *nh;
  struct message *invite, *ack, *response;

  s2_case("2.6.2", "Re-INVITE glare",
	  "NUA sends re-INVITE and then receives re-INVITE, "
	  "sends BYE.");

  nh = invite_to_nua(TAG_END());

  nua_invite(nh, TAG_END());
  fail_unless(s2_check_callstate(nua_callstate_calling));

  soa_generate_offer(soa, 1, NULL);
  request_with_sdp(dialog, SIP_METHOD_INVITE, NULL, TAG_END());

  invite = s2_sip_wait_for_request(SIP_METHOD_INVITE);
  fail_if(!invite);
  respond_with_sdp(invite, dialog, SIP_500_INTERNAL_SERVER_ERROR,
		   SIPTAG_RETRY_AFTER_STR("8"),
		   TAG_END());
  s2_sip_free_message(invite);
  ack = s2_sip_wait_for_request(SIP_METHOD_ACK);
  fail_if(!ack);
  s2_sip_free_message(ack);

  response = s2_sip_wait_for_response(491, SIP_METHOD_INVITE);
  fail_if(!response);
  fail_if(s2_sip_request_to(dialog, SIP_METHOD_ACK, NULL,
			SIPTAG_VIA(sip_object(dialog->invite)->sip_via),
			TAG_END()));
  s2_sip_free_message(response);
  fail_if(soa_process_reject(soa, NULL) < 0);

  /* We get nua_r_invite with 100 trying (and 500 in sip->sip_status) */
  fail_unless(s2_check_event(nua_r_invite, 100));

  s2_nua_fast_forward(10, s2base->root);

  fail_unless(s2_check_callstate(nua_callstate_calling));

  invite = s2_sip_wait_for_request(SIP_METHOD_INVITE);
  process_offer(invite);

  respond_with_sdp(invite, dialog, SIP_200_OK, TAG_END());
  fail_unless(s2_check_event(nua_r_invite, 200));
  fail_unless(s2_check_callstate(nua_callstate_ready));
  ack = s2_sip_wait_for_request(SIP_METHOD_ACK);
  fail_if(!ack);
  s2_sip_free_message(ack);

  bye_by_nua(nh, TAG_END());

  nua_handle_destroy(nh);
}
END_TEST

START_TEST(call_2_6_3)
{
  nua_handle_t *nh;
  struct message *response;

  s2_case("2.6.3", "Handling re-INVITE without SDP gracefully",
	  "NUA receives INVITE, "
	  "re-INVITE without SDP (w/o NUTAG_REFRESH_WITHOUT_SDP(), "
	  "re-INVITE without SDP (using NUTAG_REFRESH_WITHOUT_SDP(), "
	  "sends BYE.");

  nh = invite_to_nua(
    TAG_END());

  s2_sip_request_to(dialog, SIP_METHOD_INVITE, NULL,
		SIPTAG_USER_AGENT_STR("evil (evil) evil"),
		TAG_END());

  nua_respond(nh, SIP_200_OK, TAG_END());

  fail_unless(s2_check_callstate(nua_callstate_completed));

  response = s2_sip_wait_for_response(200, SIP_METHOD_INVITE);

  fail_if(!response);
  s2_sip_update_dialog(dialog, response);
  fail_if(!response->sip->sip_content_type);
  s2_sip_free_message(response);

  fail_if(s2_sip_request_to(dialog, SIP_METHOD_ACK, NULL, TAG_END()));

  fail_unless(s2_check_event(nua_i_ack, 200));
  fail_unless(s2_check_callstate(nua_callstate_ready));

  s2_nua_fast_forward(10, s2base->root);

  nua_set_hparams(nh, NUTAG_REFRESH_WITHOUT_SDP(1), TAG_END());
  fail_unless(s2_check_event(nua_r_set_params, 200));

  s2_sip_request_to(dialog, SIP_METHOD_INVITE, NULL,
		SIPTAG_USER_AGENT_STR("evil (evil) evil"),
		TAG_END());

  nua_respond(nh, SIP_200_OK, TAG_END());

  fail_unless(s2_check_callstate(nua_callstate_completed));

  response = s2_sip_wait_for_response(200, SIP_METHOD_INVITE);

  fail_if(!response);
  s2_sip_update_dialog(dialog, response);
  fail_if(response->sip->sip_content_type);
  s2_sip_free_message(response);

  fail_if(s2_sip_request_to(dialog, SIP_METHOD_ACK, NULL, TAG_END()));

  fail_unless(s2_check_event(nua_i_ack, 200));
  fail_unless(s2_check_callstate(nua_callstate_ready));

  bye_by_nua(nh, TAG_END());

  nua_handle_destroy(nh);
}
END_TEST

START_TEST(call_2_6_4)
{
  nua_handle_t *nh;
  struct message *invite, *ack;

  s2_case("2.6.4", "re-INVITEs w/o SDP",
	  "NUA sends re-INVITE w/o SDP, "
	  "receives SDP w/ offer, "
	  "sends ACK w/ answer, "
	  "sends BYE.");

  /* Bug reported by Liu Yang 2009-01-11 */
  nh = invite_to_nua(TAG_END());

  nua_invite(nh, SIPTAG_PAYLOAD_STR(""), TAG_END());
  fail_unless(s2_check_callstate(nua_callstate_calling));
  invite = s2_sip_wait_for_request(SIP_METHOD_INVITE);
  fail_if(!invite);
  fail_if(invite->sip->sip_content_type);
  soa_generate_offer(soa, 1, NULL);
  respond_with_sdp(invite, dialog, SIP_200_OK, TAG_END());
  s2_sip_free_message(invite);

  ack = s2_sip_wait_for_request(SIP_METHOD_ACK);
  fail_if(!ack);
  process_answer(ack);
  s2_sip_free_message(ack);

  fail_unless(s2_check_event(nua_r_invite, 200));
  fail_unless(s2_check_callstate(nua_callstate_ready));

  bye_by_nua(nh, TAG_END());

  nua_handle_destroy(nh);
}
END_TEST

TCase *reinvite_tcase(int threading)
{
  TCase *tc = tcase_create("2.6 - re-INVITEs");

  add_call_fixtures(tc, threading);
  {
    tcase_add_test(tc, call_2_6_1);
    tcase_add_test(tc, call_2_6_2);
    tcase_add_test(tc, call_2_6_3);
    tcase_add_test(tc, call_2_6_4);
  }
  return tc;
}


/* ====================================================================== */
/* 3.1 - Call error cases */

START_TEST(call_3_1_1)
{
  nua_handle_t *nh;
  struct message *invite, *ack;

  s2_case("3.1.1", "Call failure", "Call fails with 403 response");
  nh = nua_handle(nua, NULL, SIPTAG_TO(s2sip->aor),
		  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_sip_wait_for_request(SIP_METHOD_INVITE);
  fail_if(!invite);
  s2_sip_respond_to(invite, NULL, SIP_403_FORBIDDEN,
		SIPTAG_TO_STR("UAS Changed <sip:To@Header.field.invalid>"),
		TAG_END());
  s2_sip_free_message(invite);

  ack = s2_sip_wait_for_request(SIP_METHOD_ACK);
  fail_if(!ack);
  fail_if(strcmp(ack->sip->sip_to->a_display, "UAS Changed"));
  s2_sip_free_message(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(call_3_1_2)
{
  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(s2sip->aor),
		  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_sip_wait_for_request(SIP_METHOD_INVITE);
    fail_if(!invite);
    s2_sip_respond_to(invite, NULL, SIP_500_INTERNAL_SERVER_ERROR,
		  SIPTAG_RETRY_AFTER_STR("5"),
		  TAG_END());
    s2_sip_free_message(invite);
    fail_unless(s2_sip_check_request(SIP_METHOD_ACK));
    if (i == 3)
      break;
    fail_unless(s2_check_event(nua_r_invite, 100));
    s2_nua_fast_forward(5, s2base->root);
  }

  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(call_3_2_1)
{
  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(s2sip->aor),
		  TAG_END());

  invite_by_nua(nh, TAG_END());

  nua_invite(nh, TAG_END());

  fail_unless(s2_check_callstate(nua_callstate_calling));

  invite = s2_sip_wait_for_request(SIP_METHOD_INVITE);
  fail_if(!invite);
  s2_sip_respond_to(invite, NULL, SIP_403_FORBIDDEN, TAG_END());
  s2_sip_free_message(invite);

  fail_unless(s2_sip_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(call_3_2_2)
{
  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(s2sip->aor),
		  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_sip_wait_for_request(SIP_METHOD_INVITE);
    fail_if(!invite);
    s2_sip_respond_to(invite, NULL, SIP_500_INTERNAL_SERVER_ERROR,
		  SIPTAG_RETRY_AFTER_STR("5"),
		  TAG_END());
    s2_sip_free_message(invite);
    fail_unless(s2_sip_check_request(SIP_METHOD_ACK));
    if (i == 3)
      break;
    fail_unless(s2_check_event(nua_r_invite, 100));
    s2_nua_fast_forward(5, s2base->root);
  }

  fail_unless(s2_check_event(nua_r_invite, 500));
  /* Graceful termination */
  fail_unless(s2_check_callstate(nua_callstate_terminating));
  bye = s2_sip_wait_for_request(SIP_METHOD_BYE);
  fail_if(!bye);
  s2_sip_respond_to(bye, dialog, SIP_200_OK, TAG_END());
  s2_sip_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


START_TEST(call_3_2_3)
{
  nua_handle_t *nh;
  struct message *invite;

  s2_case("3.2.3", "Re-INVITE failure", "Re-INVITE fails with 491 response");
  nh = nua_handle(nua, NULL, SIPTAG_TO(s2sip->aor),
		  TAG_END());

  invite_by_nua(nh, TAG_END());

  nua_invite(nh, TAG_END());

  fail_unless(s2_check_callstate(nua_callstate_calling));

  invite = s2_sip_wait_for_request(SIP_METHOD_INVITE);
  fail_if(!invite);
  s2_sip_respond_to(invite, NULL, SIP_491_REQUEST_PENDING, TAG_END());
  s2_sip_free_message(invite);
  fail_unless(s2_sip_check_request(SIP_METHOD_ACK));
  fail_unless(s2_check_event(nua_r_invite, 491));
  /* Return to previous state */
  fail_unless(s2_check_callstate(nua_callstate_ready));

  bye_by_nua(nh, TAG_END());
}
END_TEST


TCase *invite_error_tcase(int threading)
{
  TCase *tc = tcase_create("3 - Call Errors");
  add_call_fixtures(tc, threading);
  {
    tcase_add_test(tc, call_3_1_1);
    tcase_add_test(tc, call_3_1_2);
    tcase_add_test(tc, call_3_2_1);
    tcase_add_test(tc, call_3_2_2);
    tcase_add_test(tc, call_3_2_3);
    tcase_set_timeout(tc, 5);
  }
  return tc;
}


/* ====================================================================== */
/* Weird call termination cases */

START_TEST(bye_4_1_1)
{
  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_sip_wait_for_request(SIP_METHOD_BYE);
  fail_if(!bye);
  s2_sip_respond_to(bye, dialog, SIP_407_PROXY_AUTH_REQUIRED,
		SIPTAG_PROXY_AUTHENTICATE_STR(s2_auth_digest_str),
		TAG_END());
  s2_sip_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_sip_wait_for_response(0, SIP_METHOD_INVITE);
  }
  while (r481->sip->sip_status->st_status < 200);

  s2_sip_update_dialog(dialog, r481); /* send ACK */

  fail_unless(s2_check_callstate(nua_callstate_terminated));

  nua_handle_destroy(nh);
}
END_TEST


START_TEST(bye_4_1_2)
{
  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_sip_wait_for_request(SIP_METHOD_BYE);
  fail_if(!bye);

  request_with_sdp(dialog, SIP_METHOD_INVITE, NULL, TAG_END());
  do {
    r481 = s2_sip_wait_for_response(0, SIP_METHOD_INVITE);
  }
  while (r481->sip->sip_status->st_status < 200);

  s2_sip_update_dialog(dialog, r481); /* send ACK */

  fail_unless(s2_check_callstate(nua_callstate_terminated));

  s2_sip_respond_to(bye, dialog, SIP_200_OK,
		TAG_END());
  s2_sip_free_message(bye);
  fail_unless(s2_check_event(nua_r_bye, 200));

  nua_handle_destroy(nh);
}
END_TEST

START_TEST(bye_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_sip_wait_for_request(SIP_METHOD_BYE);
  fail_if(!bye);

  s2_sip_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_sip_respond_to(bye, dialog, SIP_200_OK, TAG_END());
  s2_sip_free_message(bye);

  fail_unless(s2_check_event(nua_r_bye, 200));
  fail_unless(s2_check_callstate(nua_callstate_terminated));

  fail_unless(s2_sip_check_response(200, SIP_METHOD_BYE));

  nua_handle_destroy(nh);
}
END_TEST


START_TEST(bye_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_sip_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_sip_wait_for_request(SIP_METHOD_BYE);
  fail_if(!bye);

  s2_sip_respond_to(bye, dialog, SIP_200_OK, TAG_END());
  s2_sip_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_sip_check_response(200, SIP_METHOD_BYE));

  nua_handle_destroy(nh);
}
END_TEST


START_TEST(bye_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_sip_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_sip_wait_for_request(SIP_METHOD_BYE);
  fail_if(!bye);

  s2_sip_respond_to(bye, dialog, SIP_200_OK, TAG_END());
  s2_sip_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_sip_check_response(500, SIP_METHOD_BYE));
}
END_TEST


START_TEST(bye_4_1_6)
{
  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_sip_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_sip_wait_for_response(0, SIP_METHOD_INVITE);
  }
  while (r486->sip->sip_status->st_status < 200);
  s2_sip_update_dialog(dialog, r486); /* send ACK */
  fail_unless(r486->sip->sip_status->st_status == 486);

  bye = s2_sip_wait_for_request(SIP_METHOD_BYE);
  fail_if(!bye);
  s2_sip_respond_to(bye, dialog, SIP_200_OK, TAG_END());
  s2_sip_free_message(bye);

  nua_handle_destroy(nh);
}
END_TEST


START_TEST(bye_4_1_7)
{
  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_sip_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_sip_wait_for_request(SIP_METHOD_BYE);
  fail_if(!bye);
  s2_sip_respond_to(bye, dialog, SIP_200_OK, TAG_END());
  s2_sip_free_message(bye);

  do {
    r486 = s2_sip_wait_for_response(0, SIP_METHOD_INVITE);
  }
  while (r486->sip->sip_status->st_status < 200);
  s2_sip_update_dialog(dialog, r486); /* send ACK */
  fail_unless(r486->sip->sip_status->st_status == 486);

  nua_handle_destroy(nh);
}
END_TEST

START_TEST(bye_4_1_8)
{
  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(s2sip->aor), 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_sip_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_sip_wait_for_response(0, SIP_METHOD_INVITE);
  }
  while (r486->sip->sip_status->st_status < 200);
  s2_sip_update_dialog(dialog, r486); /* send ACK */
  fail_unless(r486->sip->sip_status->st_status == 486);

  bye = s2_sip_wait_for_request(SIP_METHOD_BYE);
  fail_if(!bye);
  s2_sip_respond_to(bye, dialog, SIP_200_OK, TAG_END());
  s2_sip_free_message(bye);

  nua_handle_destroy(nh);
}
END_TEST


START_TEST(bye_4_1_9)
{
  nua_handle_t *nh;
  struct message *bye;
  struct event *i_bye;

  s2_case("4.1.6", "Send BYE, receive BYE, destroy",
	  "NUA sends BYE, receives BYE and handle gets destroyed");

  nh = invite_to_nua(TAG_END());

  mark_point();

  s2_flush_events();

  nua_bye(nh, TAG_END());
  bye = s2_sip_wait_for_request(SIP_METHOD_BYE);
  fail_if(!bye);

  s2_sip_request_to(dialog, SIP_METHOD_BYE, NULL, TAG_END());
  i_bye = s2_wait_for_event(nua_i_bye, 200);
  fail_if(!i_bye);
  s2_free_event(i_bye), i_bye = NULL;
  fail_unless(s2_check_callstate(nua_callstate_terminated));
  fail_unless(s2_sip_check_response(200, SIP_METHOD_BYE));
  nua_handle_destroy(nh);
  mark_point();

  su_root_step(s2base->root, 10);
  su_root_step(s2base->root, 10);
  su_root_step(s2base->root, 10);

  mark_point();
  s2_sip_respond_to(bye, dialog, SIP_200_OK, TAG_END());
  s2_sip_free_message(bye);

  mark_point();
  while (su_home_check_alloc((su_home_t *)nua, (void *)nh)) {
    su_root_step(s2base->root, 10);
  }
}
END_TEST


START_TEST(bye_4_1_10)
{
  nua_handle_t *nh;
  struct message *invite, *bye;
  struct event *i_bye;

  s2_case("4.1.6", "Send auto-BYE upon receiving 501, receive BYE, destroy",
	  "NUA sends BYE, receives BYE and handle gets destroyed");

  nh = invite_to_nua(TAG_END());

  mark_point();

  s2_flush_events();

  nua_invite(nh, TAG_END());
  invite = s2_sip_wait_for_request(SIP_METHOD_INVITE);
  fail_if(!invite);
  s2_sip_respond_to(invite, dialog, SIP_501_NOT_IMPLEMENTED, TAG_END());
  s2_sip_free_message(invite);

  fail_unless(s2_sip_check_request(SIP_METHOD_ACK));

  bye = s2_sip_wait_for_request(SIP_METHOD_BYE);
  fail_if(!bye);

  fail_unless(s2_check_callstate(nua_callstate_calling));
  fail_unless(s2_check_event(nua_r_invite, 501));
  fail_unless(s2_check_callstate(nua_callstate_terminating));

  s2_sip_request_to(dialog, SIP_METHOD_BYE, NULL, TAG_END());
  i_bye = s2_wait_for_event(nua_i_bye, 200);
  fail_if(!i_bye);
  s2_free_event(i_bye), i_bye = NULL;
  fail_unless(s2_check_callstate(nua_callstate_terminated));
  fail_unless(s2_sip_check_response(200, SIP_METHOD_BYE));
  nua_handle_destroy(nh);

  su_root_step(s2base->root, 10);
  su_root_step(s2base->root, 10);
  su_root_step(s2base->root, 10);

  s2_sip_respond_to(bye, dialog, SIP_200_OK, TAG_END());
  s2_sip_free_message(bye);

  while (su_home_check_alloc((su_home_t *)nua, (void *)nh)) {
    su_root_step(s2base->root, 10);
  }
}
END_TEST

START_TEST(bye_4_1_11)
{
  nua_handle_t *nh;
  struct message *invite, *ack;
  struct event *i_bye;

  s2_case("4.1.11", "Receive BYE in completing state",
	  "NUA sends INVITE, receives 200, receives BYE.");

  nh = nua_handle(nua, NULL, SIPTAG_TO(s2sip->aor), TAG_END());

  invite = invite_sent_by_nua(nh, NUTAG_AUTOACK(0), TAG_END());
  process_offer(invite);
  s2_sip_respond_to(invite, dialog, SIP_180_RINGING, 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_sip_free_message(invite);
  fail_unless(s2_check_event(nua_r_invite, 200));
  fail_unless(s2_check_callstate(nua_callstate_completing));

  s2_sip_request_to(dialog, SIP_METHOD_BYE, NULL, TAG_END());
  i_bye = s2_wait_for_event(nua_i_bye, 200);
  fail_if(!i_bye);
  s2_free_event(i_bye), i_bye = NULL;
  fail_unless(s2_check_callstate(nua_callstate_terminated));
  fail_unless(s2_sip_check_response(200, SIP_METHOD_BYE));

  ack = s2_sip_wait_for_request(SIP_METHOD_ACK);
  fail_if(!ack);
  s2_sip_free_message(ack);

  nua_handle_destroy(nh);
}
END_TEST

START_TEST(bye_4_2_1)
{
  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_nua_fast_forward(300, s2base->root);
  invite_timer_round(nh, "300", NULL);

  nua_bye(nh, TAG_END());

  bye = s2_sip_wait_for_request(SIP_METHOD_BYE);
  fail_if(!bye);
  s2_sip_respond_to(bye, dialog, SIP_407_PROXY_AUTH_REQUIRED,
		SIPTAG_PROXY_AUTHENTICATE_STR(s2_auth_digest_str),
		TAG_END());
  s2_sip_free_message(bye);
  fail_unless(s2_check_event(nua_r_bye, 407));

  s2_nua_fast_forward(300, s2base->root);

  nua_authenticate(nh, NUTAG_AUTH("Digest:\"s2test\":abc:abc"), TAG_END());
  bye = s2_sip_wait_for_request(SIP_METHOD_BYE);
  fail_if(!bye);
  s2_sip_respond_to(bye, dialog, SIP_200_OK, TAG_END());
  s2_sip_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_4_2_2)
{
  nua_handle_t *nh;
  struct message *bye;

  s2_case("4.2.2", "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_nua_fast_forward(300, s2base->root);
  invite_timer_round(nh, "300", NULL);

  s2_nua_fast_forward(140, s2base->root);

  nua_bye(nh, TAG_END());

  bye = s2_sip_wait_for_request(SIP_METHOD_BYE);
  fail_if(!bye);
  s2_sip_respond_to(bye, dialog, SIP_407_PROXY_AUTH_REQUIRED,
		SIPTAG_PROXY_AUTHENTICATE_STR(s2_auth_digest_str),
		TAG_END());
  s2_sip_free_message(bye);
  fail_unless(s2_check_event(nua_r_bye, 407));

  s2_nua_fast_forward(160, s2base->root);

  nua_authenticate(nh, NUTAG_AUTH(s2_auth_credentials), TAG_END());
  bye = s2_sip_wait_for_request(SIP_METHOD_BYE);
  fail_if(!bye);
  s2_sip_respond_to(bye, dialog, SIP_200_OK, TAG_END());
  s2_sip_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(int threading)
{
  TCase *tc = tcase_create("4 - Call Termination");
  add_call_fixtures(tc, threading);
  {
    tcase_add_test(tc, bye_4_1_1);
    tcase_add_test(tc, bye_4_1_2);
    tcase_add_test(tc, bye_4_1_3);
    tcase_add_test(tc, bye_4_1_4);
    tcase_add_test(tc, bye_4_1_5);
    tcase_add_test(tc, bye_4_1_6);
    tcase_add_test(tc, bye_4_1_7);
    tcase_add_test(tc, bye_4_1_8);
    tcase_add_test(tc, bye_4_1_9);
    tcase_add_test(tc, bye_4_1_10);
    tcase_add_test(tc, bye_4_1_11);
    tcase_add_test(tc, bye_4_2_1);
    tcase_add_test(tc, bye_4_2_2);
    tcase_set_timeout(tc, 5);
  }
  return tc;
}

/* ====================================================================== */

START_TEST(destroy_4_3_1)
{
  nua_handle_t *nh;
  struct message *invite, *cancel;

  s2_case("4.3.1", "Destroy handle after INVITE sent",
	  "NUA sends INVITE, handle gets destroyed.");

  nh = nua_handle(nua, NULL, SIPTAG_TO(s2sip->aor), TAG_END());

  invite = invite_sent_by_nua(nh, TAG_END());
  process_offer(invite);

  nua_handle_destroy(nh);

  s2_sip_respond_to(invite, dialog, SIP_100_TRYING, TAG_END());

  cancel = s2_sip_wait_for_request(SIP_METHOD_CANCEL);
  fail_if(!cancel);
  s2_sip_respond_to(invite, dialog, SIP_487_REQUEST_CANCELLED, TAG_END());
  s2_sip_free_message(invite);

  s2_sip_respond_to(cancel, dialog, SIP_200_OK, TAG_END());
  s2_sip_free_message(cancel);
}
END_TEST


START_TEST(destroy_4_3_2)
{
  nua_handle_t *nh;
  struct message *invite, *cancel;

  s2_case("4.3.2", "Destroy handle in calling state",
	  "NUA sends INVITE, receives 180, handle gets destroyed.");

  nh = nua_handle(nua, NULL, SIPTAG_TO(s2sip->aor), TAG_END());

  invite = invite_sent_by_nua(nh, TAG_END());
  process_offer(invite);
  s2_sip_respond_to(invite, dialog, SIP_180_RINGING, TAG_END());
  fail_unless(s2_check_event(nua_r_invite, 180));
  fail_unless(s2_check_callstate(nua_callstate_proceeding));

  nua_handle_destroy(nh);

  cancel = s2_sip_wait_for_request(SIP_METHOD_CANCEL);
  fail_if(!cancel);
  s2_sip_respond_to(invite, dialog, SIP_487_REQUEST_CANCELLED, TAG_END());
  s2_sip_free_message(invite);

  s2_sip_respond_to(cancel, dialog, SIP_200_OK, TAG_END());
  s2_sip_free_message(cancel);
}
END_TEST

START_TEST(destroy_4_3_3)
{
  nua_handle_t *nh;
  struct message *invite, *ack, *bye;

  s2_case("4.3.3", "Destroy handle in completing state",
	  "NUA sends INVITE, receives 200, handle gets destroyed.");

  nh = nua_handle(nua, NULL, SIPTAG_TO(s2sip->aor), TAG_END());

  invite = invite_sent_by_nua(nh, NUTAG_AUTOACK(0), TAG_END());
  process_offer(invite);
  s2_sip_respond_to(invite, dialog, SIP_180_RINGING, 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());
  fail_unless(s2_check_event(nua_r_invite, 200));
  fail_unless(s2_check_callstate(nua_callstate_completing));

  nua_handle_destroy(nh);

  ack = s2_sip_wait_for_request(SIP_METHOD_ACK);
  fail_if(!ack);
  s2_sip_free_message(ack);

  bye = s2_sip_wait_for_request(SIP_METHOD_BYE);
  fail_if(!bye);
  s2_sip_respond_to(bye, dialog, SIP_200_OK, TAG_END());
  s2_sip_free_message(bye);

  s2_sip_free_message(invite);
}
END_TEST


START_TEST(destroy_4_3_4)
{
  nua_handle_t *nh;
  struct message *invite, *ack, *bye;

  s2_case("4.3.3", "Destroy handle in ready state ",
	  "NUA sends INVITE, receives 200, handle gets destroyed.");

  nh = nua_handle(nua, NULL, SIPTAG_TO(s2sip->aor), TAG_END());

  invite = invite_sent_by_nua(nh, NUTAG_AUTOACK(0), TAG_END());
  process_offer(invite);
  s2_sip_respond_to(invite, dialog, SIP_180_RINGING, 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());
  fail_unless(s2_check_event(nua_r_invite, 200));
  fail_unless(s2_check_callstate(nua_callstate_completing));

  nua_ack(nh, TAG_END());
  ack = s2_sip_wait_for_request(SIP_METHOD_ACK);
  fail_if(!ack);
  s2_sip_free_message(ack);

  fail_unless(s2_check_callstate(nua_callstate_ready));

  nua_handle_destroy(nh);

  bye = s2_sip_wait_for_request(SIP_METHOD_BYE);
  fail_if(!bye);
  s2_sip_respond_to(bye, dialog, SIP_200_OK, TAG_END());
  s2_sip_free_message(bye);

  s2_sip_free_message(invite);
}
END_TEST


START_TEST(destroy_4_3_5)
{
  nua_handle_t *nh;
  struct message *invite, *cancel;

  s2_case("4.3.5", "Destroy handle in re-INVITE calling state",
	  "NUA sends re-INVITE, handle gets destroyed.");

  nh = invite_to_nua(TAG_END());

  invite = invite_sent_by_nua(nh, TAG_END());
  process_offer(invite);

  nua_handle_destroy(nh);

  s2_sip_respond_to(invite, dialog, SIP_100_TRYING, TAG_END());

  cancel = s2_sip_wait_for_request(SIP_METHOD_CANCEL);
  fail_if(!cancel);
  s2_sip_respond_to(invite, dialog, SIP_487_REQUEST_CANCELLED, TAG_END());
  s2_sip_free_message(invite);

  s2_sip_respond_to(cancel, dialog, SIP_200_OK, TAG_END());
  s2_sip_free_message(cancel);
}
END_TEST


START_TEST(destroy_4_3_6)
{
  nua_handle_t *nh;
  struct message *invite, *cancel;

  s2_case("4.3.6", "Destroy handle in calling state of re-INVITE",
	  "NUA sends re-INVITE, receives 180, handle gets destroyed.");

  nh = invite_to_nua(TAG_END());

  invite = invite_sent_by_nua(nh, TAG_END());
  process_offer(invite);
  s2_sip_respond_to(invite, dialog, SIP_180_RINGING, TAG_END());
  fail_unless(s2_check_event(nua_r_invite, 180));
  fail_unless(s2_check_callstate(nua_callstate_proceeding));

  nua_handle_destroy(nh);

  cancel = s2_sip_wait_for_request(SIP_METHOD_CANCEL);
  fail_if(!cancel);
  s2_sip_respond_to(invite, dialog, SIP_487_REQUEST_CANCELLED, TAG_END());
  s2_sip_free_message(invite);

  s2_sip_respond_to(cancel, dialog, SIP_200_OK, TAG_END());
  s2_sip_free_message(cancel);
}
END_TEST


START_TEST(destroy_4_3_7)
{
  nua_handle_t *nh;
  struct message *invite, *ack, *bye;

  s2_case("4.3.7", "Destroy handle in completing state of re-INVITE",
	  "NUA sends INVITE, receives 200, handle gets destroyed.");

  nh = invite_to_nua(TAG_END());

  invite = invite_sent_by_nua(nh, NUTAG_AUTOACK(0), TAG_END());
  process_offer(invite);
  s2_sip_respond_to(invite, dialog, SIP_180_RINGING, 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());
  fail_unless(s2_check_event(nua_r_invite, 200));
  fail_unless(s2_check_callstate(nua_callstate_completing));

  nua_handle_destroy(nh);

  ack = s2_sip_wait_for_request(SIP_METHOD_ACK);
  fail_if(!ack);
  s2_sip_free_message(ack);

  bye = s2_sip_wait_for_request(SIP_METHOD_BYE);
  fail_if(!bye);
  s2_sip_respond_to(bye, dialog, SIP_200_OK, TAG_END());
  s2_sip_free_message(bye);

  s2_sip_free_message(invite);
}
END_TEST


START_TEST(destroy_4_3_8)
{
  nua_handle_t *nh;
  struct message *invite, *ack, *bye;

  s2_case("4.3.8", "Destroy handle after INVITE sent",
	  "NUA sends INVITE, handle gets destroyed, "
	  "but remote end returns 200 OK. "
	  "Make sure nua tries to release call properly.");

  nh = nua_handle(nua, NULL, SIPTAG_TO(s2sip->aor), TAG_END());

  invite = invite_sent_by_nua(nh, TAG_END());
  process_offer(invite);

  nua_handle_destroy(nh);

  respond_with_sdp(invite, dialog, SIP_200_OK, TAG_END());
  s2_sip_free_message(invite);

  ack = s2_sip_wait_for_request(SIP_METHOD_ACK);
  fail_if(!ack);
  s2_sip_free_message(ack);

  bye = s2_sip_wait_for_request(SIP_METHOD_BYE);
  fail_if(!bye);
  s2_sip_respond_to(bye, dialog, SIP_200_OK, TAG_END());
  s2_sip_free_message(bye);
}
END_TEST


START_TEST(destroy_4_3_9)
{
  nua_handle_t *nh;
  struct message *invite, *cancel, *ack, *bye;

  s2_case("4.3.9", "Destroy handle in calling state",
	  "NUA sends INVITE, receives 180, handle gets destroyed, "
	  "but remote end returns 200 OK. "
	  "Make sure nua tries to release call properly.");

  nh = nua_handle(nua, NULL, SIPTAG_TO(s2sip->aor), TAG_END());

  invite = invite_sent_by_nua(nh, TAG_END());
  process_offer(invite);
  s2_sip_respond_to(invite, dialog, SIP_180_RINGING, TAG_END());
  fail_unless(s2_check_event(nua_r_invite, 180));
  fail_unless(s2_check_callstate(nua_callstate_proceeding));

  nua_handle_destroy(nh);

  cancel = s2_sip_wait_for_request(SIP_METHOD_CANCEL);
  fail_if(!cancel);
  s2_sip_respond_to(cancel, dialog, SIP_481_NO_TRANSACTION, TAG_END());
  s2_sip_free_message(cancel);

  respond_with_sdp(invite, dialog, SIP_200_OK, TAG_END());
  s2_sip_free_message(invite);

  ack = s2_sip_wait_for_request(SIP_METHOD_ACK);
  fail_if(!ack);
  s2_sip_free_message(ack);

  bye = s2_sip_wait_for_request(SIP_METHOD_BYE);
  fail_if(!bye);
  s2_sip_respond_to(bye, dialog, SIP_200_OK, TAG_END());
  s2_sip_free_message(bye);
}
END_TEST


START_TEST(destroy_4_4_1)
{
  nua_handle_t *nh;
  struct event *invite;
  struct message *response;

  s2_case("4.4.1", "Destroy handle while call is on-going",
	  "NUA is callee, sends 100, destroys handle");

  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);

  s2_free_event(invite);

  response = s2_sip_wait_for_response(100, SIP_METHOD_INVITE);
  fail_if(!response);
  s2_sip_free_message(response);

  nua_handle_destroy(nh);

  response = s2_sip_wait_for_response(480, SIP_METHOD_INVITE);
  fail_if(!response);
  fail_if(s2_sip_request_to(dialog, SIP_METHOD_ACK, NULL,
			SIPTAG_VIA(sip_object(dialog->invite)->sip_via),
			TAG_END()));
  s2_sip_free_message(response);
}
END_TEST


START_TEST(destroy_4_4_2)
{
  nua_handle_t *nh;
  struct event *invite;
  struct message *response;

  s2_case("4.4.1", "Destroy handle while call is on-going",
	  "NUA is callee, sends 180, destroys handle");

  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);

  s2_free_event(invite);

  response = s2_sip_wait_for_response(100, SIP_METHOD_INVITE);
  fail_if(!response);
  s2_sip_free_message(response);

  nua_respond(nh, SIP_180_RINGING, TAG_END());

  response = s2_sip_wait_for_response(180, SIP_METHOD_INVITE);
  fail_if(!response);
  s2_sip_free_message(response);

  nua_handle_destroy(nh);

  response = s2_sip_wait_for_response(480, SIP_METHOD_INVITE);
  fail_if(!response);
  fail_if(s2_sip_request_to(dialog, SIP_METHOD_ACK, NULL,
			SIPTAG_VIA(sip_object(dialog->invite)->sip_via),
			TAG_END()));
  s2_sip_free_message(response);
}
END_TEST


START_TEST(destroy_4_4_3_1)
{
  nua_handle_t *nh;
  struct event *invite;
  struct message *response, *bye;

  s2_case("4.4.3.1", "Destroy handle while call is on-going",
	  "NUA is callee, sends 200, destroys handle");

  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);

  s2_free_event(invite);

  response = s2_sip_wait_for_response(100, SIP_METHOD_INVITE);
  fail_if(!response);
  s2_sip_free_message(response);

  nua_respond(nh, SIP_180_RINGING,
	      SOATAG_USER_SDP_STR("m=audio 5004 RTP/AVP 0 8"),
	      TAG_END());

  response = s2_sip_wait_for_response(180, SIP_METHOD_INVITE);
  fail_if(!response);
  s2_sip_free_message(response);

  fail_unless(s2_check_callstate(nua_callstate_early));

  nua_respond(nh, SIP_200_OK, TAG_END());
  fail_unless(s2_check_callstate(nua_callstate_completed));

  response = s2_sip_wait_for_response(200, SIP_METHOD_INVITE);
  fail_if(!response);

  nua_handle_destroy(nh);

  fail_if(s2_sip_request_to(dialog, SIP_METHOD_ACK, NULL,
			SIPTAG_VIA(sip_object(dialog->invite)->sip_via),
			TAG_END()));
  s2_sip_free_message(response);

  bye = s2_sip_wait_for_request(SIP_METHOD_BYE);
  fail_if(!bye);
  s2_sip_respond_to(bye, dialog, SIP_200_OK, TAG_END());
  s2_sip_free_message(bye);
}
END_TEST


START_TEST(destroy_4_4_3_2)
{
  nua_handle_t *nh;
  struct event *invite;
  struct message *response, *bye;

  s2_case("4.4.3.1", "Destroy handle while call is on-going",
	  "NUA is callee, sends 200, destroys handle");

  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);

  s2_free_event(invite);

  response = s2_sip_wait_for_response(100, SIP_METHOD_INVITE);
  fail_if(!response);
  s2_sip_free_message(response);

  nua_respond(nh, SIP_180_RINGING,
	      SOATAG_USER_SDP_STR("m=audio 5004 RTP/AVP 0 8"),
	      TAG_END());

  response = s2_sip_wait_for_response(180, SIP_METHOD_INVITE);
  fail_if(!response);
  s2_sip_free_message(response);

  fail_unless(s2_check_callstate(nua_callstate_early));

  nua_respond(nh, SIP_200_OK, TAG_END());
  fail_unless(s2_check_callstate(nua_callstate_completed));

  response = s2_sip_wait_for_response(200, SIP_METHOD_INVITE);
  fail_if(!response);

  nua_handle_destroy(nh);

  bye = s2_sip_wait_for_request(SIP_METHOD_BYE);
  fail_if(!bye);
  s2_sip_respond_to(bye, dialog, SIP_200_OK, TAG_END());
  s2_sip_free_message(bye);

  fail_if(s2_sip_request_to(dialog, SIP_METHOD_ACK, NULL,
			SIPTAG_VIA(sip_object(dialog->invite)->sip_via),
			TAG_END()));
  s2_sip_free_message(response);
}
END_TEST

static TCase *destroy_tcase(int threading)
{
  TCase *tc = tcase_create("4.3 - Destroying Handle");

  add_call_fixtures(tc, threading);

  {
    tcase_add_test(tc, destroy_4_3_1);
    tcase_add_test(tc, destroy_4_3_2);
    tcase_add_test(tc, destroy_4_3_3);
    tcase_add_test(tc, destroy_4_3_4);
    tcase_add_test(tc, destroy_4_3_5);
    tcase_add_test(tc, destroy_4_3_6);
    tcase_add_test(tc, destroy_4_3_7);
    if (XXX) {
      tcase_add_test(tc, destroy_4_3_8);
      tcase_add_test(tc, destroy_4_3_9);
    }

    tcase_add_test(tc, destroy_4_4_1);
    tcase_add_test(tc, destroy_4_4_2);
    tcase_add_test(tc, destroy_4_4_3_1);
    tcase_add_test(tc, destroy_4_4_3_2);

    tcase_set_timeout(tc, 5);
  }
  return tc;
}

/* ====================================================================== */

static void options_setup(void), options_teardown(void);

START_TEST(options_5_1_1)
{
  struct event *options;
  nua_handle_t *nh;
  struct message *response;

  s2_case("5.1.1", "Test nua_respond() API",
	  "Test nua_respond() API with OPTIONS.");

  s2_sip_request_to(dialog, SIP_METHOD_OPTIONS, NULL, TAG_END());

  options = s2_wait_for_event(nua_i_options, 200);
  fail_unless(options != NULL);
  nh = options->nh; fail_if(!nh);

  response = s2_sip_wait_for_response(200, SIP_METHOD_OPTIONS);

  fail_if(!response);
  s2_sip_free_message(response);

  nua_handle_destroy(nh);

  nua_set_params(nua, NUTAG_APPL_METHOD("OPTIONS"), TAG_END());
  fail_unless(s2_check_event(nua_r_set_params, 200));

  s2_sip_request_to(dialog, SIP_METHOD_OPTIONS, NULL, TAG_END());

  options = s2_wait_for_event(nua_i_options, 100);
  fail_unless(options != NULL);
  nh = options->nh; fail_if(!nh);

  nua_respond(nh, 202, "okok", NUTAG_WITH_SAVED(options->event), TAG_END());

  response = s2_sip_wait_for_response(202, SIP_METHOD_OPTIONS);
  fail_if(!response);
  s2_sip_free_message(response);

  nua_handle_destroy(nh);
}
END_TEST


#if HAVE_LIBPTHREAD
#include <pthread.h>

void *respond_to_options(void *arg)
{
  struct event *options = (struct event *)arg;

  nua_respond(options->nh, 202, "ok ok",
	      NUTAG_WITH_SAVED(options->event),
	      TAG_END());

  pthread_exit(arg);
  return NULL;
}

START_TEST(options_5_1_2)
{
  struct event *options;
  nua_handle_t *nh;
  struct message *response;
  pthread_t tid;
  void *thread_return = NULL;

  s2_case("5.1.2", "Test nua_respond() API with another thread",
	  "Test multithreading nua_respond() API with OPTIONS.");

  nua_set_params(nua, NUTAG_APPL_METHOD("OPTIONS"), TAG_END());
  fail_unless(s2_check_event(nua_r_set_params, 200));

  s2_sip_request_to(dialog, SIP_METHOD_OPTIONS, NULL, TAG_END());

  options = s2_wait_for_event(nua_i_options, 100);
  fail_unless(options != NULL);
  nh = options->nh; fail_if(!nh);

  fail_if(pthread_create(&tid, NULL, respond_to_options, (void *)options));
  pthread_join(tid, &thread_return);
  fail_unless(thread_return == (void *)options);

  response = s2_sip_wait_for_response(202, SIP_METHOD_OPTIONS);
  fail_if(!response);
  s2_sip_free_message(response);

  nua_handle_destroy(nh);
}
END_TEST
#else
START_TEST(options_5_1_2)
{
}
END_TEST
#endif

TCase *options_tcase(int threading)
{
  TCase *tc = tcase_create("5 - OPTIONS, etc");

  tcase_add_checked_fixture(tc, options_setup, options_teardown);

  tcase_add_test(tc, options_5_1_1);
  tcase_add_test(tc, options_5_1_2);

  return tc;
}

static void options_setup(void)
{
  s2_nua_thread = 1;
  call_setup();
}

static void options_teardown(void)
{
  s2_teardown_started("options");
  call_teardown();
}

/* ====================================================================== */
/* Test cases for REFER */

START_TEST(refer_5_2_1)
{
  nua_handle_t *nh;
  sip_refer_to_t r[1];
  struct event *refer;
  struct message *notify;

  s2_case("5.2.1", "Receive REFER",
	  "Make a call, receive REFER.");

  nh = nua_handle(nua, NULL, SIPTAG_TO(s2sip->aor), TAG_END());
  invite_by_nua(nh, TAG_END());

  *sip_refer_to_init(r)->r_url = *s2sip->aor->a_url;
  r->r_url->url_user = "bob2";

  s2_sip_request_to(dialog, SIP_METHOD_REFER, NULL,
		SIPTAG_REFER_TO(r),
		TAG_END());
  refer = s2_wait_for_event(nua_i_refer, 202);

  bye_by_nua(nh, TAG_END());

  nua_handle_destroy(nh);

  notify = s2_sip_wait_for_request(SIP_METHOD_NOTIFY);
  s2_sip_respond_to(notify, dialog, SIP_200_OK, TAG_END());
}
END_TEST


START_TEST(refer_5_2_2)
{
  nua_handle_t *nh, *nh2;
  sip_refer_to_t r[1];
  sip_referred_by_t by[1];
  struct event *refer, *notified;
  sip_t const *sip;
  sip_event_t const *refer_event = NULL;
  sip_subscription_state_t const *ss;
  struct message *invite;
  struct message *notify0, *notify1, *notify2;
  struct dialog *dialog1, *dialog2;

  s2_case("5.2.2", "Receive REFER",
	  "Make a call, receive REFER, "
	  "make another call with automatic NOTIFYs");

  dialog2 = su_home_new(sizeof *dialog2); fail_unless(dialog2 != NULL);

  nh = nua_handle(nua, NULL, SIPTAG_TO(s2sip->aor), TAG_END());
  invite_by_nua(nh, TAG_END());

  *sip_refer_to_init(r)->r_url = *s2sip->aor->a_url;
  r->r_url->url_user = "bob2";

  s2_sip_request_to(dialog, SIP_METHOD_REFER, NULL,
		SIPTAG_REFER_TO(r),
		TAG_END());
  refer = s2_wait_for_event(nua_i_refer, 202);
  sip = sip_object(refer->data->e_msg);
  fail_unless(sip && sip->sip_refer_to);

  bye_by_nua(nh, TAG_END());

  dialog1 = dialog, dialog = dialog2;

  *sip_referred_by_init(by)->b_url =
    *sip->sip_from->a_url;

  fail_unless(tl_gets(refer->data->e_tags,
		      NUTAG_REFER_EVENT_REF(refer_event),
		      TAG_END()) == 1);

  nua_notify(nh,
	     SIPTAG_EVENT(refer_event),
	     SIPTAG_CONTENT_TYPE_STR("message/sipfrag"),
	     SIPTAG_PAYLOAD_STR("SIP/2.0 100 Trying\r\n"),
	     NUTAG_SUBSTATE(nua_substate_active),
	     TAG_END());
  notify0 = s2_sip_wait_for_request(SIP_METHOD_NOTIFY);
  fail_unless((ss = notify0->sip->sip_subscription_state) != NULL);
  fail_unless(su_casematch("active", ss->ss_substate));
  s2_sip_respond_to(notify0, dialog1, SIP_200_OK, TAG_END());
  notified = s2_wait_for_event(nua_r_notify, 200);

  nh2 = nua_handle(nua, NULL, NUTAG_URL(r->r_url), TAG_END());

  invite = invite_sent_by_nua(nh2,
			      NUTAG_REFER_EVENT(refer_event),
			      NUTAG_NOTIFY_REFER(nh),
			      SIPTAG_REFERRED_BY(by),
			      TAG_END());
  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));

  notify1 = s2_sip_wait_for_request(SIP_METHOD_NOTIFY);
  s2_sip_respond_to(notify1, dialog1, SIP_200_OK, TAG_END());

  respond_with_sdp(invite, dialog, SIP_200_OK, TAG_END());
  s2_sip_free_message(invite);
  fail_unless(s2_check_event(nua_r_invite, 200));
  fail_unless(s2_check_callstate(nua_callstate_ready));
  fail_unless(s2_sip_check_request(SIP_METHOD_ACK));

  notify2 = s2_sip_wait_for_request(SIP_METHOD_NOTIFY);
  s2_sip_respond_to(notify2, dialog1, SIP_200_OK, TAG_END());
  fail_unless((ss = notify2->sip->sip_subscription_state) != NULL);
  fail_unless(su_casematch("terminated", ss->ss_substate));

  nua_handle_destroy(nh);
  bye_by_nua(nh2, TAG_END());
  nua_handle_destroy(nh2);
}
END_TEST

TCase *refer_tcase(int threading)
{
  TCase *tc = tcase_create("5.2 - Call Transfer");

  add_call_fixtures(tc, threading);

  tcase_add_test(tc, refer_5_2_1);
  tcase_add_test(tc, refer_5_2_2);

  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(s2sip->master, TPTAG_LOG(1), TAG_END());
  s2_setup_logs(7);
  s2_setup_logs(0);
  tport_set_params(s2sip->master, TPTAG_LOG(0), TAG_END());
}
END_TEST

TCase *empty_tcase(int threading)
{
  TCase *tc = tcase_create("0 - Empty");

  add_call_fixtures(tc, threading);

  tcase_add_test(tc, empty);

  return tc;
}

/* ====================================================================== */

void check_session_cases(Suite *suite, int threading)
{
  suite_add_tcase(suite, invite_tcase(threading));
  suite_add_tcase(suite, cancel_tcase(threading));
  suite_add_tcase(suite, session_timer_tcase(threading));
  suite_add_tcase(suite, invite_100rel_tcase(threading));
  suite_add_tcase(suite, invite_precondition_tcase(threading));
  suite_add_tcase(suite, reinvite_tcase(threading));
  suite_add_tcase(suite, invite_error_tcase(threading));
  suite_add_tcase(suite, termination_tcase(threading));
  suite_add_tcase(suite, destroy_tcase(threading));
  suite_add_tcase(suite, options_tcase(threading));
  suite_add_tcase(suite, refer_tcase(threading));

  if (0)			/* Template */
    suite_add_tcase(suite, empty_tcase(threading));
}
