From 165849935357cba289231b66cd6a06699fe13625 Mon Sep 17 00:00:00 2001 From: Greg Adams Date: Wed, 26 Aug 2026 13:47:28 +0100 Subject: [PATCH 01/10] Work for Issue pexip/mcu#50793 * ret now starts as PEX_RTMP_SERVER_STATUS_OK; the genuine parse failure explicitly sets PEX_RTMP_SERVER_STATUS_PARSE_FAILED before goto done, and the loop bails out early if a message handler fails. * Added client_handle_flv_script_data() which decodes the onMetaData AMF payload of a MSG_NOTIFY FLV tag into client->metadata and sets new_metadata, so the updated framerate is actually propagated to subscribers via client_maybe_update_metadata() instead of being silently dropped (previously subscribers always got the hardcoded framerate = 30.0 from client_set_default_metadata). --- src/client.c | 39 ++++++++++++++++++++++++++++++++++++++- 1 file changed, 38 insertions(+), 1 deletion(-) diff --git a/src/client.c b/src/client.c index af35518..158847c 100644 --- a/src/client.c +++ b/src/client.c @@ -1711,13 +1711,40 @@ client_set_default_metadata (Client * client, "Setting new default metadata: %" GST_PTR_FORMAT, client->metadata); } +/* An FLV script-data tag (onMetaData) carries the same AMF payload as an + * RTMP MSG_NOTIFY, so decode it and pick up the (possibly updated) metadata. */ +static void +client_handle_flv_script_data (Client * client) +{ + AmfDec *dec = amf_dec_new (client->buf, 0); + gchar *type = amf_dec_load_string (dec); + + if (g_strcmp0 (type, "onMetaData") == 0) { + GstStructure *metadata = amf_dec_load_object (dec); + if (metadata) { + if (client->metadata) + gst_structure_free (client->metadata); + client->metadata = metadata; + client->new_metadata = TRUE; + GST_DEBUG_OBJECT (client->server, "(%s) FLV METADATA %" GST_PTR_FORMAT, + client->path, client->metadata); + } + } else { + GST_DEBUG_OBJECT (client->server, "ignoring FLV script data: %s", + type ? type : "(unknown)"); + } + + g_free (type); + amf_dec_free (dec); +} + static PexRtmpServerStatus client_handle_flv_buffer (Client * client, GstBuffer * buf) { RTMPMessage msg; GstMapInfo map; guint payload_size; - PexRtmpServerStatus ret = PEX_RTMP_SERVER_STATUS_BAD; + PexRtmpServerStatus ret = PEX_RTMP_SERVER_STATUS_OK; guint total_parsed = 0; gst_buffer_map (buf, &map, GST_MAP_READ); @@ -1744,6 +1771,7 @@ client_handle_flv_buffer (Client * client, GstBuffer * buf) if (!(parsed = flv_parse_tag (data, map.size - total_parsed, &msg.type, &payload_size, &msg.abs_timestamp))) { GST_WARNING_OBJECT (client->server, "Could not parse header!"); + ret = PEX_RTMP_SERVER_STATUS_PARSE_FAILED; goto done; } @@ -1759,8 +1787,17 @@ client_handle_flv_buffer (Client * client, GstBuffer * buf) ret = client_handle_message (client, &msg); client->buf = g_byte_array_remove_range (client->buf, 0, client->buf->len); + } else if (msg.type == MSG_NOTIFY) { + client->buf = g_byte_array_append (client->buf, + data + parsed, payload_size); + client_handle_flv_script_data (client); + client->buf = + g_byte_array_remove_range (client->buf, 0, client->buf->len); } + if (ret != PEX_RTMP_SERVER_STATUS_OK) + goto done; + total_parsed += (parsed + payload_size + 4); } From 4c316216091bf0cacbb6e226f099128cf74f447b Mon Sep 17 00:00:00 2001 From: Greg Adams Date: Wed, 26 Aug 2026 13:59:14 +0100 Subject: [PATCH 02/10] Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- src/client.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/client.c b/src/client.c index 158847c..9464bfe 100644 --- a/src/client.c +++ b/src/client.c @@ -1721,14 +1721,19 @@ client_handle_flv_script_data (Client * client) if (g_strcmp0 (type, "onMetaData") == 0) { GstStructure *metadata = amf_dec_load_object (dec); - if (metadata) { + if (metadata && gst_structure_n_fields (metadata) > 0) { if (client->metadata) gst_structure_free (client->metadata); client->metadata = metadata; client->new_metadata = TRUE; GST_DEBUG_OBJECT (client->server, "(%s) FLV METADATA %" GST_PTR_FORMAT, client->path, client->metadata); + } else if (metadata) { + gst_structure_free (metadata); + GST_DEBUG_OBJECT (client->server, + "ignoring FLV onMetaData with empty/invalid payload"); } + } } else { GST_DEBUG_OBJECT (client->server, "ignoring FLV script data: %s", type ? type : "(unknown)"); From ebd4cda85fbed10005f81d8b42c64a9e9191ed43 Mon Sep 17 00:00:00 2001 From: Greg Adams Date: Wed, 26 Aug 2026 13:59:25 +0100 Subject: [PATCH 03/10] Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- src/client.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/client.c b/src/client.c index 9464bfe..9b4d6fb 100644 --- a/src/client.c +++ b/src/client.c @@ -1711,8 +1711,8 @@ client_set_default_metadata (Client * client, "Setting new default metadata: %" GST_PTR_FORMAT, client->metadata); } -/* An FLV script-data tag (onMetaData) carries the same AMF payload as an - * RTMP MSG_NOTIFY, so decode it and pick up the (possibly updated) metadata. */ +/* An FLV script-data tag ("onMetaData") carries the same AMF payload as the + * RTMP "@setDataFrame"/"onMetaData" MSG_NOTIFY, so decode and apply it. */ static void client_handle_flv_script_data (Client * client) { From 0dc8e004064478fd854de8bdb2e08cb5d1a35ba9 Mon Sep 17 00:00:00 2001 From: Greg Adams Date: Wed, 26 Aug 2026 14:06:16 +0100 Subject: [PATCH 04/10] Remove erroneous bracket. --- src/client.c | 1 - 1 file changed, 1 deletion(-) diff --git a/src/client.c b/src/client.c index 9b4d6fb..838009f 100644 --- a/src/client.c +++ b/src/client.c @@ -1733,7 +1733,6 @@ client_handle_flv_script_data (Client * client) GST_DEBUG_OBJECT (client->server, "ignoring FLV onMetaData with empty/invalid payload"); } - } } else { GST_DEBUG_OBJECT (client->server, "ignoring FLV script data: %s", type ? type : "(unknown)"); From 3eca8fb43bc18b602d49de198858209c05b80414 Mon Sep 17 00:00:00 2001 From: Greg Adams Date: Thu, 27 Aug 2026 09:22:19 +0100 Subject: [PATCH 05/10] Fixup handling of amf_dec_load_object call as it never returns NULL, but we have to verify it has be decoded correctly. --- src/client.c | 25 +++++++++++++++++++++---- 1 file changed, 21 insertions(+), 4 deletions(-) diff --git a/src/client.c b/src/client.c index 838009f..e0406bc 100644 --- a/src/client.c +++ b/src/client.c @@ -1711,6 +1711,22 @@ client_set_default_metadata (Client * client, "Setting new default metadata: %" GST_PTR_FORMAT, client->metadata); } +/* amf_dec_load_object() always hands back a (possibly partially filled) + * structure, so a truncated payload has to be spotted by the caller: a good + * one is consumed in full and ends with the AMF0 object-end marker. */ +static gboolean +amf_payload_fully_decoded (const AmfDec * dec) +{ + const guint8 object_end[] = { 0x00, 0x00, AMF0_OBJECT_END }; + + if (dec->pos != dec->buf->len) + return FALSE; + + return dec->buf->len >= sizeof (object_end) && + memcmp (&dec->buf->data[dec->buf->len - sizeof (object_end)], + object_end, sizeof (object_end)) == 0; +} + /* An FLV script-data tag ("onMetaData") carries the same AMF payload as the * RTMP "@setDataFrame"/"onMetaData" MSG_NOTIFY, so decode and apply it. */ static void @@ -1721,17 +1737,18 @@ client_handle_flv_script_data (Client * client) if (g_strcmp0 (type, "onMetaData") == 0) { GstStructure *metadata = amf_dec_load_object (dec); - if (metadata && gst_structure_n_fields (metadata) > 0) { + if (amf_payload_fully_decoded (dec)) { if (client->metadata) gst_structure_free (client->metadata); client->metadata = metadata; client->new_metadata = TRUE; GST_DEBUG_OBJECT (client->server, "(%s) FLV METADATA %" GST_PTR_FORMAT, client->path, client->metadata); - } else if (metadata) { + } else { gst_structure_free (metadata); - GST_DEBUG_OBJECT (client->server, - "ignoring FLV onMetaData with empty/invalid payload"); + GST_WARNING_OBJECT (client->server, + "(%s) ignoring malformed FLV onMetaData, decoded %" G_GSIZE_FORMAT + " of %u bytes", client->path, dec->pos, client->buf->len); } } else { GST_DEBUG_OBJECT (client->server, "ignoring FLV script data: %s", From 85d15e556bd32ef8c599fe0fbf823440ea93c72e Mon Sep 17 00:00:00 2001 From: Greg Adams Date: Thu, 27 Aug 2026 10:06:38 +0100 Subject: [PATCH 06/10] Fixup amf_enc_write_int to write a double if AMF0 as it has no integer type. --- utils/amf.c | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/utils/amf.c b/utils/amf.c index a994526..efd2a15 100644 --- a/utils/amf.c +++ b/utils/amf.c @@ -154,11 +154,14 @@ amf_enc_write_string (AmfEnc * enc, const gchar * str) static void amf_enc_write_int (AmfEnc * enc, gint i) { - if (enc->version == AMF3_VERSION) - amf_enc_add_char (enc, AMF3_INTEGER); - else - g_assert_not_reached (); + /* AMF0 has no integer type, so widen to a number instead of dying on + values that came in over an AMF3 decode */ + if (enc->version != AMF3_VERSION) { + amf_enc_write_double (enc, (gdouble) i); + return; + } + amf_enc_add_char (enc, AMF3_INTEGER); amf_enc_add_int (enc, i); } From a7698d7ddc2cc22d23783d592a2faf50b85d8733 Mon Sep 17 00:00:00 2001 From: Greg Adams Date: Thu, 27 Aug 2026 11:27:36 +0100 Subject: [PATCH 07/10] Parse status and per-subscriber send status tracked separately. --- src/client.c | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/client.c b/src/client.c index e0406bc..671e044 100644 --- a/src/client.c +++ b/src/client.c @@ -1805,7 +1805,14 @@ client_handle_flv_buffer (Client * client, GstBuffer * buf) data + parsed, payload_size); msg.len = payload_size; msg.buf = client->buf; - ret = client_handle_message (client, &msg); + /* this largely reports how forwarding to the subscribers went, which + must not abandon the rest of the publisher's input */ + PexRtmpServerStatus msg_ret = client_handle_message (client, &msg); + if (msg_ret != PEX_RTMP_SERVER_STATUS_OK) { + GST_WARNING_OBJECT (client->server, + "(%s) failed to handle FLV tag 0x%x (ret=%d), continuing", + client->path, msg.type, msg_ret); + } client->buf = g_byte_array_remove_range (client->buf, 0, client->buf->len); } else if (msg.type == MSG_NOTIFY) { @@ -1816,9 +1823,6 @@ client_handle_flv_buffer (Client * client, GstBuffer * buf) g_byte_array_remove_range (client->buf, 0, client->buf->len); } - if (ret != PEX_RTMP_SERVER_STATUS_OK) - goto done; - total_parsed += (parsed + payload_size + 4); } From b9b872bcbc15ca37c28b2ea6af27d09b13ac60f4 Mon Sep 17 00:00:00 2001 From: Greg Adams Date: Thu, 27 Aug 2026 11:47:15 +0100 Subject: [PATCH 08/10] Now merge updates into client->metadata instead of replacing and loosing old values. --- src/client.c | 25 +++++++++++++++++++++---- 1 file changed, 21 insertions(+), 4 deletions(-) diff --git a/src/client.c b/src/client.c index 671e044..6548967 100644 --- a/src/client.c +++ b/src/client.c @@ -1727,6 +1727,22 @@ amf_payload_fully_decoded (const AmfDec * dec) object_end, sizeof (object_end)) == 0; } +/* A re-announced onMetaData only carries what the muxer knows about, so it may + * update values but must not drop the fields it omits (avcprofile, avclevel, + * ...) nor change the type of one we already publish. */ +static gboolean +client_merge_metadata_field (const GstIdStr * field, const GValue * value, + gpointer user_data) +{ + GstStructure *metadata = user_data; + const GValue *existing = gst_structure_id_str_get_value (metadata, field); + + if (existing == NULL || G_VALUE_TYPE (existing) == G_VALUE_TYPE (value)) + gst_structure_id_str_set_value (metadata, field, value); + + return TRUE; +} + /* An FLV script-data tag ("onMetaData") carries the same AMF payload as the * RTMP "@setDataFrame"/"onMetaData" MSG_NOTIFY, so decode and apply it. */ static void @@ -1738,18 +1754,19 @@ client_handle_flv_script_data (Client * client) if (g_strcmp0 (type, "onMetaData") == 0) { GstStructure *metadata = amf_dec_load_object (dec); if (amf_payload_fully_decoded (dec)) { - if (client->metadata) - gst_structure_free (client->metadata); - client->metadata = metadata; + if (client->metadata == NULL) + client->metadata = gst_structure_new_empty ("object"); + gst_structure_foreach_id_str (metadata, client_merge_metadata_field, + client->metadata); client->new_metadata = TRUE; GST_DEBUG_OBJECT (client->server, "(%s) FLV METADATA %" GST_PTR_FORMAT, client->path, client->metadata); } else { - gst_structure_free (metadata); GST_WARNING_OBJECT (client->server, "(%s) ignoring malformed FLV onMetaData, decoded %" G_GSIZE_FORMAT " of %u bytes", client->path, dec->pos, client->buf->len); } + gst_structure_free (metadata); } else { GST_DEBUG_OBJECT (client->server, "ignoring FLV script data: %s", type ? type : "(unknown)"); From 4b782ca1bee519f537a0a87192ff6a35c399d8ed Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 27 Aug 2026 11:22:45 +0000 Subject: [PATCH 09/10] Move AMF payload validation helper to amf.c Co-authored-by: havardgraff <1926313+havardgraff@users.noreply.github.com> --- src/client.c | 18 +----------------- utils/amf.c | 16 ++++++++++++++++ utils/amf.h | 1 + 3 files changed, 18 insertions(+), 17 deletions(-) diff --git a/src/client.c b/src/client.c index 6548967..259f3aa 100644 --- a/src/client.c +++ b/src/client.c @@ -1711,22 +1711,6 @@ client_set_default_metadata (Client * client, "Setting new default metadata: %" GST_PTR_FORMAT, client->metadata); } -/* amf_dec_load_object() always hands back a (possibly partially filled) - * structure, so a truncated payload has to be spotted by the caller: a good - * one is consumed in full and ends with the AMF0 object-end marker. */ -static gboolean -amf_payload_fully_decoded (const AmfDec * dec) -{ - const guint8 object_end[] = { 0x00, 0x00, AMF0_OBJECT_END }; - - if (dec->pos != dec->buf->len) - return FALSE; - - return dec->buf->len >= sizeof (object_end) && - memcmp (&dec->buf->data[dec->buf->len - sizeof (object_end)], - object_end, sizeof (object_end)) == 0; -} - /* A re-announced onMetaData only carries what the muxer knows about, so it may * update values but must not drop the fields it omits (avcprofile, avclevel, * ...) nor change the type of one we already publish. */ @@ -1753,7 +1737,7 @@ client_handle_flv_script_data (Client * client) if (g_strcmp0 (type, "onMetaData") == 0) { GstStructure *metadata = amf_dec_load_object (dec); - if (amf_payload_fully_decoded (dec)) { + if (amf_dec_payload_fully_decoded (dec)) { if (client->metadata == NULL) client->metadata = gst_structure_new_empty ("object"); gst_structure_foreach_id_str (metadata, client_merge_metadata_field, diff --git a/utils/amf.c b/utils/amf.c index efd2a15..af09e09 100644 --- a/utils/amf.c +++ b/utils/amf.c @@ -573,6 +573,22 @@ amf_dec_load_object (AmfDec * dec) return amf_dec_load_object_with_depth (dec, 0); } +/* amf_dec_load_object() always hands back a (possibly partially filled) + * structure, so a truncated payload has to be spotted by the caller: a good + * one is consumed in full and ends with the AMF0 object-end marker. */ +gboolean +amf_dec_payload_fully_decoded (const AmfDec * dec) +{ + const guint8 object_end[] = { 0x00, 0x00, AMF0_OBJECT_END }; + + if (dec->pos != dec->buf->len) + return FALSE; + + return dec->buf->len >= sizeof (object_end) && + memcmp (&dec->buf->data[dec->buf->len - sizeof (object_end)], + object_end, sizeof (object_end)) == 0; +} + GValue * amf_dec_load (AmfDec * dec) { diff --git a/utils/amf.h b/utils/amf.h index 2e362f4..b8e9fba 100644 --- a/utils/amf.h +++ b/utils/amf.h @@ -129,5 +129,6 @@ GValue * amf_dec_load (AmfDec * dec); gboolean amf_dec_load_number (AmfDec * dec, gdouble * ret); gboolean amf_dec_load_integer (AmfDec * dec, gint * ret); gboolean amf_dec_load_boolean (AmfDec * dec, gboolean * ret); +gboolean amf_dec_payload_fully_decoded (const AmfDec * dec); #endif /* __AMF_H__ */ From 73fbadfe8e20f3a1ee9621a858d540cf3bde93ed Mon Sep 17 00:00:00 2001 From: Greg Adams Date: Thu, 27 Aug 2026 17:12:51 +0100 Subject: [PATCH 10/10] Fixup windows static builds now the define is being passed down correctly. --- utils/parse.h | 2 +- utils/tcp.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/utils/parse.h b/utils/parse.h index 178ac4b..f5c95e0 100644 --- a/utils/parse.h +++ b/utils/parse.h @@ -22,7 +22,7 @@ #include -#ifdef G_OS_WIN32 +#if defined(G_OS_WIN32) && !defined(PEX_RTMPSERVER_STATIC_BUILD) # ifdef PEX_RTMPSERVER_EXPORTS # define PEX_RTMPSERVER_EXPORT __declspec(dllexport) # else diff --git a/utils/tcp.h b/utils/tcp.h index 11a675b..d627837 100644 --- a/utils/tcp.h +++ b/utils/tcp.h @@ -22,7 +22,7 @@ #include -#ifdef G_OS_WIN32 +#if defined(G_OS_WIN32) && !defined(PEX_RTMPSERVER_STATIC_BUILD) # ifdef PEX_RTMPSERVER_EXPORTS # define PEX_RTMPSERVER_EXPORT __declspec(dllexport) # else