gstreamer/subprojects/gst-plugins-bad/gst/rtmp2/rtmp/amf.c
Jordan Petridis b6c577c70c rtmp2: reimplement librtmp's connection parameters for the connect packet
librtmp allows for attaching arbitrary AMF objects to the end of the
connect packet, and this is commonly used for authenticating with
servers.

Add a new property, extra-connect-args, that mimics librtmp's behavior.

Part-of: <https://gitlab.freedesktop.org/gstreamer/gstreamer/-/merge_requests/7054>
2024-08-13 21:50:17 +00:00

1240 lines
29 KiB
C

/* GStreamer RTMP Library
* Copyright (C) 2014 David Schleef <ds@schleef.org>
* 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 "amf.h"
#include "rtmputils.h"
#include <string.h>
#include <gst/gst.h>
#define MAX_RECURSION_DEPTH 16
GST_DEBUG_CATEGORY_STATIC (gst_rtmp_amf_debug_category);
#define GST_CAT_DEFAULT gst_rtmp_amf_debug_category
static GBytes *empty_bytes;
static void
init_static (void)
{
static gsize done = 0;
if (g_once_init_enter (&done)) {
empty_bytes = g_bytes_new_static ("", 0);
GST_DEBUG_CATEGORY_INIT (gst_rtmp_amf_debug_category, "rtmpamf", 0,
"debug category for the amf parser");
g_once_init_leave (&done, 1);
}
}
const gchar *
gst_amf_type_get_nick (GstAmfType type)
{
switch (type) {
case GST_AMF_TYPE_INVALID:
return "invalid";
case GST_AMF_TYPE_NUMBER:
return "number";
case GST_AMF_TYPE_BOOLEAN:
return "boolean";
case GST_AMF_TYPE_STRING:
return "string";
case GST_AMF_TYPE_OBJECT:
return "object";
case GST_AMF_TYPE_MOVIECLIP:
return "movieclip";
case GST_AMF_TYPE_NULL:
return "null";
case GST_AMF_TYPE_UNDEFINED:
return "undefined";
case GST_AMF_TYPE_REFERENCE:
return "reference";
case GST_AMF_TYPE_ECMA_ARRAY:
return "ecma-array";
case GST_AMF_TYPE_OBJECT_END:
return "object-end";
case GST_AMF_TYPE_STRICT_ARRAY:
return "strict-array";
case GST_AMF_TYPE_DATE:
return "date";
case GST_AMF_TYPE_LONG_STRING:
return "long-string";
case GST_AMF_TYPE_UNSUPPORTED:
return "unsupported";
case GST_AMF_TYPE_RECORDSET:
return "recordset";
case GST_AMF_TYPE_XML_DOCUMENT:
return "xml-document";
case GST_AMF_TYPE_TYPED_OBJECT:
return "typed-object";
case GST_AMF_TYPE_AVMPLUS_OBJECT:
return "avmplus-object";
default:
return "unknown";
}
}
typedef struct
{
gchar *name;
GstAmfNode *value;
} AmfObjectField;
static void
amf_object_field_clear (gpointer ptr)
{
AmfObjectField *field = ptr;
g_clear_pointer (&field->name, g_free);
g_clear_pointer (&field->value, gst_amf_node_free);
}
struct _GstAmfNode
{
GstAmfType type;
union
{
gint v_int;
gdouble v_double;
GBytes *v_bytes;
GArray *v_fields;
GPtrArray *v_elements;
} value;
};
static inline const AmfObjectField *
get_field (const GstAmfNode * node, guint index)
{
return &g_array_index (node->value.v_fields, const AmfObjectField, index);
}
static inline void
append_field (GstAmfNode * node, gchar * name, GstAmfNode * value)
{
AmfObjectField field = {
.name = name,
.value = value,
};
g_array_append_val (node->value.v_fields, field);
}
static inline const GstAmfNode *
get_element (const GstAmfNode * node, guint index)
{
return g_ptr_array_index (node->value.v_elements, index);
}
static inline void
append_element (GstAmfNode * node, GstAmfNode * value)
{
g_ptr_array_add (node->value.v_elements, value);
}
static GstAmfNode *
node_new (GstAmfType type)
{
GstAmfNode *node;
init_static ();
node = g_malloc0 (sizeof *node);
node->type = type;
switch (type) {
case GST_AMF_TYPE_STRING:
case GST_AMF_TYPE_LONG_STRING:
node->value.v_bytes = g_bytes_ref (empty_bytes);
break;
case GST_AMF_TYPE_OBJECT:
case GST_AMF_TYPE_ECMA_ARRAY:
node->value.v_fields =
g_array_new (FALSE, FALSE, sizeof (AmfObjectField));
g_array_set_clear_func (node->value.v_fields, amf_object_field_clear);
break;
case GST_AMF_TYPE_STRICT_ARRAY:
node->value.v_elements =
g_ptr_array_new_with_free_func (gst_amf_node_free);
break;
default:
break;
}
return node;
}
GstAmfNode *
gst_amf_node_new_null (void)
{
return node_new (GST_AMF_TYPE_NULL);
}
GstAmfNode *
gst_amf_node_new_boolean (gboolean value)
{
GstAmfNode *node = node_new (GST_AMF_TYPE_BOOLEAN);
node->value.v_int = !!value;
return node;
}
GstAmfNode *
gst_amf_node_new_number (gdouble value)
{
GstAmfNode *node = node_new (GST_AMF_TYPE_NUMBER);
node->value.v_double = value;
return node;
}
GstAmfNode *
gst_amf_node_new_string (const gchar * value, gssize size)
{
GstAmfNode *node = node_new (GST_AMF_TYPE_STRING);
gst_amf_node_set_string (node, value, size);
return node;
}
GstAmfNode *
gst_amf_node_new_take_string (gchar * value, gssize size)
{
GstAmfNode *node = node_new (GST_AMF_TYPE_STRING);
gst_amf_node_take_string (node, value, size);
return node;
}
GstAmfNode *
gst_amf_node_new_object (void)
{
return node_new (GST_AMF_TYPE_OBJECT);
}
GstAmfNode *
gst_amf_node_copy (const GstAmfNode * node)
{
GstAmfNode *copy;
g_return_val_if_fail (node, NULL);
copy = node_new (node->type);
switch (node->type) {
case GST_AMF_TYPE_STRING:
case GST_AMF_TYPE_LONG_STRING:
copy->value.v_bytes = g_bytes_ref (node->value.v_bytes);
break;
case GST_AMF_TYPE_OBJECT:
case GST_AMF_TYPE_ECMA_ARRAY:{
guint i, len = gst_amf_node_get_num_fields (node);
for (i = 0; i < len; i++) {
const AmfObjectField *field = get_field (node, i);
append_field (copy, g_strdup (field->name),
gst_amf_node_copy (field->value));
}
break;
}
case GST_AMF_TYPE_STRICT_ARRAY:{
guint i, len = gst_amf_node_get_num_elements (node);
for (i = 0; i < len; i++) {
const GstAmfNode *elem = get_element (node, i);
append_element (copy, gst_amf_node_copy (elem));
}
break;
}
default:
copy->value = node->value;
break;
}
return copy;
}
void
gst_amf_node_free (gpointer ptr)
{
GstAmfNode *node = ptr;
switch (node->type) {
case GST_AMF_TYPE_STRING:
case GST_AMF_TYPE_LONG_STRING:
g_bytes_unref (node->value.v_bytes);
break;
case GST_AMF_TYPE_OBJECT:
case GST_AMF_TYPE_ECMA_ARRAY:
g_array_unref (node->value.v_fields);
break;
case GST_AMF_TYPE_STRICT_ARRAY:
g_ptr_array_unref (node->value.v_elements);
break;
default:
break;
}
g_free (node);
}
GstAmfType
gst_amf_node_get_type (const GstAmfNode * node)
{
g_return_val_if_fail (node, GST_AMF_TYPE_INVALID);
return node->type;
}
gboolean
gst_amf_node_get_boolean (const GstAmfNode * node)
{
g_return_val_if_fail (gst_amf_node_get_type (node) == GST_AMF_TYPE_BOOLEAN,
FALSE);
return node->value.v_int;
}
gdouble
gst_amf_node_get_number (const GstAmfNode * node)
{
g_return_val_if_fail (gst_amf_node_get_type (node) == GST_AMF_TYPE_NUMBER,
FALSE);
return node->value.v_double;
}
gchar *
gst_amf_node_get_string (const GstAmfNode * node, gsize * out_size)
{
gsize size;
const gchar *data = gst_amf_node_peek_string (node, &size);
if (out_size) {
*out_size = size;
return g_memdup2 (data, size);
} else {
return g_strndup (data, size);
}
}
const gchar *
gst_amf_node_peek_string (const GstAmfNode * node, gsize * size)
{
GstAmfType type = gst_amf_node_get_type (node);
g_return_val_if_fail (type == GST_AMF_TYPE_STRING ||
type == GST_AMF_TYPE_LONG_STRING, FALSE);
return g_bytes_get_data (node->value.v_bytes, size);
}
const GstAmfNode *
gst_amf_node_get_field (const GstAmfNode * node, const gchar * name)
{
guint i, len = gst_amf_node_get_num_fields (node);
g_return_val_if_fail (name, NULL);
for (i = 0; i < len; i++) {
const AmfObjectField *field = get_field (node, i);
if (strcmp (field->name, name) == 0) {
return field->value;
}
}
return NULL;
}
const GstAmfNode *
gst_amf_node_get_field_by_index (const GstAmfNode * node, guint index)
{
guint len = gst_amf_node_get_num_fields (node);
g_return_val_if_fail (index < len, NULL);
return get_field (node, index)->value;
}
guint
gst_amf_node_get_num_fields (const GstAmfNode * node)
{
GstAmfType type = gst_amf_node_get_type (node);
g_return_val_if_fail (type == GST_AMF_TYPE_OBJECT ||
type == GST_AMF_TYPE_ECMA_ARRAY, 0);
return node->value.v_fields->len;
}
const GstAmfNode *
gst_amf_node_get_element (const GstAmfNode * node, guint index)
{
guint len = gst_amf_node_get_num_elements (node);
g_return_val_if_fail (index < len, NULL);
return get_element (node, index);
}
guint
gst_amf_node_get_num_elements (const GstAmfNode * node)
{
GstAmfType type = gst_amf_node_get_type (node);
g_return_val_if_fail (type == GST_AMF_TYPE_STRICT_ARRAY, 0);
return node->value.v_elements->len;
}
void
gst_amf_node_set_boolean (GstAmfNode * node, gboolean value)
{
g_return_if_fail (node->type == GST_AMF_TYPE_BOOLEAN);
node->value.v_int = !!value;
}
void
gst_amf_node_set_number (GstAmfNode * node, gdouble value)
{
g_return_if_fail (node->type == GST_AMF_TYPE_NUMBER);
node->value.v_double = value;
}
void
gst_amf_node_take_string (GstAmfNode * node, gchar * value, gssize size)
{
g_return_if_fail (node->type == GST_AMF_TYPE_STRING ||
node->type == GST_AMF_TYPE_LONG_STRING);
g_return_if_fail (value);
if (size < 0) {
size = strlen (value);
}
if (size > G_MAXUINT32) {
GST_WARNING ("Long string too long (%" G_GSSIZE_FORMAT "), truncating",
size);
size = G_MAXUINT32;
value[size] = 0;
}
if (size > G_MAXUINT16) {
node->type = GST_AMF_TYPE_LONG_STRING;
}
g_bytes_unref (node->value.v_bytes);
node->value.v_bytes = g_bytes_new_take (value, size);
}
void
gst_amf_node_set_string (GstAmfNode * node, const gchar * value, gssize size)
{
gchar *copy;
g_return_if_fail (value);
if (size < 0) {
size = strlen (value);
copy = g_memdup2 (value, size + 1);
} else {
copy = g_memdup2 (value, size);
}
gst_amf_node_take_string (node, copy, size);
}
void
gst_amf_node_append_field (GstAmfNode * node, const gchar * name,
const GstAmfNode * value)
{
gst_amf_node_append_take_field (node, name, gst_amf_node_copy (value));
}
void
gst_amf_node_append_take_field (GstAmfNode * node, const gchar * name,
GstAmfNode * value)
{
g_return_if_fail (node->type == GST_AMF_TYPE_OBJECT ||
node->type == GST_AMF_TYPE_ECMA_ARRAY);
g_return_if_fail (name);
append_field (node, g_strdup (name), value);
}
void
gst_amf_node_append_field_number (GstAmfNode * node, const gchar * name,
gdouble value)
{
gst_amf_node_append_take_field (node, name, gst_amf_node_new_number (value));
}
void
gst_amf_node_append_field_boolean (GstAmfNode * node, const gchar * name,
gboolean value)
{
gst_amf_node_append_take_field (node, name, gst_amf_node_new_boolean (value));
}
void
gst_amf_node_append_field_string (GstAmfNode * node, const gchar * name,
const gchar * value, gssize size)
{
gst_amf_node_append_take_field (node, name,
gst_amf_node_new_string (value, size));
}
void
gst_amf_node_append_field_take_string (GstAmfNode * node, const gchar * name,
gchar * value, gssize size)
{
gst_amf_node_append_take_field (node, name,
gst_amf_node_new_take_string (value, size));
}
/* Dumper *******************************************************************/
static inline void
dump_indent (GString * string, gint indent, guint depth)
{
if (indent < 0) {
g_string_append_c (string, ' ');
} else {
guint i;
g_string_append_c (string, '\n');
for (i = 0; i < indent + depth * 2; i++) {
g_string_append_c (string, ' ');
}
}
}
static inline void
dump_bytes (GString * string, GBytes * value)
{
gsize size;
const gchar *data = g_bytes_get_data (value, &size);
gst_rtmp_string_print_escaped (string, data, size);
}
static void
dump_node (GString * string, const GstAmfNode * node, gint indent,
guint recursion_depth)
{
const gchar *object_delim = "{}";
switch (gst_amf_node_get_type (node)) {
case GST_AMF_TYPE_NUMBER:
g_string_append_printf (string, "%g", node->value.v_double);
break;
case GST_AMF_TYPE_BOOLEAN:
g_string_append (string, node->value.v_int ? "True" : "False");
break;
case GST_AMF_TYPE_LONG_STRING:
g_string_append_c (string, 'L');
/* no break */
case GST_AMF_TYPE_STRING:
dump_bytes (string, node->value.v_bytes);
break;
case GST_AMF_TYPE_ECMA_ARRAY:
object_delim = "[]";
/* no break */
case GST_AMF_TYPE_OBJECT:{
guint i, len = gst_amf_node_get_num_fields (node);
g_string_append_c (string, object_delim[0]);
if (len) {
for (i = 0; i < len; i++) {
const AmfObjectField *field = get_field (node, i);
dump_indent (string, indent, recursion_depth + 1);
gst_rtmp_string_print_escaped (string, field->name, -1);
g_string_append_c (string, ':');
g_string_append_c (string, ' ');
dump_node (string, field->value, indent, recursion_depth + 1);
if (i < len - 1) {
g_string_append_c (string, ',');
}
}
dump_indent (string, indent, recursion_depth);
}
g_string_append_c (string, object_delim[1]);
break;
}
case GST_AMF_TYPE_STRICT_ARRAY:{
guint i, len = gst_amf_node_get_num_elements (node);
g_string_append_c (string, '(');
if (len) {
for (i = 0; i < len; i++) {
const GstAmfNode *value = get_element (node, i);
dump_indent (string, indent, recursion_depth + 1);
dump_node (string, value, indent, recursion_depth + 1);
if (i < len - 1) {
g_string_append_c (string, ',');
}
}
dump_indent (string, indent, recursion_depth);
}
g_string_append_c (string, ')');
break;
}
default:
g_string_append (string, gst_amf_type_get_nick (node->type));
break;
}
}
void
gst_amf_node_dump (const GstAmfNode * node, gint indent, GString * string)
{
dump_node (string, node, indent, 0);
}
static void
dump_argument (const GstAmfNode * node, guint n)
{
if (G_UNLIKELY (GST_LEVEL_LOG <= _gst_debug_min) &&
GST_LEVEL_LOG <= gst_debug_category_get_threshold (GST_CAT_DEFAULT)) {
GString *string = g_string_new (NULL);
gst_amf_node_dump (node, -1, string);
GST_LOG ("Argument #%u: %s", n, string->str);
g_string_free (string, TRUE);
}
}
/* Parser *******************************************************************/
typedef struct
{
const guint8 *data;
gsize size, offset;
guint8 recursion_depth;
} AmfParser;
static GstAmfNode *parse_value (AmfParser * parser);
static inline guint8
parse_u8 (AmfParser * parser)
{
guint8 value;
value = parser->data[parser->offset];
parser->offset += sizeof value;
return value;
}
static inline guint16
parse_u16 (AmfParser * parser)
{
guint16 value;
value = GST_READ_UINT16_BE (parser->data + parser->offset);
parser->offset += sizeof value;
return value;
}
static inline guint32
parse_u32 (AmfParser * parser)
{
guint32 value;
value = GST_READ_UINT32_BE (parser->data + parser->offset);
parser->offset += sizeof value;
return value;
}
static gdouble
parse_number (AmfParser * parser)
{
gdouble value;
if (sizeof value > parser->size - parser->offset) {
GST_ERROR ("number too long");
return 0.0;
}
value = GST_READ_DOUBLE_BE (parser->data + parser->offset);
parser->offset += sizeof value;
return value;
}
static gboolean
parse_boolean (AmfParser * parser)
{
guint8 value;
if (sizeof value > parser->size - parser->offset) {
GST_ERROR ("boolean too long");
return FALSE;
}
value = parse_u8 (parser);
return !!value;
}
static inline GBytes *
read_string (AmfParser * parser, gsize size)
{
gchar *string;
if (size == 0) {
return g_bytes_ref (empty_bytes);
}
if (size > parser->size - parser->offset) {
GST_ERROR ("string too long (%" G_GSIZE_FORMAT ")", size);
return NULL;
}
/* Null-terminate all incoming strings for internal safety */
if (parser->data[parser->offset + size - 1] == 0) {
string = g_malloc (size);
} else {
string = g_malloc (size + 1);
string[size] = 0;
}
memcpy (string, parser->data + parser->offset, size);
parser->offset += size;
return g_bytes_new_take (string, size);
}
static GBytes *
parse_string (AmfParser * parser)
{
guint16 size;
if (sizeof size > parser->size - parser->offset) {
GST_ERROR ("string size too long");
return NULL;
}
size = parse_u16 (parser);
return read_string (parser, size);
}
static GBytes *
parse_long_string (AmfParser * parser)
{
guint32 size;
if (sizeof size > parser->size - parser->offset) {
GST_ERROR ("long string size too long");
return NULL;
}
size = parse_u32 (parser);
return read_string (parser, size);
}
static guint32
parse_object (AmfParser * parser, GstAmfNode * node)
{
guint32 n_fields = 0;
while (TRUE) {
GBytes *name;
gsize size;
GstAmfNode *value;
name = parse_string (parser);
if (!name) {
GST_ERROR ("object too long");
break;
}
value = parse_value (parser);
if (!value) {
GST_ERROR ("object too long");
g_bytes_unref (name);
break;
}
if (gst_amf_node_get_type (value) == GST_AMF_TYPE_OBJECT_END) {
g_bytes_unref (name);
gst_amf_node_free (value);
break;
}
if (g_bytes_get_size (name) == 0) {
GST_ERROR ("empty object field name");
g_bytes_unref (name);
gst_amf_node_free (value);
break;
}
append_field (node, g_bytes_unref_to_data (name, &size), value);
n_fields++;
};
return n_fields;
}
static void
parse_ecma_array (AmfParser * parser, GstAmfNode * node)
{
guint32 n_elements, n_read;
if (sizeof n_elements > parser->size - parser->offset) {
GST_ERROR ("array size too long");
return;
}
n_elements = parse_u32 (parser);
/* FIXME This is weird. The one time I've seen this, the encoded value
* was 0, but the number of elements was 1. */
if (n_elements == 0) {
GST_DEBUG ("Interpreting ECMA array length 0 as 1");
n_elements = 1;
}
n_read = parse_object (parser, node);
if (n_read != n_elements) {
GST_WARNING ("Expected array with %" G_GUINT32_FORMAT " elements,"
" but read %" G_GUINT32_FORMAT, n_elements, n_read);
}
}
static void
parse_strict_array (AmfParser * parser, GstAmfNode * node)
{
GstAmfNode *value = NULL;
guint32 n_elements, i;
if (sizeof n_elements > parser->size - parser->offset) {
GST_ERROR ("array size too long");
return;
}
n_elements = parse_u32 (parser);
for (i = 0; i < n_elements; i++) {
value = parse_value (parser);
if (!value) {
GST_ERROR ("array too long");
break;
}
append_element (node, value);
}
}
static GstAmfNode *
parse_value (AmfParser * parser)
{
GstAmfNode *node = NULL;
GstAmfType type;
if (1 > parser->size - parser->offset) {
GST_ERROR ("value too long");
return NULL;
}
type = parse_u8 (parser);
node = node_new (type);
GST_TRACE ("parsing AMF type %d (%s)", type, gst_amf_type_get_nick (type));
parser->recursion_depth++;
if (parser->recursion_depth > MAX_RECURSION_DEPTH) {
GST_ERROR ("maximum recursion depth %d reached", parser->recursion_depth);
return node;
}
switch (type) {
case GST_AMF_TYPE_NUMBER:
node->value.v_double = parse_number (parser);
break;
case GST_AMF_TYPE_BOOLEAN:
node->value.v_int = parse_boolean (parser);
break;
case GST_AMF_TYPE_STRING:
node->value.v_bytes = parse_string (parser);
break;
case GST_AMF_TYPE_LONG_STRING:
node->value.v_bytes = parse_long_string (parser);
break;
case GST_AMF_TYPE_OBJECT:
parse_object (parser, node);
break;
case GST_AMF_TYPE_ECMA_ARRAY:
parse_ecma_array (parser, node);
break;
case GST_AMF_TYPE_STRICT_ARRAY:
parse_strict_array (parser, node);
break;
case GST_AMF_TYPE_NULL:
case GST_AMF_TYPE_UNDEFINED:
case GST_AMF_TYPE_OBJECT_END:
case GST_AMF_TYPE_UNSUPPORTED:
break;
default:
GST_ERROR ("unimplemented AMF type %d (%s)", type,
gst_amf_type_get_nick (type));
break;
}
parser->recursion_depth--;
return node;
}
GstAmfNode *
gst_amf_node_parse (const guint8 * data, gsize size, guint8 ** endptr)
{
AmfParser parser = {
.data = data,
.size = size,
};
GstAmfNode *node;
g_return_val_if_fail (data, NULL);
g_return_val_if_fail (size, NULL);
init_static ();
GST_TRACE ("Starting parse with %" G_GSIZE_FORMAT " bytes", parser.size);
node = parse_value (&parser);
if (!node || gst_amf_node_get_type (node) == GST_AMF_TYPE_INVALID) {
GST_ERROR ("invalid value");
goto out;
}
if (G_UNLIKELY (GST_LEVEL_LOG <= _gst_debug_min) &&
GST_LEVEL_LOG <= gst_debug_category_get_threshold (GST_CAT_DEFAULT)) {
GString *string = g_string_new (NULL);
gst_amf_node_dump (node, -1, string);
GST_LOG ("Parsed value: %s", string->str);
g_string_free (string, TRUE);
}
GST_TRACE ("Done parsing; consumed %" G_GSIZE_FORMAT " bytes and left %"
G_GSIZE_FORMAT " bytes", parser.offset, parser.size - parser.offset);
out:
if (endptr) {
*endptr = (guint8 *) parser.data + parser.offset;
}
return node;
}
GPtrArray *
gst_amf_parse_command (const guint8 * data, gsize size,
gdouble * transaction_id, gchar ** command_name)
{
AmfParser parser = {
.data = data,
.size = size,
};
GstAmfNode *node1 = NULL, *node2 = NULL;
GPtrArray *args = NULL;
g_return_val_if_fail (data, NULL);
g_return_val_if_fail (size, NULL);
init_static ();
GST_TRACE ("Starting parse with %" G_GSIZE_FORMAT " bytes", parser.size);
node1 = parse_value (&parser);
if (!node1 || gst_amf_node_get_type (node1) != GST_AMF_TYPE_STRING) {
GST_ERROR ("no command name");
goto out;
}
node2 = parse_value (&parser);
if (!node2 || gst_amf_node_get_type (node2) != GST_AMF_TYPE_NUMBER) {
GST_ERROR ("no transaction ID");
goto out;
}
GST_LOG ("Parsing command '%s', transid %.0f",
gst_amf_node_peek_string (node1, NULL), gst_amf_node_get_number (node2));
args = g_ptr_array_new_with_free_func (gst_amf_node_free);
while (parser.offset < parser.size) {
GstAmfNode *node = parse_value (&parser);
if (!node) {
break;
}
dump_argument (node, args->len);
g_ptr_array_add (args, node);
}
GST_TRACE ("Done parsing; consumed %" G_GSIZE_FORMAT " bytes and left %"
G_GSIZE_FORMAT " bytes", parser.offset, parser.size - parser.offset);
if (args->len == 0) {
GST_ERROR ("no command arguments");
g_clear_pointer (&args, g_ptr_array_unref);
goto out;
}
if (command_name) {
*command_name = gst_amf_node_get_string (node1, NULL);
}
if (transaction_id) {
*transaction_id = gst_amf_node_get_number (node2);
}
out:
g_clear_pointer (&node1, gst_amf_node_free);
g_clear_pointer (&node2, gst_amf_node_free);
return args;
}
/* Serializer ***************************************************************/
static void serialize_value (GByteArray * array, const GstAmfNode * node);
static inline void
serialize_u8 (GByteArray * array, guint8 value)
{
g_byte_array_append (array, (guint8 *) & value, sizeof value);
}
static inline void
serialize_u16 (GByteArray * array, guint16 value)
{
value = GUINT16_TO_BE (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);
}
static inline void
serialize_number (GByteArray * array, gdouble value)
{
value = GDOUBLE_TO_BE (value);
g_byte_array_append (array, (guint8 *) & value, sizeof value);
}
static inline void
serialize_boolean (GByteArray * array, gboolean value)
{
serialize_u8 (array, value);
}
static void
serialize_string (GByteArray * array, const gchar * string, gssize size)
{
if (size < 0) {
size = strlen (string);
}
if (size > G_MAXUINT16) {
GST_WARNING ("String too long: %" G_GSSIZE_FORMAT, size);
size = G_MAXUINT16;
}
serialize_u16 (array, size);
g_byte_array_append (array, (guint8 *) string, size);
}
static void
serialize_long_string (GByteArray * array, const gchar * string, gssize size)
{
if (size < 0) {
size = strlen (string);
}
if (size > G_MAXUINT32) {
GST_WARNING ("Long string too long: %" G_GSSIZE_FORMAT, size);
size = G_MAXUINT32;
}
serialize_u32 (array, size);
g_byte_array_append (array, (guint8 *) string, size);
}
static inline void
serialize_bytes (GByteArray * array, GBytes * bytes, gboolean long_string)
{
gsize size;
const gchar *data = g_bytes_get_data (bytes, &size);
if (long_string) {
serialize_long_string (array, data, size);
} else {
serialize_string (array, data, size);
}
}
static void
serialize_object (GByteArray * array, const GstAmfNode * node)
{
guint i;
for (i = 0; i < gst_amf_node_get_num_fields (node); i++) {
const AmfObjectField *field = get_field (node, i);
serialize_string (array, field->name, -1);
serialize_value (array, field->value);
}
serialize_u16 (array, 0);
serialize_u8 (array, GST_AMF_TYPE_OBJECT_END);
}
static void
serialize_ecma_array (GByteArray * array, const GstAmfNode * node)
{
/* FIXME: Shouldn't this be the field count? */
serialize_u32 (array, 0);
serialize_object (array, node);
}
static void
serialize_value (GByteArray * array, const GstAmfNode * node)
{
serialize_u8 (array, node->type);
switch (node->type) {
case GST_AMF_TYPE_NUMBER:
serialize_number (array, node->value.v_double);
break;
case GST_AMF_TYPE_BOOLEAN:
serialize_boolean (array, node->value.v_int);
break;
case GST_AMF_TYPE_STRING:
serialize_bytes (array, node->value.v_bytes, FALSE);
break;
case GST_AMF_TYPE_LONG_STRING:
serialize_bytes (array, node->value.v_bytes, TRUE);
break;
case GST_AMF_TYPE_OBJECT:
serialize_object (array, node);
break;
case GST_AMF_TYPE_ECMA_ARRAY:
serialize_ecma_array (array, node);
break;
case GST_AMF_TYPE_NULL:
case GST_AMF_TYPE_UNDEFINED:
case GST_AMF_TYPE_OBJECT_END:
case GST_AMF_TYPE_UNSUPPORTED:
break;
default:
GST_ERROR ("unimplemented AMF type %d (%s)", node->type,
gst_amf_type_get_nick (node->type));
break;
}
}
GBytes *
gst_amf_node_serialize (const GstAmfNode * node)
{
GByteArray *array = g_byte_array_new ();
g_return_val_if_fail (node, NULL);
init_static ();
if (G_UNLIKELY (GST_LEVEL_LOG <= _gst_debug_min) &&
GST_LEVEL_LOG <= gst_debug_category_get_threshold (GST_CAT_DEFAULT)) {
GString *string = g_string_new (NULL);
gst_amf_node_dump (node, -1, string);
GST_LOG ("Serializing value: %s", string->str);
g_string_free (string, TRUE);
}
serialize_value (array, node);
GST_TRACE ("Done serializing; produced %u bytes", array->len);
return g_byte_array_free_to_bytes (array);
}
GBytes *
gst_amf_serialize_command (gdouble transaction_id, const gchar * command_name,
const GstAmfNode * argument, ...)
{
va_list ap;
GBytes *ret;
va_start (ap, argument);
ret = gst_amf_serialize_command_valist (transaction_id, command_name,
argument, ap);
va_end (ap);
return ret;
}
GBytes *
gst_amf_serialize_command_valist (gdouble transaction_id,
const gchar * command_name, const GstAmfNode * argument, va_list var_args)
{
GByteArray *array = g_byte_array_new ();
guint i = 0;
g_return_val_if_fail (command_name, NULL);
g_return_val_if_fail (argument, NULL);
init_static ();
GST_LOG ("Serializing command '%s', transid %.0f", command_name,
transaction_id);
serialize_u8 (array, GST_AMF_TYPE_STRING);
serialize_string (array, command_name, -1);
serialize_u8 (array, GST_AMF_TYPE_NUMBER);
serialize_number (array, transaction_id);
while (argument) {
serialize_value (array, argument);
dump_argument (argument, i++);
argument = va_arg (var_args, const GstAmfNode *);
}
GST_TRACE ("Done serializing; consumed %u args and produced %u bytes", i,
array->len);
return g_byte_array_free_to_bytes (array);
}
GBytes *
gst_amf_serialize_command_with_args (gdouble transaction_id,
const gchar * command_name, gsize n_arguments,
const GstAmfNode ** arguments)
{
GByteArray *array = g_byte_array_new ();
gsize i = 0;
g_return_val_if_fail (command_name, NULL);
g_return_val_if_fail (n_arguments, NULL);
g_return_val_if_fail (arguments, NULL);
init_static ();
GST_LOG ("Serializing command '%s', transid %.0f", command_name,
transaction_id);
serialize_u8 (array, GST_AMF_TYPE_STRING);
serialize_string (array, command_name, -1);
serialize_u8 (array, GST_AMF_TYPE_NUMBER);
serialize_number (array, transaction_id);
for (i = 0; i < n_arguments; i++) {
serialize_value (array, arguments[i]);
dump_argument (arguments[i], i);
}
GST_TRACE ("Done serializing; consumed %" G_GSIZE_FORMAT
"args and produced %u bytes", i, array->len);
return g_byte_array_free_to_bytes (array);
}