/* GStreamer unit test for GstRTSPServer
 * Copyright (C) 2012 Axis Communications <dev-gstreamer at axis dot com>
 *   @author David Svensson Fors <davidsf at axis dot com>
 * Copyright (C) 2015 Centricular Ltd
 *   @author Tim-Philipp Müller <tim@centricular.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 <gst/sdp/gstsdpmessage.h>
#include <gst/rtp/gstrtpbuffer.h>
#include <gst/rtp/gstrtcpbuffer.h>

#include <stdio.h>
#include <netinet/in.h>

#include "rtsp-server.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"

#define TEST_MOUNT_POINT  "/test"
#define TEST_PROTO        "RTP/AVP"
#define TEST_PROTO_TCP    "RTP/AVP/TCP"
#define TEST_ENCODING     "X-GST"
#define TEST_CLOCK_RATE   "90000"

/* tested rtsp server */
static GstRTSPServer *server = NULL;

/* tcp port that the test server listens for rtsp requests on */
static gint test_port = 0;

/* id of the server's source within the GMainContext */
static guint source_id;

/* iterate the default main loop until there are no events to dispatch */
static void
iterate (void)
{
  while (g_main_context_iteration (NULL, FALSE)) {
    GST_DEBUG ("iteration");
  }
}

static void
get_client_ports_full (GstRTSPRange * range, GSocket ** rtp_socket,
    GSocket ** rtcp_socket)
{
  GSocket *rtp = NULL;
  GSocket *rtcp = NULL;
  gint rtp_port = 0;
  gint rtcp_port;
  GInetAddress *anyaddr = g_inet_address_new_any (G_SOCKET_FAMILY_IPV4);
  GSocketAddress *sockaddr;
  gboolean bound;

  for (;;) {
    if (rtp_port != 0)
      rtp_port += 2;

    rtp = g_socket_new (G_SOCKET_FAMILY_IPV4, G_SOCKET_TYPE_DATAGRAM,
        G_SOCKET_PROTOCOL_UDP, NULL);
    fail_unless (rtp != NULL);

    sockaddr = g_inet_socket_address_new (anyaddr, rtp_port);
    fail_unless (sockaddr != NULL);
    bound = g_socket_bind (rtp, sockaddr, FALSE, NULL);
    g_object_unref (sockaddr);
    if (!bound) {
      g_object_unref (rtp);
      continue;
    }

    sockaddr = g_socket_get_local_address (rtp, NULL);
    fail_unless (sockaddr != NULL && G_IS_INET_SOCKET_ADDRESS (sockaddr));
    rtp_port =
        g_inet_socket_address_get_port (G_INET_SOCKET_ADDRESS (sockaddr));
    g_object_unref (sockaddr);

    if (rtp_port % 2 != 0) {
      rtp_port += 1;
      g_object_unref (rtp);
      continue;
    }

    rtcp_port = rtp_port + 1;

    rtcp = g_socket_new (G_SOCKET_FAMILY_IPV4, G_SOCKET_TYPE_DATAGRAM,
        G_SOCKET_PROTOCOL_UDP, NULL);
    fail_unless (rtcp != NULL);

    sockaddr = g_inet_socket_address_new (anyaddr, rtcp_port);
    fail_unless (sockaddr != NULL);
    bound = g_socket_bind (rtcp, sockaddr, FALSE, NULL);
    g_object_unref (sockaddr);
    if (!bound) {
      g_object_unref (rtp);
      g_object_unref (rtcp);
      continue;
    }

    sockaddr = g_socket_get_local_address (rtcp, NULL);
    fail_unless (sockaddr != NULL && G_IS_INET_SOCKET_ADDRESS (sockaddr));
    fail_unless (rtcp_port ==
        g_inet_socket_address_get_port (G_INET_SOCKET_ADDRESS (sockaddr)));
    g_object_unref (sockaddr);

    break;
  }

  range->min = rtp_port;
  range->max = rtcp_port;
  if (rtp_socket)
    *rtp_socket = rtp;
  else
    g_object_unref (rtp);
  if (rtcp_socket)
    *rtcp_socket = rtcp;
  else
    g_object_unref (rtcp);
  GST_DEBUG ("client_port=%d-%d", range->min, range->max);
  g_object_unref (anyaddr);
}

/* get a free rtp/rtcp client port pair */
static void
get_client_ports (GstRTSPRange * range)
{
  get_client_ports_full (range, NULL, NULL);
}

/* start the tested rtsp server */
static void
start_server (void)
{
  GstRTSPMountPoints *mounts;
  gchar *service;
  GstRTSPMediaFactory *factory;

  mounts = gst_rtsp_server_get_mount_points (server);

  factory = gst_rtsp_media_factory_new ();

  gst_rtsp_media_factory_set_launch (factory,
      "( " VIDEO_PIPELINE "  " AUDIO_PIPELINE " )");
  gst_rtsp_mount_points_add_factory (mounts, TEST_MOUNT_POINT, factory);
  g_object_unref (mounts);

  /* set port to any */
  gst_rtsp_server_set_service (server, "0");

  /* attach to default main context */
  source_id = gst_rtsp_server_attach (server, NULL);
  fail_if (source_id == 0);

  /* get port */
  service = gst_rtsp_server_get_service (server);
  test_port = atoi (service);
  fail_unless (test_port != 0);
  g_free (service);

  GST_DEBUG ("rtsp server listening on port %d", test_port);
}

/* start the testing rtsp server for RECORD mode */
static GstRTSPMediaFactory *
start_record_server (const gchar * launch_line)
{
  GstRTSPMediaFactory *factory;
  GstRTSPMountPoints *mounts;
  gchar *service;

  mounts = gst_rtsp_server_get_mount_points (server);

  factory = gst_rtsp_media_factory_new ();

  gst_rtsp_media_factory_set_transport_mode (factory,
      GST_RTSP_TRANSPORT_MODE_RECORD);
  gst_rtsp_media_factory_set_launch (factory, launch_line);
  gst_rtsp_mount_points_add_factory (mounts, TEST_MOUNT_POINT, factory);
  g_object_unref (mounts);

  /* set port to any */
  gst_rtsp_server_set_service (server, "0");

  /* attach to default main context */
  source_id = gst_rtsp_server_attach (server, NULL);
  fail_if (source_id == 0);

  /* get port */
  service = gst_rtsp_server_get_service (server);
  test_port = atoi (service);
  fail_unless (test_port != 0);
  g_free (service);

  GST_DEBUG ("rtsp server listening on port %d", test_port);
  return factory;
}

/* stop the tested rtsp server */
static void
stop_server (void)
{
  g_source_remove (source_id);
  source_id = 0;

  GST_DEBUG ("rtsp server stopped");
}

/* create an rtsp connection to the server on test_port */
static GstRTSPConnection *
connect_to_server (gint port, const gchar * mount_point)
{
  GstRTSPConnection *conn = NULL;
  gchar *address;
  gchar *uri_string;
  GstRTSPUrl *url = NULL;

  address = gst_rtsp_server_get_address (server);
  uri_string = g_strdup_printf ("rtsp://%s:%d%s", address, port, mount_point);
  g_free (address);
  fail_unless (gst_rtsp_url_parse (uri_string, &url) == GST_RTSP_OK);
  g_free (uri_string);

  fail_unless (gst_rtsp_connection_create (url, &conn) == GST_RTSP_OK);
  gst_rtsp_url_free (url);

  fail_unless (gst_rtsp_connection_connect (conn, NULL) == GST_RTSP_OK);

  return conn;
}

/* create an rtsp request */
static GstRTSPMessage *
create_request (GstRTSPConnection * conn, GstRTSPMethod method,
    const gchar * control)
{
  GstRTSPMessage *request = NULL;
  gchar *base_uri;
  gchar *full_uri;

  base_uri = gst_rtsp_url_get_request_uri (gst_rtsp_connection_get_url (conn));
  full_uri = g_strdup_printf ("%s/%s", base_uri, control ? control : "");
  g_free (base_uri);
  if (gst_rtsp_message_new_request (&request, method, full_uri) != GST_RTSP_OK) {
    GST_DEBUG ("failed to create request object");
    g_free (full_uri);
    return NULL;
  }
  g_free (full_uri);
  return request;
}

/* send an rtsp request */
static gboolean
send_request (GstRTSPConnection * conn, GstRTSPMessage * request)
{
  if (gst_rtsp_connection_send (conn, request, NULL) != GST_RTSP_OK) {
    GST_DEBUG ("failed to send request");
    return FALSE;
  }
  return TRUE;
}

