net: GST_EXPORT -> GST_NET_API
[platform/upstream/gstreamer.git] / libs / gst / net / gstntppacket.c
1 /* GStreamer
2  * Copyright (C) 2005 Andy Wingo <wingo@pobox.com>
3  * Copyright (C) 2010 Tim-Philipp Müller <tim centricular net>
4  * Copyright (C) 2012 Collabora Ltd. <tim.muller@collabora.co.uk>
5  * Copyright (C) 2015 Sebastian Dröge <sebastian@centricular.com>
6  *
7  * This library is free software; you can redistribute it and/or
8  * modify it under the terms of the GNU Library General Public
9  * License as published by the Free Software Foundation; either
10  * version 2 of the License, or (at your option) any later version.
11  *
12  * This library is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15  * Library General Public License for more details.
16  *
17  * You should have received a copy of the GNU Library General Public
18  * License along with this library; if not, write to the
19  * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
20  * Boston, MA 02110-1301, USA.
21  */
22 /* THIS IS A PRIVATE API
23  * SECTION:gstntppacket
24  * @short_description: Helper structure to construct clock packets used
25  *                     by network clocks for NTPv4.
26  * @see_also: #GstClock, #GstNetClientClock, #GstNtpClock
27  *
28  * Various functions for receiving, sending an serializing #GstNtpPacket
29  * structures.
30  */
31
32 /* FIXME 2.0: Merge this with GstNetTimePacket! */
33
34 #ifdef HAVE_CONFIG_H
35 #include "config.h"
36 #endif
37
38 #include <glib.h>
39
40 #ifdef __CYGWIN__
41 # include <unistd.h>
42 # include <fcntl.h>
43 #endif
44
45 #include <gst/gst.h>
46 #include <string.h>
47
48 #include "gstntppacket.h"
49
50 G_DEFINE_BOXED_TYPE (GstNtpPacket, gst_ntp_packet,
51     gst_ntp_packet_copy, gst_ntp_packet_free);
52
53
54 static inline GstClockTime
55 ntp_timestamp_to_gst_clock_time (guint32 seconds, guint32 fraction)
56 {
57   return gst_util_uint64_scale (seconds, GST_SECOND, 1) +
58       gst_util_uint64_scale (fraction, GST_SECOND,
59       G_GUINT64_CONSTANT (1) << 32);
60 }
61
62 static inline guint32
63 gst_clock_time_to_ntp_timestamp_seconds (GstClockTime gst)
64 {
65   GstClockTime seconds = gst_util_uint64_scale (gst, 1, GST_SECOND);
66
67   return seconds;
68 }
69
70 static inline guint32
71 gst_clock_time_to_ntp_timestamp_fraction (GstClockTime gst)
72 {
73   GstClockTime seconds = gst_util_uint64_scale (gst, 1, GST_SECOND);
74
75   return gst_util_uint64_scale (gst - seconds, G_GUINT64_CONSTANT (1) << 32,
76       GST_SECOND);
77 }
78
79 /**
80  * gst_ntp_packet_new:
81  * @buffer: (array): a buffer from which to construct the packet, or NULL
82  * @error: a #GError
83  *
84  * Creates a new #GstNtpPacket from a buffer received over the network. The
85  * caller is responsible for ensuring that @buffer is at least
86  * #GST_NTP_PACKET_SIZE bytes long.
87  *
88  * If @buffer is %NULL, the local and remote times will be set to
89  * #GST_CLOCK_TIME_NONE.
90  *
91  * MT safe. Caller owns return value (gst_ntp_packet_free to free).
92  *
93  * Returns: The new #GstNtpPacket.
94  */
95 GstNtpPacket *
96 gst_ntp_packet_new (const guint8 * buffer, GError ** error)
97 {
98   GstNtpPacket *ret;
99
100   g_assert (sizeof (GstClockTime) == 8);
101
102   if (buffer) {
103     guint8 version = (buffer[0] >> 3) & 0x7;
104     guint8 stratum = buffer[1];
105     gint8 poll_interval = buffer[2];
106
107     if (version != 4) {
108       g_set_error (error, GST_NTP_ERROR, GST_NTP_ERROR_WRONG_VERSION,
109           "Invalid NTP version %d", version);
110       return NULL;
111     }
112
113     /* Kiss-o'-Death packet! */
114     if (stratum == 0) {
115       gchar code[5] = { buffer[3 * 4 + 0], buffer[3 * 4 + 1], buffer[3 * 4 + 2],
116         buffer[3 * 4 + 3], 0
117       };
118
119       /* AUTH, AUTO, CRYP, DENY, RSTR, NKEY => DENY */
120       if (strcmp (code, "AUTH") == 0 ||
121           strcmp (code, "AUTO") == 0 ||
122           strcmp (code, "CRYP") == 0 ||
123           strcmp (code, "DENY") == 0 ||
124           strcmp (code, "RSTR") == 0 || strcmp (code, "NKEY") == 0) {
125         g_set_error (error, GST_NTP_ERROR, GST_NTP_ERROR_KOD_DENY,
126             "Kiss-o'-Death denied '%s'", code);
127       } else if (strcmp (code, "RATE") == 0) {
128         g_set_error (error, GST_NTP_ERROR, GST_NTP_ERROR_KOD_RATE,
129             "Kiss-o'-Death '%s'", code);
130       } else {
131         g_set_error (error, GST_NTP_ERROR, GST_NTP_ERROR_KOD_UNKNOWN,
132             "Kiss-o'-Death unknown '%s'", code);
133       }
134
135       return NULL;
136     }
137
138     ret = g_new0 (GstNtpPacket, 1);
139     ret->origin_time =
140         ntp_timestamp_to_gst_clock_time (GST_READ_UINT32_BE (buffer + 6 * 4),
141         GST_READ_UINT32_BE (buffer + 7 * 4));
142     ret->receive_time =
143         ntp_timestamp_to_gst_clock_time (GST_READ_UINT32_BE (buffer + 8 * 4),
144         GST_READ_UINT32_BE (buffer + 9 * 4));
145     ret->transmit_time =
146         ntp_timestamp_to_gst_clock_time (GST_READ_UINT32_BE (buffer + 10 * 4),
147         GST_READ_UINT32_BE (buffer + 11 * 4));
148
149     /* Wireshark considers everything >= 3 as invalid */
150     if (poll_interval >= 3)
151       ret->poll_interval = GST_CLOCK_TIME_NONE;
152     else if (poll_interval >= 0)
153       ret->poll_interval = GST_SECOND << poll_interval;
154     else
155       ret->poll_interval = GST_SECOND >> (-poll_interval);
156   } else {
157     ret = g_new0 (GstNtpPacket, 1);
158     ret->origin_time = 0;
159     ret->receive_time = 0;
160     ret->transmit_time = 0;
161     ret->poll_interval = 0;
162   }
163
164   return ret;
165 }
166
167 /**
168  * gst_ntp_packet_free:
169  * @packet: the #GstNtpPacket
170  *
171  * Free @packet.
172  */
173 void
174 gst_ntp_packet_free (GstNtpPacket * packet)
175 {
176   g_free (packet);
177 }
178
179 /**
180  * gst_ntp_packet_copy:
181  * @packet: the #GstNtpPacket
182  *
183  * Make a copy of @packet.
184  *
185  * Returns: a copy of @packet, free with gst_ntp_packet_free().
186  */
187 GstNtpPacket *
188 gst_ntp_packet_copy (const GstNtpPacket * packet)
189 {
190   GstNtpPacket *ret;
191
192   ret = g_new0 (GstNtpPacket, 1);
193   ret->origin_time = packet->origin_time;
194   ret->receive_time = packet->receive_time;
195   ret->transmit_time = packet->transmit_time;
196
197   return ret;
198 }
199
200 /**
201  * gst_ntp_packet_serialize:
202  * @packet: the #GstNtpPacket
203  *
204  * Serialized a #GstNtpPacket into a newly-allocated sequence of
205  * #GST_NTP_PACKET_SIZE bytes, in network byte order. The value returned is
206  * suitable for passing to write(2) or sendto(2) for communication over the
207  * network.
208  *
209  * MT safe. Caller owns return value (g_free to free).
210  *
211  * Returns: A newly allocated sequence of #GST_NTP_PACKET_SIZE bytes.
212  */
213 guint8 *
214 gst_ntp_packet_serialize (const GstNtpPacket * packet)
215 {
216   guint8 *ret;
217
218   g_assert (sizeof (GstClockTime) == 8);
219
220   ret = g_new0 (guint8, GST_NTP_PACKET_SIZE);
221   /* Leap Indicator: unknown
222    * Version: 4
223    * Mode: Client
224    */
225   ret[0] = (3 << 6) | (4 << 3) | (3 << 0);
226   /* Stratum: unsynchronized */
227   ret[1] = 16;
228   /* Polling interval: invalid */
229   ret[2] = 3;
230   /* Precision: 0 */
231   ret[3] = 0;
232   /* Root delay: 0 */
233   GST_WRITE_UINT32_BE (ret + 4, 0);
234   /* Root disperson: 0 */
235   GST_WRITE_UINT32_BE (ret + 2 * 4, 0);
236   /* Reference ID: \0 */
237   GST_WRITE_UINT32_BE (ret + 3 * 4, 0);
238   /* Reference Timestamp: 0 */
239   GST_WRITE_UINT32_BE (ret + 4 * 4, 0);
240   GST_WRITE_UINT32_BE (ret + 5 * 4, 0);
241   /* Origin timestamp (local time) */
242   GST_WRITE_UINT32_BE (ret + 6 * 4,
243       gst_clock_time_to_ntp_timestamp_seconds (packet->origin_time));
244   GST_WRITE_UINT32_BE (ret + 7 * 4,
245       gst_clock_time_to_ntp_timestamp_fraction (packet->origin_time));
246   /* Receive timestamp (remote time) */
247   GST_WRITE_UINT32_BE (ret + 8 * 4,
248       gst_clock_time_to_ntp_timestamp_seconds (packet->receive_time));
249   GST_WRITE_UINT32_BE (ret + 9 * 4,
250       gst_clock_time_to_ntp_timestamp_fraction (packet->receive_time));
251   /* Transmit timestamp (remote time) */
252   GST_WRITE_UINT32_BE (ret + 10 * 4,
253       gst_clock_time_to_ntp_timestamp_seconds (packet->transmit_time));
254   GST_WRITE_UINT32_BE (ret + 11 * 4,
255       gst_clock_time_to_ntp_timestamp_fraction (packet->transmit_time));
256
257   return ret;
258 }
259
260 /**
261  * gst_ntp_packet_receive:
262  * @socket: socket to receive the time packet on
263  * @src_address: (out): address of variable to return sender address
264  * @error: return address for a #GError, or NULL
265  *
266  * Receives a #GstNtpPacket over a socket. Handles interrupted system
267  * calls, but otherwise returns NULL on error.
268  *
269  * Returns: (transfer full): a new #GstNtpPacket, or NULL on error. Free
270  *    with gst_ntp_packet_free() when done.
271  */
272 GstNtpPacket *
273 gst_ntp_packet_receive (GSocket * socket,
274     GSocketAddress ** src_address, GError ** error)
275 {
276   gchar buffer[GST_NTP_PACKET_SIZE];
277   GError *err = NULL;
278   gssize ret;
279
280   g_return_val_if_fail (G_IS_SOCKET (socket), FALSE);
281   g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
282
283   while (TRUE) {
284     ret = g_socket_receive_from (socket, src_address, buffer,
285         GST_NTP_PACKET_SIZE, NULL, &err);
286
287     if (ret < 0) {
288       if (err->code == G_IO_ERROR_WOULD_BLOCK) {
289         g_error_free (err);
290         err = NULL;
291         continue;
292       } else {
293         goto receive_error;
294       }
295     } else if (ret < GST_NTP_PACKET_SIZE) {
296       goto short_packet;
297     } else {
298       return gst_ntp_packet_new ((const guint8 *) buffer, error);
299     }
300   }
301
302 receive_error:
303   {
304     GST_DEBUG ("receive error: %s", err->message);
305     g_propagate_error (error, err);
306     return NULL;
307   }
308 short_packet:
309   {
310     GST_DEBUG ("someone sent us a short packet (%" G_GSSIZE_FORMAT " < %d)",
311         ret, GST_NTP_PACKET_SIZE);
312     g_set_error (error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA,
313         "short time packet (%d < %d)", (int) ret, GST_NTP_PACKET_SIZE);
314     return NULL;
315   }
316 }
317
318 /**
319  * gst_ntp_packet_send:
320  * @packet: the #GstNtpPacket to send
321  * @socket: socket to send the time packet on
322  * @dest_address: address to send the time packet to
323  * @error: return address for a #GError, or NULL
324  *
325  * Sends a #GstNtpPacket over a socket.
326  *
327  * MT safe.
328  *
329  * Returns: TRUE if successful, FALSE in case an error occurred.
330  */
331 gboolean
332 gst_ntp_packet_send (const GstNtpPacket * packet,
333     GSocket * socket, GSocketAddress * dest_address, GError ** error)
334 {
335   gboolean was_blocking;
336   guint8 *buffer;
337   gssize res;
338
339   g_return_val_if_fail (packet != NULL, FALSE);
340   g_return_val_if_fail (G_IS_SOCKET (socket), FALSE);
341   g_return_val_if_fail (G_IS_SOCKET_ADDRESS (dest_address), FALSE);
342   g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
343
344   was_blocking = g_socket_get_blocking (socket);
345
346   if (was_blocking)
347     g_socket_set_blocking (socket, FALSE);
348
349   /* FIXME: avoid pointless alloc/free, serialise into stack-allocated buffer */
350   buffer = gst_ntp_packet_serialize (packet);
351
352   res = g_socket_send_to (socket, dest_address, (const gchar *) buffer,
353       GST_NTP_PACKET_SIZE, NULL, error);
354
355   /* datagram packets should be sent as a whole or not at all */
356   g_assert (res < 0 || res == GST_NTP_PACKET_SIZE);
357
358   g_free (buffer);
359
360   if (was_blocking)
361     g_socket_set_blocking (socket, TRUE);
362
363   return (res == GST_NTP_PACKET_SIZE);
364 }
365
366 GQuark
367 gst_ntp_error_quark (void)
368 {
369   static GQuark quark;
370
371   /* Thread-safe because GQuark is */
372   if (!quark)
373     quark = g_quark_from_static_string ("gst-ntp-error-quark");
374
375   return quark;
376 }