/* GStreamer
 * Copyright (C) 2012 Wim Taymans <wim.taymans@gmail.com>
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Library General Public
 * License as published by the Free Software Foundation; either
 * version 2 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
 * Library General Public License for more details.
 *
 * You should have received a copy of the GNU Library 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.
 */

#include <gst/check/gstcheck.h>

#include <rtsp-client.h>

#define VIDEO_PIPELINE "videotestsrc ! " \
  "video/x-raw,width=352,height=288 ! " \
  "rtpgstpay name=pay0 pt=96"
#define AUDIO_PIPELINE "audiotestsrc ! " \
  "audio/x-raw,rate=8000 ! " \
  "rtpgstpay name=pay1 pt=97"

static gchar *session_id;
static gint cseq;
static guint expected_session_timeout = 60;
static const gchar *expected_unsupported_header;

static gboolean
test_response_200 (GstRTSPClient * client, GstRTSPMessage * response,
    gboolean close, gpointer user_data)
{
  GstRTSPStatusCode code;
  const gchar *reason;
  GstRTSPVersion version;

  fail_unless (gst_rtsp_message_get_type (response) ==
      GST_RTSP_MESSAGE_RESPONSE);

  fail_unless (gst_rtsp_message_parse_response (response, &code, &reason,
          &version)
      == GST_RTSP_OK);
  fail_unless (code == GST_RTSP_STS_OK);
  fail_unless (g_str_equal (reason, "OK"));
  fail_unless (version == GST_RTSP_VERSION_1_0);

  return TRUE;
}

static gboolean
test_response_400 (GstRTSPClient * client, GstRTSPMessage * response,
    gboolean close, gpointer user_data)
{
  GstRTSPStatusCode code;
  const gchar *reason;
  GstRTSPVersion version;

  fail_unless (gst_rtsp_message_get_type (response) ==
      GST_RTSP_MESSAGE_RESPONSE);

  fail_unless (gst_rtsp_message_parse_response (response, &code, &reason,
          &version)
      == GST_RTSP_OK);
  fail_unless (code == GST_RTSP_STS_BAD_REQUEST);
  fail_unless (g_str_equal (reason, "Bad Request"));
  fail_unless (version == GST_RTSP_VERSION_1_0);

  return TRUE;
}

static gboolean
test_response_404 (GstRTSPClient * client, GstRTSPMessage * response,
    gboolean close, gpointer user_data)
{
  GstRTSPStatusCode code;
  const gchar *reason;
  GstRTSPVersion version;

  fail_unless (gst_rtsp_message_get_type (response) ==
      GST_RTSP_MESSAGE_RESPONSE);

  fail_unless (gst_rtsp_message_parse_response (response, &code, &reason,
          &version)
      == GST_RTSP_OK);
  fail_unless (code == GST_RTSP_STS_NOT_FOUND);
  fail_unless (g_str_equal (reason, "Not Found"));
  fail_unless (version == GST_RTSP_VERSION_1_0);

  return TRUE;
}

static gboolean
test_response_454 (GstRTSPClient * client, GstRTSPMessage * response,
    gboolean close, gpointer user_data)
{
  GstRTSPStatusCode code;
  const gchar *reason;
  GstRTSPVersion version;

  fail_unless (gst_rtsp_message_get_type (response) ==
      GST_RTSP_MESSAGE_RESPONSE);

  fail_unless (gst_rtsp_message_parse_response (response, &code, &reason,
          &version)
      == GST_RTSP_OK);
  fail_unless (code == GST_RTSP_STS_SESSION_NOT_FOUND);
  fail_unless (g_str_equal (reason, "Session Not Found"));
  fail_unless (version == GST_RTSP_VERSION_1_0);

  return TRUE;
}

static gboolean
test_response_551 (GstRTSPClient * client, GstRTSPMessage * response,
    gboolean close, gpointer user_data)
{
  GstRTSPStatusCode code;
  const gchar *reason;
  GstRTSPVersion version;
  gchar *options;

  fail_unless (gst_rtsp_message_get_type (response) ==
      GST_RTSP_MESSAGE_RESPONSE);

  fail_unless (gst_rtsp_message_parse_response (response, &code, &reason,
          &version)
      == GST_RTSP_OK);
  fail_unless (code == GST_RTSP_STS_OPTION_NOT_SUPPORTED);
  fail_unless (g_str_equal (reason, "Option not supported"));
  fail_unless (gst_rtsp_message_get_header (response, GST_RTSP_HDR_UNSUPPORTED,
          &options, 0) == GST_RTSP_OK);
  fail_unless (!g_strcmp0 (expected_unsupported_header, options));
  fail_unless (version == GST_RTSP_VERSION_1_0);

  return TRUE;
}

static void
create_connection (GstRTSPConnection ** conn)
{
  GSocket *sock;
  GError *error = NULL;

  sock = g_socket_new (G_SOCKET_FAMILY_IPV4, G_SOCKET_TYPE_STREAM,
      G_SOCKET_PROTOCOL_TCP, &error);
  g_assert_no_error (error);
  fail_unless (gst_rtsp_connection_create_from_socket (sock, "127.0.0.1", 444,
          NULL, conn) == GST_RTSP_OK);
  g_object_unref (sock);
}

static GstRTSPClient *
setup_client (const gchar * launch_line)
{
  GstRTSPClient *client;
  GstRTSPSessionPool *session_pool;
  GstRTSPMountPoints *mount_points;
  GstRTSPMediaFactory *factory;
  GstRTSPThreadPool *thread_pool;

  client = gst_rtsp_client_new ();

  session_pool = gst_rtsp_session_pool_new ();
  gst_rtsp_client_set_session_pool (client, session_pool);

  mount_points = gst_rtsp_mount_points_new ();
  factory = gst_rtsp_media_factory_new ();
  if (launch_line == NULL)
    gst_rtsp_media_factory_set_launch (factory,
        "( " VIDEO_PIPELINE "  " AUDIO_PIPELINE " )");
  else
    gst_rtsp_media_factory_set_launch (factory, launch_line);

  gst_rtsp_mount_points_add_factory (mount_points, "/test", factory);
  gst_rtsp_client_set_mount_points (client, mount_points);

  thread_pool = gst_rtsp_thread_pool_new ();
  gst_rtsp_client_set_thread_pool (client, thread_pool);

  g_object_unref (mount_points);
  g_object_unref (session_pool);
  g_object_unref (thread_pool);

  return client;
}

static void
teardown_client (GstRTSPClient * client)
{
  gst_rtsp_client_set_thread_pool (client, NULL);
  g_object_unref (client);
}

static gchar *
check_requirements_cb (GstRTSPClient * client, GstRTSPContext * ctx,
    gchar ** req, gpointer user_data)
{
  int index = 0;
  GString *result = g_string_new ("");

  while (req[index] != NULL) {
    if (g_strcmp0 (req[index], "test-requirements")) {
      if (result->len > 0)
        g_string_append (result, ", ");
      g_string_append (result, req[index]);
    }
    index++;
  }

  return g_string_free (result, FALSE);
}

GST_START_TEST (test_require)
{
  GstRTSPClient *client;
  GstRTSPMessage request = { 0, };
  gchar *str;

  client = gst_rtsp_client_new ();

  /* require header without handler */
  fail_unless (gst_rtsp_message_init_request (&request, GST_RTSP_OPTIONS,
          "rtsp://localhost/test") == GST_RTSP_OK);
  str = g_strdup_printf ("test-not-supported1");
  gst_rtsp_message_add_header (&request, GST_RTSP_HDR_REQUIRE, str);
  g_free (str);

  expected_unsupported_header = "test-not-supported1";
  gst_rtsp_client_set_send_func (client, test_response_551, NULL, NULL);
  fail_unless (gst_rtsp_client_handle_message (client,
          &request) == GST_RTSP_OK);
  gst_rtsp_message_unset (&request);

  g_signal_connect (G_OBJECT (client), "check-requirements",
      G_CALLBACK (check_requirements_cb), NULL);

  /* one supported option */
  fail_unless (gst_rtsp_message_init_request (&request, GST_RTSP_OPTIONS,
          "rtsp://localhost/test") == GST_RTSP_OK);
  str = g_strdup_printf ("test-requirements");
  gst_rtsp_message_add_header (&request, GST_RTSP_HDR_REQUIRE, str);
  g_free (str);

  gst_rtsp_client_set_send_func (client, test_response_200, NULL, NULL);
  fail_unless (gst_rtsp_client_handle_message (client,
          &request) == GST_RTSP_OK);
  gst_rtsp_message_unset (&request);

  /* unsupported option */
  fail_unless (gst_rtsp_message_init_request (&request, GST_RTSP_OPTIONS,
          "rtsp://localhost/test") == GST_RTSP_OK);
  str = g_strdup_printf ("test-not-supported1");
  gst_rtsp_message_add_header (&request, GST_RTSP_HDR_REQUIRE, str);
  g_free (str);

  expected_unsupported_header = "test-not-supported1";
  gst_rtsp_client_set_send_func (client, test_response_551, NULL, NULL);
  fail_unless (gst_rtsp_client_handle_message (client,
          &request) == GST_RTSP_OK);
  gst_rtsp_message_unset (&request);

  /* more than one unsupported options */
  fail_unless (gst_rtsp_message_init_request (&request, GST_RTSP_OPTIONS,
          "rtsp://localhost/test") == GST_RTSP_OK);
  str = g_strdup_printf ("test-not-supported1");
  gst_rtsp_message_add_header (&request, GST_RTSP_HDR_REQUIRE, str);
  g_free (str);
  str = g_strdup_printf ("test-not-supported2");
  gst_rtsp_message_add_header (&request, GST_RTSP_HDR_REQUIRE, str);
  g_free (str);

  expected_unsupported_header = "test-not-supported1, test-not-supported2";
  gst_rtsp_client_set_send_func (client, test_response_551, NULL, NULL);
  fail_unless (gst_rtsp_client_handle_message (client,
          &request) == GST_RTSP_OK);
  gst_rtsp_message_unset (&request);

  /* supported and unsupported together */
  fail_unless (gst_rtsp_message_init_request (&request, GST_RTSP_OPTIONS,
          "rtsp://localhost/test") == GST_RTSP_OK);
  str = g_strdup_printf ("test-not-supported1");
  gst_rtsp_message_add_header (&request, GST_RTSP_HDR_REQUIRE, str);
  g_free (str);
  str = g_strdup_printf ("test-requirements");
  gst_rtsp_message_add_header (&request, GST_RTSP_HDR_REQUIRE, str);
  g_free (str);
  str = g_strdup_printf ("test-not-supported2");
  gst_rtsp_message_add_header (&request, GST_RTSP_HDR_REQUIRE, str);
  g_free (str);

  expected_unsupported_header = "test-not-supported1, test-not-supported2";
  gst_rtsp_client_set_send_func (client, test_response_551, NULL, NULL);
  fail_unless (gst_rtsp_client_handle_message (client,
          &request) == GST_RTSP_OK);
  gst_rtsp_message_unset (&request);

  g_object_unref (client);
}

