tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

TestParser.cpp (46956B)


      1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
      2 /* This Source Code Form is subject to the terms of the Mozilla Public
      3 * License, v. 2.0. If a copy of the MPL was not distributed with this
      4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
      5 
      6 #include "BufferStream.h"
      7 #include "MP4Metadata.h"
      8 #include "MediaData.h"
      9 #include "MoofParser.h"
     10 #include "TelemetryFixture.h"
     11 #include "TelemetryTestHelpers.h"
     12 #include "gtest/gtest.h"
     13 #include "js/Conversions.h"
     14 #include "mozilla/Preferences.h"
     15 #include "mozilla/gtest/MozAssertions.h"
     16 
     17 class TestStream;
     18 namespace mozilla {
     19 DDLoggedTypeNameAndBase(::TestStream, ByteStream);
     20 }  // namespace mozilla
     21 
     22 using namespace mozilla;
     23 
     24 static const uint32_t E = MP4Metadata::NumberTracksError();
     25 
     26 class TestStream : public ByteStream,
     27                   public DecoderDoctorLifeLogger<TestStream> {
     28 public:
     29  TestStream(const uint8_t* aBuffer, size_t aSize)
     30      : mHighestSuccessfulEndOffset(0), mBuffer(aBuffer), mSize(aSize) {}
     31  nsresult ReadAt(int64_t aOffset, void* aData, size_t aLength,
     32                  size_t* aBytesRead) override {
     33    if (aOffset < 0 || aOffset > static_cast<int64_t>(mSize)) {
     34      return NS_ERROR_DOM_MEDIA_RANGE_ERR;
     35    }
     36    // After the test, 0 <= aOffset <= mSize <= SIZE_MAX, so it's safe to cast
     37    // to size_t.
     38    size_t offset = static_cast<size_t>(aOffset);
     39    // Don't read past the end (but it's not an error to try).
     40    if (aLength > mSize - offset) {
     41      aLength = mSize - offset;
     42    }
     43    // Now, 0 <= offset <= offset + aLength <= mSize <= SIZE_MAX.
     44    *aBytesRead = aLength;
     45    memcpy(aData, mBuffer + offset, aLength);
     46    if (mHighestSuccessfulEndOffset < offset + aLength) {
     47      mHighestSuccessfulEndOffset = offset + aLength;
     48    }
     49    return NS_OK;
     50  }
     51  nsresult CachedReadAt(int64_t aOffset, void* aData, size_t aLength,
     52                        size_t* aBytesRead) override {
     53    return ReadAt(aOffset, aData, aLength, aBytesRead);
     54  }
     55  bool Length(int64_t* aLength) override {
     56    *aLength = mSize;
     57    return true;
     58  }
     59  void DiscardBefore(int64_t aOffset) override {}
     60 
     61  // Offset past the last character ever read. 0 when nothing read yet.
     62  size_t mHighestSuccessfulEndOffset;
     63 
     64 protected:
     65  virtual ~TestStream() = default;
     66 
     67  const uint8_t* mBuffer;
     68  size_t mSize;
     69 };
     70 
     71 TEST(MP4Metadata, EmptyStream)
     72 {
     73  RefPtr<ByteStream> stream = new TestStream(nullptr, 0);
     74 
     75  MP4Metadata::ResultAndByteBuffer metadataBuffer =
     76      MP4Metadata::Metadata(stream);
     77  EXPECT_TRUE(NS_OK != metadataBuffer.Result());
     78  EXPECT_FALSE(static_cast<bool>(metadataBuffer.Ref()));
     79 
     80  MP4Metadata metadata(stream);
     81  EXPECT_TRUE(0u ==
     82                  metadata.GetNumberTracks(TrackInfo::kUndefinedTrack).Ref() ||
     83              E == metadata.GetNumberTracks(TrackInfo::kUndefinedTrack).Ref());
     84  EXPECT_TRUE(0u == metadata.GetNumberTracks(TrackInfo::kAudioTrack).Ref() ||
     85              E == metadata.GetNumberTracks(TrackInfo::kAudioTrack).Ref());
     86  EXPECT_TRUE(0u == metadata.GetNumberTracks(TrackInfo::kVideoTrack).Ref() ||
     87              E == metadata.GetNumberTracks(TrackInfo::kVideoTrack).Ref());
     88  EXPECT_TRUE(0u == metadata.GetNumberTracks(TrackInfo::kTextTrack).Ref() ||
     89              E == metadata.GetNumberTracks(TrackInfo::kTextTrack).Ref());
     90  EXPECT_FALSE(metadata.GetTrackInfo(TrackInfo::kAudioTrack, 0).Ref());
     91  EXPECT_FALSE(metadata.GetTrackInfo(TrackInfo::kVideoTrack, 0).Ref());
     92  EXPECT_FALSE(metadata.GetTrackInfo(TrackInfo::kTextTrack, 0).Ref());
     93  // We can seek anywhere in any MPEG4.
     94  EXPECT_TRUE(metadata.CanSeek());
     95  EXPECT_FALSE(metadata.Crypto().Ref()->valid);
     96 }
     97 
     98 TEST(MoofParser, EmptyStream)
     99 {
    100  RefPtr<ByteStream> stream = new TestStream(nullptr, 0);
    101 
    102  MoofParser parser(stream, AsVariant(ParseAllTracks{}), false);
    103  EXPECT_EQ(0u, parser.mOffset);
    104  EXPECT_TRUE(parser.ReachedEnd());
    105 
    106  MediaByteRangeSet byteRanges;
    107  EXPECT_FALSE(parser.RebuildFragmentedIndex(byteRanges));
    108 
    109  EXPECT_TRUE(parser.GetCompositionRange(byteRanges).IsNull());
    110  EXPECT_TRUE(parser.mInitRange.IsEmpty());
    111  EXPECT_EQ(0u, parser.mOffset);
    112  EXPECT_TRUE(parser.ReachedEnd());
    113  RefPtr<MediaByteBuffer> metadataBuffer = parser.Metadata();
    114  EXPECT_FALSE(metadataBuffer);
    115  EXPECT_TRUE(parser.FirstCompleteMediaSegment().IsEmpty());
    116  EXPECT_TRUE(parser.FirstCompleteMediaHeader().IsEmpty());
    117 }
    118 
    119 nsTArray<uint8_t> ReadTestFile(const char* aFilename) {
    120  if (!aFilename) {
    121    return {};
    122  }
    123  FILE* f = fopen(aFilename, "rb");
    124  if (!f) {
    125    return {};
    126  }
    127 
    128  if (fseek(f, 0, SEEK_END) != 0) {
    129    fclose(f);
    130    return {};
    131  }
    132  long position = ftell(f);
    133  // I know EOF==-1, so this test is made obsolete by '<0', but I don't want
    134  // the code to rely on that.
    135  if (position == 0 || position == EOF || position < 0) {
    136    fclose(f);
    137    return {};
    138  }
    139  if (fseek(f, 0, SEEK_SET) != 0) {
    140    fclose(f);
    141    return {};
    142  }
    143 
    144  size_t len = static_cast<size_t>(position);
    145  nsTArray<uint8_t> buffer(len);
    146  buffer.SetLength(len);
    147  size_t read = fread(buffer.Elements(), 1, len, f);
    148  fclose(f);
    149  if (read != len) {
    150    return {};
    151  }
    152 
    153  return buffer;
    154 }
    155 
    156 struct TestFileData {
    157  const char* mFilename;
    158  bool mParseResult;
    159  uint32_t mNumberVideoTracks;
    160  bool mHasVideoIndice;
    161  double mVideoDuration;  // For first video track, -1 if N/A, in seconds.
    162  int32_t mWidth;
    163  int32_t mHeight;
    164  uint32_t mNumberAudioTracks;
    165  double mAudioDuration;  // For first audio track, -1 if N/A, in seconds.
    166  bool mHasCrypto;  // Note, MP4Metadata only considers pssh box for crypto.
    167  uint64_t mParsedOffset;  // or 0 for the end.
    168  bool mValidMoofForTrack1;
    169  bool mValidMoofForAllTracks;
    170  int8_t mAudioProfile;
    171 };
    172 
    173 static const TestFileData testFiles[] = {
    174    // filename parses? #V hasVideoIndex vDur w h #A aDur hasCrypto? moofOffset
    175    // validMoof1? validMoofAll? audio_profile
    176    {"test_case_1156505.mp4", false, 0, false, -1, 0, 0, 0, -1., false, 152,
    177     false, false, 0},  // invalid ''trak box
    178    {"test_case_1181213.mp4", true, 1, true, 0.41666666, 320, 240, 1,
    179     0.47746032, true, 0, false, false, 2},
    180    {"test_case_1181215.mp4", true, 0, false, -1, 0, 0, 0, -1, false, 0, false,
    181     false, 0},
    182    {"test_case_1181223.mp4", false, 0, false, 0.41666666, 320, 240, 0, -1,
    183     false, 0, false, false, 0},
    184 #if 0
    185    // Test disabled due to unsupported audio type (possibly corrupt track).
    186    // This file was used to verify that the parser rejected the file entirely,
    187    // but it is now accepted with the audio track ignored.
    188    {"test_case_1181719.mp4", false, 0, false, -1, 0, 0, 0, -1, false, 0, false,
    189     false, 0},
    190 #endif
    191    {"test_case_1185230.mp4", true, 2, true, 0.41666666, 320, 240, 2,
    192     0.0000059754907, false, 0, false, false, 2},
    193    {"test_case_1187067.mp4", true, 1, true, 0.080000, 160, 90, 0, -1, false, 0,
    194     false, false, 0},
    195    {"test_case_1200326.mp4", false, 0, false, -1, 0, 0, 0, -1, false, 0, false,
    196     false, 0},
    197    {"test_case_1204580.mp4", true, 1, true, 0.502500, 320, 180, 0, -1, false,
    198     0, false, false, 0},
    199    {"test_case_1216748.mp4", false, 0, false, -1, 0, 0, 0, -1, false, 152,
    200     false, false, 0},  // invalid 'trak' box
    201    {"test_case_1296473.mp4", false, 0, false, -1, 0, 0, 0, -1, false, 0, false,
    202     false, 0},
    203    {"test_case_1296532.mp4", true, 1, true, 5.589333, 560, 320, 1, 5.589333,
    204     true, 0, true, false, 2},
    205    {"test_case_1301065.mp4", true, 0, false, -1, 0, 0, 1, 100079991719, false,
    206     0, false, false, 2},
    207    {"test_case_1301065-u32max.mp4", true, 0, false, -1, 0, 0, 1, 97391.548639,
    208     false, 0, false, false, 2},
    209    {"test_case_1301065-max-ez.mp4", true, 0, false, -1, 0, 0, 1,
    210     209146758.205306, false, 0, false, false, 2},
    211    {"test_case_1301065-harder.mp4", true, 0, false, -1, 0, 0, 1,
    212     209146758.205328, false, 0, false, false, 2},
    213    {"test_case_1301065-max-ok.mp4", true, 0, false, -1, 0, 0, 1,
    214     9223372036854.775, false, 0, false, false, 2},
    215    // The duration is overflow for int64_t in TestFileData, parser uses
    216    // uint64_t so
    217    // this file is ignore.
    218    //{ "test_case_1301065-overfl.mp4", 0, -1,   0,   0, 1, 9223372036854775827,
    219    //                                                          false,   0,
    220    //                                                          false, 2
    221    //                                                          },
    222    {"test_case_1301065-i64max.mp4", true, 0, false, -1, 0, 0, 1,
    223     std::numeric_limits<double>::infinity(), false, 0, false, false, 2},
    224    {"test_case_1301065-i64min.mp4", true, 0, false, -1, 0, 0, 1,
    225     -std::numeric_limits<double>::infinity(), false, 0, false, false, 2},
    226    {"test_case_1301065-u64max.mp4", true, 0, false, -1, 0, 0, 1, 0, false, 0,
    227     false, false, 2},
    228    {"test_case_1329061.mov", false, 0, false, -1, 0, 0, 1, 234567981, false, 0,
    229     false, false, 2},
    230    {"test_case_1351094.mp4", true, 0, false, -1, 0, 0, 0, -1, false, 0, false,
    231     false, 0},
    232    {"test_case_1389299.mp4", true, 1, true, 5.589333, 560, 320, 1, 5.589333,
    233     true, 0, true, false, 2},
    234 
    235    {"test_case_1389527.mp4", true, 1, false, 5.005000, 80, 128, 1, 4.992000,
    236     false, 0, false, false, 2},
    237    {"test_case_1395244.mp4", true, 1, true, 0.41666666, 320, 240, 1,
    238     0.47746032, false, 0, false, false, 2},
    239    {"test_case_1388991.mp4", true, 0, false, -1, 0, 0, 1, 30.000181, false, 0,
    240     false, false, 2},
    241    {"test_case_1410565.mp4", false, 0, false, 0, 0, 0, 0, 0, false, 0, false,
    242     false, 2},  // negative 'timescale'
    243    {"test_case_1513651-2-sample-description-entries.mp4", true, 1, true,
    244     9.843344, 400, 300, 0, -1, true, 0, false, false, 0},
    245    {"test_case_1519617-cenc-init-with-track_id-0.mp4", true, 1, true, 0, 1272,
    246     530, 0, -1, false, 0, false, false,
    247     0},  // Uses bad track id 0 and has a sinf but no pssh
    248    {"test_case_1519617-track2-trafs-removed.mp4", true, 1, true, 10.032000,
    249     400, 300, 1, 10.032000, false, 0, true, false, 2},
    250    {"test_case_1519617-video-has-track_id-0.mp4", true, 1, true, 10.032000,
    251     400, 300, 1, 10.032000, false, 0, false, false, 2},  // Uses bad track id 0
    252    // The following file has multiple sample description entries with the same
    253    // crypto information. This does not cover multiple entries with different
    254    // crypto information which is tracked by
    255    // https://bugzilla.mozilla.org/show_bug.cgi?id=1714626
    256    {"test_case_1714125-2-sample-description-entires-with-identical-crypto.mp4",
    257     true, 1, true, 0, 1920, 1080, 0, 0, true, 0, false, false, 0},
    258 };
    259 
    260 TEST(MP4Metadata, test_case_mp4)
    261 {
    262  const TestFileData* tests = nullptr;
    263  size_t length = 0;
    264 
    265  tests = testFiles;
    266  length = std::size(testFiles);
    267 
    268  for (size_t test = 0; test < length; ++test) {
    269    nsTArray<uint8_t> buffer = ReadTestFile(tests[test].mFilename);
    270    ASSERT_FALSE(buffer.IsEmpty());
    271    RefPtr<ByteStream> stream =
    272        new TestStream(buffer.Elements(), buffer.Length());
    273 
    274    MP4Metadata::ResultAndByteBuffer metadataBuffer =
    275        MP4Metadata::Metadata(stream);
    276    EXPECT_EQ(NS_OK, metadataBuffer.Result());
    277    EXPECT_TRUE(metadataBuffer.Ref());
    278 
    279    MP4Metadata metadata(stream);
    280    nsresult res = metadata.Parse();
    281    EXPECT_EQ(tests[test].mParseResult, NS_SUCCEEDED(res))
    282        << tests[test].mFilename;
    283    if (!tests[test].mParseResult) {
    284      continue;
    285    }
    286 
    287    EXPECT_EQ(tests[test].mNumberAudioTracks,
    288              metadata.GetNumberTracks(TrackInfo::kAudioTrack).Ref())
    289        << tests[test].mFilename;
    290    EXPECT_EQ(tests[test].mNumberVideoTracks,
    291              metadata.GetNumberTracks(TrackInfo::kVideoTrack).Ref())
    292        << tests[test].mFilename;
    293    // If there is an error, we should expect an error code instead of zero
    294    // for non-Audio/Video tracks.
    295    const uint32_t None = (tests[test].mNumberVideoTracks == E) ? E : 0;
    296    EXPECT_EQ(None, metadata.GetNumberTracks(TrackInfo::kUndefinedTrack).Ref())
    297        << tests[test].mFilename;
    298    EXPECT_EQ(None, metadata.GetNumberTracks(TrackInfo::kTextTrack).Ref())
    299        << tests[test].mFilename;
    300    EXPECT_FALSE(metadata.GetTrackInfo(TrackInfo::kUndefinedTrack, 0).Ref());
    301    MP4Metadata::ResultAndTrackInfo trackInfo =
    302        metadata.GetTrackInfo(TrackInfo::kVideoTrack, 0);
    303    if (!!tests[test].mNumberVideoTracks) {
    304      ASSERT_TRUE(!!trackInfo.Ref());
    305      const VideoInfo* videoInfo = trackInfo.Ref()->GetAsVideoInfo();
    306      ASSERT_TRUE(!!videoInfo);
    307      EXPECT_TRUE(videoInfo->IsValid()) << tests[test].mFilename;
    308      EXPECT_TRUE(videoInfo->IsVideo()) << tests[test].mFilename;
    309      if (std::isinf(tests[test].mVideoDuration)) {
    310        ASSERT_TRUE(std::isinf(videoInfo->mDuration.ToSeconds()));
    311      } else {
    312        EXPECT_FLOAT_EQ(tests[test].mVideoDuration,
    313                        videoInfo->mDuration.ToSeconds())
    314            << tests[test].mFilename;
    315      }
    316      EXPECT_EQ(tests[test].mWidth, videoInfo->mDisplay.width)
    317          << tests[test].mFilename;
    318      EXPECT_EQ(tests[test].mHeight, videoInfo->mDisplay.height)
    319          << tests[test].mFilename;
    320 
    321      MP4Metadata::ResultAndIndice indices =
    322          metadata.GetTrackIndice(videoInfo->mTrackId);
    323      EXPECT_EQ(!!indices.Ref(), tests[test].mHasVideoIndice)
    324          << tests[test].mFilename;
    325      if (tests[test].mHasVideoIndice) {
    326        for (size_t i = 0; i < indices.Ref()->Length(); i++) {
    327          MP4SampleIndex::Indice data;
    328          EXPECT_TRUE(indices.Ref()->GetIndice(i, data))
    329              << tests[test].mFilename;
    330          EXPECT_TRUE(data.start_offset <= data.end_offset)
    331              << tests[test].mFilename;
    332          EXPECT_TRUE(data.start_composition <= data.end_composition)
    333              << tests[test].mFilename;
    334        }
    335      }
    336    }
    337    trackInfo = metadata.GetTrackInfo(TrackInfo::kAudioTrack, 0);
    338    if (tests[test].mNumberAudioTracks == 0 ||
    339        tests[test].mNumberAudioTracks == E) {
    340      EXPECT_TRUE(!trackInfo.Ref()) << tests[test].mFilename;
    341    } else {
    342      ASSERT_TRUE(!!trackInfo.Ref());
    343      const AudioInfo* audioInfo = trackInfo.Ref()->GetAsAudioInfo();
    344      ASSERT_TRUE(!!audioInfo);
    345      EXPECT_TRUE(audioInfo->IsValid()) << tests[test].mFilename;
    346      EXPECT_TRUE(audioInfo->IsAudio()) << tests[test].mFilename;
    347      if (std::isinf(tests[test].mAudioDuration)) {
    348        ASSERT_TRUE(std::isinf(audioInfo->mDuration.ToSeconds()))
    349        << tests[test].mFilename;
    350      } else {
    351        EXPECT_FLOAT_EQ(tests[test].mAudioDuration,
    352                        audioInfo->mDuration.ToSeconds())
    353            << tests[test].mFilename;
    354      }
    355      EXPECT_EQ(tests[test].mAudioProfile, audioInfo->mProfile)
    356          << tests[test].mFilename;
    357 
    358      MP4Metadata::ResultAndIndice indices =
    359          metadata.GetTrackIndice(audioInfo->mTrackId);
    360      EXPECT_TRUE(!!indices.Ref()) << tests[test].mFilename;
    361      for (size_t i = 0; i < indices.Ref()->Length(); i++) {
    362        MP4SampleIndex::Indice data;
    363        EXPECT_TRUE(indices.Ref()->GetIndice(i, data)) << tests[test].mFilename;
    364        EXPECT_TRUE(data.start_offset <= data.end_offset)
    365            << tests[test].mFilename;
    366        EXPECT_TRUE(int64_t(data.start_composition) <=
    367                    int64_t(data.end_composition))
    368            << tests[test].mFilename;
    369      }
    370    }
    371    EXPECT_FALSE(metadata.GetTrackInfo(TrackInfo::kTextTrack, 0).Ref())
    372        << tests[test].mFilename;
    373    // We can see anywhere in any MPEG4.
    374    EXPECT_TRUE(metadata.CanSeek()) << tests[test].mFilename;
    375    EXPECT_EQ(tests[test].mHasCrypto, metadata.Crypto().Ref()->valid)
    376        << tests[test].mFilename;
    377  }
    378 }
    379 
    380 // This test was disabled by Bug 1224019 for producing way too much output.
    381 // This test no longer produces such output, as we've moved away from
    382 // stagefright, but it does take a long time to run. I can be useful to enable
    383 // as a sanity check on changes to the parser, but is too taxing to run as part
    384 // of normal test execution.
    385 #if 0
    386 TEST(MP4Metadata, test_case_mp4_subsets) {
    387  static const size_t step = 1u;
    388  for (size_t test = 0; test < std::size(testFiles); ++test) {
    389    nsTArray<uint8_t> buffer = ReadTestFile(testFiles[test].mFilename);
    390    ASSERT_FALSE(buffer.IsEmpty());
    391    ASSERT_LE(step, buffer.Length());
    392    // Just exercizing the parser starting at different points through the file,
    393    // making sure it doesn't crash.
    394    // No checks because results would differ for each position.
    395    for (size_t offset = 0; offset < buffer.Length() - step; offset += step) {
    396      size_t size = buffer.Length() - offset;
    397      while (size > 0) {
    398        RefPtr<TestStream> stream =
    399          new TestStream(buffer.Elements() + offset, size);
    400 
    401        MP4Metadata::ResultAndByteBuffer metadataBuffer =
    402          MP4Metadata::Metadata(stream);
    403        MP4Metadata metadata(stream);
    404 
    405        if (stream->mHighestSuccessfulEndOffset <= 0) {
    406          // No successful reads -> Cutting down the size won't change anything.
    407          break;
    408        }
    409        if (stream->mHighestSuccessfulEndOffset < size) {
    410          // Read up to a point before the end -> Resize down to that point.
    411          size = stream->mHighestSuccessfulEndOffset;
    412        } else {
    413          // Read up to the end (or after?!) -> Just cut 1 byte.
    414          size -= 1;
    415        }
    416      }
    417    }
    418  }
    419 }
    420 #endif
    421 
    422 #if !defined(XP_WIN) || !defined(MOZ_ASAN)  // OOMs on Windows ASan
    423 TEST(MoofParser, test_case_mp4)
    424 {
    425  const TestFileData* tests = nullptr;
    426  size_t length = 0;
    427 
    428  tests = testFiles;
    429  length = std::size(testFiles);
    430 
    431  for (size_t test = 0; test < length; ++test) {
    432    nsTArray<uint8_t> buffer = ReadTestFile(tests[test].mFilename);
    433    ASSERT_FALSE(buffer.IsEmpty());
    434    RefPtr<ByteStream> stream =
    435        new TestStream(buffer.Elements(), buffer.Length());
    436 
    437    MoofParser parser(stream, AsVariant(ParseAllTracks{}), false);
    438    EXPECT_EQ(0u, parser.mOffset) << tests[test].mFilename;
    439    EXPECT_FALSE(parser.ReachedEnd()) << tests[test].mFilename;
    440    EXPECT_TRUE(parser.mInitRange.IsEmpty()) << tests[test].mFilename;
    441 
    442    RefPtr<MediaByteBuffer> metadataBuffer = parser.Metadata();
    443    EXPECT_TRUE(metadataBuffer) << tests[test].mFilename;
    444 
    445    EXPECT_FALSE(parser.mInitRange.IsEmpty()) << tests[test].mFilename;
    446    const MediaByteRangeSet byteRanges(
    447        MediaByteRange(0, int64_t(buffer.Length())));
    448    EXPECT_EQ(tests[test].mValidMoofForAllTracks,
    449              parser.RebuildFragmentedIndex(byteRanges))
    450        << tests[test].mFilename;
    451    if (tests[test].mParsedOffset == 0) {
    452      EXPECT_EQ(buffer.Length(), parser.mOffset) << tests[test].mFilename;
    453      EXPECT_TRUE(parser.ReachedEnd()) << tests[test].mFilename;
    454    } else {
    455      EXPECT_EQ(tests[test].mParsedOffset, parser.mOffset)
    456          << tests[test].mFilename;
    457      EXPECT_FALSE(parser.ReachedEnd()) << tests[test].mFilename;
    458    }
    459 
    460    EXPECT_FALSE(parser.mInitRange.IsEmpty()) << tests[test].mFilename;
    461    EXPECT_TRUE(parser.GetCompositionRange(byteRanges).IsNull())
    462        << tests[test].mFilename;
    463    EXPECT_TRUE(parser.FirstCompleteMediaSegment().IsEmpty())
    464        << tests[test].mFilename;
    465    // If we expect a valid moof we should have that moof's range stored.
    466    EXPECT_EQ(tests[test].mValidMoofForAllTracks,
    467              !parser.FirstCompleteMediaHeader().IsEmpty())
    468        << tests[test].mFilename;
    469  }
    470 }
    471 
    472 TEST(MoofParser, test_case_sample_description_entries)
    473 {
    474  const TestFileData* tests = testFiles;
    475  size_t length = std::size(testFiles);
    476 
    477  for (size_t test = 0; test < length; ++test) {
    478    nsTArray<uint8_t> buffer = ReadTestFile(tests[test].mFilename);
    479    ASSERT_FALSE(buffer.IsEmpty());
    480    RefPtr<ByteStream> stream =
    481        new TestStream(buffer.Elements(), buffer.Length());
    482 
    483    // Parse the first track. Treating it as audio is hacky, but this doesn't
    484    // affect how we read the sample description entries.
    485    uint32_t trackNumber = 1;
    486    MoofParser parser(stream, AsVariant(trackNumber), false);
    487    EXPECT_EQ(0u, parser.mOffset) << tests[test].mFilename;
    488    EXPECT_FALSE(parser.ReachedEnd()) << tests[test].mFilename;
    489    EXPECT_TRUE(parser.mInitRange.IsEmpty()) << tests[test].mFilename;
    490 
    491    // Explicitly don't call parser.Metadata() so that the parser itself will
    492    // read the metadata as if we're in a fragmented case. Otherwise the parser
    493    // won't read the sample description table.
    494 
    495    const MediaByteRangeSet byteRanges(
    496        MediaByteRange(0, int64_t(buffer.Length())));
    497    EXPECT_EQ(tests[test].mValidMoofForTrack1,
    498              parser.RebuildFragmentedIndex(byteRanges))
    499        << tests[test].mFilename;
    500 
    501    // We only care about crypto data from the samples descriptions right now.
    502    // This test should be expanded should we read further information.
    503    if (tests[test].mHasCrypto) {
    504      uint32_t numEncryptedEntries = 0;
    505      // It's possible to have multiple sample description entries. Bug
    506      // 1714626 tracks more robust handling of multiple entries, for now just
    507      // check that we have at least one.
    508      for (SampleDescriptionEntry entry : parser.mSampleDescriptions) {
    509        if (entry.mIsEncryptedEntry) {
    510          numEncryptedEntries++;
    511        }
    512      }
    513      EXPECT_GE(numEncryptedEntries, 1u) << tests[test].mFilename;
    514    }
    515  }
    516 }
    517 #endif  // !defined(XP_WIN) || !defined(MOZ_ASAN)
    518 
    519 // We should gracefully handle track_id 0 since Bug 1519617. We'd previously
    520 // used id 0 to trigger special handling in the MoofParser to read multiple
    521 // track metadata, but since muxers use track id 0 in the wild, we want to
    522 // make sure they can't accidentally trigger such handling.
    523 TEST(MoofParser, test_case_track_id_0_does_not_read_multitracks)
    524 {
    525  const char* zeroTrackIdFileName =
    526      "test_case_1519617-video-has-track_id-0.mp4";
    527  nsTArray<uint8_t> buffer = ReadTestFile(zeroTrackIdFileName);
    528 
    529  ASSERT_FALSE(buffer.IsEmpty());
    530  RefPtr<ByteStream> stream =
    531      new TestStream(buffer.Elements(), buffer.Length());
    532 
    533  // Parse track id 0. We expect to only get metadata from that track, not the
    534  // other track with id 2.
    535  const uint32_t videoTrackId = 0;
    536  MoofParser parser(stream, AsVariant(videoTrackId), false);
    537 
    538  // Explicitly don't call parser.Metadata() so that the parser itself will
    539  // read the metadata as if we're in a fragmented case. Otherwise we won't
    540  // read the trak data.
    541 
    542  const MediaByteRangeSet byteRanges(
    543      MediaByteRange(0, int64_t(buffer.Length())));
    544  EXPECT_TRUE(parser.RebuildFragmentedIndex(byteRanges))
    545      << "MoofParser should find a valid moof as the file contains one!";
    546 
    547  // Verify we only have data from track 0, if we parsed multiple tracks we'd
    548  // find some of the audio track metadata here. Only check for values that
    549  // differ between tracks.
    550  const uint32_t videoTimescale = 90000;
    551  const uint32_t videoSampleDuration = 3000;
    552  const uint32_t videoSampleFlags = 0x10000;
    553  const uint32_t videoNumSampleDescriptionEntries = 1;
    554  EXPECT_EQ(videoTimescale, parser.mMdhd.mTimescale)
    555      << "Wrong timescale for video track! If value is 22050, we've read from "
    556         "the audio track!";
    557  EXPECT_EQ(videoTrackId, parser.mTrex.mTrackId)
    558      << "Wrong track id for video track! If value is 2, we've read from the "
    559         "audio track!";
    560  EXPECT_EQ(videoSampleDuration, parser.mTrex.mDefaultSampleDuration)
    561      << "Wrong sample duration for video track! If value is 1024, we've read "
    562         "from the audio track!";
    563  EXPECT_EQ(videoSampleFlags, parser.mTrex.mDefaultSampleFlags)
    564      << "Wrong sample flags for video track! If value is 0x2000000 (note "
    565         "that's hex), we've read from the audio track!";
    566  EXPECT_EQ(videoNumSampleDescriptionEntries,
    567            parser.mSampleDescriptions.Length())
    568      << "Wrong number of sample descriptions for video track! If value is 2, "
    569         "then we've read sample description information from video and audio "
    570         "tracks!";
    571 }
    572 
    573 // We should gracefully handle track_id 0 since Bug 1519617. This includes
    574 // handling crypto data from the sinf box in the MoofParser. Note, as of the
    575 // time of writing, MP4Metadata uses the presence of a pssh box to determine
    576 // if its crypto member is valid. However, even on files where the pssh isn't
    577 // in the init segment, the MoofParser should still read the sinf, as in this
    578 // testcase.
    579 TEST(MoofParser, test_case_track_id_0_reads_crypto_metadata)
    580 {
    581  const char* zeroTrackIdFileName =
    582      "test_case_1519617-cenc-init-with-track_id-0.mp4";
    583  nsTArray<uint8_t> buffer = ReadTestFile(zeroTrackIdFileName);
    584 
    585  ASSERT_FALSE(buffer.IsEmpty());
    586  RefPtr<ByteStream> stream =
    587      new TestStream(buffer.Elements(), buffer.Length());
    588 
    589  // Parse track id 0. We expect to only get metadata from that track, not the
    590  // other track with id 2.
    591  const uint32_t videoTrackId = 0;
    592  MoofParser parser(stream, AsVariant(videoTrackId), false);
    593 
    594  // Explicitly don't call parser.Metadata() so that the parser itself will
    595  // read the metadata as if we're in a fragmented case. Otherwise we won't
    596  // read the trak data.
    597 
    598  const MediaByteRangeSet byteRanges(
    599      MediaByteRange(0, int64_t(buffer.Length())));
    600  EXPECT_FALSE(parser.RebuildFragmentedIndex(byteRanges))
    601      << "MoofParser should not find a valid moof, this is just an init "
    602         "segment!";
    603 
    604  // Verify we only have data from track 0, if we parsed multiple tracks we'd
    605  // find some of the audio track metadata here. Only check for values that
    606  // differ between tracks.
    607  const size_t numSampleDescriptionEntries = 1;
    608  const uint32_t defaultPerSampleIVSize = 8;
    609  const size_t keyIdLength = 16;
    610  const uint32_t defaultKeyId[keyIdLength] = {
    611      0x43, 0xbe, 0x13, 0xd0, 0x26, 0xc9, 0x41, 0x54,
    612      0x8f, 0xed, 0xf9, 0x54, 0x1a, 0xef, 0x6b, 0x0e};
    613  EXPECT_TRUE(parser.mSinf.IsValid())
    614      << "Should have a sinf that has crypto data!";
    615  EXPECT_EQ(defaultPerSampleIVSize, parser.mSinf.mDefaultIVSize)
    616      << "Wrong default per sample IV size for track! If 0 indicates we failed "
    617         "to parse some crypto info!";
    618  for (size_t i = 0; i < keyIdLength; i++) {
    619    EXPECT_EQ(defaultKeyId[i], parser.mSinf.mDefaultKeyID[i])
    620        << "Mismatched default key ID byte at index " << i
    621        << " indicates we failed to parse some crypto info!";
    622  }
    623  ASSERT_EQ(numSampleDescriptionEntries, parser.mSampleDescriptions.Length())
    624      << "Wrong number of sample descriptions for track! If 0, indicates we "
    625         "failed to parse some expected crypto!";
    626  EXPECT_TRUE(parser.mSampleDescriptions[0].mIsEncryptedEntry)
    627      << "Sample description should be marked as encrypted!";
    628 }
    629 
    630 // The MoofParser may be asked to parse metadata for multiple tracks, but then
    631 // be presented with fragments/moofs that contain data for only a subset of
    632 // those tracks. I.e. metadata contains information for tracks with ids 1 and 2,
    633 // but then the moof parser only receives moofs with data for track id 1. We
    634 // should parse such fragmented media. In this test the metadata contains info
    635 // for track ids 1 and 2, but track 2's track fragment headers (traf) have been
    636 // over written with free space boxes (free).
    637 TEST(MoofParser, test_case_moofs_missing_trafs)
    638 {
    639  const char* noTrafsForTrack2MoofsFileName =
    640      "test_case_1519617-track2-trafs-removed.mp4";
    641  nsTArray<uint8_t> buffer = ReadTestFile(noTrafsForTrack2MoofsFileName);
    642 
    643  ASSERT_FALSE(buffer.IsEmpty());
    644  RefPtr<ByteStream> stream =
    645      new TestStream(buffer.Elements(), buffer.Length());
    646 
    647  // Create parser that will read metadata from all tracks.
    648  MoofParser parser(stream, AsVariant(ParseAllTracks{}), false);
    649 
    650  // Explicitly don't call parser.Metadata() so that the parser itself will
    651  // read the metadata as if we're in a fragmented case. Otherwise we won't
    652  // read the trak data.
    653 
    654  const MediaByteRangeSet byteRanges(
    655      MediaByteRange(0, int64_t(buffer.Length())));
    656  EXPECT_TRUE(parser.RebuildFragmentedIndex(byteRanges))
    657      << "MoofParser should find a valid moof, there's 2 in the file!";
    658 
    659  // Verify we've found 2 moofs and that the parser was able to parse them.
    660  const size_t numMoofs = 2;
    661  EXPECT_EQ(numMoofs, parser.Moofs().Length())
    662      << "File has 2 moofs, we should have read both";
    663  for (size_t i = 0; i < parser.Moofs().Length(); i++) {
    664    EXPECT_TRUE(parser.Moofs()[i].IsValid()) << "All moofs should be valid";
    665  }
    666 }
    667 
    668 // This test was disabled by Bug 1224019 for producing way too much output.
    669 // This test no longer produces such output, as we've moved away from
    670 // stagefright, but it does take a long time to run. I can be useful to enable
    671 // as a sanity check on changes to the parser, but is too taxing to run as part
    672 // of normal test execution.
    673 #if 0
    674 TEST(MoofParser, test_case_mp4_subsets) {
    675  const size_t step = 1u;
    676  for (size_t test = 0; test < std::size(testFiles); ++test) {
    677    nsTArray<uint8_t> buffer = ReadTestFile(testFiles[test].mFilename);
    678    ASSERT_FALSE(buffer.IsEmpty());
    679    ASSERT_LE(step, buffer.Length());
    680    // Just exercizing the parser starting at different points through the file,
    681    // making sure it doesn't crash.
    682    // No checks because results would differ for each position.
    683    for (size_t offset = 0; offset < buffer.Length() - step; offset += step) {
    684      size_t size = buffer.Length() - offset;
    685      while (size > 0) {
    686        RefPtr<TestStream> stream =
    687          new TestStream(buffer.Elements() + offset, size);
    688 
    689        MoofParser parser(stream, AsVariant(ParseAllTracks{}), false);
    690        MediaByteRangeSet byteRanges;
    691        EXPECT_FALSE(parser.RebuildFragmentedIndex(byteRanges));
    692        parser.GetCompositionRange(byteRanges);
    693        RefPtr<MediaByteBuffer> metadataBuffer = parser.Metadata();
    694        parser.FirstCompleteMediaSegment();
    695        parser.FirstCompleteMediaHeader();
    696 
    697        if (stream->mHighestSuccessfulEndOffset <= 0) {
    698          // No successful reads -> Cutting down the size won't change anything.
    699          break;
    700        }
    701        if (stream->mHighestSuccessfulEndOffset < size) {
    702          // Read up to a point before the end -> Resize down to that point.
    703          size = stream->mHighestSuccessfulEndOffset;
    704        } else {
    705          // Read up to the end (or after?!) -> Just cut 1 byte.
    706          size -= 1;
    707        }
    708      }
    709    }
    710  }
    711 }
    712 #endif
    713 
    714 uint8_t media_gtest_video_init_mp4[] = {
    715    0x00, 0x00, 0x00, 0x18, 0x66, 0x74, 0x79, 0x70, 0x69, 0x73, 0x6f, 0x6d,
    716    0x00, 0x00, 0x00, 0x01, 0x69, 0x73, 0x6f, 0x6d, 0x61, 0x76, 0x63, 0x31,
    717    0x00, 0x00, 0x02, 0xd1, 0x6d, 0x6f, 0x6f, 0x76, 0x00, 0x00, 0x00, 0x6c,
    718    0x6d, 0x76, 0x68, 0x64, 0x00, 0x00, 0x00, 0x00, 0xc8, 0x49, 0x73, 0xf8,
    719    0xc8, 0x4a, 0xc5, 0x7a, 0x00, 0x00, 0x02, 0x58, 0x00, 0x00, 0x00, 0x00,
    720    0x00, 0x01, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    721    0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    722    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00,
    723    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    724    0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    725    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    726    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x18,
    727    0x69, 0x6f, 0x64, 0x73, 0x00, 0x00, 0x00, 0x00, 0x10, 0x80, 0x80, 0x80,
    728    0x07, 0x00, 0x4f, 0xff, 0xff, 0x29, 0x15, 0xff, 0x00, 0x00, 0x02, 0x0d,
    729    0x74, 0x72, 0x61, 0x6b, 0x00, 0x00, 0x00, 0x5c, 0x74, 0x6b, 0x68, 0x64,
    730    0x00, 0x00, 0x00, 0x01, 0xc8, 0x49, 0x73, 0xf8, 0xc8, 0x49, 0x73, 0xf9,
    731    0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    732    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    733    0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    734    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00,
    735    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    736    0x40, 0x00, 0x00, 0x00, 0x02, 0x80, 0x00, 0x00, 0x01, 0x68, 0x00, 0x00,
    737    0x00, 0x00, 0x01, 0xa9, 0x6d, 0x64, 0x69, 0x61, 0x00, 0x00, 0x00, 0x20,
    738    0x6d, 0x64, 0x68, 0x64, 0x00, 0x00, 0x00, 0x00, 0xc8, 0x49, 0x73, 0xf8,
    739    0xc8, 0x49, 0x73, 0xf9, 0x00, 0x00, 0x75, 0x30, 0x00, 0x00, 0x00, 0x00,
    740    0x55, 0xc4, 0x00, 0x00, 0x00, 0x00, 0x00, 0x38, 0x68, 0x64, 0x6c, 0x72,
    741    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x76, 0x69, 0x64, 0x65,
    742    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    743    0x47, 0x50, 0x41, 0x43, 0x20, 0x49, 0x53, 0x4f, 0x20, 0x56, 0x69, 0x64,
    744    0x65, 0x6f, 0x20, 0x48, 0x61, 0x6e, 0x64, 0x6c, 0x65, 0x72, 0x00, 0x00,
    745    0x00, 0x00, 0x01, 0x49, 0x6d, 0x69, 0x6e, 0x66, 0x00, 0x00, 0x00, 0x14,
    746    0x76, 0x6d, 0x68, 0x64, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00,
    747    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x24, 0x64, 0x69, 0x6e, 0x66,
    748    0x00, 0x00, 0x00, 0x1c, 0x64, 0x72, 0x65, 0x66, 0x00, 0x00, 0x00, 0x00,
    749    0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x0c, 0x75, 0x72, 0x6c, 0x20,
    750    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x09, 0x73, 0x74, 0x62, 0x6c,
    751    0x00, 0x00, 0x00, 0xad, 0x73, 0x74, 0x73, 0x64, 0x00, 0x00, 0x00, 0x00,
    752    0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x9d, 0x61, 0x76, 0x63, 0x31,
    753    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00,
    754    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    755    0x02, 0x80, 0x01, 0x68, 0x00, 0x48, 0x00, 0x00, 0x00, 0x48, 0x00, 0x00,
    756    0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    757    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    758    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    759    0x00, 0x00, 0x00, 0x18, 0xff, 0xff, 0x00, 0x00, 0x00, 0x33, 0x61, 0x76,
    760    0x63, 0x43, 0x01, 0x64, 0x00, 0x1f, 0xff, 0xe1, 0x00, 0x1b, 0x67, 0x64,
    761    0x00, 0x1f, 0xac, 0x2c, 0xc5, 0x02, 0x80, 0xbf, 0xe5, 0xc0, 0x44, 0x00,
    762    0x00, 0x03, 0x00, 0x04, 0x00, 0x00, 0x03, 0x00, 0xf2, 0x3c, 0x60, 0xc6,
    763    0x58, 0x01, 0x00, 0x05, 0x68, 0xe9, 0x2b, 0x2c, 0x8b, 0x00, 0x00, 0x00,
    764    0x14, 0x62, 0x74, 0x72, 0x74, 0x00, 0x01, 0x5a, 0xc2, 0x00, 0x24, 0x74,
    765    0x38, 0x00, 0x09, 0x22, 0x00, 0x00, 0x00, 0x00, 0x10, 0x73, 0x74, 0x74,
    766    0x73, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    767    0x10, 0x63, 0x74, 0x74, 0x73, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    768    0x00, 0x00, 0x00, 0x00, 0x10, 0x73, 0x74, 0x73, 0x63, 0x00, 0x00, 0x00,
    769    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x14, 0x73, 0x74, 0x73,
    770    0x7a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    771    0x00, 0x00, 0x00, 0x00, 0x10, 0x73, 0x74, 0x63, 0x6f, 0x00, 0x00, 0x00,
    772    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x38, 0x6d, 0x76, 0x65,
    773    0x78, 0x00, 0x00, 0x00, 0x10, 0x6d, 0x65, 0x68, 0x64, 0x00, 0x00, 0x00,
    774    0x00, 0x00, 0x05, 0x76, 0x18, 0x00, 0x00, 0x00, 0x20, 0x74, 0x72, 0x65,
    775    0x78, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
    776    0x01, 0x00, 0x00, 0x03, 0xe8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00,
    777    0x00};
    778 
    779 const uint32_t media_gtest_video_init_mp4_len = 745;
    780 
    781 TEST(MP4Metadata, EmptyCTTS)
    782 {
    783  RefPtr<MediaByteBuffer> buffer =
    784      new MediaByteBuffer(media_gtest_video_init_mp4_len);
    785  buffer->AppendElements(media_gtest_video_init_mp4,
    786                         media_gtest_video_init_mp4_len);
    787  RefPtr<BufferStream> stream = new BufferStream(buffer);
    788 
    789  MP4Metadata::ResultAndByteBuffer metadataBuffer =
    790      MP4Metadata::Metadata(stream);
    791  EXPECT_EQ(NS_OK, metadataBuffer.Result());
    792  EXPECT_TRUE(metadataBuffer.Ref());
    793 
    794  MP4Metadata metadata(stream);
    795  EXPECT_EQ(metadata.Parse(), NS_OK);
    796  EXPECT_EQ(1u, metadata.GetNumberTracks(TrackInfo::kVideoTrack).Ref());
    797  MP4Metadata::ResultAndTrackInfo track =
    798      metadata.GetTrackInfo(TrackInfo::kVideoTrack, 0);
    799  EXPECT_TRUE(track.Ref() != nullptr);
    800  // We can seek anywhere in any MPEG4.
    801  EXPECT_TRUE(metadata.CanSeek());
    802  EXPECT_FALSE(metadata.Crypto().Ref()->valid);
    803 }
    804 
    805 // Fixture so we test telemetry probes.
    806 class MP4MetadataTelemetryFixture : public TelemetryTestFixture {};
    807 
    808 TEST_F(MP4MetadataTelemetryFixture, Telemetry) {
    809  // Helper to fetch the metadata from a file and send telemetry in the process.
    810  auto UpdateMetadataAndHistograms = [](const char* testFileName) {
    811    nsTArray<uint8_t> buffer = ReadTestFile(testFileName);
    812    ASSERT_FALSE(buffer.IsEmpty());
    813    RefPtr<ByteStream> stream =
    814        new TestStream(buffer.Elements(), buffer.Length());
    815 
    816    MP4Metadata::ResultAndByteBuffer metadataBuffer =
    817        MP4Metadata::Metadata(stream);
    818    EXPECT_EQ(NS_OK, metadataBuffer.Result());
    819    EXPECT_TRUE(metadataBuffer.Ref());
    820 
    821    MP4Metadata metadata(stream);
    822    nsresult res = metadata.Parse();
    823    EXPECT_NS_SUCCEEDED(res);
    824    auto audioTrackCount = metadata.GetNumberTracks(TrackInfo::kAudioTrack);
    825    ASSERT_NE(audioTrackCount.Ref(), MP4Metadata::NumberTracksError());
    826    auto videoTrackCount = metadata.GetNumberTracks(TrackInfo::kVideoTrack);
    827    ASSERT_NE(videoTrackCount.Ref(), MP4Metadata::NumberTracksError());
    828 
    829    // Need to read the track data to get telemetry to fire.
    830    for (uint32_t i = 0; i < audioTrackCount.Ref(); i++) {
    831      metadata.GetTrackInfo(TrackInfo::kAudioTrack, i);
    832    }
    833    for (uint32_t i = 0; i < videoTrackCount.Ref(); i++) {
    834      metadata.GetTrackInfo(TrackInfo::kVideoTrack, i);
    835    }
    836  };
    837 
    838  AutoJSContextWithGlobal cx(mCleanGlobal);
    839 
    840  // Checks the current state of the histograms relating to sample description
    841  // entries and verifies they're in an expected state.
    842  // aExpectedMultipleCodecCounts is a tuple where the first value represents
    843  // the number of expected 'false' count, and the second the expected 'true'
    844  // count for the sample description entries have multiple codecs histogram.
    845  // aExpectedMultipleCryptoCounts is the same, but for the sample description
    846  // entires have multiple crypto histogram.
    847  // aExpectedSampleDescriptionEntryCounts is a tuple with 6 values, each is
    848  // the expected number of sample description seen. I.e, the first value in the
    849  // tuple is the number of tracks we've seen with 0 sample descriptions, the
    850  // second value with 1 sample description, and so on up to 5 sample
    851  // descriptions. aFileName is the name of the most recent file we've parsed,
    852  // and is used to log if our telem counts are not in an expected state.
    853  auto CheckHistograms =
    854      [this, &cx](
    855          const std::tuple<uint32_t, uint32_t>& aExpectedMultipleCodecCounts,
    856          const std::tuple<uint32_t, uint32_t>& aExpectedMultipleCryptoCounts,
    857          const std::tuple<uint32_t, uint32_t, uint32_t, uint32_t, uint32_t,
    858                           uint32_t>& aExpectedSampleDescriptionEntryCounts,
    859          const char* aFileName) {
    860        // Get a snapshot of the current histograms
    861        JS::Rooted<JS::Value> snapshot(cx.GetJSContext());
    862        TelemetryTestHelpers::GetSnapshots(cx.GetJSContext(), mTelemetry,
    863                                           "" /* this string is unused */,
    864                                           &snapshot, false /* is_keyed */);
    865 
    866        // We'll use these to pull values out of the histograms.
    867        JS::Rooted<JS::Value> values(cx.GetJSContext());
    868        JS::Rooted<JS::Value> value(cx.GetJSContext());
    869 
    870        // Verify our multiple codecs count histogram.
    871        JS::Rooted<JS::Value> multipleCodecsHistogram(cx.GetJSContext());
    872        TelemetryTestHelpers::GetProperty(
    873            cx.GetJSContext(),
    874            "MEDIA_MP4_PARSE_SAMPLE_DESCRIPTION_ENTRIES_HAVE_MULTIPLE_CODECS",
    875            snapshot, &multipleCodecsHistogram);
    876        ASSERT_TRUE(multipleCodecsHistogram.isObject())
    877        << "Multiple codecs histogram should exist!";
    878 
    879        TelemetryTestHelpers::GetProperty(cx.GetJSContext(), "values",
    880                                          multipleCodecsHistogram, &values);
    881        // False count.
    882        TelemetryTestHelpers::GetElement(cx.GetJSContext(), 0, values, &value);
    883        uint32_t uValue = 0;
    884        JS::ToUint32(cx.GetJSContext(), value, &uValue);
    885        EXPECT_EQ(std::get<0>(aExpectedMultipleCodecCounts), uValue)
    886            << "Unexpected number of false multiple codecs after parsing "
    887            << aFileName;
    888        // True count.
    889        TelemetryTestHelpers::GetElement(cx.GetJSContext(), 1, values, &value);
    890        JS::ToUint32(cx.GetJSContext(), value, &uValue);
    891        EXPECT_EQ(std::get<1>(aExpectedMultipleCodecCounts), uValue)
    892            << "Unexpected number of true multiple codecs after parsing "
    893            << aFileName;
    894 
    895        // Verify our multiple crypto count histogram.
    896        JS::Rooted<JS::Value> multipleCryptoHistogram(cx.GetJSContext());
    897        TelemetryTestHelpers::GetProperty(
    898            cx.GetJSContext(),
    899            "MEDIA_MP4_PARSE_SAMPLE_DESCRIPTION_ENTRIES_HAVE_MULTIPLE_CRYPTO",
    900            snapshot, &multipleCryptoHistogram);
    901        ASSERT_TRUE(multipleCryptoHistogram.isObject())
    902        << "Multiple crypto histogram should exist!";
    903 
    904        TelemetryTestHelpers::GetProperty(cx.GetJSContext(), "values",
    905                                          multipleCryptoHistogram, &values);
    906        // False count.
    907        TelemetryTestHelpers::GetElement(cx.GetJSContext(), 0, values, &value);
    908        JS::ToUint32(cx.GetJSContext(), value, &uValue);
    909        EXPECT_EQ(std::get<0>(aExpectedMultipleCryptoCounts), uValue)
    910            << "Unexpected number of false multiple cryptos after parsing "
    911            << aFileName;
    912        // True count.
    913        TelemetryTestHelpers::GetElement(cx.GetJSContext(), 1, values, &value);
    914        JS::ToUint32(cx.GetJSContext(), value, &uValue);
    915        EXPECT_EQ(std::get<1>(aExpectedMultipleCryptoCounts), uValue)
    916            << "Unexpected number of true multiple cryptos after parsing "
    917            << aFileName;
    918 
    919        // Verify our sample description entry count histogram.
    920        JS::Rooted<JS::Value> numSamplesHistogram(cx.GetJSContext());
    921        TelemetryTestHelpers::GetProperty(
    922            cx.GetJSContext(), "MEDIA_MP4_PARSE_NUM_SAMPLE_DESCRIPTION_ENTRIES",
    923            snapshot, &numSamplesHistogram);
    924        ASSERT_TRUE(numSamplesHistogram.isObject())
    925        << "Num sample description entries histogram should exist!";
    926 
    927        TelemetryTestHelpers::GetProperty(cx.GetJSContext(), "values",
    928                                          numSamplesHistogram, &values);
    929 
    930        TelemetryTestHelpers::GetElement(cx.GetJSContext(), 0, values, &value);
    931        JS::ToUint32(cx.GetJSContext(), value, &uValue);
    932        EXPECT_EQ(std::get<0>(aExpectedSampleDescriptionEntryCounts), uValue)
    933            << "Unexpected number of 0 sample entry descriptions after parsing "
    934            << aFileName;
    935        TelemetryTestHelpers::GetElement(cx.GetJSContext(), 1, values, &value);
    936        JS::ToUint32(cx.GetJSContext(), value, &uValue);
    937        EXPECT_EQ(std::get<1>(aExpectedSampleDescriptionEntryCounts), uValue)
    938            << "Unexpected number of 1 sample entry descriptions after parsing "
    939            << aFileName;
    940        TelemetryTestHelpers::GetElement(cx.GetJSContext(), 2, values, &value);
    941        JS::ToUint32(cx.GetJSContext(), value, &uValue);
    942        EXPECT_EQ(std::get<2>(aExpectedSampleDescriptionEntryCounts), uValue)
    943            << "Unexpected number of 2 sample entry descriptions after parsing "
    944            << aFileName;
    945        TelemetryTestHelpers::GetElement(cx.GetJSContext(), 3, values, &value);
    946        JS::ToUint32(cx.GetJSContext(), value, &uValue);
    947        EXPECT_EQ(std::get<3>(aExpectedSampleDescriptionEntryCounts), uValue)
    948            << "Unexpected number of 3 sample entry descriptions after parsing "
    949            << aFileName;
    950        TelemetryTestHelpers::GetElement(cx.GetJSContext(), 4, values, &value);
    951        JS::ToUint32(cx.GetJSContext(), value, &uValue);
    952        EXPECT_EQ(std::get<4>(aExpectedSampleDescriptionEntryCounts), uValue)
    953            << "Unexpected number of 4 sample entry descriptions after parsing "
    954            << aFileName;
    955        TelemetryTestHelpers::GetElement(cx.GetJSContext(), 5, values, &value);
    956        JS::ToUint32(cx.GetJSContext(), value, &uValue);
    957        EXPECT_EQ(std::get<5>(aExpectedSampleDescriptionEntryCounts), uValue)
    958            << "Unexpected number of 5 sample entry descriptions after parsing "
    959            << aFileName;
    960      };
    961 
    962  // Clear histograms
    963  TelemetryTestHelpers::GetAndClearHistogram(
    964      cx.GetJSContext(), mTelemetry,
    965      nsLiteralCString(
    966          "MEDIA_MP4_PARSE_SAMPLE_DESCRIPTION_ENTRIES_HAVE_MULTIPLE_CODECS"),
    967      false /* is_keyed */);
    968 
    969  TelemetryTestHelpers::GetAndClearHistogram(
    970      cx.GetJSContext(), mTelemetry,
    971      nsLiteralCString(
    972          "MEDIA_MP4_PARSE_SAMPLE_DESCRIPTION_ENTRIES_HAVE_MULTIPLE_CRYPTO"),
    973      false /* is_keyed */);
    974 
    975  TelemetryTestHelpers::GetAndClearHistogram(
    976      cx.GetJSContext(), mTelemetry,
    977      "MEDIA_MP4_PARSE_NUM_SAMPLE_DESCRIPTION_ENTRIES"_ns,
    978      false /* is_keyed */);
    979 
    980  // The snapshot won't have any data in it until we populate our histograms, so
    981  // we don't check for a baseline here. Just read out first MP4 metadata.
    982 
    983  // Grab one of the test cases we know should parse and parse it, this should
    984  // trigger telemetry gathering.
    985 
    986  // This file contains 2 moovs, each with a video and audio track with one
    987  // sample description entry. So we should see 4 tracks, each with a single
    988  // codec, no crypto, and a single sample description entry.
    989  UpdateMetadataAndHistograms("test_case_1185230.mp4");
    990 
    991  // Verify our histograms are updated.
    992  CheckHistograms(std::make_tuple<uint32_t, uint32_t>(4, 0),
    993                  std::make_tuple<uint32_t, uint32_t>(4, 0),
    994                  std::make_tuple<uint32_t, uint32_t, uint32_t, uint32_t,
    995                                  uint32_t, uint32_t>(0, 4, 0, 0, 0, 0),
    996                  "test_case_1185230.mp4");
    997 
    998  // Parse another test case. This one has a single moov with a single video
    999  // track. However, the track has two sample description entries, and our
   1000  // updated telemetry should reflect that.
   1001  UpdateMetadataAndHistograms(
   1002      "test_case_1513651-2-sample-description-entries.mp4");
   1003 
   1004  // Verify our histograms are updated.
   1005  CheckHistograms(std::make_tuple<uint32_t, uint32_t>(5, 0),
   1006                  std::make_tuple<uint32_t, uint32_t>(5, 0),
   1007                  std::make_tuple<uint32_t, uint32_t, uint32_t, uint32_t,
   1008                                  uint32_t, uint32_t>(0, 4, 1, 0, 0, 0),
   1009                  "test_case_1513651-2-sample-description-entries.mp4");
   1010 
   1011  // Parse another test case. This one has 2 sample decription entries, both
   1012  // with crypto information, which should be reflected in our telemetry.
   1013  UpdateMetadataAndHistograms(
   1014      "test_case_1714125-2-sample-description-entires-with-identical-crypto."
   1015      "mp4");
   1016 
   1017  // Verify our histograms are updated.
   1018  CheckHistograms(
   1019      std::make_tuple<uint32_t, uint32_t>(6, 0),
   1020      std::make_tuple<uint32_t, uint32_t>(5, 1),
   1021      std::make_tuple<uint32_t, uint32_t, uint32_t, uint32_t, uint32_t,
   1022                      uint32_t>(0, 4, 2, 0, 0, 0),
   1023      "test_case_1714125-2-sample-description-entires-with-identical-crypto."
   1024      "mp4");
   1025 }