/*
 * 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_nta_client.c
 *
 * @brief Check-driven tester for NTA client transactions
 *
 * @author Pekka Pessi <Pekka.Pessi@nokia.com>
 *
 * @copyright (C) 2009 Nokia Corporation.
 */

#include "config.h"

#include "check_nta.h"
#include "s2base.h"
#include "s2dns.h"

#include <sofia-sip/nta.h>
#include <sofia-sip/nta_tport.h>
#include <sofia-sip/sip_status.h>
#include <sofia-sip/sip_header.h>
#include <sofia-sip/su_tagarg.h>
#include <sofia-sip/su_tag_io.h>
#include <sofia-sip/sres_sip.h>

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

#define NONE ((void *)-1)

static void
client_setup(void)
{
  s2_nta_setup("NTA", NULL, TAG_END());
  s2_nta_agent_setup(URL_STRING_MAKE("sip:0.0.0.0:*"), NULL, NULL,
		     NTATAG_DEFAULT_PROXY("sip:example.org"),
		     TAG_END());
}

static void
client_setup_udp_only_server(void)
{
  char const * const transports[] = { "udp", NULL };

  s2_nta_setup("NTA", transports, TAG_END());
  s2_nta_agent_setup(URL_STRING_MAKE("sip:0.0.0.0:*"), NULL, NULL,
		     NTATAG_DEFAULT_PROXY(s2sip->contact->m_url),
		     TAG_END());
}

static void
client_setup_tcp_only_server(void)
{
  char const * const transports[] = { "tcp", NULL };

  s2_nta_setup("NTA", transports, TAG_END());
  s2_nta_agent_setup(URL_STRING_MAKE("sip:0.0.0.0:*"), NULL, NULL,
		     NTATAG_DEFAULT_PROXY(s2sip->contact->m_url),
		     TAG_END());
}

static void
client_teardown(void)
{
  mark_point();
  s2_nta_teardown();
}

START_TEST(client_2_0_0)
{
  nta_outgoing_t *orq;
  struct message *request;
  struct event *response;

  s2_case("2.0.0", "Send MESSAGE",
	  "Basic non-INVITE transaction with outbound proxy");

  orq = nta_outgoing_tcreate(s2->default_leg,
			     s2_nta_orq_callback, NULL, NULL,
			     SIP_METHOD_MESSAGE,
			     URL_STRING_MAKE("sip:test2.0.example.org"),
			     SIPTAG_FROM_STR("<sip:client@example.net>"),
			     TAG_END());
  fail_unless(orq != NULL);
  request = s2_sip_wait_for_request(SIP_METHOD_MESSAGE);
  fail_unless(request != NULL);
  s2_sip_respond_to(request, NULL, 200, "2.0.0", TAG_END());
  response = s2_nta_wait_for(wait_for_orq, orq,
			     wait_for_status, 200,
			     0);
  s2_sip_free_message(request);
  s2_nta_free_event(response);
  nta_outgoing_destroy(orq);
}
END_TEST

START_TEST(client_2_0_1)
{
  nta_outgoing_t *orq;
  struct message *request;
  struct event *response;

  s2_case("2.0.0", "Send MESSAGE",
	  "Basic non-INVITE transaction with "
	  "numeric per-transaction outbound proxy");

  orq = nta_outgoing_tcreate(s2->default_leg,
			     s2_nta_orq_callback, NULL,
			     (url_string_t *)s2sip->contact->m_url,
			     SIP_METHOD_MESSAGE,
			     URL_STRING_MAKE("sip:test2.0.example.org"),
			     SIPTAG_FROM_STR("<sip:client@example.net>"),
			     TAG_END());
  fail_unless(orq != NULL);
  request = s2_sip_wait_for_request(SIP_METHOD_MESSAGE);
  fail_unless(request != NULL);
  s2_sip_respond_to(request, NULL, 200, "OK 2.0.1", TAG_END());
  response = s2_nta_wait_for(wait_for_orq, orq,
			     wait_for_status, 200,
			     0);
  s2_sip_free_message(request);
  s2_nta_free_event(response);
  nta_outgoing_destroy(orq);
}
END_TEST