GST_END_TEST;

GST_START_TEST (test_request)
{
  GstRTSPClient *client;
  GstRTSPMessage request = { 0, };
  gchar *str;
  GstRTSPConnection *conn;

  client = gst_rtsp_client_new ();

  /* OPTIONS with invalid url */
  fail_unless (gst_rtsp_message_init_request (&request, GST_RTSP_OPTIONS,
          "foopy://padoop/") == GST_RTSP_OK);
  str = g_strdup_printf ("%d", cseq);
  gst_rtsp_message_add_header (&request, GST_RTSP_HDR_CSEQ, str);
  g_free (str);

  gst_rtsp_client_set_send_func (client, test_response_400, NULL, NULL);
  fail_unless (gst_rtsp_client_handle_message (client,
          &request) == GST_RTSP_OK);

  gst_rtsp_message_unset (&request);

  /* OPTIONS with unknown session id */
  fail_unless (gst_rtsp_message_init_request (&request, GST_RTSP_OPTIONS,
          "rtsp://localhost/test") == GST_RTSP_OK);
  str = g_strdup_printf ("%d", cseq);
  gst_rtsp_message_add_header (&request, GST_RTSP_HDR_CSEQ, str);
  g_free (str);
  gst_rtsp_message_add_header (&request, GST_RTSP_HDR_SESSION, "foobar");

  gst_rtsp_client_set_send_func (client, test_response_454, NULL, NULL);
  fail_unless (gst_rtsp_client_handle_message (client,
          &request) == GST_RTSP_OK);

  gst_rtsp_message_unset (&request);

  /* OPTIONS with an absolute path instead of an absolute url */
  /* set host information */
  create_connection (&conn);
  fail_unless (gst_rtsp_client_set_connection (client, conn));
  fail_unless (gst_rtsp_message_init_request (&request, GST_RTSP_OPTIONS,
          "/test") == GST_RTSP_OK);
  str = g_strdup_printf ("%d", cseq);
  gst_rtsp_message_add_header (&request, GST_RTSP_HDR_CSEQ, str);
  g_free (str);

  gst_rtsp_client_set_send_func (client, test_response_200, NULL, NULL);
  fail_unless (gst_rtsp_client_handle_message (client,
          &request) == GST_RTSP_OK);
  gst_rtsp_message_unset (&request);

  /* OPTIONS with an absolute path instead of an absolute url with invalid
   * host information */
  g_object_unref (client);
  client = gst_rtsp_client_new ();
  fail_unless (gst_rtsp_message_init_request (&request, GST_RTSP_OPTIONS,
          "/test") == GST_RTSP_OK);
  str = g_strdup_printf ("%d", cseq);
  gst_rtsp_message_add_header (&request, GST_RTSP_HDR_CSEQ, str);
  g_free (str);

  gst_rtsp_client_set_send_func (client, test_response_400, NULL, NULL);
  fail_unless (gst_rtsp_client_handle_message (client,
          &request) == GST_RTSP_OK);
  gst_rtsp_message_unset (&request);

  g_object_unref (client);
}

GST_END_TEST;

static gboolean
test_option_response_200 (GstRTSPClient * client, GstRTSPMessage * response,
    gboolean close, gpointer user_data)
{
  GstRTSPStatusCode code;
  const gchar *reason;
  GstRTSPVersion version;
  gchar *str;
  GstRTSPMethod methods;

  fail_unless (gst_rtsp_message_get_type (response) ==
      GST_RTSP_MESSAGE_RESPONSE);

  fail_unless (gst_rtsp_message_parse_response (response, &code, &reason,
          &version)
      == GST_RTSP_OK);
  fail_unless (code == GST_RTSP_STS_OK);
  fail_unless (g_str_equal (reason, "OK"));
  fail_unless (version == GST_RTSP_VERSION_1_0);

  fail_unless (gst_rtsp_message_get_header (response, GST_RTSP_HDR_CSEQ, &str,
          0) == GST_RTSP_OK);
  fail_unless (atoi (str) == cseq++);

  fail_unless (gst_rtsp_message_get_header (response, GST_RTSP_HDR_PUBLIC, &str,
          0) == GST_RTSP_OK);

  methods = gst_rtsp_options_from_text (str);
  fail_if (methods == 0);
  fail_unless (methods == (GST_RTSP_DESCRIBE |
          GST_RTSP_ANNOUNCE |
          GST_RTSP_OPTIONS |
          GST_RTSP_PAUSE |
          GST_RTSP_PLAY |
          GST_RTSP_RECORD |
          GST_RTSP_SETUP |
          GST_RTSP_GET_PARAMETER | GST_RTSP_SET_PARAMETER | GST_RTSP_TEARDOWN));

  return TRUE;
}

GST_START_TEST (test_options)
{
  GstRTSPClient *client;
  GstRTSPMessage request = { 0, };
  gchar *str;

  client = gst_rtsp_client_new ();

  /* simple OPTIONS */
  fail_unless (gst_rtsp_message_init_request (&request, GST_RTSP_OPTIONS,
          "rtsp://localhost/test") == GST_RTSP_OK);
  str = g_strdup_printf ("%d", cseq);
  gst_rtsp_message_add_header (&request, GST_RTSP_HDR_CSEQ, str);
  g_free (str);

  gst_rtsp_client_set_send_func (client, test_option_response_200, NULL, NULL);
  fail_unless (gst_rtsp_client_handle_message (client,
          &request) == GST_RTSP_OK);
  gst_rtsp_message_unset (&request);

  g_object_unref (client);
}

GST_END_TEST;

GST_START_TEST (test_describe)
{
  GstRTSPClient *client;
  GstRTSPMessage request = { 0, };
  gchar *str;

  client = gst_rtsp_client_new ();

  /* simple DESCRIBE for non-existing url */
  fail_unless (gst_rtsp_message_init_request (&request, GST_RTSP_DESCRIBE,
          "rtsp://localhost/test") == GST_RTSP_OK);
  str = g_strdup_printf ("%d", cseq);
  gst_rtsp_message_add_header (&request, GST_RTSP_HDR_CSEQ, str);
  g_free (str);

  gst_rtsp_client_set_send_func (client, test_response_404, NULL, NULL);
  fail_unless (gst_rtsp_client_handle_message (client,
          &request) == GST_RTSP_OK);
  gst_rtsp_message_unset (&request);

  g_object_unref (client);

  /* simple DESCRIBE for an existing url */
  client = setup_client (NULL);
  fail_unless (gst_rtsp_message_init_request (&request, GST_RTSP_DESCRIBE,
          "rtsp://localhost/test") == GST_RTSP_OK);
  str = g_strdup_printf ("%d", cseq);
  gst_rtsp_message_add_header (&request, GST_RTSP_HDR_CSEQ, str);
  g_free (str);

  gst_rtsp_client_set_send_func (client, test_response_200, NULL, NULL);
  fail_unless (gst_rtsp_client_handle_message (client,
          &request) == GST_RTSP_OK);
  gst_rtsp_message_unset (&request);

  teardown_client (client);
}

GST_END_TEST;

static const gchar *expected_transport = NULL;

