/*
 * Farstream - Farstream RAW UDP with STUN Stream Transmitter
 *
 * Copyright 2007-2008 Collabora Ltd.
 *  @author: Olivier Crete <olivier.crete@collabora.co.uk>
 * Copyright 2007-2008x Nokia Corp.
 *
 * fs-rawudp-transmitter.c - A Farstream UDPs stream transmitter with STUN
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301 USA
 */


/**
 * SECTION:fs-rawudp-stream-transmitter
 * @short_description: A stream transmitter object for UDP with STUN
 * @see_also: #FsMulticastStreamTransmitter
 *
 * This transmitter sends and receives unicast UDP packets.
 *
 * It will detect its own address using a STUN request if the
 * #FsRawUdpStreamTransmitter:stun-ip and #FsRawUdpStreamTransmitter:stun-port
 * properties are set. If the STUN request does not get a reply
 * or no STUN is requested. It will return the IP address of all the local
 * network interfaces, listing link-local addresses after other addresses
 * and the loopback interface last.
 *
 * You can configure the address and port it will listen on by setting the
 * "preferred-local-candidates" property. This property will contain a #GList
 * of #FsCandidate. These #FsCandidate must be for #FS_NETWORK_PROTOCOL_UDP.
 * These port and/or the ip can be set on these candidates to force them,
 * and this is per-component. If not all components have a port set, the
 * following components will be on the following ports. There is no guarantee
 * that the requested port will be available so a different port may the
 * native candidate. But it is guaranteed that components that do not have
 * specified ports will be sequential.
 *
 * Example: Candidate {proto=UDP, component_id=RTP, ip=NULL, port=9098} will
 *  produce native candidates
 * ({component_id=RTP, ip=IP, port=9078},{component_id=RTCP, ip=IP, port=9079})
 *  or
 * if this one is not available
 * ({component_id=RTP, ip=IP, port=9080},{component_id=RTCP, ip=IP, port=9081}).
 * The default port starts at 7078 for the first component.
 *
 * The name of this transmitter is "rawudp".
 */

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#include "fs-rawudp-stream-transmitter.h"

#include "fs-rawudp-component.h"

#include <farstream/fs-candidate.h>
#include <farstream/fs-conference.h>

#ifdef HAVE_GUPNP
#include <libgupnp-igd/gupnp-simple-igd-thread.h>
#endif

#include <gst/gst.h>

#include <string.h>


#define GST_CAT_DEFAULT fs_rawudp_transmitter_debug

#define DEFAULT_UPNP_MAPPING_TIMEOUT (600)
#define DEFAULT_UPNP_DISCOVERY_TIMEOUT (2)

/* Signals */
enum
{
  LAST_SIGNAL
};

/* props */
enum
{
  PROP_0,
  PROP_SENDING,
  PROP_PREFERRED_LOCAL_CANDIDATES,
  PROP_ASSOCIATE_ON_SOURCE,
  PROP_STUN_IP,
  PROP_STUN_PORT,
  PROP_STUN_TIMEOUT,
  PROP_UPNP_MAPPING,
  PROP_UPNP_DISCOVERY,
  PROP_UPNP_MAPPING_TIMEOUT,
  PROP_UPNP_DISCOVERY_TIMEOUT
};

struct _FsRawUdpStreamTransmitterPrivate
{
  gboolean disposed;

  /* We don't actually hold a ref to this,
   * But since our parent FsStream can not exist without its parent
   * FsSession, we should be safe
   */
  FsRawUdpTransmitter *transmitter;

  gboolean sending;

  /* This is an array of size n_components+1 */
  FsRawUdpComponent **component;

  gchar *stun_ip;
  guint stun_port;
  guint stun_timeout;

  GList *preferred_local_candidates;
  guint next_candidate_id;

  gboolean associate_on_source;

#ifdef HAVE_GUPNP
  gboolean upnp_discovery;
  gboolean upnp_mapping;
  guint upnp_mapping_timeout;
  guint upnp_discovery_timeout;

  GUPnPSimpleIgdThread *upnp_igd;
#endif

  /* Everything below this line is protected by the mutex */
  GMutex mutex;
  gboolean *candidates_prepared;
};

