<commit_before>#include <ngx_http.h>
#include <ngx_md5.h>
#include "ngx_http_vod_submodule.h"
#include "ngx_http_vod_utils.h"
#include "vod/subtitle/webvtt_builder.h"
#include "vod/hls/hls_muxer.h"
#include "vod/mp4/mp4_muxer.h"
#include "vod/mp4/mp4_fragment.h"
#include "vod/mp4/mp4_init_segment.h"
#include "vod/udrm.h"

#if (NGX_HAVE_OPENSSL_EVP)
#include "vod/dash/edash_packager.h"
#include "vod/mp4/mp4_cbcs_encrypt.h"
#include "vod/hls/aes_cbc_encrypt.h"
#endif // NGX_HAVE_OPENSSL_EVP

// constants
#define SUPPORTED_CODECS \
	(VOD_CODEC_FLAG(AVC) | \
	VOD_CODEC_FLAG(HEVC) | \
	VOD_CODEC_FLAG(AAC) | \
	VOD_CODEC_FLAG(AC3) | \
	VOD_CODEC_FLAG(EAC3) | \
	VOD_CODEC_FLAG(MP3))

// content types
static u_char m3u8_content_type[] = "application/vnd.apple.mpegurl";
static u_char encryption_key_content_type[] = "application/octet-stream";
static u_char mpeg_ts_content_type[] = "video/MP2T";
static u_char vtt_content_type[] = "text/vtt";

static const u_char ts_file_ext[] = ".ts";
static const u_char m4s_file_ext[] = ".m4s";
static const u_char vtt_file_ext[] = ".vtt";
static const u_char mp4_file_ext[] = ".mp4";
static const u_char m3u8_file_ext[] = ".m3u8";
static const u_char key_file_ext[] = ".key";

// constants
static ngx_str_t empty_string = ngx_null_string;

ngx_conf_enum_t  hls_encryption_methods[] = {
	{ ngx_string("none"), HLS_ENC_NONE },
	{ ngx_string("aes-128"), HLS_ENC_AES_128 },
	{ ngx_string("sample-aes"), HLS_ENC_SAMPLE_AES },
	{ ngx_string("sample-aes-cenc"), HLS_ENC_SAMPLE_AES_CENC },
	{ ngx_null_string, 0 }
};

ngx_conf_enum_t  hls_container_formats[] = {
	{ ngx_string("auto"), HLS_CONTAINER_AUTO },
	{ ngx_string("mpegts"), HLS_CONTAINER_MPEGTS },
	{ ngx_string("fmp4"), HLS_CONTAINER_FMP4 },
	{ ngx_null_string, 0 }
};

static ngx_uint_t
ngx_http_vod_hls_get_container_format(
	ngx_http_vod_hls_loc_conf_t* conf,
	media_set_t* media_set)
{
	if (conf->m3u8_config.container_format != HLS_CONTAINER_AUTO)
	{
		return conf->m3u8_config.container_format;
	}

	if (media_set->filtered_tracks[0].media_info.codec_id == VOD_CODEC_ID_HEVC ||
		conf->encryption_method == HLS_ENC_SAMPLE_AES_CENC)
	{
		return HLS_CONTAINER_FMP4;
	}

	return HLS_CONTAINER_MPEGTS;
}

#if (NGX_HAVE_OPENSSL_EVP)
// some random salt to prevent the iv from being equal to key in case encryption_iv_seed is null
static u_char iv_salt[] = { 0xa7, 0xc6, 0x17, 0xab, 0x52, 0x2c, 0x40, 0x3c, 0xf6, 0x8a };

static void
ngx_http_vod_hls_init_encryption_iv(u_char* iv, uint32_t segment_index)
{
	u_char* p;

	// the IV is the segment index in big endian
	vod_memzero(iv, AES_BLOCK_SIZE - sizeof(uint32_t));
	segment_index++;
	p = iv + AES_BLOCK_SIZE - sizeof(uint32_t);
	*p++ = (u_char)(segment_index >> 24);
	*p++ = (u_char)(segment_index >> 16);
	*p++ = (u_char)(segment_index >> 8);
	*p++ = (u_char)(segment_index);
}

static ngx_int_t
ngx_http_vod_hls_get_iv_seed(
	ngx_http_vod_submodule_context_t* submodule_context, 
	media_sequence_t* sequence,
	ngx_str_t* result)
{
	ngx_http_vod_loc_conf_t* conf = submodule_context->conf;
	ngx_http_complex_value_t* value;

	if (conf->encryption_iv_seed != NULL)
	{
		value = conf->encryption_iv_seed;
	}
	else if (conf->secret_key != NULL)
	{
		value = conf->secret_key;
	}
	else
	{
		*result = sequence->mapped_uri;
		return NGX_OK;
	}

	if (ngx_http_complex_value(
		submodule_context->r,
		value,
		result) != NGX_OK)
	{
		ngx_log_debug0(NGX_LOG_DEBUG_HTTP, submodule_context->request_context.log, 0,
			"ngx_http_vod_hls_get_iv_seed: ngx_http_complex_value failed");
		return NGX_ERROR;
	}

	return NGX_OK;
}

static ngx_int_t
ngx_http_vod_hls_init_encryption_params(
	hls_encryption_params_t* encryption_params,
	ngx_http_vod_submodule_context_t* submodule_context,
	ngx_uint_t container_format)
{
	ngx_http_vod_loc_conf_t* conf = submodule_context->conf;
	media_sequence_t* sequence;
	drm_info_t* drm_info;
	ngx_str_t iv_seed;
	ngx_md5_t md5;
	ngx_int_t rc;

	encryption_params->type = conf->hls.encryption_method;
	if (encryption_params->type == HLS_ENC_NONE)
	{
		return NGX_OK;
	}

	encryption_params->iv = encryption_params->iv_buf;
	encryption_params->return_iv = FALSE;

	sequence = &submodule_context->media_set.sequences[0];

	if (conf->drm_enabled)
	{
		drm_info = sequence->drm_info;
		encryption_params->key = drm_info->key;

		if (drm_info->iv_set)
		{
			encryption_params->iv = drm_info->iv;
			return NGX_OK;
		}
	}
	else
	{
		encryption_params->key = sequence->encryption_key;
	}

	if (container_format != HLS_CONTAINER_FMP4 || encryption_params->type != HLS_ENC_AES_128)
	{
		ngx_http_vod_hls_init_encryption_iv(
			encryption_params->iv_buf,
			submodule_context->request_params.segment_index);
		return NGX_OK;
	}

	// must generate an iv in this case
	rc = ngx_http_vod_hls_get_iv_seed(
		submodule_context,
		sequence,
		&iv_seed);
	if (rc != NGX_OK)
	{
		return rc;
	}

	ngx_md5_init(&md5);
	ngx_md5_update(&md5, iv_salt, sizeof(iv_salt));
	ngx_md5_update(&md5, iv_seed.data, iv_seed.len);
	ngx_md5_final(encryption_params->iv_buf, &md5);
	encryption_params->return_iv = TRUE;
	return NGX_OK;
}