static gboolean
test_setup_response_200 (GstRTSPClient * client, GstRTSPMessage * response,
    gboolean close, gpointer user_data)
{
  GstRTSPStatusCode code;
  const gchar *reason;
  GstRTSPVersion version;
  gchar *str;
  gchar *pattern;
  GstRTSPSessionPool *session_pool;
  GstRTSPSession *session;
  gchar **session_hdr_params;

  fail_unless (expected_transport != NULL);

  fail_unless_equals_int (gst_rtsp_message_get_type (response),
      GST_RTSP_MESSAGE_RESPONSE);

  fail_unless (gst_rtsp_message_parse_response (response, &code, &reason,
          &version)
      == GST_RTSP_OK);
  fail_unless_equals_int (code, GST_RTSP_STS_OK);
  fail_unless_equals_string (reason, "OK");
  fail_unless_equals_int (version, GST_RTSP_VERSION_1_0);

  fail_unless (gst_rtsp_message_get_header (response, GST_RTSP_HDR_CSEQ, &str,
          0) == GST_RTSP_OK);
  fail_unless (atoi (str) == cseq++);

  fail_unless (gst_rtsp_message_get_header (response, GST_RTSP_HDR_TRANSPORT,
          &str, 0) == GST_RTSP_OK);

  pattern = g_strdup_printf ("^%s$", expected_transport);
  fail_unless (g_regex_match_simple (pattern, str, 0, 0),
      "Transport '%s' doesn't match pattern '%s'", str, pattern);
  g_free (pattern);

  fail_unless (gst_rtsp_message_get_header (response, GST_RTSP_HDR_SESSION,
          &str, 0) == GST_RTSP_OK);
  session_hdr_params = g_strsplit (str, ";", -1);

  /* session-id value */
  fail_unless (session_hdr_params[0] != NULL);

  if (expected_session_timeout != 60) {
    /* session timeout param */
    gchar *timeout_str = g_strdup_printf ("timeout=%u",
        expected_session_timeout);

    fail_unless (session_hdr_params[1] != NULL);
    g_strstrip (session_hdr_params[1]);
    fail_unless (g_strcmp0 (session_hdr_params[1], timeout_str) == 0);
    g_free (timeout_str);
  }

  session_pool = gst_rtsp_client_get_session_pool (client);
  fail_unless (session_pool != NULL);

  session = gst_rtsp_session_pool_find (session_pool, session_hdr_params[0]);
  g_strfreev (session_hdr_params);

  /* remember session id to be able to send teardown */
  if (session_id)
    g_free (session_id);
  session_id = g_strdup (gst_rtsp_session_get_sessionid (session));
  fail_unless (session_id != NULL);

  fail_unless (session != NULL);
  g_object_unref (session);

  g_object_unref (session_pool);


  return TRUE;
}

static gboolean
test_setup_response_461 (GstRTSPClient * client,
    GstRTSPMessage * response, gboolean close, gpointer user_data)
{
  GstRTSPStatusCode code;
  const gchar *reason;
  GstRTSPVersion version;
  gchar *str;

  fail_unless (expected_transport == NULL);

  fail_unless (gst_rtsp_message_get_type (response) ==
      GST_RTSP_MESSAGE_RESPONSE);

  fail_unless (gst_rtsp_message_parse_response (response, &code, &reason,
          &version)
      == GST_RTSP_OK);
  fail_unless (code == GST_RTSP_STS_UNSUPPORTED_TRANSPORT);
  fail_unless (g_str_equal (reason, "Unsupported transport"));
  fail_unless (version == GST_RTSP_VERSION_1_0);

  fail_unless (gst_rtsp_message_get_header (response, GST_RTSP_HDR_CSEQ, &str,
          0) == GST_RTSP_OK);
  fail_unless (atoi (str) == cseq++);


  return TRUE;
}

static gboolean
test_teardown_response_200 (GstRTSPClient * client,
    GstRTSPMessage * response, gboolean close, gpointer user_data)
{
  GstRTSPStatusCode code;
  const gchar *reason;
  GstRTSPVersion version;

  fail_unless (gst_rtsp_message_get_type (response) ==
      GST_RTSP_MESSAGE_RESPONSE);

  fail_unless (gst_rtsp_message_parse_response (response, &code, &reason,
          &version)
      == GST_RTSP_OK);
  fail_unless (code == GST_RTSP_STS_OK);
  fail_unless (g_str_equal (reason, "OK"));
  fail_unless (version == GST_RTSP_VERSION_1_0);

  return TRUE;
}

static void
send_teardown (GstRTSPClient * client)
{
  GstRTSPMessage request = { 0, };
  gchar *str;

  fail_unless (session_id != NULL);
  fail_unless (gst_rtsp_message_init_request (&request, GST_RTSP_TEARDOWN,
          "rtsp://localhost/test") == GST_RTSP_OK);
  str = g_strdup_printf ("%d", cseq);
  gst_rtsp_message_take_header (&request, GST_RTSP_HDR_CSEQ, str);
  gst_rtsp_message_add_header (&request, GST_RTSP_HDR_SESSION, session_id);
  gst_rtsp_client_set_send_func (client, test_teardown_response_200,
      NULL, NULL);
  fail_unless (gst_rtsp_client_handle_message (client,
          &request) == GST_RTSP_OK);
  gst_rtsp_message_unset (&request);
  g_free (session_id);
  session_id = NULL;
}

GST_START_TEST (test_setup_tcp)
{
  GstRTSPClient *client;
  GstRTSPConnection *conn;
  GstRTSPMessage request = { 0, };
  gchar *str;

  client = setup_client (NULL);
  create_connection (&conn);
  fail_unless (gst_rtsp_client_set_connection (client, conn));

  fail_unless (gst_rtsp_message_init_request (&request, GST_RTSP_SETUP,
          "rtsp://localhost/test/stream=0") == GST_RTSP_OK);
  str = g_strdup_printf ("%d", cseq);
  gst_rtsp_message_add_header (&request, GST_RTSP_HDR_CSEQ, str);
  g_free (str);
  gst_rtsp_message_add_header (&request, GST_RTSP_HDR_TRANSPORT,
      "RTP/AVP/TCP;unicast");

  gst_rtsp_client_set_send_func (client, test_setup_response_200, NULL, NULL);
  expected_transport =
      "RTP/AVP/TCP;unicast;interleaved=0-1;ssrc=.*;mode=\"PLAY\"";
  fail_unless (gst_rtsp_client_handle_message (client,
          &request) == GST_RTSP_OK);

  gst_rtsp_message_unset (&request);

  send_teardown (client);
  teardown_client (client);
}

GST_END_TEST;

GST_START_TEST (test_setup_tcp_two_streams_same_channels)
{
  GstRTSPClient *client;
  GstRTSPConnection *conn;
  GstRTSPMessage request = { 0, };
  gchar *str;

  client = setup_client (NULL);
  create_connection (&conn);
  fail_unless (gst_rtsp_client_set_connection (client, conn));

  /* test SETUP of a video stream with 0-1 as interleaved channels */
  fail_unless (gst_rtsp_message_init_request (&request, GST_RTSP_SETUP,
          "rtsp://localhost/test/stream=0") == GST_RTSP_OK);
  str = g_strdup_printf ("%d", cseq);
  gst_rtsp_message_add_header (&request, GST_RTSP_HDR_CSEQ, str);
  g_free (str);
  gst_rtsp_message_add_header (&request, GST_RTSP_HDR_TRANSPORT,
      "RTP/AVP/TCP;unicast;interleaved=0-1");
  gst_rtsp_client_set_send_func (client, test_setup_response_200, NULL, NULL);
  expected_transport =
      "RTP/AVP/TCP;unicast;interleaved=0-1;ssrc=.*;mode=\"PLAY\"";
  fail_unless (gst_rtsp_client_handle_message (client,
          &request) == GST_RTSP_OK);
  gst_rtsp_message_unset (&request);

  /* test SETUP of an audio stream with *the same* interleaved channels.
   * we expect the server to allocate new channel numbers */
  fail_unless (gst_rtsp_message_init_request (&request, GST_RTSP_SETUP,
          "rtsp://localhost/test/stream=1") == GST_RTSP_OK);
  str = g_strdup_printf ("%d", cseq);
  gst_rtsp_message_add_header (&request, GST_RTSP_HDR_CSEQ, str);
  g_free (str);
  gst_rtsp_message_add_header (&request, GST_RTSP_HDR_TRANSPORT,
      "RTP/AVP/TCP;unicast;interleaved=0-1");
  gst_rtsp_message_add_header (&request, GST_RTSP_HDR_SESSION, session_id);
  gst_rtsp_client_set_send_func (client, test_setup_response_200, NULL, NULL);
  expected_transport =
      "RTP/AVP/TCP;unicast;interleaved=2-3;ssrc=.*;mode=\"PLAY\"";
  fail_unless (gst_rtsp_client_handle_message (client,
          &request) == GST_RTSP_OK);
  gst_rtsp_message_unset (&request);

  send_teardown (client);
  teardown_client (client);
}

GST_END_TEST;

