@@ -25,7 +25,8 @@
static char * ngx_rtmp_hls_merge_app_conf(ngx_conf_t *cf,
void *parent, void *child);
static ngx_int_t ngx_rtmp_hls_flush_audio(ngx_rtmp_session_t *s);
-static ngx_int_t ngx_rtmp_hls_ensure_directory(ngx_rtmp_session_t *s);
+static ngx_int_t ngx_rtmp_hls_ensure_directory(ngx_rtmp_session_t *s,
+ ngx_str_t *path);
#define NGX_RTMP_HLS_BUFSIZE (1024*1024)
@@ -34,6 +35,7 @@
typedef struct {
uint64_t id;
+ uint64_t key_id;
double duration;
unsigned active:1;
unsigned discont:1; /* before */
@@ -49,22 +51,26 @@
typedef struct {
unsigned opened:1;
- ngx_file_t file;
+ ngx_rtmp_mpegts_file_t file;
ngx_str_t playlist;
ngx_str_t playlist_bak;
ngx_str_t var_playlist;
ngx_str_t var_playlist_bak;
ngx_str_t stream;
+ ngx_str_t keyfile;
ngx_str_t name;
+ u_char key[16];
uint64_t frag;
uint64_t frag_ts;
+ uint64_t key_id;
ngx_uint_t nfrags;
ngx_rtmp_hls_frag_t *frags; /* circular 2 * winfrags + 1 */
ngx_uint_t audio_cc;
ngx_uint_t video_cc;
+ ngx_uint_t key_frags;
uint64_t aframe_base;
uint64_t aframe_num;
@@ -79,6 +85,7 @@
typedef struct {
ngx_str_t path;
ngx_msec_t playlen;
+ ngx_uint_t frags_per_key;
} ngx_rtmp_hls_cleanup_t;
@@ -103,6 +110,10 @@
ngx_array_t *variant;
ngx_str_t base_url;
ngx_int_t granularity;
+ ngx_flag_t keys;
+ ngx_str_t key_path;
+ ngx_str_t key_url;
+ ngx_uint_t frags_per_key;
} ngx_rtmp_hls_app_conf_t;
@@ -269,6 +280,34 @@
offsetof(ngx_rtmp_hls_app_conf_t, granularity),
NULL },
+ { ngx_string("hls_keys"),
+ NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_RTMP_APP_CONF|NGX_CONF_TAKE1,
+ ngx_conf_set_flag_slot,
+ NGX_RTMP_APP_CONF_OFFSET,
+ offsetof(ngx_rtmp_hls_app_conf_t, keys),
+ NULL },
+
+ { ngx_string("hls_key_path"),
+ NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_RTMP_APP_CONF|NGX_CONF_TAKE1,
+ ngx_conf_set_str_slot,
+ NGX_RTMP_APP_CONF_OFFSET,
+ offsetof(ngx_rtmp_hls_app_conf_t, key_path),
+ NULL },
+
+ { ngx_string("hls_key_url"),
+ NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_RTMP_APP_CONF|NGX_CONF_TAKE1,
+ ngx_conf_set_str_slot,
+ NGX_RTMP_APP_CONF_OFFSET,
+ offsetof(ngx_rtmp_hls_app_conf_t, key_url),
+ NULL },
+
+ { ngx_string("hls_fragments_per_key"),
+ NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_RTMP_APP_CONF|NGX_CONF_TAKE1,
+ ngx_conf_set_num_slot,
+ NGX_RTMP_APP_CONF_OFFSET,
+ offsetof(ngx_rtmp_hls_app_conf_t, frags_per_key),
+ NULL },
+
ngx_null_command
};
@@ -445,14 +484,15 @@
{
static u_char buffer[1024];
ngx_fd_t fd;
- u_char *p;
+ u_char *p, *end;
ngx_rtmp_hls_ctx_t *ctx;
ssize_t n;
ngx_rtmp_hls_app_conf_t *hacf;
ngx_rtmp_hls_frag_t *f;
ngx_uint_t i, max_frag;
- ngx_str_t name_part;
- const char *sep;
+ ngx_str_t name_part, key_name_part;
+ uint64_t prev_key_id;
+ const char *sep, *key_sep;
hacf = ngx_rtmp_get_module_app_conf(s, ngx_rtmp_hls_module);
@@ -477,15 +517,19 @@
}
}
- p = ngx_snprintf(buffer, sizeof(buffer),
+ p = buffer;
+ end = p + sizeof(buffer);
+
+ p = ngx_slprintf(p, end,
"#EXTM3U\n"
"#EXT-X-VERSION:3\n"
"#EXT-X-MEDIA-SEQUENCE:%uL\n"
- "#EXT-X-TARGETDURATION:%ui\n"
- "%s",
- ctx->frag, max_frag,
- hacf->type == NGX_RTMP_HLS_TYPE_EVENT ?
- "#EXT-X-PLAYLIST-TYPE: EVENT\n" : "");
+ "#EXT-X-TARGETDURATION:%ui\n",
+ ctx->frag, max_frag);
+
+ if (hacf->type == NGX_RTMP_HLS_TYPE_EVENT) {
+ p = ngx_slprintf(p, end, "#EXT-X-PLAYLIST-TYPE: EVENT\n");
+ }
n = ngx_write_fd(fd, buffer, p - buffer);
if (n < 0) {
@@ -497,20 +541,42 @@
}
sep = hacf->nested ? (hacf->base_url.len ? "/" : "") : "-";
+ key_sep = hacf->nested ? (hacf->key_url.len ? "/" : "") : "-";
name_part.len = 0;
if (!hacf->nested || hacf->base_url.len) {
name_part = ctx->name;
}
+ key_name_part.len = 0;
+ if (!hacf->nested || hacf->key_url.len) {
+ key_name_part = ctx->name;
+ }
+
+ prev_key_id = 0;
+
for (i = 0; i < ctx->nfrags; i++) {
f = ngx_rtmp_hls_get_frag(s, i);
- p = ngx_snprintf(buffer, sizeof(buffer),
- "%s"
+ p = buffer;
+ end = p + sizeof(buffer);
+
+ if (f->discont) {
+ p = ngx_slprintf(p, end, "#EXT-X-DISCONTINUITY\n");
+ }
+
+ if (hacf->keys && (i == 0 || f->key_id != prev_key_id)) {
+ p = ngx_slprintf(p, end, "#EXT-X-KEY:METHOD=AES-128,"
+ "URI=\"%V%V%s%uL.key\",IV=0x%032XL\n",
+ &hacf->key_url, &key_name_part,
+ key_sep, f->key_id, f->key_id);
+ }
+
+ prev_key_id = f->key_id;
+
+ p = ngx_slprintf(p, end,
"#EXTINF:%.3f,\n"
"%V%V%s%uL.ts\n",
- f->discont ? "#EXT-X-DISCONTINUITY\n" : "",
f->duration, &hacf->base_url, &name_part, sep, f->id);
ngx_log_debug5(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
@@ -762,10 +828,9 @@
ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
"hls: close fragment n=%uL", ctx->frag);
- ngx_close_file(ctx->file.fd);
+ ngx_rtmp_mpegts_close_file(&ctx->file);
ctx->opened = 0;
- ctx->file.fd = NGX_INVALID_FILE;
ngx_rtmp_hls_next_frag(s);
@@ -780,21 +845,29 @@
ngx_int_t discont)
{
uint64_t id;
+ ngx_fd_t fd;
ngx_uint_t g;
ngx_rtmp_hls_ctx_t *ctx;
ngx_rtmp_hls_frag_t *f;
ngx_rtmp_hls_app_conf_t *hacf;
ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_hls_module);
+
if (ctx->opened) {
return NGX_OK;
}
- if (ngx_rtmp_hls_ensure_directory(s) != NGX_OK) {
+ hacf = ngx_rtmp_get_module_app_conf(s, ngx_rtmp_hls_module);
+
+ if (ngx_rtmp_hls_ensure_directory(s, &hacf->path) != NGX_OK) {
return NGX_ERROR;
}
- hacf = ngx_rtmp_get_module_app_conf(s, ngx_rtmp_hls_module);
+ if (hacf->keys &&
+ ngx_rtmp_hls_ensure_directory(s, &hacf->key_path) != NGX_OK)
+ {
+ return NGX_ERROR;
+ }
id = ngx_rtmp_hls_get_fragment_id(s, ts);
@@ -803,32 +876,77 @@
id = (uint64_t) (id / g) * g;
}
- *ngx_sprintf(ctx->stream.data + ctx->stream.len, "%uL.ts", id) = 0;
+ ngx_sprintf(ctx->stream.data + ctx->stream.len, "%uL.ts%Z", id);
+
+ if (hacf->keys) {
+ if (ctx->key_frags == 0) {
+
+ ctx->key_frags = hacf->frags_per_key - 1;
+ ctx->key_id = id;
+
+ if (RAND_bytes(ctx->key, 16) < 0) {
+ ngx_log_error(NGX_LOG_ERR, s->connection->log, 0,
+ "hls: failed to create key");
+ return NGX_ERROR;
+ }
- ngx_log_debug5(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
- "hls: open fragment file='%s', frag=%uL, n=%ui, time=%uL, "
- "discont=%i",
- ctx->stream.data, ctx->frag, ctx->nfrags, ts, discont);
+ ngx_sprintf(ctx->keyfile.data + ctx->keyfile.len, "%uL.key%Z", id);
- ngx_memzero(&ctx->file, sizeof(ctx->file));
+ fd = ngx_open_file(ctx->keyfile.data, NGX_FILE_WRONLY,
+ NGX_FILE_TRUNCATE, NGX_FILE_DEFAULT_ACCESS);
- ctx->file.log = s->connection->log;
+ if (fd == NGX_INVALID_FILE) {
+ ngx_log_error(NGX_LOG_ERR, s->connection->log, ngx_errno,
+ "hls: failed to open key file '%s'",
+ ctx->keyfile.data);
+ return NGX_ERROR;
+ }
- ngx_str_set(&ctx->file.name, "hls");
+ if (ngx_write_fd(fd, ctx->key, 16) != 16) {
+ ngx_log_error(NGX_LOG_ERR, s->connection->log, ngx_errno,
+ "hls: failed to write key file '%s'",
+ ctx->keyfile.data);
+ ngx_close_file(fd);
+ return NGX_ERROR;
+ }
- ctx->file.fd = ngx_open_file(ctx->stream.data, NGX_FILE_WRONLY,
- NGX_FILE_TRUNCATE, NGX_FILE_DEFAULT_ACCESS);
+ ngx_close_file(fd);
- if (ctx->file.fd == NGX_INVALID_FILE) {
- ngx_log_error(NGX_LOG_ERR, s->connection->log, ngx_errno,
- "hls: error creating fragment file");
+ } else {
+ if (hacf->frags_per_key) {
+ ctx->key_frags--;
+ }
+
+ if (ngx_set_file_time(ctx->keyfile.data, 0, ngx_cached_time->sec)
+ != NGX_OK)
+ {
+ ngx_log_error(NGX_LOG_ALERT, s->connection->log, ngx_errno,
+ ngx_set_file_time_n " '%s' failed",
+ ctx->keyfile.data);
+ }
+ }
+ }
+
+ ngx_log_debug6(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
+ "hls: open fragment file='%s', keyfile='%s', "
+ "frag=%uL, n=%ui, time=%uL, discont=%i",
+ ctx->stream.data,
+ ctx->keyfile.data ? ctx->keyfile.data : (u_char *) "",
+ ctx->frag, ctx->nfrags, ts, discont);
+
+ if (hacf->keys &&
+ ngx_rtmp_mpegts_init_encryption(&ctx->file, ctx->key, 16, ctx->key_id)
+ != NGX_OK)
+ {
+ ngx_log_error(NGX_LOG_ERR, s->connection->log, 0,
+ "hls: failed to initialize hls encryption");
return NGX_ERROR;
}
- if (ngx_rtmp_mpegts_write_header(&ctx->file) != NGX_OK) {
- ngx_log_error(NGX_LOG_ERR, s->connection->log, ngx_errno,
- "hls: error writing fragment header");
- ngx_close_file(ctx->file.fd);
+ if (ngx_rtmp_mpegts_open_file(&ctx->file, ctx->stream.data,
+ s->connection->log)
+ != NGX_OK)
+ {
return NGX_ERROR;
}
@@ -841,6 +959,7 @@
f->active = 1;
f->discont = discont;
f->id = id;
+ f->key_id = ctx->key_id;
ctx->frag_ts = ts;
@@ -859,11 +978,11 @@
ngx_file_t file;
ssize_t ret;
off_t offset;
- u_char *p, *last, *end, *next, *pa;
+ u_char *p, *last, *end, *next, *pa, *pp, c;
ngx_rtmp_hls_frag_t *f;
double duration;
ngx_int_t discont;
- uint64_t mag;
+ uint64_t mag, key_id, base;
static u_char buffer[4096];
ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_hls_module);
@@ -885,6 +1004,7 @@
f = NULL;
duration = 0;
discont = 0;
+ key_id = 0;
for ( ;; ) {
@@ -928,6 +1048,54 @@
}
+#define NGX_RTMP_XKEY "#EXT-X-KEY:"
+#define NGX_RTMP_XKEY_LEN (sizeof(NGX_RTMP_XKEY) - 1)
+
+ if (ngx_memcmp(p, NGX_RTMP_XKEY, NGX_RTMP_XKEY_LEN) == 0) {
+
+ /* recover key id from initialization vector */
+
+ key_id = 0;
+ base = 1;
+ pp = last - 1;
+
+ for ( ;; ) {
+ if (pp < p) {
+ ngx_log_error(NGX_LOG_ERR, s->connection->log, 0,
+ "hls: failed to read key id");
+ break;
+ }
+
+ c = *pp;
+ if (c == 'x') {
+ break;
+ }
+
+ if (c >= '0' && c <= '9') {
+ c -= '0';
+ goto next;
+ }
+
+ c |= 0x20;
+
+ if (c >= 'a' && c <= 'f') {
+ c -= 'a' - 10;
+ goto next;
+ }
+
+ ngx_log_error(NGX_LOG_ERR, s->connection->log, 0,
+ "hls: bad character in key id");
+ break;
+
+ next:
+
+ key_id += base * c;
+ base *= 0x10;
+ pp--;
+ }
+ }
+
+
#define NGX_RTMP_EXTINF "#EXTINF:"
#define NGX_RTMP_EXTINF_LEN (sizeof(NGX_RTMP_EXTINF) - 1)
@@ -978,6 +1146,8 @@
mag *= 10;
}
+ f->key_id = key_id;
+
ngx_rtmp_hls_next_frag(s);
ngx_log_debug6(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
@@ -997,51 +1167,53 @@
static ngx_int_t
-ngx_rtmp_hls_ensure_directory(ngx_rtmp_session_t *s)
+ngx_rtmp_hls_ensure_directory(ngx_rtmp_session_t *s, ngx_str_t *path)
{
size_t len;
ngx_file_info_t fi;
ngx_rtmp_hls_ctx_t *ctx;
ngx_rtmp_hls_app_conf_t *hacf;
- static u_char path[NGX_MAX_PATH + 1];
+ static u_char zpath[NGX_MAX_PATH + 1];
hacf = ngx_rtmp_get_module_app_conf(s, ngx_rtmp_hls_module);
- *ngx_snprintf(path, sizeof(path) - 1, "%V", &hacf->path) = 0;
+ if (path->len + 1 > sizeof(zpath)) {
+ ngx_log_error(NGX_LOG_ERR, s->connection->log, 0, "hls: too long path");
+ return NGX_ERROR;
+ }
+
+ ngx_snprintf(zpath, sizeof(zpath), "%V%Z", path);
- if (ngx_file_info(path, &fi) == NGX_FILE_ERROR) {
+ if (ngx_file_info(zpath, &fi) == NGX_FILE_ERROR) {
if (ngx_errno != NGX_ENOENT) {
ngx_log_error(NGX_LOG_ERR, s->connection->log, ngx_errno,
- "hls: " ngx_file_info_n " failed on '%V'",
- &hacf->path);
+ "hls: " ngx_file_info_n " failed on '%V'", path);
return NGX_ERROR;
}
/* ENOENT */
- if (ngx_create_dir(path, NGX_RTMP_HLS_DIR_ACCESS) == NGX_FILE_ERROR) {
+ if (ngx_create_dir(zpath, NGX_RTMP_HLS_DIR_ACCESS) == NGX_FILE_ERROR) {
ngx_log_error(NGX_LOG_ERR, s->connection->log, ngx_errno,
- "hls: " ngx_create_dir_n " failed on '%V'",
- &hacf->path);
+ "hls: " ngx_create_dir_n " failed on '%V'", path);
return NGX_ERROR;
}
ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
- "hls: directory '%V' created", &hacf->path);
+ "hls: directory '%V' created", path);
} else {
if (!ngx_is_dir(&fi)) {
ngx_log_error(NGX_LOG_ERR, s->connection->log, 0,
- "hls: '%V' exists and is not a directory",
- &hacf->path);
+ "hls: '%V' exists and is not a directory", path);
return NGX_ERROR;
}
ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
- "hls: directory '%V' exists", &hacf->path);
+ "hls: directory '%V' exists", path);
}
if (!hacf->nested) {
@@ -1050,44 +1222,49 @@
ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_hls_module);
- len = hacf->path.len;
- if (hacf->path.data[len - 1] == '/') {
+ len = path->len;
+ if (path->data[len - 1] == '/') {
len--;
}
- *ngx_snprintf(path, sizeof(path) - 1, "%*s/%V", len, hacf->path.data,
- &ctx->name) = 0;
+ if (len + 1 + ctx->name.len + 1 > sizeof(zpath)) {
+ ngx_log_error(NGX_LOG_ERR, s->connection->log, 0, "hls: too long path");
+ return NGX_ERROR;
+ }
+
+ ngx_snprintf(zpath, sizeof(zpath) - 1, "%*s/%V%Z", len, path->data,
+ &ctx->name);
- if (ngx_file_info(path, &fi) != NGX_FILE_ERROR) {
+ if (ngx_file_info(zpath, &fi) != NGX_FILE_ERROR) {
if (ngx_is_dir(&fi)) {
ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
- "hls: directory '%s' exists", path);
+ "hls: directory '%s' exists", zpath);
return NGX_OK;
}
ngx_log_error(NGX_LOG_ERR, s->connection->log, 0,
- "hls: '%s' exists and is not a directory", path);
+ "hls: '%s' exists and is not a directory", zpath);
return NGX_ERROR;
}
if (ngx_errno != NGX_ENOENT) {
ngx_log_error(NGX_LOG_ERR, s->connection->log, ngx_errno,
- "hls: " ngx_file_info_n " failed on '%s'", path);
+ "hls: " ngx_file_info_n " failed on '%s'", zpath);
return NGX_ERROR;
}
/* NGX_ENOENT */
- if (ngx_create_dir(path, NGX_RTMP_HLS_DIR_ACCESS) == NGX_FILE_ERROR) {
+ if (ngx_create_dir(zpath, NGX_RTMP_HLS_DIR_ACCESS) == NGX_FILE_ERROR) {
ngx_log_error(NGX_LOG_ERR, s->connection->log, ngx_errno,
- "hls: " ngx_create_dir_n " failed on '%s'", path);
+ "hls: " ngx_create_dir_n " failed on '%s'", zpath);
return NGX_ERROR;
}
ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
- "hls: directory '%s' created", path);
+ "hls: directory '%s' created", zpath);
return NGX_OK;
}
@@ -1258,9 +1435,35 @@
*p = 0;
- ngx_log_debug3(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
- "hls: playlist='%V' playlist_bak='%V' stream_pattern='%V'",
- &ctx->playlist, &ctx->playlist_bak, &ctx->stream);
+ /* key path */
+
+ if (hacf->keys) {
+ len = hacf->key_path.len + 1 + ctx->name.len + 1 + NGX_INT64_LEN
+ + sizeof(".key");
+
+ ctx->keyfile.data = ngx_palloc(s->connection->pool, len);
+ if (ctx->keyfile.data == NULL) {
+ return NGX_ERROR;
+ }
+
+ p = ngx_cpymem(ctx->keyfile.data, hacf->key_path.data,
+ hacf->key_path.len);
+
+ if (p[-1] != '/') {
+ *p++ = '/';
+ }
+
+ p = ngx_cpymem(p, ctx->name.data, ctx->name.len);
+ *p++ = (hacf->nested ? '/' : '-');
+
+ ctx->keyfile.len = p - ctx->keyfile.data;
+ }
+
+ ngx_log_debug4(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
+ "hls: playlist='%V' playlist_bak='%V' "
+ "stream_pattern='%V' keyfile_pattern='%V'",
+ &ctx->playlist, &ctx->playlist_bak,
+ &ctx->stream, &ctx->keyfile);
if (hacf->continuous) {
ngx_rtmp_hls_restore_stream(s);
@@ -1973,6 +2176,13 @@
{
max_age = playlen / 1000;
+ } else if (name.len >= 4 && name.data[name.len - 4] == '.' &&
+ name.data[name.len - 3] == 'k' &&
+ name.data[name.len - 2] == 'e' &&
+ name.data[name.len - 1] == 'y')
+ {
+ max_age = playlen / 500;
+
} else {
ngx_log_debug1(NGX_LOG_DEBUG_RTMP, ngx_cycle->log, 0,
"hls: cleanup skip unknown file type '%V'", &name);
@@ -2088,6 +2298,8 @@
conf->audio_buffer_size = NGX_CONF_UNSET_SIZE;
conf->cleanup = NGX_CONF_UNSET;
conf->granularity = NGX_CONF_UNSET;
+ conf->keys = NGX_CONF_UNSET;
+ conf->frags_per_key = NGX_CONF_UNSET_UINT;
return conf;
}
@@ -2122,6 +2334,10 @@
ngx_conf_merge_value(conf->cleanup, prev->cleanup, 1);
ngx_conf_merge_str_value(conf->base_url, prev->base_url, "");
ngx_conf_merge_value(conf->granularity, prev->granularity, 0);
+ ngx_conf_merge_value(conf->keys, prev->keys, 0);
+ ngx_conf_merge_str_value(conf->key_path, prev->key_path, "");
+ ngx_conf_merge_str_value(conf->key_url, prev->key_url, "");
+ ngx_conf_merge_uint_value(conf->frags_per_key, prev->frags_per_key, 0);
if (conf->fraglen) {
conf->winfrags = conf->playlen / conf->fraglen;
@@ -2162,6 +2378,44 @@
ngx_conf_merge_str_value(conf->path, prev->path, "");
+ if (conf->keys && conf->cleanup && conf->key_path.len &&
+ ngx_strcmp(conf->key_path.data, conf->path.data) != 0 &&
+ conf->type != NGX_RTMP_HLS_TYPE_EVENT)
+ {
+ if (conf->key_path.data[conf->key_path.len - 1] == '/') {
+ conf->key_path.len--;
+ }
+
+ cleanup = ngx_pcalloc(cf->pool, sizeof(*cleanup));
+ if (cleanup == NULL) {
+ return NGX_CONF_ERROR;
+ }
+
+ cleanup->path = conf->key_path;
+ cleanup->playlen = conf->playlen;
+
+ conf->slot = ngx_pcalloc(cf->pool, sizeof(*conf->slot));
+ if (conf->slot == NULL) {
+ return NGX_CONF_ERROR;
+ }
+
+ conf->slot->manager = ngx_rtmp_hls_cleanup;
+ conf->slot->name = conf->key_path;
+ conf->slot->data = cleanup;
+ conf->slot->conf_file = cf->conf_file->file.name.data;
+ conf->slot->line = cf->conf_file->line;
+
+ if (ngx_add_path(cf, &conf->slot) != NGX_OK) {
+ return NGX_CONF_ERROR;
+ }
+ }
+
+ ngx_conf_merge_str_value(conf->key_path, prev->key_path, "");
+
+ if (conf->key_path.len == 0) {
+ conf->key_path = conf->path;
+ }
+
return NGX_CONF_OK;
}
|
@@ -75,15 +75,90 @@
#define NGX_RTMP_HLS_DELAY 63000
-ngx_int_t
-ngx_rtmp_mpegts_write_header(ngx_file_t *file)
+static ngx_int_t
+ngx_rtmp_mpegts_write_file(ngx_rtmp_mpegts_file_t *file, u_char *in,
+ size_t in_size)
{
- ssize_t rc;
+ u_char *out;
+ size_t out_size, n;
+ ssize_t rc;
+
+ static u_char buf[1024];
+
+ if (!file->encrypt) {
+ ngx_log_debug1(NGX_LOG_DEBUG_CORE, file->log, 0,
+ "mpegts: write %uz bytes", in_size);
+
+ rc = ngx_write_fd(file->fd, in, in_size);
+ if (rc < 0) {
+ return NGX_ERROR;
+ }
+
+ return NGX_OK;
+ }
+
+ /* encrypt */
+
+ ngx_log_debug1(NGX_LOG_DEBUG_CORE, file->log, 0,
+ "mpegts: write %uz encrypted bytes", in_size);
- rc = ngx_write_file(file, ngx_rtmp_mpegts_header,
- sizeof(ngx_rtmp_mpegts_header), 0);
+ out = buf;
+ out_size = sizeof(buf);
+
+ if (file->size > 0 && file->size + in_size >= 16) {
+ ngx_memcpy(file->buf + file->size, in, 16 - file->size);
+
+ in += 16 - file->size;
+ in_size -= 16 - file->size;
+
+ AES_cbc_encrypt(file->buf, out, 16, &file->key, file->iv, AES_ENCRYPT);
+
+ out += 16;
+ out_size -= 16;
+
+ file->size = 0;
+ }
+
+ for ( ;; ) {
+ n = in_size & ~0x0f;
+
+ if (n > 0) {
+ if (n > out_size) {
+ n = out_size;
+ }
- return rc > 0 ? NGX_OK : rc;
+ AES_cbc_encrypt(in, out, n, &file->key, file->iv, AES_ENCRYPT);
+
+ in += n;
+ in_size -= n;
+
+ } else if (out == buf) {
+ break;
+ }
+
+ rc = ngx_write_fd(file->fd, buf, out - buf + n);
+ if (rc < 0) {
+ return NGX_ERROR;
+ }
+
+ out = buf;
+ out_size = sizeof(buf);
+ }
+
+ if (in_size) {
+ ngx_memcpy(file->buf + file->size, in, in_size);
+ file->size += in_size;
+ }
+
+ return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_rtmp_mpegts_write_header(ngx_rtmp_mpegts_file_t *file)
+{
+ return ngx_rtmp_mpegts_write_file(file, ngx_rtmp_mpegts_header,
+ sizeof(ngx_rtmp_mpegts_header));
}
@@ -122,14 +197,14 @@
ngx_int_t
-ngx_rtmp_mpegts_write_frame(ngx_file_t *file, ngx_rtmp_mpegts_frame_t *f,
- ngx_buf_t *b)
+ngx_rtmp_mpegts_write_frame(ngx_rtmp_mpegts_file_t *file,
+ ngx_rtmp_mpegts_frame_t *f, ngx_buf_t *b)
{
ngx_uint_t pes_size, header_size, body_size, in_size, stuff_size, flags;
u_char packet[188], *p, *base;
ngx_int_t first, rc;
- ngx_log_debug6(NGX_LOG_DEBUG_HTTP, file->log, 0,
+ ngx_log_debug6(NGX_LOG_DEBUG_CORE, file->log, 0,
"mpegts: pid=%ui, sid=%ui, pts=%uL, "
"dts=%uL, key=%ui, size=%ui",
f->pid, f->sid, f->pts, f->dts,
@@ -238,11 +313,87 @@
b->pos = b->last;
}
- rc = ngx_write_file(file, packet, sizeof(packet), file->offset);
- if (rc < 0) {
+ rc = ngx_rtmp_mpegts_write_file(file, packet, sizeof(packet));
+ if (rc != NGX_OK) {
return rc;
}
}
return NGX_OK;
}
+
+
+ngx_int_t
+ngx_rtmp_mpegts_init_encryption(ngx_rtmp_mpegts_file_t *file,
+ u_char *key, size_t key_len, uint64_t iv)
+{
+ if (AES_set_encrypt_key(key, key_len * 8, &file->key)) {
+ return NGX_ERROR;
+ }
+
+ ngx_memzero(file->iv, 8);
+
+ file->iv[8] = (u_char) (iv >> 56);
+ file->iv[9] = (u_char) (iv >> 48);
+ file->iv[10] = (u_char) (iv >> 40);
+ file->iv[11] = (u_char) (iv >> 32);
+ file->iv[12] = (u_char) (iv >> 24);
+ file->iv[13] = (u_char) (iv >> 16);
+ file->iv[14] = (u_char) (iv >> 8);
+ file->iv[15] = (u_char) (iv);
+
+ file->encrypt = 1;
+
+ return NGX_OK;
+}
+
+
+ngx_int_t
+ngx_rtmp_mpegts_open_file(ngx_rtmp_mpegts_file_t *file, u_char *path,
+ ngx_log_t *log)
+{
+ file->log = log;
+
+ file->fd = ngx_open_file(path, NGX_FILE_WRONLY, NGX_FILE_TRUNCATE,
+ NGX_FILE_DEFAULT_ACCESS);
+
+ if (file->fd == NGX_INVALID_FILE) {
+ ngx_log_error(NGX_LOG_ERR, log, ngx_errno,
+ "hls: error creating fragment file");
+ return NGX_ERROR;
+ }
+
+ file->size = 0;
+
+ if (ngx_rtmp_mpegts_write_header(file) != NGX_OK) {
+ ngx_log_error(NGX_LOG_ERR, log, ngx_errno,
+ "hls: error writing fragment header");
+ ngx_close_file(file->fd);
+ return NGX_ERROR;
+ }
+
+ return NGX_OK;
+}
+
+
+ngx_int_t
+ngx_rtmp_mpegts_close_file(ngx_rtmp_mpegts_file_t *file)
+{
+ u_char buf[16];
+ ssize_t rc;
+
+ if (file->encrypt) {
+ ngx_memset(file->buf + file->size, 16 - file->size, 16 - file->size);
+
+ AES_cbc_encrypt(file->buf, buf, 16, &file->key, file->iv, AES_ENCRYPT);
+
+ rc = ngx_write_fd(file->fd, buf, 16);
+ if (rc < 0) {
+ return NGX_ERROR;
+ }
+ }
+
+ ngx_close_file(file->fd);
+
+ return NGX_OK;
+}
|