diff options
| author | Carl Hetherington <cth@carlh.net> | 2023-04-16 23:34:42 +0200 |
|---|---|---|
| committer | Carl Hetherington <cth@carlh.net> | 2023-04-16 23:34:42 +0200 |
| commit | e46e6c6054e4294c35d0f5e17de251b2fbb94cdc (patch) | |
| tree | d0e50ab76520dcf21a80b4800b3f05271b53dea6 | |
| parent | eda64d142769ca4e81578fc1dc59e265eac28ac7 (diff) | |
Check for tile parts being too big in the verifier (DoM #2450).
| -rw-r--r-- | src/verify.cc | 11 | ||||
| -rw-r--r-- | src/verify.h | 44 | ||||
| -rw-r--r-- | src/verify_j2k.cc | 16 | ||||
| -rw-r--r-- | src/verify_j2k.h | 6 | ||||
| -rw-r--r-- | test/verify_test.cc | 70 |
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 }, + }); +} + |