/* read rtsp response. response must be freed by the caller */
static GstRTSPMessage *
read_response (GstRTSPConnection * conn)
{
  GstRTSPMessage *response = NULL;

  if (gst_rtsp_message_new (&response) != GST_RTSP_OK) {
    GST_DEBUG ("failed to create response object");
    return NULL;
  }
  if (gst_rtsp_connection_receive (conn, response, NULL) != GST_RTSP_OK) {
    GST_DEBUG ("failed to read response");
    gst_rtsp_message_free (response);
    return NULL;
  }
  fail_unless (gst_rtsp_message_get_type (response) ==
      GST_RTSP_MESSAGE_RESPONSE);
  return response;
}

/* send an rtsp request and receive response. gchar** parameters are out
 * parameters that have to be freed by the caller */
static GstRTSPStatusCode
do_request_full (GstRTSPConnection * conn, GstRTSPMethod method,
    const gchar * control, const gchar * session_in, const gchar * transport_in,
    const gchar * range_in, const gchar * require_in,
    gchar ** content_type, gchar ** content_base, gchar ** body,
    gchar ** session_out, gchar ** transport_out, gchar ** range_out,
    gchar ** unsupported_out)
{
  GstRTSPMessage *request;
  GstRTSPMessage *response;
  GstRTSPStatusCode code;
  gchar *value;

  /* create request */
  request = create_request (conn, method, control);

  /* add headers */
  if (session_in) {
    gst_rtsp_message_add_header (request, GST_RTSP_HDR_SESSION, session_in);
  }
  if (transport_in) {
    gst_rtsp_message_add_header (request, GST_RTSP_HDR_TRANSPORT, transport_in);
  }
  if (range_in) {
    gst_rtsp_message_add_header (request, GST_RTSP_HDR_RANGE, range_in);
  }
  if (require_in) {
    gst_rtsp_message_add_header (request, GST_RTSP_HDR_REQUIRE, require_in);
  }

  /* send request */
  fail_unless (send_request (conn, request));
  gst_rtsp_message_free (request);

  iterate ();

  /* read response */
  response = read_response (conn);

  /* check status line */
  gst_rtsp_message_parse_response (response, &code, NULL, NULL);
  if (code != GST_RTSP_STS_OK) {
    if (unsupported_out != NULL && code == GST_RTSP_STS_OPTION_NOT_SUPPORTED) {
      gst_rtsp_message_get_header (response, GST_RTSP_HDR_UNSUPPORTED,
          &value, 0);
      *unsupported_out = g_strdup (value);
    }
    gst_rtsp_message_free (response);
    return code;
  }

  /* get information from response */
  if (content_type) {
    gst_rtsp_message_get_header (response, GST_RTSP_HDR_CONTENT_TYPE,
        &value, 0);
    *content_type = g_strdup (value);
  }
  if (content_base) {
    gst_rtsp_message_get_header (response, GST_RTSP_HDR_CONTENT_BASE,
        &value, 0);
    *content_base = g_strdup (value);
  }
  if (body) {
    *body = g_malloc (response->body_size + 1);
    strncpy (*body, (gchar *) response->body, response->body_size);
  }
  if (session_out) {
    gst_rtsp_message_get_header (response, GST_RTSP_HDR_SESSION, &value, 0);

    value = g_strdup (value);

    /* Remove the timeout */
    if (value) {
      char *pos = strchr (value, ';');
      if (pos)
        *pos = 0;
    }
    if (session_in) {
      /* check that we got the same session back */
      fail_unless (!g_strcmp0 (value, session_in));
    }
    *session_out = value;
  }
  if (transport_out) {
    gst_rtsp_message_get_header (response, GST_RTSP_HDR_TRANSPORT, &value, 0);
    *transport_out = g_strdup (value);
  }
  if (range_out) {
    gst_rtsp_message_get_header (response, GST_RTSP_HDR_RANGE, &value, 0);
    *range_out = g_strdup (value);
  }

  gst_rtsp_message_free (response);
  return code;
}

/* send an rtsp request and receive response. gchar** parameters are out
 * parameters that have to be freed by the caller */
static GstRTSPStatusCode
do_request (GstRTSPConnection * conn, GstRTSPMethod method,
    const gchar * control, const gchar * session_in,
    const gchar * transport_in, const gchar * range_in,
    gchar ** content_type, gchar ** content_base, gchar ** body,
    gchar ** session_out, gchar ** transport_out, gchar ** range_out)
{
  return do_request_full (conn, method, control, session_in, transport_in,
      range_in, NULL, content_type, content_base, body, session_out,
      transport_out, range_out, NULL);
}

/* send an rtsp request with a method and a session, and receive response */
static GstRTSPStatusCode
do_simple_request (GstRTSPConnection * conn, GstRTSPMethod method,
    const gchar * session)
{
  return do_request (conn, method, NULL, session, NULL, NULL, NULL,
      NULL, NULL, NULL, NULL, NULL);
}

/* send a DESCRIBE request and receive response. returns a received
 * GstSDPMessage that must be freed by the caller */
static GstSDPMessage *
do_describe (GstRTSPConnection * conn, const gchar * mount_point)
{
  GstSDPMessage *sdp_message;
  gchar *content_type;
  gchar *content_base;
  gchar *body;
  gchar *address;
  gchar *expected_content_base;

  /* send DESCRIBE request */
  fail_unless (do_request (conn, GST_RTSP_DESCRIBE, NULL, NULL, NULL, NULL,
          &content_type, &content_base, &body, NULL, NULL, NULL) ==
      GST_RTSP_STS_OK);

  /* check response values */
  fail_unless (!g_strcmp0 (content_type, "application/sdp"));
  address = gst_rtsp_server_get_address (server);
  expected_content_base =
      g_strdup_printf ("rtsp://%s:%d%s/", address, test_port, mount_point);
  fail_unless (!g_strcmp0 (content_base, expected_content_base));

  /* create sdp message */
  fail_unless (gst_sdp_message_new (&sdp_message) == GST_SDP_OK);
  fail_unless (gst_sdp_message_parse_buffer ((guint8 *) body,
          strlen (body), sdp_message) == GST_SDP_OK);

  /* clean up */
  g_free (content_type);
  g_free (content_base);
  g_free (body);
  g_free (address);
  g_free (expected_content_base);

  return sdp_message;
}

/* send a SETUP request and receive response. if *session is not NULL,
 * it is used in the request. otherwise, *session is set to a returned
 * session string that must be freed by the caller. the returned
 * transport must be freed by the caller. */
static GstRTSPStatusCode
do_setup_full (GstRTSPConnection * conn, const gchar * control,
    gboolean use_tcp_transport, const GstRTSPRange * client_ports,
    const gchar * require, gchar ** session, GstRTSPTransport ** transport,
    gchar ** unsupported)
{
  GstRTSPStatusCode code;
  gchar *session_in = NULL;
  gchar *transport_string_in = NULL;
  gchar **session_out = NULL;
  gchar *transport_string_out = NULL;

  /* prepare and send SETUP request */
  if (session) {
    if (*session) {
      session_in = *session;
    } else {
      session_out = session;
    }
  }

  if (use_tcp_transport) {
    transport_string_in = g_strdup_printf (TEST_PROTO_TCP ";unicast");
  } else {
    transport_string_in =
        g_strdup_printf (TEST_PROTO ";unicast;client_port=%d-%d",
        client_ports->min, client_ports->max);
  }
  code =
      do_request_full (conn, GST_RTSP_SETUP, control, session_in,
      transport_string_in, NULL, require, NULL, NULL, NULL, session_out,
      &transport_string_out, NULL, unsupported);
  g_free (transport_string_in);

  if (transport_string_out) {
    /* create transport */
    fail_unless (gst_rtsp_transport_new (transport) == GST_RTSP_OK);
    fail_unless (gst_rtsp_transport_parse (transport_string_out,
            *transport) == GST_RTSP_OK);
    g_free (transport_string_out);
  }
  GST_INFO ("code=%d", code);
  return code;
}

/* send a SETUP request and receive response. if *session is not NULL,
 * it is used in the request. otherwise, *session is set to a returned
 * session string that must be freed by the caller. the returned
 * transport must be freed by the caller. */
static GstRTSPStatusCode
do_setup (GstRTSPConnection * conn, const gchar * control,
    const GstRTSPRange * client_ports, gchar ** session,
    GstRTSPTransport ** transport)
{
  return do_setup_full (conn, control, FALSE, client_ports, NULL, session,
      transport, NULL);
}

/* send a SETUP request and receive response. if *session is not NULL,
 * it is used in the request. otherwise, *session is set to a returned
 * session string that must be freed by the caller. the returned
 * transport must be freed by the caller. */
