libdcp
verify_j2k.cc
Go to the documentation of this file.
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 
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;
187  auto num_POC_in_main = 0;
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  require_8(0, "invalid COC component number");
263  require_8(1, "invalid coding style %1");
264  require_8(5, "invalid number of transform levels %1");
265  require_8(3, "invalid code block width exponent %1");
266  require_8(3, "invalid code block height exponent %1");
267  require_8(0, "invalid mode variations");
268  require_8(0x77, "invalid precinct size %1");
269  require_8(0x88, "invalid precinct size %1");
270  require_8(0x88, "invalid precinct size %1");
271  require_8(0x88, "invalid precinct size %1");
272  require_8(0x88, "invalid precinct size %1");
273  require_8(0x88, "invalid precinct size %1");
274  } else if (*marker_name == "TLM") {
275  auto const len = get_16();
276  ptr += len - 2;
277  tlm = true;
278  } else if (*marker_name == "QCC" || *marker_name == "COM") {
279  auto const len = get_16();
280  ptr += len - 2;
281  } else if (*marker_name == "POC") {
282  if (main_header_finished) {
283  num_POC_after_main++;
284  } else {
285  num_POC_in_main++;
286  }
287 
288  auto require_8_poc = [&](uint16_t value, string note) {
289  if (get_8() != value) {
290  notes.push_back ({ VerificationNote::Type::BV21_ERROR, VerificationNote::Code::INCORRECT_JPEG2000_POC_MARKER, String::compose(note, value) });
291  }
292  };
293 
294  auto require_16_poc = [&](uint16_t value, string note) {
295  if (get_16() != value) {
296  notes.push_back ({ VerificationNote::Type::BV21_ERROR, VerificationNote::Code::INCORRECT_JPEG2000_POC_MARKER, String::compose(note, value) });
297  }
298  };
299 
300  require_16_poc(16, "invalid length %1");
301  require_8_poc(0, "invalid RSpoc %1");
302  require_8_poc(0, "invalid CSpoc %1");
303  require_16_poc(1, "invalid LYEpoc %1");
304  require_8_poc(6, "invalid REpoc %1");
305  require_8_poc(3, "invalid CEpoc %1");
306  require_8_poc(4, "invalid Ppoc %1");
307  require_8_poc(6, "invalid RSpoc %1");
308  require_8_poc(0, "invalid CSpoc %1");
309  require_16_poc(1, "invalid LYEpoc %1");
310  require_8_poc(7, "invalid REpoc %1");
311  require_8_poc(3, "invalid CEpoc %1");
312  require_8_poc(4, "invalid Ppoc %1");
313  }
314  }
315 
316  if (num_COD == 0) {
317  throw InvalidCodestream("no COD marker found");
318  }
319  if (num_COD > 1) {
320  throw InvalidCodestream("more than one COD marker found");
321  }
322  if (num_QCD == 0) {
323  throw InvalidCodestream("no QCD marker found");
324  }
325  if (num_QCD > 1) {
326  throw InvalidCodestream("more than one QCD marker found");
327  }
328  if (num_POC_in_main != 0 && !fourk) {
329  notes.push_back ({ VerificationNote::Type::BV21_ERROR, VerificationNote::Code::INCORRECT_JPEG2000_POC_MARKER_COUNT_FOR_2K, raw_convert<string>(num_POC_in_main) });
330  }
331  if (num_POC_in_main != 1 && fourk) {
332  notes.push_back ({ VerificationNote::Type::BV21_ERROR, VerificationNote::Code::INCORRECT_JPEG2000_POC_MARKER_COUNT_FOR_4K, raw_convert<string>(num_POC_in_main) });
333  }
334  if (num_POC_after_main != 0) {
335  notes.push_back ({ VerificationNote::Type::BV21_ERROR, VerificationNote::Code::INVALID_JPEG2000_POC_MARKER_LOCATION });
336  }
337  if (!tlm) {
338  notes.push_back ({ VerificationNote::Type::BV21_ERROR, VerificationNote::Code::MISSING_JPEG200_TLM_MARKER });
339  }
340  }
341  catch (InvalidCodestream const& e)
342  {
343  notes.push_back ({VerificationNote::Type::ERROR, VerificationNote::Code::INVALID_JPEG2000_CODESTREAM, string(e.what()) });
344  }
345 }
346 
Data class.
void verify_j2k(std::shared_ptr< const Data > data, std::vector< VerificationNote > &notes)
Definition: verify_j2k.cc:68
P raw_convert(Q, int precision=16, bool fixed=false)
Definition: raw_convert.h:57
Methods for conversion to/from string.
dcp::verify() method and associated code
Verification that JPEG2000 files meet requirements.