1 // Copyright (c) 2016 The WebM project authors. All Rights Reserved.
2 //
3 // Use of this source code is governed by a BSD-style license
4 // that can be found in the LICENSE file in the root of the source
5 // tree. An additional intellectual property rights grant can be found
6 // in the file PATENTS.  All contributing project authors may
7 // be found in the AUTHORS file in the root of the source tree.
8 #include "testing/test_util.h"
9 
10 #include <cstdint>
11 #include <cstdio>
12 #include <cstdlib>
13 #include <cstring>
14 #include <ios>
15 #include <string>
16 
17 #include "common/libwebm_util.h"
18 #include "common/webmids.h"
19 
20 #include "mkvparser/mkvparser.h"
21 #include "mkvparser/mkvreader.h"
22 
23 namespace test {
24 
GetTestDataDir()25 std::string GetTestDataDir() {
26   const char* test_data_path = std::getenv("LIBWEBM_TEST_DATA_PATH");
27   return test_data_path ? std::string(test_data_path) : std::string();
28 }
29 
GetTestFilePath(const std::string & name)30 std::string GetTestFilePath(const std::string& name) {
31   const std::string libwebm_testdata_dir = GetTestDataDir();
32   return libwebm_testdata_dir + "/" + name;
33 }
34 
CompareFiles(const std::string & file1,const std::string & file2)35 bool CompareFiles(const std::string& file1, const std::string& file2) {
36   const std::size_t kBlockSize = 4096;
37   std::uint8_t buf1[kBlockSize] = {0};
38   std::uint8_t buf2[kBlockSize] = {0};
39 
40   libwebm::FilePtr f1 =
41       libwebm::FilePtr(std::fopen(file1.c_str(), "rb"), libwebm::FILEDeleter());
42   libwebm::FilePtr f2 =
43       libwebm::FilePtr(std::fopen(file2.c_str(), "rb"), libwebm::FILEDeleter());
44 
45   if (!f1.get() || !f2.get()) {
46     // Files cannot match if one or both couldn't be opened.
47     return false;
48   }
49 
50   do {
51     const std::size_t r1 = std::fread(buf1, 1, kBlockSize, f1.get());
52     const std::size_t r2 = std::fread(buf2, 1, kBlockSize, f2.get());
53 
54     // TODO(fgalligan): Add output of which byte differs.
55     if (r1 != r2 || std::memcmp(buf1, buf2, r1)) {
56       return 0;  // Files are not equal
57     }
58   } while (!std::feof(f1.get()) && !std::feof(f2.get()));
59 
60   return std::feof(f1.get()) && std::feof(f2.get());
61 }
62 
HasCuePoints(const mkvparser::Segment * segment,std::int64_t * cues_offset)63 bool HasCuePoints(const mkvparser::Segment* segment,
64                   std::int64_t* cues_offset) {
65   if (!segment || !cues_offset) {
66     return false;
67   }
68   using mkvparser::SeekHead;
69   const SeekHead* const seek_head = segment->GetSeekHead();
70   if (!seek_head) {
71     return false;
72   }
73 
74   std::int64_t offset = 0;
75   for (int i = 0; i < seek_head->GetCount(); ++i) {
76     const SeekHead::Entry* const entry = seek_head->GetEntry(i);
77     if (entry->id == libwebm::kMkvCues) {
78       offset = entry->pos;
79     }
80   }
81 
82   if (offset <= 0) {
83     // No Cues found.
84     return false;
85   }
86 
87   *cues_offset = offset;
88   return true;
89 }
90 
ValidateCues(mkvparser::Segment * segment,mkvparser::IMkvReader * reader)91 bool ValidateCues(mkvparser::Segment* segment, mkvparser::IMkvReader* reader) {
92   if (!segment) {
93     return false;
94   }
95 
96   std::int64_t cues_offset = 0;
97   if (!HasCuePoints(segment, &cues_offset)) {
98     // No cues to validate, everything is OK.
99     return true;
100   }
101 
102   // Parse Cues.
103   long long cues_pos = 0;  // NOLINT
104   long cues_len = 0;  // NOLINT
105   if (segment->ParseCues(cues_offset, cues_pos, cues_len)) {
106     return false;
107   }
108 
109   // Get a pointer to the video track if it exists. Otherwise, we assume
110   // that Cues are based on the first track (which is true for all our test
111   // files).
112   const mkvparser::Tracks* const tracks = segment->GetTracks();
113   const mkvparser::Track* cues_track = tracks->GetTrackByIndex(0);
114   for (int i = 1; i < static_cast<int>(tracks->GetTracksCount()); ++i) {
115     const mkvparser::Track* const track = tracks->GetTrackByIndex(i);
116     if (track->GetType() == mkvparser::Track::kVideo) {
117       cues_track = track;
118       break;
119     }
120   }
121 
122   // Iterate through Cues and verify if they are pointing to the correct
123   // Cluster position.
124   const mkvparser::Cues* const cues = segment->GetCues();
125   const mkvparser::CuePoint* cue_point = NULL;
126   while (cues->LoadCuePoint()) {
127     if (!cue_point) {
128       cue_point = cues->GetFirst();
129     } else {
130       cue_point = cues->GetNext(cue_point);
131     }
132     const mkvparser::CuePoint::TrackPosition* const track_position =
133         cue_point->Find(cues_track);
134     const long long cluster_pos = track_position->m_pos +  // NOLINT
135                                   segment->m_start;
136 
137     // If a cluster does not begin at |cluster_pos|, then the file is
138     // incorrect.
139     long length;  // NOLINT
140     const std::int64_t id = mkvparser::ReadID(reader, cluster_pos, length);
141     if (id != libwebm::kMkvCluster) {
142       return false;
143     }
144   }
145   return true;
146 }
147 
~MkvParser()148 MkvParser::~MkvParser() {
149   delete segment;
150   delete reader;
151 }
152 
ParseMkvFileReleaseParser(const std::string & webm_file,MkvParser * parser_out)153 bool ParseMkvFileReleaseParser(const std::string& webm_file,
154                                MkvParser* parser_out) {
155   parser_out->reader = new (std::nothrow) mkvparser::MkvReader;
156   mkvparser::MkvReader& reader = *parser_out->reader;
157   if (!parser_out->reader || reader.Open(webm_file.c_str()) < 0) {
158     return false;
159   }
160 
161   long long pos = 0;  // NOLINT
162   mkvparser::EBMLHeader ebml_header;
163   if (ebml_header.Parse(&reader, pos)) {
164     return false;
165   }
166 
167   using mkvparser::Segment;
168   Segment* segment_ptr = nullptr;
169   if (Segment::CreateInstance(&reader, pos, segment_ptr)) {
170     return false;
171   }
172 
173   std::unique_ptr<Segment> segment(segment_ptr);
174   long result;
175   if ((result = segment->Load()) < 0) {
176     return false;
177   }
178 
179   const mkvparser::Cluster* cluster = segment->GetFirst();
180   if (!cluster || cluster->EOS()) {
181     return false;
182   }
183 
184   while (cluster && cluster->EOS() == false) {
185     if (cluster->GetTimeCode() < 0) {
186       return false;
187     }
188 
189     const mkvparser::BlockEntry* block = nullptr;
190     if (cluster->GetFirst(block) < 0) {
191       return false;
192     }
193 
194     while (block != NULL && block->EOS() == false) {
195       if (cluster->GetNext(block, block) < 0) {
196         return false;
197       }
198     }
199 
200     cluster = segment->GetNext(cluster);
201   }
202 
203   parser_out->segment = segment.release();
204   return true;
205 }
206 
ParseMkvFile(const std::string & webm_file)207 bool ParseMkvFile(const std::string& webm_file) {
208   MkvParser parser;
209   const bool result = ParseMkvFileReleaseParser(webm_file, &parser);
210   delete parser.segment;
211   delete parser.reader;
212   return result;
213 }
214 
215 }  // namespace test
216