#define FS_RAWUDP_STREAM_TRANSMITTER_GET_PRIVATE(o)                     \
  (G_TYPE_INSTANCE_GET_PRIVATE ((o), FS_TYPE_RAWUDP_STREAM_TRANSMITTER, \
      FsRawUdpStreamTransmitterPrivate))

#define FS_RAWUDP_STREAM_TRANSMITTER_LOCK(o)   g_mutex_lock (&(o)->priv->mutex)
#define FS_RAWUDP_STREAM_TRANSMITTER_UNLOCK(o) g_mutex_unlock (&(o)->priv->mutex)

static void fs_rawudp_stream_transmitter_class_init (
    FsRawUdpStreamTransmitterClass *klass);
static void fs_rawudp_stream_transmitter_init (FsRawUdpStreamTransmitter *self);
static void fs_rawudp_stream_transmitter_dispose (GObject *object);
static void fs_rawudp_stream_transmitter_finalize (GObject *object);

static void fs_rawudp_stream_transmitter_get_property (GObject *object,
    guint prop_id,
    GValue *value,
    GParamSpec *pspec);
static void fs_rawudp_stream_transmitter_set_property (GObject *object,
    guint prop_id,
    const GValue *value,
    GParamSpec *pspec);

static void
fs_rawudp_stream_transmitter_stop (FsStreamTransmitter *streamtransmitter);

static gboolean fs_rawudp_stream_transmitter_force_remote_candidates (
    FsStreamTransmitter *streamtransmitter,
    GList *candidates,
    GError **error);
static gboolean fs_rawudp_stream_transmitter_gather_local_candidates (
    FsStreamTransmitter *streamtransmitter,
    GError **error);

static FsCandidate* fs_rawudp_stream_transmitter_build_forced_candidate (
    FsRawUdpStreamTransmitter *self,
    const char *ip,
    gint port,
    guint component_id);

static void
_component_new_local_candidate (FsRawUdpComponent *component,
    FsCandidate *candidate, gpointer user_data);
static void
_component_local_candidates_prepared (FsRawUdpComponent *component,
    gpointer user_data);
static void
_component_new_active_candidate_pair (FsRawUdpComponent *component,
    FsCandidate *local, FsCandidate *remote, gpointer user_data);
static void
_component_error (FsRawUdpComponent *component,
    FsError error_no, gchar *error_msg, gpointer user_data);
static void
_component_known_source_packet_received (FsRawUdpComponent *component,
    guint component_id, GstBuffer *buffer, gpointer user_data);

static GObjectClass *parent_class = NULL;
// static guint signals[LAST_SIGNAL] = { 0 };

static GType type = 0;

GType
fs_rawudp_stream_transmitter_get_type (void)
{
  return type;
}

GType
fs_rawudp_stream_transmitter_register_type (FsPlugin *module)
{
  static const GTypeInfo info = {
    sizeof (FsRawUdpStreamTransmitterClass),
    NULL,
    NULL,
    (GClassInitFunc) fs_rawudp_stream_transmitter_class_init,
    NULL,
    NULL,
    sizeof (FsRawUdpStreamTransmitter),
    0,
    (GInstanceInitFunc) fs_rawudp_stream_transmitter_init
  };

  fs_rawudp_component_register_type (module);

  type = g_type_register_static (FS_TYPE_STREAM_TRANSMITTER,
      "FsRawUdpStreamTransmitter", &info, 0);

  return type;
}

