From 7e9b7cdc9f7c030b7adc4440ce6dba8246b7f33d Mon Sep 17 00:00:00 2001
From: Dale Curtis <dalecurtis@chromium.org>
Date: Mon, 28 Aug 2017 12:37:09 -0700
Subject: [PATCH] [mov] Avoid O(n^2) memmove() operations when inserting ctts
 entries.

A previous patch to ctts reads added support for inserting ctts entries
in sample order. If an insert happens in the middle of existing ctts
entries, we would shift 1 entry at a time to make room. This patch
attempts to shift entries only once based on the expected ctts
entry count.

The caveat is that av_add_index_entry() may fail and in such a case we
need to recover the gap in ctts entries. This is done in best effort
fashion, although I'm not confident this will always work. I don't have
any concrete examples, just a vague worry this can lead to ctts desync
when index insertion fails.

Signed-off-by: Dale Curtis <dalecurtis@chromium.org>
---
 libavformat/mov.c | 34 ++++++++++++++++++++++++++--------
 1 file changed, 26 insertions(+), 8 deletions(-)

diff --git a/libavformat/mov.c b/libavformat/mov.c
index 876f48d912..91c956431f 100644
--- a/libavformat/mov.c
+++ b/libavformat/mov.c
@@ -4263,6 +4263,7 @@ static int mov_read_trun(MOVContext *c, AVIOContext *pb, MOVAtom atom)
     uint64_t offset;
     int64_t dts;
     int data_offset = 0;
+    int expected_ctts_count, old_ctts_count, first_ctts_index = -1;
     unsigned entries, first_sample_flags = frag->flags;
     int flags, distance, i;
 
@@ -4306,6 +4307,9 @@ static int mov_read_trun(MOVContext *c, AVIOContext *pb, MOVAtom atom)
     }
     if ((uint64_t)entries+sc->ctts_count >= UINT_MAX/sizeof(*sc->ctts_data))
         return AVERROR_INVALIDDATA;
+    old_ctts_count = sc->ctts_count;
+    expected_ctts_count = sc->ctts_count + entries;
+
     if (flags & MOV_TRUN_DATA_OFFSET)        data_offset        = avio_rb32(pb);
     if (flags & MOV_TRUN_FIRST_SAMPLE_FLAGS) first_sample_flags = avio_rb32(pb);
     dts    = sc->track_end - sc->time_offset;
@@ -4361,9 +4365,10 @@ static int mov_read_trun(MOVContext *c, AVIOContext *pb, MOVAtom atom)
         ctts_index = av_add_index_entry(st, offset, dts, sample_size, distance,
                                         keyframe ? AVINDEX_KEYFRAME : 0);
         if (ctts_index >= 0 && old_nb_index_entries < st->nb_index_entries) {
-            unsigned int size_needed = st->nb_index_entries * sizeof(*sc->ctts_data);
+            unsigned int size_needed = expected_ctts_count * sizeof(*sc->ctts_data);
             unsigned int request_size = size_needed > sc->ctts_allocated_size ?
                 FFMAX(size_needed, 2 * sc->ctts_allocated_size) : size_needed;
+            av_assert0(ctts_index < expected_ctts_count);
             ctts_data = av_fast_realloc(sc->ctts_data, &sc->ctts_allocated_size, request_size);
             if (!ctts_data) {
                 av_freep(&sc->ctts_data);
@@ -4371,14 +4376,16 @@ static int mov_read_trun(MOVContext *c, AVIOContext *pb, MOVAtom atom)
             }
 
             sc->ctts_data = ctts_data;
-            if (ctts_index != old_nb_index_entries) {
-                memmove(sc->ctts_data + ctts_index + 1, sc->ctts_data + ctts_index,
+            if (first_ctts_index < 0 && ctts_index != old_nb_index_entries) {
+                memmove(sc->ctts_data + entries, sc->ctts_data + ctts_index,
                         sizeof(*sc->ctts_data) * (sc->ctts_count - ctts_index));
-                if (ctts_index <= sc->current_sample) {
-                    // if we inserted a new item before the current sample, move the
-                    // counter ahead so it is still pointing to the same sample.
-                    sc->current_sample++;
-                }
+                first_ctts_index = ctts_index;
+            }
+
+            if (ctts_index != old_nb_index_entries && ctts_index <= sc->current_sample) {
+                // if we inserted a new item before the current sample, move the
+                // counter ahead so it is still pointing to the same sample.
+                sc->current_sample++;
             }
 
             sc->ctts_data[ctts_index].count = 1;
@@ -4399,6 +4406,17 @@ static int mov_read_trun(MOVContext *c, AVIOContext *pb, MOVAtom atom)
         sc->nb_frames_for_fps ++;
     }
 
+    // Some error occurred while inserting ctts entries. We need to shift back
+    // the entries we expected to fill to remove the gap.
+    if (expected_ctts_count != sc->ctts_count && first_ctts_index >= 0) {
+        int actual_entry_count = sc->ctts_count - old_ctts_count;
+        int actual_entry_end = first_ctts_index + actual_entry_count;
+        av_log(c->fc, AV_LOG_ERROR, "Failed to add index entry - rewinding ctts table gap\n");
+        av_assert0(expected_ctts_count > sc->ctts_count);
+        memmove(sc->ctts_data + actual_entry_end, sc->ctts_data + entries,
+                sizeof(*sc->ctts_data) * (sc->ctts_count - actual_entry_end));
+    }
+
     if (pb->eof_reached)
         return AVERROR_EOF;
 
-- 
2.14.1.342.g6490525c54-goog