static GstRTSPClient *
setup_multicast_client (guint max_ttl)
{
  GstRTSPClient *client;
  GstRTSPSessionPool *session_pool;
  GstRTSPMountPoints *mount_points;
  GstRTSPMediaFactory *factory;
  GstRTSPAddressPool *address_pool;
  GstRTSPThreadPool *thread_pool;

  client = gst_rtsp_client_new ();

  session_pool = gst_rtsp_session_pool_new ();
  gst_rtsp_client_set_session_pool (client, session_pool);

  mount_points = gst_rtsp_mount_points_new ();
  factory = gst_rtsp_media_factory_new ();
  gst_rtsp_media_factory_set_launch (factory,
      "audiotestsrc ! audio/x-raw,rate=44100 ! audioconvert ! rtpL16pay name=pay0");
  address_pool = gst_rtsp_address_pool_new ();
  fail_unless (gst_rtsp_address_pool_add_range (address_pool,
          "233.252.0.1", "233.252.0.1", 5000, 5010, 1));
  gst_rtsp_media_factory_set_address_pool (factory, address_pool);
  gst_rtsp_media_factory_add_role (factory, "user",
      "media.factory.access", G_TYPE_BOOLEAN, TRUE,
      "media.factory.construct", G_TYPE_BOOLEAN, TRUE, NULL);
  gst_rtsp_mount_points_add_factory (mount_points, "/test", factory);
  gst_rtsp_client_set_mount_points (client, mount_points);
  gst_rtsp_media_factory_set_max_mcast_ttl (factory, max_ttl);

  thread_pool = gst_rtsp_thread_pool_new ();
  gst_rtsp_client_set_thread_pool (client, thread_pool);

  g_object_unref (mount_points);
  g_object_unref (session_pool);
  g_object_unref (address_pool);
  g_object_unref (thread_pool);

  return client;
}

GST_START_TEST (test_client_multicast_transport_404)
{
  GstRTSPClient *client;
  GstRTSPMessage request = { 0, };
  gchar *str;

  client = setup_multicast_client (1);

  /* simple SETUP for non-existing url */
  fail_unless (gst_rtsp_message_init_request (&request, GST_RTSP_SETUP,
          "rtsp://localhost/test2/stream=0") == GST_RTSP_OK);
  str = g_strdup_printf ("%d", cseq);
  gst_rtsp_message_take_header (&request, GST_RTSP_HDR_CSEQ, str);
  gst_rtsp_message_add_header (&request, GST_RTSP_HDR_TRANSPORT,
      "RTP/AVP;multicast");

  gst_rtsp_client_set_send_func (client, test_response_404, NULL, NULL);
  fail_unless (gst_rtsp_client_handle_message (client,
          &request) == GST_RTSP_OK);
  gst_rtsp_message_unset (&request);

  teardown_client (client);
}

GST_END_TEST;

static void
new_session_cb (GObject * client, GstRTSPSession * session, gpointer user_data)
{
  GST_DEBUG ("%p: new session %p", client, session);
  gst_rtsp_session_set_timeout (session, expected_session_timeout);
}

GST_START_TEST (test_client_multicast_transport)
{
  GstRTSPClient *client;
  GstRTSPMessage request = { 0, };
  gchar *str;

  client = setup_multicast_client (1);

  expected_session_timeout = 20;
  g_signal_connect (G_OBJECT (client), "new-session",
      G_CALLBACK (new_session_cb), NULL);

  /* simple SETUP with a valid URI and multicast */
  fail_unless (gst_rtsp_message_init_request (&request, GST_RTSP_SETUP,
          "rtsp://localhost/test/stream=0") == GST_RTSP_OK);
  str = g_strdup_printf ("%d", cseq);
  gst_rtsp_message_take_header (&request, GST_RTSP_HDR_CSEQ, str);
  gst_rtsp_message_add_header (&request, GST_RTSP_HDR_TRANSPORT,
      "RTP/AVP;multicast");

  expected_transport = "RTP/AVP;multicast;destination=233.252.0.1;"
      "ttl=1;port=5000-5001;mode=\"PLAY\"";
  gst_rtsp_client_set_send_func (client, test_setup_response_200, NULL, NULL);
  fail_unless (gst_rtsp_client_handle_message (client,
          &request) == GST_RTSP_OK);
  gst_rtsp_message_unset (&request);
  expected_transport = NULL;
  expected_session_timeout = 60;

  send_teardown (client);

  teardown_client (client);
}

GST_END_TEST;

GST_START_TEST (test_client_multicast_ignore_transport_specific)
{
  GstRTSPClient *client;
  GstRTSPMessage request = { 0, };
  gchar *str;

  client = setup_multicast_client (1);

  /* simple SETUP with a valid URI and multicast and a specific dest,
   * but ignore it  */
  fail_unless (gst_rtsp_message_init_request (&request, GST_RTSP_SETUP,
          "rtsp://localhost/test/stream=0") == GST_RTSP_OK);
  str = g_strdup_printf ("%d", cseq);
  gst_rtsp_message_take_header (&request, GST_RTSP_HDR_CSEQ, str);
  gst_rtsp_message_add_header (&request, GST_RTSP_HDR_TRANSPORT,
      "RTP/AVP;multicast;destination=233.252.0.2;ttl=2;port=5001-5006;");

  expected_transport = "RTP/AVP;multicast;destination=233.252.0.1;"
      "ttl=1;port=5000-5001;mode=\"PLAY\"";
  gst_rtsp_client_set_send_func (client, test_setup_response_200, NULL, NULL);
  fail_unless (gst_rtsp_client_handle_message (client,
          &request) == GST_RTSP_OK);
  gst_rtsp_message_unset (&request);
  expected_transport = NULL;

  send_teardown (client);

  teardown_client (client);
}

GST_END_TEST;

static void
multicast_transport_specific (void)
{
  GstRTSPClient *client;
  GstRTSPMessage request = { 0, };
  gchar *str;
  GstRTSPSessionPool *session_pool;
  GstRTSPContext ctx = { NULL };

  client = setup_multicast_client (1);

  ctx.client = client;
  ctx.auth = gst_rtsp_auth_new ();
  ctx.token =
      gst_rtsp_token_new (GST_RTSP_TOKEN_TRANSPORT_CLIENT_SETTINGS,
      G_TYPE_BOOLEAN, TRUE, GST_RTSP_TOKEN_MEDIA_FACTORY_ROLE, G_TYPE_STRING,
      "user", NULL);
  gst_rtsp_context_push_current (&ctx);

  /* simple SETUP with a valid URI */
  fail_unless (gst_rtsp_message_init_request (&request, GST_RTSP_SETUP,
          "rtsp://localhost/test/stream=0") == GST_RTSP_OK);
  str = g_strdup_printf ("%d", cseq);
  gst_rtsp_message_take_header (&request, GST_RTSP_HDR_CSEQ, str);
  gst_rtsp_message_add_header (&request, GST_RTSP_HDR_TRANSPORT,
      expected_transport);

  gst_rtsp_client_set_send_func (client, test_setup_response_200, NULL, NULL);
  fail_unless (gst_rtsp_client_handle_message (client,
          &request) == GST_RTSP_OK);
  gst_rtsp_message_unset (&request);

  gst_rtsp_client_set_send_func (client, test_setup_response_200, NULL, NULL);
  session_pool = gst_rtsp_client_get_session_pool (client);
  fail_unless (session_pool != NULL);
  fail_unless (gst_rtsp_session_pool_get_n_sessions (session_pool) == 1);
  g_object_unref (session_pool);

  /* send PLAY request */
  fail_unless (gst_rtsp_message_init_request (&request, GST_RTSP_PLAY,
          "rtsp://localhost/test") == GST_RTSP_OK);
  str = g_strdup_printf ("%d", cseq);
  gst_rtsp_message_take_header (&request, GST_RTSP_HDR_CSEQ, str);
  gst_rtsp_message_add_header (&request, GST_RTSP_HDR_SESSION, session_id);
  gst_rtsp_client_set_send_func (client, test_response_200, NULL, NULL);
  fail_unless (gst_rtsp_client_handle_message (client,
          &request) == GST_RTSP_OK);
  gst_rtsp_message_unset (&request);

  send_teardown (client);
  teardown_client (client);
  g_object_unref (ctx.auth);
  gst_rtsp_token_unref (ctx.token);
  gst_rtsp_context_pop_current (&ctx);
}

/* CASE: multicast address requested by the client exists in the address pool */
GST_START_TEST (test_client_multicast_transport_specific)
{
  expected_transport = "RTP/AVP;multicast;destination=233.252.0.1;"
      "ttl=1;port=5000-5001;mode=\"PLAY\"";
  multicast_transport_specific ();
  expected_transport = NULL;
}

GST_END_TEST;

/* CASE: multicast address requested by the client does not exist in the address pool */
GST_START_TEST (test_client_multicast_transport_specific_no_address_in_pool)
{
  expected_transport = "RTP/AVP;multicast;destination=234.252.0.3;"
      "ttl=1;port=10002-10004;mode=\"PLAY\"";
  multicast_transport_specific ();
  expected_transport = NULL;
}

GST_END_TEST;