static void
fs_rawudp_stream_transmitter_class_init (FsRawUdpStreamTransmitterClass *klass)
{
  GObjectClass *gobject_class = (GObjectClass *) klass;
  FsStreamTransmitterClass *streamtransmitterclass =
    FS_STREAM_TRANSMITTER_CLASS (klass);

  parent_class = g_type_class_peek_parent (klass);

  gobject_class->set_property = fs_rawudp_stream_transmitter_set_property;
  gobject_class->get_property = fs_rawudp_stream_transmitter_get_property;

  streamtransmitterclass->force_remote_candidates =
    fs_rawudp_stream_transmitter_force_remote_candidates;
  streamtransmitterclass->gather_local_candidates =
    fs_rawudp_stream_transmitter_gather_local_candidates;
  streamtransmitterclass->stop = fs_rawudp_stream_transmitter_stop;

  g_object_class_override_property (gobject_class, PROP_SENDING, "sending");
  g_object_class_override_property (gobject_class,
      PROP_PREFERRED_LOCAL_CANDIDATES, "preferred-local-candidates");
  g_object_class_override_property (gobject_class,
      PROP_ASSOCIATE_ON_SOURCE, "associate-on-source");

  g_object_class_install_property (gobject_class,
      PROP_STUN_IP,
      g_param_spec_string ("stun-ip",
          "The IP address of the STUN server",
          "The IPv4 address of the STUN server as a x.x.x.x string",
          NULL,
          G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));

  g_object_class_install_property (gobject_class,
      PROP_STUN_PORT,
      g_param_spec_uint ("stun-port",
          "The port of the STUN server",
          "The IPv4 UDP port of the STUN server as a ",
          0, 65535, 3478,
          G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));

  g_object_class_install_property (gobject_class,
      PROP_STUN_TIMEOUT,
      g_param_spec_uint ("stun-timeout",
          "The timeout for the STUN reply",
          "How long to wait for for the STUN reply (in seconds) before giving up",
          1, MAX_STUN_TIMEOUT, DEFAULT_STUN_TIMEOUT,
          G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));

  g_object_class_install_property (gobject_class,
      PROP_UPNP_MAPPING,
      g_param_spec_boolean ("upnp-mapping",
#ifdef HAVE_GUPNP
          "Try to map ports using UPnP",
          "Tries to map ports using UPnP if enabled",
          TRUE,
#else
          "Try to map ports using UPnP (NOT COMPILED IN)",
          "Tries to map ports using UPnP if enabled",
          FALSE,
#endif
          G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));

  g_object_class_install_property (gobject_class,
      PROP_UPNP_DISCOVERY,
      g_param_spec_boolean ("upnp-discovery",
#ifdef HAVE_GUPNP
          "Try to use UPnP to find the external IP address",
          "Tries to discovery the external IP with UPnP if stun fails",
          TRUE,
#else
          "Try to use UPnP to find the external IP address (NOT COMPILED IN)",
          "Tries to discovery the external IP with UPnP if stun fails",
          FALSE,
#endif

          G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));

  g_object_class_install_property (gobject_class,
      PROP_UPNP_MAPPING_TIMEOUT,
      g_param_spec_uint ("upnp-mapping-timeout",
          "Timeout after which UPnP mappings expire",
          "The UPnP port mappings expire after this period if the app has"
          " crashed (in seconds)",
          0, G_MAXUINT32, DEFAULT_UPNP_MAPPING_TIMEOUT,
          G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));

  g_object_class_install_property (gobject_class,
      PROP_UPNP_DISCOVERY_TIMEOUT,
      g_param_spec_uint ("upnp-discovery-timeout",
          "Timeout after which UPnP discovery fails",
          "After this period, UPnP discovery is considered to have failed"
          " and the local IP is returned",
          0, G_MAXUINT32, DEFAULT_UPNP_DISCOVERY_TIMEOUT,
          G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));

  gobject_class->dispose = fs_rawudp_stream_transmitter_dispose;
  gobject_class->finalize = fs_rawudp_stream_transmitter_finalize;

  g_type_class_add_private (klass, sizeof (FsRawUdpStreamTransmitterPrivate));
}

static void
fs_rawudp_stream_transmitter_init (FsRawUdpStreamTransmitter *self)
{
  /* member init */
  self->priv = FS_RAWUDP_STREAM_TRANSMITTER_GET_PRIVATE (self);
  self->priv->disposed = FALSE;

  self->priv->sending = TRUE;
  self->priv->associate_on_source = TRUE;

#ifdef HAVE_GUPNP
  self->priv->upnp_mapping = TRUE;
  self->priv->upnp_discovery_timeout = DEFAULT_UPNP_DISCOVERY_TIMEOUT;
  self->priv->upnp_mapping_timeout = DEFAULT_UPNP_MAPPING_TIMEOUT;
  self->priv->upnp_discovery = TRUE;
#endif

  g_mutex_init (&self->priv->mutex);
}