static GstRTSPStatusCode
do_setup_tcp (GstRTSPConnection * conn, const gchar * control,
    gchar ** session, GstRTSPTransport ** transport)
{
  return do_setup_full (conn, control, TRUE, NULL, NULL, session, transport,
      NULL);
}

/* fixture setup function */
static void
setup (void)
{
  server = gst_rtsp_server_new ();
}

/* fixture clean-up function */
static void
teardown (void)
{
  if (server) {
    g_object_unref (server);
    server = NULL;
  }
  test_port = 0;
}

GST_START_TEST (test_connect)
{
  GstRTSPConnection *conn;

  start_server ();

  /* connect to server */
  conn = connect_to_server (test_port, TEST_MOUNT_POINT);

  /* clean up */
  gst_rtsp_connection_free (conn);
  stop_server ();

  /* iterate so the clean-up can finish */
  iterate ();
}

GST_END_TEST;

GST_START_TEST (test_describe)
{
  GstRTSPConnection *conn;
  GstSDPMessage *sdp_message = NULL;
  const GstSDPMedia *sdp_media;
  gint32 format;
  gchar *expected_rtpmap;
  const gchar *rtpmap;
  const gchar *control_video;
  const gchar *control_audio;

  start_server ();

  conn = connect_to_server (test_port, TEST_MOUNT_POINT);

  /* send DESCRIBE request */
  sdp_message = do_describe (conn, TEST_MOUNT_POINT);

  fail_unless (gst_sdp_message_medias_len (sdp_message) == 2);

  /* check video sdp */
  sdp_media = gst_sdp_message_get_media (sdp_message, 0);
  fail_unless (!g_strcmp0 (gst_sdp_media_get_proto (sdp_media), TEST_PROTO));
  fail_unless (gst_sdp_media_formats_len (sdp_media) == 1);
  sscanf (gst_sdp_media_get_format (sdp_media, 0), "%" G_GINT32_FORMAT,
      &format);
  expected_rtpmap =
      g_strdup_printf ("%d " TEST_ENCODING "/" TEST_CLOCK_RATE, format);
  rtpmap = gst_sdp_media_get_attribute_val (sdp_media, "rtpmap");
  fail_unless (!g_strcmp0 (rtpmap, expected_rtpmap));
  g_free (expected_rtpmap);
  control_video = gst_sdp_media_get_attribute_val (sdp_media, "control");
  fail_unless (!g_strcmp0 (control_video, "stream=0"));

  /* check audio sdp */
  sdp_media = gst_sdp_message_get_media (sdp_message, 1);
  fail_unless (!g_strcmp0 (gst_sdp_media_get_proto (sdp_media), TEST_PROTO));
  fail_unless (gst_sdp_media_formats_len (sdp_media) == 1);
  sscanf (gst_sdp_media_get_format (sdp_media, 0), "%" G_GINT32_FORMAT,
      &format);
  expected_rtpmap =
      g_strdup_printf ("%d " TEST_ENCODING "/" TEST_CLOCK_RATE, format);
  rtpmap = gst_sdp_media_get_attribute_val (sdp_media, "rtpmap");
  fail_unless (!g_strcmp0 (rtpmap, expected_rtpmap));
  g_free (expected_rtpmap);
  control_audio = gst_sdp_media_get_attribute_val (sdp_media, "control");
  fail_unless (!g_strcmp0 (control_audio, "stream=1"));

  /* clean up and iterate so the clean-up can finish */
  gst_sdp_message_free (sdp_message);
  gst_rtsp_connection_free (conn);
  stop_server ();
  iterate ();
}

GST_END_TEST;

GST_START_TEST (test_describe_record_media)
{
  GstRTSPConnection *conn;

  start_record_server ("( fakesink name=depay0 )");

  conn = connect_to_server (test_port, TEST_MOUNT_POINT);

  /* send DESCRIBE request */
  fail_unless_equals_int (do_request (conn, GST_RTSP_DESCRIBE, NULL, NULL, NULL,
          NULL, NULL, NULL, NULL, NULL, NULL, NULL),
      GST_RTSP_STS_METHOD_NOT_ALLOWED);

  /* clean up and iterate so the clean-up can finish */
  gst_rtsp_connection_free (conn);
  stop_server ();
  iterate ();
}

GST_END_TEST;

GST_START_TEST (test_describe_non_existing_mount_point)
{
  GstRTSPConnection *conn;

  start_server ();

  /* send DESCRIBE request for a non-existing mount point
   * and check that we get a 404 Not Found */
  conn = connect_to_server (test_port, "/non-existing");
  fail_unless (do_simple_request (conn, GST_RTSP_DESCRIBE, NULL)
      == GST_RTSP_STS_NOT_FOUND);

  /* clean up and iterate so the clean-up can finish */
  gst_rtsp_connection_free (conn);
  stop_server ();
  iterate ();
}

GST_END_TEST;

GST_START_TEST (test_setup)
{
  GstRTSPConnection *conn;
  GstSDPMessage *sdp_message = NULL;
  const GstSDPMedia *sdp_media;
  const gchar *video_control;
  const gchar *audio_control;
  GstRTSPRange client_ports;
  gchar *session = NULL;
  GstRTSPTransport *video_transport = NULL;
  GstRTSPTransport *audio_transport = NULL;

  start_server ();

  conn = connect_to_server (test_port, TEST_MOUNT_POINT);

  sdp_message = do_describe (conn, TEST_MOUNT_POINT);

  /* get control strings from DESCRIBE response */
  fail_unless (gst_sdp_message_medias_len (sdp_message) == 2);
  sdp_media = gst_sdp_message_get_media (sdp_message, 0);
  video_control = gst_sdp_media_get_attribute_val (sdp_media, "control");
  sdp_media = gst_sdp_message_get_media (sdp_message, 1);
  audio_control = gst_sdp_media_get_attribute_val (sdp_media, "control");

  get_client_ports (&client_ports);

  /* send SETUP request for video */
  fail_unless (do_setup (conn, video_control, &client_ports, &session,
          &video_transport) == GST_RTSP_STS_OK);
  GST_DEBUG ("set up video %s, got session '%s'", video_control, session);

  /* check response from SETUP */
  fail_unless (video_transport->trans == GST_RTSP_TRANS_RTP);
  fail_unless (video_transport->profile == GST_RTSP_PROFILE_AVP);
  fail_unless (video_transport->lower_transport == GST_RTSP_LOWER_TRANS_UDP);
  fail_unless (video_transport->mode_play);
  gst_rtsp_transport_free (video_transport);

  /* send SETUP request for audio */
  fail_unless (do_setup (conn, audio_control, &client_ports, &session,
          &audio_transport) == GST_RTSP_STS_OK);
  GST_DEBUG ("set up audio %s with session '%s'", audio_control, session);

  /* check response from SETUP */
  fail_unless (audio_transport->trans == GST_RTSP_TRANS_RTP);
  fail_unless (audio_transport->profile == GST_RTSP_PROFILE_AVP);
  fail_unless (audio_transport->lower_transport == GST_RTSP_LOWER_TRANS_UDP);
  fail_unless (audio_transport->mode_play);
  gst_rtsp_transport_free (audio_transport);

  /* send TEARDOWN request and check that we get 200 OK */
  fail_unless (do_simple_request (conn, GST_RTSP_TEARDOWN,
          session) == GST_RTSP_STS_OK);

  /* clean up and iterate so the clean-up can finish */
  g_free (session);
  gst_sdp_message_free (sdp_message);
  gst_rtsp_connection_free (conn);
  stop_server ();
  iterate ();
}

GST_END_TEST;

