OpenJPH
Open-source implementation of JPEG2000 Part-15
Loading...
Searching...
No Matches
test_truncated_decode.cpp
Go to the documentation of this file.
1//***************************************************************************/
2// This software is released under the 2-Clause BSD license, included
3// below.
4//
5// Copyright (c) 2019, Aous Naman
6// Copyright (c) 2019, Kakadu Software Pty Ltd, Australia
7// Copyright (c) 2019, The University of New South Wales, Australia
8//
9// Redistribution and use in source and binary forms, with or without
10// modification, are permitted provided that the following conditions are
11// met:
12//
13// 1. Redistributions of source code must retain the above copyright
14// notice, this list of conditions and the following disclaimer.
15//
16// 2. Redistributions in binary form must reproduce the above copyright
17// notice, this list of conditions and the following disclaimer in the
18// documentation and/or other materials provided with the distribution.
19//
20// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
21// IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
22// TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
23// PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
24// HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
25// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
26// TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
27// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
28// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
29// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
30// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
31//***************************************************************************/
32// This file is part of the OpenJPH software implementation.
33// File: test_truncated_decode.cpp
34// Author: Bill Wallace
35// Date: 07 August 2026
36//***************************************************************************/
37//
38// These tests decode codestreams that were physically truncated -- the tail
39// of the file is simply missing, and the decoder cannot know how long the
40// original was. This is what a partially received codestream looks like to
41// a streaming client.
42//
43// Not every truncation is detectable. When the cut falls inside codeblock
44// data the parser stops cleanly and reconstructs from what it has; when it
45// falls inside a packet header the parser has to raise an error. It is that
46// second group that codestream::enable_resilience() governs:
47//
48// - resilience off (the default): the truncation is reported by throwing,
49// so a caller that needs a complete image is told the codestream is
50// broken. Importantly it throws rather than terminating the process.
51// - resilience on: the truncation is reported through OJPH_INFO and the
52// decoder returns the part of the image it was able to reconstruct.
53//
54// Everything is done in memory, so the tests need no external files and run
55// the same way on all platforms.
56
57#include <stdexcept>
58#include <vector>
59
60#include "ojph_arch.h"
61#include "ojph_codestream.h"
62#include "ojph_file.h"
63#include "ojph_mem.h"
64#include "ojph_message.h"
65#include "ojph_params.h"
66#include "gtest/gtest.h"
67
68namespace {
69
71// The image is big enough, and detailed enough, that a reversible codestream
72// spans many packets, so that cutting it short lands in tile data rather than
73// in the main header.
74static const ojph::ui32 IMAGE_WIDTH = 256;
75static const ojph::ui32 IMAGE_HEIGHT = 256;
76
77// The number of truncation lengths tried; the codestream is cut at each
78// 1/NUM_CUTS of its length.
79static const ojph::ui32 NUM_CUTS = 16;
80
82// encode_test_codestream
84// Encodes a single component 8 bit reversible image to memory and returns the
85// resulting codestream. The encoding is lossless, so the bytes produced are
86// the same on every platform.
87static std::vector<ojph::ui8> encode_test_codestream()
88{
90
91 ojph::param_siz siz = cs.access_siz();
92 siz.set_image_extent(ojph::point(IMAGE_WIDTH, IMAGE_HEIGHT));
93 siz.set_num_components(1);
94 siz.set_component(0, ojph::point(1, 1), 8, false);
95
96 ojph::param_cod cod = cs.access_cod();
98 cod.set_block_dims(64, 64);
99 cod.set_reversible(true);
100
102 out.open();
103 cs.write_headers(&out);
104
105 ojph::ui32 next_comp = 0;
106 ojph::line_buf* line = cs.exchange(NULL, next_comp);
107 for (ojph::ui32 y = 0; y < IMAGE_HEIGHT; ++y)
108 {
109 ojph::si32* dp = line->i32;
110 for (ojph::ui32 x = 0; x < IMAGE_WIDTH; ++x)
111 dp[x] = (ojph::si32)((x * 7 + y * 13 + ((x * y) >> 3)) & 0xFF);
112 line = cs.exchange(line, next_comp);
113 }
114 cs.flush();
115
116 std::vector<ojph::ui8> buf(out.get_data(),
117 out.get_data() + (size_t)out.tell());
118 cs.close();
119 return buf;
120}
121
123// decode_lines
125// Decodes the codestream in buf and returns the number of lines pulled. Any
126// error raised by the library propagates to the caller.
127static ojph::ui32 decode_lines(const std::vector<ojph::ui8>& buf,
128 bool resilient)
129{
131 in.open(buf.data(), buf.size());
132
134 if (resilient)
136 cs.read_headers(&in);
137 cs.create();
138
139 ojph::param_siz siz = cs.access_siz();
140 ojph::ui32 num_lines = 0;
141 for (ojph::ui32 y = siz.get_recon_height(0); y > 0; --y)
142 {
143 ojph::ui32 comp_num = 0;
144 cs.pull(comp_num);
145 ++num_lines;
146 }
147 cs.close();
148 return num_lines;
149}
150
152// truncated_decode
154class truncated_decode : public ::testing::Test
155{
156protected:
157 void SetUp() override
158 {
159 // A truncated codestream is expected to be noisy; these tests are about
160 // the return path, not about the text that is printed.
162 full = encode_test_codestream();
163 ASSERT_GT(full.size(), NUM_CUTS * 64u)
164 << "the test codestream is too small to be meaningfully truncated";
165 }
166
167 void TearDown() override
168 {
170 }
171
172 // Returns the codestream cut down to cut/NUM_CUTS of its length.
173 std::vector<ojph::ui8> truncate(ojph::ui32 cut) const
174 {
175 size_t len = full.size() * cut / NUM_CUTS;
176 return std::vector<ojph::ui8>(
177 full.begin(), full.begin() + static_cast<std::ptrdiff_t>(len));
178 }
179
180 std::vector<ojph::ui8> full;
181};
182
184// A complete codestream decodes the same way whether or not resilience is
185// enabled; enabling it must not change the handling of good codestreams.
186TEST_F(truncated_decode, complete_codestream_decodes_in_both_modes)
187{
188 EXPECT_EQ(decode_lines(full, false), IMAGE_HEIGHT);
189 EXPECT_EQ(decode_lines(full, true), IMAGE_HEIGHT);
190}
191
193// With resilience enabled, no truncation length may raise an error, and the
194// decoder must still produce a full frame from whatever it received.
195TEST_F(truncated_decode, resilient_mode_decodes_every_truncation_length)
196{
197 for (ojph::ui32 cut = 1; cut < NUM_CUTS; ++cut)
198 {
199 std::vector<ojph::ui8> part = truncate(cut);
200 ojph::ui32 num_lines = 0;
201 ASSERT_NO_THROW(num_lines = decode_lines(part, true))
202 << "truncated to " << part.size() << " of " << full.size() << " bytes";
203 EXPECT_EQ(num_lines, IMAGE_HEIGHT)
204 << "truncated to " << part.size() << " of " << full.size() << " bytes";
205 }
206}
207
209// Without resilience, a truncation that the parser detects is reported by
210// throwing -- not by terminating the process, and not by silently returning
211// an incomplete image. Truncations that fall inside codeblock data are not
212// detectable and are expected to decode without an error in either mode.
213TEST_F(truncated_decode, non_resilient_mode_throws_on_detected_truncation)
214{
215 ojph::ui32 num_detected = 0;
216 for (ojph::ui32 cut = 1; cut < NUM_CUTS; ++cut)
217 {
218 std::vector<ojph::ui8> part = truncate(cut);
219 bool detected = false;
220 try {
221 decode_lines(part, false);
222 }
223 catch (const std::runtime_error&) {
224 detected = true;
225 }
226 catch (...) {
227 FAIL() << "the library must report errors as a std::runtime_error; "
228 << "truncated to " << part.size() << " of " << full.size() << " bytes";
229 }
230 if (detected)
231 {
232 ++num_detected;
233 // The same input, with resilience enabled, must decode instead of
234 // throwing; it is the flag, and only the flag, that decides.
235 EXPECT_EQ(decode_lines(part, true), IMAGE_HEIGHT)
236 << "truncated to " << part.size() << " of " << full.size() << " bytes";
237 }
238 }
239
240 EXPECT_GT(num_detected, 0u)
241 << "no truncation of the test codestream was detected by the parser, so "
242 << "this test is no longer testing anything";
243}
244
245} // anonymous namespace
The object represent a codestream.
param_siz access_siz()
Returns the underlying SIZ marker segment object.
param_cod access_cod()
Returns the underlying COD marker segment object.
void close()
Call this function to close the underlying file; works for both encoding and decoding codestreams.
void enable_resilience()
This enables codestream resilience; that is, the library tries its best to decode the codestream,...
line_buf * exchange(line_buf *line, ui32 &next_component)
This call is used to send image data rows to the library. We expect to send one row from a single com...
void read_headers(infile_base *file)
This call reads the headers of a codestream. It is for a reading (or decoding) codestream,...
void write_headers(outfile_base *file, const comment_exchange *comments=NULL, ui32 num_comments=0)
Writes codestream headers when the codestream is used for writing. This function should be called aft...
void create()
This call is for a decoding (or reading) codestream. Call this function after calling restrict_input_...
void flush()
This is the last call to a writing (encoding) codestream. This will write encoded bitstream data to t...
line_buf * pull(ui32 &comp_num)
This call is to pull one row from the codestream, being decoded. The returned line_buf object holds o...
void open(const ui8 *data, size_t size)
mem_outfile stores encoded j2k codestreams in memory
Definition ojph_file.h:127
si64 tell() override
Call this function to know the file size (i.e., number of bytes used to store the file).
Definition ojph_file.h:177
void open(size_t initial_size=65536, bool clear_mem=false)
Call this function to open a memory file.
const ui8 * get_data()
Call this function to access memory file data.
Definition ojph_file.h:201
void set_num_decomposition(ui32 num_decompositions)
void set_block_dims(ui32 width, ui32 height)
void set_reversible(bool reversible)
void set_component(ui32 comp_num, const point &downsampling, ui32 bit_depth, bool is_signed)
void set_num_components(ui32 num_comps)
ui32 get_recon_height(ui32 comp_num) const
void set_image_extent(point extent)
@ OJPH_MSG_ALL_MSG
@ OJPH_MSG_NO_MSG
int32_t si32
Definition ojph_defs.h:55
OJPH_EXPORT void set_message_level(OJPH_MSG_LEVEL level)
Sets the minimum severity of the message to be reported.
uint32_t ui32
Definition ojph_defs.h:54