hlsdemux: re-enable decryption after uridownloader removal

Now the decryption is done buffer by buffer instead of on the
whole fragment at once. As it expects multiples of 16 bytes a
GstAdapter was added to properly chunk the buffers.

Also the last buffer must be resized depending on the value of the
last byte of the fragment, so hlsdemux always keeps a pending buffer
as it doesn't know if it is the last one yet
This commit is contained in:
Thiago Santos 2014-04-08 17:31:32 -03:00
parent 3611759557
commit c93c222786
2 changed files with 212 additions and 108 deletions

View file

@ -41,12 +41,6 @@
#endif
#include <string.h>
#ifdef HAVE_NETTLE
#include <nettle/aes.h>
#include <nettle/cbc.h>
#else
#include <gcrypt.h>
#endif
#include <gst/base/gsttypefindhelper.h>
#include "gsthlsdemux.h"
@ -117,6 +111,12 @@ static gchar *gst_hls_src_buf_to_utf8_playlist (GstBuffer * buf);
static gboolean gst_hls_demux_change_playlist (GstHLSDemux * demux,
guint max_bitrate);
static GstBuffer *gst_hls_demux_decrypt_fragment (GstHLSDemux * demux,
GstBuffer * encrypted_buffer, GError ** err);
static gboolean
gst_hls_demux_decrypt_start (GstHLSDemux * demux, const guint8 * key_data,
const guint8 * iv_data);
static void gst_hls_demux_decrypt_end (GstHLSDemux * demux);
#define gst_hls_demux_parent_class parent_class
G_DEFINE_TYPE (GstHLSDemux, gst_hls_demux, GST_TYPE_BIN);
@ -307,6 +307,9 @@ gst_hls_demux_change_state (GstElement * element, GstStateChange transition)
gst_hls_demux_reset (demux, FALSE);
gst_uri_downloader_reset (demux->downloader);
break;
case GST_STATE_CHANGE_NULL_TO_READY:
demux->adapter = gst_adapter_new ();
break;
default:
break;
}
@ -321,6 +324,10 @@ gst_hls_demux_change_state (GstElement * element, GstStateChange transition)
gst_task_join (demux->stream_task);
gst_hls_demux_reset (demux, FALSE);
break;
case GST_STATE_CHANGE_READY_TO_NULL:
gst_object_unref (demux->adapter);
demux->adapter = NULL;
break;
default:
break;
}
@ -735,14 +742,90 @@ _src_chain (GstPad * pad, GstObject * parent, GstBuffer * buffer)
gst_caps_unref (caps);
}
/* Is it encrypted? */
if (demux->current_key) {
GError *err = NULL;
GstBuffer *tmp_buffer;
gsize available;
/* restart the decrypting lib for a new fragment */
if (demux->starting_fragment) {
GstFragment *key_fragment;
GstBuffer *key_buffer;
GstMapInfo key_info;
/* new key? */
if (demux->key_url && strcmp (demux->key_url, demux->current_key) == 0) {
key_fragment = g_object_ref (demux->key_fragment);
} else {
g_free (demux->key_url);
demux->key_url = NULL;
if (demux->key_fragment)
g_object_unref (demux->key_fragment);
demux->key_fragment = NULL;
GST_INFO_OBJECT (demux, "Fetching key %s", demux->current_key);
key_fragment =
gst_uri_downloader_fetch_uri_with_referer (demux->downloader,
demux->current_key, demux->client->main ?
demux->client->main->uri : NULL, FALSE, &err);
if (key_fragment == NULL)
goto key_failed;
demux->key_url = g_strdup (demux->current_key);
demux->key_fragment = g_object_ref (key_fragment);
}
key_buffer = gst_fragment_get_buffer (key_fragment);
gst_buffer_map (key_buffer, &key_info, GST_MAP_READ);
gst_hls_demux_decrypt_start (demux, key_info.data, demux->current_iv);
gst_buffer_unmap (key_buffer, &key_info);
gst_buffer_unref (key_buffer);
gst_object_unref (key_fragment);
}
gst_adapter_push (demux->adapter, buffer);
/* must be a multiple of 16 */
available = gst_adapter_available (demux->adapter) & (~0xF);
if (available == 0) {
return GST_FLOW_OK;
}
buffer = gst_adapter_take_buffer (demux->adapter, available);
buffer = gst_hls_demux_decrypt_fragment (demux, buffer, &err);
if (buffer == NULL) {
GST_ELEMENT_ERROR (demux, STREAM, DECODE, ("Failed to decrypt buffer"),
("decryption failed %s", err->message));
g_error_free (err);
return GST_FLOW_ERROR;
}
tmp_buffer = demux->pending_buffer;
demux->pending_buffer = buffer;
buffer = tmp_buffer;
}
if (demux->starting_fragment) {
if (demux->segment.rate < 0)
/* Set DISCONT flag for every first buffer in reverse playback mode
* as each fragment for its own has to be reversed */
demux->discont = TRUE;
demux->starting_fragment = FALSE;
}
if (!buffer) {
return GST_FLOW_OK;
}
if (demux->discont) {
GST_DEBUG_OBJECT (demux, "Marking fragment as discontinuous");
GST_BUFFER_FLAG_SET (buffer, GST_BUFFER_FLAG_DISCONT);
demux->discont = FALSE;
} else if (demux->starting_fragment && demux->segment.rate < 0) {
/* Set DISCONT flag for every buffer in reverse playback mode
* as each fragment for its own has to be reversed */
GST_BUFFER_FLAG_SET (buffer, GST_BUFFER_FLAG_DISCONT);
} else {
GST_BUFFER_FLAG_UNSET (buffer, GST_BUFFER_FLAG_DISCONT);
}
@ -788,6 +871,10 @@ _src_chain (GstPad * pad, GstObject * parent, GstBuffer * buffer)
ret = GST_FLOW_OK;
return ret;
key_failed:
/* TODO ERROR here */
return GST_FLOW_ERROR;
}
static gboolean
@ -798,6 +885,27 @@ _src_event (GstPad * pad, GstObject * parent, GstEvent * event)
switch (GST_EVENT_TYPE (event)) {
case GST_EVENT_EOS:
if (demux->current_key)
gst_hls_demux_decrypt_end (demux);
/* TODO adapter should be empty */
/* pending buffer is only used for encrypted streams */
if (demux->pending_buffer) {
GstMapInfo info;
gsize unpadded_size;
/* Handle pkcs7 unpadding here */
gst_buffer_map (demux->pending_buffer, &info, GST_MAP_READ);
unpadded_size = info.size - info.data[info.size - 1];
gst_buffer_unmap (demux->pending_buffer, &info);
gst_buffer_resize (demux->pending_buffer, 0, unpadded_size);
/* TODO check return */
gst_pad_push (demux->srcpad, demux->pending_buffer);
demux->pending_buffer = NULL;
}
g_cond_signal (&demux->fragment_download_cond);
break;
default:
@ -1120,6 +1228,14 @@ gst_hls_demux_reset (GstHLSDemux * demux, gboolean dispose)
demux->srcpad = NULL;
}
if (demux->adapter)
gst_adapter_clear (demux->adapter);
if (demux->pending_buffer)
gst_buffer_unref (demux->pending_buffer);
demux->pending_buffer = NULL;
demux->current_key = NULL;
demux->current_iv = NULL;
demux->current_download_rate = -1;
}
@ -1325,8 +1441,8 @@ gst_hls_demux_update_playlist (GstHLSDemux * demux, gboolean update,
GST_M3U8_CLIENT_LOCK (demux->client);
last_sequence =
GST_M3U8_MEDIA_FILE (g_list_last (demux->client->current->
files)->data)->sequence;
GST_M3U8_MEDIA_FILE (g_list_last (demux->client->current->files)->
data)->sequence;
if (demux->client->sequence >= last_sequence - 3) {
GST_DEBUG_OBJECT (demux, "Sequence is beyond playlist. Moving back to %u",
@ -1426,8 +1542,8 @@ retry_failover_protection:
gst_m3u8_client_set_current (demux->client, previous_variant->data);
/* Try a lower bitrate (or stop if we just tried the lowest) */
if (GST_M3U8 (previous_variant->data)->iframe && new_bandwidth ==
GST_M3U8 (g_list_first (demux->client->main->iframe_lists)->data)->
bandwidth)
GST_M3U8 (g_list_first (demux->client->main->iframe_lists)->
data)->bandwidth)
return FALSE;
else if (!GST_M3U8 (previous_variant->data)->iframe && new_bandwidth ==
GST_M3U8 (g_list_first (demux->client->main->lists)->data)->bandwidth)
@ -1487,126 +1603,105 @@ gst_hls_demux_switch_playlist (GstHLSDemux * demux, GstFragment * fragment)
}
#endif
#if 0
#ifdef HAVE_NETTLE
static gboolean
decrypt_fragment (GstHLSDemux * demux, gsize length,
const guint8 * encrypted_data, guint8 * decrypted_data,
const guint8 * key_data, const guint8 * iv_data)
gst_hls_demux_decrypt_start (GstHLSDemux * demux, const guint8 * key_data,
const guint8 * iv_data)
{
struct CBC_CTX (struct aes_ctx, AES_BLOCK_SIZE) aes_ctx;
if (length % 16 != 0)
return FALSE;
aes_set_decrypt_key (&aes_ctx.ctx, 16, key_data);
CBC_SET_IV (&aes_ctx, iv_data);
CBC_DECRYPT (&aes_ctx, aes_decrypt, length, decrypted_data, encrypted_data);
aes_set_decrypt_key (&demux->aes_ctx.ctx, 16, key_data);
CBC_SET_IV (&demux->aes_ctx, iv_data);
return TRUE;
}
#else
static gboolean
decrypt_fragment (GstHLSDemux * demux, gsize length,
const guint8 * encrypted_data, guint8 * decrypted_data,
const guint8 * key_data, const guint8 * iv_data)
const guint8 * encrypted_data, guint8 * decrypted_data)
{
if (length % 16 != 0)
return FALSE;
CBC_DECRYPT (&demux->aes_ctx, aes_decrypt, length, decrypted_data,
encrypted_data);
return TRUE;
}
static void
gst_hls_demux_decrypt_end (GstHLSDemux * demux)
{
/* NOP */
}
#else
static gboolean
gst_hls_demux_decrypt_start (GstHLSDemux * demux, const guint8 * key_data,
const guint8 * iv_data)
{
gcry_cipher_hd_t aes_ctx = NULL;
gcry_error_t err = 0;
gboolean ret = FALSE;
err =
gcry_cipher_open (&aes_ctx, GCRY_CIPHER_AES128, GCRY_CIPHER_MODE_CBC, 0);
gcry_cipher_open (&demux->aes_ctx, GCRY_CIPHER_AES128,
GCRY_CIPHER_MODE_CBC, 0);
if (err)
goto out;
err = gcry_cipher_setkey (aes_ctx, key_data, 16);
err = gcry_cipher_setkey (demux->aes_ctx, key_data, 16);
if (err)
goto out;
err = gcry_cipher_setiv (aes_ctx, iv_data, 16);
if (err)
goto out;
err = gcry_cipher_decrypt (aes_ctx, decrypted_data, length,
encrypted_data, length);
if (err)
goto out;
err = gcry_cipher_setiv (demux->aes_ctx, iv_data, 16);
if (!err)
ret = TRUE;
out:
if (aes_ctx)
gcry_cipher_close (aes_ctx);
if (!ret)
if (demux->aes_ctx)
gcry_cipher_close (demux->aes_ctx);
return ret;
}
static gboolean
decrypt_fragment (GstHLSDemux * demux, gsize length,
const guint8 * encrypted_data, guint8 * decrypted_data)
{
gcry_error_t err = 0;
err = gcry_cipher_decrypt (demux->aes_ctx, decrypted_data, length,
encrypted_data, length);
return err == 0;
}
static void
gst_hls_demux_decrypt_end (GstHLSDemux * demux)
{
if (demux->aes_ctx)
gcry_cipher_close (demux->aes_ctx);
}
#endif
static GstFragment *
static GstBuffer *
gst_hls_demux_decrypt_fragment (GstHLSDemux * demux,
GstFragment * encrypted_fragment, const gchar * key, const guint8 * iv,
GError ** err)
GstBuffer * encrypted_buffer, GError ** err)
{
GstFragment *key_fragment, *ret = NULL;
GstBuffer *key_buffer, *encrypted_buffer, *decrypted_buffer;
GstMapInfo key_info, encrypted_info, decrypted_info;
gsize unpadded_size;
GstBuffer *decrypted_buffer = NULL;
GstMapInfo encrypted_info, decrypted_info;
if (demux->key_url && strcmp (demux->key_url, key) == 0) {
key_fragment = g_object_ref (demux->key_fragment);
} else {
g_free (demux->key_url);
demux->key_url = NULL;
if (demux->key_fragment)
g_object_unref (demux->key_fragment);
demux->key_fragment = NULL;
GST_INFO_OBJECT (demux, "Fetching key %s", key);
key_fragment =
gst_uri_downloader_fetch_uri_with_referer (demux->downloader, key,
demux->client->main ? demux->client->main->uri : NULL, FALSE, err);
if (key_fragment == NULL)
goto key_failed;
demux->key_url = g_strdup (key);
demux->key_fragment = g_object_ref (key_fragment);
}
key_buffer = gst_fragment_get_buffer (key_fragment);
encrypted_buffer = gst_fragment_get_buffer (encrypted_fragment);
decrypted_buffer =
gst_buffer_new_allocate (NULL, gst_buffer_get_size (encrypted_buffer),
NULL);
gst_buffer_map (key_buffer, &key_info, GST_MAP_READ);
gst_buffer_map (encrypted_buffer, &encrypted_info, GST_MAP_READ);
gst_buffer_map (decrypted_buffer, &decrypted_info, GST_MAP_WRITE);
if (key_info.size != 16)
goto decrypt_error;
if (!decrypt_fragment (demux, encrypted_info.size,
encrypted_info.data, decrypted_info.data, key_info.data, iv))
encrypted_info.data, decrypted_info.data))
goto decrypt_error;
/* Handle pkcs7 unpadding here */
unpadded_size =
decrypted_info.size - decrypted_info.data[decrypted_info.size - 1];
gst_buffer_unmap (decrypted_buffer, &decrypted_info);
gst_buffer_unmap (encrypted_buffer, &encrypted_info);
gst_buffer_unmap (key_buffer, &key_info);
gst_buffer_resize (decrypted_buffer, 0, unpadded_size);
gst_buffer_unref (key_buffer);
gst_buffer_unref (encrypted_buffer);
g_object_unref (key_fragment);
ret = gst_fragment_new ();
gst_fragment_add_buffer (ret, decrypted_buffer);
ret->completed = TRUE;
key_failed:
g_object_unref (encrypted_fragment);
return ret;
decrypt_error:
GST_ERROR_OBJECT (demux, "Failed to decrypt fragment");
@ -1615,17 +1710,12 @@ decrypt_error:
gst_buffer_unmap (decrypted_buffer, &decrypted_info);
gst_buffer_unmap (encrypted_buffer, &encrypted_info);
gst_buffer_unmap (key_buffer, &key_info);
gst_buffer_unref (key_buffer);
gst_buffer_unref (encrypted_buffer);
gst_buffer_unref (decrypted_buffer);
g_object_unref (key_fragment);
g_object_unref (encrypted_fragment);
return ret;
return decrypted_buffer;
}
#endif
static gboolean
gst_hls_demux_get_next_fragment (GstHLSDemux * demux,
@ -1661,6 +1751,8 @@ gst_hls_demux_get_next_fragment (GstHLSDemux * demux,
/* set up our source for download */
demux->current_timestamp = timestamp;
demux->starting_fragment = TRUE;
demux->current_key = key;
demux->current_iv = iv;
g_object_set (demux->src, "location", next_fragment_uri, NULL);
gst_element_set_state (demux->src, GST_STATE_READY); /* TODO check return */
gst_element_send_event (demux->src, gst_event_new_seek (1.0, GST_FORMAT_BYTES,
@ -1675,13 +1767,5 @@ gst_hls_demux_get_next_fragment (GstHLSDemux * demux,
g_mutex_unlock (&demux->fragment_download_lock);
gst_element_set_state (demux->src, GST_STATE_NULL);
#if 0
if (key) {
download = gst_hls_demux_decrypt_fragment (demux, download, key, iv, err);
if (download == NULL)
goto error;
}
#endif
return TRUE;
}

View file

@ -29,6 +29,12 @@
#include "m3u8.h"
#include "gstfragmented.h"
#include <gst/uridownloader/gsturidownloader.h>
#ifdef HAVE_NETTLE
#include <nettle/aes.h>
#include <nettle/cbc.h>
#else
#include <gcrypt.h>
#endif
G_BEGIN_DECLS
#define GST_TYPE_HLS_DEMUX \
@ -110,6 +116,20 @@ struct _GstHLSDemux
GCond fragment_download_cond;
GstClockTime current_timestamp;
gboolean starting_fragment;
/* decryption tooling */
#ifdef HAVE_NETTLE
struct CBC_CTX (struct aes_ctx, AES_BLOCK_SIZE) aes_ctx;
#else
gcry_cipher_hd_t aes_ctx;
#endif
const gchar *current_key;
const guint8 *current_iv;
GstAdapter *adapter; /* used to accumulate 16 bytes multiple chunks */
GstBuffer *pending_buffer; /* decryption scenario:
* the last buffer can only be pushed when
* resized, so need to store and wait for
* EOS to know it is the last */
};
struct _GstHLSDemuxClass