GST_START_TEST (test_setup_tcp)
{
  GstRTSPConnection *conn;
  GstSDPMessage *sdp_message = NULL;
  const GstSDPMedia *sdp_media;
  const gchar *video_control;
  const gchar *audio_control;
  gchar *session = NULL;
  GstRTSPTransport *video_transport = NULL;
  GstRTSPTransport *audio_transport = NULL;

  start_server ();

  conn = connect_to_server (test_port, TEST_MOUNT_POINT);

  sdp_message = do_describe (conn, TEST_MOUNT_POINT);

  /* get control strings from DESCRIBE response */
  fail_unless (gst_sdp_message_medias_len (sdp_message) == 2);
  sdp_media = gst_sdp_message_get_media (sdp_message, 0);
  video_control = gst_sdp_media_get_attribute_val (sdp_media, "control");
  sdp_media = gst_sdp_message_get_media (sdp_message, 1);
  audio_control = gst_sdp_media_get_attribute_val (sdp_media, "control");

  /* send SETUP request for video */
  fail_unless (do_setup_tcp (conn, video_control, &session,
          &video_transport) == GST_RTSP_STS_OK);
  GST_DEBUG ("set up video %s, got session '%s'", video_control, session);

  /* check response from SETUP */
  fail_unless (video_transport->trans == GST_RTSP_TRANS_RTP);
  fail_unless (video_transport->profile == GST_RTSP_PROFILE_AVP);
  fail_unless (video_transport->lower_transport == GST_RTSP_LOWER_TRANS_TCP);
  fail_unless (video_transport->mode_play);
  gst_rtsp_transport_free (video_transport);

  /* send SETUP request for audio */
  fail_unless (do_setup_tcp (conn, audio_control, &session,
          &audio_transport) == GST_RTSP_STS_OK);
  GST_DEBUG ("set up audio %s with session '%s'", audio_control, session);

  /* check response from SETUP */
  fail_unless (audio_transport->trans == GST_RTSP_TRANS_RTP);
  fail_unless (audio_transport->profile == GST_RTSP_PROFILE_AVP);
  fail_unless (audio_transport->lower_transport == GST_RTSP_LOWER_TRANS_TCP);
  fail_unless (audio_transport->mode_play);
  gst_rtsp_transport_free (audio_transport);

  /* send TEARDOWN request and check that we get 200 OK */
  fail_unless (do_simple_request (conn, GST_RTSP_TEARDOWN,
          session) == GST_RTSP_STS_OK);

  /* clean up and iterate so the clean-up can finish */
  g_free (session);
  gst_sdp_message_free (sdp_message);
  gst_rtsp_connection_free (conn);
  stop_server ();
  iterate ();
}

GST_END_TEST;

GST_START_TEST (test_setup_twice)
{
  GstRTSPConnection *conn;
  GstSDPMessage *sdp_message;
  const GstSDPMedia *sdp_media;
  const gchar *video_control;
  GstRTSPRange client_ports;
  GstRTSPTransport *video_transport = NULL;
  gchar *session1 = NULL;
  gchar *session2 = NULL;

  start_server ();

  conn = connect_to_server (test_port, TEST_MOUNT_POINT);

  /* we wan't more than one session for this connection */
  gst_rtsp_connection_set_remember_session_id (conn, FALSE);

  sdp_message = do_describe (conn, TEST_MOUNT_POINT);

  /* get the control url */
  fail_unless (gst_sdp_message_medias_len (sdp_message) == 2);
  sdp_media = gst_sdp_message_get_media (sdp_message, 0);
  video_control = gst_sdp_media_get_attribute_val (sdp_media, "control");

  get_client_ports (&client_ports);

  /* send SETUP request for one session */
  fail_unless (do_setup (conn, video_control, &client_ports, &session1,
          &video_transport) == GST_RTSP_STS_OK);
  GST_DEBUG ("set up video %s, got session '%s'", video_control, session1);

  /* check response from SETUP */
  fail_unless (video_transport->trans == GST_RTSP_TRANS_RTP);
  fail_unless (video_transport->profile == GST_RTSP_PROFILE_AVP);
  fail_unless (video_transport->lower_transport == GST_RTSP_LOWER_TRANS_UDP);
  fail_unless (video_transport->mode_play);
  gst_rtsp_transport_free (video_transport);

  /* send SETUP request for another session */
  fail_unless (do_setup (conn, video_control, &client_ports, &session2,
          &video_transport) == GST_RTSP_STS_OK);
  GST_DEBUG ("set up video %s, got session '%s'", video_control, session2);

  /* check response from SETUP */
  fail_unless (video_transport->trans == GST_RTSP_TRANS_RTP);
  fail_unless (video_transport->profile == GST_RTSP_PROFILE_AVP);
  fail_unless (video_transport->lower_transport == GST_RTSP_LOWER_TRANS_UDP);
  fail_unless (video_transport->mode_play);
  gst_rtsp_transport_free (video_transport);

  /* session can not be the same */
  fail_unless (strcmp (session1, session2));

  /* send TEARDOWN request for the first session */
  fail_unless (do_simple_request (conn, GST_RTSP_TEARDOWN,
          session1) == GST_RTSP_STS_OK);

  /* send TEARDOWN request for the second session */
  fail_unless (do_simple_request (conn, GST_RTSP_TEARDOWN,
          session2) == GST_RTSP_STS_OK);

  g_free (session1);
  g_free (session2);
  gst_sdp_message_free (sdp_message);
  gst_rtsp_connection_free (conn);
  stop_server ();
  iterate ();
}

GST_END_TEST;

GST_START_TEST (test_setup_with_require_header)
{
  GstRTSPConnection *conn;
  GstSDPMessage *sdp_message = NULL;
  const GstSDPMedia *sdp_media;
  const gchar *video_control;
  GstRTSPRange client_ports;
  gchar *session = NULL;
  gchar *unsupported = NULL;
  GstRTSPTransport *video_transport = NULL;

  start_server ();

  conn = connect_to_server (test_port, TEST_MOUNT_POINT);

  sdp_message = do_describe (conn, TEST_MOUNT_POINT);

  /* get control strings from DESCRIBE response */
  fail_unless (gst_sdp_message_medias_len (sdp_message) == 2);
  sdp_media = gst_sdp_message_get_media (sdp_message, 0);
  video_control = gst_sdp_media_get_attribute_val (sdp_media, "control");

  get_client_ports (&client_ports);

  /* send SETUP request for video, with single Require header */
  fail_unless_equals_int (do_setup_full (conn, video_control, FALSE,
          &client_ports, "funky-feature", &session, &video_transport,
          &unsupported), GST_RTSP_STS_OPTION_NOT_SUPPORTED);
  fail_unless_equals_string (unsupported, "funky-feature");
  g_free (unsupported);
  unsupported = NULL;

  /* send SETUP request for video, with multiple Require headers */
  fail_unless_equals_int (do_setup_full (conn, video_control, FALSE,
          &client_ports, "funky-feature, foo-bar, superburst", &session,
          &video_transport, &unsupported), GST_RTSP_STS_OPTION_NOT_SUPPORTED);
  fail_unless_equals_string (unsupported, "funky-feature, foo-bar, superburst");
  g_free (unsupported);
  unsupported = NULL;

  /* ok, just do a normal setup then (make sure that still works) */
  fail_unless_equals_int (do_setup (conn, video_control, &client_ports,
          &session, &video_transport), GST_RTSP_STS_OK);

  GST_DEBUG ("set up video %s, got session '%s'", video_control, session);

  /* check response from SETUP */
  fail_unless (video_transport->trans == GST_RTSP_TRANS_RTP);
  fail_unless (video_transport->profile == GST_RTSP_PROFILE_AVP);
  fail_unless (video_transport->lower_transport == GST_RTSP_LOWER_TRANS_UDP);
  fail_unless (video_transport->mode_play);
  gst_rtsp_transport_free (video_transport);

  /* send TEARDOWN request and check that we get 200 OK */
  fail_unless (do_simple_request (conn, GST_RTSP_TEARDOWN,
          session) == GST_RTSP_STS_OK);

  /* clean up and iterate so the clean-up can finish */
  g_free (session);
  gst_sdp_message_free (sdp_message);
  gst_rtsp_connection_free (conn);
  stop_server ();
  iterate ();
}

GST_END_TEST;

GST_START_TEST (test_setup_non_existing_stream)
{
  GstRTSPConnection *conn;
  GstRTSPRange client_ports;

  start_server ();

  conn = connect_to_server (test_port, TEST_MOUNT_POINT);

  get_client_ports (&client_ports);

  /* send SETUP request with a non-existing stream and check that we get a
   * 404 Not Found */
  fail_unless (do_setup (conn, "stream=7", &client_ports, NULL,
          NULL) == GST_RTSP_STS_NOT_FOUND);

  /* clean up and iterate so the clean-up can finish */
  gst_rtsp_connection_free (conn);
  stop_server ();
  iterate ();
}

GST_END_TEST;

static void
receive_rtp (GSocket * socket, GSocketAddress ** addr)
{
  GstBuffer *buffer = gst_buffer_new_allocate (NULL, 65536, NULL);

  for (;;) {
    gssize bytes;
    GstMapInfo map = GST_MAP_INFO_INIT;
    GstRTPBuffer rtpbuffer = GST_RTP_BUFFER_INIT;

    gst_buffer_map (buffer, &map, GST_MAP_WRITE);
    bytes = g_socket_receive_from (socket, addr, (gchar *) map.data,
        map.maxsize, NULL, NULL);
    fail_unless (bytes > 0);
    gst_buffer_unmap (buffer, &map);
    gst_buffer_set_size (buffer, bytes);

    if (gst_rtp_buffer_map (buffer, GST_MAP_READ, &rtpbuffer)) {
      gst_rtp_buffer_unmap (&rtpbuffer);
      break;
    }

    if (addr)
      g_clear_object (addr);
  }

  gst_buffer_unref (buffer);
}