static gboolean
test_response_sdp (GstRTSPClient * client, GstRTSPMessage * response,
    gboolean close, gpointer user_data)
{
  guint8 *data;
  guint size;
  GstSDPMessage *sdp_msg;
  const GstSDPMedia *sdp_media;
  const GstSDPBandwidth *bw;
  gint bandwidth_val = GPOINTER_TO_INT (user_data);

  fail_unless (gst_rtsp_message_get_body (response, &data, &size)
      == GST_RTSP_OK);
  gst_sdp_message_new (&sdp_msg);
  fail_unless (gst_sdp_message_parse_buffer (data, size, sdp_msg)
      == GST_SDP_OK);

  /* session description */
  /* v= */
  fail_unless (gst_sdp_message_get_version (sdp_msg) != NULL);
  /* o= */
  fail_unless (gst_sdp_message_get_origin (sdp_msg) != NULL);
  /* s= */
  fail_unless (gst_sdp_message_get_session_name (sdp_msg) != NULL);
  /* t=0 0 */
  fail_unless (gst_sdp_message_times_len (sdp_msg) == 0);

  /* verify number of medias */
  fail_unless (gst_sdp_message_medias_len (sdp_msg) == 1);

  /* media description */
  sdp_media = gst_sdp_message_get_media (sdp_msg, 0);
  fail_unless (sdp_media != NULL);

  /* m= */
  fail_unless (gst_sdp_media_get_media (sdp_media) != NULL);

  /* media bandwidth */
  if (bandwidth_val) {
    fail_unless (gst_sdp_media_bandwidths_len (sdp_media) == 1);
    bw = gst_sdp_media_get_bandwidth (sdp_media, 0);
    fail_unless (bw != NULL);
    fail_unless (g_strcmp0 (bw->bwtype, "AS") == 0);
    fail_unless (bw->bandwidth == bandwidth_val);
  } else {
    fail_unless (gst_sdp_media_bandwidths_len (sdp_media) == 0);
  }

  gst_sdp_message_free (sdp_msg);

  return TRUE;
}

static void
test_client_sdp (const gchar * launch_line, guint * bandwidth_val)
{
  GstRTSPClient *client;
  GstRTSPMessage request = { 0, };
  gchar *str;

  /* simple DESCRIBE for an existing url */
  client = setup_client (launch_line);
  fail_unless (gst_rtsp_message_init_request (&request, GST_RTSP_DESCRIBE,
          "rtsp://localhost/test") == GST_RTSP_OK);
  str = g_strdup_printf ("%d", cseq);
  gst_rtsp_message_add_header (&request, GST_RTSP_HDR_CSEQ, str);
  g_free (str);

  gst_rtsp_client_set_send_func (client, test_response_sdp,
      (gpointer) bandwidth_val, NULL);
  fail_unless (gst_rtsp_client_handle_message (client,
          &request) == GST_RTSP_OK);
  gst_rtsp_message_unset (&request);

  teardown_client (client);
}

GST_START_TEST (test_client_sdp_with_max_bitrate_tag)
{
  test_client_sdp ("videotestsrc "
      "! taginject tags=\"maximum-bitrate=(uint)50000000\" "
      "! video/x-raw,width=352,height=288 ! rtpgstpay name=pay0 pt=96",
      GUINT_TO_POINTER (50000));


  /* max-bitrate=0: no bandwidth line */
  test_client_sdp ("videotestsrc "
      "! taginject tags=\"maximum-bitrate=(uint)0\" "
      "! video/x-raw,width=352,height=288 ! rtpgstpay name=pay0 pt=96",
      GUINT_TO_POINTER (0));
}

GST_END_TEST;

GST_START_TEST (test_client_sdp_with_bitrate_tag)
{
  test_client_sdp ("videotestsrc "
      "! taginject tags=\"bitrate=(uint)7000000\" "
      "! video/x-raw,width=352,height=288 ! rtpgstpay name=pay0 pt=96",
      GUINT_TO_POINTER (7000));

  /* bitrate=0: no bandwdith line */
  test_client_sdp ("videotestsrc "
      "! taginject tags=\"bitrate=(uint)0\" "
      "! video/x-raw,width=352,height=288 ! rtpgstpay name=pay0 pt=96",
      GUINT_TO_POINTER (0));
}

GST_END_TEST;

GST_START_TEST (test_client_sdp_with_max_bitrate_and_bitrate_tags)
{
  test_client_sdp ("videotestsrc "
      "! taginject tags=\"bitrate=(uint)7000000,maximum-bitrate=(uint)50000000\" "
      "! video/x-raw,width=352,height=288 ! rtpgstpay name=pay0 pt=96",
      GUINT_TO_POINTER (50000));

  /* max-bitrate is zero: fallback to bitrate */
  test_client_sdp ("videotestsrc "
      "! taginject tags=\"bitrate=(uint)7000000,maximum-bitrate=(uint)0\" "
      "! video/x-raw,width=352,height=288 ! rtpgstpay name=pay0 pt=96",
      GUINT_TO_POINTER (7000));

  /* max-bitrate=bitrate=0o: no bandwidth line */
  test_client_sdp ("videotestsrc "
      "! taginject tags=\"bitrate=(uint)0,maximum-bitrate=(uint)0\" "
      "! video/x-raw,width=352,height=288 ! rtpgstpay name=pay0 pt=96",
      GUINT_TO_POINTER (0));
}

GST_END_TEST;

GST_START_TEST (test_client_sdp_with_no_bitrate_tags)
{
  test_client_sdp ("videotestsrc "
      "! video/x-raw,width=352,height=288 ! rtpgstpay name=pay0 pt=96", NULL);
}

GST_END_TEST;

static void
mcast_transport_two_clients (gboolean shared, const gchar * transport1,
    const gchar * expected_transport1, const gchar * addr1,
    const gchar * transport2, const gchar * expected_transport2,
    const gchar * addr2, gboolean bind_mcast_address)
{
  GstRTSPClient *client1, *client2;
  GstRTSPMessage request = { 0, };
  gchar *str;
  GstRTSPSessionPool *session_pool;
  GstRTSPContext ctx = { NULL };
  GstRTSPContext ctx2 = { NULL };
  GstRTSPMountPoints *mount_points;
  GstRTSPMediaFactory *factory;
  GstRTSPAddressPool *address_pool;
  GstRTSPThreadPool *thread_pool;
  gchar *session_id1;
  gchar *client_addr = NULL;

  mount_points = gst_rtsp_mount_points_new ();
  factory = gst_rtsp_media_factory_new ();
  if (shared)
    gst_rtsp_media_factory_set_shared (factory, TRUE);
  gst_rtsp_media_factory_set_max_mcast_ttl (factory, 5);
  gst_rtsp_media_factory_set_bind_mcast_address (factory, bind_mcast_address);
  gst_rtsp_media_factory_set_launch (factory,
      "audiotestsrc ! audio/x-raw,rate=44100 ! audioconvert ! rtpL16pay name=pay0");
  address_pool = gst_rtsp_address_pool_new ();
  fail_unless (gst_rtsp_address_pool_add_range (address_pool,
          "233.252.0.1", "233.252.0.1", 5000, 5001, 1));
  gst_rtsp_media_factory_set_address_pool (factory, address_pool);
  gst_rtsp_media_factory_add_role (factory, "user",
      "media.factory.access", G_TYPE_BOOLEAN, TRUE,
      "media.factory.construct", G_TYPE_BOOLEAN, TRUE, NULL);
  gst_rtsp_mount_points_add_factory (mount_points, "/test", factory);
  session_pool = gst_rtsp_session_pool_new ();
  thread_pool = gst_rtsp_thread_pool_new ();

  /* first multicast client with transport specific request */
  client1 = gst_rtsp_client_new ();
  gst_rtsp_client_set_session_pool (client1, session_pool);
  gst_rtsp_client_set_mount_points (client1, mount_points);
  gst_rtsp_client_set_thread_pool (client1, thread_pool);

  ctx.client = client1;
  ctx.auth = gst_rtsp_auth_new ();
  ctx.token =
      gst_rtsp_token_new (GST_RTSP_TOKEN_TRANSPORT_CLIENT_SETTINGS,
      G_TYPE_BOOLEAN, TRUE, GST_RTSP_TOKEN_MEDIA_FACTORY_ROLE, G_TYPE_STRING,
      "user", NULL);
  gst_rtsp_context_push_current (&ctx);

  expected_transport = expected_transport1;

  /* send SETUP request */
  fail_unless (gst_rtsp_message_init_request (&request, GST_RTSP_SETUP,
          "rtsp://localhost/test/stream=0") == GST_RTSP_OK);
  str = g_strdup_printf ("%d", cseq);
  gst_rtsp_message_take_header (&request, GST_RTSP_HDR_CSEQ, str);
  gst_rtsp_message_add_header (&request, GST_RTSP_HDR_TRANSPORT, transport1);

  gst_rtsp_client_set_send_func (client1, test_setup_response_200, NULL, NULL);
  fail_unless (gst_rtsp_client_handle_message (client1,
          &request) == GST_RTSP_OK);
  gst_rtsp_message_unset (&request);
  expected_transport = NULL;

  /* send PLAY request */
  fail_unless (gst_rtsp_message_init_request (&request, GST_RTSP_PLAY,
          "rtsp://localhost/test") == GST_RTSP_OK);
  str = g_strdup_printf ("%d", cseq);
  gst_rtsp_message_take_header (&request, GST_RTSP_HDR_CSEQ, str);
  gst_rtsp_message_add_header (&request, GST_RTSP_HDR_SESSION, session_id);
  gst_rtsp_client_set_send_func (client1, test_response_200, NULL, NULL);
  fail_unless (gst_rtsp_client_handle_message (client1,
          &request) == GST_RTSP_OK);
  gst_rtsp_message_unset (&request);

  /* check address */
  client_addr = gst_rtsp_stream_get_multicast_client_addresses (ctx.stream);
  fail_if (client_addr == NULL);
  fail_unless (g_str_equal (client_addr, addr1));
  g_free (client_addr);

  gst_rtsp_context_pop_current (&ctx);
  session_id1 = g_strdup (session_id);

  /* second multicast client with transport specific request */
  cseq = 0;
  client2 = gst_rtsp_client_new ();
  gst_rtsp_client_set_session_pool (client2, session_pool);
  gst_rtsp_client_set_mount_points (client2, mount_points);
  gst_rtsp_client_set_thread_pool (client2, thread_pool);

  ctx2.client = client2;
  ctx2.auth = gst_rtsp_auth_new ();
  ctx2.token =
      gst_rtsp_token_new (GST_RTSP_TOKEN_TRANSPORT_CLIENT_SETTINGS,
      G_TYPE_BOOLEAN, TRUE, GST_RTSP_TOKEN_MEDIA_FACTORY_ROLE, G_TYPE_STRING,
      "user", NULL);
  gst_rtsp_context_push_current (&ctx2);

  expected_transport = expected_transport2;

  /* send SETUP request */
  fail_unless (gst_rtsp_message_init_request (&request, GST_RTSP_SETUP,
          "rtsp://localhost/test/stream=0") == GST_RTSP_OK);
  str = g_strdup_printf ("%d", cseq);
  gst_rtsp_message_take_header (&request, GST_RTSP_HDR_CSEQ, str);
  gst_rtsp_message_add_header (&request, GST_RTSP_HDR_TRANSPORT, transport2);

  gst_rtsp_client_set_send_func (client2, test_setup_response_200, NULL, NULL);
  fail_unless (gst_rtsp_client_handle_message (client2,
          &request) == GST_RTSP_OK);
  gst_rtsp_message_unset (&request);
  expected_transport = NULL;

  /* send PLAY request */
  fail_unless (gst_rtsp_message_init_request (&request, GST_RTSP_PLAY,
          "rtsp://localhost/test") == GST_RTSP_OK);
  str = g_strdup_printf ("%d", cseq);
  gst_rtsp_message_take_header (&request, GST_RTSP_HDR_CSEQ, str);
  gst_rtsp_message_add_header (&request, GST_RTSP_HDR_SESSION, session_id);
  gst_rtsp_client_set_send_func (client2, test_response_200, NULL, NULL);
  fail_unless (gst_rtsp_client_handle_message (client2,
          &request) == GST_RTSP_OK);
  gst_rtsp_message_unset (&request);

  /* check addresses */
  client_addr = gst_rtsp_stream_get_multicast_client_addresses (ctx2.stream);
  fail_if (client_addr == NULL);
  if (shared) {
    if (g_str_equal (addr1, addr2)) {
      fail_unless (g_str_equal (client_addr, addr1));
    } else {
      gchar *addr_str = g_strdup_printf ("%s,%s", addr2, addr1);
      fail_unless (g_str_equal (client_addr, addr_str));
      g_free (addr_str);
    }
  } else {
    fail_unless (g_str_equal (client_addr, addr2));
  }
  g_free (client_addr);

  send_teardown (client2);
  gst_rtsp_context_pop_current (&ctx2);

  gst_rtsp_context_push_current (&ctx);
  session_id = session_id1;
  send_teardown (client1);
  gst_rtsp_context_pop_current (&ctx);

  teardown_client (client1);
  teardown_client (client2);
  g_object_unref (ctx.auth);
  g_object_unref (ctx2.auth);
  gst_rtsp_token_unref (ctx.token);
  gst_rtsp_token_unref (ctx2.token);
  g_object_unref (mount_points);
  g_object_unref (session_pool);
  g_object_unref (address_pool);
  g_object_unref (thread_pool);
}

