Check for badly-formed CPL <Id>
[libdcp.git] / test / verify_test.cc
1 /*
2     Copyright (C) 2018-2019 Carl Hetherington <cth@carlh.net>
3
4     This file is part of libdcp.
5
6     libdcp is free software; you can redistribute it and/or modify
7     it under the terms of the GNU General Public License as published by
8     the Free Software Foundation; either version 2 of the License, or
9     (at your option) any later version.
10
11     libdcp is distributed in the hope that it will be useful,
12     but WITHOUT ANY WARRANTY; without even the implied warranty of
13     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14     GNU General Public License for more details.
15
16     You should have received a copy of the GNU General Public License
17     along with libdcp.  If not, see <http://www.gnu.org/licenses/>.
18
19     In addition, as a special exception, the copyright holders give
20     permission to link the code of portions of this program with the
21     OpenSSL library under certain conditions as described in each
22     individual source file, and distribute linked combinations
23     including the two.
24
25     You must obey the GNU General Public License in all respects
26     for all of the code used other than OpenSSL.  If you modify
27     file(s) with this exception, you may extend this exception to your
28     version of the file(s), but you are not obligated to do so.  If you
29     do not wish to do so, delete this exception statement from your
30     version.  If you delete this exception statement from all source
31     files in the program, then also delete it here.
32 */
33
34 #include "verify.h"
35 #include "util.h"
36 #include "compose.hpp"
37 #include <boost/test/unit_test.hpp>
38 #include <boost/foreach.hpp>
39 #include <boost/algorithm/string.hpp>
40 #include <cstdio>
41 #include <iostream>
42
43 using std::list;
44 using std::pair;
45 using std::string;
46 using std::vector;
47 using std::make_pair;
48 using boost::optional;
49
50 static list<pair<string, optional<boost::filesystem::path> > > stages;
51
52 static void
53 stage (string s, optional<boost::filesystem::path> p)
54 {
55         stages.push_back (make_pair (s, p));
56 }
57
58 static void
59 progress (float)
60 {
61
62 }
63
64 static vector<boost::filesystem::path>
65 setup (int n)
66 {
67         boost::filesystem::remove_all (dcp::String::compose("build/test/verify_test%1", n));
68         boost::filesystem::create_directory (dcp::String::compose("build/test/verify_test%1", n));
69         for (boost::filesystem::directory_iterator i("test/ref/DCP/dcp_test1"); i != boost::filesystem::directory_iterator(); ++i) {
70                 boost::filesystem::copy_file (i->path(), dcp::String::compose("build/test/verify_test%1", n) / i->path().filename());
71         }
72
73         vector<boost::filesystem::path> directories;
74         directories.push_back (dcp::String::compose("build/test/verify_test%1", n));
75         return directories;
76
77 }
78
79 class Editor
80 {
81 public:
82         Editor (boost::filesystem::path path)
83                 : _path(path)
84         {
85                 _content = dcp::file_to_string (_path);
86         }
87
88         ~Editor ()
89         {
90                 FILE* f = fopen(_path.string().c_str(), "w");
91                 BOOST_REQUIRE (f);
92                 fwrite (_content.c_str(), _content.length(), 1, f);
93                 fclose (f);
94         }
95
96         void replace (string a, string b)
97         {
98                 boost::algorithm::replace_all (_content, a, b);
99         }
100
101 private:
102         boost::filesystem::path _path;
103         std::string _content;
104 };
105
106 /* Check DCP as-is (should be OK) */
107 BOOST_AUTO_TEST_CASE (verify_test1)
108 {
109         vector<boost::filesystem::path> directories = setup (1);
110         list<dcp::VerificationNote> notes = dcp::verify (directories, &stage, &progress);
111
112         boost::filesystem::path const cpl_file = "build/test/verify_test1/cpl_81fb54df-e1bf-4647-8788-ea7ba154375b.xml";
113
114         list<pair<string, optional<boost::filesystem::path> > >::const_iterator st = stages.begin();
115         BOOST_CHECK_EQUAL (st->first, "Checking DCP");
116         BOOST_REQUIRE (st->second);
117         BOOST_CHECK_EQUAL (st->second.get(), boost::filesystem::canonical("build/test/verify_test1"));
118         ++st;
119         BOOST_CHECK_EQUAL (st->first, "Checking CPL");
120         BOOST_REQUIRE (st->second);
121         BOOST_CHECK_EQUAL (st->second.get(), boost::filesystem::canonical(cpl_file));
122         ++st;
123         BOOST_CHECK_EQUAL (st->first, "Checking reel");
124         BOOST_REQUIRE (!st->second);
125         ++st;
126         BOOST_CHECK_EQUAL (st->first, "Checking picture asset hash");
127         BOOST_REQUIRE (st->second);
128         BOOST_CHECK_EQUAL (st->second.get(), boost::filesystem::canonical("build/test/verify_test1/video.mxf"));
129         ++st;
130         BOOST_CHECK_EQUAL (st->first, "Checking sound asset hash");
131         BOOST_REQUIRE (st->second);
132         BOOST_CHECK_EQUAL (st->second.get(), boost::filesystem::canonical("build/test/verify_test1/audio.mxf"));
133         ++st;
134         BOOST_REQUIRE (st == stages.end());
135
136         BOOST_CHECK_EQUAL (notes.size(), 0);
137 }
138
139 /* Corrupt the MXFs and check that this is spotted */
140 BOOST_AUTO_TEST_CASE (verify_test2)
141 {
142         vector<boost::filesystem::path> directories = setup (2);
143
144         FILE* mod = fopen("build/test/verify_test2/video.mxf", "r+b");
145         BOOST_REQUIRE (mod);
146         fseek (mod, 4096, SEEK_SET);
147         int x = 42;
148         fwrite (&x, sizeof(x), 1, mod);
149         fclose (mod);
150
151         mod = fopen("build/test/verify_test2/audio.mxf", "r+b");
152         BOOST_REQUIRE (mod);
153         fseek (mod, 4096, SEEK_SET);
154         BOOST_REQUIRE (fwrite (&x, sizeof(x), 1, mod) == 1);
155         fclose (mod);
156
157         list<dcp::VerificationNote> notes = dcp::verify (directories, &stage, &progress);
158
159         BOOST_REQUIRE_EQUAL (notes.size(), 2);
160         BOOST_CHECK_EQUAL (notes.front().type(), dcp::VerificationNote::VERIFY_ERROR);
161         BOOST_CHECK_EQUAL (notes.front().code(), dcp::VerificationNote::PICTURE_HASH_INCORRECT);
162         BOOST_CHECK_EQUAL (notes.back().type(), dcp::VerificationNote::VERIFY_ERROR);
163         BOOST_CHECK_EQUAL (notes.back().code(), dcp::VerificationNote::SOUND_HASH_INCORRECT);
164 }
165
166 /* Corrupt the hashes in the PKL and check that the disagreement between CPL and PKL is spotted */
167 BOOST_AUTO_TEST_CASE (verify_test3)
168 {
169         vector<boost::filesystem::path> directories = setup (3);
170
171         {
172                 Editor e ("build/test/verify_test3/pkl_ae8a9818-872a-4f86-8493-11dfdea03e09.xml");
173                 e.replace ("<Hash>", "<Hash>x");
174         }
175
176         list<dcp::VerificationNote> notes = dcp::verify (directories, &stage, &progress);
177
178         BOOST_REQUIRE_EQUAL (notes.size(), 3);
179         list<dcp::VerificationNote>::const_iterator i = notes.begin();
180         BOOST_CHECK_EQUAL (i->type(), dcp::VerificationNote::VERIFY_ERROR);
181         BOOST_CHECK_EQUAL (i->code(), dcp::VerificationNote::CPL_HASH_INCORRECT);
182         ++i;
183         BOOST_CHECK_EQUAL (i->type(), dcp::VerificationNote::VERIFY_ERROR);
184         BOOST_CHECK_EQUAL (i->code(), dcp::VerificationNote::PKL_CPL_PICTURE_HASHES_DISAGREE);
185         ++i;
186         BOOST_CHECK_EQUAL (i->type(), dcp::VerificationNote::VERIFY_ERROR);
187         BOOST_CHECK_EQUAL (i->code(), dcp::VerificationNote::PKL_CPL_SOUND_HASHES_DISAGREE);
188         ++i;
189 }
190
191 /* Corrupt the ContentKind in the CPL */
192 BOOST_AUTO_TEST_CASE (verify_test4)
193 {
194         vector<boost::filesystem::path> directories = setup (4);
195
196         {
197                 Editor e ("build/test/verify_test4/cpl_81fb54df-e1bf-4647-8788-ea7ba154375b.xml");
198                 e.replace ("<ContentKind>", "<ContentKind>x");
199         }
200
201         list<dcp::VerificationNote> notes = dcp::verify (directories, &stage, &progress);
202
203         BOOST_REQUIRE_EQUAL (notes.size(), 1);
204         BOOST_CHECK_EQUAL (notes.front().code(), dcp::VerificationNote::GENERAL_READ);
205         BOOST_CHECK_EQUAL (*notes.front().note(), "Bad content kind 'xfeature'");
206 }
207
208 static
209 boost::filesystem::path
210 cpl (int n)
211 {
212         return dcp::String::compose("build/test/verify_test%1/cpl_81fb54df-e1bf-4647-8788-ea7ba154375b.xml", n);
213 }
214
215 static
216 void check_after_replace (int n, boost::function<boost::filesystem::path (int)> file, string from, string to, dcp::VerificationNote::Code code1)
217 {
218         vector<boost::filesystem::path> directories = setup (n);
219
220         {
221                 Editor e (file(n));
222                 e.replace (from, to);
223         }
224
225         list<dcp::VerificationNote> notes = dcp::verify (directories, &stage, &progress);
226
227         BOOST_REQUIRE_EQUAL (notes.size(), 1);
228         BOOST_CHECK_EQUAL (notes.front().code(), code1);
229 }
230
231 static
232 void check_after_replace (int n, boost::function<boost::filesystem::path (int)> file, string from, string to, dcp::VerificationNote::Code code1, dcp::VerificationNote::Code code2)
233 {
234         vector<boost::filesystem::path> directories = setup (n);
235
236         {
237                 Editor e (file(n));
238                 e.replace (from, to);
239         }
240
241         list<dcp::VerificationNote> notes = dcp::verify (directories, &stage, &progress);
242
243         BOOST_REQUIRE_EQUAL (notes.size(), 2);
244         BOOST_CHECK_EQUAL (notes.front().code(), code1);
245         BOOST_CHECK_EQUAL (notes.back().code(), code2);
246 }
247
248 /* FrameRate */
249 BOOST_AUTO_TEST_CASE (verify_test5)
250 {
251         check_after_replace (
252                         5, &cpl,
253                         "<FrameRate>24 1", "<FrameRate>99 1",
254                         dcp::VerificationNote::CPL_HASH_INCORRECT,
255                         dcp::VerificationNote::INVALID_PICTURE_FRAME_RATE
256                         );
257 }
258
259 /* Missing asset */
260 BOOST_AUTO_TEST_CASE (verify_test6)
261 {
262         vector<boost::filesystem::path> directories = setup (6);
263
264         boost::filesystem::remove ("build/test/verify_test6/video.mxf");
265         list<dcp::VerificationNote> notes = dcp::verify (directories, &stage, &progress);
266
267         BOOST_REQUIRE_EQUAL (notes.size(), 1);
268         BOOST_CHECK_EQUAL (notes.front().type(), dcp::VerificationNote::VERIFY_ERROR);
269         BOOST_CHECK_EQUAL (notes.front().code(), dcp::VerificationNote::Code::MISSING_ASSET);
270 }
271
272 static
273 boost::filesystem::path
274 assetmap (int n)
275 {
276         return dcp::String::compose("build/test/verify_test%1/ASSETMAP.xml", n);
277 }
278
279 /* Empty asset filename in ASSETMAP */
280 BOOST_AUTO_TEST_CASE (verify_test7)
281 {
282         check_after_replace (
283                         7, &assetmap,
284                         "<Path>video.mxf</Path>", "<Path></Path>",
285                         dcp::VerificationNote::Code::EMPTY_ASSET_PATH
286                         );
287 }
288
289 /* Mismatched standard */
290 BOOST_AUTO_TEST_CASE (verify_test8)
291 {
292         check_after_replace (
293                         8, &cpl,
294                         "http://www.smpte-ra.org/schemas/429-7/2006/CPL", "http://www.digicine.com/PROTO-ASDCP-CPL-20040511#",
295                         dcp::VerificationNote::Code::MISMATCHED_STANDARD,
296                         dcp::VerificationNote::Code::CPL_HASH_INCORRECT
297                         );
298 }
299
300 /* Badly formatted <Id> in CPL */
301 BOOST_AUTO_TEST_CASE (verify_test9)
302 {
303         check_after_replace (
304                         9, &cpl,
305                         "<Id>urn:uuid:81fb54df-e1bf-4647-8788-ea7ba154375b", "<Id>urn:uuid:81fb54df-e1bf-4647-8788-ea7ba154375",
306                         dcp::VerificationNote::Code::BAD_URN_UUID
307                         );
308 }
309