diff mbox

[FFmpeg-devel,1/3] avformat/mov: Increase support for common encryption.

Message ID CAO7y9i-Bx8b-utuzUdqsNkQzoKGtJWXAN-R2XFt65SLTVYWX6Q@mail.gmail.com
State Superseded
Headers show

Commit Message

Jacob Trimble Jan. 24, 2018, 7:43 p.m. UTC
On Mon, Jan 22, 2018 at 7:38 PM, Michael Niedermayer
<michael@niedermayer.cc> wrote
> [...]
>> This removes support for saio/saiz atoms, but it was incorrect before.
>> A follow-up change will add correct support for those.
>
> This removal should be done by a seperate patch if it is done.
> diff has matched up the removed function with a added one making this
> hard to read as is
>

The problem is that the old code used the saiz atoms to parse the senc
atom.  I split the patch up for readability, but the two patches need
to be applied at the same time (or squashed) since the first breaks
encrypted content.  But I can squash them again if it is preferable to
not have a commit that intentionally breaks things.

>
>>
>> Signed-off-by: Jacob Trimble <modmaker@google.com>
>> ---
>>  libavformat/isom.h                     |  20 +-
>>  libavformat/mov.c                      | 432 ++++++++++++++++++++++-----------
>>  tests/fate/mov.mak                     |   8 +
>>  tests/ref/fate/mov-frag-encrypted      |  57 +++++
>>  tests/ref/fate/mov-tenc-only-encrypted |  57 +++++
>>  5 files changed, 422 insertions(+), 152 deletions(-)
>>  create mode 100644 tests/ref/fate/mov-frag-encrypted
>>  create mode 100644 tests/ref/fate/mov-tenc-only-encrypted
>
> This depends on other patches you posted, this should be mentioned or
> all patches should be in the same patchset in order
>

This depends on
http://ffmpeg.org/pipermail/ffmpeg-devel/2018-January/223754.html and
the recently pushed change to libavutil/aes_ctr.  Should I add
something to the commit message or is that enough?