/* CASE: media is shared.
 * client 1: SETUP    --->
 * client 1: PLAY     --->
 * client 2: SETUP    --->
 * client 1: TEARDOWN --->
 * client 2: PLAY     --->
 * client 2: TEARDOWN --->
 */
static void
mcast_transport_two_clients_teardown_play (const gchar * transport1,
    const gchar * expected_transport1, const gchar * transport2,
    const gchar * expected_transport2, gboolean bind_mcast_address,
    gboolean is_shared)
{
  GstRTSPClient *client1, *client2;
  GstRTSPMessage request = { 0, };
  gchar *str;
  GstRTSPSessionPool *session_pool;
  GstRTSPContext ctx = { NULL };
  GstRTSPContext ctx2 = { NULL };
  GstRTSPMountPoints *mount_points;
  GstRTSPMediaFactory *factory;
  GstRTSPAddressPool *address_pool;
  GstRTSPThreadPool *thread_pool;
  gchar *session_id1, *session_id2;

  mount_points = gst_rtsp_mount_points_new ();
  factory = gst_rtsp_media_factory_new ();
  gst_rtsp_media_factory_set_shared (factory, is_shared);
  gst_rtsp_media_factory_set_max_mcast_ttl (factory, 5);
  gst_rtsp_media_factory_set_bind_mcast_address (factory, bind_mcast_address);
  gst_rtsp_media_factory_set_launch (factory,
      "audiotestsrc ! audio/x-raw,rate=44100 ! audioconvert ! rtpL16pay name=pay0");
  address_pool = gst_rtsp_address_pool_new ();
  if (is_shared)
    fail_unless (gst_rtsp_address_pool_add_range (address_pool,
            "233.252.0.1", "233.252.0.1", 5000, 5001, 1));
  else
    fail_unless (gst_rtsp_address_pool_add_range (address_pool,
            "233.252.0.1", "233.252.0.1", 5000, 5003, 1));
  gst_rtsp_media_factory_set_address_pool (factory, address_pool);
  gst_rtsp_media_factory_add_role (factory, "user",
      "media.factory.access", G_TYPE_BOOLEAN, TRUE,
      "media.factory.construct", G_TYPE_BOOLEAN, TRUE, NULL);
  gst_rtsp_mount_points_add_factory (mount_points, "/test", factory);
  session_pool = gst_rtsp_session_pool_new ();
  thread_pool = gst_rtsp_thread_pool_new ();

  /* client 1 configuration */
  client1 = gst_rtsp_client_new ();
  gst_rtsp_client_set_session_pool (client1, session_pool);
  gst_rtsp_client_set_mount_points (client1, mount_points);
  gst_rtsp_client_set_thread_pool (client1, thread_pool);

  ctx.client = client1;
  ctx.auth = gst_rtsp_auth_new ();
  ctx.token =
      gst_rtsp_token_new (GST_RTSP_TOKEN_TRANSPORT_CLIENT_SETTINGS,
      G_TYPE_BOOLEAN, TRUE, GST_RTSP_TOKEN_MEDIA_FACTORY_ROLE, G_TYPE_STRING,
      "user", NULL);
  gst_rtsp_context_push_current (&ctx);

  expected_transport = expected_transport1;

  /* client 1 sends SETUP request */
  fail_unless (gst_rtsp_message_init_request (&request, GST_RTSP_SETUP,
          "rtsp://localhost/test/stream=0") == GST_RTSP_OK);
  str = g_strdup_printf ("%d", cseq);
  gst_rtsp_message_take_header (&request, GST_RTSP_HDR_CSEQ, str);
  gst_rtsp_message_add_header (&request, GST_RTSP_HDR_TRANSPORT, transport1);

  gst_rtsp_client_set_send_func (client1, test_setup_response_200, NULL, NULL);
  fail_unless (gst_rtsp_client_handle_message (client1,
          &request) == GST_RTSP_OK);
  gst_rtsp_message_unset (&request);
  expected_transport = NULL;


  /* client 1 sends PLAY request */
  fail_unless (gst_rtsp_message_init_request (&request, GST_RTSP_PLAY,
          "rtsp://localhost/test") == GST_RTSP_OK);
  str = g_strdup_printf ("%d", cseq);
  gst_rtsp_message_take_header (&request, GST_RTSP_HDR_CSEQ, str);
  gst_rtsp_message_add_header (&request, GST_RTSP_HDR_SESSION, session_id);
  gst_rtsp_client_set_send_func (client1, test_response_200, NULL, NULL);
  fail_unless (gst_rtsp_client_handle_message (client1,
          &request) == GST_RTSP_OK);
  gst_rtsp_message_unset (&request);

  gst_rtsp_context_pop_current (&ctx);
  session_id1 = g_strdup (session_id);

  /* client 2 configuration */
  cseq = 0;
  client2 = gst_rtsp_client_new ();
  gst_rtsp_client_set_session_pool (client2, session_pool);
  gst_rtsp_client_set_mount_points (client2, mount_points);
  gst_rtsp_client_set_thread_pool (client2, thread_pool);

  ctx2.client = client2;
  ctx2.auth = gst_rtsp_auth_new ();
  ctx2.token =
      gst_rtsp_token_new (GST_RTSP_TOKEN_TRANSPORT_CLIENT_SETTINGS,
      G_TYPE_BOOLEAN, TRUE, GST_RTSP_TOKEN_MEDIA_FACTORY_ROLE, G_TYPE_STRING,
      "user", NULL);
  gst_rtsp_context_push_current (&ctx2);

  expected_transport = expected_transport2;

  /* client 2 sends SETUP request */
  fail_unless (gst_rtsp_message_init_request (&request, GST_RTSP_SETUP,
          "rtsp://localhost/test/stream=0") == GST_RTSP_OK);
  str = g_strdup_printf ("%d", cseq);
  gst_rtsp_message_take_header (&request, GST_RTSP_HDR_CSEQ, str);
  gst_rtsp_message_add_header (&request, GST_RTSP_HDR_TRANSPORT, transport2);

  gst_rtsp_client_set_send_func (client2, test_setup_response_200, NULL, NULL);
  fail_unless (gst_rtsp_client_handle_message (client2,
          &request) == GST_RTSP_OK);
  gst_rtsp_message_unset (&request);
  expected_transport = NULL;

  session_id2 = g_strdup (session_id);
  g_free (session_id);
  gst_rtsp_context_pop_current (&ctx2);

  /* the first client sends TEARDOWN request */
  gst_rtsp_context_push_current (&ctx);
  session_id = session_id1;
  send_teardown (client1);
  gst_rtsp_context_pop_current (&ctx);
  teardown_client (client1);

  /* the second client sends PLAY request */
  gst_rtsp_context_push_current (&ctx2);
  session_id = session_id2;
  fail_unless (gst_rtsp_message_init_request (&request, GST_RTSP_PLAY,
          "rtsp://localhost/test") == GST_RTSP_OK);
  str = g_strdup_printf ("%d", cseq);
  gst_rtsp_message_take_header (&request, GST_RTSP_HDR_CSEQ, str);
  gst_rtsp_message_add_header (&request, GST_RTSP_HDR_SESSION, session_id);
  gst_rtsp_client_set_send_func (client2, test_response_200, NULL, NULL);
  fail_unless (gst_rtsp_client_handle_message (client2,
          &request) == GST_RTSP_OK);
  gst_rtsp_message_unset (&request);

  /* client 2 sends TEARDOWN request */
  send_teardown (client2);
  gst_rtsp_context_pop_current (&ctx2);

  teardown_client (client2);
  g_object_unref (ctx.auth);
  g_object_unref (ctx2.auth);
  gst_rtsp_token_unref (ctx.token);
  gst_rtsp_token_unref (ctx2.token);
  g_object_unref (mount_points);
  g_object_unref (session_pool);
  g_object_unref (address_pool);
  g_object_unref (thread_pool);
}