static void
fs_rawudp_stream_transmitter_dispose (GObject *object)
{
  FsRawUdpStreamTransmitter *self = FS_RAWUDP_STREAM_TRANSMITTER (object);
  gint c;

  if (self->priv->disposed)
    /* If dispose did already run, return. */
    return;

  if (self->priv->component)
  {
    for (c = 1; c <= self->priv->transmitter->components; c++)
    {
      if (self->priv->component[c])
      {
        g_object_unref (self->priv->component[c]);
        self->priv->component[c] = NULL;
      }
    }
  }

#ifdef HAVE_GUPNP
  if (self->priv->upnp_igd)
  {
    g_object_unref (self->priv->upnp_igd);
    self->priv->upnp_igd = NULL;
  }
#endif

  /* Make sure dispose does not run twice. */
  self->priv->disposed = TRUE;

  parent_class->dispose (object);
}

static void
fs_rawudp_stream_transmitter_finalize (GObject *object)
{
  FsRawUdpStreamTransmitter *self = FS_RAWUDP_STREAM_TRANSMITTER (object);

  g_free (self->priv->stun_ip);

  if (self->priv->preferred_local_candidates)
    fs_candidate_list_destroy (self->priv->preferred_local_candidates);

  if (self->priv->component)
  {
    g_free (self->priv->component);
    self->priv->component = NULL;
  }

  g_mutex_clear (&self->priv->mutex);

  g_free (self->priv->candidates_prepared);

  parent_class->finalize (object);
}

static void
fs_rawudp_stream_transmitter_get_property (GObject *object,
    guint prop_id,
    GValue *value,
    GParamSpec *pspec)
{
  FsRawUdpStreamTransmitter *self = FS_RAWUDP_STREAM_TRANSMITTER (object);

  switch (prop_id)
  {
    case PROP_SENDING:
      g_value_set_boolean (value, self->priv->sending);
      break;
    case PROP_PREFERRED_LOCAL_CANDIDATES:
      g_value_set_boxed (value, self->priv->preferred_local_candidates);
      break;
    case PROP_ASSOCIATE_ON_SOURCE:
      g_value_set_boolean (value, self->priv->associate_on_source);
      break;
    case PROP_STUN_IP:
      g_value_set_string (value, self->priv->stun_ip);
      break;
    case PROP_STUN_PORT:
      g_value_set_uint (value, self->priv->stun_port);
      break;
    case PROP_STUN_TIMEOUT:
      g_value_set_uint (value, self->priv->stun_timeout);
      break;
#ifdef HAVE_GUPNP
    case PROP_UPNP_MAPPING:
      g_value_set_boolean (value, self->priv->upnp_mapping);
      break;
    case PROP_UPNP_DISCOVERY:
      g_value_set_boolean (value, self->priv->upnp_discovery);
      break;
    case PROP_UPNP_MAPPING_TIMEOUT:
      g_value_set_uint (value, self->priv->upnp_mapping_timeout);
      break;
    case PROP_UPNP_DISCOVERY_TIMEOUT:
      g_value_set_uint (value, self->priv->upnp_discovery_timeout);
      break;
#else
    case PROP_UPNP_MAPPING:
    case PROP_UPNP_DISCOVERY:
      g_value_set_boolean (value, FALSE);
      break;
    case PROP_UPNP_MAPPING_TIMEOUT:
    case PROP_UPNP_DISCOVERY_TIMEOUT:
      break;
#endif
   default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
      break;
  }
}

