mirror of
https://gitlab.freedesktop.org/gstreamer/gstreamer.git
synced 2024-11-25 11:11:08 +00:00
376 lines
11 KiB
C
376 lines
11 KiB
C
/* GStreamer
|
|
* Copyright (C) 2005 Andy Wingo <wingo@pobox.com>
|
|
* Copyright (C) 2010 Tim-Philipp Müller <tim centricular net>
|
|
* Copyright (C) 2012 Collabora Ltd. <tim.muller@collabora.co.uk>
|
|
* Copyright (C) 2015 Sebastian Dröge <sebastian@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.
|
|
*/
|
|
/* THIS IS A PRIVATE API
|
|
* SECTION:gstntppacket
|
|
* @short_description: Helper structure to construct clock packets used
|
|
* by network clocks for NTPv4.
|
|
* @see_also: #GstClock, #GstNetClientClock, #GstNtpClock
|
|
*
|
|
* Various functions for receiving, sending an serializing #GstNtpPacket
|
|
* structures.
|
|
*/
|
|
|
|
/* FIXME 2.0: Merge this with GstNetTimePacket! */
|
|
|
|
#ifdef HAVE_CONFIG_H
|
|
#include "config.h"
|
|
#endif
|
|
|
|
#include <glib.h>
|
|
|
|
#ifdef __CYGWIN__
|
|
# include <unistd.h>
|
|
# include <fcntl.h>
|
|
#endif
|
|
|
|
#include <gst/gst.h>
|
|
#include <string.h>
|
|
|
|
#include "gstntppacket.h"
|
|
|
|
G_DEFINE_BOXED_TYPE (GstNtpPacket, gst_ntp_packet,
|
|
gst_ntp_packet_copy, gst_ntp_packet_free);
|
|
|
|
|
|
static inline GstClockTime
|
|
ntp_timestamp_to_gst_clock_time (guint32 seconds, guint32 fraction)
|
|
{
|
|
return gst_util_uint64_scale (seconds, GST_SECOND, 1) +
|
|
gst_util_uint64_scale (fraction, GST_SECOND,
|
|
G_GUINT64_CONSTANT (1) << 32);
|
|
}
|
|
|
|
static inline guint32
|
|
gst_clock_time_to_ntp_timestamp_seconds (GstClockTime gst)
|
|
{
|
|
GstClockTime seconds = gst_util_uint64_scale (gst, 1, GST_SECOND);
|
|
|
|
return seconds;
|
|
}
|
|
|
|
static inline guint32
|
|
gst_clock_time_to_ntp_timestamp_fraction (GstClockTime gst)
|
|
{
|
|
GstClockTime seconds = gst_util_uint64_scale (gst, 1, GST_SECOND);
|
|
|
|
return gst_util_uint64_scale (gst - seconds, G_GUINT64_CONSTANT (1) << 32,
|
|
GST_SECOND);
|
|
}
|
|
|
|
/**
|
|
* gst_ntp_packet_new:
|
|
* @buffer: (array): a buffer from which to construct the packet, or NULL
|
|
* @error: a #GError
|
|
*
|
|
* Creates a new #GstNtpPacket from a buffer received over the network. The
|
|
* caller is responsible for ensuring that @buffer is at least
|
|
* #GST_NTP_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 (gst_ntp_packet_free to free).
|
|
*
|
|
* Returns: The new #GstNtpPacket.
|
|
*/
|
|
GstNtpPacket *
|
|
gst_ntp_packet_new (const guint8 * buffer, GError ** error)
|
|
{
|
|
GstNtpPacket *ret;
|
|
|
|
g_assert (sizeof (GstClockTime) == 8);
|
|
|
|
if (buffer) {
|
|
guint8 version = (buffer[0] >> 3) & 0x7;
|
|
guint8 stratum = buffer[1];
|
|
gint8 poll_interval = buffer[2];
|
|
|
|
if (version != 4) {
|
|
g_set_error (error, GST_NTP_ERROR, GST_NTP_ERROR_WRONG_VERSION,
|
|
"Invalid NTP version %d", version);
|
|
return NULL;
|
|
}
|
|
|
|
/* Kiss-o'-Death packet! */
|
|
if (stratum == 0) {
|
|
gchar code[5] = { buffer[3 * 4 + 0], buffer[3 * 4 + 1], buffer[3 * 4 + 2],
|
|
buffer[3 * 4 + 3], 0
|
|
};
|
|
|
|
/* AUTH, AUTO, CRYP, DENY, RSTR, NKEY => DENY */
|
|
if (strcmp (code, "AUTH") == 0 ||
|
|
strcmp (code, "AUTO") == 0 ||
|
|
strcmp (code, "CRYP") == 0 ||
|
|
strcmp (code, "DENY") == 0 ||
|
|
strcmp (code, "RSTR") == 0 || strcmp (code, "NKEY") == 0) {
|
|
g_set_error (error, GST_NTP_ERROR, GST_NTP_ERROR_KOD_DENY,
|
|
"Kiss-o'-Death denied '%s'", code);
|
|
} else if (strcmp (code, "RATE") == 0) {
|
|
g_set_error (error, GST_NTP_ERROR, GST_NTP_ERROR_KOD_RATE,
|
|
"Kiss-o'-Death '%s'", code);
|
|
} else {
|
|
g_set_error (error, GST_NTP_ERROR, GST_NTP_ERROR_KOD_UNKNOWN,
|
|
"Kiss-o'-Death unknown '%s'", code);
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
ret = g_new0 (GstNtpPacket, 1);
|
|
ret->origin_time =
|
|
ntp_timestamp_to_gst_clock_time (GST_READ_UINT32_BE (buffer + 6 * 4),
|
|
GST_READ_UINT32_BE (buffer + 7 * 4));
|
|
ret->receive_time =
|
|
ntp_timestamp_to_gst_clock_time (GST_READ_UINT32_BE (buffer + 8 * 4),
|
|
GST_READ_UINT32_BE (buffer + 9 * 4));
|
|
ret->transmit_time =
|
|
ntp_timestamp_to_gst_clock_time (GST_READ_UINT32_BE (buffer + 10 * 4),
|
|
GST_READ_UINT32_BE (buffer + 11 * 4));
|
|
|
|
/* Wireshark considers everything >= 3 as invalid */
|
|
if (poll_interval >= 3)
|
|
ret->poll_interval = GST_CLOCK_TIME_NONE;
|
|
else if (poll_interval >= 0)
|
|
ret->poll_interval = GST_SECOND << poll_interval;
|
|
else
|
|
ret->poll_interval = GST_SECOND >> (-poll_interval);
|
|
} else {
|
|
ret = g_new0 (GstNtpPacket, 1);
|
|
ret->origin_time = 0;
|
|
ret->receive_time = 0;
|
|
ret->transmit_time = 0;
|
|
ret->poll_interval = 0;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
/**
|
|
* gst_ntp_packet_free:
|
|
* @packet: the #GstNtpPacket
|
|
*
|
|
* Free @packet.
|
|
*/
|
|
void
|
|
gst_ntp_packet_free (GstNtpPacket * packet)
|
|
{
|
|
g_free (packet);
|
|
}
|
|
|
|
/**
|
|
* gst_ntp_packet_copy:
|
|
* @packet: the #GstNtpPacket
|
|
*
|
|
* Make a copy of @packet.
|
|
*
|
|
* Returns: a copy of @packet, free with gst_ntp_packet_free().
|
|
*/
|
|
GstNtpPacket *
|
|
gst_ntp_packet_copy (const GstNtpPacket * packet)
|
|
{
|
|
GstNtpPacket *ret;
|
|
|
|
ret = g_new0 (GstNtpPacket, 1);
|
|
ret->origin_time = packet->origin_time;
|
|
ret->receive_time = packet->receive_time;
|
|
ret->transmit_time = packet->transmit_time;
|
|
|
|
return ret;
|
|
}
|
|
|
|
/**
|
|
* gst_ntp_packet_serialize:
|
|
* @packet: the #GstNtpPacket
|
|
*
|
|
* Serialized a #GstNtpPacket into a newly-allocated sequence of
|
|
* #GST_NTP_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_NTP_PACKET_SIZE bytes.
|
|
*/
|
|
guint8 *
|
|
gst_ntp_packet_serialize (const GstNtpPacket * packet)
|
|
{
|
|
guint8 *ret;
|
|
|
|
g_assert (sizeof (GstClockTime) == 8);
|
|
|
|
ret = g_new0 (guint8, GST_NTP_PACKET_SIZE);
|
|
/* Leap Indicator: unknown
|
|
* Version: 4
|
|
* Mode: Client
|
|
*/
|
|
ret[0] = (3 << 6) | (4 << 3) | (3 << 0);
|
|
/* Stratum: unsynchronized */
|
|
ret[1] = 16;
|
|
/* Polling interval: invalid */
|
|
ret[2] = 3;
|
|
/* Precision: 0 */
|
|
ret[3] = 0;
|
|
/* Root delay: 0 */
|
|
GST_WRITE_UINT32_BE (ret + 4, 0);
|
|
/* Root disperson: 0 */
|
|
GST_WRITE_UINT32_BE (ret + 2 * 4, 0);
|
|
/* Reference ID: \0 */
|
|
GST_WRITE_UINT32_BE (ret + 3 * 4, 0);
|
|
/* Reference Timestamp: 0 */
|
|
GST_WRITE_UINT32_BE (ret + 4 * 4, 0);
|
|
GST_WRITE_UINT32_BE (ret + 5 * 4, 0);
|
|
/* Origin timestamp (local time) */
|
|
GST_WRITE_UINT32_BE (ret + 6 * 4,
|
|
gst_clock_time_to_ntp_timestamp_seconds (packet->origin_time));
|
|
GST_WRITE_UINT32_BE (ret + 7 * 4,
|
|
gst_clock_time_to_ntp_timestamp_fraction (packet->origin_time));
|
|
/* Receive timestamp (remote time) */
|
|
GST_WRITE_UINT32_BE (ret + 8 * 4,
|
|
gst_clock_time_to_ntp_timestamp_seconds (packet->receive_time));
|
|
GST_WRITE_UINT32_BE (ret + 9 * 4,
|
|
gst_clock_time_to_ntp_timestamp_fraction (packet->receive_time));
|
|
/* Transmit timestamp (remote time) */
|
|
GST_WRITE_UINT32_BE (ret + 10 * 4,
|
|
gst_clock_time_to_ntp_timestamp_seconds (packet->transmit_time));
|
|
GST_WRITE_UINT32_BE (ret + 11 * 4,
|
|
gst_clock_time_to_ntp_timestamp_fraction (packet->transmit_time));
|
|
|
|
return ret;
|
|
}
|
|
|
|
/**
|
|
* gst_ntp_packet_receive:
|
|
* @socket: socket to receive the time packet on
|
|
* @src_address: (out): address of variable to return sender address
|
|
* @error: return address for a #GError, or NULL
|
|
*
|
|
* Receives a #GstNtpPacket over a socket. Handles interrupted system
|
|
* calls, but otherwise returns NULL on error.
|
|
*
|
|
* Returns: (transfer full): a new #GstNtpPacket, or NULL on error. Free
|
|
* with gst_ntp_packet_free() when done.
|
|
*/
|
|
GstNtpPacket *
|
|
gst_ntp_packet_receive (GSocket * socket,
|
|
GSocketAddress ** src_address, GError ** error)
|
|
{
|
|
gchar buffer[GST_NTP_PACKET_SIZE];
|
|
GError *err = NULL;
|
|
gssize ret;
|
|
|
|
g_return_val_if_fail (G_IS_SOCKET (socket), FALSE);
|
|
g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
|
|
|
|
while (TRUE) {
|
|
ret = g_socket_receive_from (socket, src_address, buffer,
|
|
GST_NTP_PACKET_SIZE, NULL, &err);
|
|
|
|
if (ret < 0) {
|
|
if (err->code == G_IO_ERROR_WOULD_BLOCK) {
|
|
g_error_free (err);
|
|
err = NULL;
|
|
continue;
|
|
} else {
|
|
goto receive_error;
|
|
}
|
|
} else if (ret < GST_NTP_PACKET_SIZE) {
|
|
goto short_packet;
|
|
} else {
|
|
return gst_ntp_packet_new ((const guint8 *) buffer, error);
|
|
}
|
|
}
|
|
|
|
receive_error:
|
|
{
|
|
GST_DEBUG ("receive error: %s", err->message);
|
|
g_propagate_error (error, err);
|
|
return NULL;
|
|
}
|
|
short_packet:
|
|
{
|
|
GST_DEBUG ("someone sent us a short packet (%" G_GSSIZE_FORMAT " < %d)",
|
|
ret, GST_NTP_PACKET_SIZE);
|
|
g_set_error (error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA,
|
|
"short time packet (%d < %d)", (int) ret, GST_NTP_PACKET_SIZE);
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* gst_ntp_packet_send:
|
|
* @packet: the #GstNtpPacket to send
|
|
* @socket: socket to send the time packet on
|
|
* @dest_address: address to send the time packet to
|
|
* @error: return address for a #GError, or NULL
|
|
*
|
|
* Sends a #GstNtpPacket over a socket.
|
|
*
|
|
* MT safe.
|
|
*
|
|
* Returns: TRUE if successful, FALSE in case an error occurred.
|
|
*/
|
|
gboolean
|
|
gst_ntp_packet_send (const GstNtpPacket * packet,
|
|
GSocket * socket, GSocketAddress * dest_address, GError ** error)
|
|
{
|
|
gboolean was_blocking;
|
|
guint8 *buffer;
|
|
gssize res;
|
|
|
|
g_return_val_if_fail (packet != NULL, FALSE);
|
|
g_return_val_if_fail (G_IS_SOCKET (socket), FALSE);
|
|
g_return_val_if_fail (G_IS_SOCKET_ADDRESS (dest_address), FALSE);
|
|
g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
|
|
|
|
was_blocking = g_socket_get_blocking (socket);
|
|
|
|
if (was_blocking)
|
|
g_socket_set_blocking (socket, FALSE);
|
|
|
|
/* FIXME: avoid pointless alloc/free, serialise into stack-allocated buffer */
|
|
buffer = gst_ntp_packet_serialize (packet);
|
|
|
|
res = g_socket_send_to (socket, dest_address, (const gchar *) buffer,
|
|
GST_NTP_PACKET_SIZE, NULL, error);
|
|
|
|
/* datagram packets should be sent as a whole or not at all */
|
|
g_assert (res < 0 || res == GST_NTP_PACKET_SIZE);
|
|
|
|
g_free (buffer);
|
|
|
|
if (was_blocking)
|
|
g_socket_set_blocking (socket, TRUE);
|
|
|
|
return (res == GST_NTP_PACKET_SIZE);
|
|
}
|
|
|
|
GQuark
|
|
gst_ntp_error_quark (void)
|
|
{
|
|
static GQuark quark;
|
|
|
|
/* Thread-safe because GQuark is */
|
|
if (!quark)
|
|
quark = g_quark_from_static_string ("gst-ntp-error-quark");
|
|
|
|
return quark;
|
|
}
|