static ngx_int_t
ngx_http_vod_hls_init_segment_encryption(
	ngx_http_vod_submodule_context_t* submodule_context,
	segment_writer_t* segment_writer,
	ngx_uint_t container_format,
	hls_encryption_params_t* encryption_params)
{
	aes_cbc_encrypt_context_t* encrypted_write_context;
	buffer_pool_t* buffer_pool;
	vod_status_t rc;

	rc = ngx_http_vod_hls_init_encryption_params(encryption_params, submodule_context, container_format);
	if (rc != NGX_OK)
	{
		return rc;
	}

	if (encryption_params->type != HLS_ENC_AES_128)
	{
		return NGX_OK;
	}

	if (container_format == HLS_CONTAINER_MPEGTS)
	{
		buffer_pool = submodule_context->request_context.output_buffer_pool;
	}
	else
	{
		// Note: should not use buffer pool for fmp4 since the buffers have varying sizes
		buffer_pool = NULL;
	}

	rc = aes_cbc_encrypt_init(
		&encrypted_write_context,
		&submodule_context->request_context,
		segment_writer->write_tail,
		segment_writer->context,
		buffer_pool,
		encryption_params->key,
		encryption_params->iv);
	if (rc != VOD_OK)
	{
		ngx_log_debug1(NGX_LOG_DEBUG_HTTP, submodule_context->request_context.log, 0,
			"ngx_http_vod_hls_init_segment_encryption: aes_cbc_encrypt_init failed %i", rc);
		return ngx_http_vod_status_to_ngx_error(submodule_context->r, rc);
	}

	segment_writer->write_tail = (write_callback_t)aes_cbc_encrypt_write;
	segment_writer->context = encrypted_write_context;
	return NGX_OK;
}
#endif // NGX_HAVE_OPENSSL_EVP

static ngx_int_t
ngx_http_vod_hls_handle_master_playlist(
	ngx_http_vod_submodule_context_t* submodule_context,
	ngx_str_t* response,
	ngx_str_t* content_type)
{
	ngx_http_vod_loc_conf_t* conf = submodule_context->conf;
	ngx_str_t base_url = ngx_null_string;
	vod_status_t rc;

	if (conf->hls.absolute_master_urls)
	{
		rc = ngx_http_vod_get_base_url(submodule_context->r, conf->base_url, &empty_string, &base_url);
		if (rc != NGX_OK)
		{
			return rc;
		}
	}

	rc = m3u8_builder_build_master_playlist(
		&submodule_context->request_context,
		&conf->hls.m3u8_config,
		conf->hls.encryption_method,
		&base_url,
		&submodule_context->media_set,
		response);
	if (rc != VOD_OK)
	{
		ngx_log_debug1(NGX_LOG_DEBUG_HTTP, submodule_context->request_context.log, 0,
			"ngx_http_vod_hls_handle_master_playlist: m3u8_builder_build_master_playlist failed %i", rc);
		return ngx_http_vod_status_to_ngx_error(submodule_context->r, rc);
	}

	content_type->data = m3u8_content_type;
	content_type->len = sizeof(m3u8_content_type) - 1;
	
	return NGX_OK;
}

static ngx_int_t 
ngx_http_vod_hls_handle_index_playlist(
	ngx_http_vod_submodule_context_t* submodule_context,
	ngx_str_t* response,
	ngx_str_t* content_type)
{
	ngx_http_vod_loc_conf_t* conf = submodule_context->conf;
	hls_encryption_params_t encryption_params;
	ngx_uint_t container_format;
	ngx_str_t segments_base_url = ngx_null_string;
	ngx_str_t base_url = ngx_null_string;
	vod_status_t rc;

	if (conf->hls.absolute_index_urls)
	{
		rc = ngx_http_vod_get_base_url(submodule_context->r, conf->base_url, &submodule_context->r->uri, &base_url);
		if (rc != NGX_OK)
		{
			return rc;
		}

		if (conf->segments_base_url != NULL)
		{
			rc = ngx_http_vod_get_base_url(
				submodule_context->r,
				conf->segments_base_url,
				&submodule_context->r->uri,
				&segments_base_url);
			if (rc != NGX_OK)
			{
				return rc;
			}
		}
		else
		{
			segments_base_url = base_url;
		}
	}

	container_format = ngx_http_vod_hls_get_container_format(
		&conf->hls, 
		&submodule_context->media_set);

#if (NGX_HAVE_OPENSSL_EVP)
	rc = ngx_http_vod_hls_init_encryption_params(&encryption_params, submodule_context, container_format);
	if (rc != NGX_OK)
	{
		return rc;
	}

	if (encryption_params.type != HLS_ENC_NONE)
	{
		if (conf->hls.encryption_key_uri != NULL)
		{
			if (ngx_http_complex_value(
				submodule_context->r,
				conf->hls.encryption_key_uri,
				&encryption_params.key_uri) != NGX_OK)
			{
				ngx_log_debug0(NGX_LOG_DEBUG_HTTP, submodule_context->request_context.log, 0,
					"ngx_http_vod_hls_handle_index_playlist: ngx_http_complex_value failed");
				return NGX_ERROR;
			}
		}
		else
		{
			encryption_params.key_uri.len = 0;
		}
	}
#else
	encryption_params.type = HLS_ENC_NONE;
#endif // NGX_HAVE_OPENSSL_EVP

	rc = m3u8_builder_build_index_playlist(
		&submodule_context->request_context,
		&conf->hls.m3u8_config,
		&base_url,
		&segments_base_url,
		&submodule_context->request_params,
		&encryption_params,
		container_format,
		&submodule_context->media_set,
		response);
	if (rc != VOD_OK)
	{
		ngx_log_debug1(NGX_LOG_DEBUG_HTTP, submodule_context->request_context.log, 0,
			"ngx_http_vod_hls_handle_index_playlist: m3u8_builder_build_index_playlist failed %i", rc);
		return ngx_http_vod_status_to_ngx_error(submodule_context->r, rc);
	}

	content_type->data = m3u8_content_type;
	content_type->len = sizeof(m3u8_content_type) - 1;
	
	return NGX_OK;
}

static ngx_int_t
ngx_http_vod_hls_handle_iframe_playlist(
	ngx_http_vod_submodule_context_t* submodule_context,
	ngx_str_t* response,
	ngx_str_t* content_type)
{
	ngx_http_vod_loc_conf_t* conf = submodule_context->conf;
	ngx_str_t base_url = ngx_null_string;
	vod_status_t rc;
	
	if (conf->hls.encryption_method != HLS_ENC_NONE)
	{
		ngx_log_error(NGX_LOG_ERR, submodule_context->request_context.log, 0,
			"ngx_http_vod_hls_handle_iframe_playlist: iframes playlist not supported with encryption");
		return ngx_http_vod_status_to_ngx_error(submodule_context->r, VOD_BAD_REQUEST);
	}

	if (submodule_context->media_set.audio_filtering_needed)
	{
		ngx_log_error(NGX_LOG_ERR, submodule_context->request_context.log, 0,
			"ngx_http_vod_hls_handle_iframe_playlist: iframes playlist not supported with audio filtering");
		return ngx_http_vod_status_to_ngx_error(submodule_context->r, VOD_BAD_REQUEST);
	}

	if (conf->hls.absolute_iframe_urls)
	{
		rc = ngx_http_vod_get_base_url(submodule_context->r, conf->base_url, &submodule_context->r->uri, &base_url);
		if (rc != NGX_OK)
		{
			return rc;
		}
	}

	if (ngx_http_vod_hls_get_container_format(
		&conf->hls,
		&submodule_context->media_set) == HLS_CONTAINER_FMP4)
	{
		ngx_log_error(NGX_LOG_ERR, submodule_context->request_context.log, 0,
			"ngx_http_vod_hls_handle_iframe_playlist: iframes playlist not supported with fmp4 container");
		return ngx_http_vod_status_to_ngx_error(submodule_context->r, VOD_BAD_REQUEST);
	}

	rc = m3u8_builder_build_iframe_playlist(
		&submodule_context->request_context,
		&conf->hls.m3u8_config,
		&conf->hls.mpegts_muxer_config,
		&base_url,
		&submodule_context->request_params,
		&submodule_context->media_set,
		response);
	if (rc != VOD_OK)
	{
		ngx_log_debug1(NGX_LOG_DEBUG_HTTP, submodule_context->request_context.log, 0,
			"ngx_http_vod_hls_handle_iframe_playlist: m3u8_builder_build_iframe_playlist failed %i", rc);
		return ngx_http_vod_status_to_ngx_error(submodule_context->r, rc);
	}

	content_type->data = m3u8_content_type;
	content_type->len = sizeof(m3u8_content_type) - 1;
	
	return NGX_OK;
}