START_TEST(client_2_0_2)
{
  nta_outgoing_t *orq;
  struct message *request;
  struct event *response;

  char payload[2048];

  s2_case("2.0.2", "Send MESSAGE",
	  "Basic non-INVITE transaction exceeding "
	  "default path MTU (1300 bytes)");

  memset(payload, 'x', sizeof payload);
  payload[(sizeof payload) - 1] = '\0';

  orq = nta_outgoing_tcreate(s2->default_leg,
			     s2_nta_orq_callback, NULL, NULL,
			     SIP_METHOD_MESSAGE,
			     URL_STRING_MAKE("sip:test2.0.example.org"),
			     SIPTAG_FROM_STR("<sip:client@example.net>"),
			     SIPTAG_PAYLOAD_STR(payload),
			     TAG_END());
  fail_unless(orq != NULL);
  request = s2_sip_wait_for_request(SIP_METHOD_MESSAGE);
  fail_unless(request != NULL);
  fail_unless(request->sip->sip_via->v_protocol == sip_transport_tcp);

  s2_sip_respond_to(request, NULL, 200, "OK 2.0.2", TAG_END());
  response = s2_nta_wait_for(wait_for_orq, orq,
			     wait_for_status, 200,
			     0);
  s2_nta_free_event(response);
  nta_outgoing_destroy(orq);
}
END_TEST

/* ---------------------------------------------------------------------- */

TCase *check_nta_client_2_0(void)
{
  TCase *tc = tcase_create("NTA 2.0 - Client");

  tcase_add_checked_fixture(tc, client_setup, client_teardown);

  tcase_set_timeout(tc, 2);

  tcase_add_test(tc, client_2_0_0);
  tcase_add_test(tc, client_2_0_1);
  tcase_add_test(tc, client_2_0_2);

  return tc;
}

/* ---------------------------------------------------------------------- */

START_TEST(client_2_1_0)
{
  nta_outgoing_t *orq;
  struct message *request;
  struct event *response;

  char payload[2048];

  s2_case("2.1.0", "Try UDP after trying with TCP",
	  "TCP connect() is refused");

  memset(payload, 'x', sizeof payload);
  payload[(sizeof payload) - 1] = '\0';

  client_setup_udp_only_server();

  orq = nta_outgoing_tcreate(s2->default_leg,
			     s2_nta_orq_callback, NULL, NULL,
			     SIP_METHOD_MESSAGE,
			     URL_STRING_MAKE("sip:test2.0.example.org"),
			     SIPTAG_FROM_STR("<sip:client@example.net>"),
			     SIPTAG_PAYLOAD_STR(payload),
			     TAG_END());
  fail_unless(orq != NULL);

  request = s2_sip_wait_for_request(SIP_METHOD_MESSAGE);
  fail_unless(request != NULL);
  fail_unless(request->sip->sip_via->v_protocol == sip_transport_udp);
  s2_sip_respond_to(request, NULL, 200, "OK", TAG_END());
  s2_sip_free_message(request);

  response = s2_nta_wait_for(wait_for_orq, orq,
			     wait_for_status, 200,
			     0);
  s2_nta_free_event(response);
  nta_outgoing_destroy(orq);
}
END_TEST

#undef SU_LOG

#include "tport_internal.h"

tport_vtable_t hacked_tcp_vtable;

/* Make TCP connection to 192.168.255.2:9999 */
static tport_t *
hacked_tcp_connect(tport_primary_t *pri,
		   su_addrinfo_t *ai,
		   tp_name_t const *tpn)
{
  su_addrinfo_t fake_ai[1];
  su_sockaddr_t fake_addr[1];
  uint32_t fake_ip = htonl(0xc0a8ff02); /* 192.168.255.2 */

  *fake_ai = *ai;
  assert(ai->ai_addrlen <= (sizeof fake_addr));
  fake_ai->ai_addr = memcpy(fake_addr, ai->ai_addr, ai->ai_addrlen);

  fake_ai->ai_family = AF_INET;
  fake_addr->su_family = AF_INET;
  memcpy(&fake_addr->su_sin.sin_addr, &fake_ip, sizeof fake_ip);
  fake_addr->su_sin.sin_port = htons(9999);

  return tport_base_connect(pri, fake_ai, ai, tpn);
}