>
>>
>> diff --git a/libavformat/isom.h b/libavformat/isom.h
>> index 65676fb0f5..3794b1f0fd 100644
>> --- a/libavformat/isom.h
>> +++ b/libavformat/isom.h
>> @@ -27,6 +27,7 @@
>>  #include <stddef.h>
>>  #include <stdint.h>
>>
>> +#include "libavutil/encryption_info.h"
>>  #include "libavutil/mastering_display_metadata.h"
>>  #include "libavutil/spherical.h"
>>  #include "libavutil/stereo3d.h"
>> @@ -108,12 +109,20 @@ typedef struct MOVSbgp {
>>      unsigned int index;
>>  } MOVSbgp;
>>
>> +typedef struct MOVEncryptionIndex {
>> +    // Individual encrypted samples.  If there are no elements, then the default
>> +    // settings will be used.
>> +    unsigned int nb_encrypted_samples;
>> +    AVEncryptionInfo **encrypted_samples;
>> +} MOVEncryptionIndex;
>> +
>>  typedef struct MOVFragmentStreamInfo {
>>      int id;
>>      int64_t sidx_pts;
>>      int64_t first_tfra_pts;
>>      int64_t tfdt_dts;
>>      int index_entry;
>> +    MOVEncryptionIndex *encryption_index;
>>  } MOVFragmentStreamInfo;
>>
>>  typedef struct MOVFragmentIndexItem {
>> @@ -214,15 +223,10 @@ typedef struct MOVStreamContext {
>>
>>      int has_sidx;  // If there is an sidx entry for this stream.
>>      struct {
>> -        int use_subsamples;
>> -        uint8_t* auxiliary_info;
>> -        uint8_t* auxiliary_info_end;
>> -        uint8_t* auxiliary_info_pos;
>> -        uint8_t auxiliary_info_default_size;
>> -        uint8_t* auxiliary_info_sizes;
>> -        size_t auxiliary_info_sizes_count;
>> -        int64_t auxiliary_info_index;
>>          struct AVAESCTR* aes_ctr;
>> +        unsigned int per_sample_iv_size;  // Either 0, 8, or 16.
>> +        AVEncryptionInfo *default_encrypted_sample;
>> +        MOVEncryptionIndex *encryption_index;
>>      } cenc;
>>  } MOVStreamContext;
>>
>> diff --git a/libavformat/mov.c b/libavformat/mov.c
>> index 22faecfc17..37320af2f6 100644
>> --- a/libavformat/mov.c
>> +++ b/libavformat/mov.c
>> @@ -1324,6 +1324,7 @@ static int update_frag_index(MOVContext *c, int64_t offset)
>>          frag_stream_info[i].tfdt_dts = AV_NOPTS_VALUE;
>>          frag_stream_info[i].first_tfra_pts = AV_NOPTS_VALUE;
>>          frag_stream_info[i].index_entry = -1;
>> +        frag_stream_info[i].encryption_index = NULL;
>>      }
>>
>>      if (index < c->frag_index.nb_items)
>> @@ -5710,117 +5711,252 @@ static int mov_read_frma(MOVContext *c, AVIOContext *pb, MOVAtom atom)
>>      return 0;
>>  }
>>
>> -static int mov_read_senc(MOVContext *c, AVIOContext *pb, MOVAtom atom)
>> +/**
>> + * Gets the current encryption info and associated current stream context.  If
>> + * we are parsing a track fragment, this will return the specific encryption
>> + * info for this fragment; otherwise this will return the global encryption
>> + * info for the current stream.
>> + */
>
>> +static int get_current_encryption_info(MOVContext *c, MOVEncryptionIndex **encryption_index, MOVStreamContext **sc)
>>  {
>> +    MOVFragmentStreamInfo *frag_stream_info;
>>      AVStream *st;
>> -    MOVStreamContext *sc;
>> -    size_t auxiliary_info_size;
>> +    int i;
>>
>> -    if (c->decryption_key_len == 0 || c->fc->nb_streams < 1)
>> -        return 0;
>> +    frag_stream_info = get_current_frag_stream_info(&c->frag_index);
>> +    if (frag_stream_info) {
>> +        for (i = 0; i < c->fc->nb_streams; i++) {
>> +            if (c->fc->streams[i]->id == frag_stream_info->id) {
>> +              st = c->fc->streams[i];
>> +              break;
>> +            }
>> +        }
>
> the indention is inconsistent here
>

No it's not, it looks like it because the diff looks odd.  If you
apply the patch, the indentation in this method is consistent.

>
> [...]
>
>> +static int mov_read_senc(MOVContext *c, AVIOContext *pb, MOVAtom atom)
>> +{
>> +    AVEncryptionInfo **encrypted_samples;
>> +    MOVEncryptionIndex *encryption_index;
>> +    MOVStreamContext *sc;
>> +    int use_subsamples, ret;
>> +    unsigned int sample_count, i, alloc_size = 0;
>>
>> -    if (avio_read(pb, sc->cenc.auxiliary_info, auxiliary_info_size) != auxiliary_info_size) {
>> -        av_log(c->fc, AV_LOG_ERROR, "failed to read the auxiliary info");
>> -        return AVERROR_INVALIDDATA;
>> +    ret = get_current_encryption_info(c, &encryption_index, &sc);
>> +    if (ret != 1)
>> +      return ret;
>> +
>> +    if (encryption_index->nb_encrypted_samples) {
>> +        // This can happen if we have both saio/saiz and senc atoms.
>> +        av_log(c->fc, AV_LOG_DEBUG, "Ignoring duplicate encryption info in senc\n");
>> +        return 0;
>>      }
>>
>> -    /* initialize the cipher */
>> -    sc->cenc.aes_ctr = av_aes_ctr_alloc();
>> -    if (!sc->cenc.aes_ctr) {
>> +    avio_r8(pb); /* version */
>> +    use_subsamples = avio_rb24(pb) & 0x02; /* flags */
>> +
>> +    sample_count = avio_rb32(pb);
>> +    if (sample_count >= INT_MAX / sizeof(*encrypted_samples))
>>          return AVERROR(ENOMEM);
>> +
>> +    for (i = 0; i < sample_count && !pb->eof_reached; i++) {
>> +        unsigned int min_samples = FFMIN(FFMAX(i, 1024 * 1024), sample_count);
>> +        encrypted_samples = av_fast_realloc(encryption_index->encrypted_samples, &alloc_size,
>> +                                            min_samples * sizeof(*encrypted_samples));
>> +        if (!encrypted_samples) {
>> +            ret = AVERROR(ENOMEM);
>> +            goto end;
>> +        }
>> +        encryption_index->encrypted_samples = encrypted_samples;
>> +
>> +        ret = mov_read_sample_encryption_info(c, pb, sc, &encryption_index->encrypted_samples[i], use_subsamples);
>> +        if (ret < 0) {
>> +            goto end;
>> +        }
>> +    }
>> +
>> +    if (pb->eof_reached) {
>> +        av_log(c->fc, AV_LOG_ERROR, "Hit EOF while reading senc\n");
>> +        ret = AVERROR_INVALIDDATA;
>>      }
>>
>> -    return av_aes_ctr_init(sc->cenc.aes_ctr, c->decryption_key);
>> +end:
>> +    if (ret < 0) {
>> +        for (; i > 0; i--)
>> +            av_encryption_info_free(encryption_index->encrypted_samples[i - 1]);
>
> I think its a bit risky to use "i" here like this.
> if someone adds a goto end before i is first used this breaks
> if someone adds a loop after the main loop this breaks too
>

Yeah, you're right.  Changed to free the partial array in the loop.

> [...]
> --
> Michael     GnuPG fingerprint: 9FF2128B147EF6730BADF133611EC787040B0FAB
>
> Everything should be made as simple as possible, but not simpler.
> -- Albert Einstein
>
> _______________________________________________
> ffmpeg-devel mailing list
> ffmpeg-devel@ffmpeg.org
> http://ffmpeg.org/mailman/listinfo/ffmpeg-devel
>

Comments

Michael Niedermayer Jan. 25, 2018, 1:46 a.m. UTC | #1
On Wed, Jan 24, 2018 at 11:43:26AM -0800, Jacob Trimble wrote:
> On Mon, Jan 22, 2018 at 7:38 PM, Michael Niedermayer
> <michael@niedermayer.cc> wrote
> > [...]
> >> This removes support for saio/saiz atoms, but it was incorrect before.
> >> A follow-up change will add correct support for those.
> >
> > This removal should be done by a seperate patch if it is done.
> > diff has matched up the removed function with a added one making this
> > hard to read as is
> >
> 
> The problem is that the old code used the saiz atoms to parse the senc
> atom.  I split the patch up for readability, but the two patches need
> to be applied at the same time (or squashed) since the first breaks
> encrypted content.  But I can squash them again if it is preferable to
> not have a commit that intentionally breaks things.

I didnt investigate this deeply so there is likely a better option that
i miss but you could just remove the functions which become unused in a 
subsequent patch to prevent diff from messing the line matching up totally


> 
> >
> >>
> >> Signed-off-by: Jacob Trimble <modmaker@google.com>
> >> ---
> >>  libavformat/isom.h                     |  20 +-
> >>  libavformat/mov.c                      | 432 ++++++++++++++++++++++-----------
> >>  tests/fate/mov.mak                     |   8 +
> >>  tests/ref/fate/mov-frag-encrypted      |  57 +++++
> >>  tests/ref/fate/mov-tenc-only-encrypted |  57 +++++
> >>  5 files changed, 422 insertions(+), 152 deletions(-)
> >>  create mode 100644 tests/ref/fate/mov-frag-encrypted
> >>  create mode 100644 tests/ref/fate/mov-tenc-only-encrypted
> >
> > This depends on other patches you posted, this should be mentioned or
> > all patches should be in the same patchset in order
> >
> 
> This depends on
> http://ffmpeg.org/pipermail/ffmpeg-devel/2018-January/223754.html and
> the recently pushed change to libavutil/aes_ctr.  Should I add
> something to the commit message or is that enough?

If you post a new version, then there should be a mail or comment explaining
any dependancies on yet to be applied patches.
It should not be in the commit messages or commited changes ideally
This way people trying to test code dont need to guess what they need
to apply first before a patchset


[...]
> >> +static int get_current_encryption_info(MOVContext *c, MOVEncryptionIndex **encryption_index, MOVStreamContext **sc)
> >>  {
> >> +    MOVFragmentStreamInfo *frag_stream_info;
> >>      AVStream *st;
> >> -    MOVStreamContext *sc;
> >> -    size_t auxiliary_info_size;
> >> +    int i;
> >>
> >> -    if (c->decryption_key_len == 0 || c->fc->nb_streams < 1)
> >> -        return 0;
> >> +    frag_stream_info = get_current_frag_stream_info(&c->frag_index);
> >> +    if (frag_stream_info) {
> >> +        for (i = 0; i < c->fc->nb_streams; i++) {
> >> +            if (c->fc->streams[i]->id == frag_stream_info->id) {
> >> +              st = c->fc->streams[i];
> >> +              break;
> >> +            }
> >> +        }
> >
> > the indention is inconsistent here
> >
> 
> No it's not, it looks like it because the diff looks odd.  If you
> apply the patch, the indentation in this method is consistent.

Indention depth is 4 in mov*.c
the hunk seems to add lines with a depth of 2
I would be surprised if this is not in the file after applying the patch

personally i dont care about the depth that much but i know many other people
care so this needs to be fixed before this can be applied

[...]
diff mbox

Patch

From 31b5b2bf91f9a03b17d66934060e21723363583f Mon Sep 17 00:00:00 2001
From: Jacob Trimble <modmaker@google.com>
Date: Wed, 6 Dec 2017 16:17:54 -0800
Subject: [PATCH] avformat/mov: Increase support for common encryption.

- Parse schm atom to get different encryption schemes.
- Allow senc atom to appear in track fragments.
- Allow 16-byte IVs.
- Allow constant IVs (specified in tenc).
- Allow only tenc to specify encryption (i.e. no senc/saiz/saio).
- Use sample descriptor to detect clear fragments.

This doesn't support:
- Different sample descriptor holding different encryption info.
  - Only first sample descriptor can be encrypted.
- Encrypted sample groups (i.e. seig).
- Non-'cenc' encryption scheme when using -decryption_key.

Signed-off-by: Jacob Trimble <modmaker@google.com>
---
 libavformat/isom.h                     |  20 +-
 libavformat/mov.c                      | 395 ++++++++++++++++++++++++++-------
 tests/fate/mov.mak                     |   8 +
 tests/ref/fate/mov-frag-encrypted      |  57 +++++
 tests/ref/fate/mov-tenc-only-encrypted |  57 +++++
 5 files changed, 450 insertions(+), 87 deletions(-)
 create mode 100644 tests/ref/fate/mov-frag-encrypted
 create mode 100644 tests/ref/fate/mov-tenc-only-encrypted

diff --git a/libavformat/isom.h b/libavformat/isom.h
index 65676fb0f5..3794b1f0fd 100644
--- a/libavformat/isom.h
+++ b/libavformat/isom.h
@@ -27,6 +27,7 @@ 
 #include <stddef.h>
 #include <stdint.h>
 
+#include "libavutil/encryption_info.h"
 #include "libavutil/mastering_display_metadata.h"
 #include "libavutil/spherical.h"
 #include "libavutil/stereo3d.h"
@@ -108,12 +109,20 @@  typedef struct MOVSbgp {
     unsigned int index;
 } MOVSbgp;
 
+typedef struct MOVEncryptionIndex {
+    // Individual encrypted samples.  If there are no elements, then the default
+    // settings will be used.
+    unsigned int nb_encrypted_samples;
+    AVEncryptionInfo **encrypted_samples;
+} MOVEncryptionIndex;
+
 typedef struct MOVFragmentStreamInfo {
     int id;
     int64_t sidx_pts;
     int64_t first_tfra_pts;
     int64_t tfdt_dts;
     int index_entry;
+    MOVEncryptionIndex *encryption_index;
 } MOVFragmentStreamInfo;
 
 typedef struct MOVFragmentIndexItem {
@@ -214,15 +223,10 @@  typedef struct MOVStreamContext {
 
     int has_sidx;  // If there is an sidx entry for this stream.
     struct {
-        int use_subsamples;
-        uint8_t* auxiliary_info;
-        uint8_t* auxiliary_info_end;
-        uint8_t* auxiliary_info_pos;
-        uint8_t auxiliary_info_default_size;
-        uint8_t* auxiliary_info_sizes;
-        size_t auxiliary_info_sizes_count;
-        int64_t auxiliary_info_index;
         struct AVAESCTR* aes_ctr;
+        unsigned int per_sample_iv_size;  // Either 0, 8, or 16.
+        AVEncryptionInfo *default_encrypted_sample;
+        MOVEncryptionIndex *encryption_index;
     } cenc;
 } MOVStreamContext;
 
diff --git a/libavformat/mov.c b/libavformat/mov.c
index 542a6f0e36..65b66cddf1 100644
--- a/libavformat/mov.c
+++ b/libavformat/mov.c
@@ -1324,6 +1324,7 @@  static int update_frag_index(MOVContext *c, int64_t offset)
         frag_stream_info[i].tfdt_dts = AV_NOPTS_VALUE;
         frag_stream_info[i].first_tfra_pts = AV_NOPTS_VALUE;
         frag_stream_info[i].index_entry = -1;
+        frag_stream_info[i].encryption_index = NULL;
     }
 
     if (index < c->frag_index.nb_items)
@@ -5710,57 +5711,250 @@  static int mov_read_frma(MOVContext *c, AVIOContext *pb, MOVAtom atom)
     return 0;
 }
 
+/**
+ * Gets the current encryption info and associated current stream context.  If
+ * we are parsing a track fragment, this will return the specific encryption
+ * info for this fragment; otherwise this will return the global encryption
+ * info for the current stream.
+ */
+static int get_current_encryption_info(MOVContext *c, MOVEncryptionIndex **encryption_index, MOVStreamContext **sc)
+{
+    MOVFragmentStreamInfo *frag_stream_info;
+    AVStream *st;
+    int i;
+
+    frag_stream_info = get_current_frag_stream_info(&c->frag_index);
+    if (frag_stream_info) {
+        for (i = 0; i < c->fc->nb_streams; i++) {
+            if (c->fc->streams[i]->id == frag_stream_info->id) {
+              st = c->fc->streams[i];
+              break;
+            }
+        }
+        if (i == c->fc->nb_streams)
+            return 0;
+        *sc = st->priv_data;
+
+        if (!frag_stream_info->encryption_index) {
+            frag_stream_info->encryption_index = av_mallocz(sizeof(*frag_stream_info->encryption_index));
+            if (!frag_stream_info->encryption_index)
+                return AVERROR(ENOMEM);
+        }
+        *encryption_index = frag_stream_info->encryption_index;
+        return 1;
+    } else {
+        // No current track fragment, using stream level encryption info.
+
+        if (c->fc->nb_streams < 1)
+            return 0;
+        st = c->fc->streams[c->fc->nb_streams - 1];
+        *sc = st->priv_data;
+
+        if (!(*sc)->cenc.encryption_index) {
+            (*sc)->cenc.encryption_index = av_mallocz(sizeof(*frag_stream_info->encryption_index));
+            if (!(*sc)->cenc.encryption_index)
+                return AVERROR(ENOMEM);
+        }
+
+        *encryption_index = (*sc)->cenc.encryption_index;
+        return 1;
+    }
+}
+
+static int mov_read_sample_encryption_info(MOVContext *c, AVIOContext *pb, MOVStreamContext *sc, AVEncryptionInfo **sample, int use_subsamples)
+{
+    int i;
+    unsigned int subsample_count;
+    AVSubsampleEncryptionInfo *subsamples;
+
+    *sample = av_encryption_info_clone(sc->cenc.default_encrypted_sample);
+    if (!*sample)
+        return AVERROR(ENOMEM);
+
+    if (sc->cenc.per_sample_iv_size != 0) {
+        if (avio_read(pb, (*sample)->iv, sc->cenc.per_sample_iv_size) != sc->cenc.per_sample_iv_size) {
+            av_log(c->fc, AV_LOG_ERROR, "failed to read the initialization vector\n");
+            av_encryption_info_free(*sample);
+            *sample = NULL;
+            return AVERROR_INVALIDDATA;
+        }
+    }
+
+    if (use_subsamples) {
+        subsample_count = avio_rb16(pb);
+        (*sample)->subsamples = av_mallocz_array(subsample_count, sizeof(*subsamples));
+        if (!(*sample)->subsamples) {
+            av_encryption_info_free(*sample);
+            *sample = NULL;
+            return AVERROR(ENOMEM);
+        }
+
+        for (i = 0; i < subsample_count && !pb->eof_reached; i++) {
+            (*sample)->subsamples[i].bytes_of_clear_data = avio_rb16(pb);
+            (*sample)->subsamples[i].bytes_of_protected_data = avio_rb32(pb);
+        }
+
+        if (pb->eof_reached) {
+            av_log(c->fc, AV_LOG_ERROR, "hit EOF while reading sub-sample encryption info\n");
+            av_encryption_info_free(*sample);
+            *sample = NULL;
+            return AVERROR_INVALIDDATA;
+        }
+        (*sample)->subsample_count = subsample_count;
+    }
+
+    return 0;
+}
+
 static int mov_read_senc(MOVContext *c, AVIOContext *pb, MOVAtom atom)
 {
-    AVStream *st;
+    AVEncryptionInfo **encrypted_samples;
+    MOVEncryptionIndex *encryption_index;
     MOVStreamContext *sc;
-    size_t auxiliary_info_size;
+    int use_subsamples, ret;
+    unsigned int sample_count, i, alloc_size = 0;
 
-    if (c->decryption_key_len == 0 || c->fc->nb_streams < 1)
+    ret = get_current_encryption_info(c, &encryption_index, &sc);
+    if (ret != 1)
+      return ret;
+
+    if (encryption_index->nb_encrypted_samples) {
+        // This can happen if we have both saio/saiz and senc atoms.
+        av_log(c->fc, AV_LOG_DEBUG, "Ignoring duplicate encryption info in senc\n");
         return 0;
-
-    st = c->fc->streams[c->fc->nb_streams - 1];
-    sc = st->priv_data;
-
-    if (sc->cenc.aes_ctr) {
-        av_log(c->fc, AV_LOG_ERROR, "duplicate senc atom\n");
-        return AVERROR_INVALIDDATA;
     }
 
     avio_r8(pb); /* version */
-    sc->cenc.use_subsamples = avio_rb24(pb) & 0x02; /* flags */
+    use_subsamples = avio_rb24(pb) & 0x02; /* flags */
 
-    avio_rb32(pb);        /* entries */
+    sample_count = avio_rb32(pb);
+    if (sample_count >= INT_MAX / sizeof(*encrypted_samples))
+        return AVERROR(ENOMEM);
+
+    for (i = 0; i < sample_count; i++) {
+        unsigned int min_samples = FFMIN(FFMAX(i, 1024 * 1024), sample_count);
+        encrypted_samples = av_fast_realloc(encryption_index->encrypted_samples, &alloc_size,
+                                            min_samples * sizeof(*encrypted_samples));
+        if (encrypted_samples) {
+            encryption_index->encrypted_samples = encrypted_samples;
+
+            ret = mov_read_sample_encryption_info(
+                c, pb, sc, &encryption_index->encrypted_samples[i], use_subsamples);
+        } else {
+            ret = AVERROR(ENOMEM);
+        }
+        if (pb->eof_reached) {
+            av_log(c->fc, AV_LOG_ERROR, "Hit EOF while reading senc\n");
+            ret = AVERROR_INVALIDDATA;
+        }
+
+        if (ret < 0) {
+            for (; i > 0; i--)
+                av_encryption_info_free(encryption_index->encrypted_samples[i - 1]);
+            av_freep(&encryption_index->encrypted_samples);
+            return ret;
+        }
+    }
+    encryption_index->nb_encrypted_samples = sample_count;
+
+    return 0;
+}
+
+static int mov_read_schm(MOVContext *c, AVIOContext *pb, MOVAtom atom)
+{
+    AVStream *st;
+    MOVStreamContext *sc;
 
-    if (atom.size < 8 || atom.size > FFMIN(INT_MAX, SIZE_MAX)) {
-        av_log(c->fc, AV_LOG_ERROR, "senc atom size %"PRId64" invalid\n", atom.size);
+    if (c->fc->nb_streams < 1)
+        return 0;
+    st = c->fc->streams[c->fc->nb_streams-1];
+    sc = st->priv_data;
+
+    if (sc->pseudo_stream_id != 0) {
+        av_log(c->fc, AV_LOG_ERROR, "schm boxes are only supported in first sample descriptor\n");
+        return AVERROR_PATCHWELCOME;
+    }
+
+    if (atom.size < 8)
         return AVERROR_INVALIDDATA;
+
+    avio_rb32(pb); /* version and flags */
+
+    if (!sc->cenc.default_encrypted_sample) {
+      sc->cenc.default_encrypted_sample = av_encryption_info_alloc(0, 16, 16);
+      if (!sc->cenc.default_encrypted_sample) {
+          return AVERROR(ENOMEM);
+      }
     }
 
-    /* save the auxiliary info as is */
-    auxiliary_info_size = atom.size - 8;
+    sc->cenc.default_encrypted_sample->scheme = avio_rl32(pb);
+    return 0;
+}
 
-    sc->cenc.auxiliary_info = av_malloc(auxiliary_info_size);
-    if (!sc->cenc.auxiliary_info) {
-        return AVERROR(ENOMEM);
+static int mov_read_tenc(MOVContext *c, AVIOContext *pb, MOVAtom atom)
+{
+    AVStream *st;
+    MOVStreamContext *sc;
+    unsigned int version, pattern, is_protected, iv_size;
+
+    if (c->fc->nb_streams < 1)
+        return 0;
+    st = c->fc->streams[c->fc->nb_streams-1];
+    sc = st->priv_data;
+
+    if (sc->pseudo_stream_id != 0) {
+        av_log(c->fc, AV_LOG_ERROR, "tenc atom are only supported in first sample descriptor\n");
+        return AVERROR_PATCHWELCOME;
+    }
+
+    if (!sc->cenc.default_encrypted_sample) {
+      sc->cenc.default_encrypted_sample = av_encryption_info_alloc(0, 16, 16);
+      if (!sc->cenc.default_encrypted_sample) {
+          return AVERROR(ENOMEM);
+      }
     }
 
-    sc->cenc.auxiliary_info_end = sc->cenc.auxiliary_info + auxiliary_info_size;
-    sc->cenc.auxiliary_info_pos = sc->cenc.auxiliary_info;
-    sc->cenc.auxiliary_info_index = 0;
+    if (atom.size < 20)
+        return AVERROR_INVALIDDATA;
+
+    version = avio_r8(pb); /* version */
+    avio_rb24(pb); /* flags */
 
-    if (avio_read(pb, sc->cenc.auxiliary_info, auxiliary_info_size) != auxiliary_info_size) {
-        av_log(c->fc, AV_LOG_ERROR, "failed to read the auxiliary info");
+    avio_r8(pb); /* reserved */
+    pattern = avio_r8(pb);
+
+    if (version > 0) {
+      sc->cenc.default_encrypted_sample->crypt_byte_block = pattern >> 4;
+      sc->cenc.default_encrypted_sample->skip_byte_block = pattern & 0xf;
+    }
+
+    is_protected = avio_r8(pb);
+    if (is_protected && !sc->cenc.encryption_index) {
+        // The whole stream should be by-default encrypted.
+        sc->cenc.encryption_index = av_mallocz(sizeof(MOVEncryptionIndex));
+        if (!sc->cenc.encryption_index)
+            return AVERROR(ENOMEM);
+    }
+    sc->cenc.per_sample_iv_size = avio_r8(pb);
+    if (avio_read(pb, sc->cenc.default_encrypted_sample->key_id, 16) != 16) {
+        av_log(c->fc, AV_LOG_ERROR, "failed to read the default key ID");
         return AVERROR_INVALIDDATA;
     }
 
-    /* initialize the cipher */
-    sc->cenc.aes_ctr = av_aes_ctr_alloc();
-    if (!sc->cenc.aes_ctr) {
-        return AVERROR(ENOMEM);
+    if (is_protected && !sc->cenc.per_sample_iv_size) {
+        iv_size = avio_r8(pb);
+        if (iv_size != 8 && iv_size != 16) {
+            av_log(c->fc, AV_LOG_ERROR, "invalid default_constant_IV_size in tenc atom\n");
+            return AVERROR_INVALIDDATA;
+        }
+
+        if (avio_read(pb, sc->cenc.default_encrypted_sample->iv, iv_size) != iv_size) {
+            av_log(c->fc, AV_LOG_ERROR, "failed to read the default IV");
+            return AVERROR_INVALIDDATA;
+        }
     }
 
-    return av_aes_ctr_init(sc->cenc.aes_ctr, c->decryption_key);
+    return 0;
 }
 
 static int mov_read_dfla(MOVContext *c, AVIOContext *pb, MOVAtom atom)
@@ -5800,74 +5994,103 @@  static int mov_read_dfla(MOVContext *c, AVIOContext *pb, MOVAtom atom)
     return 0;
 }
 
-static int cenc_filter(MOVContext *c, MOVStreamContext *sc, int64_t index, uint8_t *input, int size)
+static int cenc_decrypt(MOVContext *c, MOVStreamContext *sc, AVEncryptionInfo *sample, uint8_t *input, int size)
 {
-    uint32_t encrypted_bytes;
-    uint16_t subsample_count;
-    uint16_t clear_bytes;
-    uint8_t* input_end = input + size;
-    int ret;
+    int i, ret;
 
-    av_log(c->fc, AV_LOG_ERROR, "cenc_filter will be fixed in a follow-up commit.\n");
-    return AVERROR_PATCHWELCOME;
+    if (sample->scheme != MKTAG('c','e','n','c') || sample->crypt_byte_block != 0 || sample->skip_byte_block != 0) {
+        av_log(c->fc, AV_LOG_ERROR, "Only the 'cenc' encryption scheme is supported\n");
+        return AVERROR_PATCHWELCOME;
+    }
+
+    if (!sc->cenc.aes_ctr) {
+        /* initialize the cipher */
+        sc->cenc.aes_ctr = av_aes_ctr_alloc();
+        if (!sc->cenc.aes_ctr) {
+            return AVERROR(ENOMEM);
+        }
 
-    /* read the iv */
-    if (AES_CTR_IV_SIZE > sc->cenc.auxiliary_info_end - sc->cenc.auxiliary_info_pos) {
-        av_log(c->fc, AV_LOG_ERROR, "failed to read iv from the auxiliary info\n");
-        return AVERROR_INVALIDDATA;
+        ret = av_aes_ctr_init(sc->cenc.aes_ctr, c->decryption_key);
+        if (ret < 0) {
+            return ret;
+        }
     }
 
-    av_aes_ctr_set_iv(sc->cenc.aes_ctr, sc->cenc.auxiliary_info_pos);
-    sc->cenc.auxiliary_info_pos += AES_CTR_IV_SIZE;
+    av_aes_ctr_set_full_iv(sc->cenc.aes_ctr, sample->iv);
 
-    if (!sc->cenc.use_subsamples)
+    if (!sample->subsample_count)
     {
         /* decrypt the whole packet */
         av_aes_ctr_crypt(sc->cenc.aes_ctr, input, input, size);
         return 0;
     }
 
-    /* read the subsample count */
-    if (sizeof(uint16_t) > sc->cenc.auxiliary_info_end - sc->cenc.auxiliary_info_pos) {
-        av_log(c->fc, AV_LOG_ERROR, "failed to read subsample count from the auxiliary info\n");
-        return AVERROR_INVALIDDATA;
-    }
-
-    subsample_count = AV_RB16(sc->cenc.auxiliary_info_pos);
-    sc->cenc.auxiliary_info_pos += sizeof(uint16_t);
-
-    for (; subsample_count > 0; subsample_count--)
+    for (i = 0; i < sample->subsample_count; i++)
     {
-        if (6 > sc->cenc.auxiliary_info_end - sc->cenc.auxiliary_info_pos) {
-            av_log(c->fc, AV_LOG_ERROR, "failed to read subsample from the auxiliary info\n");
-            return AVERROR_INVALIDDATA;
-        }
-
-        /* read the number of clear / encrypted bytes */
-        clear_bytes = AV_RB16(sc->cenc.auxiliary_info_pos);
-        sc->cenc.auxiliary_info_pos += sizeof(uint16_t);
-        encrypted_bytes = AV_RB32(sc->cenc.auxiliary_info_pos);
-        sc->cenc.auxiliary_info_pos += sizeof(uint32_t);
-
-        if ((uint64_t)clear_bytes + encrypted_bytes > input_end - input) {
+        if (sample->subsamples[i].bytes_of_clear_data + sample->subsamples[i].bytes_of_protected_data > size) {
             av_log(c->fc, AV_LOG_ERROR, "subsample size exceeds the packet size left\n");
             return AVERROR_INVALIDDATA;
         }
 
         /* skip the clear bytes */
-        input += clear_bytes;
+        input += sample->subsamples[i].bytes_of_clear_data;
+        size -= sample->subsamples[i].bytes_of_clear_data;
 
         /* decrypt the encrypted bytes */
-        av_aes_ctr_crypt(sc->cenc.aes_ctr, input, input, encrypted_bytes);
-        input += encrypted_bytes;
+        av_aes_ctr_crypt(sc->cenc.aes_ctr, input, input, sample->subsamples[i].bytes_of_protected_data);
+        input += sample->subsamples[i].bytes_of_protected_data;
+        size -= sample->subsamples[i].bytes_of_protected_data;
     }
 
-    if (input < input_end) {
+    if (size > 0) {
         av_log(c->fc, AV_LOG_ERROR, "leftover packet bytes after subsample processing\n");
         return AVERROR_INVALIDDATA;
     }
 
-    sc->cenc.auxiliary_info_index++;
+    return 0;
+}
+
+static int cenc_filter(MOVContext *mov, MOVStreamContext *sc, AVPacket *pkt, int current_index)
+{
+    MOVFragmentStreamInfo *frag_stream_info;
+    MOVEncryptionIndex *encryption_index;
+    AVEncryptionInfo *encrypted_sample;
+    int encrypted_index;
+
+    frag_stream_info = get_current_frag_stream_info(&mov->frag_index);
+    encrypted_index = current_index;
+    encryption_index = NULL;
+    if (frag_stream_info) {
+        // Note this only supports encryption info in the first sample descriptor.
+        if (mov->fragment.stsd_id == 1) {
+            if (frag_stream_info->encryption_index) {
+                encrypted_index = current_index - frag_stream_info->index_entry;
+                encryption_index = frag_stream_info->encryption_index;
+            } else {
+                encryption_index = sc->cenc.encryption_index;
+            }
+        }
+    } else {
+        encryption_index = sc->cenc.encryption_index;
+    }
+
+    if (encryption_index) {
+        if (!encryption_index->nb_encrypted_samples) {
+            // Full-sample encryption with default settings.
+            encrypted_sample = sc->cenc.default_encrypted_sample;
+        } else if (encrypted_index >= 0 && encrypted_index < encryption_index->nb_encrypted_samples) {
+            // Per-sample setting override.
+            encrypted_sample = encryption_index->encrypted_samples[encrypted_index];
+        } else {
+            av_log(mov->fc, AV_LOG_ERROR, "Incorrect number of samples in encryption info\n");
+            return AVERROR_INVALIDDATA;
+        }
+
+        if (mov->decryption_key) {
+            return cenc_decrypt(mov, sc, encrypted_sample, pkt->data, pkt->size);
+        }
+    }
+
     return 0;
 }
 
@@ -5996,6 +6219,9 @@  static const MOVParseTableEntry mov_default_parse_table[] = {
 { MKTAG('s','i','n','f'), mov_read_default },
 { MKTAG('f','r','m','a'), mov_read_frma },
 { MKTAG('s','e','n','c'), mov_read_senc },
+{ MKTAG('s','c','h','m'), mov_read_schm },
+{ MKTAG('s','c','h','i'), mov_read_default },
+{ MKTAG('t','e','n','c'), mov_read_tenc },
 { MKTAG('d','f','L','a'), mov_read_dfla },
 { MKTAG('s','t','3','d'), mov_read_st3d }, /* stereoscopic 3D video box */
 { MKTAG('s','v','3','d'), mov_read_sv3d }, /* spherical video box */
@@ -6381,6 +6607,16 @@  static int mov_read_timecode_track(AVFormatContext *s, AVStream *st)
     return 0;
 }
 
+static void mov_free_encryption_index(MOVEncryptionIndex **index) {
+    int i;
+    if (!index || !*index) return;
+    for (i = 0; i < (*index)->nb_encrypted_samples; i++) {
+        av_encryption_info_free((*index)->encrypted_samples[i]);
+    }
+    av_freep(&(*index)->encrypted_samples);
+    av_freep(index);
+}
+
 static int mov_read_close(AVFormatContext *s)
 {
     MOVContext *mov = s->priv_data;
@@ -6423,8 +6659,8 @@  static int mov_read_close(AVFormatContext *s)
         av_freep(&sc->extradata);
         av_freep(&sc->extradata_size);
 
-        av_freep(&sc->cenc.auxiliary_info);
-        av_freep(&sc->cenc.auxiliary_info_sizes);
+        mov_free_encryption_index(&sc->cenc.encryption_index);
+        av_encryption_info_free(sc->cenc.default_encrypted_sample);
         av_aes_ctr_free(sc->cenc.aes_ctr);
 
         av_freep(&sc->stereo3d);
@@ -6449,6 +6685,10 @@  static int mov_read_close(AVFormatContext *s)
     av_freep(&mov->bitrates);
 
     for (i = 0; i < mov->frag_index.nb_items; i++) {
+        MOVFragmentStreamInfo *frag = mov->frag_index.item[i].stream_info;
+        for (j = 0; j < mov->frag_index.item[i].nb_stream_info; j++) {
+            mov_free_encryption_index(&frag[j].encryption_index);
+        }
         av_freep(&mov->frag_index.item[i].stream_info);
     }
     av_freep(&mov->frag_index.item);
@@ -7019,12 +7259,9 @@  static int mov_read_packet(AVFormatContext *s, AVPacket *pkt)
     if (mov->aax_mode)
         aax_filter(pkt->data, pkt->size, mov);
 
-    if (sc->cenc.aes_ctr) {
-        ret = cenc_filter(mov, sc, current_index, pkt->data, pkt->size);
-        if (ret) {
-            return ret;
-        }
-    }
+    ret = cenc_filter(mov, sc, pkt, current_index);
+    if (ret < 0)
+        return ret;
 
     return 0;
 }
diff --git a/tests/fate/mov.mak b/tests/fate/mov.mak
index 64f92e9488..90ee06a000 100644
--- a/tests/fate/mov.mak
+++ b/tests/fate/mov.mak
@@ -6,6 +6,8 @@  FATE_MOV = fate-mov-3elist \
            fate-mov-1elist-ends-last-bframe \
            fate-mov-2elist-elist1-ends-bframe \
            fate-mov-3elist-encrypted \
+           fate-mov-frag-encrypted \
+           fate-mov-tenc-only-encrypted \
            fate-mov-invalid-elst-entry-count \
            fate-mov-gpmf-remux \
            fate-mov-440hz-10ms \
@@ -37,6 +39,12 @@  fate-mov-3elist-1ctts: CMD = framemd5 -i $(TARGET_SAMPLES)/mov/mov-3elist-1ctts.
 # Edit list with encryption
 fate-mov-3elist-encrypted: CMD = framemd5 -decryption_key 12345678901234567890123456789012 -i $(TARGET_SAMPLES)/mov/mov-3elist-encrypted.mov
 
+# Fragmented encryption with senc boxes in movie fragments.
+fate-mov-frag-encrypted: CMD = framemd5 -decryption_key 12345678901234567890123456789012 -i $(TARGET_SAMPLES)/mov/mov-frag-encrypted.mp4
+
+# Full-sample encryption and constant IV using only tenc atom (no senc/saio/saiz).
+fate-mov-tenc-only-encrypted: CMD = framemd5 -decryption_key 12345678901234567890123456789012 -i $(TARGET_SAMPLES)/mov/mov-tenc-only-encrypted.mp4
+
 # Makes sure that the CTTS is also modified when we fix avindex in mov.c while parsing edit lists.
 fate-mov-elist-starts-ctts-2ndsample: CMD = framemd5 -i $(TARGET_SAMPLES)/mov/mov-elist-starts-ctts-2ndsample.mov
 
diff --git a/tests/ref/fate/mov-frag-encrypted b/tests/ref/fate/mov-frag-encrypted
new file mode 100644
index 0000000000..e6c109b566
--- /dev/null
+++ b/tests/ref/fate/mov-frag-encrypted
@@ -0,0 +1,57 @@ 
+#format: frame checksums
+#version: 2
+#hash: MD5
+#tb 0: 1/24
+#media_type 0: video
+#codec_id 0: rawvideo
+#dimensions 0: 120x52
+#sar 0: 544/545
+#stream#, dts,        pts, duration,     size, hash
+0,          0,          0,        1,     9360, 920bdc277a6a31c1daed9aca44b10caf
+0,          1,          1,        1,     9360, f1c0b61fef593de57cb97be7fa846569
+0,          2,          2,        1,     9360, 6ef32d9d4398355aebf6d3fb11d51d3f
+0,          3,          3,        1,     9360, d38fd3ef1e5a92fc109b8dd9eb6dadeb
+0,          4,          4,        1,     9360, 54cc0c8a25d2f14f32663837d5e646f1
+0,          5,          5,        1,     9360, b4b6829726dc3decb8b80ba0c35bcf30
+0,          6,          6,        1,     9360, fca3f941e60a2f0a4ce30d5e0efbec3c
+0,          7,          7,        1,     9360, cda6e26b6c1039ff3d229b262c9210c3
+0,          8,          8,        1,     9360, f0d69255e3a27a8b4ae8a4b7b210929d
+0,          9,          9,        1,     9360, 12cb23dd4e32af9c3b35f943714e3fdd
+0,         10,         10,        1,     9360, 082aaf3216124ddcecb422fe5c832e82
+0,         11,         11,        1,     9360, ff37bb8cd6bd0412a3b3cb45db54afc9
+0,         12,         12,        1,     9360, dfb9085441575732844b6c2f05d5f542
+0,         13,         13,        1,     9360, 0017100feaaa9fc7eacd2447d50d7542
+0,         14,         14,        1,     9360, 4e2f1b8c4e04c59934c2f58541e62613
+0,         15,         15,        1,     9360, 27a44dfea7cd2d30e488194c34ab473c
+0,         16,         16,        1,     9360, fc7b56bd95e990a33cf575d1ef820902
+0,         17,         17,        1,     9360, fa2d1609e69714dffc410e65f3c8b755
+0,         18,         18,        1,     9360, 705d7429f447cb13febe202d567795f2
+0,         19,         19,        1,     9360, 234802ce86e868faaf2cd40a286846ea
+0,         20,         20,        1,     9360, 2f0354b40d211d0a4ade4568bea4f85e
+0,         21,         21,        1,     9360, e96af3b6c0cc931463ca77d6be0f1148
+0,         22,         22,        1,     9360, 04a904d798361959971361401879c7e4
+0,         23,         23,        1,     9360, 2f119642340df6d25362b5590ded46b7
+0,         24,         24,        1,     9360, 5993fca2e60050706f857ac76e48f386
+0,         25,         25,        1,     9360, 2ff3b5775fed3d527bfbbeea786787fe
+0,         26,         26,        1,     9360, 42024dbe23d3fb5b0d8987ae1ce390a8
+0,         27,         27,        1,     9360, d804204f0bd9db5f6a758e2c934d9e38
+0,         28,         28,        1,     9360, e322712e6e34c58ec1a2ab5e2c1e3bfe
+0,         29,         29,        1,     9360, 3975bd1a5f6a6b6260276777f9de611e
+0,         30,         30,        1,     9360, 4388f0412efc6310706a7cdedc859ea9
+0,         31,         31,        1,     9360, b4b9a11b0b86635267345a569640e8d4
+0,         32,         32,        1,     9360, 31879c7b8d6b67a4209ffde786bb8cb4
+0,         33,         33,        1,     9360, 4b6dc02d7c889fe4abd4e013b25f585a
+0,         34,         34,        1,     9360, dc73aae82bd39a1220d1106c8d3e8252
+0,         35,         35,        1,     9360, 54c7dfbd49f312806f6c1a89f7c2c36f
+0,         36,         36,        1,     9360, 150abc64f8994d444a521ea90570443c
+0,         37,         37,        1,     9360, d277cdc7dcadbe0016f2e950459e7ebf
+0,         38,         38,        1,     9360, 2196bf338ead90ea54687b85c73c8229
+0,         39,         39,        1,     9360, 53ce5da5365abc0bd3217dd98e7c465d
+0,         40,         40,        1,     9360, 34ee9832aea55c0c4e6f4381c413c10e
+0,         41,         41,        1,     9360, 1769c7b5849e4681119067a06ac29a4f
+0,         42,         42,        1,     9360, 71f53df739ef283a5184c91ef4b158e8
+0,         43,         43,        1,     9360, d2d394739e9a59c06f0354c16843cb63
+0,         44,         44,        1,     9360, d8e458e92ae29344505a24a3059fc584
+0,         45,         45,        1,     9360, 0f1b11a09911851b798df2ef76253a7f
+0,         46,         46,        1,     9360, 5c4a9f22baecf4e749c0d5c65a4f1007
+0,         47,         47,        1,     9360, 3e2b7e7262fdca08d9d1ef6070125c4b
diff --git a/tests/ref/fate/mov-tenc-only-encrypted b/tests/ref/fate/mov-tenc-only-encrypted
new file mode 100644
index 0000000000..1d57aa6a80
--- /dev/null
+++ b/tests/ref/fate/mov-tenc-only-encrypted
@@ -0,0 +1,57 @@ 
+#format: frame checksums
+#version: 2
+#hash: MD5
+#tb 0: 1/24
+#media_type 0: video
+#codec_id 0: rawvideo
+#dimensions 0: 1024x436
+#sar 0: 1/1
+#stream#, dts,        pts, duration,     size, hash
+0,          0,          0,        1,   669696, f48f296a85eda5ba069dc851a3228bef
+0,          1,          1,        1,   669696, a50c5f69bfa3387d49b5bdf738e6529c
+0,          2,          2,        1,   669696, 05061299003760f6a4795b408f72aa31
+0,          3,          3,        1,   669696, 2572119f0b0cdd83f8a7e06252cecd3b
+0,          4,          4,        1,   669696, 29fe6a6bdb4a69018e318886a297f07e
+0,          5,          5,        1,   669696, e8233c7fbaecfbff965c7dfdd3982b1b
+0,          6,          6,        1,   669696, d9259df9880ff5d4a4b38282e67f407b
+0,          7,          7,        1,   669696, 3e8d795195038993503ea9ab6984c915
+0,          8,          8,        1,   669696, bc4e2d253b715a34f85aae1b080e3460
+0,          9,          9,        1,   669696, 09aba8b3a96f53f9268e7420a10bfab6
+0,         10,         10,        1,   669696, 179447977dd580da8b35fb5310a809ca
+0,         11,         11,        1,   669696, 7a0eea9d54577990345f5705ab9882be
+0,         12,         12,        1,   669696, 5bb96eb76f461825740e5938456df759
+0,         13,         13,        1,   669696, bd4ac4a760ead774b9422a27dc071964
+0,         14,         14,        1,   669696, 1cc05f760a9b751fc89e77f2bcc97259
+0,         15,         15,        1,   669696, 825d0dee6f0174ba7102892c7de30b4d
+0,         16,         16,        1,   669696, d26a2ef5267f6bb03c4e1d8514eee0df
+0,         17,         17,        1,   669696, c916ffdeadca76596a8f7fd47914b5ef
+0,         18,         18,        1,   669696, 6e085acfa7fee0658ea0ae6188274c17
+0,         19,         19,        1,   669696, 1e95fa5b3561283f05bf0bd44cb91721
+0,         20,         20,        1,   669696, 37e3d135aba9dfb8b87e441753115374
+0,         21,         21,        1,   669696, 9c398310e8564491de624393c16265ce
+0,         22,         22,        1,   669696, c87209e4d2617bc2ab40a75f455f09da
+0,         23,         23,        1,   669696, 2679c2f8d1d1af21982e245945c1ee60
+0,         24,         24,        1,   669696, 6151ab4781f31c5beb66b356ad547122
+0,         25,         25,        1,   669696, f7ef6293bfb3a6a329061cb6a5ed5a38
+0,         26,         26,        1,   669696, 2f6e666d14dfc407ca0c0f347b13eb08
+0,         27,         27,        1,   669696, 3454fa1730d79b1aa8dbbc865dc150f4
+0,         28,         28,        1,   669696, e93dc683e2453419a0419ab9af0f8f95
+0,         29,         29,        1,   669696, 031eb3154f7f83cf86d42bee66be9cf7
+0,         30,         30,        1,   669696, 1205c36723e88811206c68892d3aaed6
+0,         31,         31,        1,   669696, 7dd7a8a19dcd73b31ddc6a6d0c597a42
+0,         32,         32,        1,   669696, 7c91115368ea2531262a1197468bc3f4
+0,         33,         33,        1,   669696, 3cf6d9ba385e0fff76da33299ed5380c
+0,         34,         34,        1,   669696, 859fc8c3ef049e3c1175a85fb0a90a3d
+0,         35,         35,        1,   669696, 1d09ce6c7027103d99a4d5799f6e72ab
+0,         36,         36,        1,   669696, 3dcb8357408ac88abd734128d8f5dd6f
+0,         37,         37,        1,   669696, 4dafce137a0a5178f6efaec878e64d36
+0,         38,         38,        1,   669696, 44c478f29a1399ed03275a7357f57d48
+0,         39,         39,        1,   669696, 6e9edaac7414c0e14591ac3d4d0b1ac4
+0,         40,         40,        1,   669696, 522e4aaeea0825da27f631a9e690d654
+0,         41,         41,        1,   669696, 85f2502a718440834c40051d30f8a65e
+0,         42,         42,        1,   669696, ae8816f7bd4645ef1a17ee6d09b4c8d2
+0,         43,         43,        1,   669696, 914b006fa92f1eb3e590245749f6810d
+0,         44,         44,        1,   669696, 9406901542e94c429dff46108782ed69
+0,         45,         45,        1,   669696, 324c13641c39eef5c476023e358c0391
+0,         46,         46,        1,   669696, 4058e886e17c22e4eb9da1dd0d6ad891
+0,         47,         47,        1,   669696, 9edf9cd15eea985b42fd1f5035b1d693
-- 
2.16.0.rc1.238.g530d649a79-goog