static ngx_int_t
ngx_http_vod_hls_handle_encryption_key(
	ngx_http_vod_submodule_context_t* submodule_context,
	ngx_str_t* response,
	ngx_str_t* content_type)
{
	u_char* encryption_key;

	encryption_key = ngx_palloc(submodule_context->request_context.pool, BUFFER_CACHE_KEY_SIZE);
	if (encryption_key == NULL)
	{
		ngx_log_debug0(NGX_LOG_DEBUG_HTTP, submodule_context->request_context.log, 0,
			"ngx_http_vod_hls_handle_encryption_key: ngx_palloc failed");
		return ngx_http_vod_status_to_ngx_error(submodule_context->r, VOD_ALLOC_FAILED);
	}

	ngx_memcpy(encryption_key, submodule_context->media_set.sequences[0].encryption_key, BUFFER_CACHE_KEY_SIZE);

	response->data = encryption_key;
	response->len = BUFFER_CACHE_KEY_SIZE;

	content_type->data = encryption_key_content_type;
	content_type->len = sizeof(encryption_key_content_type) - 1;

	return NGX_OK;
}

static ngx_int_t
ngx_http_vod_hls_init_ts_frame_processor(
	ngx_http_vod_submodule_context_t* submodule_context,
	segment_writer_t* segment_writer,
	ngx_http_vod_frame_processor_t* frame_processor,
	void** frame_processor_state,
	ngx_str_t* output_buffer,
	size_t* response_size,
	ngx_str_t* content_type)
{
	hls_encryption_params_t encryption_params;
	hls_muxer_state_t* state;
	vod_status_t rc;
	bool_t reuse_output_buffers;

#if (NGX_HAVE_OPENSSL_EVP)
	rc = ngx_http_vod_hls_init_segment_encryption(
		submodule_context,
		segment_writer,
		HLS_CONTAINER_MPEGTS,
		&encryption_params);
	if (rc != NGX_OK)
	{
		return rc;
	}

	if (encryption_params.type == HLS_ENC_SAMPLE_AES_CENC)
	{
		ngx_log_error(NGX_LOG_ERR, submodule_context->request_context.log, 0,
			"ngx_http_vod_hls_init_ts_frame_processor: sample aes cenc not supported with mpeg ts container");
		return ngx_http_vod_status_to_ngx_error(submodule_context->r, VOD_BAD_REQUEST);
	}

	reuse_output_buffers = encryption_params.type == HLS_ENC_AES_128;
#else
	encryption_params.type = HLS_ENC_NONE;
	reuse_output_buffers = FALSE;
#endif // NGX_HAVE_OPENSSL_EVP

	rc = hls_muxer_init_segment(
		&submodule_context->request_context,
		&submodule_context->conf->hls.mpegts_muxer_config,
		&encryption_params,
		submodule_context->request_params.segment_index,
		&submodule_context->media_set,
		segment_writer->write_tail,
		segment_writer->context,
		reuse_output_buffers,
		response_size, 
		output_buffer,
		&state);
	if (rc != VOD_OK)
	{
		ngx_log_debug1(NGX_LOG_DEBUG_HTTP, submodule_context->request_context.log, 0,
			"ngx_http_vod_hls_init_ts_frame_processor: hls_muxer_init failed %i", rc);
		return ngx_http_vod_status_to_ngx_error(submodule_context->r, rc);
	}

	if (encryption_params.type == HLS_ENC_AES_128 && 
		*response_size != 0)
	{
		*response_size = aes_round_up_to_block(*response_size);
	}

	*frame_processor = (ngx_http_vod_frame_processor_t)hls_muxer_process;
	*frame_processor_state = state;

	content_type->len = sizeof(mpeg_ts_content_type) - 1;
	content_type->data = (u_char *)mpeg_ts_content_type;

	return NGX_OK;
}

static ngx_int_t
ngx_http_vod_hls_handle_mp4_init_segment(
	ngx_http_vod_submodule_context_t* submodule_context,
	ngx_str_t* response,
	ngx_str_t* content_type)
{
	atom_writer_t* stsd_atom_writers = NULL;
	vod_status_t rc;

#if (NGX_HAVE_OPENSSL_EVP)
	aes_cbc_encrypt_context_t* encrypted_write_context;
	hls_encryption_params_t encryption_params;
	drm_info_t* drm_info;

	rc = ngx_http_vod_hls_init_encryption_params(&encryption_params, submodule_context, HLS_CONTAINER_FMP4);
	if (rc != NGX_OK)
	{
		return rc;
	}

	switch (encryption_params.type)
	{
	case HLS_ENC_SAMPLE_AES:
		rc = mp4_init_segment_get_encrypted_stsd_writers(
			&submodule_context->request_context,
			&submodule_context->media_set,
			SCHEME_TYPE_CBCS,
			FALSE,
			NULL,
			encryption_params.iv,
			&stsd_atom_writers);
		if (rc != VOD_OK)
		{
			ngx_log_debug1(NGX_LOG_DEBUG_HTTP, submodule_context->request_context.log, 0,
				"ngx_http_vod_hls_handle_mp4_init_segment: mp4_init_segment_get_encrypted_stsd_writers failed %i (1)", rc);
			return ngx_http_vod_status_to_ngx_error(submodule_context->r, rc);
		}
		break;

	case HLS_ENC_SAMPLE_AES_CENC:
		drm_info = (drm_info_t*)submodule_context->media_set.sequences[0].drm_info;

		rc = mp4_init_segment_get_encrypted_stsd_writers(
			&submodule_context->request_context,
			&submodule_context->media_set,
			SCHEME_TYPE_CENC,
			FALSE,
			drm_info->key_id,
			NULL,
			&stsd_atom_writers);
		if (rc != VOD_OK)
		{
			ngx_log_debug1(NGX_LOG_DEBUG_HTTP, submodule_context->request_context.log, 0,
				"ngx_http_vod_hls_handle_mp4_init_segment: mp4_init_segment_get_encrypted_stsd_writers failed %i (2)", rc);
			return ngx_http_vod_status_to_ngx_error(submodule_context->r, rc);
		}
		break;

	default:;
	}
#endif // NGX_HAVE_OPENSSL_EVP

	rc = mp4_init_segment_build(
		&submodule_context->request_context,
		&submodule_context->media_set,
		ngx_http_vod_submodule_size_only(submodule_context),
		NULL,
		stsd_atom_writers,
		response);
	if (rc != VOD_OK)
	{
		ngx_log_debug1(NGX_LOG_DEBUG_HTTP, submodule_context->request_context.log, 0,
			"ngx_http_vod_hls_handle_mp4_init_segment: mp4_init_segment_build failed %i", rc);
		return ngx_http_vod_status_to_ngx_error(submodule_context->r, rc);
	}

#if (NGX_HAVE_OPENSSL_EVP)
	if (encryption_params.type == HLS_ENC_AES_128)
	{
		rc = aes_cbc_encrypt_init(
			&encrypted_write_context,
			&submodule_context->request_context,
			NULL,
			NULL,
			NULL,
			encryption_params.key,
			encryption_params.iv);
		if (rc != VOD_OK)
		{
			ngx_log_debug1(NGX_LOG_DEBUG_HTTP, submodule_context->request_context.log, 0,
				"ngx_http_vod_hls_handle_mp4_init_segment: aes_cbc_encrypt_init failed %i", rc);
			return ngx_http_vod_status_to_ngx_error(submodule_context->r, rc);
		}

		rc = aes_cbc_encrypt(
			encrypted_write_context,
			response,
			response,
			TRUE);
		if (rc != VOD_OK)
		{
			ngx_log_debug1(NGX_LOG_DEBUG_HTTP, submodule_context->request_context.log, 0,
				"ngx_http_vod_hls_handle_mp4_init_segment: aes_cbc_encrypt failed %i", rc);
			return ngx_http_vod_status_to_ngx_error(submodule_context->r, rc);
		}
	}
#endif // NGX_HAVE_OPENSSL_EVP

	mp4_fragment_get_content_type(
		submodule_context->media_set.track_count[MEDIA_TYPE_VIDEO],
		content_type);
	return NGX_OK;
}

