Fix signed-ness warning on macOS.
[libdcp.git] / src / verify_j2k.cc
1 /*
2     Copyright (C) 2021 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
35 /** @file  src/verify_j2k.cc
36  *  @brief Verification that JPEG2000 files meet requirements
37  */
38
39
40 #include "compose.hpp"
41 #include "data.h"
42 #include "raw_convert.h"
43 #include "verify.h"
44 #include "verify_j2k.h"
45 #include <memory>
46 #include <vector>
47
48
49 using std::shared_ptr;
50 using std::map;
51 using std::runtime_error;
52 using std::string;
53 using std::vector;
54 using boost::optional;
55 using dcp::raw_convert;
56
57
58 class InvalidCodestream : public runtime_error
59 {
60 public:
61         InvalidCodestream (string note)
62                 : runtime_error(note)
63         {}
64 };
65
66
67 void
68 dcp::verify_j2k (shared_ptr<const Data> j2k, vector<VerificationNote>& notes)
69 {
70         try {
71                 auto ptr = j2k->data();
72                 auto end = ptr + j2k->size();
73
74                 map<string, uint8_t> markers = {
75                         { "SOC", 0x4f },
76                         { "SIZ", 0x51 },
77                         { "COD", 0x52 },
78                         { "COC", 0x53 },
79                         { "TLM", 0x55 },
80                         { "QCD", 0x5c },
81                         { "QCC", 0x5d },
82                         { "POC", 0x5f },
83                         { "COM", 0x64 },
84                         { "SOT", 0x90 },
85                         { "SOD", 0x93 },
86                         { "EOC", 0xd9 },
87                 };
88
89                 auto marker_name_from_id = [&markers](uint8_t b) -> optional<string> {
90                         for (auto const& i: markers) {
91                                 if (i.second == b) {
92                                         return i.first;
93                                 }
94                         }
95                         return {};
96                 };
97
98                 auto require_marker = [&](string name) {
99                         if (ptr == end || *ptr != 0xff) {
100                                 throw InvalidCodestream ("missing marker start byte");
101                         }
102                         ++ptr;
103                         if (ptr == end || *ptr != markers[name]) {
104                                 throw InvalidCodestream ("missing_marker " + name);
105                         }
106                         ++ptr;
107                 };
108
109                 auto get_8 = [&]() {
110                         if (ptr >= end) {
111                                 throw InvalidCodestream ("unexpected end of file");
112                         }
113                         return *ptr++;
114                 };
115
116                 auto get_16 = [&]() {
117                         if (ptr >= (end - 1)) {
118                                 throw InvalidCodestream ("unexpected end of file");
119                         }
120                         auto const a = *ptr++;
121                         auto const b = *ptr++;
122                         return b | (a << 8);
123                 };
124
125                 auto get_32 = [&]() -> uint32_t {
126                         if (ptr >= (end - 3)) {
127                                 throw InvalidCodestream ("unexpected end of file");
128                         }
129                         auto const a = *ptr++;
130                         auto const b = *ptr++;
131                         auto const c = *ptr++;
132                         auto const d = *ptr++;
133                         return d | (c << 8) | (b << 16) | (a << 24);
134                 };
135
136                 auto require_8 = [&](uint8_t value, string note) {
137                         auto v = get_8 ();
138                         if (v != value) {
139                                 throw InvalidCodestream (String::compose(note, v));
140                         }
141                 };
142
143                 auto require_16 = [&](uint16_t value, string note) {
144                         auto v = get_16 ();
145                         if (v != value) {
146                                 throw InvalidCodestream (String::compose(note, v));
147                         }
148                 };
149
150                 auto require_32 = [&](uint32_t value, string note) {
151                         auto v = get_32 ();
152                         if (v != value) {
153                                 throw InvalidCodestream (String::compose(note, v));
154                         }
155                 };
156
157                 require_marker ("SOC");
158                 require_marker ("SIZ");
159                 auto L_siz = get_16();
160                 if (L_siz != 47) {
161                         throw InvalidCodestream("unexpected SIZ size " + raw_convert<string>(L_siz));
162                 }
163
164                 get_16(); // CA: codestream capabilities
165                 auto const image_width = get_32();
166                 auto const image_height = get_32();
167                 auto const fourk = image_width > 2048;
168                 require_32 (0, "invalid top-left image x coordinate %1");
169                 require_32 (0, "invalid top-left image y coordinate %1");
170                 auto const tile_width = get_32();
171                 auto const tile_height = get_32();
172                 if (tile_width != image_width || tile_height != image_height) {
173                         notes.push_back ({ VerificationNote::Type::BV21_ERROR, VerificationNote::Code::INVALID_JPEG2000_TILE_SIZE });
174                 }
175                 require_32 (0, "invalid tile anchor x coordinate %1");
176                 require_32 (0, "invalid tile anchor y coordinate %1");
177                 require_16 (3, "invalid component count %1");
178                 for (auto i = 0; i < 3; ++i) {
179                         require_8 (12 - 1, "invalid bit depth %1");
180                         require_8 (1, "invalid horizontal subsampling factor %1");
181                         require_8 (1, "invalid vertical subsampling factor %1");
182                 }
183
184                 auto num_COD = 0;
185                 auto num_QCD = 0;
186                 /** number of POC markers in the main header */
187                 auto num_POC_in_main = 0;
188                 /** number of POC markers after the main header */
189                 auto num_POC_after_main = 0;
190                 bool main_header_finished = false;
191                 bool tlm = false;
192
193                 while (ptr < end)
194                 {
195                         require_8(0xff, "missing marker start byte");
196                         auto marker_id = get_8();
197                         auto marker_name = marker_name_from_id (marker_id);
198                         if (!marker_name) {
199                                 char buffer[16];
200                                 snprintf (buffer, 16, "%2x", marker_id);
201                                 throw InvalidCodestream(String::compose("unknown marker %1", buffer));
202                         } else if (*marker_name == "SOT") {
203                                 require_16(10, "invalid SOT size %1");
204                                 get_16(); // tile index
205                                 get_32(); // tile part length
206                                 get_8(); // tile part index
207                                 auto tile_parts = get_8();
208                                 if (!fourk && tile_parts != 3) {
209                                         notes.push_back ({ VerificationNote::Type::BV21_ERROR, VerificationNote::Code::INVALID_JPEG2000_TILE_PARTS_FOR_2K, raw_convert<string>(tile_parts) });
210                                 }
211                                 if (fourk && tile_parts != 6) {
212                                         notes.push_back ({ VerificationNote::Type::BV21_ERROR, VerificationNote::Code::INVALID_JPEG2000_TILE_PARTS_FOR_4K, raw_convert<string>(tile_parts) });
213                                 }
214                                 main_header_finished = true;
215                         } else if (*marker_name == "SOD") {
216                                 while (ptr < (end - 1) && (ptr[0] != 0xff || ptr[1] < 0x90)) {
217                                         ++ptr;
218                                 }
219                         } else if (*marker_name == "SIZ") {
220                                 throw InvalidCodestream ("duplicate SIZ marker");
221                         } else if (*marker_name == "COD") {
222                                 num_COD++;
223                                 get_16(); // length
224                                 require_8(1, "invalid coding style %1");
225                                 require_8(4, "invalid progression order %1"); // CPRL
226                                 require_16(1, "invalid quality layers count %1");
227                                 require_8(1, "invalid multi-component transform flag %1");
228                                 require_8(fourk ? 6 : 5, "invalid number of transform levels %1");
229                                 auto log_code_block_width = get_8();
230                                 if (log_code_block_width != 3) {
231                                         notes.push_back ({ VerificationNote::Type::BV21_ERROR, VerificationNote::Code::INVALID_JPEG2000_CODE_BLOCK_WIDTH, raw_convert<string>(4 * (2 << log_code_block_width)) });
232                                 }
233                                 auto log_code_block_height = get_8();
234                                 if (log_code_block_height != 3) {
235                                         notes.push_back ({ VerificationNote::Type::BV21_ERROR, VerificationNote::Code::INVALID_JPEG2000_CODE_BLOCK_HEIGHT, raw_convert<string>(4 * (2 << log_code_block_height)) });
236                                 }
237                                 require_8(0, "invalid mode variations");
238                                 require_8(0, "invalid wavelet transform type %1"); // 9/7 irreversible
239                                 require_8(0x77, "invalid precinct size %1");
240                                 require_8(0x88, "invalid precinct size %1");
241                                 require_8(0x88, "invalid precinct size %1");
242                                 require_8(0x88, "invalid precinct size %1");
243                                 require_8(0x88, "invalid precinct size %1");
244                                 require_8(0x88, "invalid precinct size %1");
245                                 if (fourk) {
246                                         require_8(0x88, "invalid precinct size %1");
247                                 }
248                         } else if (*marker_name == "QCD") {
249                                 num_QCD++;
250                                 auto const L_qcd = get_16();
251                                 auto quantization_style = get_8();
252                                 int guard_bits = (quantization_style >> 5) & 7;
253                                 if (fourk && guard_bits != 2) {
254                                         notes.push_back ({ VerificationNote::Type::BV21_ERROR, VerificationNote::Code::INVALID_JPEG2000_GUARD_BITS_FOR_4K, raw_convert<string>(guard_bits) });
255                                 }
256                                 if (!fourk && guard_bits != 1) {
257                                         notes.push_back ({ VerificationNote::Type::BV21_ERROR, VerificationNote::Code::INVALID_JPEG2000_GUARD_BITS_FOR_2K, raw_convert<string>(guard_bits) });
258                                 }
259                                 ptr += L_qcd - 3;
260                         } else if (*marker_name == "COC") {
261                                 get_16(); // length
262                                 auto const coc_component_number = get_8();
263                                 /* I don't know if this is really a requirement, but it seems to make sense that there should only
264                                  * be components 0, 1 and 2.  DoM bug #2395 is about a DCP with COC component number 1 which seems
265                                  * like it should be OK.
266                                  */
267                                 if (coc_component_number > 2) {
268                                         throw InvalidCodestream(String::compose("invalid COC component number %1", coc_component_number));
269                                 }
270                                 require_8(1, "invalid coding style %1");
271                                 require_8(5, "invalid number of transform levels %1");
272                                 require_8(3, "invalid code block width exponent %1");
273                                 require_8(3, "invalid code block height exponent %1");
274                                 require_8(0, "invalid mode variations");
275                                 require_8(0x77, "invalid precinct size %1");
276                                 require_8(0x88, "invalid precinct size %1");
277                                 require_8(0x88, "invalid precinct size %1");
278                                 require_8(0x88, "invalid precinct size %1");
279                                 require_8(0x88, "invalid precinct size %1");
280                                 require_8(0x88, "invalid precinct size %1");
281                         } else if (*marker_name == "TLM") {
282                                 auto const len = get_16();
283                                 ptr += len - 2;
284                                 tlm = true;
285                         } else if (*marker_name == "QCC" || *marker_name == "COM") {
286                                 auto const len = get_16();
287                                 ptr += len - 2;
288                         } else if (*marker_name == "POC") {
289                                 if (main_header_finished) {
290                                         num_POC_after_main++;
291                                 } else {
292                                         num_POC_in_main++;
293                                 }
294
295                                 auto require_8_poc = [&](uint16_t value, string note) {
296                                         if (get_8() != value) {
297                                                 notes.push_back ({ VerificationNote::Type::BV21_ERROR, VerificationNote::Code::INCORRECT_JPEG2000_POC_MARKER, String::compose(note, value) });
298                                         }
299                                 };
300
301                                 auto require_16_poc = [&](uint16_t value, string note) {
302                                         if (get_16() != value) {
303                                                 notes.push_back ({ VerificationNote::Type::BV21_ERROR, VerificationNote::Code::INCORRECT_JPEG2000_POC_MARKER, String::compose(note, value) });
304                                         }
305                                 };
306
307                                 require_16_poc(16, "invalid length %1");
308                                 require_8_poc(0, "invalid RSpoc %1");
309                                 require_8_poc(0, "invalid CSpoc %1");
310                                 require_16_poc(1, "invalid LYEpoc %1");
311                                 require_8_poc(6, "invalid REpoc %1");
312                                 require_8_poc(3, "invalid CEpoc %1");
313                                 require_8_poc(4, "invalid Ppoc %1");
314                                 require_8_poc(6, "invalid RSpoc %1");
315                                 require_8_poc(0, "invalid CSpoc %1");
316                                 require_16_poc(1, "invalid LYEpoc %1");
317                                 require_8_poc(7, "invalid REpoc %1");
318                                 require_8_poc(3, "invalid CEpoc %1");
319                                 require_8_poc(4, "invalid Ppoc %1");
320                         }
321                 }
322
323                 if (num_COD == 0) {
324                         throw InvalidCodestream("no COD marker found");
325                 }
326                 if (num_COD > 1) {
327                         throw InvalidCodestream("more than one COD marker found");
328                 }
329                 if (num_QCD == 0) {
330                         throw InvalidCodestream("no QCD marker found");
331                 }
332                 if (num_QCD > 1) {
333                         throw InvalidCodestream("more than one QCD marker found");
334                 }
335                 if (num_POC_in_main != 0 && !fourk) {
336                         notes.push_back ({ VerificationNote::Type::BV21_ERROR, VerificationNote::Code::INCORRECT_JPEG2000_POC_MARKER_COUNT_FOR_2K, raw_convert<string>(num_POC_in_main) });
337                 }
338                 if (num_POC_in_main != 1 && fourk) {
339                         notes.push_back ({ VerificationNote::Type::BV21_ERROR, VerificationNote::Code::INCORRECT_JPEG2000_POC_MARKER_COUNT_FOR_4K, raw_convert<string>(num_POC_in_main) });
340                 }
341                 if (num_POC_after_main != 0) {
342                         notes.push_back ({ VerificationNote::Type::BV21_ERROR, VerificationNote::Code::INVALID_JPEG2000_POC_MARKER_LOCATION });
343                 }
344                 if (!tlm) {
345                         notes.push_back ({ VerificationNote::Type::BV21_ERROR, VerificationNote::Code::MISSING_JPEG200_TLM_MARKER });
346                 }
347         }
348         catch (InvalidCodestream const& e)
349         {
350                 notes.push_back ({VerificationNote::Type::ERROR, VerificationNote::Code::INVALID_JPEG2000_CODESTREAM, string(e.what()) });
351         }
352 }
353