static void
fs_rawudp_stream_transmitter_set_property (GObject *object,
    guint prop_id,
    const GValue *value,
    GParamSpec *pspec)
{
  FsRawUdpStreamTransmitter *self = FS_RAWUDP_STREAM_TRANSMITTER (object);

  switch (prop_id)
  {
    case PROP_SENDING:
      {
        self->priv->sending = g_value_get_boolean (value);

        if (self->priv->component[1])
          g_object_set_property (G_OBJECT (self->priv->component[1]),
              "sending", value);
      }
      break;
    case PROP_PREFERRED_LOCAL_CANDIDATES:
      self->priv->preferred_local_candidates = g_value_dup_boxed (value);
      break;
    case PROP_ASSOCIATE_ON_SOURCE:
      self->priv->associate_on_source = g_value_get_boolean (value);
      break;
    case PROP_STUN_IP:
      g_free (self->priv->stun_ip);
      self->priv->stun_ip = g_value_dup_string (value);
      break;
    case PROP_STUN_PORT:
      self->priv->stun_port = g_value_get_uint (value);
      break;
    case PROP_STUN_TIMEOUT:
      self->priv->stun_timeout = g_value_get_uint (value);
      break;
#ifdef HAVE_GUPNP
    case PROP_UPNP_MAPPING:
      self->priv->upnp_mapping = g_value_get_boolean (value);
      break;
    case PROP_UPNP_DISCOVERY:
      self->priv->upnp_discovery = g_value_get_boolean (value);
      break;
    case PROP_UPNP_MAPPING_TIMEOUT:
      self->priv->upnp_mapping_timeout = g_value_get_uint (value);
      break;
    case PROP_UPNP_DISCOVERY_TIMEOUT:
      self->priv->upnp_discovery_timeout = g_value_get_uint (value);
      break;
#else
    case PROP_UPNP_MAPPING:
    case PROP_UPNP_DISCOVERY:
    case PROP_UPNP_MAPPING_TIMEOUT:
    case PROP_UPNP_DISCOVERY_TIMEOUT:
      break;
#endif
    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
      break;
  }
}

static gboolean
fs_rawudp_stream_transmitter_build (FsRawUdpStreamTransmitter *self,
    GError **error)
{
  const gchar **ips = g_new0 (const gchar *,
      self->priv->transmitter->components + 1);
  guint16 *ports = g_new0 (guint16, self->priv->transmitter->components + 1);

  GList *item;
  gint c;
  guint16 next_port;

#ifdef HAVE_GUPNP
  if (self->priv->upnp_mapping ||
      (self->priv->upnp_discovery && !self->priv->stun_ip))
    self->priv->upnp_igd = gupnp_simple_igd_thread_new ();
#endif

  self->priv->component = g_new0 (FsRawUdpComponent *,
      self->priv->transmitter->components + 1);
  self->priv->candidates_prepared = g_new0 (gboolean,
      self->priv->transmitter->components + 1);

  for (item = g_list_first (self->priv->preferred_local_candidates);
       item;
       item = g_list_next (item)) {
    FsCandidate *candidate = item->data;

    if (candidate->proto != FS_NETWORK_PROTOCOL_UDP) {
      g_set_error (error, FS_ERROR, FS_ERROR_INVALID_ARGUMENTS,
          "You set preferred candidate of a type %d that is not"
          " FS_NETWORK_PROTOCOL_UDP",
          candidate->proto);
      goto error;
    }

    if (candidate->component_id == 0) {
      g_set_error (error, FS_ERROR, FS_ERROR_INVALID_ARGUMENTS,
          "Component id 0 is invalid");
      goto error;
    }

    if (candidate->component_id > self->priv->transmitter->components)
    {
      g_set_error (error, FS_ERROR, FS_ERROR_INVALID_ARGUMENTS,
          "You specified an invalid component id %d with is higher"
          " than the maximum %d", candidate->component_id,
          self->priv->transmitter->components);
      goto error;
    }

    if (ips[candidate->component_id] || ports[candidate->component_id])
    {
      g_set_error (error, FS_ERROR,
          FS_ERROR_INVALID_ARGUMENTS,
          "You set more than one preferred local candidate for component %u",
          candidate->component_id);
      goto error;
    }

    /*
     * We should verify that the IP is valid now!!
     *
     */

    ips[candidate->component_id] = candidate->ip;
    if (candidate->port)
      ports[candidate->component_id] = candidate->port;
  }

  /* Lets make sure we start from a reasonnable value */
  if (ports[1] == 0)
    ports[1] = 7078;

  next_port = ports[1];

  for (c = 1; c <= self->priv->transmitter->components; c++)
  {
    gint requested_port = ports[c];
    guint used_port;

    if (!requested_port)
      requested_port = next_port;


    self->priv->component[c] = fs_rawudp_component_new (c,
        self->priv->transmitter,
        self->priv->associate_on_source,
        ips[c],
        requested_port,
        self->priv->stun_ip,
        self->priv->stun_port,
        self->priv->stun_timeout,
#ifdef HAVE_GUPNP
        self->priv->upnp_mapping,
        self->priv->upnp_discovery,
        self->priv->upnp_mapping_timeout,
        self->priv->upnp_discovery_timeout,
        self->priv->upnp_igd,
#else
        FALSE, FALSE, 0, 0, NULL,
#endif
        &used_port,
        error);
    if (self->priv->component[c] == NULL)
      goto error;

    g_signal_connect (self->priv->component[c], "new-local-candidate",
        G_CALLBACK (_component_new_local_candidate), self);
    g_signal_connect (self->priv->component[c], "local-candidates-prepared",
        G_CALLBACK (_component_local_candidates_prepared), self);
    g_signal_connect (self->priv->component[c], "new-active-candidate-pair",
        G_CALLBACK (_component_new_active_candidate_pair), self);
    g_signal_connect (self->priv->component[c], "error",
        G_CALLBACK (_component_error), self);
    g_signal_connect (self->priv->component[c], "known-source-packet-received",
        G_CALLBACK (_component_known_source_packet_received), self);

    /* If we dont get the requested port and it wasnt a forced port,
     * then we rewind up to the last forced port and jump to the next
     * package of components, all non-forced ports must be consecutive!
     */

    if (used_port != requested_port  &&  !ports[c])
    {
      do {
        fs_rawudp_component_stop (self->priv->component[c]);
        g_object_unref (self->priv->component[c]);
        self->priv->component[c] = NULL;

        c--;
      } while (!ports[c]);  /* Will always stop because ports[1] != 0 */
      ports[c] += self->priv->transmitter->components;
      next_port = ports[c];
      continue;
    }

    if (ips[c])
    {
      FsCandidate *forced =
        fs_rawudp_stream_transmitter_build_forced_candidate (self, ips[c],
            used_port, c);
      g_object_set (self->priv->component[c], "forced-candidate", forced, NULL);
      fs_candidate_destroy (forced);
    }

    next_port = used_port+1;
  }

  g_free ((gpointer *)ips);
  g_free (ports);

  return TRUE;

 error:
  g_free ((gpointer *)ips);
  g_free (ports);

  return FALSE;
}