static void
receive_rtcp (GSocket * socket, GSocketAddress ** addr, GstRTCPType type)
{
  GstBuffer *buffer = gst_buffer_new_allocate (NULL, 65536, NULL);

  for (;;) {
    gssize bytes;
    GstMapInfo map = GST_MAP_INFO_INIT;

    gst_buffer_map (buffer, &map, GST_MAP_WRITE);
    bytes = g_socket_receive_from (socket, addr, (gchar *) map.data,
        map.maxsize, NULL, NULL);
    fail_unless (bytes > 0);
    gst_buffer_unmap (buffer, &map);
    gst_buffer_set_size (buffer, bytes);

    if (gst_rtcp_buffer_validate (buffer)) {
      GstRTCPBuffer rtcpbuffer = GST_RTCP_BUFFER_INIT;
      GstRTCPPacket packet;

      if (type) {
        fail_unless (gst_rtcp_buffer_map (buffer, GST_MAP_READ, &rtcpbuffer));
        fail_unless (gst_rtcp_buffer_get_first_packet (&rtcpbuffer, &packet));
        do {
          if (gst_rtcp_packet_get_type (&packet) == type) {
            gst_rtcp_buffer_unmap (&rtcpbuffer);
            goto done;
          }
        } while (gst_rtcp_packet_move_to_next (&packet));
        gst_rtcp_buffer_unmap (&rtcpbuffer);
      } else {
        break;
      }
    }

    if (addr)
      g_clear_object (addr);
  }

done:

  gst_buffer_unref (buffer);
}

static void
do_test_play (const gchar * range)
{
  GstRTSPConnection *conn;
  GstSDPMessage *sdp_message = NULL;
  const GstSDPMedia *sdp_media;
  const gchar *video_control;
  const gchar *audio_control;
  GstRTSPRange client_port;
  gchar *session = NULL;
  GstRTSPTransport *video_transport = NULL;
  GstRTSPTransport *audio_transport = NULL;
  GSocket *rtp_socket, *rtcp_socket;
  gchar *range_out = NULL;

  conn = connect_to_server (test_port, TEST_MOUNT_POINT);

  sdp_message = do_describe (conn, TEST_MOUNT_POINT);

  /* get control strings from DESCRIBE response */
  fail_unless (gst_sdp_message_medias_len (sdp_message) == 2);
  sdp_media = gst_sdp_message_get_media (sdp_message, 0);
  video_control = gst_sdp_media_get_attribute_val (sdp_media, "control");
  sdp_media = gst_sdp_message_get_media (sdp_message, 1);
  audio_control = gst_sdp_media_get_attribute_val (sdp_media, "control");

  get_client_ports_full (&client_port, &rtp_socket, &rtcp_socket);

  /* do SETUP for video and audio */
  fail_unless (do_setup (conn, video_control, &client_port, &session,
          &video_transport) == GST_RTSP_STS_OK);
  fail_unless (do_setup (conn, audio_control, &client_port, &session,
          &audio_transport) == GST_RTSP_STS_OK);

  /* send PLAY request and check that we get 200 OK */
  fail_unless (do_request (conn, GST_RTSP_PLAY, NULL, session, NULL, range,
          NULL, NULL, NULL, NULL, NULL, &range_out) == GST_RTSP_STS_OK);
  if (range)
    fail_unless_equals_string (range, range_out);
  g_free (range_out);

  receive_rtp (rtp_socket, NULL);
  receive_rtcp (rtcp_socket, NULL, 0);

  /* send TEARDOWN request and check that we get 200 OK */
  fail_unless (do_simple_request (conn, GST_RTSP_TEARDOWN,
          session) == GST_RTSP_STS_OK);

  /* FIXME: The rtsp-server always disconnects the transport before
   * sending the RTCP BYE
   * receive_rtcp (rtcp_socket, NULL, GST_RTCP_TYPE_BYE);
   */

  /* clean up and iterate so the clean-up can finish */
  g_object_unref (rtp_socket);
  g_object_unref (rtcp_socket);
  g_free (session);
  gst_rtsp_transport_free (video_transport);
  gst_rtsp_transport_free (audio_transport);
  gst_sdp_message_free (sdp_message);
  gst_rtsp_connection_free (conn);
}


GST_START_TEST (test_play)
{
  start_server ();

  do_test_play (NULL);

  stop_server ();
  iterate ();
}

GST_END_TEST;

GST_START_TEST (test_play_without_session)
{
  GstRTSPConnection *conn;

  start_server ();

  conn = connect_to_server (test_port, TEST_MOUNT_POINT);

  /* send PLAY request without a session and check that we get a
   * 454 Session Not Found */
  fail_unless (do_simple_request (conn, GST_RTSP_PLAY,
          NULL) == GST_RTSP_STS_SESSION_NOT_FOUND);

  /* clean up and iterate so the clean-up can finish */
  gst_rtsp_connection_free (conn);
  stop_server ();
  iterate ();
}

GST_END_TEST;

GST_START_TEST (test_bind_already_in_use)
{
  GstRTSPServer *serv;
  GSocketService *service;
  GError *error = NULL;
  guint16 port;
  gchar *port_str;

  serv = gst_rtsp_server_new ();
  service = g_socket_service_new ();

  /* bind service to port */
  port =
      g_socket_listener_add_any_inet_port (G_SOCKET_LISTENER (service), NULL,
      &error);
  g_assert_no_error (error);

  port_str = g_strdup_printf ("%d\n", port);

  /* try to bind server to the same port */
  g_object_set (serv, "service", port_str, NULL);
  g_free (port_str);

  /* attach to default main context */
  fail_unless (gst_rtsp_server_attach (serv, NULL) == 0);

  /* cleanup */
  g_object_unref (serv);
  g_socket_service_stop (service);
  g_object_unref (service);
}

GST_END_TEST;


GST_START_TEST (test_play_multithreaded)
{
  GstRTSPThreadPool *pool;

  pool = gst_rtsp_server_get_thread_pool (server);
  gst_rtsp_thread_pool_set_max_threads (pool, 2);
  g_object_unref (pool);

  start_server ();

  do_test_play (NULL);

  stop_server ();
  iterate ();
}

GST_END_TEST;

enum
{
  BLOCK_ME,
  BLOCKED,
  UNBLOCK
};


static void
media_constructed_block (GstRTSPMediaFactory * factory,
    GstRTSPMedia * media, gpointer user_data)
{
  gint *block_state = user_data;

  g_mutex_lock (&check_mutex);

  *block_state = BLOCKED;
  g_cond_broadcast (&check_cond);

  while (*block_state != UNBLOCK)
    g_cond_wait (&check_cond, &check_mutex);
  g_mutex_unlock (&check_mutex);
}


GST_START_TEST (test_play_multithreaded_block_in_describe)
{
  GstRTSPConnection *conn;
  GstRTSPMountPoints *mounts;
  GstRTSPMediaFactory *factory;
  gint block_state = BLOCK_ME;
  GstRTSPMessage *request;
  GstRTSPMessage *response;
  GstRTSPStatusCode code;
  GstRTSPThreadPool *pool;

  pool = gst_rtsp_server_get_thread_pool (server);
  gst_rtsp_thread_pool_set_max_threads (pool, 2);
  g_object_unref (pool);

  mounts = gst_rtsp_server_get_mount_points (server);
  fail_unless (mounts != NULL);
  factory = gst_rtsp_media_factory_new ();
  gst_rtsp_media_factory_set_launch (factory,
      "( " VIDEO_PIPELINE "  " AUDIO_PIPELINE " )");
  g_signal_connect (factory, "media-constructed",
      G_CALLBACK (media_constructed_block), &block_state);
  gst_rtsp_mount_points_add_factory (mounts, TEST_MOUNT_POINT "2", factory);
  g_object_unref (mounts);

  start_server ();

  conn = connect_to_server (test_port, TEST_MOUNT_POINT "2");
  iterate ();

  /* do describe, it will not return now as we've blocked it */
  request = create_request (conn, GST_RTSP_DESCRIBE, NULL);
  fail_unless (send_request (conn, request));
  gst_rtsp_message_free (request);

  g_mutex_lock (&check_mutex);
  while (block_state != BLOCKED)
    g_cond_wait (&check_cond, &check_mutex);
  g_mutex_unlock (&check_mutex);

  /* Do a second connection while the first one is blocked */
  do_test_play (NULL);

  /* Now unblock the describe */
  g_mutex_lock (&check_mutex);
  block_state = UNBLOCK;
  g_cond_broadcast (&check_cond);
  g_mutex_unlock (&check_mutex);

  response = read_response (conn);
  gst_rtsp_message_parse_response (response, &code, NULL, NULL);
  fail_unless (code == GST_RTSP_STS_OK);
  gst_rtsp_message_free (response);


  gst_rtsp_connection_free (conn);
  stop_server ();
  iterate ();

}