/* test if two multicast clients can choose different transport settings
 * CASE: media is shared */
GST_START_TEST
    (test_client_multicast_transport_specific_two_clients_shared_media) {
  const gchar *transport_client_1 = "RTP/AVP;multicast;destination=233.252.0.1;"
      "ttl=1;port=5000-5001;mode=\"PLAY\"";
  const gchar *expected_transport_1 = transport_client_1;
  const gchar *addr_client_1 = "233.252.0.1:5000";

  const gchar *transport_client_2 = "RTP/AVP;multicast;destination=233.252.0.2;"
      "ttl=1;port=5002-5003;mode=\"PLAY\"";
  const gchar *expected_transport_2 = transport_client_2;
  const gchar *addr_client_2 = "233.252.0.2:5002";

  mcast_transport_two_clients (TRUE, transport_client_1,
      expected_transport_1, addr_client_1, transport_client_2,
      expected_transport_2, addr_client_2, FALSE);
}

GST_END_TEST;

/* test if two multicast clients can choose different transport settings
 * CASE: media is not shared */
GST_START_TEST (test_client_multicast_transport_specific_two_clients)
{
  const gchar *transport_client_1 = "RTP/AVP;multicast;destination=233.252.0.1;"
      "ttl=1;port=5000-5001;mode=\"PLAY\"";
  const gchar *expected_transport_1 = transport_client_1;
  const gchar *addr_client_1 = "233.252.0.1:5000";

  const gchar *transport_client_2 = "RTP/AVP;multicast;destination=233.252.0.2;"
      "ttl=1;port=5002-5003;mode=\"PLAY\"";
  const gchar *expected_transport_2 = transport_client_2;
  const gchar *addr_client_2 = "233.252.0.2:5002";

  mcast_transport_two_clients (FALSE, transport_client_1,
      expected_transport_1, addr_client_1, transport_client_2,
      expected_transport_2, addr_client_2, FALSE);
}

GST_END_TEST;

/* test if two multicast clients can choose the same ports but different
 * multicast destinations
 * CASE: media is not shared */
GST_START_TEST (test_client_multicast_transport_specific_two_clients_same_ports)
{
  const gchar *transport_client_1 = "RTP/AVP;multicast;destination=233.252.0.1;"
      "ttl=1;port=9000-9001;mode=\"PLAY\"";
  const gchar *expected_transport_1 = transport_client_1;
  const gchar *addr_client_1 = "233.252.0.1:9000";

  const gchar *transport_client_2 = "RTP/AVP;multicast;destination=233.252.0.2;"
      "ttl=1;port=9000-9001;mode=\"PLAY\"";
  const gchar *expected_transport_2 = transport_client_2;
  const gchar *addr_client_2 = "233.252.0.2:9000";

  /* configure the multicast socket to be bound to the requested multicast address instead of INADDR_ANY.
   * The clients request the same rtp/rtcp borts and having the socket that are bound to ANY would result
   * in bind() failure */
  gboolean allow_bind_mcast_address = TRUE;

  mcast_transport_two_clients (FALSE, transport_client_1,
      expected_transport_1, addr_client_1, transport_client_2,
      expected_transport_2, addr_client_2, allow_bind_mcast_address);
}

GST_END_TEST;

/* test if two multicast clients can choose the same multicast destination but different
 * ports
 * CASE: media is not shared */
GST_START_TEST
    (test_client_multicast_transport_specific_two_clients_same_destination) {
  const gchar *transport_client_1 = "RTP/AVP;multicast;destination=233.252.0.2;"
      "ttl=1;port=9002-9003;mode=\"PLAY\"";
  const gchar *expected_transport_1 = transport_client_1;
  const gchar *addr_client_1 = "233.252.0.2:9002";

  const gchar *transport_client_2 = "RTP/AVP;multicast;destination=233.252.0.2;"
      "ttl=1;port=9004-9005;mode=\"PLAY\"";
  const gchar *expected_transport_2 = transport_client_2;
  const gchar *addr_client_2 = "233.252.0.2:9004";

  mcast_transport_two_clients (FALSE, transport_client_1,
      expected_transport_1, addr_client_1, transport_client_2,
      expected_transport_2, addr_client_2, FALSE);
}

GST_END_TEST;
/* test if two multicast clients can choose the same transport settings.
 * CASE: media is shared */
GST_START_TEST
    (test_client_multicast_transport_specific_two_clients_shared_media_same_transport)
{

  const gchar *transport_client_1 = "RTP/AVP;multicast;destination=233.252.0.1;"
      "ttl=1;port=5000-5001;mode=\"PLAY\"";
  const gchar *expected_transport_1 = transport_client_1;
  const gchar *addr_client_1 = "233.252.0.1:5000";

  const gchar *transport_client_2 = transport_client_1;
  const gchar *expected_transport_2 = expected_transport_1;
  const gchar *addr_client_2 = addr_client_1;

  mcast_transport_two_clients (TRUE, transport_client_1,
      expected_transport_1, addr_client_1, transport_client_2,
      expected_transport_2, addr_client_2, FALSE);
}

GST_END_TEST;

/* test if two multicast clients get the same transport settings without
 * requesting specific transport.
 * CASE: media is shared */
GST_START_TEST (test_client_multicast_two_clients_shared_media)
{
  const gchar *transport_client_1 = "RTP/AVP;multicast;mode=\"PLAY\"";
  const gchar *expected_transport_1 =
      "RTP/AVP;multicast;destination=233.252.0.1;"
      "ttl=1;port=5000-5001;mode=\"PLAY\"";
  const gchar *addr_client_1 = "233.252.0.1:5000";

  const gchar *transport_client_2 = transport_client_1;
  const gchar *expected_transport_2 = expected_transport_1;
  const gchar *addr_client_2 = addr_client_1;

  mcast_transport_two_clients (TRUE, transport_client_1,
      expected_transport_1, addr_client_1, transport_client_2,
      expected_transport_2, addr_client_2, FALSE);
}

GST_END_TEST;

/* test if it's possible to play the shared media, after one of the clients
 * has terminated its session.
 */
GST_START_TEST (test_client_multicast_two_clients_shared_media_teardown_play)
{
  const gchar *transport_client_1 = "RTP/AVP;multicast;mode=\"PLAY\"";
  const gchar *expected_transport_1 =
      "RTP/AVP;multicast;destination=233.252.0.1;"
      "ttl=1;port=5000-5001;mode=\"PLAY\"";

  const gchar *transport_client_2 = transport_client_1;
  const gchar *expected_transport_2 = expected_transport_1;

  mcast_transport_two_clients_teardown_play (transport_client_1,
      expected_transport_1, transport_client_2, expected_transport_2, FALSE,
      TRUE);
}

GST_END_TEST;

/* test if it's possible to play the shared media, after one of the clients
 * has terminated its session.
 */
GST_START_TEST
    (test_client_multicast_two_clients_not_shared_media_teardown_play) {
  const gchar *transport_client_1 = "RTP/AVP;multicast;mode=\"PLAY\"";
  const gchar *expected_transport_1 =
      "RTP/AVP;multicast;destination=233.252.0.1;"
      "ttl=1;port=5000-5001;mode=\"PLAY\"";

  const gchar *transport_client_2 = transport_client_1;
  const gchar *expected_transport_2 =
      "RTP/AVP;multicast;destination=233.252.0.1;"
      "ttl=1;port=5002-5003;mode=\"PLAY\"";

  mcast_transport_two_clients_teardown_play (transport_client_1,
      expected_transport_1, transport_client_2, expected_transport_2, FALSE,
      FALSE);
}