static void
fs_rawudp_stream_transmitter_stop (FsStreamTransmitter *streamtransmitter)
{
  FsRawUdpStreamTransmitter *self =
    FS_RAWUDP_STREAM_TRANSMITTER (streamtransmitter);
  gint c;

  if (self->priv->component)
  {
    for (c = 1; c <= self->priv->transmitter->components; c++)
    {
      if (self->priv->component[c])
        fs_rawudp_component_stop (self->priv->component[c]);
    }
  }
}


static gboolean
fs_rawudp_stream_transmitter_force_remote_candidates (
    FsStreamTransmitter *streamtransmitter, GList *candidates,
    GError **error)
{
  FsRawUdpStreamTransmitter *self =
    FS_RAWUDP_STREAM_TRANSMITTER (streamtransmitter);
  GList *item = NULL;

  for (item = candidates; item; item = g_list_next (item))
  {
    FsCandidate *candidate = item->data;

    if (candidate->proto != FS_NETWORK_PROTOCOL_UDP)
    {
      g_set_error (error, FS_ERROR, FS_ERROR_INVALID_ARGUMENTS,
          "You set a candidate of a type %d that is not  FS_NETWORK_PROTOCOL_UDP",
          candidate->proto);
      return FALSE;
    }

    if (!candidate->ip)
    {
      g_set_error (error, FS_ERROR, FS_ERROR_INVALID_ARGUMENTS,
          "The candidate passed does not contain a valid ip");
      return FALSE;
    }

    if (candidate->component_id == 0 ||
        candidate->component_id > self->priv->transmitter->components)
    {
      g_set_error (error, FS_ERROR, FS_ERROR_INVALID_ARGUMENTS,
          "The candidate passed has an invalid component id %u (not in [1,%u])",
          candidate->component_id, self->priv->transmitter->components);
      return FALSE;
    }

    /*
     * IMPROVE ME: We should probably check that the candidate's IP
     *  has the format x.x.x.x where x is [0,255] using GRegex, etc
     */

  }

  for (item = candidates; item; item = g_list_next (item))
  {
    FsCandidate *candidate = item->data;
    if (!fs_rawudp_component_set_remote_candidate (
            self->priv->component[candidate->component_id],
            candidate, error))
      return FALSE;
  }

  return TRUE;
}


