gstreamer/gst/rtmp2/rtmp/rtmphandshake.c
Matthew Waters 640a65bf96 gst: don't use volatile to mean atomic
volatile is not sufficient to provide atomic guarantees and real atomics
should be used instead.  GCC 11 has started warning about using volatile
with atomic operations.

https://gitlab.gnome.org/GNOME/glib/-/merge_requests/1719

Discovered in https://gitlab.freedesktop.org/gstreamer/gst-plugins-good/-/issues/868

Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-plugins-bad/-/merge_requests/2098>
2021-03-22 14:34:36 +11:00

311 lines
8.3 KiB
C

/* GStreamer RTMP Library
* Copyright (C) 2017 Make.TV, Inc. <info@make.tv>
* Contact: Jan Alexander Steffens (heftig) <jsteffens@make.tv>
*
* 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.
*/
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include "rtmphandshake.h"
#include "rtmputils.h"
#include <gst/gst.h>
#include <string.h>
GST_DEBUG_CATEGORY_STATIC (gst_rtmp_handshake_debug_category);
#define GST_CAT_DEFAULT gst_rtmp_handshake_debug_category
static void
init_debug (void)
{
static gsize done = 0;
if (g_once_init_enter (&done)) {
GST_DEBUG_CATEGORY_INIT (gst_rtmp_handshake_debug_category, "rtmphandshake",
0, "debug category for the rtmp connection handshake");
g_once_init_leave (&done, 1);
}
}
static void client_handshake1_done (GObject * source, GAsyncResult * result,
gpointer user_data);
static void client_handshake2_done (GObject * source, GAsyncResult * result,
gpointer user_data);
static void client_handshake3_done (GObject * source, GAsyncResult * result,
gpointer user_data);
static inline void
serialize_u8 (GByteArray * array, guint8 value)
{
g_byte_array_append (array, (guint8 *) & value, sizeof value);
}
static inline void
serialize_u32 (GByteArray * array, guint32 value)
{
value = GUINT32_TO_BE (value);
g_byte_array_append (array, (guint8 *) & value, sizeof value);
}
#define SIZE_P0 1
#define SIZE_P1 1536
#define SIZE_P2 SIZE_P1
#define SIZE_P0P1 (SIZE_P0 + SIZE_P1)
#define SIZE_P0P1P2 (SIZE_P0P1 + SIZE_P2)
#define SIZE_RANDOM (SIZE_P1 - 8)
typedef struct
{
GBytes *random_bytes;
gboolean strict;
} HandshakeData;
static GBytes *
handshake_random_data (void)
{
G_STATIC_ASSERT (SIZE_RANDOM % 4 == 0);
GByteArray *ba = g_byte_array_sized_new (SIZE_RANDOM);
gint i;
for (i = 0; i < SIZE_RANDOM; i += 4) {
serialize_u32 (ba, g_random_int ());
}
return g_byte_array_free_to_bytes (ba);
}
static HandshakeData *
handshake_data_new (gboolean strict)
{
HandshakeData *data = g_slice_new0 (HandshakeData);
data->random_bytes = handshake_random_data ();
data->strict = strict;
return data;
}
static void
handshake_data_free (gpointer ptr)
{
HandshakeData *data = ptr;
g_clear_pointer (&data->random_bytes, g_bytes_unref);
g_slice_free (HandshakeData, data);
}
static gboolean
handshake_data_check (HandshakeData * data, const guint8 * p2)
{
const guint8 *ourrandom = g_bytes_get_data (data->random_bytes, NULL);
return memcmp (ourrandom, p2 + 8, SIZE_P2 - 8) == 0;
}
static GBytes *
create_c0c1 (GBytes * random_bytes)
{
GByteArray *ba = g_byte_array_sized_new (SIZE_P0P1);
/* C0 version */
serialize_u8 (ba, 3);
/* C1 time */
serialize_u32 (ba, g_get_monotonic_time () / 1000);
/* C1 zero */
serialize_u32 (ba, 0);
/* C1 random data */
gst_rtmp_byte_array_append_bytes (ba, random_bytes);
GST_DEBUG ("Sending C0+C1");
GST_MEMDUMP (">>> C0", ba->data, SIZE_P0);
GST_MEMDUMP (">>> C1", ba->data + SIZE_P0, SIZE_P1);
return g_byte_array_free_to_bytes (ba);
}
void
gst_rtmp_client_handshake (GIOStream * stream, gboolean strict,
GCancellable * cancellable, GAsyncReadyCallback callback,
gpointer user_data)
{
GTask *task;
HandshakeData *data;
g_return_if_fail (G_IS_IO_STREAM (stream));
init_debug ();
GST_INFO ("Starting client handshake");
task = g_task_new (stream, cancellable, callback, user_data);
data = handshake_data_new (strict);
g_task_set_task_data (task, data, handshake_data_free);
{
GOutputStream *os = g_io_stream_get_output_stream (stream);
GBytes *bytes = create_c0c1 (data->random_bytes);
gst_rtmp_output_stream_write_all_bytes_async (os,
bytes, G_PRIORITY_DEFAULT,
g_task_get_cancellable (task), client_handshake1_done, task);
g_bytes_unref (bytes);
}
}
static void
client_handshake1_done (GObject * source, GAsyncResult * result,
gpointer user_data)
{
GOutputStream *os = G_OUTPUT_STREAM (source);
GTask *task = user_data;
GIOStream *stream = g_task_get_source_object (task);
GInputStream *is = g_io_stream_get_input_stream (stream);
GError *error = NULL;
gboolean res;
res = gst_rtmp_output_stream_write_all_bytes_finish (os, result, &error);
if (!res) {
GST_ERROR ("Failed to send C0+C1: %s", error->message);
g_task_return_error (task, error);
g_object_unref (task);
return;
}
GST_DEBUG ("Sent C0+C1, waiting for S0+S1+S2");
gst_rtmp_input_stream_read_all_bytes_async (is, SIZE_P0P1P2,
G_PRIORITY_DEFAULT, g_task_get_cancellable (task),
client_handshake2_done, task);
}
static GBytes *
create_c2 (const guint8 * s0s1s2)
{
G_STATIC_ASSERT (SIZE_P1 == SIZE_P2);
GByteArray *ba = g_byte_array_sized_new (SIZE_P2);
gint64 c2time = g_get_monotonic_time ();
/* Copy S1 to C2 */
g_byte_array_set_size (ba, SIZE_P2);
memcpy (ba->data, s0s1s2 + SIZE_P0, SIZE_P1);
/* C2 time2 */
GST_WRITE_UINT32_BE (ba->data + 4, c2time / 1000);
GST_DEBUG ("Sending C2");
GST_MEMDUMP (">>> C2", ba->data, SIZE_P2);
return g_byte_array_free_to_bytes (ba);
}
static void
client_handshake2_done (GObject * source, GAsyncResult * result,
gpointer user_data)
{
GInputStream *is = G_INPUT_STREAM (source);
GTask *task = user_data;
GIOStream *stream = g_task_get_source_object (task);
HandshakeData *data = g_task_get_task_data (task);
GError *error = NULL;
GBytes *res;
const guint8 *s0s1s2;
gsize size;
res = gst_rtmp_input_stream_read_all_bytes_finish (is, result, &error);
if (!res) {
GST_ERROR ("Failed to read S0+S1+S2: %s", error->message);
g_task_return_error (task, error);
g_object_unref (task);
return;
}
s0s1s2 = g_bytes_get_data (res, &size);
if (size < SIZE_P0P1P2) {
GST_ERROR ("Short read (want %d have %" G_GSIZE_FORMAT ")", SIZE_P0P1P2,
size);
g_task_return_new_error (task, G_IO_ERROR, G_IO_ERROR_PARTIAL_INPUT,
"Short read (want %d have %" G_GSIZE_FORMAT ")", SIZE_P0P1P2, size);
g_object_unref (task);
goto out;
}
GST_DEBUG ("Got S0+S1+S2");
GST_MEMDUMP ("<<< S0", s0s1s2, SIZE_P0);
GST_MEMDUMP ("<<< S1", s0s1s2 + SIZE_P0, SIZE_P1);
GST_MEMDUMP ("<<< S2", s0s1s2 + SIZE_P0P1, SIZE_P2);
if (handshake_data_check (data, s0s1s2 + SIZE_P0P1)) {
GST_DEBUG ("S2 random data matches C1");
} else {
if (data->strict) {
GST_ERROR ("Handshake response data did not match");
g_task_return_new_error (task, G_IO_ERROR, G_IO_ERROR_INVALID_DATA,
"Handshake response data did not match");
g_object_unref (task);
goto out;
}
GST_WARNING ("Handshake reponse data did not match; continuing anyway");
}
{
GOutputStream *os = g_io_stream_get_output_stream (stream);
GBytes *bytes = create_c2 (s0s1s2);
gst_rtmp_output_stream_write_all_bytes_async (os,
bytes, G_PRIORITY_DEFAULT,
g_task_get_cancellable (task), client_handshake3_done, task);
g_bytes_unref (bytes);
}
out:
g_bytes_unref (res);
}
static void
client_handshake3_done (GObject * source, GAsyncResult * result,
gpointer user_data)
{
GOutputStream *os = G_OUTPUT_STREAM (source);
GTask *task = user_data;
GError *error = NULL;
gboolean res;
res = gst_rtmp_output_stream_write_all_bytes_finish (os, result, &error);
if (!res) {
GST_ERROR ("Failed to send C2: %s", error->message);
g_task_return_error (task, error);
g_object_unref (task);
return;
}
GST_DEBUG ("Sent C2");
GST_INFO ("Client handshake finished");
g_task_return_boolean (task, TRUE);
g_object_unref (task);
}
gboolean
gst_rtmp_client_handshake_finish (GIOStream * stream, GAsyncResult * result,
GError ** error)
{
g_return_val_if_fail (g_task_is_valid (result, stream), FALSE);
return g_task_propagate_boolean (G_TASK (result), error);
}