summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorCarl Hetherington <cth@carlh.net>2023-04-16 23:34:42 +0200
committerCarl Hetherington <cth@carlh.net>2023-04-16 23:34:42 +0200
commite46e6c6054e4294c35d0f5e17de251b2fbb94cdc (patch)
treed0e50ab76520dcf21a80b4800b3f05271b53dea6
parenteda64d142769ca4e81578fc1dc59e265eac28ac7 (diff)
Check for tile parts being too big in the verifier (DoM #2450).
-rw-r--r--src/verify.cc11
-rw-r--r--src/verify.h44
-rw-r--r--src/verify_j2k.cc16
-rw-r--r--src/verify_j2k.h6
-rw-r--r--test/verify_test.cc70
5 files changed, 132 insertions, 15 deletions
diff --git a/src/verify.cc b/src/verify.cc
index d43c7ff1..81ca0bc9 100644
--- a/src/verify.cc
+++ b/src/verify.cc
@@ -449,7 +449,7 @@ verify_picture_asset (shared_ptr<const ReelFileAsset> reel_file_asset, boost::fi
biggest_frame = max(biggest_frame, frame->size());
if (!mono_asset->encrypted() || mono_asset->key()) {
vector<VerificationNote> j2k_notes;
- verify_j2k (frame, j2k_notes);
+ verify_j2k(frame, i, mono_asset->frame_rate().numerator, j2k_notes);
check_and_add (j2k_notes);
}
progress (float(i) / duration);
@@ -461,8 +461,8 @@ verify_picture_asset (shared_ptr<const ReelFileAsset> reel_file_asset, boost::fi
biggest_frame = max(biggest_frame, max(frame->left()->size(), frame->right()->size()));
if (!stereo_asset->encrypted() || stereo_asset->key()) {
vector<VerificationNote> j2k_notes;
- verify_j2k (frame->left(), j2k_notes);
- verify_j2k (frame->right(), j2k_notes);
+ verify_j2k(frame->left(), i, stereo_asset->frame_rate().numerator, j2k_notes);
+ verify_j2k(frame->right(), i, stereo_asset->frame_rate().numerator, j2k_notes);
check_and_add (j2k_notes);
}
progress (float(i) / duration);
@@ -2014,6 +2014,11 @@ dcp::note_to_string (VerificationNote note)
return String::compose("<MainSoundConfiguration> has an invalid value: %1", note.note().get());
case VerificationNote::Code::MISSING_FONT:
return String::compose("The font file for font ID \"%1\" was not found, or was not referred to in the ASSETMAP.", note.note().get());
+ case VerificationNote::Code::INVALID_JPEG2000_TILE_PART_SIZE:
+ return String::compose(
+ "Frame %1 has an image component that is too large (component %2 is %3 bytes in size).",
+ note.frame().get(), note.component().get(), note.size().get()
+ );
}
return "";
diff --git a/src/verify.h b/src/verify.h
index 8eec9749..4d65fae7 100644
--- a/src/verify.h
+++ b/src/verify.h
@@ -436,7 +436,13 @@ public:
/** An interop subtitle file has a <LoadFont> node which refers to a font file that is not found.
* note contains the <LoadFont> ID
*/
- MISSING_FONT
+ MISSING_FONT,
+ /** A tile part in a JPEG2000 frame is too big.
+ * frame contains the frame index (counted from 0)
+ * component contains the component index (0, 1 or 2)
+ * size contains the invalid size in bytes.
+ */
+ INVALID_JPEG2000_TILE_PART_SIZE,
};
VerificationNote (Type type, Code code)
@@ -485,9 +491,12 @@ public:
private:
enum class Data {
- NOTE, ///< further information about the error
- FILE, ///< path of file containing the error
- LINE ///< error line number within the FILE
+ NOTE, ///< further information about the error
+ FILE, ///< path of file containing the error
+ LINE, ///< error line number within the FILE
+ FRAME,
+ COMPONENT,
+ SIZE,
};
template <class T>
@@ -513,6 +522,33 @@ public:
return data<uint64_t>(Data::LINE);
}
+ VerificationNote& set_frame(int frame) {
+ _data[Data::FRAME] = frame;
+ return *this;
+ }
+
+ boost::optional<int> frame() const {
+ return data<int>(Data::FRAME);
+ }
+
+ VerificationNote& set_component(int component) {
+ _data[Data::COMPONENT] = component;
+ return *this;
+ }
+
+ boost::optional<int> component() const {
+ return data<int>(Data::COMPONENT);
+ }
+
+ VerificationNote& set_size(int size) {
+ _data[Data::SIZE] = size;
+ return *this;
+ }
+
+ boost::optional<int> size() const {
+ return data<int>(Data::SIZE);
+ }
+
private:
Type _type;
Code _code;
diff --git a/src/verify_j2k.cc b/src/verify_j2k.cc
index 86ffb5b4..b9158849 100644
--- a/src/verify_j2k.cc
+++ b/src/verify_j2k.cc
@@ -65,8 +65,11 @@ public:
void
-dcp::verify_j2k (shared_ptr<const Data> j2k, vector<VerificationNote>& notes)
+dcp::verify_j2k(shared_ptr<const Data> j2k, int frame_index, int frame_rate, vector<VerificationNote>& notes)
{
+ /* See ITU-T T800 (visible on https://github.com/Ymagis/ClairMeta/issues/130) */
+ unsigned int const max_tile_part_size = std::floor(200e6 / (8 * frame_rate));
+
try {
auto ptr = j2k->data();
auto end = ptr + j2k->size();
@@ -202,8 +205,8 @@ dcp::verify_j2k (shared_ptr<const Data> j2k, vector<VerificationNote>& notes)
} else if (*marker_name == "SOT") {
require_16(10, "invalid SOT size %1");
get_16(); // tile index
- get_32(); // tile part length
- get_8(); // tile part index
+ auto const tile_part_length = get_32();
+ auto const tile_part_index = get_8();
auto tile_parts = get_8();
if (!fourk && tile_parts != 3) {
notes.push_back ({ VerificationNote::Type::BV21_ERROR, VerificationNote::Code::INVALID_JPEG2000_TILE_PARTS_FOR_2K, raw_convert<string>(tile_parts) });
@@ -211,6 +214,13 @@ dcp::verify_j2k (shared_ptr<const Data> j2k, vector<VerificationNote>& notes)
if (fourk && tile_parts != 6) {
notes.push_back ({ VerificationNote::Type::BV21_ERROR, VerificationNote::Code::INVALID_JPEG2000_TILE_PARTS_FOR_4K, raw_convert<string>(tile_parts) });
}
+ if (tile_part_length > max_tile_part_size) {
+ VerificationNote note{VerificationNote::Type::ERROR, VerificationNote::Code::INVALID_JPEG2000_TILE_PART_SIZE};
+ note.set_frame(frame_index);
+ note.set_component(tile_part_index);
+ note.set_size(tile_part_length);
+ notes.push_back(note);
+ }
main_header_finished = true;
} else if (*marker_name == "SOD") {
while (ptr < (end - 1) && (ptr[0] != 0xff || ptr[1] < 0x90)) {
diff --git a/src/verify_j2k.h b/src/verify_j2k.h
index 3d9093dd..ac69155c 100644
--- a/src/verify_j2k.h
+++ b/src/verify_j2k.h
@@ -51,7 +51,11 @@ namespace dcp {
class Data;
-void verify_j2k (std::shared_ptr<const Data> data, std::vector<VerificationNote>& notes);
+/** @param frame_index Video frame index, so that notes can say which frame contains the problem.
+ * @param frame_rate Video frame rate (in frames per second) to calculate how big the tile parts
+ * can be.
+ */
+void verify_j2k(std::shared_ptr<const Data> data, int frame_index, int frame_rate, std::vector<VerificationNote>& notes);
}
diff --git a/test/verify_test.cc b/test/verify_test.cc
index 62e386b7..e182f05d 100644
--- a/test/verify_test.cc
+++ b/test/verify_test.cc
@@ -58,8 +58,9 @@
#include "util.h"
#include "verify.h"
#include "verify_j2k.h"
-#include <boost/test/unit_test.hpp>
#include <boost/algorithm/string.hpp>
+#include <boost/random.hpp>
+#include <boost/test/unit_test.hpp>
#include <cstdio>
#include <iostream>
@@ -3087,7 +3088,7 @@ BOOST_AUTO_TEST_CASE (verify_jpeg2000_codestream_2k)
dcp::MonoPictureAsset picture (find_file(private_test / "data" / "JourneyToJah_TLR-1_F_EN-DE-FR_CH_51_2K_LOK_20140225_DGL_SMPTE_OV", "j2c.mxf"));
auto reader = picture.start_read ();
auto frame = reader->get_frame (0);
- verify_j2k (frame, notes);
+ verify_j2k(frame, 0, 24, notes);
BOOST_REQUIRE_EQUAL (notes.size(), 0U);
}
@@ -3098,7 +3099,7 @@ BOOST_AUTO_TEST_CASE (verify_jpeg2000_codestream_4k)
dcp::MonoPictureAsset picture (find_file(private_test / "data" / "sul", "TLR"));
auto reader = picture.start_read ();
auto frame = reader->get_frame (0);
- verify_j2k (frame, notes);
+ verify_j2k(frame, 0, 24, notes);
BOOST_REQUIRE_EQUAL (notes.size(), 0U);
}
@@ -3113,7 +3114,7 @@ BOOST_AUTO_TEST_CASE (verify_jpeg2000_codestream_libdcp)
dcp::MonoPictureAsset picture (find_file(dir, "video"));
auto reader = picture.start_read ();
auto frame = reader->get_frame (0);
- verify_j2k (frame, notes);
+ verify_j2k(frame, 0, 24, notes);
BOOST_REQUIRE_EQUAL (notes.size(), 0U);
}
@@ -3576,3 +3577,64 @@ BOOST_AUTO_TEST_CASE(verify_invalid_main_sound_configuration)
});
}
+
+BOOST_AUTO_TEST_CASE(verify_invalid_tile_part_size)
+{
+ boost::filesystem::path const path = "build/test/verify_invalid_tile_part_size";
+ auto constexpr video_frames = 24;
+ auto constexpr sample_rate = 48000;
+
+ boost::filesystem::remove_all(path);
+ boost::filesystem::create_directories(path);
+
+ auto mp = make_shared<dcp::MonoPictureAsset>(dcp::Fraction(24, 1), dcp::Standard::SMPTE);
+ auto picture_writer = mp->start_write(path / "video.mxf", dcp::PictureAsset::Behaviour::MAKE_NEW);
+
+ dcp::Size const size(1998, 1080);
+ auto image = make_shared<dcp::OpenJPEGImage>(size);
+ boost::random::mt19937 rng(1);
+ boost::random::uniform_int_distribution<> dist(0, 4095);
+ for (int c = 0; c < 3; ++c) {
+ for (int p = 0; p < (1998 * 1080); ++p) {
+ image->data(c)[p] = dist(rng);
+ }
+ }
+ auto j2c = dcp::compress_j2k(image, 750000000, video_frames, false, false);
+ for (int i = 0; i < 24; ++i) {
+ picture_writer->write(j2c.data(), j2c.size());
+ }
+ picture_writer->finalize();
+
+ auto dcp = make_shared<dcp::DCP>(path);
+ auto cpl = make_shared<dcp::CPL>("A Test DCP", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
+ cpl->set_content_version(
+ dcp::ContentVersion("urn:uuid:75ac29aa-42ac-1234-ecae-49251abefd11", "content-version-label-text")
+ );
+ cpl->set_main_sound_configuration(dcp::MainSoundConfiguration("51/L,R,C,LFE,Ls,Rs"));
+ cpl->set_main_sound_sample_rate(sample_rate);
+ cpl->set_main_picture_stored_area(dcp::Size(1998, 1080));
+ cpl->set_main_picture_active_area(dcp::Size(1998, 1080));
+ cpl->set_version_number(1);
+
+ auto ms = simple_sound(path, "", dcp::MXFMetadata(), "en-US", video_frames, sample_rate, {});
+
+ auto reel = make_shared<dcp::Reel>(
+ make_shared<dcp::ReelMonoPictureAsset>(mp, 0),
+ make_shared<dcp::ReelSoundAsset>(ms, 0)
+ );
+
+ cpl->add(reel);
+ dcp->add(cpl);
+ dcp->set_annotation_text("A Test DCP");
+ dcp->write_xml();
+
+ check_verify_result(
+ { path },
+ {
+ dcp::VerificationNote(dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_JPEG2000_TILE_PART_SIZE).set_frame(0).set_component(0).set_size(1321721),
+ { dcp::VerificationNote::Type::ERROR, dcp::VerificationNote::Code::INVALID_PICTURE_FRAME_SIZE_IN_BYTES, canonical(path / "video.mxf") },
+ { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::MISSING_FFOC },
+ { dcp::VerificationNote::Type::WARNING, dcp::VerificationNote::Code::MISSING_LFOC },
+ });
+}
+