GST_END_TEST;


static void
new_session_timeout_one (GstRTSPClient * client,
    GstRTSPSession * session, gpointer user_data)
{
  gst_rtsp_session_set_timeout (session, 1);

  g_signal_handlers_disconnect_by_func (client, new_session_timeout_one,
      user_data);
}

static void
session_connected_new_session_cb (GstRTSPServer * server,
    GstRTSPClient * client, gpointer user_data)
{

  g_signal_connect (client, "new-session", user_data, NULL);
}

GST_START_TEST (test_play_multithreaded_timeout_client)
{
  GstRTSPConnection *conn;
  GstSDPMessage *sdp_message = NULL;
  const GstSDPMedia *sdp_media;
  const gchar *video_control;
  const gchar *audio_control;
  GstRTSPRange client_port;
  gchar *session = NULL;
  GstRTSPTransport *video_transport = NULL;
  GstRTSPTransport *audio_transport = NULL;
  GstRTSPSessionPool *pool;
  GstRTSPThreadPool *thread_pool;

  thread_pool = gst_rtsp_server_get_thread_pool (server);
  gst_rtsp_thread_pool_set_max_threads (thread_pool, 2);
  g_object_unref (thread_pool);

  pool = gst_rtsp_server_get_session_pool (server);
  g_signal_connect (server, "client-connected",
      G_CALLBACK (session_connected_new_session_cb), new_session_timeout_one);

  start_server ();


  conn = connect_to_server (test_port, TEST_MOUNT_POINT);

  sdp_message = do_describe (conn, TEST_MOUNT_POINT);

  /* get control strings from DESCRIBE response */
  fail_unless (gst_sdp_message_medias_len (sdp_message) == 2);
  sdp_media = gst_sdp_message_get_media (sdp_message, 0);
  video_control = gst_sdp_media_get_attribute_val (sdp_media, "control");
  sdp_media = gst_sdp_message_get_media (sdp_message, 1);
  audio_control = gst_sdp_media_get_attribute_val (sdp_media, "control");

  get_client_ports (&client_port);

  /* do SETUP for video and audio */
  fail_unless (do_setup (conn, video_control, &client_port, &session,
          &video_transport) == GST_RTSP_STS_OK);
  fail_unless (do_setup (conn, audio_control, &client_port, &session,
          &audio_transport) == GST_RTSP_STS_OK);

  fail_unless (gst_rtsp_session_pool_get_n_sessions (pool) == 1);

  /* send PLAY request and check that we get 200 OK */
  fail_unless (do_simple_request (conn, GST_RTSP_PLAY,
          session) == GST_RTSP_STS_OK);

  sleep (7);

  fail_unless (gst_rtsp_session_pool_cleanup (pool) == 1);
  fail_unless (gst_rtsp_session_pool_get_n_sessions (pool) == 0);

  /* clean up and iterate so the clean-up can finish */
  g_object_unref (pool);
  g_free (session);
  gst_rtsp_transport_free (video_transport);
  gst_rtsp_transport_free (audio_transport);
  gst_sdp_message_free (sdp_message);
  gst_rtsp_connection_free (conn);

  stop_server ();
  iterate ();
}

GST_END_TEST;


GST_START_TEST (test_play_multithreaded_timeout_session)
{
  GstRTSPConnection *conn;
  GstSDPMessage *sdp_message = NULL;
  const GstSDPMedia *sdp_media;
  const gchar *video_control;
  const gchar *audio_control;
  GstRTSPRange client_port;
  gchar *session1 = NULL;
  gchar *session2 = NULL;
  GstRTSPTransport *video_transport = NULL;
  GstRTSPTransport *audio_transport = NULL;
  GstRTSPSessionPool *pool;
  GstRTSPThreadPool *thread_pool;

  thread_pool = gst_rtsp_server_get_thread_pool (server);
  gst_rtsp_thread_pool_set_max_threads (thread_pool, 2);
  g_object_unref (thread_pool);

  pool = gst_rtsp_server_get_session_pool (server);
  g_signal_connect (server, "client-connected",
      G_CALLBACK (session_connected_new_session_cb), new_session_timeout_one);

  start_server ();


  conn = connect_to_server (test_port, TEST_MOUNT_POINT);

  gst_rtsp_connection_set_remember_session_id (conn, FALSE);

  sdp_message = do_describe (conn, TEST_MOUNT_POINT);

  /* get control strings from DESCRIBE response */
  fail_unless (gst_sdp_message_medias_len (sdp_message) == 2);
  sdp_media = gst_sdp_message_get_media (sdp_message, 0);
  video_control = gst_sdp_media_get_attribute_val (sdp_media, "control");
  sdp_media = gst_sdp_message_get_media (sdp_message, 1);
  audio_control = gst_sdp_media_get_attribute_val (sdp_media, "control");

  get_client_ports (&client_port);

  /* do SETUP for video and audio */
  fail_unless (do_setup (conn, video_control, &client_port, &session1,
          &video_transport) == GST_RTSP_STS_OK);
  fail_unless (do_setup (conn, audio_control, &client_port, &session2,
          &audio_transport) == GST_RTSP_STS_OK);

  fail_unless (gst_rtsp_session_pool_get_n_sessions (pool) == 2);

  /* send PLAY request and check that we get 200 OK */
  fail_unless (do_simple_request (conn, GST_RTSP_PLAY,
          session1) == GST_RTSP_STS_OK);
  fail_unless (do_simple_request (conn, GST_RTSP_PLAY,
          session2) == GST_RTSP_STS_OK);

  sleep (7);

  fail_unless (gst_rtsp_session_pool_cleanup (pool) == 1);

  /* send TEARDOWN request and check that we get 454 Session Not found */
  fail_unless (do_simple_request (conn, GST_RTSP_TEARDOWN,
          session1) == GST_RTSP_STS_SESSION_NOT_FOUND);

  fail_unless (do_simple_request (conn, GST_RTSP_TEARDOWN,
          session2) == GST_RTSP_STS_OK);

  /* clean up and iterate so the clean-up can finish */
  g_object_unref (pool);
  g_free (session1);
  g_free (session2);
  gst_rtsp_transport_free (video_transport);
  gst_rtsp_transport_free (audio_transport);
  gst_sdp_message_free (sdp_message);
  gst_rtsp_connection_free (conn);

  stop_server ();
  iterate ();
}

GST_END_TEST;


GST_START_TEST (test_play_disconnect)
{
  GstRTSPConnection *conn;
  GstSDPMessage *sdp_message = NULL;
  const GstSDPMedia *sdp_media;
  const gchar *video_control;
  const gchar *audio_control;
  GstRTSPRange client_port;
  gchar *session = NULL;
  GstRTSPTransport *video_transport = NULL;
  GstRTSPTransport *audio_transport = NULL;
  GstRTSPSessionPool *pool;

  pool = gst_rtsp_server_get_session_pool (server);
  g_signal_connect (server, "client-connected",
      G_CALLBACK (session_connected_new_session_cb), new_session_timeout_one);

  start_server ();

  conn = connect_to_server (test_port, TEST_MOUNT_POINT);

  sdp_message = do_describe (conn, TEST_MOUNT_POINT);

  /* get control strings from DESCRIBE response */
  fail_unless (gst_sdp_message_medias_len (sdp_message) == 2);
  sdp_media = gst_sdp_message_get_media (sdp_message, 0);
  video_control = gst_sdp_media_get_attribute_val (sdp_media, "control");
  sdp_media = gst_sdp_message_get_media (sdp_message, 1);
  audio_control = gst_sdp_media_get_attribute_val (sdp_media, "control");

  get_client_ports (&client_port);

  /* do SETUP for video and audio */
  fail_unless (do_setup (conn, video_control, &client_port, &session,
          &video_transport) == GST_RTSP_STS_OK);
  fail_unless (do_setup (conn, audio_control, &client_port, &session,
          &audio_transport) == GST_RTSP_STS_OK);

  fail_unless (gst_rtsp_session_pool_get_n_sessions (pool) == 1);

  /* send PLAY request and check that we get 200 OK */
  fail_unless (do_simple_request (conn, GST_RTSP_PLAY,
          session) == GST_RTSP_STS_OK);

  gst_rtsp_connection_free (conn);

  sleep (7);

  fail_unless (gst_rtsp_session_pool_get_n_sessions (pool) == 1);
  fail_unless (gst_rtsp_session_pool_cleanup (pool) == 1);


  /* clean up and iterate so the clean-up can finish */
  g_object_unref (pool);
  g_free (session);
  gst_rtsp_transport_free (video_transport);
  gst_rtsp_transport_free (audio_transport);
  gst_sdp_message_free (sdp_message);

  stop_server ();
  iterate ();
}