static ngx_int_t
ngx_http_vod_hls_init_fmp4_frame_processor(
	ngx_http_vod_submodule_context_t* submodule_context,
	segment_writer_t* segment_writer,
	ngx_http_vod_frame_processor_t* frame_processor,
	void** frame_processor_state,
	ngx_str_t* output_buffer,
	size_t* response_size,
	ngx_str_t* content_type)
{
	dash_fragment_header_extensions_t header_extensions;
	fragment_writer_state_t* state;
	mp4_muxer_state_t* muxer_state;
	segment_writer_t* segment_writers;
	vod_status_t rc;
	bool_t per_stream_writer;
	bool_t reuse_input_buffers = FALSE;
	bool_t size_only = ngx_http_vod_submodule_size_only(submodule_context);

#if (NGX_HAVE_OPENSSL_EVP)
	ngx_http_vod_loc_conf_t* conf = submodule_context->conf;
	hls_encryption_params_t encryption_params;
	segment_writer_t drm_writer;

	rc = ngx_http_vod_hls_init_segment_encryption(
		submodule_context,
		segment_writer,
		HLS_CONTAINER_FMP4,
		&encryption_params);
	if (rc != NGX_OK)
	{
		return rc;
	}

	reuse_input_buffers = encryption_params.type != HLS_ENC_NONE;

	if (conf->hls.encryption_method == HLS_ENC_SAMPLE_AES)
	{
		rc = mp4_cbcs_encrypt_get_writers(
			&submodule_context->request_context,
			&submodule_context->media_set,
			segment_writer,
			encryption_params.key,
			encryption_params.iv,
			&segment_writers);
		if (rc != VOD_OK)
		{
			ngx_log_debug1(NGX_LOG_DEBUG_HTTP, submodule_context->request_context.log, 0,
				"ngx_http_vod_hls_init_fmp4_frame_processor: mp4_cbcs_encrypt_get_writers failed %i", rc);
			return ngx_http_vod_status_to_ngx_error(submodule_context->r, rc);
		}
		per_stream_writer = TRUE;
	}
	else
#endif // NGX_HAVE_OPENSSL_EVP
	{
		segment_writers = segment_writer;
		per_stream_writer = FALSE;
	}

	if (submodule_context->media_set.total_track_count > 1)
	{
#if (NGX_HAVE_OPENSSL_EVP)
		if (encryption_params.type == HLS_ENC_SAMPLE_AES_CENC)
		{
			ngx_log_error(NGX_LOG_ERR, submodule_context->request_context.log, 0,
				"ngx_http_vod_hls_init_fmp4_frame_processor: multiple streams not supported for sample aes cenc");
			return ngx_http_vod_status_to_ngx_error(submodule_context->r, VOD_BAD_REQUEST);
		}
#endif // NGX_HAVE_OPENSSL_EVP

		// muxed segment
		rc = mp4_muxer_init_fragment(
			&submodule_context->request_context,
			submodule_context->request_params.segment_index,
			&submodule_context->media_set,
			segment_writers,
			per_stream_writer,
			reuse_input_buffers,
			size_only,
			output_buffer,
			response_size,
			&muxer_state);
		if (rc != VOD_OK)
		{
			ngx_log_debug1(NGX_LOG_DEBUG_HTTP, submodule_context->request_context.log, 0,
				"ngx_http_vod_hls_init_fmp4_frame_processor: mp4_muxer_init_fragment failed %i", rc);
			return ngx_http_vod_status_to_ngx_error(submodule_context->r, rc);
		}

		*frame_processor = (ngx_http_vod_frame_processor_t)mp4_muxer_process_frames;
		*frame_processor_state = muxer_state;
	}
	else
	{
#if (NGX_HAVE_OPENSSL_EVP)
		if (encryption_params.type == HLS_ENC_SAMPLE_AES_CENC)
		{
			drm_writer = *segment_writer;		// must not change segment_writer, otherwise the header will be encrypted

			// encyrpted fragment
			rc = edash_packager_get_fragment_writer(
				&drm_writer,
				&submodule_context->request_context,
				&submodule_context->media_set,
				submodule_context->request_params.segment_index,
				conf->min_single_nalu_per_frame_segment > 0 &&
				submodule_context->media_set.initial_segment_clip_relative_index >= conf->min_single_nalu_per_frame_segment - 1,
				submodule_context->media_set.sequences[0].encryption_key,		// iv
				size_only,
				output_buffer,
				response_size);
			switch (rc)
			{
			case VOD_DONE:		// passthrough
				break;

			case VOD_OK:
				segment_writers = &drm_writer;
				reuse_input_buffers = TRUE;		// mp4_cenc_encrypt allocates new buffers
				break;

			default:
				ngx_log_debug1(NGX_LOG_DEBUG_HTTP, submodule_context->request_context.log, 0,
					"ngx_http_vod_hls_init_fmp4_frame_processor: edash_packager_get_fragment_writer failed %i", rc);
				return ngx_http_vod_status_to_ngx_error(submodule_context->r, rc);
			}
		}
		else
#endif // NGX_HAVE_OPENSSL_EVP
		{
			// for single stream use dash segments
			ngx_memzero(&header_extensions, sizeof(header_extensions));

			rc = dash_packager_build_fragment_header(
				&submodule_context->request_context,
				&submodule_context->media_set,
				submodule_context->request_params.segment_index,
				0,	// sample description index
				&header_extensions,
				size_only,
				output_buffer,
				response_size);
			if (rc != VOD_OK)
			{
				ngx_log_debug1(NGX_LOG_DEBUG_HTTP, submodule_context->request_context.log, 0,<commit_msg>remove redundant line<commit_after>#include <ngx_http.h>
#include <ngx_md5.h>
#include "ngx_http_vod_submodule.h"
#include "ngx_http_vod_utils.h"
#include "vod/subtitle/webvtt_builder.h"
#include "vod/hls/hls_muxer.h"
#include "vod/mp4/mp4_muxer.h"
#include "vod/mp4/mp4_fragment.h"
#include "vod/mp4/mp4_init_segment.h"
#include "vod/udrm.h"

#if (NGX_HAVE_OPENSSL_EVP)
#include "vod/dash/edash_packager.h"
#include "vod/mp4/mp4_cbcs_encrypt.h"
#include "vod/hls/aes_cbc_encrypt.h"
#endif // NGX_HAVE_OPENSSL_EVP

// constants
#define SUPPORTED_CODECS \
	(VOD_CODEC_FLAG(AVC) | \
	VOD_CODEC_FLAG(HEVC) | \
	VOD_CODEC_FLAG(AAC) | \
	VOD_CODEC_FLAG(AC3) | \
	VOD_CODEC_FLAG(EAC3) | \
	VOD_CODEC_FLAG(MP3))

// content types
static u_char m3u8_content_type[] = "application/vnd.apple.mpegurl";
static u_char encryption_key_content_type[] = "application/octet-stream";
static u_char mpeg_ts_content_type[] = "video/MP2T";
static u_char vtt_content_type[] = "text/vtt";

static const u_char ts_file_ext[] = ".ts";
static const u_char m4s_file_ext[] = ".m4s";
static const u_char vtt_file_ext[] = ".vtt";
static const u_char mp4_file_ext[] = ".mp4";
static const u_char m3u8_file_ext[] = ".m3u8";
static const u_char key_file_ext[] = ".key";

// constants
static ngx_str_t empty_string = ngx_null_string;

ngx_conf_enum_t  hls_encryption_methods[] = {
	{ ngx_string("none"), HLS_ENC_NONE },
	{ ngx_string("aes-128"), HLS_ENC_AES_128 },
	{ ngx_string("sample-aes"), HLS_ENC_SAMPLE_AES },
	{ ngx_string("sample-aes-cenc"), HLS_ENC_SAMPLE_AES_CENC },
	{ ngx_null_string, 0 }
};

ngx_conf_enum_t  hls_container_formats[] = {
	{ ngx_string("auto"), HLS_CONTAINER_AUTO },
	{ ngx_string("mpegts"), HLS_CONTAINER_MPEGTS },
	{ ngx_string("fmp4"), HLS_CONTAINER_FMP4 },
	{ ngx_null_string, 0 }
};

static ngx_uint_t
ngx_http_vod_hls_get_container_format(
	ngx_http_vod_hls_loc_conf_t* conf,
	media_set_t* media_set)
{
	if (conf->m3u8_config.container_format != HLS_CONTAINER_AUTO)
	{
		return conf->m3u8_config.container_format;
	}

	if (media_set->filtered_tracks[0].media_info.codec_id == VOD_CODEC_ID_HEVC ||
		conf->encryption_method == HLS_ENC_SAMPLE_AES_CENC)
	{
		return HLS_CONTAINER_FMP4;
	}

	return HLS_CONTAINER_MPEGTS;
}

#if (NGX_HAVE_OPENSSL_EVP)
// some random salt to prevent the iv from being equal to key in case encryption_iv_seed is null
static u_char iv_salt[] = { 0xa7, 0xc6, 0x17, 0xab, 0x52, 0x2c, 0x40, 0x3c, 0xf6, 0x8a };

static void
ngx_http_vod_hls_init_encryption_iv(u_char* iv, uint32_t segment_index)
{
	u_char* p;

	// the IV is the segment index in big endian
	vod_memzero(iv, AES_BLOCK_SIZE - sizeof(uint32_t));
	segment_index++;
	p = iv + AES_BLOCK_SIZE - sizeof(uint32_t);
	*p++ = (u_char)(segment_index >> 24);
	*p++ = (u_char)(segment_index >> 16);
	*p++ = (u_char)(segment_index >> 8);
	*p++ = (u_char)(segment_index);
}

static ngx_int_t
ngx_http_vod_hls_get_iv_seed(
	ngx_http_vod_submodule_context_t* submodule_context, 
	media_sequence_t* sequence,
	ngx_str_t* result)
{
	ngx_http_vod_loc_conf_t* conf = submodule_context->conf;
	ngx_http_complex_value_t* value;

	if (conf->encryption_iv_seed != NULL)
	{
		value = conf->encryption_iv_seed;
	}
	else if (conf->secret_key != NULL)
	{
		value = conf->secret_key;
	}
	else
	{
		*result = sequence->mapped_uri;
		return NGX_OK;
	}

	if (ngx_http_complex_value(
		submodule_context->r,
		value,
		result) != NGX_OK)
	{
		ngx_log_debug0(NGX_LOG_DEBUG_HTTP, submodule_context->request_context.log, 0,
			"ngx_http_vod_hls_get_iv_seed: ngx_http_complex_value failed");
		return NGX_ERROR;
	}

	return NGX_OK;
}

static ngx_int_t
ngx_http_vod_hls_init_encryption_params(
	hls_encryption_params_t* encryption_params,
	ngx_http_vod_submodule_context_t* submodule_context,
	ngx_uint_t container_format)
{
	ngx_http_vod_loc_conf_t* conf = submodule_context->conf;
	media_sequence_t* sequence;
	drm_info_t* drm_info;
	ngx_str_t iv_seed;
	ngx_md5_t md5;
	ngx_int_t rc;

	encryption_params->type = conf->hls.encryption_method;
	if (encryption_params->type == HLS_ENC_NONE)
	{
		return NGX_OK;
	}

	encryption_params->iv = encryption_params->iv_buf;
	encryption_params->return_iv = FALSE;

	sequence = &submodule_context->media_set.sequences[0];

	if (conf->drm_enabled)
	{
		drm_info = sequence->drm_info;
		encryption_params->key = drm_info->key;

		if (drm_info->iv_set)
		{
			encryption_params->iv = drm_info->iv;
			return NGX_OK;
		}
	}
	else
	{
		encryption_params->key = sequence->encryption_key;
	}

	if (container_format != HLS_CONTAINER_FMP4 || encryption_params->type != HLS_ENC_AES_128)
	{
		ngx_http_vod_hls_init_encryption_iv(
			encryption_params->iv_buf,
			submodule_context->request_params.segment_index);
		return NGX_OK;
	}

	// must generate an iv in this case
	rc = ngx_http_vod_hls_get_iv_seed(
		submodule_context,
		sequence,
		&iv_seed);
	if (rc != NGX_OK)
	{
		return rc;
	}

	ngx_md5_init(&md5);
	ngx_md5_update(&md5, iv_salt, sizeof(iv_salt));
	ngx_md5_update(&md5, iv_seed.data, iv_seed.len);
	ngx_md5_final(encryption_params->iv_buf, &md5);
	encryption_params->return_iv = TRUE;
	return NGX_OK;
}

static ngx_int_t
ngx_http_vod_hls_init_segment_encryption(
	ngx_http_vod_submodule_context_t* submodule_context,
	segment_writer_t* segment_writer,
	ngx_uint_t container_format,
	hls_encryption_params_t* encryption_params)
{
	aes_cbc_encrypt_context_t* encrypted_write_context;
	buffer_pool_t* buffer_pool;
	vod_status_t rc;

	rc = ngx_http_vod_hls_init_encryption_params(encryption_params, submodule_context, container_format);
	if (rc != NGX_OK)
	{
		return rc;
	}

	if (encryption_params->type != HLS_ENC_AES_128)
	{
		return NGX_OK;
	}

	if (container_format == HLS_CONTAINER_MPEGTS)
	{
		buffer_pool = submodule_context->request_context.output_buffer_pool;
	}
	else
	{
		// Note: should not use buffer pool for fmp4 since the buffers have varying sizes
		buffer_pool = NULL;
	}

	rc = aes_cbc_encrypt_init(
		&encrypted_write_context,
		&submodule_context->request_context,
		segment_writer->write_tail,
		segment_writer->context,
		buffer_pool,
		encryption_params->key,
		encryption_params->iv);
	if (rc != VOD_OK)
	{
		ngx_log_debug1(NGX_LOG_DEBUG_HTTP, submodule_context->request_context.log, 0,
			"ngx_http_vod_hls_init_segment_encryption: aes_cbc_encrypt_init failed %i", rc);
		return ngx_http_vod_status_to_ngx_error(submodule_context->r, rc);
	}

	segment_writer->write_tail = (write_callback_t)aes_cbc_encrypt_write;
	segment_writer->context = encrypted_write_context;
	return NGX_OK;
}
#endif // NGX_HAVE_OPENSSL_EVP

static ngx_int_t
ngx_http_vod_hls_handle_master_playlist(
	ngx_http_vod_submodule_context_t* submodule_context,
	ngx_str_t* response,
	ngx_str_t* content_type)
{
	ngx_http_vod_loc_conf_t* conf = submodule_context->conf;
	ngx_str_t base_url = ngx_null_string;
	vod_status_t rc;

	if (conf->hls.absolute_master_urls)
	{
		rc = ngx_http_vod_get_base_url(submodule_context->r, conf->base_url, &empty_string, &base_url);
		if (rc != NGX_OK)
		{
			return rc;
		}
	}

	rc = m3u8_builder_build_master_playlist(
		&submodule_context->request_context,
		&conf->hls.m3u8_config,
		conf->hls.encryption_method,
		&base_url,
		&submodule_context->media_set,
		response);
	if (rc != VOD_OK)
	{
		ngx_log_debug1(NGX_LOG_DEBUG_HTTP, submodule_context->request_context.log, 0,
			"ngx_http_vod_hls_handle_master_playlist: m3u8_builder_build_master_playlist failed %i", rc);
		return ngx_http_vod_status_to_ngx_error(submodule_context->r, rc);
	}

	content_type->data = m3u8_content_type;
	content_type->len = sizeof(m3u8_content_type) - 1;
	
	return NGX_OK;
}

static ngx_int_t 
ngx_http_vod_hls_handle_index_playlist(
	ngx_http_vod_submodule_context_t* submodule_context,
	ngx_str_t* response,
	ngx_str_t* content_type)
{
	ngx_http_vod_loc_conf_t* conf = submodule_context->conf;
	hls_encryption_params_t encryption_params;
	ngx_uint_t container_format;
	ngx_str_t segments_base_url = ngx_null_string;
	ngx_str_t base_url = ngx_null_string;
	vod_status_t rc;

	if (conf->hls.absolute_index_urls)
	{
		rc = ngx_http_vod_get_base_url(submodule_context->r, conf->base_url, &submodule_context->r->uri, &base_url);
		if (rc != NGX_OK)
		{
			return rc;
		}

		if (conf->segments_base_url != NULL)
		{
			rc = ngx_http_vod_get_base_url(
				submodule_context->r,
				conf->segments_base_url,
				&submodule_context->r->uri,
				&segments_base_url);
			if (rc != NGX_OK)
			{
				return rc;
			}
		}
		else
		{
			segments_base_url = base_url;
		}
	}

	container_format = ngx_http_vod_hls_get_container_format(
		&conf->hls, 
		&submodule_context->media_set);

#if (NGX_HAVE_OPENSSL_EVP)
	rc = ngx_http_vod_hls_init_encryption_params(&encryption_params, submodule_context, container_format);
	if (rc != NGX_OK)
	{
		return rc;
	}

	if (encryption_params.type != HLS_ENC_NONE)
	{
		if (conf->hls.encryption_key_uri != NULL)
		{
			if (ngx_http_complex_value(
				submodule_context->r,
				conf->hls.encryption_key_uri,
				&encryption_params.key_uri) != NGX_OK)
			{
				ngx_log_debug0(NGX_LOG_DEBUG_HTTP, submodule_context->request_context.log, 0,
					"ngx_http_vod_hls_handle_index_playlist: ngx_http_complex_value failed");
				return NGX_ERROR;
			}
		}
		else
		{
			encryption_params.key_uri.len = 0;
		}
	}
#else
	encryption_params.type = HLS_ENC_NONE;
#endif // NGX_HAVE_OPENSSL_EVP

	rc = m3u8_builder_build_index_playlist(
		&submodule_context->request_context,
		&conf->hls.m3u8_config,
		&base_url,
		&segments_base_url,
		&submodule_context->request_params,
		&encryption_params,
		container_format,
		&submodule_context->media_set,
		response);
	if (rc != VOD_OK)
	{
		ngx_log_debug1(NGX_LOG_DEBUG_HTTP, submodule_context->request_context.log, 0,
			"ngx_http_vod_hls_handle_index_playlist: m3u8_builder_build_index_playlist failed %i", rc);
		return ngx_http_vod_status_to_ngx_error(submodule_context->r, rc);
	}

	content_type->data = m3u8_content_type;
	content_type->len = sizeof(m3u8_content_type) - 1;
	
	return NGX_OK;
}

static ngx_int_t
ngx_http_vod_hls_handle_iframe_playlist(
	ngx_http_vod_submodule_context_t* submodule_context,
	ngx_str_t* response,
	ngx_str_t* content_type)
{
	ngx_http_vod_loc_conf_t* conf = submodule_context->conf;
	ngx_str_t base_url = ngx_null_string;
	vod_status_t rc;
	
	if (conf->hls.encryption_method != HLS_ENC_NONE)
	{
		ngx_log_error(NGX_LOG_ERR, submodule_context->request_context.log, 0,
			"ngx_http_vod_hls_handle_iframe_playlist: iframes playlist not supported with encryption");
		return ngx_http_vod_status_to_ngx_error(submodule_context->r, VOD_BAD_REQUEST);
	}

	if (submodule_context->media_set.audio_filtering_needed)
	{
		ngx_log_error(NGX_LOG_ERR, submodule_context->request_context.log, 0,
			"ngx_http_vod_hls_handle_iframe_playlist: iframes playlist not supported with audio filtering");
		return ngx_http_vod_status_to_ngx_error(submodule_context->r, VOD_BAD_REQUEST);
	}

	if (conf->hls.absolute_iframe_urls)
	{
		rc = ngx_http_vod_get_base_url(submodule_context->r, conf->base_url, &submodule_context->r->uri, &base_url);
		if (rc != NGX_OK)
		{
			return rc;
		}
	}

	if (ngx_http_vod_hls_get_container_format(
		&conf->hls,
		&submodule_context->media_set) == HLS_CONTAINER_FMP4)
	{
		ngx_log_error(NGX_LOG_ERR, submodule_context->request_context.log, 0,
			"ngx_http_vod_hls_handle_iframe_playlist: iframes playlist not supported with fmp4 container");
		return ngx_http_vod_status_to_ngx_error(submodule_context->r, VOD_BAD_REQUEST);
	}

	rc = m3u8_builder_build_iframe_playlist(
		&submodule_context->request_context,
		&conf->hls.m3u8_config,
		&conf->hls.mpegts_muxer_config,
		&base_url,
		&submodule_context->request_params,
		&submodule_context->media_set,
		response);
	if (rc != VOD_OK)
	{
		ngx_log_debug1(NGX_LOG_DEBUG_HTTP, submodule_context->request_context.log, 0,
			"ngx_http_vod_hls_handle_iframe_playlist: m3u8_builder_build_iframe_playlist failed %i", rc);
		return ngx_http_vod_status_to_ngx_error(submodule_context->r, rc);
	}

	content_type->data = m3u8_content_type;
	content_type->len = sizeof(m3u8_content_type) - 1;
	
	return NGX_OK;
}

static ngx_int_t
ngx_http_vod_hls_handle_encryption_key(
	ngx_http_vod_submodule_context_t* submodule_context,
	ngx_str_t* response,
	ngx_str_t* content_type)
{
	u_char* encryption_key;

	encryption_key = ngx_palloc(submodule_context->request_context.pool, BUFFER_CACHE_KEY_SIZE);
	if (encryption_key == NULL)
	{
		ngx_log_debug0(NGX_LOG_DEBUG_HTTP, submodule_context->request_context.log, 0,
			"ngx_http_vod_hls_handle_encryption_key: ngx_palloc failed");
		return ngx_http_vod_status_to_ngx_error(submodule_context->r, VOD_ALLOC_FAILED);
	}

	ngx_memcpy(encryption_key, submodule_context->media_set.sequences[0].encryption_key, BUFFER_CACHE_KEY_SIZE);

	response->data = encryption_key;
	response->len = BUFFER_CACHE_KEY_SIZE;

	content_type->data = encryption_key_content_type;
	content_type->len = sizeof(encryption_key_content_type) - 1;

	return NGX_OK;
}

static ngx_int_t
ngx_http_vod_hls_init_ts_frame_processor(
	ngx_http_vod_submodule_context_t* submodule_context,
	segment_writer_t* segment_writer,
	ngx_http_vod_frame_processor_t* frame_processor,
	void** frame_processor_state,
	ngx_str_t* output_buffer,
	size_t* response_size,
	ngx_str_t* content_type)
{
	hls_encryption_params_t encryption_params;
	hls_muxer_state_t* state;
	vod_status_t rc;
	bool_t reuse_output_buffers;

#if (NGX_HAVE_OPENSSL_EVP)
	rc = ngx_http_vod_hls_init_segment_encryption(
		submodule_context,
		segment_writer,
		HLS_CONTAINER_MPEGTS,
		&encryption_params);
	if (rc != NGX_OK)
	{
		return rc;
	}

	if (encryption_params.type == HLS_ENC_SAMPLE_AES_CENC)
	{
		ngx_log_error(NGX_LOG_ERR, submodule_context->request_context.log, 0,
			"ngx_http_vod_hls_init_ts_frame_processor: sample aes cenc not supported with mpeg ts container");
		return ngx_http_vod_status_to_ngx_error(submodule_context->r, VOD_BAD_REQUEST);
	}

	reuse_output_buffers = encryption_params.type == HLS_ENC_AES_128;
#else
	encryption_params.type = HLS_ENC_NONE;
	reuse_output_buffers = FALSE;
#endif // NGX_HAVE_OPENSSL_EVP

	rc = hls_muxer_init_segment(
		&submodule_context->request_context,
		&submodule_context->conf->hls.mpegts_muxer_config,
		&encryption_params,
		submodule_context->request_params.segment_index,
		&submodule_context->media_set,
		segment_writer->write_tail,
		segment_writer->context,
		reuse_output_buffers,
		response_size, 
		output_buffer,
		&state);
	if (rc != VOD_OK)
	{
		ngx_log_debug1(NGX_LOG_DEBUG_HTTP, submodule_context->request_context.log, 0,
			"ngx_http_vod_hls_init_ts_frame_processor: hls_muxer_init failed %i", rc);
		return ngx_http_vod_status_to_ngx_error(submodule_context->r, rc);
	}

	if (encryption_params.type == HLS_ENC_AES_128 && 
		*response_size != 0)
	{
		*response_size = aes_round_up_to_block(*response_size);
	}

	*frame_processor = (ngx_http_vod_frame_processor_t)hls_muxer_process;
	*frame_processor_state = state;

	content_type->len = sizeof(mpeg_ts_content_type) - 1;
	content_type->data = (u_char *)mpeg_ts_content_type;

	return NGX_OK;
}

static ngx_int_t
ngx_http_vod_hls_handle_mp4_init_segment(
	ngx_http_vod_submodule_context_t* submodule_context,
	ngx_str_t* response,
	ngx_str_t* content_type)
{
	atom_writer_t* stsd_atom_writers = NULL;
	vod_status_t rc;

#if (NGX_HAVE_OPENSSL_EVP)
	aes_cbc_encrypt_context_t* encrypted_write_context;
	hls_encryption_params_t encryption_params;
	drm_info_t* drm_info;

	rc = ngx_http_vod_hls_init_encryption_params(&encryption_params, submodule_context, HLS_CONTAINER_FMP4);
	if (rc != NGX_OK)
	{
		return rc;
	}

	switch (encryption_params.type)
	{
	case HLS_ENC_SAMPLE_AES:
		rc = mp4_init_segment_get_encrypted_stsd_writers(
			&submodule_context->request_context,
			&submodule_context->media_set,
			SCHEME_TYPE_CBCS,
			FALSE,
			NULL,
			encryption_params.iv,
			&stsd_atom_writers);
		if (rc != VOD_OK)
		{
			ngx_log_debug1(NGX_LOG_DEBUG_HTTP, submodule_context->request_context.log, 0,
				"ngx_http_vod_hls_handle_mp4_init_segment: mp4_init_segment_get_encrypted_stsd_writers failed %i (1)", rc);
			return ngx_http_vod_status_to_ngx_error(submodule_context->r, rc);
		}
		break;

	case HLS_ENC_SAMPLE_AES_CENC:
		drm_info = (drm_info_t*)submodule_context->media_set.sequences[0].drm_info;

		rc = mp4_init_segment_get_encrypted_stsd_writers(
			&submodule_context->request_context,
			&submodule_context->media_set,
			SCHEME_TYPE_CENC,
			FALSE,
			drm_info->key_id,
			NULL,
			&stsd_atom_writers);
		if (rc != VOD_OK)
		{
			ngx_log_debug1(NGX_LOG_DEBUG_HTTP, submodule_context->request_context.log, 0,
				"ngx_http_vod_hls_handle_mp4_init_segment: mp4_init_segment_get_encrypted_stsd_writers failed %i (2)", rc);
			return ngx_http_vod_status_to_ngx_error(submodule_context->r, rc);
		}
		break;

	default:;
	}
#endif // NGX_HAVE_OPENSSL_EVP

	rc = mp4_init_segment_build(
		&submodule_context->request_context,
		&submodule_context->media_set,
		ngx_http_vod_submodule_size_only(submodule_context),
		NULL,
		stsd_atom_writers,
		response);
	if (rc != VOD_OK)
	{
		ngx_log_debug1(NGX_LOG_DEBUG_HTTP, submodule_context->request_context.log, 0,
			"ngx_http_vod_hls_handle_mp4_init_segment: mp4_init_segment_build failed %i", rc);
		return ngx_http_vod_status_to_ngx_error(submodule_context->r, rc);
	}

#if (NGX_HAVE_OPENSSL_EVP)
	if (encryption_params.type == HLS_ENC_AES_128)
	{
		rc = aes_cbc_encrypt_init(
			&encrypted_write_context,
			&submodule_context->request_context,
			NULL,
			NULL,
			NULL,
			encryption_params.key,
			encryption_params.iv);
		if (rc != VOD_OK)
		{
			ngx_log_debug1(NGX_LOG_DEBUG_HTTP, submodule_context->request_context.log, 0,
				"ngx_http_vod_hls_handle_mp4_init_segment: aes_cbc_encrypt_init failed %i", rc);
			return ngx_http_vod_status_to_ngx_error(submodule_context->r, rc);
		}

		rc = aes_cbc_encrypt(
			encrypted_write_context,
			response,
			response,
			TRUE);
		if (rc != VOD_OK)
		{
			ngx_log_debug1(NGX_LOG_DEBUG_HTTP, submodule_context->request_context.log, 0,
				"ngx_http_vod_hls_handle_mp4_init_segment: aes_cbc_encrypt failed %i", rc);
			return ngx_http_vod_status_to_ngx_error(submodule_context->r, rc);
		}
	}
#endif // NGX_HAVE_OPENSSL_EVP

	mp4_fragment_get_content_type(
		submodule_context->media_set.track_count[MEDIA_TYPE_VIDEO],
		content_type);
	return NGX_OK;
}

static ngx_int_t
ngx_http_vod_hls_init_fmp4_frame_processor(
	ngx_http_vod_submodule_context_t* submodule_context,
	segment_writer_t* segment_writer,
	ngx_http_vod_frame_processor_t* frame_processor,
	void** frame_processor_state,
	ngx_str_t* output_buffer,
	size_t* response_size,
	ngx_str_t* content_type)
{
	dash_fragment_header_extensions_t header_extensions;
	fragment_writer_state_t* state;
	mp4_muxer_state_t* muxer_state;
	segment_writer_t* segment_writers;
	vod_status_t rc;
	bool_t per_stream_writer;
	bool_t reuse_input_buffers = FALSE;
	bool_t size_only = ngx_http_vod_submodule_size_only(submodule_context);

#if (NGX_HAVE_OPENSSL_EVP)
	ngx_http_vod_loc_conf_t* conf = submodule_context->conf;
	hls_encryption_params_t encryption_params;
	segment_writer_t drm_writer;

	rc = ngx_http_vod_hls_init_segment_encryption(
		submodule_context,
		segment_writer,
		HLS_CONTAINER_FMP4,
		&encryption_params);
	if (rc != NGX_OK)
	{
		return rc;
	}

	reuse_input_buffers = encryption_params.type != HLS_ENC_NONE;

	if (conf->hls.encryption_method == HLS_ENC_SAMPLE_AES)
	{
		rc = mp4_cbcs_encrypt_get_writers(
			&submodule_context->request_context,
			&submodule_context->media_set,
			segment_writer,
			encryption_params.key,
			encryption_params.iv,
			&segment_writers);
		if (rc != VOD_OK)
		{
			ngx_log_debug1(NGX_LOG_DEBUG_HTTP, submodule_context->request_context.log, 0,
				"ngx_http_vod_hls_init_fmp4_frame_processor: mp4_cbcs_encrypt_get_writers failed %i", rc);
			return ngx_http_vod_status_to_ngx_error(submodule_context->r, rc);
		}
		per_stream_writer = TRUE;
	}
	else
#endif // NGX_HAVE_OPENSSL_EVP
	{
		segment_writers = segment_writer;
		per_stream_writer = FALSE;
	}

	if (submodule_context->media_set.total_track_count > 1)
	{
#if (NGX_HAVE_OPENSSL_EVP)
		if (encryption_params.type == HLS_ENC_SAMPLE_AES_CENC)
		{
			ngx_log_error(NGX_LOG_ERR, submodule_context->request_context.log, 0,
				"ngx_http_vod_hls_init_fmp4_frame_processor: multiple streams not supported for sample aes cenc");
			return ngx_http_vod_status_to_ngx_error(submodule_context->r, VOD_BAD_REQUEST);
		}
#endif // NGX_HAVE_OPENSSL_EVP

		// muxed segment
		rc = mp4_muxer_init_fragment(
			&submodule_context->request_context,
			submodule_context->request_params.segment_index,
			&submodule_context->media_set,
			segment_writers,
			per_stream_writer,
			reuse_input_buffers,
			size_only,
			output_buffer,
			response_size,
			&muxer_state);
		if (rc != VOD_OK)
		{
			ngx_log_debug1(NGX_LOG_DEBUG_HTTP, submodule_context->request_context.log, 0,
				"ngx_http_vod_hls_init_fmp4_frame_processor: mp4_muxer_init_fragment failed %i", rc);
			return ngx_http_vod_status_to_ngx_error(submodule_context->r, rc);
		}

		*frame_processor = (ngx_http_vod_frame_processor_t)mp4_muxer_process_frames;
		*frame_processor_state = muxer_state;
	}
	else
	{
#if (NGX_HAVE_OPENSSL_EVP)
		if (encryption_params.type == HLS_ENC_SAMPLE_AES_CENC)
		{
			drm_writer = *segment_writer;		// must not change segment_writer, otherwise the header will be encrypted

			// encyrpted fragment
			rc = edash_packager_get_fragment_writer(
				&drm_writer,
				&submodule_context->request_context,
				&submodule_context->media_set,
				submodule_context->request_params.segment_index,
				conf->min_single_nalu_per_frame_segment > 0 &&
				submodule_context->media_set.initial_segment_clip_relative_index >= conf->min_single_nalu_per_frame_segment - 1,
				submodule_context->media_set.sequences[0].encryption_key,		// iv
				size_only,
				output_buffer,
				response_size);
			switch (rc)
			{
			case VOD_DONE:		// passthrough
				break;

			case VOD_OK:
				segment_writers = &drm_writer;
				break;

			default:
				ngx_log_debug1(NGX_LOG_DEBUG_HTTP, submodule_context->request_context.log, 0,
					"ngx_http_vod_hls_init_fmp4_frame_processor: edash_packager_get_fragment_writer failed %i", rc);
				return ngx_http_vod_status_to_ngx_error(submodule_context->r, rc);
			}
		}
		else
#endif // NGX_HAVE_OPENSSL_EVP
		{
			// for single stream use dash segments
			ngx_memzero(&header_extensions, sizeof(header_extensions));

			rc = dash_packager_build_fragment_header(
				&submodule_context->request_context,
				&submodule_context->media_set,
				submodule_context->request_params.segment_index,
				0,	// sample description index
				&header_extensions,
				size_only,
				output_buffer,
				response_size);
			if (rc != VOD_OK)
			{
				ngx_log_debug1(NGX_LOG_DEBUG_HTTP, submodule_context->request_context.log, 0,
{'fraction_non_alphanumeric': 0.09682299546142209, 'fraction_numerical': 0.009362187287596746, 'mean_word_length': 6.0147646877883725, 'pattern_counts': {'":': 0, '<': 7, '<?xml version=': 0, '>': 375, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '7417141', 'n_tokens_mistral': 20437, 'n_tokens_neox': 19063, 'n_words': 2565}