mirror of
https://gitlab.freedesktop.org/gstreamer/gstreamer.git
synced 2024-12-28 03:00:35 +00:00
311 lines
8.3 KiB
C
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);
|
|
}
|