GST_END_TEST;

/* Only different with test_play is the specific ports selected */

GST_START_TEST (test_play_specific_server_port)
{
  GstRTSPMountPoints *mounts;
  gchar *service;
  GstRTSPMediaFactory *factory;
  GstRTSPAddressPool *pool;
  GstRTSPConnection *conn;
  GstSDPMessage *sdp_message = NULL;
  const GstSDPMedia *sdp_media;
  const gchar *video_control;
  GstRTSPRange client_port;
  gchar *session = NULL;
  GstRTSPTransport *video_transport = NULL;
  GSocket *rtp_socket, *rtcp_socket;
  GSocketAddress *rtp_address, *rtcp_address;
  guint16 rtp_port, rtcp_port;

  mounts = gst_rtsp_server_get_mount_points (server);

  factory = gst_rtsp_media_factory_new ();
  pool = gst_rtsp_address_pool_new ();
  gst_rtsp_address_pool_add_range (pool, GST_RTSP_ADDRESS_POOL_ANY_IPV4,
      GST_RTSP_ADDRESS_POOL_ANY_IPV4, 7770, 7780, 0);
  gst_rtsp_media_factory_set_address_pool (factory, pool);
  g_object_unref (pool);
  gst_rtsp_media_factory_set_launch (factory, "( " VIDEO_PIPELINE " )");
  gst_rtsp_mount_points_add_factory (mounts, TEST_MOUNT_POINT, factory);
  g_object_unref (mounts);

  /* set port to any */
  gst_rtsp_server_set_service (server, "0");

  /* attach to default main context */
  source_id = gst_rtsp_server_attach (server, NULL);
  fail_if (source_id == 0);

  /* get port */
  service = gst_rtsp_server_get_service (server);
  test_port = atoi (service);
  fail_unless (test_port != 0);
  g_free (service);

  GST_DEBUG ("rtsp server listening on port %d", test_port);


  conn = connect_to_server (test_port, TEST_MOUNT_POINT);

  sdp_message = do_describe (conn, TEST_MOUNT_POINT);

  /* get control strings from DESCRIBE response */
  fail_unless (gst_sdp_message_medias_len (sdp_message) == 1);
  sdp_media = gst_sdp_message_get_media (sdp_message, 0);
  video_control = gst_sdp_media_get_attribute_val (sdp_media, "control");

  get_client_ports_full (&client_port, &rtp_socket, &rtcp_socket);

  /* do SETUP for video */
  fail_unless (do_setup (conn, video_control, &client_port, &session,
          &video_transport) == GST_RTSP_STS_OK);

  /* send PLAY request and check that we get 200 OK */
  fail_unless (do_simple_request (conn, GST_RTSP_PLAY,
          session) == GST_RTSP_STS_OK);

  receive_rtp (rtp_socket, &rtp_address);
  receive_rtcp (rtcp_socket, &rtcp_address, 0);

  fail_unless (G_IS_INET_SOCKET_ADDRESS (rtp_address));
  fail_unless (G_IS_INET_SOCKET_ADDRESS (rtcp_address));
  rtp_port =
      g_inet_socket_address_get_port (G_INET_SOCKET_ADDRESS (rtp_address));
  rtcp_port =
      g_inet_socket_address_get_port (G_INET_SOCKET_ADDRESS (rtcp_address));
  fail_unless (rtp_port >= 7770 && rtp_port <= 7780 && rtp_port % 2 == 0);
  fail_unless (rtcp_port >= 7770 && rtcp_port <= 7780 && rtcp_port % 2 == 1);
  fail_unless (rtp_port + 1 == rtcp_port);

  g_object_unref (rtp_address);
  g_object_unref (rtcp_address);

  /* send TEARDOWN request and check that we get 200 OK */
  fail_unless (do_simple_request (conn, GST_RTSP_TEARDOWN,
          session) == GST_RTSP_STS_OK);

  /* FIXME: The rtsp-server always disconnects the transport before
   * sending the RTCP BYE
   * receive_rtcp (rtcp_socket, NULL, GST_RTCP_TYPE_BYE);
   */

  /* clean up and iterate so the clean-up can finish */
  g_object_unref (rtp_socket);
  g_object_unref (rtcp_socket);
  g_free (session);
  gst_rtsp_transport_free (video_transport);
  gst_sdp_message_free (sdp_message);
  gst_rtsp_connection_free (conn);


  stop_server ();
  iterate ();
}

GST_END_TEST;


GST_START_TEST (test_play_smpte_range)
{
  start_server ();

  do_test_play ("npt=5-");
  do_test_play ("smpte=0:00:00-");
  do_test_play ("smpte=1:00:00-");
  do_test_play ("smpte=1:00:03-");
  do_test_play ("clock=20120321T152256Z-");

  stop_server ();
  iterate ();
}

GST_END_TEST;

GST_START_TEST (test_announce_without_sdp)
{
  GstRTSPConnection *conn;
  GstRTSPStatusCode status;
  GstRTSPMessage *request;
  GstRTSPMessage *response;

  start_record_server ("( fakesink name=depay0 )");

  conn = connect_to_server (test_port, TEST_MOUNT_POINT);

  /* create and send ANNOUNCE request */
  request = create_request (conn, GST_RTSP_ANNOUNCE, NULL);

  fail_unless (send_request (conn, request));

  iterate ();

  response = read_response (conn);

  /* check response */
  gst_rtsp_message_parse_response (response, &status, NULL, NULL);
  fail_unless_equals_int (status, GST_RTSP_STS_BAD_REQUEST);
  gst_rtsp_message_free (response);

  /* try again, this type with content-type, but still no SDP */
  gst_rtsp_message_add_header (request, GST_RTSP_HDR_CONTENT_TYPE,
      "application/sdp");

  fail_unless (send_request (conn, request));

  iterate ();

  response = read_response (conn);

  /* check response */
  gst_rtsp_message_parse_response (response, &status, NULL, NULL);
  fail_unless_equals_int (status, GST_RTSP_STS_BAD_REQUEST);
  gst_rtsp_message_free (response);

  /* try again, this type with an unknown content-type */
  gst_rtsp_message_remove_header (request, GST_RTSP_HDR_CONTENT_TYPE, -1);
  gst_rtsp_message_add_header (request, GST_RTSP_HDR_CONTENT_TYPE,
      "application/x-something");

  fail_unless (send_request (conn, request));

  iterate ();

  response = read_response (conn);

  /* check response */
  gst_rtsp_message_parse_response (response, &status, NULL, NULL);
  fail_unless_equals_int (status, GST_RTSP_STS_BAD_REQUEST);
  gst_rtsp_message_free (response);

  /* clean up and iterate so the clean-up can finish */
  gst_rtsp_message_free (request);
  gst_rtsp_connection_free (conn);
  stop_server ();
  iterate ();
}

GST_END_TEST;

static GstRTSPStatusCode
do_announce (GstRTSPConnection * conn, GstSDPMessage * sdp)
{
  GstRTSPMessage *request;
  GstRTSPMessage *response;
  GstRTSPStatusCode code;
  gchar *str;

  /* create request */
  request = create_request (conn, GST_RTSP_ANNOUNCE, NULL);

  gst_rtsp_message_add_header (request, GST_RTSP_HDR_CONTENT_TYPE,
      "application/sdp");

  /* add SDP to the response body */
  str = gst_sdp_message_as_text (sdp);
  gst_rtsp_message_take_body (request, (guint8 *) str, strlen (str));
  gst_sdp_message_free (sdp);

  /* send request */
  fail_unless (send_request (conn, request));
  gst_rtsp_message_free (request);

  iterate ();

  /* read response */
  response = read_response (conn);

  /* check status line */
  gst_rtsp_message_parse_response (response, &code, NULL, NULL);

  gst_rtsp_message_free (response);
  return code;
}

static void
media_constructed_cb (GstRTSPMediaFactory * mfactory, GstRTSPMedia * media,
    gpointer user_data)
{
  GstElement **p_sink = user_data;
  GstElement *bin;

  bin = gst_rtsp_media_get_element (media);
  *p_sink = gst_bin_get_by_name (GST_BIN (bin), "sink");
  GST_INFO ("media constructed!: %" GST_PTR_FORMAT, *p_sink);
}