GST_END_TEST;

/* test if two multicast clients get the different transport settings: the first client 
 * requests the specific transport configuration while the second client lets
 * the server select the multicast address and the ports.
 * CASE: media is shared */
GST_START_TEST
    (test_client_multicast_two_clients_first_specific_transport_shared_media) {
  const gchar *transport_client_1 = "RTP/AVP;multicast;destination=233.252.0.1;"
      "ttl=1;port=5000-5001;mode=\"PLAY\"";
  const gchar *expected_transport_1 = transport_client_1;
  const gchar *addr_client_1 = "233.252.0.1:5000";

  const gchar *transport_client_2 = "RTP/AVP;multicast;mode=\"PLAY\"";
  const gchar *expected_transport_2 = expected_transport_1;
  const gchar *addr_client_2 = addr_client_1;

  mcast_transport_two_clients (TRUE, transport_client_1,
      expected_transport_1, addr_client_1, transport_client_2,
      expected_transport_2, addr_client_2, FALSE);
}

GST_END_TEST;
/* test if two multicast clients get the different transport settings: the first client lets
 * the server select the multicast address and the ports while the second client requests 
 * the specific transport configuration.
 * CASE: media is shared */
GST_START_TEST
    (test_client_multicast_two_clients_second_specific_transport_shared_media) {
  const gchar *transport_client_1 = "RTP/AVP;multicast;mode=\"PLAY\"";
  const gchar *expected_transport_1 =
      "RTP/AVP;multicast;destination=233.252.0.1;"
      "ttl=1;port=5000-5001;mode=\"PLAY\"";
  const gchar *addr_client_1 = "233.252.0.1:5000";

  const gchar *transport_client_2 = "RTP/AVP;multicast;destination=233.252.0.2;"
      "ttl=2;port=5004-5005;mode=\"PLAY\"";
  const gchar *expected_transport_2 = transport_client_2;
  const gchar *addr_client_2 = "233.252.0.2:5004";

  mcast_transport_two_clients (TRUE, transport_client_1,
      expected_transport_1, addr_client_1, transport_client_2,
      expected_transport_2, addr_client_2, FALSE);
}

GST_END_TEST;

/* test if the maximum ttl multicast value is chosen by the server
 * CASE: the first client provides the highest ttl value */
GST_START_TEST (test_client_multicast_max_ttl_first_client)
{
  const gchar *transport_client_1 = "RTP/AVP;multicast;destination=233.252.0.1;"
      "ttl=3;port=5000-5001;mode=\"PLAY\"";
  const gchar *expected_transport_1 = transport_client_1;
  const gchar *addr_client_1 = "233.252.0.1:5000";

  const gchar *transport_client_2 = "RTP/AVP;multicast;destination=233.252.0.2;"
      "ttl=1;port=5002-5003;mode=\"PLAY\"";
  const gchar *expected_transport_2 =
      "RTP/AVP;multicast;destination=233.252.0.2;"
      "ttl=3;port=5002-5003;mode=\"PLAY\"";
  const gchar *addr_client_2 = "233.252.0.2:5002";

  mcast_transport_two_clients (TRUE, transport_client_1,
      expected_transport_1, addr_client_1, transport_client_2,
      expected_transport_2, addr_client_2, FALSE);
}

GST_END_TEST;

/* test if the maximum ttl multicast value is chosen by the server
 * CASE: the second client provides the highest ttl value */
GST_START_TEST (test_client_multicast_max_ttl_second_client)
{
  const gchar *transport_client_1 = "RTP/AVP;multicast;destination=233.252.0.1;"
      "ttl=2;port=5000-5001;mode=\"PLAY\"";
  const gchar *expected_transport_1 = transport_client_1;
  const gchar *addr_client_1 = "233.252.0.1:5000";

  const gchar *transport_client_2 = "RTP/AVP;multicast;destination=233.252.0.2;"
      "ttl=4;port=5002-5003;mode=\"PLAY\"";
  const gchar *expected_transport_2 = transport_client_2;
  const gchar *addr_client_2 = "233.252.0.2:5002";

  mcast_transport_two_clients (TRUE, transport_client_1,
      expected_transport_1, addr_client_1, transport_client_2,
      expected_transport_2, addr_client_2, FALSE);
}

GST_END_TEST;
GST_START_TEST (test_client_multicast_invalid_ttl)
{
  GstRTSPClient *client;
  GstRTSPMessage request = { 0, };
  gchar *str;
  GstRTSPSessionPool *session_pool;
  GstRTSPContext ctx = { NULL };

  client = setup_multicast_client (3);

  ctx.client = client;
  ctx.auth = gst_rtsp_auth_new ();
  ctx.token =
      gst_rtsp_token_new (GST_RTSP_TOKEN_TRANSPORT_CLIENT_SETTINGS,
      G_TYPE_BOOLEAN, TRUE, GST_RTSP_TOKEN_MEDIA_FACTORY_ROLE, G_TYPE_STRING,
      "user", NULL);
  gst_rtsp_context_push_current (&ctx);

  /* simple SETUP with an invalid ttl=0 */
  fail_unless (gst_rtsp_message_init_request (&request, GST_RTSP_SETUP,
          "rtsp://localhost/test/stream=0") == GST_RTSP_OK);
  str = g_strdup_printf ("%d", cseq);
  gst_rtsp_message_take_header (&request, GST_RTSP_HDR_CSEQ, str);
  gst_rtsp_message_add_header (&request, GST_RTSP_HDR_TRANSPORT,
      "RTP/AVP;multicast;destination=233.252.0.1;ttl=0;port=5000-5001;");

  gst_rtsp_client_set_send_func (client, test_setup_response_461, NULL, NULL);
  fail_unless (gst_rtsp_client_handle_message (client,
          &request) == GST_RTSP_OK);
  gst_rtsp_message_unset (&request);

  session_pool = gst_rtsp_client_get_session_pool (client);
  fail_unless (session_pool != NULL);
  fail_unless (gst_rtsp_session_pool_get_n_sessions (session_pool) == 0);
  g_object_unref (session_pool);

  teardown_client (client);
  g_object_unref (ctx.auth);
  gst_rtsp_token_unref (ctx.token);
  gst_rtsp_context_pop_current (&ctx);
}

GST_END_TEST;

static Suite *
rtspclient_suite (void)
{
  Suite *s = suite_create ("rtspclient");
  TCase *tc = tcase_create ("general");

  suite_add_tcase (s, tc);
  tcase_set_timeout (tc, 20);
  tcase_add_test (tc, test_require);
  tcase_add_test (tc, test_request);
  tcase_add_test (tc, test_options);
  tcase_add_test (tc, test_describe);
  tcase_add_test (tc, test_setup_tcp);
  tcase_add_test (tc, test_setup_tcp_two_streams_same_channels);
  tcase_add_test (tc, test_client_multicast_transport_404);
  tcase_add_test (tc, test_client_multicast_transport);
  tcase_add_test (tc, test_client_multicast_ignore_transport_specific);
  tcase_add_test (tc, test_client_multicast_transport_specific);
  tcase_add_test (tc, test_client_sdp_with_max_bitrate_tag);
  tcase_add_test (tc, test_client_sdp_with_bitrate_tag);
  tcase_add_test (tc, test_client_sdp_with_max_bitrate_and_bitrate_tags);
  tcase_add_test (tc, test_client_sdp_with_no_bitrate_tags);
  tcase_add_test (tc,
      test_client_multicast_transport_specific_two_clients_shared_media);
  tcase_add_test (tc, test_client_multicast_transport_specific_two_clients);
#ifndef G_OS_WIN32
  tcase_add_test (tc,
      test_client_multicast_transport_specific_two_clients_same_ports);
#else
  /* skip the test on windows as the test restricts the multicast sockets to multicast traffic only,
   * by specifying the multicast IP as the bind address and this currently doesn't work on Windows */
  tcase_skip_broken_test (tc,
      test_client_multicast_transport_specific_two_clients_same_ports);
#endif
  tcase_add_test (tc,
      test_client_multicast_transport_specific_two_clients_same_destination);
  tcase_add_test (tc,
      test_client_multicast_transport_specific_two_clients_shared_media_same_transport);
  tcase_add_test (tc, test_client_multicast_two_clients_shared_media);
  tcase_add_test (tc,
      test_client_multicast_two_clients_shared_media_teardown_play);
  tcase_add_test (tc,
      test_client_multicast_two_clients_not_shared_media_teardown_play);
  tcase_add_test (tc,
      test_client_multicast_two_clients_first_specific_transport_shared_media);
  tcase_add_test (tc,
      test_client_multicast_two_clients_second_specific_transport_shared_media);
  tcase_add_test (tc,
      test_client_multicast_transport_specific_no_address_in_pool);
  tcase_add_test (tc, test_client_multicast_max_ttl_first_client);
  tcase_add_test (tc, test_client_multicast_max_ttl_second_client);
  tcase_add_test (tc, test_client_multicast_invalid_ttl);

  return s;
}

GST_CHECK_MAIN (rtspclient);