START_TEST(client_2_1_1)
{
  tport_t *tp;

  nta_outgoing_t *orq;
  struct message *request;
  struct event *response;

  char payload[2048];

  s2_case("2.1.1", "Try UDP after trying with TCP",
	  "TCP connect() times out");

  memset(payload, 'x', sizeof payload);
  payload[(sizeof payload) - 1] = '\0';

  client_setup_udp_only_server();

  hacked_tcp_vtable = tport_tcp_vtable;
  hacked_tcp_vtable.vtp_connect = hacked_tcp_connect;
  fail_unless(tport_tcp_vtable.vtp_connect == NULL);

  for (tp = tport_primaries(nta_agent_tports(s2->nta));
       tp;
       tp = tport_next(tp)) {
    if (tport_is_tcp(tp)) {
      tp->tp_pri->pri_vtable = &hacked_tcp_vtable;
      break;
    }
  }

  orq = nta_outgoing_tcreate(s2->default_leg,
			     s2_nta_orq_callback, NULL,
			     NULL,
			     SIP_METHOD_MESSAGE,
			     URL_STRING_MAKE("sip:test2.0.example.org"),
			     SIPTAG_FROM_STR("<sip:client@example.net>"),
			     SIPTAG_PAYLOAD_STR(payload),
			     TAG_END());
  fail_unless(orq != NULL);

  s2_fast_forward(1, s2->root);
  s2_fast_forward(1, s2->root);
  s2_fast_forward(1, s2->root);
  s2_fast_forward(1, s2->root);
  s2_fast_forward(1, s2->root);

  request = s2_sip_wait_for_request(SIP_METHOD_MESSAGE);
  fail_unless(request != NULL);
  fail_unless(request->sip->sip_via->v_protocol == sip_transport_udp);
  s2_sip_respond_to(request, NULL, 200, "OK", TAG_END());
  s2_sip_free_message(request);

  response = s2_nta_wait_for(wait_for_orq, orq,
			     wait_for_status, 200,
			     0);
  s2_nta_free_event(response);
  nta_outgoing_destroy(orq);
}
END_TEST

START_TEST(client_2_1_2)
{
  nta_outgoing_t *orq;
  struct message *request;
  struct event *response;
  url_t udpurl[1];

  s2_case("2.1.2", "Send MESSAGE",
	  "Non-INVITE transaction to TCP-only server");

  client_setup_tcp_only_server();

  *udpurl = *s2sip->tcp.contact->m_url;
  udpurl->url_params = "transport=udp";

  /* Create DNS records for both UDP and TCP */
  s2_dns_domain("udptcp.org", 1,
		"s2", 1, udpurl,
		"s2", 1, s2sip->tcp.contact->m_url,
		NULL);

  orq = nta_outgoing_tcreate(s2->default_leg,
			     s2_nta_orq_callback, NULL,
			     URL_STRING_MAKE("sip:udptcp.org"),
			     SIP_METHOD_MESSAGE,
			     URL_STRING_MAKE("sip:test2.0.example.org"),
			     SIPTAG_FROM_STR("<sip:client@example.net>"),
			     TAG_END());
  fail_unless(orq != NULL);
  request = s2_sip_wait_for_request(SIP_METHOD_MESSAGE);
  fail_unless(request != NULL);
  s2_sip_respond_to(request, NULL, 200, "2.0.0", TAG_END());
  s2_sip_free_message(request);
  response = s2_nta_wait_for(wait_for_orq, orq,
			     wait_for_status, 200,
			     0);
  s2_nta_free_event(response);
  nta_outgoing_destroy(orq);
}
END_TEST


TCase *check_nta_client_2_1(void)
{
  TCase *tc = tcase_create("NTA 2.1 - Client");

  tcase_add_checked_fixture(tc, NULL, client_teardown);

  tcase_set_timeout(tc, 20);

  tcase_add_test(tc, client_2_1_0);
  tcase_add_test(tc, client_2_1_1);
  tcase_add_test(tc, client_2_1_2);

  return tc;
}