blob: 8f9946f0e9b243a871b182346ada27ac0c5c884d [file] [log] [blame]
From 0052f3d8315e8cefb04a62a64cac17cbfbc6b8b6 Mon Sep 17 00:00:00 2001
From: Charlie Turner <chturne@gmail.com>
Date: Fri, 25 May 2018 09:43:30 +0100
Subject: [PATCH] Parse playready payload.
Look at the playready specific data payload to extract default key ids
for decrypting samples. The format of this payload seems to be
proprietary.
Signed-off-by: Xabier Rodriguez Calvar <calvaris@igalia.com>
---
ext/smoothstreaming/gstmssdemux.c | 14 ++-
ext/smoothstreaming/gstmssmanifest.c | 138 +++++++++++++++++++++++++++
ext/smoothstreaming/gstmssmanifest.h | 1 +
3 files changed, 152 insertions(+), 1 deletion(-)
diff --git a/ext/smoothstreaming/gstmssdemux.c b/ext/smoothstreaming/gstmssdemux.c
index bcea8ca..4fa95b9 100644
--- a/ext/smoothstreaming/gstmssdemux.c
+++ b/ext/smoothstreaming/gstmssdemux.c
@@ -495,7 +495,19 @@ gst_mss_demux_setup_streams (GstAdaptiveDemux * demux)
gst_event_new_protection (protection_system_id, protection_buffer,
"smooth-streaming");
- GST_LOG_OBJECT (stream, "Queueing Protection event on source pad");
+ GstBuffer *key_id = gst_mss_manifest_get_key_id (mssdemux->manifest);
+ if (!gst_buffer_get_size (key_id)) {
+ GST_WARNING_OBJECT (stream,
+ "protected manifest, but no key ids available");
+ } else {
+ GST_LOG_OBJECT (stream,
+ "Adding key id to the protection event of size %lu",
+ gst_buffer_get_size (key_id));
+ GstStructure *structure = gst_event_writable_structure (event);
+ gst_structure_set (structure, "key_id", GST_TYPE_BUFFER, key_id, NULL);
+ }
+
+ GST_LOG_OBJECT (stream, "Queuing Protection event on source pad");
gst_adaptive_demux_stream_queue_event ((GstAdaptiveDemuxStream *) stream,
event);
gst_buffer_unref (protection_buffer);
diff --git a/ext/smoothstreaming/gstmssmanifest.c b/ext/smoothstreaming/gstmssmanifest.c
index c855cb3..df31330 100644
--- a/ext/smoothstreaming/gstmssmanifest.c
+++ b/ext/smoothstreaming/gstmssmanifest.c
@@ -28,6 +28,8 @@
#include <ctype.h>
#include <libxml/parser.h>
#include <libxml/tree.h>
+#include <libxml/xpath.h>
+#include <libxml/xpathInternals.h>
/* for parsing h264 codec data */
#include <gst/codecparsers/gsth264parser.h>
@@ -109,6 +111,10 @@ struct _GstMssManifest
GString *protection_system_id;
gchar *protection_data;
+ // FIXME: There can be multiple key ids present in protected manifests.
+ gchar *key_id;
+ gsize key_id_len;
+
GSList *streams;
};
@@ -300,6 +306,122 @@ _gst_mss_stream_init (GstMssManifest * manifest, GstMssStream * stream,
gst_mss_fragment_parser_init (&stream->fragment_parser);
}
+static void
+_gst_mss_parse_protection_data (GstMssManifest * manifest)
+{
+ guchar *decoded_protection_data = NULL;
+ gsize decoded_protection_data_len = 0;
+ xmlChar *protection_data_text = NULL;
+ int protection_data_text_len = 0;
+ xmlDocPtr protection_data_xml = NULL;
+ // Note, this does not need to free'd, freeing the doc ptr will free this.
+ xmlNode *protection_data_root_element = NULL;
+ const xmlChar *protection_data_namespace = NULL;
+ xmlXPathContextPtr xpath_ctx = NULL;
+ xmlXPathObjectPtr xpath_obj = NULL;
+ xmlNodeSetPtr key_id_node = NULL;
+ gsize protection_data_xml_len = 0;
+ guchar *start_tag = NULL;
+
+ decoded_protection_data =
+ g_base64_decode (manifest->protection_data, &decoded_protection_data_len);
+
+ start_tag = decoded_protection_data;
+
+ // The protection data starts with a 10-byte PlayReady version
+ // header that needs to be skipped over to avoid XML parsing
+ // errors.
+ while (start_tag && *start_tag != '<')
+ start_tag++;
+
+ if (!start_tag) {
+ GST_ERROR ("failed to find a start tag in protection data payload");
+ goto beach;
+ }
+
+ protection_data_xml_len =
+ decoded_protection_data_len - (start_tag - decoded_protection_data);
+ protection_data_xml =
+ xmlReadMemory ((const gchar *) start_tag, protection_data_xml_len,
+ "protection_data", "utf-16", 0);
+
+ if (!protection_data_xml) {
+ GST_ERROR ("failed to parse protection data XML");
+ goto beach;
+ }
+
+ if (gst_debug_category_get_threshold (GST_CAT_DEFAULT) >= GST_LEVEL_MEMDUMP) {
+ xmlDocDumpMemoryEnc (protection_data_xml, &protection_data_text,
+ &protection_data_text_len, "utf8");
+ GST_MEMDUMP ("protection data XML", protection_data_text,
+ protection_data_text_len);
+ xmlFree (protection_data_text);
+ }
+
+ protection_data_root_element = xmlDocGetRootElement (protection_data_xml);
+ if (protection_data_root_element->type != XML_ELEMENT_NODE ||
+ xmlStrcmp (protection_data_root_element->name,
+ (const xmlChar *) "WRMHEADER") || !protection_data_root_element->ns) {
+ GST_ERROR ("invalid protection data XML");
+ goto beach;
+ }
+
+ xpath_ctx = xmlXPathNewContext (protection_data_xml);
+ if (!xpath_ctx) {
+ GST_ERROR ("failed to create xpath context");
+ goto beach;
+ }
+
+ protection_data_namespace = protection_data_root_element->ns->href;
+ if (xmlXPathRegisterNs (xpath_ctx, (const xmlChar *) "prhdr",
+ protection_data_namespace) < 0) {
+ GST_ERROR ("failed to register XML namespace");
+ goto beach;
+ }
+
+ xpath_obj =
+ xmlXPathEvalExpression ((const xmlChar *) "//prhdr:KID", xpath_ctx);
+ if (!xpath_obj) {
+ GST_DEBUG ("failed to eval XPath expression");
+ goto beach;
+ }
+
+ key_id_node = xpath_obj->nodesetval;
+ int num_key_ids = key_id_node ? key_id_node->nodeNr : 0;
+
+ GST_DEBUG ("found %d key ids", num_key_ids);
+
+ if (num_key_ids != 0 && (key_id_node->nodeTab[0]->type == XML_ELEMENT_NODE)) {
+ xmlChar *encoded_key_id = xmlNodeGetContent (key_id_node->nodeTab[0]);
+ gsize decoded_key_id_len;
+ guchar *decoded_key_id =
+ g_base64_decode ((const gchar *) encoded_key_id, &decoded_key_id_len);
+
+ GST_MEMDUMP ("key retrieved", decoded_key_id, decoded_key_id_len);
+
+ manifest->key_id = g_realloc (manifest->key_id, decoded_key_id_len);
+ memcpy (manifest->key_id, decoded_key_id, decoded_key_id_len);
+ manifest->key_id_len = decoded_key_id_len;
+
+ xmlFree (encoded_key_id);
+ g_free (decoded_key_id);
+ } else {
+ GST_ERROR ("invalid XML payload");
+ goto beach;
+ }
+
+beach:
+ if (decoded_protection_data)
+ g_free (decoded_protection_data);
+ if (xpath_ctx)
+ xmlXPathFreeContext (xpath_ctx);
+ if (xpath_obj)
+ xmlXPathFreeObject (xpath_obj);
+ if (protection_data_xml)
+ xmlFreeDoc (protection_data_xml);
+
+ return;
+}
static void
_gst_mss_parse_protection (GstMssManifest * manifest,
@@ -329,6 +451,7 @@ _gst_mss_parse_protection (GstMssManifest * manifest,
manifest->protection_system_id = system_id;
manifest->protection_data = (gchar *) xmlNodeGetContent (nodeiter);
+ _gst_mss_parse_protection_data (manifest);
xmlFree (system_id_attribute);
break;
}
@@ -394,6 +517,9 @@ gst_mss_manifest_new (GstBuffer * data)
}
}
+ manifest->key_id = NULL;
+ manifest->key_id_len = 0;
+
for (nodeiter = root->children; nodeiter; nodeiter = nodeiter->next) {
if (nodeiter->type == XML_ELEMENT_NODE
&& (strcmp ((const char *) nodeiter->name, "StreamIndex") == 0)) {
@@ -440,6 +566,9 @@ gst_mss_manifest_free (GstMssManifest * manifest)
g_slist_free_full (manifest->streams, (GDestroyNotify) gst_mss_stream_free);
+ if (manifest->key_id)
+ g_free (manifest->key_id);
+
if (manifest->protection_system_id != NULL)
g_string_free (manifest->protection_system_id, TRUE);
xmlFree (manifest->protection_data);
@@ -462,6 +591,15 @@ gst_mss_manifest_get_protection_data (GstMssManifest * manifest)
return manifest->protection_data;
}
+GstBuffer *
+gst_mss_manifest_get_key_id (GstMssManifest * manifest)
+{
+ GstBuffer *key_id_buffer =
+ gst_buffer_new_allocate (NULL, manifest->key_id_len, NULL);
+ gst_buffer_fill (key_id_buffer, 0, manifest->key_id, manifest->key_id_len);
+ return key_id_buffer;
+}
+
GSList *
gst_mss_manifest_get_streams (GstMssManifest * manifest)
{
diff --git a/ext/smoothstreaming/gstmssmanifest.h b/ext/smoothstreaming/gstmssmanifest.h
index 03b066a..4e6ad73 100644
--- a/ext/smoothstreaming/gstmssmanifest.h
+++ b/ext/smoothstreaming/gstmssmanifest.h
@@ -56,6 +56,7 @@ GstClockTime gst_mss_manifest_get_min_fragment_duration (GstMssManifest * manife
const gchar * gst_mss_manifest_get_protection_system_id (GstMssManifest * manifest);
const gchar * gst_mss_manifest_get_protection_data (GstMssManifest * manifest);
gboolean gst_mss_manifest_get_live_seek_range (GstMssManifest * manifest, gint64 * start, gint64 * stop);
+GstBuffer * gst_mss_manifest_get_key_id (GstMssManifest * manifest);
GstMssStreamType gst_mss_stream_get_type (GstMssStream *stream);
GstCaps * gst_mss_stream_get_caps (GstMssStream * stream);
--
2.20.1