/* GStreamer
 * Copyright (C) 2005 Andy Wingo <wingo@pobox.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., 59 Temple Place - Suite 330,
 * Boston, MA 02111-1307, USA.
 */
/**
 * SECTION:gstnettimepacket
 * @short_description: Helper structure to construct clock packets used
 *                     by network clocks.
 * @see_also: #GstClock, #GstNetClientClock, #GstNetTimeProvider
 *
 * Various functions for receiving, sending an serializing #GstNetTimePacket
 * structures.
 *
 * Last reviewed on 2005-11-23 (0.9.5)
 */

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

#include <glib.h>

#ifdef __CYGWIN__
# include <unistd.h>
# include <fcntl.h>
#endif

#include "gstnettimepacket.h"


/**
 * gst_net_time_packet_new:
 * @buffer: a buffer from which to construct the packet, or NULL
 *
 * Creates a new #GstNetTimePacket from a buffer received over the network. The
 * caller is responsible for ensuring that @buffer is at least
 * #GST_NET_TIME_PACKET_SIZE bytes long.
 *
 * If @buffer is #NULL, the local and remote times will be set to
 * #GST_CLOCK_TIME_NONE.
 *
 * MT safe. Caller owns return value (g_free to free).
 *
 * Returns: The new #GstNetTimePacket.
 */
#ifdef __SYMBIAN32__
EXPORT_C
#endif

GstNetTimePacket *
gst_net_time_packet_new (const guint8 * buffer)
{
  GstNetTimePacket *ret;

  g_assert (sizeof (GstClockTime) == 8);

  ret = g_new0 (GstNetTimePacket, 1);

  if (buffer) {
    ret->local_time = GST_READ_UINT64_BE (buffer);
    ret->remote_time = GST_READ_UINT64_BE (buffer + sizeof (GstClockTime));
  } else {
    ret->local_time = GST_CLOCK_TIME_NONE;
    ret->remote_time = GST_CLOCK_TIME_NONE;
  }

  return ret;
}

/**
 * gst_net_time_packet_serialize:
 * @packet: the #GstNetTimePacket
 *
 * Serialized a #GstNetTimePacket into a newly-allocated sequence of
 * #GST_NET_TIME_PACKET_SIZE bytes, in network byte order. The value returned is
 * suitable for passing to write(2) or sendto(2) for communication over the
 * network.
 *
 * MT safe. Caller owns return value (g_free to free).
 *
 * Returns: A newly allocated sequence of #GST_NET_TIME_PACKET_SIZE bytes.
 */
#ifdef __SYMBIAN32__
EXPORT_C
#endif

guint8 *
gst_net_time_packet_serialize (const GstNetTimePacket * packet)
{
  guint8 *ret;

  g_assert (sizeof (GstClockTime) == 8);

  ret = g_new0 (guint8, GST_NET_TIME_PACKET_SIZE);

  GST_WRITE_UINT64_BE (ret, packet->local_time);
  GST_WRITE_UINT64_BE (ret + sizeof (GstClockTime), packet->remote_time);

  return ret;
}

/**
 * gst_net_time_packet_receive:
 * @fd: a file descriptor created by socket(2)
 * @addr: a pointer to a sockaddr to hold the address of the sender
 * @len: a pointer to the size of the data pointed to by @addr
 *
 * Receives a #GstNetTimePacket over a socket. Handles interrupted system calls,
 * but otherwise returns NULL on error. See recvfrom(2) for more information on
 * how to interpret @sockaddr.
 *
 * MT safe. Caller owns return value (g_free to free).
 *
 * Returns: The new #GstNetTimePacket.
 */
#ifdef __SYMBIAN32__
EXPORT_C
#endif

GstNetTimePacket *
gst_net_time_packet_receive (gint fd, struct sockaddr * addr, socklen_t * len)
{
  guint8 buffer[GST_NET_TIME_PACKET_SIZE];
  gint ret;

  while (TRUE) {
#ifdef G_OS_WIN32
    ret = recvfrom (fd, (char *) buffer, GST_NET_TIME_PACKET_SIZE,
#else
    ret = recvfrom (fd, buffer, GST_NET_TIME_PACKET_SIZE,
#endif
        0, (struct sockaddr *) addr, len);
    if (ret < 0) {
      if (errno != EAGAIN && errno != EINTR)
        goto receive_error;
      else
        continue;
    } else if (ret < GST_NET_TIME_PACKET_SIZE) {
      goto short_packet;
    } else {
      return gst_net_time_packet_new (buffer);
    }
  }

receive_error:
  {
    GST_DEBUG ("receive error %d: %s (%d)", ret, g_strerror (errno), errno);
    return NULL;
  }
short_packet:
  {
    GST_DEBUG ("someone sent us a short packet (%d < %d)",
        ret, GST_NET_TIME_PACKET_SIZE);
    return NULL;
  }
}

/**
 * gst_net_time_packet_send:
 * @packet: the #GstNetTimePacket
 * @fd: a file descriptor created by socket(2)
 * @addr: a pointer to a sockaddr to hold the address of the sender
 * @len: the size of the data pointed to by @addr
 *
 * Sends a #GstNetTimePacket over a socket. Essentially a thin wrapper around
 * sendto(2) and gst_net_time_packet_serialize(). 
 *
 * MT safe.
 *
 * Returns: The return value of sendto(2).
 */
#ifdef __SYMBIAN32__
EXPORT_C
#endif

gint
gst_net_time_packet_send (const GstNetTimePacket * packet, gint fd,
    struct sockaddr * addr, socklen_t len)
{
#if defined __CYGWIN__
  gint fdflags;
#elif defined G_OS_WIN32
  gulong flags;
#endif

  guint8 *buffer;
  gint ret, send_flags;

  g_return_val_if_fail (packet != NULL, -EINVAL);

#ifdef __CYGWIN__
  send_flags = 0;
  fdflags = fcntl (fd, F_GETFL);
  fcntl (fd, F_SETFL, fdflags | O_NONBLOCK);
#elif defined G_OS_WIN32
  flags = 1;
  send_flags = 0;
#else
  send_flags = MSG_DONTWAIT;
#endif

  buffer = gst_net_time_packet_serialize (packet);

#ifdef G_OS_WIN32
  ioctlsocket (fd, FIONBIO, &flags);    /* Set nonblocking mode */
  ret =
      sendto (fd, (char *) buffer, GST_NET_TIME_PACKET_SIZE, send_flags, addr,
      len);
#else
  ret = sendto (fd, buffer, GST_NET_TIME_PACKET_SIZE, send_flags, addr, len);
#endif

#ifdef __CYGWIN__
  fcntl (fd, F_SETFL, fdflags);
#endif

  g_free (buffer);

  return ret;
}