#define RECORD_N_BUFS 10

GST_START_TEST (test_record_tcp)
{
  GstRTSPMediaFactory *mfactory;
  GstRTSPConnection *conn;
  GstRTSPStatusCode status;
  GstRTSPMessage *response;
  GstRTSPMessage *request;
  GstSDPMessage *sdp;
  GstRTSPResult rres;
  GSocketAddress *sa;
  GInetAddress *ia;
  GstElement *server_sink = NULL;
  GSocket *conn_socket;
  const gchar *proto;
  gchar *client_ip, *sess_id, *session = NULL;
  gint i;

  mfactory =
      start_record_server ("( rtppcmadepay name=depay0 ! appsink name=sink )");

  g_signal_connect (mfactory, "media-constructed",
      G_CALLBACK (media_constructed_cb), &server_sink);

  conn = connect_to_server (test_port, TEST_MOUNT_POINT);

  conn_socket = gst_rtsp_connection_get_read_socket (conn);

  sa = g_socket_get_local_address (conn_socket, NULL);
  ia = g_inet_socket_address_get_address (G_INET_SOCKET_ADDRESS (sa));
  client_ip = g_inet_address_to_string (ia);
  if (g_socket_address_get_family (sa) == G_SOCKET_FAMILY_IPV6)
    proto = "IP6";
  else if (g_socket_address_get_family (sa) == G_SOCKET_FAMILY_IPV4)
    proto = "IP4";
  else
    g_assert_not_reached ();
  g_object_unref (sa);

  gst_sdp_message_new (&sdp);

  /* some standard things first */
  gst_sdp_message_set_version (sdp, "0");

  /* session ID doesn't have to be super-unique in this case */
  sess_id = g_strdup_printf ("%u", g_random_int ());
  gst_sdp_message_set_origin (sdp, "-", sess_id, "1", "IN", proto, client_ip);
  g_free (sess_id);
  g_free (client_ip);

  gst_sdp_message_set_session_name (sdp, "Session streamed with GStreamer");
  gst_sdp_message_set_information (sdp, "rtsp-server-test");
  gst_sdp_message_add_time (sdp, "0", "0", NULL);
  gst_sdp_message_add_attribute (sdp, "tool", "GStreamer");

  /* add stream 0 */
  {
    GstSDPMedia *smedia;

    gst_sdp_media_new (&smedia);
    gst_sdp_media_set_media (smedia, "audio");
    gst_sdp_media_add_format (smedia, "8");     /* pcma/alaw */
    gst_sdp_media_set_port_info (smedia, 0, 1);
    gst_sdp_media_set_proto (smedia, "RTP/AVP");
    gst_sdp_media_add_attribute (smedia, "rtpmap", "8 PCMA/8000");
    gst_sdp_message_add_media (sdp, smedia);
    gst_sdp_media_free (smedia);
  }

  /* send ANNOUNCE request */
  status = do_announce (conn, sdp);
  fail_unless_equals_int (status, GST_RTSP_STS_OK);

  /* create and send SETUP request */
  request = create_request (conn, GST_RTSP_SETUP, NULL);
  gst_rtsp_message_add_header (request, GST_RTSP_HDR_TRANSPORT,
      "RTP/AVP/TCP;interleaved=0;mode=record");
  fail_unless (send_request (conn, request));
  gst_rtsp_message_free (request);
  iterate ();
  response = read_response (conn);
  gst_rtsp_message_parse_response (response, &status, NULL, NULL);
  fail_unless_equals_int (status, GST_RTSP_STS_OK);

  rres =
      gst_rtsp_message_get_header (response, GST_RTSP_HDR_SESSION, &session, 0);
  session = g_strdup (session);
  fail_unless_equals_int (rres, GST_RTSP_OK);
  gst_rtsp_message_free (response);

  /* send RECORD */
  request = create_request (conn, GST_RTSP_RECORD, NULL);
  gst_rtsp_message_add_header (request, GST_RTSP_HDR_SESSION, session);
  fail_unless (send_request (conn, request));
  gst_rtsp_message_free (request);
  iterate ();
  response = read_response (conn);
  gst_rtsp_message_parse_response (response, &status, NULL, NULL);
  fail_unless_equals_int (status, GST_RTSP_STS_OK);
  gst_rtsp_message_free (response);

  /* send some data */
  {
    GstElement *pipeline, *src, *enc, *pay, *sink;

    pipeline = gst_pipeline_new ("send-pipeline");
    src = gst_element_factory_make ("audiotestsrc", NULL);
    g_object_set (src, "num-buffers", RECORD_N_BUFS,
        "samplesperbuffer", 1000, NULL);
    enc = gst_element_factory_make ("alawenc", NULL);
    pay = gst_element_factory_make ("rtppcmapay", NULL);
    sink = gst_element_factory_make ("appsink", NULL);
    fail_unless (pipeline && src && enc && pay && sink);
    gst_bin_add_many (GST_BIN (pipeline), src, enc, pay, sink, NULL);
    gst_element_link_many (src, enc, pay, sink, NULL);
    gst_element_set_state (pipeline, GST_STATE_PLAYING);

    do {
      GstRTSPMessage *data_msg;
      GstMapInfo map = GST_MAP_INFO_INIT;
      GstRTSPResult rres;
      GstSample *sample = NULL;
      GstBuffer *buf;

      g_signal_emit_by_name (G_OBJECT (sink), "pull-sample", &sample);
      if (sample == NULL)
        break;
      buf = gst_sample_get_buffer (sample);
      rres = gst_rtsp_message_new_data (&data_msg, 0);
      fail_unless_equals_int (rres, GST_RTSP_OK);
      gst_buffer_map (buf, &map, GST_MAP_READ);
      GST_INFO ("sending %u bytes of data on channel 0", (guint) map.size);
      GST_MEMDUMP ("data on channel 0", map.data, map.size);
      rres = gst_rtsp_message_set_body (data_msg, map.data, map.size);
      fail_unless_equals_int (rres, GST_RTSP_OK);
      gst_buffer_unmap (buf, &map);
      rres = gst_rtsp_connection_send (conn, data_msg, NULL);
      fail_unless_equals_int (rres, GST_RTSP_OK);
      gst_rtsp_message_free (data_msg);
      gst_sample_unref (sample);
    } while (TRUE);

    gst_element_set_state (pipeline, GST_STATE_NULL);
    gst_object_unref (pipeline);
  }

  /* check received data (we assume every buffer created by audiotestsrc and
   * subsequently encoded by mulawenc results in exactly one RTP packet) */
  for (i = 0; i < RECORD_N_BUFS; ++i) {
    GstSample *sample = NULL;

    g_signal_emit_by_name (G_OBJECT (server_sink), "pull-sample", &sample);
    GST_INFO ("%2d recv sample: %p", i, sample);
    gst_sample_unref (sample);
  }

  fail_unless_equals_int (GST_STATE (server_sink), GST_STATE_PLAYING);

  /* clean up and iterate so the clean-up can finish */
  gst_rtsp_connection_free (conn);
  stop_server ();
  iterate ();
  g_free (session);
}

GST_END_TEST;

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

  suite_add_tcase (s, tc);
  tcase_add_checked_fixture (tc, setup, teardown);
  tcase_set_timeout (tc, 120);
  tcase_add_test (tc, test_connect);
  tcase_add_test (tc, test_describe);
  tcase_add_test (tc, test_describe_non_existing_mount_point);
  tcase_add_test (tc, test_describe_record_media);
  tcase_add_test (tc, test_setup);
  tcase_add_test (tc, test_setup_tcp);
  tcase_add_test (tc, test_setup_twice);
  tcase_add_test (tc, test_setup_with_require_header);
  tcase_add_test (tc, test_setup_non_existing_stream);
  tcase_add_test (tc, test_play);
  tcase_add_test (tc, test_play_without_session);
  tcase_add_test (tc, test_bind_already_in_use);
  tcase_add_test (tc, test_play_multithreaded);
  tcase_add_test (tc, test_play_multithreaded_block_in_describe);
  tcase_add_test (tc, test_play_multithreaded_timeout_client);
  tcase_add_test (tc, test_play_multithreaded_timeout_session);
  tcase_add_test (tc, test_play_disconnect);
  tcase_add_test (tc, test_play_specific_server_port);
  tcase_add_test (tc, test_play_smpte_range);
  tcase_add_test (tc, test_announce_without_sdp);
  tcase_add_test (tc, test_record_tcp);
  return s;
}

GST_CHECK_MAIN (rtspserver);