FsRawUdpStreamTransmitter *
fs_rawudp_stream_transmitter_newv (FsRawUdpTransmitter *transmitter,
    guint n_parameters, GParameter *parameters, GError **error)
{
  FsRawUdpStreamTransmitter *streamtransmitter = NULL;

  streamtransmitter = g_object_newv (FS_TYPE_RAWUDP_STREAM_TRANSMITTER,
      n_parameters, parameters);

  if (!streamtransmitter)
  {
    g_set_error (error, FS_ERROR, FS_ERROR_CONSTRUCTION,
        "Could not build the stream transmitter");
    return NULL;
  }

  streamtransmitter->priv->transmitter = transmitter;

  if (!fs_rawudp_stream_transmitter_build (streamtransmitter, error))
  {
    g_object_unref (streamtransmitter);
    return NULL;
  }

  return streamtransmitter;
}

struct CandidateTransit {
  FsRawUdpStreamTransmitter *self;
  FsCandidate *candidate;
  guint component_id;
};

static FsCandidate *
fs_rawudp_stream_transmitter_build_forced_candidate (
    FsRawUdpStreamTransmitter *self, const char *ip, gint port,
    guint component_id)
{
  FsCandidate *candidate;
  gchar *id;

  id = g_strdup_printf ("L%u",
      self->priv->next_candidate_id++);
  candidate = fs_candidate_new (id, component_id, FS_CANDIDATE_TYPE_HOST,
      FS_NETWORK_PROTOCOL_UDP, ip, port);
  g_free (id);

  return candidate;
}


static gboolean
fs_rawudp_stream_transmitter_gather_local_candidates (
    FsStreamTransmitter *streamtransmitter,
    GError **error)
{
  FsRawUdpStreamTransmitter *self =
    FS_RAWUDP_STREAM_TRANSMITTER (streamtransmitter);
  int c;

  for (c = 1; c <= self->priv->transmitter->components; c++)
    if (!fs_rawudp_component_gather_local_candidates (self->priv->component[c],
            error))
      return FALSE;

  return TRUE;
}

static void
_component_new_local_candidate (FsRawUdpComponent *component,
    FsCandidate *candidate, gpointer user_data)
{
  FsRawUdpStreamTransmitter *self = FS_RAWUDP_STREAM_TRANSMITTER (user_data);

  g_signal_emit_by_name (self, "new-local-candidate", candidate);
}

static void
_component_local_candidates_prepared (FsRawUdpComponent *component,
    gpointer user_data)
{
  FsRawUdpStreamTransmitter *self = FS_RAWUDP_STREAM_TRANSMITTER (user_data);
  guint component_id;
  guint c;
  gboolean emit = TRUE;

  g_object_get (component, "component", &component_id, NULL);

  FS_RAWUDP_STREAM_TRANSMITTER_LOCK (self);
  self->priv->candidates_prepared[component_id] = TRUE;

  for (c = 1; c <= self->priv->transmitter->components; c++)
  {
    if (self->priv->candidates_prepared[c] == FALSE)
    {
      emit = FALSE;
      break;
    }
  }

  FS_RAWUDP_STREAM_TRANSMITTER_UNLOCK (self);

  if (emit)
    g_signal_emit_by_name (self, "local-candidates-prepared");
}

static void
_component_new_active_candidate_pair (FsRawUdpComponent *component,
    FsCandidate *local, FsCandidate *remote, gpointer user_data)
{
  FsRawUdpStreamTransmitter *self = FS_RAWUDP_STREAM_TRANSMITTER (user_data);

  g_signal_emit_by_name (self, "new-active-candidate-pair", local, remote);
}



static void
_component_error (FsRawUdpComponent *component,
    FsError error_no, gchar *error_msg, gpointer user_data)
{
  FsRawUdpStreamTransmitter *self = FS_RAWUDP_STREAM_TRANSMITTER (user_data);

  fs_stream_transmitter_emit_error (FS_STREAM_TRANSMITTER (self), error_no,
      error_msg);
}

static void
_component_known_source_packet_received (FsRawUdpComponent *component,
    guint component_id, GstBuffer *buffer, gpointer user_data)
{
  FsRawUdpStreamTransmitter *self = FS_RAWUDP_STREAM_TRANSMITTER (user_data);

  g_signal_emit_by_name (self, "known-source-packet-received", component_id,
      buffer);
}

