diff --git a/benchmark/gzip.cc b/benchmark/gzip.cc index d5aaa567c5..735401eec6 100644 --- a/benchmark/gzip.cc +++ b/benchmark/gzip.cc @@ -5,6 +5,10 @@ #include // std::uint8_t #include // std::filesystem +#include // std::streamsize +#include // std::istream +#include // std::istringstream +#include // std::string static void // NOLINTNEXTLINE(readability-identifier-naming) @@ -39,6 +43,55 @@ GZIP_Decompress_ISO_Language_Set_3_Locations(benchmark::State &state) { } } +static void +// NOLINTNEXTLINE(readability-identifier-naming) +GZIP_Decompress_Default_Level_ISO_Language_Set_3_Locations( + benchmark::State &state) { + const sourcemeta::core::FileView view{ + std::filesystem::path{CURRENT_DIRECTORY} / "files" / + "iso_language_2023_set_3_locations.json.gz"}; + const auto contents{ + sourcemeta::core::gunzip(view.as(), view.size())}; + const auto compressed{sourcemeta::core::gzip( + reinterpret_cast(contents.data()), + contents.size())}; + + for (auto iteration : state) { + auto result{sourcemeta::core::gunzip( + reinterpret_cast(compressed.data()), + compressed.size(), contents.size())}; + benchmark::DoNotOptimize(result); + } +} + +static void +// NOLINTNEXTLINE(readability-identifier-naming) +GZIP_Decompress_Stream_Default_Level_ISO_Language_Set_3_Locations( + benchmark::State &state) { + const sourcemeta::core::FileView view{ + std::filesystem::path{CURRENT_DIRECTORY} / "files" / + "iso_language_2023_set_3_locations.json.gz"}; + const auto contents{ + sourcemeta::core::gunzip(view.as(), view.size())}; + std::istringstream source{sourcemeta::core::gzip( + reinterpret_cast(contents.data()), + contents.size())}; + + for (auto iteration : state) { + source.clear(); + source.seekg(0); + sourcemeta::core::GZIPStreamBuffer buffer{source}; + std::istream decompressed{&buffer}; + std::string result; + result.resize(contents.size()); + decompressed.read(result.data(), + static_cast(contents.size())); + // Reaching the end of the stream validates the member trailer + benchmark::DoNotOptimize(decompressed.peek()); + benchmark::DoNotOptimize(result); + } +} + // NOLINTNEXTLINE(readability-identifier-naming) static void GZIP_Compress_ISO_Language_Set_3_Schema(benchmark::State &state) { const sourcemeta::core::FileView view{ @@ -72,5 +125,7 @@ static void GZIP_Decompress_ISO_Language_Set_3_Schema(benchmark::State &state) { BENCHMARK(GZIP_Compress_ISO_Language_Set_3_Locations); BENCHMARK(GZIP_Decompress_ISO_Language_Set_3_Locations); +BENCHMARK(GZIP_Decompress_Default_Level_ISO_Language_Set_3_Locations); +BENCHMARK(GZIP_Decompress_Stream_Default_Level_ISO_Language_Set_3_Locations); BENCHMARK(GZIP_Compress_ISO_Language_Set_3_Schema); BENCHMARK(GZIP_Decompress_ISO_Language_Set_3_Schema); diff --git a/test/gzip/CMakeLists.txt b/test/gzip/CMakeLists.txt index 9759b09557..74714327df 100644 --- a/test/gzip/CMakeLists.txt +++ b/test/gzip/CMakeLists.txt @@ -1,9 +1,8 @@ sourcemeta_test(NAMESPACE sourcemeta PROJECT core NAME gzip SOURCES gzip_test.cc + gzip_error_test.cc gzip_streambuf_test.cc) target_link_libraries(sourcemeta_core_gzip_unit PRIVATE sourcemeta::core::gzip) -target_link_libraries(sourcemeta_core_gzip_unit - PRIVATE sourcemeta::core::io) diff --git a/test/gzip/gzip_error_test.cc b/test/gzip/gzip_error_test.cc new file mode 100644 index 0000000000..38ed06ea70 --- /dev/null +++ b/test/gzip/gzip_error_test.cc @@ -0,0 +1,1163 @@ +#include +#include + +#include // std::size_t +#include // std::uint8_t +#include // std::istream +#include // std::istreambuf_iterator +#include // std::numeric_limits +#include // std::istringstream +#include // std::string +#include // std::vector + +namespace { + +auto decompress_one_shot(const std::vector &input) + -> std::string { + return sourcemeta::core::gunzip(input.data(), input.size()); +} + +auto decompress_stream(const std::vector &input) -> std::string { + std::istringstream stream{std::string{input.cbegin(), input.cend()}}; + sourcemeta::core::GZIPStreamBuffer buffer{stream}; + std::istream decompressed{&buffer}; + std::string result; + result.assign(std::istreambuf_iterator(decompressed), + std::istreambuf_iterator()); + return result; +} + +auto decompress_one_shot_error(const std::vector &input, + const std::size_t output_hint = 0, + const std::size_t maximum_size = 268435456) + -> std::string { + try { + sourcemeta::core::gunzip(input.data(), input.size(), output_hint, + maximum_size); + } catch (const sourcemeta::core::GZIPError &error) { + return error.what(); + } + + FAIL(); +} + +auto decompress_stream_error(const std::vector &input) + -> std::string { + try { + decompress_stream(input); + } catch (const sourcemeta::core::GZIPError &error) { + return error.what(); + } + + FAIL(); +} + +auto compress_error(const int level) -> std::string { + const std::string input{"hello world"}; + try { + sourcemeta::core::gzip(reinterpret_cast(input.data()), + input.size(), level); + } catch (const sourcemeta::core::GZIPError &error) { + return error.what(); + } + + FAIL(); +} + +} // namespace + +TEST(compress_level_above_maximum) { + EXPECT_EQ(compress_error(13), "Could not allocate compressor"); +} + +TEST(compress_negative_level) { + EXPECT_EQ(compress_error(-2), "Could not allocate compressor"); +} + +TEST(compress_largest_integer_level) { + EXPECT_EQ(compress_error(std::numeric_limits::max()), + "Could not allocate compressor"); +} + +TEST(compress_smallest_integer_level) { + EXPECT_EQ(compress_error(std::numeric_limits::min()), + "Could not allocate compressor"); +} + +TEST(invalid_input) { + const std::vector input{ + 0x74, 0x68, 0x69, 0x73, 0x20, 0x69, 0x73, 0x20, 0x6e, 0x6f, 0x74, + 0x20, 0x67, 0x7a, 0x69, 0x70, 0x20, 0x64, 0x61, 0x74, 0x61}; + // TODO: Report the same error message from both mechanisms + EXPECT_EQ(decompress_one_shot_error(input), "Could not decompress input"); + EXPECT_EQ(decompress_stream_error(input), "Invalid gzip magic bytes"); +} + +TEST(empty_input) { + const std::vector input; + // TODO: Report the same error message from both mechanisms + EXPECT_EQ(decompress_one_shot_error(input), "Could not decompress input"); + EXPECT_EQ(decompress_stream_error(input), "Empty source stream"); +} + +TEST(single_identification_byte) { + const std::vector input{0x1f}; + // TODO: Report the same error message from both mechanisms + EXPECT_EQ(decompress_one_shot_error(input), "Could not decompress input"); + EXPECT_EQ(decompress_stream_error(input), "Unexpected end of source stream"); +} + +TEST(identification_bytes_only) { + const std::vector input{0x1f, 0x8b}; + // TODO: Report the same error message from both mechanisms + EXPECT_EQ(decompress_one_shot_error(input), "Could not decompress input"); + EXPECT_EQ(decompress_stream_error(input), "Unexpected end of source stream"); +} + +TEST(wrong_first_identification_byte) { + const std::vector input{ + 0x1e, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x01, 0x0b, + 0x00, 0xf4, 0xff, 0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x20, 0x77, 0x6f, 0x72, + 0x6c, 0x64, 0x85, 0x11, 0x4a, 0x0d, 0x0b, 0x00, 0x00, 0x00}; + // TODO: Report the same error message from both mechanisms + EXPECT_EQ(decompress_one_shot_error(input), "Could not decompress input"); + EXPECT_EQ(decompress_stream_error(input), "Invalid gzip magic bytes"); +} + +TEST(wrong_second_identification_byte) { + const std::vector input{ + 0x1f, 0x8c, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x01, 0x0b, + 0x00, 0xf4, 0xff, 0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x20, 0x77, 0x6f, 0x72, + 0x6c, 0x64, 0x85, 0x11, 0x4a, 0x0d, 0x0b, 0x00, 0x00, 0x00}; + // TODO: Report the same error message from both mechanisms + EXPECT_EQ(decompress_one_shot_error(input), "Could not decompress input"); + EXPECT_EQ(decompress_stream_error(input), "Invalid gzip magic bytes"); +} + +TEST(swapped_identification_bytes) { + const std::vector input{ + 0x8b, 0x1f, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x01, 0x0b, + 0x00, 0xf4, 0xff, 0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x20, 0x77, 0x6f, 0x72, + 0x6c, 0x64, 0x85, 0x11, 0x4a, 0x0d, 0x0b, 0x00, 0x00, 0x00}; + // TODO: Report the same error message from both mechanisms + EXPECT_EQ(decompress_one_shot_error(input), "Could not decompress input"); + EXPECT_EQ(decompress_stream_error(input), "Invalid gzip magic bytes"); +} + +TEST(reserved_compression_method_0) { + const std::vector input{ + 0x1f, 0x8b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x01, 0x0b, + 0x00, 0xf4, 0xff, 0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x20, 0x77, 0x6f, 0x72, + 0x6c, 0x64, 0x85, 0x11, 0x4a, 0x0d, 0x0b, 0x00, 0x00, 0x00}; + // TODO: Report the same error message from both mechanisms + EXPECT_EQ(decompress_one_shot_error(input), "Could not decompress input"); + EXPECT_EQ(decompress_stream_error(input), + "Unsupported gzip compression method"); +} + +TEST(reserved_compression_method_7) { + const std::vector input{ + 0x1f, 0x8b, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x01, 0x0b, + 0x00, 0xf4, 0xff, 0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x20, 0x77, 0x6f, 0x72, + 0x6c, 0x64, 0x85, 0x11, 0x4a, 0x0d, 0x0b, 0x00, 0x00, 0x00}; + // TODO: Report the same error message from both mechanisms + EXPECT_EQ(decompress_one_shot_error(input), "Could not decompress input"); + EXPECT_EQ(decompress_stream_error(input), + "Unsupported gzip compression method"); +} + +TEST(unknown_compression_method_9) { + const std::vector input{ + 0x1f, 0x8b, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x01, 0x0b, + 0x00, 0xf4, 0xff, 0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x20, 0x77, 0x6f, 0x72, + 0x6c, 0x64, 0x85, 0x11, 0x4a, 0x0d, 0x0b, 0x00, 0x00, 0x00}; + // TODO: Report the same error message from both mechanisms + EXPECT_EQ(decompress_one_shot_error(input), "Could not decompress input"); + EXPECT_EQ(decompress_stream_error(input), + "Unsupported gzip compression method"); +} + +TEST(reserved_flag_bit_5) { + const std::vector input{ + 0x1f, 0x8b, 0x08, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x01, 0x0b, + 0x00, 0xf4, 0xff, 0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x20, 0x77, 0x6f, 0x72, + 0x6c, 0x64, 0x85, 0x11, 0x4a, 0x0d, 0x0b, 0x00, 0x00, 0x00}; + // TODO: Report the same error message from both mechanisms + EXPECT_EQ(decompress_one_shot_error(input), "Could not decompress input"); + EXPECT_EQ(decompress_stream_error(input), + "Reserved gzip FLG bits must be zero"); +} + +TEST(reserved_flag_bit_6) { + const std::vector input{ + 0x1f, 0x8b, 0x08, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x01, 0x0b, + 0x00, 0xf4, 0xff, 0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x20, 0x77, 0x6f, 0x72, + 0x6c, 0x64, 0x85, 0x11, 0x4a, 0x0d, 0x0b, 0x00, 0x00, 0x00}; + // TODO: Report the same error message from both mechanisms + EXPECT_EQ(decompress_one_shot_error(input), "Could not decompress input"); + EXPECT_EQ(decompress_stream_error(input), + "Reserved gzip FLG bits must be zero"); +} + +TEST(reserved_flag_bit_7) { + const std::vector input{ + 0x1f, 0x8b, 0x08, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x01, 0x0b, + 0x00, 0xf4, 0xff, 0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x20, 0x77, 0x6f, 0x72, + 0x6c, 0x64, 0x85, 0x11, 0x4a, 0x0d, 0x0b, 0x00, 0x00, 0x00}; + // TODO: Report the same error message from both mechanisms + EXPECT_EQ(decompress_one_shot_error(input), "Could not decompress input"); + EXPECT_EQ(decompress_stream_error(input), + "Reserved gzip FLG bits must be zero"); +} + +TEST(reserved_flag_bit_alongside_ftext) { + const std::vector input{ + 0x1f, 0x8b, 0x08, 0x21, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x01, 0x0b, + 0x00, 0xf4, 0xff, 0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x20, 0x77, 0x6f, 0x72, + 0x6c, 0x64, 0x85, 0x11, 0x4a, 0x0d, 0x0b, 0x00, 0x00, 0x00}; + // TODO: Report the same error message from both mechanisms + EXPECT_EQ(decompress_one_shot_error(input), "Could not decompress input"); + EXPECT_EQ(decompress_stream_error(input), + "Reserved gzip FLG bits must be zero"); +} + +TEST(header_only) { + const std::vector input{0x1f, 0x8b, 0x08, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0xff}; + // TODO: Report the same error message from both mechanisms + EXPECT_EQ(decompress_one_shot_error(input), "Could not decompress input"); + EXPECT_EQ(decompress_stream_error(input), "Unexpected end of source stream"); +} + +TEST(header_and_trailer_without_blocks) { + const std::vector input{0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; + // TODO: Report the same error message from both mechanisms + EXPECT_EQ(decompress_one_shot_error(input), "Could not decompress input"); + EXPECT_EQ(decompress_stream_error(input), "Stored block LEN/NLEN mismatch"); +} + +TEST(truncated_fextra) { + const std::vector input{ + 0x1f, 0x8b, 0x08, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x64, + 0x00, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, + 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78}; + // TODO: Report the same error message from both mechanisms + EXPECT_EQ(decompress_one_shot_error(input), "Could not decompress input"); + EXPECT_EQ(decompress_stream_error(input), "Unexpected end of source stream"); +} + +TEST(fextra_swallowing_rest_of_member) { + const std::vector input{ + 0x1f, 0x8b, 0x08, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x18, 0x00, + 0x01, 0x0b, 0x00, 0xf4, 0xff, 0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x20, 0x77, + 0x6f, 0x72, 0x6c, 0x64, 0x85, 0x11, 0x4a, 0x0d, 0x0b, 0x00, 0x00, 0x00}; + // TODO: Report the same error message from both mechanisms + EXPECT_EQ(decompress_one_shot_error(input), "Could not decompress input"); + EXPECT_EQ(decompress_stream_error(input), "Unexpected end of source stream"); +} + +TEST(unterminated_fname) { + const std::vector input{ + 0x1f, 0x8b, 0x08, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, + 0x6e, 0x6e, 0x6e, 0x6e, 0x6e, 0x6e, 0x6e, 0x6e, 0x6e, 0x6e, + 0x6e, 0x6e, 0x6e, 0x6e, 0x6e, 0x6e, 0x6e, 0x6e, 0x6e, 0x6e, + 0x6e, 0x6e, 0x6e, 0x6e, 0x6e, 0x6e, 0x6e, 0x6e, 0x6e, 0x6e}; + // TODO: Report the same error message from both mechanisms + EXPECT_EQ(decompress_one_shot_error(input), "Could not decompress input"); + EXPECT_EQ(decompress_stream_error(input), "Unexpected end of source stream"); +} + +TEST(unterminated_fcomment) { + const std::vector input{ + 0x1f, 0x8b, 0x08, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, + 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, + 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, + 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63}; + // TODO: Report the same error message from both mechanisms + EXPECT_EQ(decompress_one_shot_error(input), "Could not decompress input"); + EXPECT_EQ(decompress_stream_error(input), "Unexpected end of source stream"); +} + +TEST(truncated_fhcrc) { + const std::vector input{0x1f, 0x8b, 0x08, 0x02, 0x00, 0x00, + 0x00, 0x00, 0x00, 0xff, 0x90}; + // TODO: Report the same error message from both mechanisms + EXPECT_EQ(decompress_one_shot_error(input), "Could not decompress input"); + EXPECT_EQ(decompress_stream_error(input), "Unexpected end of source stream"); +} + +TEST(fhcrc_mismatch) { + const std::vector input{ + 0x1f, 0x8b, 0x08, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x91, 0xc9, + 0x01, 0x0b, 0x00, 0xf4, 0xff, 0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x20, 0x77, + 0x6f, 0x72, 0x6c, 0x64, 0x85, 0x11, 0x4a, 0x0d, 0x0b, 0x00, 0x00, 0x00}; + // TODO: The one-shot mechanism skips the header checksum while the stream + // mechanism verifies it, and RFC 1952 section 2.3.1.2 permits either + EXPECT_EQ(decompress_one_shot(input), "hello world"); + EXPECT_EQ(decompress_stream_error(input), "FHCRC mismatch"); +} + +TEST(missing_trailer) { + const std::vector input{ + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xff, 0x01, 0x0b, 0x00, 0xf4, 0xff, 0x68, 0x65, 0x6c, + 0x6c, 0x6f, 0x20, 0x77, 0x6f, 0x72, 0x6c, 0x64}; + // TODO: Report the same error message from both mechanisms + EXPECT_EQ(decompress_one_shot_error(input), "Could not decompress input"); + EXPECT_EQ(decompress_stream_error(input), "Unexpected end of source stream"); +} + +TEST(trailer_missing_last_byte) { + const std::vector input{ + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x01, + 0x0b, 0x00, 0xf4, 0xff, 0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x20, 0x77, + 0x6f, 0x72, 0x6c, 0x64, 0x85, 0x11, 0x4a, 0x0d, 0x0b, 0x00, 0x00}; + // TODO: Report the same error message from both mechanisms + EXPECT_EQ(decompress_one_shot_error(input), "Could not decompress input"); + EXPECT_EQ(decompress_stream_error(input), "Unexpected end of source stream"); +} + +TEST(crc32_lowest_bit_mismatch) { + const std::vector input{ + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x01, 0x0b, + 0x00, 0xf4, 0xff, 0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x20, 0x77, 0x6f, 0x72, + 0x6c, 0x64, 0x84, 0x11, 0x4a, 0x0d, 0x0b, 0x00, 0x00, 0x00}; + // TODO: Report the same error message from both mechanisms + EXPECT_EQ(decompress_one_shot_error(input), "Could not decompress input"); + EXPECT_EQ(decompress_stream_error(input), "Gzip member CRC32 mismatch"); +} + +TEST(crc32_highest_bit_mismatch) { + const std::vector input{ + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x01, 0x0b, + 0x00, 0xf4, 0xff, 0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x20, 0x77, 0x6f, 0x72, + 0x6c, 0x64, 0x85, 0x11, 0x4a, 0x8d, 0x0b, 0x00, 0x00, 0x00}; + // TODO: Report the same error message from both mechanisms + EXPECT_EQ(decompress_one_shot_error(input), "Could not decompress input"); + EXPECT_EQ(decompress_stream_error(input), "Gzip member CRC32 mismatch"); +} + +TEST(isize_off_by_one) { + const std::vector input{ + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x01, 0x0b, + 0x00, 0xf4, 0xff, 0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x20, 0x77, 0x6f, 0x72, + 0x6c, 0x64, 0x85, 0x11, 0x4a, 0x0d, 0x0c, 0x00, 0x00, 0x00}; + // TODO: Report the same error message from both mechanisms + EXPECT_EQ(decompress_one_shot_error(input), "Could not decompress input"); + EXPECT_EQ(decompress_stream_error(input), "Gzip member ISIZE mismatch"); +} + +TEST(isize_highest_byte_mismatch) { + const std::vector input{ + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x01, 0x0b, + 0x00, 0xf4, 0xff, 0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x20, 0x77, 0x6f, 0x72, + 0x6c, 0x64, 0x85, 0x11, 0x4a, 0x0d, 0x0b, 0x00, 0x00, 0x01}; + // TODO: Report the same error message from both mechanisms + EXPECT_EQ(decompress_one_shot_error(input), "Could not decompress input"); + EXPECT_EQ(decompress_stream_error(input), "Gzip member ISIZE mismatch"); +} + +TEST(empty_payload_with_nonzero_crc32) { + const std::vector input{ + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x01, 0x00, + 0x00, 0xff, 0xff, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; + // TODO: Report the same error message from both mechanisms + EXPECT_EQ(decompress_one_shot_error(input), "Could not decompress input"); + EXPECT_EQ(decompress_stream_error(input), "Gzip member CRC32 mismatch"); +} + +TEST(swapped_trailer_fields) { + const std::vector input{ + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x01, 0x0b, + 0x00, 0xf4, 0xff, 0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x20, 0x77, 0x6f, 0x72, + 0x6c, 0x64, 0x0b, 0x00, 0x00, 0x00, 0x85, 0x11, 0x4a, 0x0d}; + // TODO: Report the same error message from both mechanisms + EXPECT_EQ(decompress_one_shot_error(input), "Could not decompress input"); + EXPECT_EQ(decompress_stream_error(input), "Gzip member CRC32 mismatch"); +} + +TEST(truncated_compressed_blocks) { + const std::vector input{ + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0xff, 0x01, + 0x29, 0x00, 0xd6, 0xff, 0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x20, 0x77, + 0x6f, 0x72, 0x6c, 0x64, 0x2c, 0x20, 0x74, 0x68, 0x69, 0x73}; + // TODO: Report the same error message from both mechanisms + EXPECT_EQ(decompress_one_shot_error(input), "Could not decompress input"); + EXPECT_EQ(decompress_stream_error(input), "Unexpected end of source stream"); +} + +TEST(truncated_stored_block_payload) { + std::vector input{0x1f, 0x8b, 0x08, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0xff, + 0x01, 0xe8, 0x03, 0x17, 0xfc}; + for (std::size_t index = 0; index < 497; ++index) { + input.push_back(0x61); + } + + // TODO: Report the same error message from both mechanisms + EXPECT_EQ(decompress_one_shot_error(input), "Could not decompress input"); + EXPECT_EQ(decompress_stream_error(input), "Unexpected end of source stream"); +} + +TEST(stored_block_nlen_mismatch) { + const std::vector input{ + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x01, 0x0b, + 0x00, 0x00, 0x00, 0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x20, 0x77, 0x6f, 0x72, + 0x6c, 0x64, 0x85, 0x11, 0x4a, 0x0d, 0x0b, 0x00, 0x00, 0x00}; + // TODO: Report the same error message from both mechanisms + EXPECT_EQ(decompress_one_shot_error(input), "Could not decompress input"); + EXPECT_EQ(decompress_stream_error(input), "Stored block LEN/NLEN mismatch"); +} + +TEST(stored_block_nlen_equal_to_len) { + const std::vector input{ + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x01, 0xff, + 0x00, 0xff, 0x00, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, + 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, + 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, + 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, + 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, + 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, + 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, + 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, + 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, + 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, + 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, + 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, + 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, + 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, + 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, + 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, + 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, + 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, + 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, + 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, + 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, + 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x3d, 0x0b, 0xc4, 0xa2, 0xff, 0x00, + 0x00, 0x00}; + // TODO: Report the same error message from both mechanisms + EXPECT_EQ(decompress_one_shot_error(input), "Could not decompress input"); + EXPECT_EQ(decompress_stream_error(input), "Stored block LEN/NLEN mismatch"); +} + +TEST(stored_block_len_beyond_input) { + const std::vector input{ + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x01, + 0x64, 0x00, 0x9b, 0xff, 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, + 0x37, 0x38, 0x39, 0xc6, 0xc7, 0x84, 0xa6, 0x0a, 0x00, 0x00, 0x00}; + // TODO: Report the same error message from both mechanisms + EXPECT_EQ(decompress_one_shot_error(input), "Could not decompress input"); + EXPECT_EQ(decompress_stream_error(input), "Unexpected end of source stream"); +} + +TEST(reserved_block_type) { + const std::vector input{ + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, + 0x07, 0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x20, 0x77, 0x6f, 0x72, + 0x6c, 0x64, 0x85, 0x11, 0x4a, 0x0d, 0x0b, 0x00, 0x00, 0x00}; + // TODO: Report the same error message from both mechanisms + EXPECT_EQ(decompress_one_shot_error(input), "Could not decompress input"); + EXPECT_EQ(decompress_stream_error(input), "Reserved deflate block type"); +} + +TEST(reserved_block_type_after_valid_block) { + const std::vector input{ + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x00, 0x05, + 0x00, 0xfa, 0xff, 0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x07, 0x20, 0x77, 0x6f, + 0x72, 0x6c, 0x64, 0x85, 0x11, 0x4a, 0x0d, 0x0b, 0x00, 0x00, 0x00}; + // TODO: Report the same error message from both mechanisms + EXPECT_EQ(decompress_one_shot_error(input), "Could not decompress input"); + EXPECT_EQ(decompress_stream_error(input), "Reserved deflate block type"); +} + +TEST(missing_final_block) { + const std::vector input{ + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, + 0x00, 0x05, 0x00, 0xfa, 0xff, 0x68, 0x65, 0x6c, 0x6c, 0x6f, + 0x86, 0xa6, 0x10, 0x36, 0x05, 0x00, 0x00, 0x00}; + // TODO: Report the same error message from both mechanisms + EXPECT_EQ(decompress_one_shot_error(input), "Could not decompress input"); + EXPECT_EQ(decompress_stream_error(input), "Reserved deflate block type"); +} + +TEST(fixed_block_match_before_any_output) { + const std::vector input{ + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x03, + 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; + // TODO: Report the same error message from both mechanisms + EXPECT_EQ(decompress_one_shot_error(input), "Could not decompress input"); + EXPECT_EQ(decompress_stream_error(input), + "Backref distance exceeds bytes available"); +} + +TEST(fixed_block_distance_beyond_output) { + const std::vector input{ + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x4b, + 0x04, 0x42, 0x00, 0x45, 0xe5, 0x98, 0xad, 0x04, 0x00, 0x00, 0x00}; + // TODO: Report the same error message from both mechanisms + EXPECT_EQ(decompress_one_shot_error(input), "Could not decompress input"); + EXPECT_EQ(decompress_stream_error(input), + "Backref distance exceeds bytes available"); +} + +TEST(fixed_block_maximum_distance_one_beyond_output) { + std::vector input{0x1f, 0x8b, 0x08, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0xff, + 0x00, 0xff, 0x7f, 0x00, 0x80}; + for (std::size_t index = 0; index < 32767; ++index) { + input.push_back(static_cast((index ^ (index >> 8)) & 0xff)); + } + + input.insert(input.end(), {0x03, 0xde, 0xff, 0x0f, 0x00, 0x12, 0x5c, 0x3a, + 0x51, 0xff, 0x7f, 0x00, 0x00}); + // TODO: Report the same error message from both mechanisms + EXPECT_EQ(decompress_one_shot_error(input), "Could not decompress input"); + EXPECT_EQ(decompress_stream_error(input), + "Backref distance exceeds bytes available"); +} + +TEST(match_into_previous_member) { + const std::vector input{ + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, + 0x01, 0x05, 0x00, 0xfa, 0xff, 0x68, 0x65, 0x6c, 0x6c, 0x6f, + 0x86, 0xa6, 0x10, 0x36, 0x05, 0x00, 0x00, 0x00, 0x1f, 0x8b, + 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x03, 0x13, + 0x00, 0x86, 0xa6, 0x10, 0x36, 0x05, 0x00, 0x00, 0x00}; + // TODO: Report the same error message from both mechanisms + EXPECT_EQ(decompress_one_shot_error(input), "Could not decompress input"); + EXPECT_EQ(decompress_stream_error(input), + "Backref distance exceeds bytes available"); +} + +TEST(fixed_block_unassigned_distance_code) { + const std::vector input{ + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x4b, 0x04, + 0x3e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; + // TODO: Report the same error message from both mechanisms + EXPECT_EQ(decompress_one_shot_error(input), "Could not decompress input"); + EXPECT_EQ(decompress_stream_error(input), "Invalid Huffman code"); +} + +TEST(fixed_block_invalid_literal_length_symbol) { + const std::vector input{ + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x1b, + 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; + // TODO: Report the same error message from both mechanisms + EXPECT_EQ(decompress_one_shot_error(input), "Could not decompress input"); + EXPECT_EQ(decompress_stream_error(input), "Invalid literal/length code"); +} + +TEST(fixed_block_literal_length_symbol_286) { + const std::vector input{ + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x4b, + 0x1c, 0x03, 0x00, 0x56, 0xfa, 0xc2, 0x34, 0x03, 0x01, 0x00, 0x00}; + // TODO: The one-shot mechanism decodes literal/length symbols 286 and 287 + // as a match of length 258, but RFC 1951 section 3.2.6 states that they + // never occur in compressed data + EXPECT_EQ(decompress_one_shot(input), std::string(259, 'a')); + EXPECT_EQ(decompress_stream_error(input), "Invalid literal/length code"); +} + +TEST(fixed_block_literal_length_symbol_287) { + const std::vector input{ + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x4b, + 0x1c, 0x07, 0x00, 0x56, 0xfa, 0xc2, 0x34, 0x03, 0x01, 0x00, 0x00}; + // TODO: The one-shot mechanism decodes literal/length symbols 286 and 287 + // as a match of length 258, but RFC 1951 section 3.2.6 states that they + // never occur in compressed data + EXPECT_EQ(decompress_one_shot(input), std::string(259, 'a')); + EXPECT_EQ(decompress_stream_error(input), "Invalid literal/length code"); +} + +TEST(fixed_block_distance_code_30) { + std::vector input{0x1f, 0x8b, 0x08, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0xff, + 0x00, 0x00, 0x80, 0xff, 0x7f}; + std::string expected; + for (std::size_t index = 0; index < 32768; ++index) { + const auto byte{static_cast((index ^ (index >> 8)) & 0xff)}; + input.push_back(byte); + expected.push_back(static_cast(byte)); + } + + input.insert(input.end(), {0x03, 0x3e, 0x00, 0x00, 0x00, 0x65, 0xd8, 0xb7, + 0x34, 0x03, 0x80, 0x00, 0x00}); + expected += "\xe0\x20\x21"; + // TODO: The one-shot mechanism decodes distance codes 30 and 31 as + // distances beyond 24576, but RFC 1951 section 3.2.6 states that they never + // occur in compressed data + EXPECT_EQ(decompress_one_shot(input), expected); + EXPECT_EQ(decompress_stream_error(input), "Invalid Huffman code"); +} + +TEST(fixed_block_distance_code_31) { + std::vector input{0x1f, 0x8b, 0x08, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0xff, + 0x00, 0x00, 0x80, 0xff, 0x7f}; + std::string expected; + for (std::size_t index = 0; index < 32768; ++index) { + const auto byte{static_cast((index ^ (index >> 8)) & 0xff)}; + input.push_back(byte); + expected.push_back(static_cast(byte)); + } + + input.insert(input.end(), {0x03, 0x7e, 0x00, 0x00, 0x00, 0x65, 0xd8, 0xb7, + 0x34, 0x03, 0x80, 0x00, 0x00}); + expected += "\xe0\x20\x21"; + // TODO: The one-shot mechanism decodes distance codes 30 and 31 as + // distances beyond 24576, but RFC 1951 section 3.2.6 states that they never + // occur in compressed data + EXPECT_EQ(decompress_one_shot(input), expected); + EXPECT_EQ(decompress_stream_error(input), "Invalid Huffman code"); +} + +TEST(dynamic_block_distance_code_30) { + std::vector input{0x1f, 0x8b, 0x08, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0xff, + 0x00, 0x00, 0x80, 0xff, 0x7f}; + std::string expected; + for (std::size_t index = 0; index < 32768; ++index) { + const auto byte{static_cast((index ^ (index >> 8)) & 0xff)}; + input.push_back(byte); + expected.push_back(static_cast(byte)); + } + + input.insert( + input.end(), + {0x0d, 0xfe, 0x01, 0x90, 0x24, 0x49, 0x92, 0x24, 0x49, 0x02, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x22, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xe0, 0x00, 0x00, + 0x65, 0xd8, 0xb7, 0x34, 0x03, 0x80, 0x00, 0x00}); + expected += "\xe0\x20\x21"; + // TODO: The one-shot mechanism decodes distance codes 30 and 31 as + // distances beyond 24576, but RFC 1951 section 3.2.5 only defines distance + // codes up to 29 + EXPECT_EQ(decompress_one_shot(input), expected); + EXPECT_EQ(decompress_stream_error(input), "Invalid distance code"); +} + +TEST(dynamic_block_287_literal_length_codes) { + const std::vector input{ + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xf5, 0xe0, + 0x01, 0x90, 0x24, 0x49, 0x92, 0x24, 0x49, 0x02, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x43, 0xbe, 0xb7, 0xe8, + 0x01, 0x00, 0x00, 0x00}; + // TODO: The one-shot mechanism accepts more literal/length codes than the + // maximum of 286 in RFC 1951 section 3.2.7 + EXPECT_EQ(decompress_one_shot(input), "a"); + EXPECT_EQ(decompress_stream_error(input), "Too many literal/length codes"); +} + +TEST(dynamic_block_288_literal_length_codes) { + const std::vector input{ + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xfd, 0xe0, + 0x01, 0x90, 0x24, 0x49, 0x92, 0x24, 0x49, 0x02, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x43, 0xbe, 0xb7, 0xe8, + 0x01, 0x00, 0x00, 0x00}; + // TODO: The one-shot mechanism accepts more literal/length codes than the + // maximum of 286 in RFC 1951 section 3.2.7 + EXPECT_EQ(decompress_one_shot(input), "a"); + EXPECT_EQ(decompress_stream_error(input), "Too many literal/length codes"); +} + +TEST(dynamic_block_unused_code_of_single_distance_code) { + const std::vector input{ + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x0d, 0xe0, + 0x01, 0x90, 0x24, 0x49, 0x92, 0x24, 0x49, 0x02, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x10, 0x21, 0x3e, 0x77, 0x80, 0x7b, 0x4c, 0x05, 0x00, + 0x00, 0x00}; + // TODO: The one-shot mechanism decodes the unused code of a single code + // alphabet from RFC 1951 section 3.2.7 as the used code + EXPECT_EQ(decompress_one_shot(input), "abbbb"); + EXPECT_EQ(decompress_stream_error(input), "Invalid Huffman code"); +} + +TEST(dynamic_block_unused_code_of_single_literal_length_code) { + const std::vector input{ + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x05, 0xe0, + 0x01, 0x90, 0x24, 0x49, 0x92, 0x24, 0x49, 0x02, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x20, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00}; + // TODO: The one-shot mechanism decodes the unused code of a single code + // alphabet from RFC 1951 section 3.2.7 as the used code + EXPECT_EQ(decompress_one_shot(input), ""); + EXPECT_EQ(decompress_stream_error(input), "Invalid Huffman code"); +} + +TEST(dynamic_block_incomplete_literal_length_code) { + const std::vector input{ + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x05, 0xe0, + 0x01, 0x90, 0x24, 0x49, 0x92, 0x24, 0x49, 0x02, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x10, 0x20, 0x43, 0xbe, 0xb7, 0xe8, 0x01, 0x00, 0x00, + 0x00}; + // TODO: Report the same error message from both mechanisms + EXPECT_EQ(decompress_one_shot_error(input), "Could not decompress input"); + EXPECT_EQ(decompress_stream_error(input), "Incomplete Huffman code"); +} + +TEST(dynamic_block_incomplete_distance_code) { + const std::vector input{ + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x0d, 0xe1, + 0x01, 0x90, 0x24, 0x49, 0x92, 0x24, 0x49, 0x02, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x10, 0x11, 0x31, 0x01, 0x45, 0xe5, 0x98, 0xad, 0x04, + 0x00, 0x00, 0x00}; + // TODO: Report the same error message from both mechanisms + EXPECT_EQ(decompress_one_shot_error(input), "Could not decompress input"); + EXPECT_EQ(decompress_stream_error(input), "Incomplete Huffman code"); +} + +TEST(dynamic_block_incomplete_code_length_code) { + const std::vector input{ + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x05, 0xc0, + 0x01, 0x08, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x11, + 0x43, 0xbe, 0xb7, 0xe8, 0x01, 0x00, 0x00, 0x00}; + // TODO: Report the same error message from both mechanisms + EXPECT_EQ(decompress_one_shot_error(input), "Could not decompress input"); + EXPECT_EQ(decompress_stream_error(input), "Incomplete Huffman code"); +} + +TEST(dynamic_block_over_subscribed_literal_length_code) { + const std::vector input{ + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x05, 0xe0, + 0x01, 0x90, 0x24, 0x49, 0x92, 0x24, 0x49, 0x02, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x22, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x43, 0xbe, 0xb7, 0xe8, 0x01, + 0x00, 0x00, 0x00}; + // TODO: Report the same error message from both mechanisms + EXPECT_EQ(decompress_one_shot_error(input), "Could not decompress input"); + EXPECT_EQ(decompress_stream_error(input), "Over-subscribed Huffman code"); +} + +TEST(dynamic_block_over_subscribed_distance_code) { + const std::vector input{ + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x0d, 0xe2, + 0x01, 0x90, 0x24, 0x49, 0x92, 0x24, 0x49, 0x02, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x10, 0x21, 0x22, 0x00, 0x00, 0x43, 0xbe, 0xb7, 0xe8, + 0x01, 0x00, 0x00, 0x00}; + // TODO: Report the same error message from both mechanisms + EXPECT_EQ(decompress_one_shot_error(input), "Could not decompress input"); + EXPECT_EQ(decompress_stream_error(input), "Over-subscribed Huffman code"); +} + +TEST(dynamic_block_over_subscribed_code_length_code) { + const std::vector input{ + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, + 0x05, 0x00, 0x92, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; + // TODO: Report the same error message from both mechanisms + EXPECT_EQ(decompress_one_shot_error(input), "Could not decompress input"); + EXPECT_EQ(decompress_stream_error(input), "Over-subscribed Huffman code"); +} + +TEST(dynamic_block_repeat_previous_without_previous) { + const std::vector input{ + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, + 0x05, 0x00, 0x02, 0xe4, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; + // TODO: Report the same error message from both mechanisms + EXPECT_EQ(decompress_one_shot_error(input), "Could not decompress input"); + EXPECT_EQ(decompress_stream_error(input), + "Repeat-previous code length with no previous"); +} + +TEST(dynamic_block_repeat_zero_beyond_code_length_count) { + const std::vector input{ + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x05, + 0x00, 0x80, 0xe4, 0xff, 0x1f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; + // TODO: Report the same error message from both mechanisms + EXPECT_EQ(decompress_one_shot_error(input), "Could not decompress input"); + EXPECT_EQ(decompress_stream_error(input), "Code length count overflow"); +} + +TEST(dynamic_block_repeat_previous_beyond_code_length_count) { + const std::vector input{ + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x05, + 0x00, 0x82, 0xe0, 0x3f, 0x1b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; + // TODO: Report the same error message from both mechanisms + EXPECT_EQ(decompress_one_shot_error(input), "Could not decompress input"); + EXPECT_EQ(decompress_stream_error(input), "Code length count overflow"); +} + +TEST(dynamic_block_code_length_overshoot) { + const std::vector input{ + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x03, 0x05, 0x00, 0x80, 0xe4, 0xff, 0x1f, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; + // TODO: Report the same error message from both mechanisms + EXPECT_EQ(decompress_one_shot_error(input), "Could not decompress input"); + EXPECT_EQ(decompress_stream_error(input), "Code length count overflow"); +} + +TEST(dynamic_block_code_length_cap_overflow) { + const std::vector input{ + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x03, 0xed, 0x1f, 0x80, 0xe4, 0xff, 0xff, 0x1f, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; + // TODO: Report the same error message from both mechanisms + EXPECT_EQ(decompress_one_shot_error(input), "Could not decompress input"); + EXPECT_EQ(decompress_stream_error(input), "Code length count overflow"); +} + +TEST(dynamic_block_repeat_previous_cap_overflow) { + const std::vector input{ + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, + 0xed, 0x1f, 0x84, 0x28, 0x7f, 0xff, 0xff, 0xff, 0xff, 0x79, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; + // TODO: Report the same error message from both mechanisms + EXPECT_EQ(decompress_one_shot_error(input), "Could not decompress input"); + EXPECT_EQ(decompress_stream_error(input), "Code length count overflow"); +} + +TEST(dynamic_block_repeat_zero_cap_overflow) { + const std::vector input{ + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, + 0xed, 0x1f, 0x20, 0xe5, 0xff, 0xff, 0xde, 0x7b, 0xef, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; + // TODO: Report the same error message from both mechanisms + EXPECT_EQ(decompress_one_shot_error(input), "Could not decompress input"); + EXPECT_EQ(decompress_stream_error(input), "Code length count overflow"); +} + +TEST(dynamic_block_without_end_of_block_code) { + const std::vector input{ + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x05, 0xe0, + 0x01, 0x90, 0x24, 0x49, 0x92, 0x24, 0x49, 0x02, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x22, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x55, 0x65, 0xb4, 0x89, 0x40, 0x00, 0x00, 0x00}; + // TODO: Report the same error message from both mechanisms + EXPECT_EQ(decompress_one_shot_error(input), "Could not decompress input"); + EXPECT_EQ(decompress_stream_error(input), "Unexpected end of source stream"); +} + +TEST(second_member_crc32_mismatch) { + const std::vector input{ + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0xff, 0x01, 0x05, + 0x00, 0xfa, 0xff, 0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x86, 0xa6, 0x10, 0x36, + 0x05, 0x00, 0x00, 0x00, 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x04, 0xff, 0x01, 0x05, 0x00, 0xfa, 0xff, 0x77, 0x6f, 0x72, 0x6c, 0x64, + 0x42, 0x11, 0x77, 0x3a, 0x05, 0x00, 0x00, 0x00}; + // TODO: Report the same error message from both mechanisms + EXPECT_EQ(decompress_one_shot_error(input), "Could not decompress input"); + EXPECT_EQ(decompress_stream_error(input), "Gzip member CRC32 mismatch"); +} + +TEST(second_member_truncated) { + const std::vector input{ + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x01, + 0x0b, 0x00, 0xf4, 0xff, 0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x20, 0x77, + 0x6f, 0x72, 0x6c, 0x64, 0x85, 0x11, 0x4a, 0x0d, 0x0b, 0x00, 0x00, + 0x00, 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, + 0x01, 0x0b, 0x00, 0xf4, 0xff, 0x68, 0x65, 0x6c, 0x6c, 0x6f}; + // TODO: Report the same error message from both mechanisms + EXPECT_EQ(decompress_one_shot_error(input), "Could not decompress input"); + EXPECT_EQ(decompress_stream_error(input), "Unexpected end of source stream"); +} + +TEST(second_member_reserved_block_type) { + const std::vector input{ + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x01, + 0x0b, 0x00, 0xf4, 0xff, 0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x20, 0x77, + 0x6f, 0x72, 0x6c, 0x64, 0x85, 0x11, 0x4a, 0x0d, 0x0b, 0x00, 0x00, + 0x00, 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, + 0x07, 0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x20, 0x77, 0x6f, 0x72, 0x6c, + 0x64, 0x85, 0x11, 0x4a, 0x0d, 0x0b, 0x00, 0x00, 0x00}; + // TODO: Report the same error message from both mechanisms + EXPECT_EQ(decompress_one_shot_error(input), "Could not decompress input"); + EXPECT_EQ(decompress_stream_error(input), "Reserved deflate block type"); +} + +TEST(member_without_trailer_followed_by_member) { + const std::vector input{ + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x01, 0x05, + 0x00, 0xfa, 0xff, 0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x1f, 0x8b, 0x08, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x04, 0xff, 0x01, 0x05, 0x00, 0xfa, 0xff, 0x77, + 0x6f, 0x72, 0x6c, 0x64, 0x43, 0x11, 0x77, 0x3a, 0x05, 0x00, 0x00, 0x00}; + // TODO: Report the same error message from both mechanisms + EXPECT_EQ(decompress_one_shot_error(input), "Could not decompress input"); + EXPECT_EQ(decompress_stream_error(input), "Gzip member CRC32 mismatch"); +} + +TEST(valid_member_after_corrupt_member) { + const std::vector input{ + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0xff, 0x01, 0x05, + 0x00, 0xfa, 0xff, 0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x86, 0xa6, 0x10, 0x36, + 0x05, 0x00, 0x00, 0x7f, 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x04, 0xff, 0x01, 0x05, 0x00, 0xfa, 0xff, 0x77, 0x6f, 0x72, 0x6c, 0x64, + 0x43, 0x11, 0x77, 0x3a, 0x05, 0x00, 0x00, 0x00}; + // TODO: Report the same error message from both mechanisms + EXPECT_EQ(decompress_one_shot_error(input), "Could not decompress input"); + EXPECT_EQ(decompress_stream_error(input), "Gzip member ISIZE mismatch"); +} + +TEST(trailing_header_without_blocks) { + const std::vector input{ + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x01, + 0x0b, 0x00, 0xf4, 0xff, 0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x20, 0x77, + 0x6f, 0x72, 0x6c, 0x64, 0x85, 0x11, 0x4a, 0x0d, 0x0b, 0x00, 0x00, + 0x00, 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff}; + // TODO: Report the same error message from both mechanisms + EXPECT_EQ(decompress_one_shot_error(input), "Could not decompress input"); + EXPECT_EQ(decompress_stream_error(input), "Unexpected end of source stream"); +} + +TEST(trailing_identification_bytes_only) { + const std::vector input{ + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x01, 0x0b, + 0x00, 0xf4, 0xff, 0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x20, 0x77, 0x6f, 0x72, + 0x6c, 0x64, 0x85, 0x11, 0x4a, 0x0d, 0x0b, 0x00, 0x00, 0x00, 0x1f, 0x8b}; + // TODO: The one-shot mechanism treats trailing data that starts with the + // gzip identification bytes as a corrupt member, while the stream mechanism + // ignores it as trailing garbage + EXPECT_EQ(decompress_one_shot_error(input), "Could not decompress input"); + EXPECT_EQ(decompress_stream(input), "hello world"); +} + +TEST(trailing_identification_bytes_with_invalid_compression_method) { + const std::vector input{ + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x01, + 0x0b, 0x00, 0xf4, 0xff, 0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x20, 0x77, + 0x6f, 0x72, 0x6c, 0x64, 0x85, 0x11, 0x4a, 0x0d, 0x0b, 0x00, 0x00, + 0x00, 0x1f, 0x8b, 0x63, 0x6f, 0x72, 0x72, 0x75, 0x70, 0x74}; + // TODO: The one-shot mechanism treats trailing data that starts with the + // gzip identification bytes as a corrupt member, while the stream mechanism + // ignores it as trailing garbage + EXPECT_EQ(decompress_one_shot_error(input), "Could not decompress input"); + EXPECT_EQ(decompress_stream(input), "hello world"); +} + +TEST(trailing_identification_bytes_with_truncated_fname) { + const std::vector input{ + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x01, 0x0b, + 0x00, 0xf4, 0xff, 0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x20, 0x77, 0x6f, 0x72, + 0x6c, 0x64, 0x85, 0x11, 0x4a, 0x0d, 0x0b, 0x00, 0x00, 0x00, 0x1f, 0x8b, + 0x08, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x61, 0x62, 0x63}; + // TODO: The one-shot mechanism treats trailing data that starts with the + // gzip identification bytes as a corrupt member, while the stream mechanism + // ignores it as trailing garbage + EXPECT_EQ(decompress_one_shot_error(input), "Could not decompress input"); + EXPECT_EQ(decompress_stream(input), "hello world"); +} + +TEST(second_member_reserved_flag_bit) { + const std::vector input{ + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x01, 0x0b, + 0x00, 0xf4, 0xff, 0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x20, 0x77, 0x6f, 0x72, + 0x6c, 0x64, 0x85, 0x11, 0x4a, 0x0d, 0x0b, 0x00, 0x00, 0x00, 0x1f, 0x8b, + 0x08, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x01, 0x0b, 0x00, 0xf4, + 0xff, 0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x20, 0x77, 0x6f, 0x72, 0x6c, 0x64, + 0x85, 0x11, 0x4a, 0x0d, 0x0b, 0x00, 0x00, 0x00}; + // TODO: The one-shot mechanism treats trailing data that starts with the + // gzip identification bytes as a corrupt member, while the stream mechanism + // ignores it as trailing garbage + EXPECT_EQ(decompress_one_shot_error(input), "Could not decompress input"); + EXPECT_EQ(decompress_stream(input), "hello world"); +} + +TEST(second_member_unsupported_compression_method) { + const std::vector input{ + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x01, 0x0b, + 0x00, 0xf4, 0xff, 0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x20, 0x77, 0x6f, 0x72, + 0x6c, 0x64, 0x85, 0x11, 0x4a, 0x0d, 0x0b, 0x00, 0x00, 0x00, 0x1f, 0x8b, + 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x01, 0x0b, 0x00, 0xf4, + 0xff, 0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x20, 0x77, 0x6f, 0x72, 0x6c, 0x64, + 0x85, 0x11, 0x4a, 0x0d, 0x0b, 0x00, 0x00, 0x00}; + // TODO: The one-shot mechanism treats trailing data that starts with the + // gzip identification bytes as a corrupt member, while the stream mechanism + // ignores it as trailing garbage + EXPECT_EQ(decompress_one_shot_error(input), "Could not decompress input"); + EXPECT_EQ(decompress_stream(input), "hello world"); +} + +TEST(second_member_fhcrc_mismatch) { + const std::vector input{ + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x01, 0x0b, + 0x00, 0xf4, 0xff, 0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x20, 0x77, 0x6f, 0x72, + 0x6c, 0x64, 0x85, 0x11, 0x4a, 0x0d, 0x0b, 0x00, 0x00, 0x00, 0x1f, 0x8b, + 0x08, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x91, 0xc9, 0x01, 0x0b, + 0x00, 0xf4, 0xff, 0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x20, 0x77, 0x6f, 0x72, + 0x6c, 0x64, 0x85, 0x11, 0x4a, 0x0d, 0x0b, 0x00, 0x00, 0x00}; + // TODO: The one-shot mechanism skips the header checksum of a later member + // and decodes it, while the stream mechanism ignores that member as + // trailing garbage + EXPECT_EQ(decompress_one_shot(input), "hello worldhello world"); + EXPECT_EQ(decompress_stream(input), "hello world"); +} + +TEST(maximum_size_exceeded_by_one_byte) { + const std::vector input{ + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x01, 0x0b, + 0x00, 0xf4, 0xff, 0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x20, 0x77, 0x6f, 0x72, + 0x6c, 0x64, 0x85, 0x11, 0x4a, 0x0d, 0x0b, 0x00, 0x00, 0x00}; + EXPECT_EQ(decompress_one_shot_error(input, 0, 10), + "Decompressed output exceeds the maximum allowed size"); +} + +TEST(zero_maximum_size_with_non_empty_payload) { + const std::vector input{ + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x01, 0x0b, + 0x00, 0xf4, 0xff, 0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x20, 0x77, 0x6f, 0x72, + 0x6c, 0x64, 0x85, 0x11, 0x4a, 0x0d, 0x0b, 0x00, 0x00, 0x00}; + EXPECT_EQ(decompress_one_shot_error(input, 0, 0), + "Decompressed output exceeds the maximum allowed size"); +} + +TEST(output_hint_above_maximum_size_with_exceeding_output) { + const std::vector input{ + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x01, 0x0b, + 0x00, 0xf4, 0xff, 0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x20, 0x77, 0x6f, 0x72, + 0x6c, 0x64, 0x85, 0x11, 0x4a, 0x0d, 0x0b, 0x00, 0x00, 0x00}; + EXPECT_EQ(decompress_one_shot_error(input, 1048576, 10), + "Decompressed output exceeds the maximum allowed size"); +} + +TEST(growth_beyond_uneven_maximum_size) { + const std::vector input{ + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x01, 0x0b, + 0x00, 0xf4, 0xff, 0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x20, 0x77, 0x6f, 0x72, + 0x6c, 0x64, 0x85, 0x11, 0x4a, 0x0d, 0x0b, 0x00, 0x00, 0x00}; + EXPECT_EQ(decompress_one_shot_error(input, 3, 10), + "Decompressed output exceeds the maximum allowed size"); +} + +TEST(crafted_bomb_beyond_maximum_size) { + const std::vector input{ + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x4b, 0x1c, + 0x05, 0xa3, 0x60, 0x14, 0x8c, 0x82, 0x51, 0x30, 0x0a, 0x46, 0xc1, 0x28, + 0x18, 0x05, 0xa3, 0x60, 0x14, 0x8c, 0x82, 0x51, 0x30, 0x0a, 0x46, 0xc1, + 0x28, 0x18, 0x05, 0x00, 0xa1, 0x87, 0xcc, 0x71, 0x23, 0x11, 0x00, 0x00}; + EXPECT_EQ(decompress_one_shot_error(input, 0, 4386), + "Decompressed output exceeds the maximum allowed size"); +} + +TEST(members_totaling_one_byte_over_maximum_size) { + const std::vector input{ + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x01, 0x0b, + 0x00, 0xf4, 0xff, 0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x20, 0x77, 0x6f, 0x72, + 0x6c, 0x64, 0x85, 0x11, 0x4a, 0x0d, 0x0b, 0x00, 0x00, 0x00, 0x1f, 0x8b, + 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x01, 0x0b, 0x00, 0xf4, + 0xff, 0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x20, 0x77, 0x6f, 0x72, 0x6c, 0x64, + 0x85, 0x11, 0x4a, 0x0d, 0x0b, 0x00, 0x00, 0x00}; + EXPECT_EQ(decompress_one_shot_error(input, 0, 21), + "Decompressed output exceeds the maximum allowed size"); +} + +TEST(second_member_alone_exceeding_maximum_size) { + const std::vector input{ + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x01, 0x00, + 0x00, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1f, + 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x01, 0x0b, 0x00, + 0xf4, 0xff, 0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x20, 0x77, 0x6f, 0x72, 0x6c, + 0x64, 0x85, 0x11, 0x4a, 0x0d, 0x0b, 0x00, 0x00, 0x00}; + EXPECT_EQ(decompress_one_shot_error(input, 0, 5), + "Decompressed output exceeds the maximum allowed size"); +} diff --git a/test/gzip/gzip_streambuf_test.cc b/test/gzip/gzip_streambuf_test.cc index f58b6ab462..87a48e318a 100644 --- a/test/gzip/gzip_streambuf_test.cc +++ b/test/gzip/gzip_streambuf_test.cc @@ -1,5 +1,4 @@ #include -#include #include #include // std::array @@ -8,71 +7,9 @@ #include // std::memcmp #include // std::istream #include // std::istreambuf_iterator -#include // std::mt19937, std::uniform_int_distribution #include // std::istringstream #include // std::string -namespace { - -auto decompress_via_stream(std::istream &compressed) -> std::string { - sourcemeta::core::GZIPStreamBuffer buffer{compressed}; - std::istream decompressed{&buffer}; - std::string result; - result.assign(std::istreambuf_iterator(decompressed), - std::istreambuf_iterator()); - return result; -} - -auto decompress_via_stream(const std::string &compressed) -> std::string { - std::istringstream stream{compressed}; - return decompress_via_stream(stream); -} - -} // namespace - -TEST(empty) { - const std::string input; - const auto compressed{sourcemeta::core::gzip( - reinterpret_cast(input.data()), input.size())}; - const auto result{decompress_via_stream(compressed)}; - EXPECT_TRUE(result.empty()); -} - -TEST(hello_world) { - const std::string input{"hello world"}; - const auto compressed{sourcemeta::core::gzip( - reinterpret_cast(input.data()), input.size())}; - const auto result{decompress_via_stream(compressed)}; - EXPECT_EQ(result, input); -} - -TEST(large_input) { - const std::string pattern{"The quick brown fox jumps over the lazy dog. "}; - std::string input; - for (int index = 0; index < 1000; ++index) { - input += pattern; - } - - const auto compressed{sourcemeta::core::gzip( - reinterpret_cast(input.data()), input.size())}; - const auto result{decompress_via_stream(compressed)}; - EXPECT_EQ(result, input); -} - -TEST(binary_data) { - std::string input; - input.resize(256); - for (unsigned int index = 0; index < 256; ++index) { - input[index] = static_cast(index); - } - - const auto compressed{sourcemeta::core::gzip( - reinterpret_cast(input.data()), input.size())}; - const auto result{decompress_via_stream(compressed)}; - EXPECT_EQ(result.size(), input.size()); - EXPECT_EQ(std::memcmp(result.data(), input.data(), input.size()), 0); -} - TEST(read_char_by_char) { const std::string input{"hello world"}; const auto compressed{sourcemeta::core::gzip( @@ -115,147 +52,6 @@ TEST(multiple_small_reads) { EXPECT_EQ(result, input); } -TEST(invalid_input_throws) { - const std::string garbage{"this is not gzip data"}; - try { - decompress_via_stream(garbage); - FAIL(); - } catch (const sourcemeta::core::GZIPError &error) { - EXPECT_EQ(std::string{error.what()}, "Invalid gzip magic bytes"); - } -} - -TEST(truncated_input_throws) { - const std::string input{"hello world, this needs to be long enough"}; - auto compressed{sourcemeta::core::gzip( - reinterpret_cast(input.data()), input.size())}; - compressed.resize(compressed.size() / 2); - try { - decompress_via_stream(compressed); - FAIL(); - } catch (const sourcemeta::core::GZIPError &error) { - EXPECT_EQ(std::string{error.what()}, "Unexpected end of source stream"); - } -} - -TEST(output_size_one_byte) { - const std::string input{"X"}; - const auto compressed{sourcemeta::core::gzip( - reinterpret_cast(input.data()), input.size())}; - const auto result{decompress_via_stream(compressed)}; - EXPECT_EQ(result, input); -} - -TEST(output_size_two_bytes) { - const std::string input{"AB"}; - const auto compressed{sourcemeta::core::gzip( - reinterpret_cast(input.data()), input.size())}; - const auto result{decompress_via_stream(compressed)}; - EXPECT_EQ(result, input); -} - -TEST(output_size_16383) { - std::string input; - input.resize(16383); - for (std::size_t index = 0; index < input.size(); ++index) { - input[index] = static_cast(index & 0xff); - } - const auto compressed{sourcemeta::core::gzip( - reinterpret_cast(input.data()), input.size())}; - const auto result{decompress_via_stream(compressed)}; - EXPECT_EQ(result.size(), input.size()); - EXPECT_EQ(std::memcmp(result.data(), input.data(), input.size()), 0); -} - -TEST(output_size_16384) { - std::string input; - input.resize(16384); - for (std::size_t index = 0; index < input.size(); ++index) { - input[index] = static_cast(index & 0xff); - } - const auto compressed{sourcemeta::core::gzip( - reinterpret_cast(input.data()), input.size())}; - const auto result{decompress_via_stream(compressed)}; - EXPECT_EQ(result.size(), input.size()); - EXPECT_EQ(std::memcmp(result.data(), input.data(), input.size()), 0); -} - -TEST(output_size_16385) { - std::string input; - input.resize(16385); - for (std::size_t index = 0; index < input.size(); ++index) { - input[index] = static_cast(index & 0xff); - } - const auto compressed{sourcemeta::core::gzip( - reinterpret_cast(input.data()), input.size())}; - const auto result{decompress_via_stream(compressed)}; - EXPECT_EQ(result.size(), input.size()); - EXPECT_EQ(std::memcmp(result.data(), input.data(), input.size()), 0); -} - -TEST(output_size_32768) { - std::string input; - input.resize(32768); - for (std::size_t index = 0; index < input.size(); ++index) { - input[index] = static_cast(index & 0xff); - } - const auto compressed{sourcemeta::core::gzip( - reinterpret_cast(input.data()), input.size())}; - const auto result{decompress_via_stream(compressed)}; - EXPECT_EQ(result.size(), input.size()); - EXPECT_EQ(std::memcmp(result.data(), input.data(), input.size()), 0); -} - -TEST(output_size_one_megabyte) { - std::string input; - input.resize(std::size_t{1024} * 1024); - for (std::size_t index = 0; index < input.size(); ++index) { - input[index] = static_cast(index & 0xff); - } - const auto compressed{sourcemeta::core::gzip( - reinterpret_cast(input.data()), input.size())}; - const auto result{decompress_via_stream(compressed)}; - EXPECT_EQ(result.size(), input.size()); - EXPECT_EQ(std::memcmp(result.data(), input.data(), input.size()), 0); -} - -TEST(highly_compressible_zeros) { - const std::string input(65536, '\0'); - const auto compressed{sourcemeta::core::gzip( - reinterpret_cast(input.data()), input.size())}; - EXPECT_LT(compressed.size(), input.size() / 10); - const auto result{decompress_via_stream(compressed)}; - EXPECT_EQ(result.size(), input.size()); - EXPECT_EQ(std::memcmp(result.data(), input.data(), input.size()), 0); -} - -TEST(highly_compressible_repeated_byte) { - const std::string input(65536, static_cast(0xff)); - const auto compressed{sourcemeta::core::gzip( - reinterpret_cast(input.data()), input.size())}; - EXPECT_LT(compressed.size(), input.size() / 10); - const auto result{decompress_via_stream(compressed)}; - EXPECT_EQ(result.size(), input.size()); - EXPECT_EQ(std::memcmp(result.data(), input.data(), input.size()), 0); -} - -TEST(incompressible_random_bytes) { - // A fixed seed keeps the incompressible payload identical across runs - // NOLINTNEXTLINE(cert-msc32-c,cert-msc51-cpp,bugprone-random-generator-seed) - std::mt19937 generator{42}; - std::uniform_int_distribution distribution{0, 255}; - std::string input; - input.resize(65536); - for (char &index : input) { - index = static_cast(distribution(generator)); - } - const auto compressed{sourcemeta::core::gzip( - reinterpret_cast(input.data()), input.size())}; - const auto result{decompress_via_stream(compressed)}; - EXPECT_EQ(result.size(), input.size()); - EXPECT_EQ(std::memcmp(result.data(), input.data(), input.size()), 0); -} - TEST(read_after_end_returns_eof) { const std::string input{"hello"}; const auto compressed{sourcemeta::core::gzip( @@ -354,369 +150,6 @@ TEST(read_chunk_larger_than_internal_buffer) { EXPECT_EQ(std::memcmp(result.data(), input.data(), input.size()), 0); } -TEST(header_with_ftext_flag) { - // RFC 1952 section 2.3 gzip stream with FLG.FTEXT (0x01) set. - // FTEXT is informational, so the streambuf must accept and ignore it. - sourcemeta::core::InputByteStream stream{ - 0x1f, 0x8b, // ID1, ID2 (gzip magic) - 0x08, // CM = 8 (deflate) - 0x01, // FLG = FTEXT - 0x00, 0x00, 0x00, 0x00, // MTIME - 0x00, // XFL - 0xff, // OS = unknown - // Deflate stored block for "hello world" - 0x01, // BFINAL=1, BTYPE=00 - 0x0b, 0x00, // LEN = 11 - 0xf4, 0xff, // NLEN = ~LEN - 'h', 'e', 'l', 'l', 'o', ' ', 'w', 'o', 'r', 'l', 'd', - // Trailer - 0x85, 0x11, 0x4a, 0x0d, // CRC32 of "hello world" little-endian - 0x0b, 0x00, 0x00, 0x00}; // ISIZE = 11 little-endian - EXPECT_EQ(decompress_via_stream(stream), "hello world"); -} - -TEST(header_with_fname) { - // FLG.FNAME (0x08) introduces a null-terminated filename after the fixed - // 10-byte header - sourcemeta::core::InputByteStream stream{ - 0x1f, 0x8b, // ID1, ID2 - 0x08, // CM - 0x08, // FLG = FNAME - 0x00, 0x00, 0x00, 0x00, // MTIME - 0x00, // XFL - 0xff, // OS - 'd', 'a', 't', 'a', '.', 't', 'x', 't', 0x00, // FNAME - 0x01, 0x0b, 0x00, 0xf4, 0xff, // stored block header - 'h', 'e', 'l', 'l', 'o', ' ', 'w', 'o', 'r', - 'l', 'd', 0x85, 0x11, 0x4a, 0x0d, // CRC32 little-endian - 0x0b, 0x00, 0x00, 0x00}; // ISIZE - EXPECT_EQ(decompress_via_stream(stream), "hello world"); -} - -TEST(header_with_fcomment) { - // FLG.FCOMMENT (0x10) introduces a null-terminated comment after the fixed - // 10-byte header - sourcemeta::core::InputByteStream stream{ - 0x1f, 0x8b, // ID1, ID2 - 0x08, // CM - 0x10, // FLG = FCOMMENT - 0x00, 0x00, 0x00, 0x00, // MTIME - 0x00, // XFL - 0xff, // OS - 't', 'h', 'i', 's', ' ', 'i', 's', ' ', 'a', - ' ', 'c', 'o', 'm', 'm', 'e', 'n', 't', 0x00, // FCOMMENT - 0x01, 0x0b, 0x00, 0xf4, 0xff, // stored block header - 'h', 'e', 'l', 'l', 'o', ' ', 'w', 'o', 'r', - 'l', 'd', 0x85, 0x11, 0x4a, 0x0d, // CRC32 little-endian - 0x0b, 0x00, 0x00, 0x00}; // ISIZE - EXPECT_EQ(decompress_via_stream(stream), "hello world"); -} - -TEST(header_with_fextra) { - // FLG.FEXTRA (0x04) introduces a length-prefixed extra field after the - // fixed 10-byte header. XLEN is a little-endian 16-bit count of the - // following bytes - sourcemeta::core::InputByteStream stream{ - 0x1f, 0x8b, // ID1, ID2 - 0x08, // CM - 0x04, // FLG = FEXTRA - 0x00, 0x00, 0x00, 0x00, // MTIME - 0x00, // XFL - 0xff, // OS - 0x05, 0x00, // XLEN = 5 little-endian - 'E', 'X', 'T', 'R', 'A', // FEXTRA data - 0x01, 0x0b, 0x00, 0xf4, 0xff, // stored block header - 'h', 'e', 'l', 'l', 'o', ' ', 'w', 'o', - 'r', 'l', 'd', 0x85, 0x11, 0x4a, 0x0d, // CRC32 little-endian - 0x0b, 0x00, 0x00, 0x00}; // ISIZE - EXPECT_EQ(decompress_via_stream(stream), "hello world"); -} - -TEST(header_with_fhcrc) { - // FLG.FHCRC (0x02) appends a 16-bit CRC of the preceding header bytes. - // The 16-bit CRC is the low 16 bits of the CRC-32 of the 10 header bytes - sourcemeta::core::InputByteStream stream{ - 0x1f, 0x8b, // ID1, ID2 - 0x08, // CM - 0x02, // FLG = FHCRC - 0x00, 0x00, 0x00, 0x00, // MTIME - 0x00, // XFL - 0xff, // OS - 0x90, 0xc9, // FHCRC over preceding 10 bytes - 0x01, 0x0b, 0x00, 0xf4, 0xff, // stored block header - 'h', 'e', 'l', 'l', 'o', ' ', 'w', 'o', - 'r', 'l', 'd', 0x85, 0x11, 0x4a, 0x0d, // CRC32 little-endian - 0x0b, 0x00, 0x00, 0x00}; // ISIZE - EXPECT_EQ(decompress_via_stream(stream), "hello world"); -} - -TEST(header_with_all_optional_flags) { - // Every optional FLG bit set at once. RFC 1952 fixes the order of optional - // fields: FEXTRA, FNAME, FCOMMENT, FHCRC - sourcemeta::core::InputByteStream stream{ - 0x1f, 0x8b, // ID1, ID2 - 0x08, // CM - 0x1f, // FLG = FTEXT|FHCRC|FEXTRA|FNAME|FCOMMENT - 0x00, 0x00, 0x00, 0x00, // MTIME - 0x00, // XFL - 0xff, // OS - 0x04, 0x00, // FEXTRA XLEN = 4 - 0xaa, 0xbb, 0xcc, 0xdd, // FEXTRA data - 'n', 'a', 'm', 'e', '.', 't', 'x', 't', 0x00, // FNAME - 'c', 'o', 'm', 'm', 'e', 'n', 't', 0x00, // FCOMMENT - 0x90, 0x3e, // FHCRC over all preceding header bytes - 0x01, 0x0b, 0x00, 0xf4, 0xff, // stored block header - 'h', 'e', 'l', 'l', 'o', ' ', 'w', 'o', 'r', - 'l', 'd', 0x85, 0x11, 0x4a, 0x0d, // CRC32 little-endian - 0x0b, 0x00, 0x00, 0x00}; // ISIZE - EXPECT_EQ(decompress_via_stream(stream), "hello world"); -} - -TEST(wrong_first_magic_byte_throws) { - const std::string input{"hello world"}; - auto compressed{sourcemeta::core::gzip( - reinterpret_cast(input.data()), input.size())}; - compressed[0] = static_cast(0x00); - try { - decompress_via_stream(compressed); - FAIL(); - } catch (const sourcemeta::core::GZIPError &error) { - EXPECT_EQ(std::string{error.what()}, "Invalid gzip magic bytes"); - } -} - -TEST(wrong_second_magic_byte_throws) { - const std::string input{"hello world"}; - auto compressed{sourcemeta::core::gzip( - reinterpret_cast(input.data()), input.size())}; - compressed[1] = static_cast(0x00); - try { - decompress_via_stream(compressed); - FAIL(); - } catch (const sourcemeta::core::GZIPError &error) { - EXPECT_EQ(std::string{error.what()}, "Invalid gzip magic bytes"); - } -} - -TEST(unsupported_compression_method_throws) { - const std::string input{"hello world"}; - auto compressed{sourcemeta::core::gzip( - reinterpret_cast(input.data()), input.size())}; - compressed[2] = static_cast(0x09); - try { - decompress_via_stream(compressed); - FAIL(); - } catch (const sourcemeta::core::GZIPError &error) { - EXPECT_EQ(std::string{error.what()}, "Unsupported gzip compression method"); - } -} - -TEST(reserved_flag_bit_5_set_throws) { - const std::string input{"hello world"}; - auto compressed{sourcemeta::core::gzip( - reinterpret_cast(input.data()), input.size())}; - compressed[3] = static_cast(0x20); - try { - decompress_via_stream(compressed); - FAIL(); - } catch (const sourcemeta::core::GZIPError &error) { - EXPECT_EQ(std::string{error.what()}, "Reserved gzip FLG bits must be zero"); - } -} - -TEST(reserved_flag_bit_6_set_throws) { - const std::string input{"hello world"}; - auto compressed{sourcemeta::core::gzip( - reinterpret_cast(input.data()), input.size())}; - compressed[3] = static_cast(0x40); - try { - decompress_via_stream(compressed); - FAIL(); - } catch (const sourcemeta::core::GZIPError &error) { - EXPECT_EQ(std::string{error.what()}, "Reserved gzip FLG bits must be zero"); - } -} - -TEST(reserved_flag_bit_7_set_throws) { - const std::string input{"hello world"}; - auto compressed{sourcemeta::core::gzip( - reinterpret_cast(input.data()), input.size())}; - compressed[3] = static_cast(0x80); - try { - decompress_via_stream(compressed); - FAIL(); - } catch (const sourcemeta::core::GZIPError &error) { - EXPECT_EQ(std::string{error.what()}, "Reserved gzip FLG bits must be zero"); - } -} - -TEST(corrupted_trailing_crc32_throws) { - const std::string input{ - "hello world, this is long enough to produce a real deflate body"}; - auto compressed{sourcemeta::core::gzip( - reinterpret_cast(input.data()), input.size())}; - compressed[compressed.size() - 8] = - static_cast(compressed[compressed.size() - 8] ^ 0xff); - try { - decompress_via_stream(compressed); - FAIL(); - } catch (const sourcemeta::core::GZIPError &error) { - EXPECT_EQ(std::string{error.what()}, "Gzip member CRC32 mismatch"); - } -} - -TEST(corrupted_trailing_isize_throws) { - const std::string input{ - "hello world, this is long enough to produce a real deflate body"}; - auto compressed{sourcemeta::core::gzip( - reinterpret_cast(input.data()), input.size())}; - compressed[compressed.size() - 1] = - static_cast(compressed[compressed.size() - 1] ^ 0xff); - try { - decompress_via_stream(compressed); - FAIL(); - } catch (const sourcemeta::core::GZIPError &error) { - EXPECT_EQ(std::string{error.what()}, "Gzip member ISIZE mismatch"); - } -} - -TEST(empty_source_stream_throws) { - try { - decompress_via_stream(""); - FAIL(); - } catch (const sourcemeta::core::GZIPError &error) { - EXPECT_EQ(std::string{error.what()}, "Empty source stream"); - } -} - -TEST(trailing_data_after_member_is_ignored) { - const std::string input{"hello world"}; - const auto compressed{sourcemeta::core::gzip( - reinterpret_cast(input.data()), input.size())}; - const std::string padded{compressed + "garbage data after gzip end"}; - const auto result{decompress_via_stream(padded)}; - EXPECT_EQ(result, input); -} - -TEST(fhcrc_mismatch_throws) { - // RFC 1952 section 2.3.1.2: a compliant decoder must reject an FHCRC - // mismatch. The correct CRC16 over the preceding 10 header bytes is - // 0x90 0xc9, so flipping a bit must cause a hard failure - sourcemeta::core::InputByteStream stream{ - 0x1f, 0x8b, // ID1, ID2 - 0x08, // CM - 0x02, // FLG = FHCRC - 0x00, 0x00, 0x00, 0x00, // MTIME - 0x00, // XFL - 0xff, // OS - 0x91, 0xc9, // FHCRC corrupted (correct is 0x90 0xc9) - 0x01, 0x0b, 0x00, 0xf4, 0xff, 'h', 'e', 'l', 'l', 'o', ' ', 'w', - 'o', 'r', 'l', 'd', 0x85, 0x11, 0x4a, 0x0d, 0x0b, 0x00, 0x00, 0x00}; - try { - decompress_via_stream(stream); - FAIL(); - } catch (const sourcemeta::core::GZIPError &error) { - EXPECT_EQ(std::string{error.what()}, "FHCRC mismatch"); - } -} - -TEST(multiple_deflate_blocks) { - // RFC 1951 allows a deflate stream to contain multiple blocks. Only the - // last block has BFINAL set. This fixture splits "hello world" into two - // stored blocks - sourcemeta::core::InputByteStream stream{ - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, - // Block 1 (non-final stored) - 0x00, // BFINAL=0, BTYPE=00 (stored) - 0x05, 0x00, // LEN = 5 - 0xfa, 0xff, // NLEN = ~5 - 'h', 'e', 'l', 'l', 'o', - // Block 2 (final stored) - 0x01, // BFINAL=1, BTYPE=00 - 0x06, 0x00, // LEN = 6 - 0xf9, 0xff, // NLEN = ~6 - ' ', 'w', 'o', 'r', 'l', 'd', 0x85, 0x11, 0x4a, - 0x0d, // CRC32 little-endian - 0x0b, 0x00, 0x00, 0x00}; // ISIZE - EXPECT_EQ(decompress_via_stream(stream), "hello world"); -} - -TEST(fextra_with_zero_xlen) { - // RFC 1952: XLEN=0 is a valid FEXTRA encoding (no extra-field bytes) - sourcemeta::core::InputByteStream stream{ - 0x1f, 0x8b, 0x08, 0x04, 0x00, 0x00, 0x00, 0x00, - 0x00, 0xff, 0x00, 0x00, // XLEN = 0 - 0x01, 0x0b, 0x00, 0xf4, 0xff, 'h', 'e', 'l', - 'l', 'o', ' ', 'w', 'o', 'r', 'l', 'd', - 0x85, 0x11, 0x4a, 0x0d, 0x0b, 0x00, 0x00, 0x00}; - EXPECT_EQ(decompress_via_stream(stream), "hello world"); -} - -TEST(mtime_nonzero_is_accepted) { - // RFC 1952 section 2.3.1.2: MTIME is informational and any value, including - // non-zero, must be accepted - sourcemeta::core::InputByteStream stream{ - 0x1f, 0x8b, 0x08, 0x00, 0x78, 0x56, 0x34, 0x12, // MTIME = 0x12345678 - // little-endian - 0x00, 0xff, 0x01, 0x0b, 0x00, 0xf4, 0xff, 'h', 'e', 'l', 'l', 'o', ' ', - 'w', 'o', 'r', 'l', 'd', 0x85, 0x11, 0x4a, 0x0d, 0x0b, 0x00, 0x00, 0x00}; - EXPECT_EQ(decompress_via_stream(stream), "hello world"); -} - -TEST(xfl_max_compression_is_accepted) { - // RFC 1952 section 2.3.1.2: XFL=2 indicates maximum compression - sourcemeta::core::InputByteStream stream{ - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x02, // XFL = 2 (max compression) - 0xff, 0x01, 0x0b, 0x00, 0xf4, 0xff, 'h', 'e', 'l', - 'l', 'o', ' ', 'w', 'o', 'r', 'l', 'd', 0x85, - 0x11, 0x4a, 0x0d, 0x0b, 0x00, 0x00, 0x00}; - EXPECT_EQ(decompress_via_stream(stream), "hello world"); -} - -TEST(xfl_fastest_compression_is_accepted) { - // RFC 1952 section 2.3.1.2: XFL=4 indicates fastest compression - sourcemeta::core::InputByteStream stream{ - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x04, // XFL = 4 (fastest) - 0xff, 0x01, 0x0b, 0x00, 0xf4, 0xff, 'h', 'e', 'l', - 'l', 'o', ' ', 'w', 'o', 'r', 'l', 'd', 0x85, - 0x11, 0x4a, 0x0d, 0x0b, 0x00, 0x00, 0x00}; - EXPECT_EQ(decompress_via_stream(stream), "hello world"); -} - -TEST(os_field_unix_is_accepted) { - // RFC 1952 section 2.3.1.2: OS=3 indicates Unix; decoders must accept any - // value - sourcemeta::core::InputByteStream stream{ - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x03, // OS = Unix - 0x01, 0x0b, 0x00, 0xf4, 0xff, 'h', 'e', 'l', 'l', 'o', ' ', 'w', - 'o', 'r', 'l', 'd', 0x85, 0x11, 0x4a, 0x0d, 0x0b, 0x00, 0x00, 0x00}; - EXPECT_EQ(decompress_via_stream(stream), "hello world"); -} - -TEST(fname_with_latin1_high_bytes) { - // RFC 1952 section 2.3.1.2: FNAME is ISO 8859-1 (Latin-1), so bytes in the - // 0x80 to 0xff range must be accepted - sourcemeta::core::InputByteStream stream{ - 0x1f, 0x8b, 0x08, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xa9, 0xc6, - 0xff, 0x00, // FNAME = three Latin-1 bytes + null terminator - 0x01, 0x0b, 0x00, 0xf4, 0xff, 'h', 'e', 'l', 'l', 'o', ' ', 'w', - 'o', 'r', 'l', 'd', 0x85, 0x11, 0x4a, 0x0d, 0x0b, 0x00, 0x00, 0x00}; - EXPECT_EQ(decompress_via_stream(stream), "hello world"); -} - -TEST(fcomment_with_latin1_high_bytes) { - // RFC 1952 section 2.3.1.2: FCOMMENT is ISO 8859-1 - sourcemeta::core::InputByteStream stream{ - 0x1f, 0x8b, 0x08, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xc0, 0xc1, - 0xfe, 0x00, // FCOMMENT with Latin-1 high bytes - 0x01, 0x0b, 0x00, 0xf4, 0xff, 'h', 'e', 'l', 'l', 'o', ' ', 'w', - 'o', 'r', 'l', 'd', 0x85, 0x11, 0x4a, 0x0d, 0x0b, 0x00, 0x00, 0x00}; - EXPECT_EQ(decompress_via_stream(stream), "hello world"); -} - TEST(read_via_getline) { const std::string input{"line one\nline two\nline three"}; const auto compressed{sourcemeta::core::gzip( @@ -765,440 +198,3 @@ TEST(operator_in_extracts_tokens) { EXPECT_EQ(first, "hello"); EXPECT_EQ(second, "world"); } - -TEST(stored_block_with_empty_non_final_then_data) { - // RFC 1951 section 3.2.4: a stored block with LEN=0 is well-formed - sourcemeta::core::InputByteStream stream{ - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, - // Non-final empty stored block - 0x00, // BFINAL=0, BTYPE=00 - 0x00, 0x00, // LEN = 0 - 0xff, 0xff, // NLEN = ~0 - // Final stored block with the actual payload - 0x01, 0x0b, 0x00, 0xf4, 0xff, 'h', 'e', 'l', 'l', 'o', ' ', 'w', 'o', 'r', - 'l', 'd', 0x85, 0x11, 0x4a, 0x0d, 0x0b, 0x00, 0x00, 0x00}; - EXPECT_EQ(decompress_via_stream(stream), "hello world"); -} - -TEST(empty_payload_via_empty_final_stored_block) { - // Hand-crafted minimum valid gzip member encoding an empty payload - sourcemeta::core::InputByteStream stream{ - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, - 0x01, // BFINAL=1, BTYPE=00 - 0x00, 0x00, // LEN = 0 - 0xff, 0xff, // NLEN = ~0 - 0x00, 0x00, 0x00, 0x00, // CRC32 of empty input - 0x00, 0x00, 0x00, 0x00}; // ISIZE = 0 - EXPECT_TRUE(decompress_via_stream(stream).empty()); -} - -TEST(header_with_fextra_and_fname) { - // RFC 1952 fixes the order: FEXTRA precedes FNAME - sourcemeta::core::InputByteStream stream{ - 0x1f, 0x8b, 0x08, 0x0c, 0x00, 0x00, 0x00, 0x00, - 0x00, 0xff, 0x03, 0x00, // FEXTRA XLEN = 3 - 'x', 'y', 'z', // FEXTRA data - 'n', 'a', 'm', 'e', 0x00, // FNAME - 0x01, 0x0b, 0x00, 0xf4, 0xff, 'h', 'e', 'l', - 'l', 'o', ' ', 'w', 'o', 'r', 'l', 'd', - 0x85, 0x11, 0x4a, 0x0d, 0x0b, 0x00, 0x00, 0x00}; - EXPECT_EQ(decompress_via_stream(stream), "hello world"); -} - -TEST(header_with_fextra_and_fcomment) { - // RFC 1952 fixes the order: FEXTRA precedes FCOMMENT - sourcemeta::core::InputByteStream stream{ - 0x1f, 0x8b, 0x08, 0x14, 0x00, 0x00, 0x00, 0x00, - 0x00, 0xff, 0x02, 0x00, // FEXTRA XLEN = 2 - 'A', 'B', // FEXTRA data - 'h', 'i', 0x00, // FCOMMENT - 0x01, 0x0b, 0x00, 0xf4, 0xff, 'h', 'e', 'l', - 'l', 'o', ' ', 'w', 'o', 'r', 'l', 'd', - 0x85, 0x11, 0x4a, 0x0d, 0x0b, 0x00, 0x00, 0x00}; - EXPECT_EQ(decompress_via_stream(stream), "hello world"); -} - -TEST(header_with_fname_and_fcomment) { - // RFC 1952 fixes the order: FNAME precedes FCOMMENT - sourcemeta::core::InputByteStream stream{ - 0x1f, 0x8b, 0x08, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 'a', '.', - 't', 'x', 't', 0x00, // FNAME - 'c', 'm', 't', 0x00, // FCOMMENT - 0x01, 0x0b, 0x00, 0xf4, 0xff, 'h', 'e', 'l', 'l', 'o', ' ', 'w', - 'o', 'r', 'l', 'd', 0x85, 0x11, 0x4a, 0x0d, 0x0b, 0x00, 0x00, 0x00}; - EXPECT_EQ(decompress_via_stream(stream), "hello world"); -} - -TEST(reserved_deflate_block_type_throws) { - // RFC 1951 section 3.2.3: BTYPE=11 is reserved and a compliant decoder - // must reject it. Deflate packs bits LSB-first, so BFINAL=1 + BTYPE=11 - // is the byte 0b00000111 = 0x07 - sourcemeta::core::InputByteStream stream{ - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, - 0x07, // BFINAL=1, BTYPE=11 (reserved/invalid) - 0x00, 0x00, 0x00, 0x00, // arbitrary trailing bytes - 0x85, 0x11, 0x4a, 0x0d, 0x0b, 0x00, 0x00, 0x00}; - try { - decompress_via_stream(stream); - FAIL(); - } catch (const sourcemeta::core::GZIPError &error) { - EXPECT_EQ(std::string{error.what()}, "Reserved deflate block type"); - } -} - -TEST(stored_block_with_mismatched_nlen_throws) { - // RFC 1951 section 3.2.4: NLEN must be the one's complement of LEN - sourcemeta::core::InputByteStream stream{ - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, - 0x01, // BFINAL=1, BTYPE=00 - 0x0b, 0x00, // LEN = 11 - 0x00, 0x00, // NLEN corrupted (must be 0xf4 0xff) - 'h', 'e', 'l', 'l', 'o', ' ', 'w', 'o', 'r', 'l', - 'd', 0x85, 0x11, 0x4a, 0x0d, 0x0b, 0x00, 0x00, 0x00}; - try { - decompress_via_stream(stream); - FAIL(); - } catch (const sourcemeta::core::GZIPError &error) { - EXPECT_EQ(std::string{error.what()}, "Stored block LEN/NLEN mismatch"); - } -} - -TEST(two_concatenated_members) { - const std::string first{"hello"}; - const std::string second{" world"}; - const auto first_gzip{sourcemeta::core::gzip( - reinterpret_cast(first.data()), first.size())}; - const auto second_gzip{sourcemeta::core::gzip( - reinterpret_cast(second.data()), second.size())}; - const std::string combined{first_gzip + second_gzip}; - const auto result{decompress_via_stream(combined)}; - EXPECT_EQ(result, first + second); -} - -TEST(three_concatenated_members_with_empty) { - const std::string first{"foo"}; - const std::string second; - const std::string third{"baz"}; - const auto first_gzip{sourcemeta::core::gzip( - reinterpret_cast(first.data()), first.size())}; - const auto second_gzip{sourcemeta::core::gzip( - reinterpret_cast(second.data()), second.size())}; - const auto third_gzip{sourcemeta::core::gzip( - reinterpret_cast(third.data()), third.size())}; - const std::string combined{first_gzip + second_gzip + third_gzip}; - const auto result{decompress_via_stream(combined)}; - EXPECT_EQ(result, first + second + third); -} - -TEST(header_only_source_throws) { - const std::string input{"hello world"}; - const auto compressed{sourcemeta::core::gzip( - reinterpret_cast(input.data()), input.size())}; - const std::string header_only{compressed.substr(0, 10)}; - try { - decompress_via_stream(header_only); - FAIL(); - } catch (const sourcemeta::core::GZIPError &error) { - EXPECT_EQ(std::string{error.what()}, "Unexpected end of source stream"); - } -} - -TEST(truncated_mid_fname_throws) { - // FLG.FNAME set but the source stream ends before the null terminator - sourcemeta::core::InputByteStream stream{ - 0x1f, 0x8b, 0x08, 0x08, 0x00, 0x00, 0x00, - 0x00, 0x00, 0xff, 'd', 'a', 't'}; // no null terminator - try { - decompress_via_stream(stream); - FAIL(); - } catch (const sourcemeta::core::GZIPError &error) { - EXPECT_EQ(std::string{error.what()}, "Unexpected end of source stream"); - } -} - -TEST(truncated_mid_fcomment_throws) { - // FLG.FCOMMENT set but the source stream ends before the null terminator - sourcemeta::core::InputByteStream stream{ - 0x1f, 0x8b, 0x08, 0x10, 0x00, 0x00, 0x00, - 0x00, 0x00, 0xff, 'c', 'm', 't'}; // no null terminator - try { - decompress_via_stream(stream); - FAIL(); - } catch (const sourcemeta::core::GZIPError &error) { - EXPECT_EQ(std::string{error.what()}, "Unexpected end of source stream"); - } -} - -TEST(truncated_mid_fextra_throws) { - // FLG.FEXTRA with XLEN=10 but only 3 bytes of extra-field data present - sourcemeta::core::InputByteStream stream{ - 0x1f, 0x8b, 0x08, 0x04, 0x00, 0x00, - 0x00, 0x00, 0x00, 0xff, 0x0a, 0x00, // XLEN = 10 - 'A', 'B', 'C'}; // only 3 of 10 promised bytes present - try { - decompress_via_stream(stream); - FAIL(); - } catch (const sourcemeta::core::GZIPError &error) { - EXPECT_EQ(std::string{error.what()}, "Unexpected end of source stream"); - } -} - -TEST(fextra_spans_internal_buffer) { - std::string compressed; - compressed.append("\x1f\x8b\x08\x04\x00\x00\x00\x00\x00\xff", 10); - const std::size_t extra_size{20000}; - compressed.push_back(static_cast(extra_size & 0xff)); - compressed.push_back(static_cast((extra_size >> 8) & 0xff)); - compressed.append(extra_size, '\0'); - compressed.append("\x01\x0b\x00\xf4\xff", 5); - compressed.append("hello world", 11); - compressed.append("\x85\x11\x4a\x0d\x0b\x00\x00\x00", 8); - EXPECT_EQ(decompress_via_stream(compressed), "hello world"); -} - -TEST(dynamic_block_hlit_above_286_throws) { - // RFC 1951 section 3.2.7 caps the literal/length alphabet at 286 symbols. - // The deflate payload starts a dynamic block with HLIT encoding 288 codes - // (BFINAL=1, BTYPE=10, HLIT field = 31 so 31 + 257 = 288) - sourcemeta::core::InputByteStream stream{0x1f, 0x8b, 0x08, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0xff, - 0xfd, 0x00, 0x00}; - try { - decompress_via_stream(stream); - FAIL(); - } catch (const sourcemeta::core::GZIPError &error) { - EXPECT_EQ(std::string{error.what()}, "Too many literal/length codes"); - } -} - -TEST(dynamic_block_incomplete_code_throws) { - // RFC 1951 forbids incomplete Huffman codes outside the single-code case. - // The deflate payload starts a dynamic block whose code-length code tree - // assigns length two to two symbols, leaving the alphabet incomplete - // (BFINAL=1, BTYPE=10, HLIT=0, HDIST=0, HCLEN=0, code-length lengths for - // symbols 16 and 17 set to two) - sourcemeta::core::InputByteStream stream{0x1f, 0x8b, 0x08, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0xff, - 0x05, 0x00, 0x24, 0x00}; - try { - decompress_via_stream(stream); - FAIL(); - } catch (const sourcemeta::core::GZIPError &error) { - EXPECT_EQ(std::string{error.what()}, "Incomplete Huffman code"); - } -} - -TEST(fixed_huffman_block_round_trip) { - const std::string compressed{ - "\x1f\x8b\x08\x00\x00\x00\x00\x00\x04\x13\xcb\x48\xcd\xc9\xc9\x57\xc8\xc0" - "\x4e\x02\x00\xf6\xd2\x53\x38\x1d\x00\x00\x00", - 29}; - const auto result{decompress_via_stream(compressed)}; - EXPECT_EQ(result, "hello hello hello hello hello"); -} - -TEST(fixed_huffman_empty_block) { - const std::string compressed{"\x1f\x8b\x08\x00\x00\x00\x00\x00\x04\x13\x03" - "\x00\x00\x00\x00\x00\x00\x00\x00\x00", - 20}; - const auto result{decompress_via_stream(compressed)}; - EXPECT_EQ(result, std::string{}); -} - -TEST(fixed_backref_before_any_output_throws) { - const std::string compressed{"\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\x03\x03" - "\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", - 22}; - try { - decompress_via_stream(compressed); - FAIL(); - } catch (const sourcemeta::core::GZIPError &error) { - EXPECT_EQ(std::string{error.what()}, - "Backref distance exceeds bytes available"); - } -} - -TEST(fixed_unassigned_distance_code_throws) { - const std::string compressed{ - "\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\x03\x4b\x04\x3e\x00\x00\x00\x00\x00" - "\x00\x00\x00\x00\x00", - 23}; - try { - decompress_via_stream(compressed); - FAIL(); - } catch (const sourcemeta::core::GZIPError &error) { - EXPECT_EQ(std::string{error.what()}, "Invalid Huffman code"); - } -} - -TEST(fixed_invalid_literal_length_symbol_throws) { - const std::string compressed{"\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\x03\x1b" - "\x03\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", - 22}; - try { - decompress_via_stream(compressed); - FAIL(); - } catch (const sourcemeta::core::GZIPError &error) { - EXPECT_EQ(std::string{error.what()}, "Invalid literal/length code"); - } -} - -TEST(dynamic_repeat_without_previous_throws) { - const std::string compressed{ - "\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\x03\x05\x00\x02\x24\x00\x00\x00\x00" - "\x00\x00\x00\x00\x00\x00", - 24}; - try { - decompress_via_stream(compressed); - FAIL(); - } catch (const sourcemeta::core::GZIPError &error) { - EXPECT_EQ(std::string{error.what()}, - "Repeat-previous code length with no previous"); - } -} - -TEST(dynamic_code_length_overshoot_throws) { - const std::string compressed{ - "\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\x03\x05\x00\x80\xe4\xff\x1f\x00\x00" - "\x00\x00\x00\x00\x00\x00\x00\x00", - 26}; - try { - decompress_via_stream(compressed); - FAIL(); - } catch (const sourcemeta::core::GZIPError &error) { - EXPECT_EQ(std::string{error.what()}, "Code length count overflow"); - } -} - -TEST(dynamic_code_length_cap_overflow_throws) { - const std::string compressed{ - "\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\x03\xed\x1f\x80\xe4\xff\xff\x1f\x00" - "\x00\x00\x00\x00\x00\x00\x00\x00\x00", - 27}; - try { - decompress_via_stream(compressed); - FAIL(); - } catch (const sourcemeta::core::GZIPError &error) { - EXPECT_EQ(std::string{error.what()}, "Code length count overflow"); - } -} - -TEST(dynamic_oversubscribed_code_lengths_throws) { - const std::string compressed{ - "\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\x03\x05\x20\x92\x24\x00\x00\x00\x00" - "\x00\x00\x00\x00\x00\x00", - 24}; - try { - decompress_via_stream(compressed); - FAIL(); - } catch (const sourcemeta::core::GZIPError &error) { - EXPECT_EQ(std::string{error.what()}, "Over-subscribed Huffman code"); - } -} - -TEST(dynamic_block_without_distance_codes) { - const std::string compressed{ - "\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\x03\x05\xc0\x81\x08\x00\x00\x00\x00" - "\x20\x7f\xeb\x03\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", - 32}; - const auto result{decompress_via_stream(compressed)}; - EXPECT_EQ(result, std::string{}); -} - -TEST(window_wraparound_round_trip) { - const std::string input(200000, 'a'); - const auto compressed{sourcemeta::core::gzip( - reinterpret_cast(input.data()), input.size())}; - const auto result{decompress_via_stream(compressed)}; - EXPECT_EQ(result.size(), input.size()); - EXPECT_EQ(result, input); -} - -TEST(window_wrapping_backrefs_round_trip) { - const std::string compressed{ - "\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\x03\x4b\x4c\x1a\x85\xa3\x70\x14" - "\x8e\xc2\x51\x38\x0a\x47\xe1\x28\x1c\x85\xa3\x70\x14\x8e\xc2\x51\x38" - "\x0a\x47\xe1\x28\x1c\x85\xa3\x70\x14\x8e\xc2\x51\x38\x0a\x47\xe1\x28" - "\x1c\x85\xa3\x70\x14\x8e\xc2\x51\x38\x0a\x47\xe1\x28\x1c\x85\xa3\x70" - "\x14\x8e\xc2\x51\x38\x0a\x47\xe1\x28\x1c\x85\xa3\x70\x14\x8e\xc2\x51" - "\x38\x0a\x47\xe1\x28\x1c\x85\xa3\x70\x14\x8e\xc2\x51\x38\x0a\x47\xe1" - "\x28\x1c\x85\xa3\x70\x14\x8e\xc2\x51\x38\x0a\x47\xe1\x28\x1c\x85\xa3" - "\x70\x14\x8e\xc2\x51\x38\x0a\x47\xe1\x28\x1c\x85\xa3\x70\x14\x8e\xc2" - "\x51\x38\x0a\x47\xe1\x28\x1c\x85\xa3\x70\x14\x8e\xc2\x51\x38\x0a\x47" - "\xe1\x28\x1c\x85\xa3\x70\x14\x8e\xc2\x51\x38\x0a\x47\xe1\x28\x1c\x85" - "\xa3\x70\x14\x8e\xc2\x51\x38\x0a\x47\xe1\x28\x1c\x85\xa3\x70\x14\x8e" - "\xc2\x51\x38\x0a\x47\xe1\x28\x1c\x85\xa3\x70\x14\x8e\xc2\x51\x38\x0a" - "\x47\xe1\x28\x1c\x85\xa3\x70\x14\x8e\xc2\x51\x38\x0a\x87\x36\x1c\x0d" - "\xab\xd1\x10\x00\x00\xba\xf1\x51\x56\xc0\x81\x00\x00", - 234}; - // The member trailer carries the CRC32 of the full expected output, so a - // successful decode already proves every byte, and the probes pin the - // alternating pattern at the start, both window boundary copies, and the end - const auto result{decompress_via_stream(compressed)}; - EXPECT_EQ(result.size(), 33216); - EXPECT_EQ(result.at(0), 'a'); - EXPECT_EQ(result.at(1), 'b'); - EXPECT_EQ(result.at(32700), 'a'); - EXPECT_EQ(result.at(32701), 'b'); - EXPECT_EQ(result.at(32958), 'a'); - EXPECT_EQ(result.at(32959), 'b'); - EXPECT_EQ(result.at(33214), 'a'); - EXPECT_EQ(result.at(33215), 'b'); -} - -TEST(dynamic_repeat_previous_cap_overflow_throws) { - const std::string compressed{ - "\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\x03\xed\x1f\x84\x28\x7f\xff\xff" - "\xff\xff\x79\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", - 30}; - try { - decompress_via_stream(compressed); - FAIL(); - } catch (const sourcemeta::core::GZIPError &error) { - EXPECT_EQ(std::string{error.what()}, "Code length count overflow"); - } -} - -TEST(dynamic_repeat_zero_cap_overflow_throws) { - const std::string compressed{ - "\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\x03\xed\x1f\x20\xe5\xff\xff\xde" - "\x7b\xef\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", - 29}; - try { - decompress_via_stream(compressed); - FAIL(); - } catch (const sourcemeta::core::GZIPError &error) { - EXPECT_EQ(std::string{error.what()}, "Code length count overflow"); - } -} - -TEST(multiple_members_concatenate) { - const std::string first{"hello "}; - const std::string second{"world"}; - const auto compressed_first{sourcemeta::core::gzip( - reinterpret_cast(first.data()), first.size())}; - const auto compressed_second{sourcemeta::core::gzip( - reinterpret_cast(second.data()), second.size())}; - const auto result{ - decompress_via_stream(compressed_first + compressed_second)}; - EXPECT_EQ(result, "hello world"); -} - -TEST(trailing_garbage_after_member_is_ignored) { - const std::string input{"hello world"}; - const auto compressed{sourcemeta::core::gzip( - reinterpret_cast(input.data()), input.size())}; - const auto result{decompress_via_stream(compressed + "trailing garbage")}; - EXPECT_EQ(result, input); -} - -TEST(truncated_trailer_throws) { - const std::string compressed{ - "\x1f\x8b\x08\x00\x00\x00\x00\x00\x04\x13\x03\x00\x00\x00\x00\x00", 16}; - try { - decompress_via_stream(compressed); - FAIL(); - } catch (const sourcemeta::core::GZIPError &) { - // Rejecting the corrupt stream is expected - } -} diff --git a/test/gzip/gzip_test.cc b/test/gzip/gzip_test.cc index e00afce59d..e01545342b 100644 --- a/test/gzip/gzip_test.cc +++ b/test/gzip/gzip_test.cc @@ -1,201 +1,1865 @@ #include #include -#include // std::uint8_t -#include // std::memcmp -#include // std::string +#include // std::size_t +#include // std::uint8_t, std::uint32_t +#include // std::istream +#include // std::istreambuf_iterator +#include // std::numeric_limits +#include // std::mt19937, std::uniform_int_distribution +#include // std::istringstream +#include // std::string +#include // std::move +#include // std::vector + +namespace { + +auto compress(const std::string &input, const int level) + -> std::vector { + const auto output{sourcemeta::core::gzip( + reinterpret_cast(input.data()), input.size(), + level)}; + return {output.cbegin(), output.cend()}; +} + +auto decompress_one_shot(const std::vector &input, + const std::size_t output_hint = 0, + const std::size_t maximum_size = 268435456) + -> std::string { + return sourcemeta::core::gunzip(input.data(), input.size(), output_hint, + maximum_size); +} + +auto decompress_stream(const std::vector &input) -> std::string { + std::istringstream stream{std::string{input.cbegin(), input.cend()}}; + sourcemeta::core::GZIPStreamBuffer buffer{stream}; + std::istream decompressed{&buffer}; + std::string result; + result.assign(std::istreambuf_iterator(decompressed), + std::istreambuf_iterator()); + return result; +} + +} // namespace TEST(compress_empty_input) { const std::string input; - const auto compressed{sourcemeta::core::gzip( - reinterpret_cast(input.data()), input.size())}; - EXPECT_FALSE(compressed.empty()); - - const auto decompressed{sourcemeta::core::gunzip( - reinterpret_cast(compressed.data()), - compressed.size())}; - EXPECT_EQ(decompressed, input); + const auto compressed{compress(input, 1)}; + EXPECT_EQ(decompress_one_shot(compressed), input); + EXPECT_EQ(decompress_stream(compressed), input); } TEST(compress_hello_world) { const std::string input{"hello world"}; - const auto compressed{sourcemeta::core::gzip( - reinterpret_cast(input.data()), input.size())}; - EXPECT_FALSE(compressed.empty()); - EXPECT_LT(compressed.size(), input.size() + 30); + const auto compressed{compress(input, 1)}; + EXPECT_EQ(decompress_one_shot(compressed), input); + EXPECT_EQ(decompress_stream(compressed), input); +} - const auto decompressed{sourcemeta::core::gunzip( - reinterpret_cast(compressed.data()), - compressed.size())}; - EXPECT_EQ(decompressed, input); +TEST(compress_every_byte_value) { + std::string input; + for (std::size_t index = 0; index < 256; ++index) { + input.push_back(static_cast(index)); + } + + const auto compressed{compress(input, 1)}; + EXPECT_EQ(decompress_one_shot(compressed), input); + EXPECT_EQ(decompress_stream(compressed), input); } -TEST(compress_round_trip_binary_data) { +TEST(compress_repeated_pangram) { std::string input; - input.resize(256); - for (unsigned int index = 0; index < 256; ++index) { - input[index] = static_cast(index); + for (std::size_t index = 0; index < 1000; ++index) { + input += "The quick brown fox jumps over the lazy dog. "; } - const auto compressed{sourcemeta::core::gzip( - reinterpret_cast(input.data()), input.size())}; - EXPECT_FALSE(compressed.empty()); + const auto compressed{compress(input, 1)}; + EXPECT_LT(compressed.size(), input.size()); + EXPECT_EQ(decompress_one_shot(compressed), input); + EXPECT_EQ(decompress_stream(compressed), input); +} + +TEST(compress_level_0_round_trips) { + std::string input; + for (std::size_t index = 0; index < 2000; ++index) { + input += "The quick brown fox jumps over the lazy dog. "; + } - const auto decompressed{sourcemeta::core::gunzip( - reinterpret_cast(compressed.data()), - compressed.size())}; - EXPECT_EQ(decompressed.size(), input.size()); - EXPECT_EQ(std::memcmp(decompressed.data(), input.data(), input.size()), 0); + const auto compressed{compress(input, 0)}; + EXPECT_EQ(decompress_one_shot(compressed), input); + EXPECT_EQ(decompress_stream(compressed), input); } -TEST(compress_round_trip_large_input) { - const std::string pattern{"The quick brown fox jumps over the lazy dog. "}; +TEST(compress_level_1_round_trips) { std::string input; - for (int index = 0; index < 1000; ++index) { - input += pattern; + for (std::size_t index = 0; index < 2000; ++index) { + input += "The quick brown fox jumps over the lazy dog. "; } - const auto compressed{sourcemeta::core::gzip( - reinterpret_cast(input.data()), input.size())}; - EXPECT_FALSE(compressed.empty()); - EXPECT_LT(compressed.size(), input.size()); + const auto compressed{compress(input, 1)}; + EXPECT_EQ(decompress_one_shot(compressed), input); + EXPECT_EQ(decompress_stream(compressed), input); +} + +TEST(compress_level_2_round_trips) { + std::string input; + for (std::size_t index = 0; index < 2000; ++index) { + input += "The quick brown fox jumps over the lazy dog. "; + } - const auto decompressed{sourcemeta::core::gunzip( - reinterpret_cast(compressed.data()), - compressed.size())}; - EXPECT_EQ(decompressed, input); + const auto compressed{compress(input, 2)}; + EXPECT_EQ(decompress_one_shot(compressed), input); + EXPECT_EQ(decompress_stream(compressed), input); } -TEST(decompress_with_output_hint) { - const std::string input{"hello world"}; - const auto compressed{sourcemeta::core::gzip( - reinterpret_cast(input.data()), input.size())}; +TEST(compress_level_3_round_trips) { + std::string input; + for (std::size_t index = 0; index < 2000; ++index) { + input += "The quick brown fox jumps over the lazy dog. "; + } + + const auto compressed{compress(input, 3)}; + EXPECT_EQ(decompress_one_shot(compressed), input); + EXPECT_EQ(decompress_stream(compressed), input); +} - const auto decompressed{sourcemeta::core::gunzip( - reinterpret_cast(compressed.data()), - compressed.size(), input.size())}; - EXPECT_EQ(decompressed, input); +TEST(compress_level_4_round_trips) { + std::string input; + for (std::size_t index = 0; index < 2000; ++index) { + input += "The quick brown fox jumps over the lazy dog. "; + } + + const auto compressed{compress(input, 4)}; + EXPECT_EQ(decompress_one_shot(compressed), input); + EXPECT_EQ(decompress_stream(compressed), input); +} + +TEST(compress_level_5_round_trips) { + std::string input; + for (std::size_t index = 0; index < 2000; ++index) { + input += "The quick brown fox jumps over the lazy dog. "; + } + + const auto compressed{compress(input, 5)}; + EXPECT_EQ(decompress_one_shot(compressed), input); + EXPECT_EQ(decompress_stream(compressed), input); +} + +TEST(compress_level_6_round_trips) { + std::string input; + for (std::size_t index = 0; index < 2000; ++index) { + input += "The quick brown fox jumps over the lazy dog. "; + } + + const auto compressed{compress(input, 6)}; + EXPECT_EQ(decompress_one_shot(compressed), input); + EXPECT_EQ(decompress_stream(compressed), input); +} + +TEST(compress_level_7_round_trips) { + std::string input; + for (std::size_t index = 0; index < 2000; ++index) { + input += "The quick brown fox jumps over the lazy dog. "; + } + + const auto compressed{compress(input, 7)}; + EXPECT_EQ(decompress_one_shot(compressed), input); + EXPECT_EQ(decompress_stream(compressed), input); +} + +TEST(compress_level_8_round_trips) { + std::string input; + for (std::size_t index = 0; index < 2000; ++index) { + input += "The quick brown fox jumps over the lazy dog. "; + } + + const auto compressed{compress(input, 8)}; + EXPECT_EQ(decompress_one_shot(compressed), input); + EXPECT_EQ(decompress_stream(compressed), input); } -TEST(compress_with_explicit_level_round_trips) { - const std::string pattern{"The quick brown fox jumps over the lazy dog. "}; +TEST(compress_level_9_round_trips) { std::string input; - for (int index = 0; index < 1000; ++index) { - input += pattern; + for (std::size_t index = 0; index < 2000; ++index) { + input += "The quick brown fox jumps over the lazy dog. "; } - const auto compressed{sourcemeta::core::gzip( - reinterpret_cast(input.data()), input.size(), 9)}; - EXPECT_FALSE(compressed.empty()); + const auto compressed{compress(input, 9)}; + EXPECT_EQ(decompress_one_shot(compressed), input); + EXPECT_EQ(decompress_stream(compressed), input); +} - const auto decompressed{sourcemeta::core::gunzip( - reinterpret_cast(compressed.data()), - compressed.size())}; - EXPECT_EQ(decompressed, input); +TEST(compress_level_10_round_trips) { + std::string input; + for (std::size_t index = 0; index < 2000; ++index) { + input += "The quick brown fox jumps over the lazy dog. "; + } + + const auto compressed{compress(input, 10)}; + EXPECT_EQ(decompress_one_shot(compressed), input); + EXPECT_EQ(decompress_stream(compressed), input); +} + +TEST(compress_level_11_round_trips) { + std::string input; + for (std::size_t index = 0; index < 2000; ++index) { + input += "The quick brown fox jumps over the lazy dog. "; + } + + const auto compressed{compress(input, 11)}; + EXPECT_EQ(decompress_one_shot(compressed), input); + EXPECT_EQ(decompress_stream(compressed), input); +} + +TEST(compress_level_12_round_trips) { + std::string input; + for (std::size_t index = 0; index < 2000; ++index) { + input += "The quick brown fox jumps over the lazy dog. "; + } + + const auto compressed{compress(input, 12)}; + EXPECT_EQ(decompress_one_shot(compressed), input); + EXPECT_EQ(decompress_stream(compressed), input); } TEST(compress_higher_level_is_not_larger) { - const std::string pattern{"The quick brown fox jumps over the lazy dog. "}; - std::string input; - for (int index = 0; index < 1000; ++index) { - input += pattern; - } - - const auto fastest{sourcemeta::core::gzip( - reinterpret_cast(input.data()), input.size(), 1)}; - const auto smallest{sourcemeta::core::gzip( - reinterpret_cast(input.data()), input.size(), 12)}; - EXPECT_LE(smallest.size(), fastest.size()); -} - -TEST(decompress_invalid_input_throws) { - const std::string garbage{"this is not gzip data"}; - try { - sourcemeta::core::gunzip( - reinterpret_cast(garbage.data()), garbage.size()); - FAIL(); - } catch (const sourcemeta::core::GZIPError &error) { - EXPECT_EQ(std::string{error.what()}, "Could not decompress input"); - } -} - -TEST(decompress_beyond_maximum_size_throws) { - const std::string input{"Hello, World! Highly compressible content here."}; - const auto compressed{sourcemeta::core::gzip( - reinterpret_cast(input.data()), input.size())}; - try { - sourcemeta::core::gunzip( - reinterpret_cast(compressed.data()), - compressed.size(), 0, 4); - FAIL(); - } catch (const sourcemeta::core::GZIPError &error) { - EXPECT_EQ(std::string{error.what()}, - "Decompressed output exceeds the maximum allowed size"); - } -} - -TEST(decompress_within_maximum_size_succeeds) { - const std::string input{"Hello, World! Highly compressible content here."}; - const auto compressed{sourcemeta::core::gzip( - reinterpret_cast(input.data()), input.size())}; - const auto decompressed{sourcemeta::core::gunzip( - reinterpret_cast(compressed.data()), - compressed.size(), 0, 1024)}; - EXPECT_EQ(decompressed, input); -} - -TEST(decompress_concatenated_members) { - const std::string first{"hello "}; - const std::string second{"world"}; - auto compressed{sourcemeta::core::gzip( - reinterpret_cast(first.data()), first.size())}; - compressed += sourcemeta::core::gzip( - reinterpret_cast(second.data()), second.size()); - const auto decompressed{sourcemeta::core::gunzip( - reinterpret_cast(compressed.data()), - compressed.size())}; - EXPECT_EQ(decompressed, "hello world"); -} - -TEST(decompress_concatenated_members_grows_across_members) { - // A tiny hint forces the second member to grow the buffer that already holds - // the first, exercising the multi-member growth path - const std::string first{"hello "}; - const std::string second{"world"}; - auto compressed{sourcemeta::core::gzip( - reinterpret_cast(first.data()), first.size())}; - compressed += sourcemeta::core::gzip( - reinterpret_cast(second.data()), second.size()); - const auto decompressed{sourcemeta::core::gunzip( - reinterpret_cast(compressed.data()), - compressed.size(), 1, 1024)}; - EXPECT_EQ(decompressed, "hello world"); -} - -TEST(decompress_ignores_trailing_data) { - const std::string input{"hello world"}; - auto compressed{sourcemeta::core::gzip( - reinterpret_cast(input.data()), input.size())}; - compressed += "not a gzip member"; - const auto decompressed{sourcemeta::core::gunzip( - reinterpret_cast(compressed.data()), - compressed.size())}; - EXPECT_EQ(decompressed, input); + std::string input; + for (std::size_t index = 0; index < 1000; ++index) { + input += "The quick brown fox jumps over the lazy dog. "; + } + + EXPECT_LE(compress(input, 12).size(), compress(input, 1).size()); } -TEST(decompress_corrupt_trailing_member_throws) { - const std::string input{"hello world"}; - auto compressed{sourcemeta::core::gzip( - reinterpret_cast(input.data()), input.size())}; - compressed.push_back('\x1F'); - compressed.push_back('\x8B'); - compressed.append("corrupt"); - try { - sourcemeta::core::gunzip( - reinterpret_cast(compressed.data()), - compressed.size()); - FAIL(); - } catch (const sourcemeta::core::GZIPError &error) { - EXPECT_EQ(std::string{error.what()}, "Could not decompress input"); +TEST(compress_output_header_identifies_deflate_member) { + const auto compressed{compress("hello world", 6)}; + EXPECT_GE(compressed.size(), 18); + EXPECT_EQ(compressed.at(0), 0x1f); + EXPECT_EQ(compressed.at(1), 0x8b); + EXPECT_EQ(compressed.at(2), 0x08); + EXPECT_EQ(compressed.at(3) & 0xe0, 0); +} + +TEST(compress_output_trailer_of_empty_input) { + const auto compressed{compress("", 1)}; + const std::vector trailer{compressed.cend() - 8, + compressed.cend()}; + const std::vector expected{0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00}; + EXPECT_EQ(trailer, expected); +} + +TEST(compress_output_trailer_of_hello_world_at_level_0) { + const auto compressed{compress("hello world", 0)}; + const std::vector trailer{compressed.cend() - 8, + compressed.cend()}; + const std::vector expected{0x85, 0x11, 0x4a, 0x0d, + 0x0b, 0x00, 0x00, 0x00}; + EXPECT_EQ(trailer, expected); +} + +TEST(compress_output_trailer_of_hello_world_at_level_1) { + const auto compressed{compress("hello world", 1)}; + const std::vector trailer{compressed.cend() - 8, + compressed.cend()}; + const std::vector expected{0x85, 0x11, 0x4a, 0x0d, + 0x0b, 0x00, 0x00, 0x00}; + EXPECT_EQ(trailer, expected); +} + +TEST(compress_output_trailer_of_hello_world_at_level_12) { + const auto compressed{compress("hello world", 12)}; + const std::vector trailer{compressed.cend() - 8, + compressed.cend()}; + const std::vector expected{0x85, 0x11, 0x4a, 0x0d, + 0x0b, 0x00, 0x00, 0x00}; + EXPECT_EQ(trailer, expected); +} + +TEST(compress_output_trailer_of_crc32_check_string) { + const auto compressed{compress("123456789", 6)}; + const std::vector trailer{compressed.cend() - 8, + compressed.cend()}; + const std::vector expected{0x26, 0x39, 0xf4, 0xcb, + 0x09, 0x00, 0x00, 0x00}; + EXPECT_EQ(trailer, expected); +} + +TEST(compress_output_trailer_of_pangram) { + const auto compressed{ + compress("The quick brown fox jumps over the lazy dog", 9)}; + const std::vector trailer{compressed.cend() - 8, + compressed.cend()}; + const std::vector expected{0x39, 0xa3, 0x4f, 0x41, + 0x2b, 0x00, 0x00, 0x00}; + EXPECT_EQ(trailer, expected); +} + +TEST(compress_output_trailer_size_spans_multiple_bytes) { + const auto compressed{compress(std::string(70000, 'z'), 1)}; + const std::vector size{compressed.cend() - 4, + compressed.cend()}; + const std::vector expected{0x70, 0x11, 0x01, 0x00}; + EXPECT_EQ(size, expected); +} + +TEST(compress_single_byte) { + const std::string input{"X"}; + const auto compressed{compress(input, 1)}; + EXPECT_EQ(decompress_one_shot(compressed), input); + EXPECT_EQ(decompress_stream(compressed), input); +} + +TEST(compress_two_bytes) { + const std::string input{"AB"}; + const auto compressed{compress(input, 1)}; + EXPECT_EQ(decompress_one_shot(compressed), input); + EXPECT_EQ(decompress_stream(compressed), input); +} + +TEST(compress_single_zero_byte) { + const std::string input(1, '\0'); + const auto compressed{compress(input, 1)}; + EXPECT_EQ(decompress_one_shot(compressed), input); + EXPECT_EQ(decompress_stream(compressed), input); +} + +TEST(compress_minimum_match_length_input) { + const std::string input{"abcabc"}; + const auto compressed{compress(input, 12)}; + EXPECT_EQ(decompress_one_shot(compressed), input); + EXPECT_EQ(decompress_stream(compressed), input); +} + +TEST(compress_run_of_maximum_match_length) { + const std::string input(259, 'q'); + const auto compressed{compress(input, 6)}; + EXPECT_EQ(decompress_one_shot(compressed), input); + EXPECT_EQ(decompress_stream(compressed), input); +} + +TEST(compress_run_one_past_maximum_match_length) { + const std::string input(260, 'q'); + const auto compressed{compress(input, 6)}; + EXPECT_EQ(decompress_one_shot(compressed), input); + EXPECT_EQ(decompress_stream(compressed), input); +} + +TEST(compress_counter_bytes_one_below_stream_buffer_size) { + std::string input; + for (std::size_t index = 0; index < 16383; ++index) { + input.push_back(static_cast(index & 0xff)); + } + + const auto compressed{compress(input, 1)}; + EXPECT_EQ(decompress_one_shot(compressed), input); + EXPECT_EQ(decompress_stream(compressed), input); +} + +TEST(compress_counter_bytes_of_stream_buffer_size) { + std::string input; + for (std::size_t index = 0; index < 16384; ++index) { + input.push_back(static_cast(index & 0xff)); + } + + const auto compressed{compress(input, 1)}; + EXPECT_EQ(decompress_one_shot(compressed), input); + EXPECT_EQ(decompress_stream(compressed), input); +} + +TEST(compress_counter_bytes_one_above_stream_buffer_size) { + std::string input; + for (std::size_t index = 0; index < 16385; ++index) { + input.push_back(static_cast(index & 0xff)); + } + + const auto compressed{compress(input, 1)}; + EXPECT_EQ(decompress_one_shot(compressed), input); + EXPECT_EQ(decompress_stream(compressed), input); +} + +TEST(compress_counter_bytes_of_window_size) { + std::string input; + for (std::size_t index = 0; index < 32768; ++index) { + input.push_back(static_cast(index & 0xff)); + } + + const auto compressed{compress(input, 1)}; + EXPECT_EQ(decompress_one_shot(compressed), input); + EXPECT_EQ(decompress_stream(compressed), input); +} + +TEST(compress_counter_bytes_of_one_megabyte) { + std::string input; + for (std::size_t index = 0; index < 1048576; ++index) { + input.push_back(static_cast(index & 0xff)); + } + + const auto compressed{compress(input, 1)}; + EXPECT_EQ(decompress_one_shot(compressed), input); + EXPECT_EQ(decompress_stream(compressed), input); +} + +TEST(compress_random_input_one_below_window_size) { + // NOLINTNEXTLINE(cert-msc32-c,cert-msc51-cpp,bugprone-random-generator-seed) + std::mt19937 generator{1}; + std::uniform_int_distribution distribution{0, 255}; + std::string input; + for (std::size_t index = 0; index < 32767; ++index) { + input.push_back(static_cast(distribution(generator))); + } + + const auto compressed{compress(input, 1)}; + EXPECT_EQ(decompress_one_shot(compressed), input); + EXPECT_EQ(decompress_stream(compressed), input); +} + +TEST(compress_random_input_of_window_size) { + // NOLINTNEXTLINE(cert-msc32-c,cert-msc51-cpp,bugprone-random-generator-seed) + std::mt19937 generator{1}; + std::uniform_int_distribution distribution{0, 255}; + std::string input; + for (std::size_t index = 0; index < 32768; ++index) { + input.push_back(static_cast(distribution(generator))); + } + + const auto compressed{compress(input, 1)}; + EXPECT_EQ(decompress_one_shot(compressed), input); + EXPECT_EQ(decompress_stream(compressed), input); +} + +TEST(compress_random_input_one_above_window_size) { + // NOLINTNEXTLINE(cert-msc32-c,cert-msc51-cpp,bugprone-random-generator-seed) + std::mt19937 generator{1}; + std::uniform_int_distribution distribution{0, 255}; + std::string input; + for (std::size_t index = 0; index < 32769; ++index) { + input.push_back(static_cast(distribution(generator))); + } + + const auto compressed{compress(input, 1)}; + EXPECT_EQ(decompress_one_shot(compressed), input); + EXPECT_EQ(decompress_stream(compressed), input); +} + +TEST(compress_level_0_random_input_of_maximum_stored_block_length) { + // NOLINTNEXTLINE(cert-msc32-c,cert-msc51-cpp,bugprone-random-generator-seed) + std::mt19937 generator{2}; + std::uniform_int_distribution distribution{0, 255}; + std::string input; + for (std::size_t index = 0; index < 65535; ++index) { + input.push_back(static_cast(distribution(generator))); + } + + const auto compressed{compress(input, 0)}; + EXPECT_EQ(decompress_one_shot(compressed), input); + EXPECT_EQ(decompress_stream(compressed), input); +} + +TEST(compress_level_0_random_input_one_above_maximum_stored_block_length) { + // NOLINTNEXTLINE(cert-msc32-c,cert-msc51-cpp,bugprone-random-generator-seed) + std::mt19937 generator{2}; + std::uniform_int_distribution distribution{0, 255}; + std::string input; + for (std::size_t index = 0; index < 65536; ++index) { + input.push_back(static_cast(distribution(generator))); + } + + const auto compressed{compress(input, 0)}; + EXPECT_EQ(decompress_one_shot(compressed), input); + EXPECT_EQ(decompress_stream(compressed), input); +} + +TEST(compress_level_0_random_input_of_two_maximum_stored_blocks) { + // NOLINTNEXTLINE(cert-msc32-c,cert-msc51-cpp,bugprone-random-generator-seed) + std::mt19937 generator{2}; + std::uniform_int_distribution distribution{0, 255}; + std::string input; + for (std::size_t index = 0; index < 131070; ++index) { + input.push_back(static_cast(distribution(generator))); + } + + const auto compressed{compress(input, 0)}; + EXPECT_EQ(decompress_one_shot(compressed), input); + EXPECT_EQ(decompress_stream(compressed), input); +} + +TEST(compress_highly_compressible_zeros) { + const std::string input(65536, '\0'); + const auto compressed{compress(input, 1)}; + EXPECT_LT(compressed.size(), input.size() / 10); + EXPECT_EQ(decompress_one_shot(compressed), input); + EXPECT_EQ(decompress_stream(compressed), input); +} + +TEST(compress_highly_compressible_repeated_byte) { + const std::string input(65536, static_cast(0xff)); + const auto compressed{compress(input, 1)}; + EXPECT_LT(compressed.size(), input.size() / 10); + EXPECT_EQ(decompress_one_shot(compressed), input); + EXPECT_EQ(decompress_stream(compressed), input); +} + +TEST(compress_incompressible_random_input) { + // NOLINTNEXTLINE(cert-msc32-c,cert-msc51-cpp,bugprone-random-generator-seed) + std::mt19937 generator{42}; + std::uniform_int_distribution distribution{0, 255}; + std::string input; + for (std::size_t index = 0; index < 65536; ++index) { + input.push_back(static_cast(distribution(generator))); + } + + const auto compressed{compress(input, 1)}; + EXPECT_EQ(decompress_one_shot(compressed), input); + EXPECT_EQ(decompress_stream(compressed), input); +} + +TEST(compress_incompressible_input_at_level_0_barely_expands) { + // NOLINTNEXTLINE(cert-msc32-c,cert-msc51-cpp,bugprone-random-generator-seed) + std::mt19937 generator{3}; + std::uniform_int_distribution distribution{0, 255}; + std::string input; + for (std::size_t index = 0; index < 1048576; ++index) { + input.push_back(static_cast(distribution(generator))); + } + + const auto compressed{compress(input, 0)}; + EXPECT_LE(compressed.size(), input.size() + input.size() / 100); + EXPECT_EQ(decompress_one_shot(compressed), input); + EXPECT_EQ(decompress_stream(compressed), input); +} + +TEST(compress_incompressible_input_at_level_1_barely_expands) { + // NOLINTNEXTLINE(cert-msc32-c,cert-msc51-cpp,bugprone-random-generator-seed) + std::mt19937 generator{3}; + std::uniform_int_distribution distribution{0, 255}; + std::string input; + for (std::size_t index = 0; index < 1048576; ++index) { + input.push_back(static_cast(distribution(generator))); + } + + const auto compressed{compress(input, 1)}; + EXPECT_LE(compressed.size(), input.size() + input.size() / 100); + EXPECT_EQ(decompress_one_shot(compressed), input); + EXPECT_EQ(decompress_stream(compressed), input); +} + +TEST(compress_incompressible_input_at_level_6_barely_expands) { + // NOLINTNEXTLINE(cert-msc32-c,cert-msc51-cpp,bugprone-random-generator-seed) + std::mt19937 generator{3}; + std::uniform_int_distribution distribution{0, 255}; + std::string input; + for (std::size_t index = 0; index < 1048576; ++index) { + input.push_back(static_cast(distribution(generator))); + } + + const auto compressed{compress(input, 6)}; + EXPECT_LE(compressed.size(), input.size() + input.size() / 100); + EXPECT_EQ(decompress_one_shot(compressed), input); + EXPECT_EQ(decompress_stream(compressed), input); +} + +TEST(compress_incompressible_input_at_level_12_barely_expands) { + // NOLINTNEXTLINE(cert-msc32-c,cert-msc51-cpp,bugprone-random-generator-seed) + std::mt19937 generator{3}; + std::uniform_int_distribution distribution{0, 255}; + std::string input; + for (std::size_t index = 0; index < 1048576; ++index) { + input.push_back(static_cast(distribution(generator))); + } + + const auto compressed{compress(input, 12)}; + EXPECT_LE(compressed.size(), input.size() + input.size() / 100); + EXPECT_EQ(decompress_one_shot(compressed), input); + EXPECT_EQ(decompress_stream(compressed), input); +} + +TEST(compress_run_wrapping_the_window_many_times) { + const std::string input(200000, 'a'); + const auto compressed{compress(input, 1)}; + EXPECT_EQ(decompress_one_shot(compressed), input); + EXPECT_EQ(decompress_stream(compressed), input); +} + +TEST(compress_eight_megabytes_of_zeros_at_level_1) { + const std::string input(8388608, '\0'); + const auto compressed{compress(input, 1)}; + EXPECT_LT(compressed.size(), input.size() / 100); + EXPECT_EQ(decompress_one_shot(compressed), input); + EXPECT_EQ(decompress_stream(compressed), input); +} + +TEST(compress_four_megabytes_of_zeros_at_level_12) { + const std::string input(4194304, '\0'); + const auto compressed{compress(input, 12)}; + EXPECT_LT(compressed.size(), input.size() / 100); + EXPECT_EQ(decompress_one_shot(compressed), input); + EXPECT_EQ(decompress_stream(compressed), input); +} + +TEST(compress_alternating_bytes) { + std::string input; + for (std::size_t index = 0; index < 524288; ++index) { + input += "\x01\xfe"; + } + + const auto compressed{compress(input, 6)}; + EXPECT_LT(compressed.size(), input.size() / 100); + EXPECT_EQ(decompress_one_shot(compressed), input); + EXPECT_EQ(decompress_stream(compressed), input); +} + +TEST(compress_random_block_repeated_at_window_size_distance_at_level_6) { + // NOLINTNEXTLINE(cert-msc32-c,cert-msc51-cpp,bugprone-random-generator-seed) + std::mt19937 generator{4}; + std::uniform_int_distribution distribution{0, 255}; + std::string block; + for (std::size_t index = 0; index < 32768; ++index) { + block.push_back(static_cast(distribution(generator))); + } + + const auto input{block + block + block + block}; + const auto compressed{compress(input, 6)}; + EXPECT_EQ(decompress_one_shot(compressed), input); + EXPECT_EQ(decompress_stream(compressed), input); +} + +TEST(compress_random_block_repeated_at_window_size_distance_at_level_12) { + // NOLINTNEXTLINE(cert-msc32-c,cert-msc51-cpp,bugprone-random-generator-seed) + std::mt19937 generator{4}; + std::uniform_int_distribution distribution{0, 255}; + std::string block; + for (std::size_t index = 0; index < 32768; ++index) { + block.push_back(static_cast(distribution(generator))); + } + + const auto input{block + block + block + block}; + const auto compressed{compress(input, 12)}; + EXPECT_EQ(decompress_one_shot(compressed), input); + EXPECT_EQ(decompress_stream(compressed), input); +} + +TEST(compress_random_block_repeated_beyond_window_size_distance) { + // NOLINTNEXTLINE(cert-msc32-c,cert-msc51-cpp,bugprone-random-generator-seed) + std::mt19937 generator{5}; + std::uniform_int_distribution distribution{0, 255}; + std::string block; + for (std::size_t index = 0; index < 32769; ++index) { + block.push_back(static_cast(distribution(generator))); + } + + const auto input{block + block + block + block}; + const auto compressed{compress(input, 12)}; + EXPECT_EQ(decompress_one_shot(compressed), input); + EXPECT_EQ(decompress_stream(compressed), input); +} + +TEST(compress_random_block_repeated_at_maximum_match_length_period) { + // NOLINTNEXTLINE(cert-msc32-c,cert-msc51-cpp,bugprone-random-generator-seed) + std::mt19937 generator{6}; + std::uniform_int_distribution distribution{0, 255}; + std::string block; + for (std::size_t index = 0; index < 258; ++index) { + block.push_back(static_cast(distribution(generator))); + } + + std::string input; + for (std::size_t index = 0; index < 1000; ++index) { + input += block; + } + + const auto compressed{compress(input, 6)}; + EXPECT_LT(compressed.size(), input.size() / 10); + EXPECT_EQ(decompress_one_shot(compressed), input); + EXPECT_EQ(decompress_stream(compressed), input); +} + +TEST(compress_random_block_repeated_beyond_maximum_match_length_period) { + // NOLINTNEXTLINE(cert-msc32-c,cert-msc51-cpp,bugprone-random-generator-seed) + std::mt19937 generator{6}; + std::uniform_int_distribution distribution{0, 255}; + std::string block; + for (std::size_t index = 0; index < 259; ++index) { + block.push_back(static_cast(distribution(generator))); + } + + std::string input; + for (std::size_t index = 0; index < 1000; ++index) { + input += block; + } + + const auto compressed{compress(input, 6)}; + EXPECT_LT(compressed.size(), input.size() / 10); + EXPECT_EQ(decompress_one_shot(compressed), input); + EXPECT_EQ(decompress_stream(compressed), input); +} + +TEST(compress_runs_of_every_match_length) { + std::string input; + for (std::size_t length = 1; length <= 300; ++length) { + input.append(length, static_cast(length & 0xff)); + input.append(length, static_cast(~length & 0xff)); + } + + const auto compressed{compress(input, 9)}; + EXPECT_EQ(decompress_one_shot(compressed), input); + EXPECT_EQ(decompress_stream(compressed), input); +} + +TEST(compress_fibonacci_word_at_level_1) { + std::string previous{"b"}; + std::string input{"a"}; + while (input.size() < 1048576) { + auto next{input + previous}; + previous = std::move(input); + input = std::move(next); + } + + const auto compressed{compress(input, 1)}; + EXPECT_EQ(decompress_one_shot(compressed), input); + EXPECT_EQ(decompress_stream(compressed), input); +} + +TEST(compress_fibonacci_word_at_level_12) { + std::string previous{"b"}; + std::string input{"a"}; + while (input.size() < 1048576) { + auto next{input + previous}; + previous = std::move(input); + input = std::move(next); + } + + const auto compressed{compress(input, 12)}; + EXPECT_EQ(decompress_one_shot(compressed), input); + EXPECT_EQ(decompress_stream(compressed), input); +} + +TEST(compress_counter_sequence) { + std::string input; + for (std::uint32_t index = 0; index < 262144; ++index) { + input.push_back(static_cast(index & 0xff)); + input.push_back(static_cast((index >> 8) & 0xff)); + input.push_back(static_cast((index >> 16) & 0xff)); + input.push_back(static_cast((index >> 24) & 0xff)); + } + + const auto compressed{compress(input, 6)}; + EXPECT_EQ(decompress_one_shot(compressed), input); + EXPECT_EQ(decompress_stream(compressed), input); +} + +TEST(compress_already_compressed_input) { + std::string text; + for (std::size_t index = 0; index < 2000; ++index) { + text += "The quick brown fox jumps over the lazy dog. "; + } + + const auto inner{compress(text, 12)}; + const std::string input{inner.cbegin(), inner.cend()}; + const auto compressed{compress(input, 12)}; + EXPECT_EQ(decompress_one_shot(compressed), input); + EXPECT_EQ(decompress_stream(compressed), input); +} + +TEST(compress_sixteen_megabytes) { + std::string input; + for (std::uint32_t index = 0; index < 4194304; ++index) { + input.push_back(static_cast(index & 0xff)); + input.push_back(static_cast((index >> 8) & 0xff)); + input.push_back(static_cast((index >> 16) & 0xff)); + input.push_back(static_cast((index >> 24) & 0xff)); } + + const auto compressed{compress(input, 1)}; + EXPECT_LT(compressed.size(), input.size()); + EXPECT_EQ(decompress_one_shot(compressed), input); + EXPECT_EQ(decompress_stream(compressed), input); +} + +TEST(compress_members_of_different_levels_concatenate) { + const std::string first(70000, 'f'); + const std::string second{"second"}; + auto compressed{compress(first, 0)}; + const auto second_compressed{compress(second, 12)}; + const auto empty_compressed{compress("", 6)}; + compressed.insert(compressed.end(), second_compressed.cbegin(), + second_compressed.cend()); + compressed.insert(compressed.end(), empty_compressed.cbegin(), + empty_compressed.cend()); + EXPECT_EQ(decompress_one_shot(compressed), first + second); + EXPECT_EQ(decompress_stream(compressed), first + second); +} + +TEST(decompress_header_with_ftext) { + const std::vector input{ + 0x1f, 0x8b, 0x08, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x01, 0x0b, + 0x00, 0xf4, 0xff, 0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x20, 0x77, 0x6f, 0x72, + 0x6c, 0x64, 0x85, 0x11, 0x4a, 0x0d, 0x0b, 0x00, 0x00, 0x00}; + EXPECT_EQ(decompress_one_shot(input), "hello world"); + EXPECT_EQ(decompress_stream(input), "hello world"); +} + +TEST(decompress_header_with_fhcrc) { + const std::vector input{ + 0x1f, 0x8b, 0x08, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x90, 0xc9, + 0x01, 0x0b, 0x00, 0xf4, 0xff, 0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x20, 0x77, + 0x6f, 0x72, 0x6c, 0x64, 0x85, 0x11, 0x4a, 0x0d, 0x0b, 0x00, 0x00, 0x00}; + EXPECT_EQ(decompress_one_shot(input), "hello world"); + EXPECT_EQ(decompress_stream(input), "hello world"); +} + +TEST(decompress_header_with_fextra) { + const std::vector input{ + 0x1f, 0x8b, 0x08, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x05, + 0x00, 0x45, 0x58, 0x54, 0x52, 0x41, 0x01, 0x0b, 0x00, 0xf4, 0xff, + 0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x20, 0x77, 0x6f, 0x72, 0x6c, 0x64, + 0x85, 0x11, 0x4a, 0x0d, 0x0b, 0x00, 0x00, 0x00}; + EXPECT_EQ(decompress_one_shot(input), "hello world"); + EXPECT_EQ(decompress_stream(input), "hello world"); +} + +TEST(decompress_header_with_empty_fextra) { + const std::vector input{ + 0x1f, 0x8b, 0x08, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, + 0x01, 0x0b, 0x00, 0xf4, 0xff, 0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x20, 0x77, + 0x6f, 0x72, 0x6c, 0x64, 0x85, 0x11, 0x4a, 0x0d, 0x0b, 0x00, 0x00, 0x00}; + EXPECT_EQ(decompress_one_shot(input), "hello world"); + EXPECT_EQ(decompress_stream(input), "hello world"); +} + +TEST(decompress_header_with_fextra_subfields) { + const std::vector input{ + 0x1f, 0x8b, 0x08, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x0a, 0x00, + 0x41, 0x42, 0x02, 0x00, 0x01, 0x02, 0x43, 0x44, 0x00, 0x00, 0x01, 0x0b, + 0x00, 0xf4, 0xff, 0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x20, 0x77, 0x6f, 0x72, + 0x6c, 0x64, 0x85, 0x11, 0x4a, 0x0d, 0x0b, 0x00, 0x00, 0x00}; + EXPECT_EQ(decompress_one_shot(input), "hello world"); + EXPECT_EQ(decompress_stream(input), "hello world"); +} + +TEST(decompress_header_with_fextra_of_null_bytes_before_fname) { + const std::vector input{ + 0x1f, 0x8b, 0x08, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x03, + 0x00, 0x00, 0x00, 0x00, 0x6e, 0x61, 0x6d, 0x65, 0x00, 0x01, 0x0b, + 0x00, 0xf4, 0xff, 0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x20, 0x77, 0x6f, + 0x72, 0x6c, 0x64, 0x85, 0x11, 0x4a, 0x0d, 0x0b, 0x00, 0x00, 0x00}; + EXPECT_EQ(decompress_one_shot(input), "hello world"); + EXPECT_EQ(decompress_stream(input), "hello world"); +} + +TEST(decompress_header_with_fextra_and_fname) { + const std::vector input{ + 0x1f, 0x8b, 0x08, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x03, + 0x00, 0x78, 0x79, 0x7a, 0x6e, 0x61, 0x6d, 0x65, 0x00, 0x01, 0x0b, + 0x00, 0xf4, 0xff, 0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x20, 0x77, 0x6f, + 0x72, 0x6c, 0x64, 0x85, 0x11, 0x4a, 0x0d, 0x0b, 0x00, 0x00, 0x00}; + EXPECT_EQ(decompress_one_shot(input), "hello world"); + EXPECT_EQ(decompress_stream(input), "hello world"); +} + +TEST(decompress_header_with_fextra_and_fcomment) { + const std::vector input{ + 0x1f, 0x8b, 0x08, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x02, + 0x00, 0x41, 0x42, 0x68, 0x69, 0x00, 0x01, 0x0b, 0x00, 0xf4, 0xff, + 0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x20, 0x77, 0x6f, 0x72, 0x6c, 0x64, + 0x85, 0x11, 0x4a, 0x0d, 0x0b, 0x00, 0x00, 0x00}; + EXPECT_EQ(decompress_one_shot(input), "hello world"); + EXPECT_EQ(decompress_stream(input), "hello world"); +} + +TEST(decompress_header_with_fname_and_fcomment) { + const std::vector input{ + 0x1f, 0x8b, 0x08, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x61, + 0x2e, 0x74, 0x78, 0x74, 0x00, 0x63, 0x6d, 0x74, 0x00, 0x01, 0x0b, + 0x00, 0xf4, 0xff, 0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x20, 0x77, 0x6f, + 0x72, 0x6c, 0x64, 0x85, 0x11, 0x4a, 0x0d, 0x0b, 0x00, 0x00, 0x00}; + EXPECT_EQ(decompress_one_shot(input), "hello world"); + EXPECT_EQ(decompress_stream(input), "hello world"); +} + +TEST(decompress_header_with_fname) { + const std::vector input{ + 0x1f, 0x8b, 0x08, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x64, + 0x61, 0x74, 0x61, 0x2e, 0x74, 0x78, 0x74, 0x00, 0x01, 0x0b, 0x00, + 0xf4, 0xff, 0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x20, 0x77, 0x6f, 0x72, + 0x6c, 0x64, 0x85, 0x11, 0x4a, 0x0d, 0x0b, 0x00, 0x00, 0x00}; + EXPECT_EQ(decompress_one_shot(input), "hello world"); + EXPECT_EQ(decompress_stream(input), "hello world"); +} + +TEST(decompress_header_with_empty_fname) { + const std::vector input{ + 0x1f, 0x8b, 0x08, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x00, 0x01, + 0x0b, 0x00, 0xf4, 0xff, 0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x20, 0x77, 0x6f, + 0x72, 0x6c, 0x64, 0x85, 0x11, 0x4a, 0x0d, 0x0b, 0x00, 0x00, 0x00}; + EXPECT_EQ(decompress_one_shot(input), "hello world"); + EXPECT_EQ(decompress_stream(input), "hello world"); +} + +TEST(decompress_header_with_fname_containing_gzip_magic) { + const std::vector input{ + 0x1f, 0x8b, 0x08, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, + 0x1f, 0x8b, 0x08, 0x1f, 0x8b, 0x00, 0x01, 0x0b, 0x00, 0xf4, + 0xff, 0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x20, 0x77, 0x6f, 0x72, + 0x6c, 0x64, 0x85, 0x11, 0x4a, 0x0d, 0x0b, 0x00, 0x00, 0x00}; + EXPECT_EQ(decompress_one_shot(input), "hello world"); + EXPECT_EQ(decompress_stream(input), "hello world"); +} + +TEST(decompress_header_with_latin1_fname) { + const std::vector input{ + 0x1f, 0x8b, 0x08, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, + 0xa9, 0xc6, 0xff, 0x00, 0x01, 0x0b, 0x00, 0xf4, 0xff, 0x68, + 0x65, 0x6c, 0x6c, 0x6f, 0x20, 0x77, 0x6f, 0x72, 0x6c, 0x64, + 0x85, 0x11, 0x4a, 0x0d, 0x0b, 0x00, 0x00, 0x00}; + EXPECT_EQ(decompress_one_shot(input), "hello world"); + EXPECT_EQ(decompress_stream(input), "hello world"); +} + +TEST(decompress_header_with_fcomment) { + const std::vector input{ + 0x1f, 0x8b, 0x08, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x61, 0x20, + 0x63, 0x6f, 0x6d, 0x6d, 0x65, 0x6e, 0x74, 0x0a, 0x77, 0x69, 0x74, 0x68, + 0x20, 0x74, 0x77, 0x6f, 0x20, 0x6c, 0x69, 0x6e, 0x65, 0x73, 0x00, 0x01, + 0x0b, 0x00, 0xf4, 0xff, 0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x20, 0x77, 0x6f, + 0x72, 0x6c, 0x64, 0x85, 0x11, 0x4a, 0x0d, 0x0b, 0x00, 0x00, 0x00}; + EXPECT_EQ(decompress_one_shot(input), "hello world"); + EXPECT_EQ(decompress_stream(input), "hello world"); +} + +TEST(decompress_header_with_empty_fcomment) { + const std::vector input{ + 0x1f, 0x8b, 0x08, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x00, 0x01, + 0x0b, 0x00, 0xf4, 0xff, 0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x20, 0x77, 0x6f, + 0x72, 0x6c, 0x64, 0x85, 0x11, 0x4a, 0x0d, 0x0b, 0x00, 0x00, 0x00}; + EXPECT_EQ(decompress_one_shot(input), "hello world"); + EXPECT_EQ(decompress_stream(input), "hello world"); +} + +TEST(decompress_header_with_latin1_fcomment) { + const std::vector input{ + 0x1f, 0x8b, 0x08, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, + 0xc0, 0xc1, 0xfe, 0x00, 0x01, 0x0b, 0x00, 0xf4, 0xff, 0x68, + 0x65, 0x6c, 0x6c, 0x6f, 0x20, 0x77, 0x6f, 0x72, 0x6c, 0x64, + 0x85, 0x11, 0x4a, 0x0d, 0x0b, 0x00, 0x00, 0x00}; + EXPECT_EQ(decompress_one_shot(input), "hello world"); + EXPECT_EQ(decompress_stream(input), "hello world"); +} + +TEST(decompress_header_with_every_optional_field) { + const std::vector input{ + 0x1f, 0x8b, 0x08, 0x1f, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x04, 0x00, + 0xaa, 0xbb, 0xcc, 0xdd, 0x6e, 0x61, 0x6d, 0x65, 0x2e, 0x74, 0x78, 0x74, + 0x00, 0x63, 0x6f, 0x6d, 0x6d, 0x65, 0x6e, 0x74, 0x00, 0x90, 0x3e, 0x01, + 0x0b, 0x00, 0xf4, 0xff, 0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x20, 0x77, 0x6f, + 0x72, 0x6c, 0x64, 0x85, 0x11, 0x4a, 0x0d, 0x0b, 0x00, 0x00, 0x00}; + EXPECT_EQ(decompress_one_shot(input), "hello world"); + EXPECT_EQ(decompress_stream(input), "hello world"); +} + +TEST(decompress_header_with_nonzero_mtime) { + const std::vector input{ + 0x1f, 0x8b, 0x08, 0x00, 0x78, 0x56, 0x34, 0x12, 0x00, 0xff, 0x01, 0x0b, + 0x00, 0xf4, 0xff, 0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x20, 0x77, 0x6f, 0x72, + 0x6c, 0x64, 0x85, 0x11, 0x4a, 0x0d, 0x0b, 0x00, 0x00, 0x00}; + EXPECT_EQ(decompress_one_shot(input), "hello world"); + EXPECT_EQ(decompress_stream(input), "hello world"); +} + +TEST(decompress_header_with_maximum_compression_xfl) { + const std::vector input{ + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x01, 0x0b, + 0x00, 0xf4, 0xff, 0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x20, 0x77, 0x6f, 0x72, + 0x6c, 0x64, 0x85, 0x11, 0x4a, 0x0d, 0x0b, 0x00, 0x00, 0x00}; + EXPECT_EQ(decompress_one_shot(input), "hello world"); + EXPECT_EQ(decompress_stream(input), "hello world"); +} + +TEST(decompress_header_with_fastest_compression_xfl) { + const std::vector input{ + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0xff, 0x01, 0x0b, + 0x00, 0xf4, 0xff, 0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x20, 0x77, 0x6f, 0x72, + 0x6c, 0x64, 0x85, 0x11, 0x4a, 0x0d, 0x0b, 0x00, 0x00, 0x00}; + EXPECT_EQ(decompress_one_shot(input), "hello world"); + EXPECT_EQ(decompress_stream(input), "hello world"); +} + +TEST(decompress_header_with_unix_os) { + const std::vector input{ + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x01, 0x0b, + 0x00, 0xf4, 0xff, 0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x20, 0x77, 0x6f, 0x72, + 0x6c, 0x64, 0x85, 0x11, 0x4a, 0x0d, 0x0b, 0x00, 0x00, 0x00}; + EXPECT_EQ(decompress_one_shot(input), "hello world"); + EXPECT_EQ(decompress_stream(input), "hello world"); +} + +TEST(decompress_stored_block_with_empty_final_block) { + const std::vector input{ + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x01, 0x00, + 0x00, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; + EXPECT_EQ(decompress_one_shot(input), ""); + EXPECT_EQ(decompress_stream(input), ""); +} + +TEST(decompress_empty_stored_block_before_data) { + const std::vector input{ + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, + 0x00, 0x00, 0x00, 0xff, 0xff, 0x01, 0x0b, 0x00, 0xf4, 0xff, + 0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x20, 0x77, 0x6f, 0x72, 0x6c, + 0x64, 0x85, 0x11, 0x4a, 0x0d, 0x0b, 0x00, 0x00, 0x00}; + EXPECT_EQ(decompress_one_shot(input), "hello world"); + EXPECT_EQ(decompress_stream(input), "hello world"); +} + +TEST(decompress_two_stored_blocks) { + const std::vector input{ + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, + 0x00, 0x05, 0x00, 0xfa, 0xff, 0x68, 0x65, 0x6c, 0x6c, 0x6f, + 0x01, 0x06, 0x00, 0xf9, 0xff, 0x20, 0x77, 0x6f, 0x72, 0x6c, + 0x64, 0x85, 0x11, 0x4a, 0x0d, 0x0b, 0x00, 0x00, 0x00}; + EXPECT_EQ(decompress_one_shot(input), "hello world"); + EXPECT_EQ(decompress_stream(input), "hello world"); +} + +TEST(decompress_stored_block_ignores_nonzero_bits_before_byte_boundary) { + const std::vector input{ + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xff, 0x4a, 0x04, 0xe4, 0x01, 0x00, 0xfe, 0xff, 0x62, + 0x6d, 0x48, 0x83, 0x9e, 0x02, 0x00, 0x00, 0x00}; + EXPECT_EQ(decompress_one_shot(input), "ab"); + EXPECT_EQ(decompress_stream(input), "ab"); +} + +TEST(decompress_fixed_block_with_every_literal_value) { + const std::vector input{ + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x63, 0x60, + 0x64, 0x62, 0x66, 0x61, 0x65, 0x63, 0xe7, 0xe0, 0xe4, 0xe2, 0xe6, 0xe1, + 0xe5, 0xe3, 0x17, 0x10, 0x14, 0x12, 0x16, 0x11, 0x15, 0x13, 0x97, 0x90, + 0x94, 0x92, 0x96, 0x91, 0x95, 0x93, 0x57, 0x50, 0x54, 0x52, 0x56, 0x51, + 0x55, 0x53, 0xd7, 0xd0, 0xd4, 0xd2, 0xd6, 0xd1, 0xd5, 0xd3, 0x37, 0x30, + 0x34, 0x32, 0x36, 0x31, 0x35, 0x33, 0xb7, 0xb0, 0xb4, 0xb2, 0xb6, 0xb1, + 0xb5, 0xb3, 0x77, 0x70, 0x74, 0x72, 0x76, 0x71, 0x75, 0x73, 0xf7, 0xf0, + 0xf4, 0xf2, 0xf6, 0xf1, 0xf5, 0xf3, 0x0f, 0x08, 0x0c, 0x0a, 0x0e, 0x09, + 0x0d, 0x0b, 0x8f, 0x88, 0x8c, 0x8a, 0x8e, 0x89, 0x8d, 0x8b, 0x4f, 0x48, + 0x4c, 0x4a, 0x4e, 0x49, 0x4d, 0x4b, 0xcf, 0xc8, 0xcc, 0xca, 0xce, 0xc9, + 0xcd, 0xcb, 0x2f, 0x28, 0x2c, 0x2a, 0x2e, 0x29, 0x2d, 0x2b, 0xaf, 0xa8, + 0xac, 0xaa, 0xae, 0xa9, 0xad, 0xab, 0x6f, 0x68, 0x6c, 0x6a, 0x6e, 0x69, + 0x6d, 0x6b, 0xef, 0xe8, 0xec, 0xea, 0xee, 0xe9, 0xed, 0xeb, 0x9f, 0x30, + 0x71, 0xd2, 0xe4, 0x29, 0x53, 0xa7, 0x4d, 0x9f, 0x31, 0x73, 0xd6, 0xec, + 0x39, 0x73, 0xe7, 0xcd, 0x5f, 0xb0, 0x70, 0xd1, 0xe2, 0x25, 0x4b, 0x97, + 0x2d, 0x5f, 0xb1, 0x72, 0xd5, 0xea, 0x35, 0x6b, 0xd7, 0xad, 0xdf, 0xb0, + 0x71, 0xd3, 0xe6, 0x2d, 0x5b, 0xb7, 0x6d, 0xdf, 0xb1, 0x73, 0xd7, 0xee, + 0x3d, 0x7b, 0xf7, 0xed, 0x3f, 0x70, 0xf0, 0xd0, 0xe1, 0x23, 0x47, 0x8f, + 0x1d, 0x3f, 0x71, 0xf2, 0xd4, 0xe9, 0x33, 0x67, 0xcf, 0x9d, 0xbf, 0x70, + 0xf1, 0xd2, 0xe5, 0x2b, 0x57, 0xaf, 0x5d, 0xbf, 0x71, 0xf3, 0xd6, 0xed, + 0x3b, 0x77, 0xef, 0xdd, 0x7f, 0xf0, 0xf0, 0xd1, 0xe3, 0x27, 0x4f, 0x9f, + 0x3d, 0x7f, 0xf1, 0xf2, 0xd5, 0xeb, 0x37, 0x6f, 0xdf, 0xbd, 0xff, 0xf0, + 0xf1, 0xd3, 0xe7, 0x2f, 0x5f, 0xbf, 0x7d, 0xff, 0xf1, 0xf3, 0xd7, 0xef, + 0x3f, 0x7f, 0xff, 0xfd, 0x07, 0x00, 0x73, 0x8c, 0x05, 0x29, 0x00, 0x01, + 0x00, 0x00}; + std::string expected; + for (std::size_t index = 0; index < 256; ++index) { + expected.push_back(static_cast(index)); + } + + EXPECT_EQ(decompress_one_shot(input), expected); + EXPECT_EQ(decompress_stream(input), expected); +} + +TEST(decompress_fixed_block_with_only_end_of_block) { + const std::vector input{ + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x13, + 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; + EXPECT_EQ(decompress_one_shot(input), ""); + EXPECT_EQ(decompress_stream(input), ""); +} + +TEST(decompress_fixed_block_with_repeated_word) { + const std::vector input{ + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x13, + 0xcb, 0x48, 0xcd, 0xc9, 0xc9, 0x57, 0xc8, 0xc0, 0x4e, 0x02, + 0x00, 0xf6, 0xd2, 0x53, 0x38, 0x1d, 0x00, 0x00, 0x00}; + EXPECT_EQ(decompress_one_shot(input), "hello hello hello hello hello"); + EXPECT_EQ(decompress_stream(input), "hello hello hello hello hello"); +} + +TEST(decompress_fixed_block_with_boundary_match_lengths) { + const std::vector input{ + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x4b, + 0x04, 0x02, 0x04, 0x40, 0x02, 0xc8, 0x00, 0x13, 0x60, 0x01, 0x84, + 0x01, 0x11, 0x60, 0xe0, 0xc1, 0x20, 0x00, 0x23, 0x00, 0x8c, 0x78, + 0x30, 0x0a, 0x00, 0x9b, 0xb4, 0x10, 0xfa, 0x7a, 0x04, 0x00, 0x00}; + EXPECT_EQ(decompress_one_shot(input), std::string(1146, 'a')); + EXPECT_EQ(decompress_stream(input), std::string(1146, 'a')); +} + +TEST(decompress_fixed_block_overlapping_match) { + const std::vector input{ + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x8b, 0x88, + 0x04, 0x43, 0x00, 0x60, 0xa5, 0xd7, 0x74, 0x07, 0x00, 0x00, 0x00}; + EXPECT_EQ(decompress_one_shot(input), "XYXYXYX"); + EXPECT_EQ(decompress_stream(input), "XYXYXYX"); +} + +TEST(decompress_fixed_block_maximum_length_match_at_distance_one) { + const std::vector input{ + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xab, + 0x18, 0x05, 0x00, 0xad, 0x7c, 0x22, 0xf7, 0x03, 0x01, 0x00, 0x00}; + EXPECT_EQ(decompress_one_shot(input), std::string(259, 'x')); + EXPECT_EQ(decompress_stream(input), std::string(259, 'x')); +} + +TEST(decompress_fixed_block_matches_wrapping_the_window) { + const std::vector input{ + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x4b, 0x4c, + 0x1a, 0x85, 0xa3, 0x70, 0x14, 0x8e, 0xc2, 0x51, 0x38, 0x0a, 0x47, 0xe1, + 0x28, 0x1c, 0x85, 0xa3, 0x70, 0x14, 0x8e, 0xc2, 0x51, 0x38, 0x0a, 0x47, + 0xe1, 0x28, 0x1c, 0x85, 0xa3, 0x70, 0x14, 0x8e, 0xc2, 0x51, 0x38, 0x0a, + 0x47, 0xe1, 0x28, 0x1c, 0x85, 0xa3, 0x70, 0x14, 0x8e, 0xc2, 0x51, 0x38, + 0x0a, 0x47, 0xe1, 0x28, 0x1c, 0x85, 0xa3, 0x70, 0x14, 0x8e, 0xc2, 0x51, + 0x38, 0x0a, 0x47, 0xe1, 0x28, 0x1c, 0x85, 0xa3, 0x70, 0x14, 0x8e, 0xc2, + 0x51, 0x38, 0x0a, 0x47, 0xe1, 0x28, 0x1c, 0x85, 0xa3, 0x70, 0x14, 0x8e, + 0xc2, 0x51, 0x38, 0x0a, 0x47, 0xe1, 0x28, 0x1c, 0x85, 0xa3, 0x70, 0x14, + 0x8e, 0xc2, 0x51, 0x38, 0x0a, 0x47, 0xe1, 0x28, 0x1c, 0x85, 0xa3, 0x70, + 0x14, 0x8e, 0xc2, 0x51, 0x38, 0x0a, 0x47, 0xe1, 0x28, 0x1c, 0x85, 0xa3, + 0x70, 0x14, 0x8e, 0xc2, 0x51, 0x38, 0x0a, 0x47, 0xe1, 0x28, 0x1c, 0x85, + 0xa3, 0x70, 0x14, 0x8e, 0xc2, 0x51, 0x38, 0x0a, 0x47, 0xe1, 0x28, 0x1c, + 0x85, 0xa3, 0x70, 0x14, 0x8e, 0xc2, 0x51, 0x38, 0x0a, 0x47, 0xe1, 0x28, + 0x1c, 0x85, 0xa3, 0x70, 0x14, 0x8e, 0xc2, 0x51, 0x38, 0x0a, 0x47, 0xe1, + 0x28, 0x1c, 0x85, 0xa3, 0x70, 0x14, 0x8e, 0xc2, 0x51, 0x38, 0x0a, 0x47, + 0xe1, 0x28, 0x1c, 0x85, 0xa3, 0x70, 0x14, 0x8e, 0xc2, 0x51, 0x38, 0x0a, + 0x47, 0xe1, 0x28, 0x1c, 0x85, 0xa3, 0x70, 0x14, 0x8e, 0xc2, 0x51, 0x38, + 0x0a, 0x87, 0x36, 0x1c, 0x0d, 0xab, 0xd1, 0x10, 0x00, 0x00, 0xba, 0xf1, + 0x51, 0x56, 0xc0, 0x81, 0x00, 0x00}; + std::string expected; + for (std::size_t index = 0; index < 16608; ++index) { + expected += "ab"; + } + + EXPECT_EQ(decompress_one_shot(input), expected); + EXPECT_EQ(decompress_stream(input), expected); +} + +TEST(decompress_match_across_stored_and_fixed_blocks) { + const std::vector input{ + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x00, + 0x06, 0x00, 0xf9, 0xff, 0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x20, 0x03, + 0x93, 0x00, 0x40, 0xa6, 0x2d, 0x01, 0x0b, 0x00, 0x00, 0x00}; + EXPECT_EQ(decompress_one_shot(input), "hello hello"); + EXPECT_EQ(decompress_stream(input), "hello hello"); +} + +TEST(decompress_match_spanning_several_blocks) { + const std::vector input{ + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, + 0x00, 0x02, 0x00, 0xfd, 0xff, 0x61, 0x62, 0x4a, 0x4e, 0x01, + 0x00, 0x02, 0x00, 0xfd, 0xff, 0x65, 0x66, 0x83, 0x90, 0x00, + 0x66, 0xe9, 0xe4, 0x71, 0x0c, 0x00, 0x00, 0x00}; + EXPECT_EQ(decompress_one_shot(input), "abcdefabcdef"); + EXPECT_EQ(decompress_stream(input), "abcdefabcdef"); +} + +TEST(decompress_ignores_nonzero_padding_bits_after_final_block) { + const std::vector input{ + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x4b, + 0x04, 0xfc, 0x43, 0xbe, 0xb7, 0xe8, 0x01, 0x00, 0x00, 0x00}; + EXPECT_EQ(decompress_one_shot(input), "a"); + EXPECT_EQ(decompress_stream(input), "a"); +} + +TEST(decompress_dynamic_block_with_literals_and_match) { + const std::vector input{ + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x0d, 0xe0, + 0x01, 0x90, 0x24, 0x49, 0x92, 0x24, 0x49, 0x02, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x10, 0x21, 0xe2, 0x02, 0x14, 0x41, 0xc8, 0x7f, 0x07, + 0x00, 0x00, 0x00}; + EXPECT_EQ(decompress_one_shot(input), "ababbbb"); + EXPECT_EQ(decompress_stream(input), "ababbbb"); +} + +TEST(decompress_dynamic_block_without_distance_codes) { + const std::vector input{ + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x05, 0xe0, + 0x01, 0x90, 0x24, 0x49, 0x92, 0x24, 0x49, 0x02, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x20, 0x20, 0x2d, 0x73, 0x07, 0xf0, 0x03, 0x00, 0x00, + 0x00}; + EXPECT_EQ(decompress_one_shot(input), "aaa"); + EXPECT_EQ(decompress_stream(input), "aaa"); +} + +TEST(decompress_dynamic_block_with_only_end_of_block) { + const std::vector input{ + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x05, + 0xc0, 0x81, 0x08, 0x00, 0x00, 0x00, 0x00, 0x20, 0x7f, 0xeb, 0x03, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; + EXPECT_EQ(decompress_one_shot(input), ""); + EXPECT_EQ(decompress_stream(input), ""); +} + +TEST(decompress_dynamic_block_with_single_literal_length_code) { + const std::vector input{ + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x05, 0xe0, + 0x01, 0x90, 0x24, 0x49, 0x92, 0x24, 0x49, 0x02, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00}; + EXPECT_EQ(decompress_one_shot(input), ""); + EXPECT_EQ(decompress_stream(input), ""); +} + +TEST(decompress_dynamic_block_with_maximum_literal_length_codes) { + const std::vector input{ + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xed, 0xe0, + 0x01, 0x90, 0x24, 0x49, 0x92, 0x24, 0x49, 0x02, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xa1, 0x05, 0x56, 0xfa, 0xc2, 0x34, + 0x03, 0x01, 0x00, 0x00}; + EXPECT_EQ(decompress_one_shot(input), std::string(259, 'a')); + EXPECT_EQ(decompress_stream(input), std::string(259, 'a')); +} + +TEST(decompress_dynamic_block_with_maximum_distance_codes) { + const std::vector input{ + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x0d, 0xff, + 0x01, 0x90, 0x24, 0x49, 0x92, 0x24, 0x49, 0x02, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x10, 0x21, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xe0, 0x17, 0x0b, 0xe0, + 0xcc, 0xc9, 0x08, 0x00, 0x00, 0x00}; + EXPECT_EQ(decompress_one_shot(input), "ababaaaa"); + EXPECT_EQ(decompress_stream(input), "ababaaaa"); +} + +TEST(decompress_dynamic_block_with_fifteen_bit_literal_length_codes) { + const std::vector input{ + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x05, 0xe0, + 0x01, 0x90, 0x24, 0x49, 0x92, 0x24, 0x49, 0x02, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x12, 0x8b, 0x9a, 0x47, + 0x56, 0xcf, 0xde, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x3c, 0x68, 0xf7, 0xbe, 0xdf, 0xdf, 0xbf, 0xff, 0xfe, + 0xf7, 0x7f, 0xff, 0xef, 0xff, 0xfb, 0xff, 0xfd, 0xff, 0xfe, 0xff, 0x66, + 0xcd, 0x32, 0x19, 0x10, 0x00, 0x00, 0x00}; + EXPECT_EQ(decompress_one_shot(input), "abcdefghijklmnoo"); + EXPECT_EQ(decompress_stream(input), "abcdefghijklmnoo"); +} + +TEST(decompress_dynamic_block_with_fifteen_bit_distance_codes) { + const std::vector input{ + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, + 0x01, 0xff, 0xfe, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, + 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, + 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20, + 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2a, 0x2b, 0x2c, + 0x2d, 0x2e, 0x2f, 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, + 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, 0x40, 0x41, 0x42, 0x43, 0x44, + 0x45, 0x46, 0x47, 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, 0x50, + 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5a, 0x5b, 0x5c, + 0x5d, 0x5e, 0x5f, 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, + 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, 0x70, 0x71, 0x72, 0x73, 0x74, + 0x75, 0x76, 0x77, 0x78, 0x79, 0x7a, 0x7b, 0x7c, 0x7d, 0x7e, 0x7f, 0x80, + 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8a, 0x8b, 0x8c, + 0x8d, 0x8e, 0x8f, 0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, + 0x99, 0x9a, 0x9b, 0x9c, 0x9d, 0x9e, 0x9f, 0xa0, 0xa1, 0xa2, 0xa3, 0xa4, + 0xa5, 0xa6, 0xa7, 0xa8, 0xa9, 0xaa, 0xab, 0xac, 0xad, 0xae, 0xaf, 0xb0, + 0xb1, 0xb2, 0xb3, 0xb4, 0xb5, 0xb6, 0xb7, 0xb8, 0xb9, 0xba, 0xbb, 0xbc, + 0xbd, 0xbe, 0xbf, 0xc0, 0xc1, 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc7, 0xc8, + 0xc9, 0xca, 0xcb, 0xcc, 0xcd, 0xce, 0xcf, 0xd0, 0xd1, 0xd2, 0xd3, 0xd4, + 0xd5, 0xd6, 0xd7, 0xd8, 0xd9, 0xda, 0xdb, 0xdc, 0xdd, 0xde, 0xdf, 0xe0, + 0xe1, 0xe2, 0xe3, 0xe4, 0xe5, 0xe6, 0xe7, 0xe8, 0xe9, 0xea, 0xeb, 0xec, + 0xed, 0xee, 0xef, 0xf0, 0xf1, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, + 0xf9, 0xfa, 0xfb, 0xfc, 0xfd, 0xfe, 0xff, 0x0d, 0xef, 0x01, 0x90, 0x24, + 0x49, 0x92, 0x24, 0x49, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x20, 0x22, 0xb1, 0xa8, 0x79, 0x64, 0xf5, 0xec, 0xfd, 0xff, 0xff, 0x03, + 0xff, 0x7f, 0x7f, 0x00, 0x61, 0xdc, 0xdd, 0x16, 0x09, 0x01, 0x00, 0x00}; + std::string expected; + for (std::size_t index = 0; index < 256; ++index) { + expected.push_back(static_cast(index)); + } + + expected.append({0x3f, 0x40, 0x41, 0x43, 0x44, 0x45, 0x45, 0x45, 0x45}); + EXPECT_EQ(decompress_one_shot(input), expected); + EXPECT_EQ(decompress_stream(input), expected); +} + +TEST(decompress_dynamic_block_with_seven_bit_code_length_codes) { + const std::vector input{ + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x05, 0xc0, + 0x01, 0x04, 0xc7, 0x61, 0x14, 0xc4, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xed, 0xde, 0xf7, 0xfb, 0x03, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfc, 0x69, 0xf7, 0xbe, 0xdf, 0x1f, + 0xa6, 0x6a, 0x2a, 0x31, 0x07, 0x00, 0x00, 0x00}; + EXPECT_EQ(decompress_one_shot(input), "abcdefg"); + EXPECT_EQ(decompress_stream(input), "abcdefg"); +} + +TEST(decompress_dynamic_block_repeat_previous_code_length_across_alphabets) { + const std::vector input{ + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x0d, + 0x83, 0x05, 0x01, 0x00, 0x00, 0x00, 0x40, 0xb6, 0xf2, 0x7f, 0x84, + 0x44, 0xfc, 0x0b, 0xbb, 0xc2, 0xf9, 0x28, 0x0a, 0x00, 0x00, 0x00}; + EXPECT_EQ(decompress_one_shot(input), "abababaaba"); + EXPECT_EQ(decompress_stream(input), "abababaaba"); +} + +TEST(decompress_dynamic_block_repeat_zero_code_length_across_alphabets) { + const std::vector input{ + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x1d, 0xc3, + 0x21, 0x01, 0x00, 0x00, 0x00, 0x80, 0xa0, 0xad, 0xfa, 0x7f, 0x84, 0x16, + 0x40, 0xfc, 0x02, 0xbb, 0xc2, 0xf9, 0x28, 0x0a, 0x00, 0x00, 0x00}; + EXPECT_EQ(decompress_one_shot(input), "abababaaba"); + EXPECT_EQ(decompress_stream(input), "abababaaba"); +} + +TEST(decompress_member_mixing_every_block_type) { + const std::vector input{ + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x00, 0x07, + 0x00, 0xf8, 0xff, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x64, 0x20, 0x4a, 0xcb, + 0xac, 0x48, 0x4d, 0x51, 0x00, 0x10, 0x80, 0x07, 0x40, 0x92, 0x24, 0x49, + 0x92, 0x24, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x0c, 0xcc, 0x00, 0x00, 0x0c, 0x00, 0xcc, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0xe0, + 0x1b, 0x1b, 0xe1, 0x07, 0x15, 0x04, 0x00, 0x4a, 0xa1, 0x0b, 0x7c, 0x1c, + 0x00, 0x00, 0x00}; + EXPECT_EQ(decompress_one_shot(input), "stored fixed dynamic stored "); + EXPECT_EQ(decompress_stream(input), "stored fixed dynamic stored "); +} + +TEST(decompress_two_members) { + const std::vector input{ + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0xff, 0x01, 0x05, + 0x00, 0xfa, 0xff, 0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x86, 0xa6, 0x10, 0x36, + 0x05, 0x00, 0x00, 0x00, 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x04, 0xff, 0x01, 0x06, 0x00, 0xf9, 0xff, 0x20, 0x77, 0x6f, 0x72, 0x6c, + 0x64, 0xcb, 0x42, 0x3b, 0x4a, 0x06, 0x00, 0x00, 0x00}; + EXPECT_EQ(decompress_one_shot(input), "hello world"); + EXPECT_EQ(decompress_stream(input), "hello world"); +} + +TEST(decompress_three_members_with_empty_member) { + const std::vector input{ + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0xff, 0x01, + 0x03, 0x00, 0xfc, 0xff, 0x66, 0x6f, 0x6f, 0x21, 0x65, 0x73, 0x8c, + 0x03, 0x00, 0x00, 0x00, 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x04, 0xff, 0x01, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x04, 0xff, 0x01, 0x03, 0x00, 0xfc, 0xff, 0x62, 0x61, + 0x7a, 0x98, 0x04, 0x24, 0x78, 0x03, 0x00, 0x00, 0x00}; + EXPECT_EQ(decompress_one_shot(input), "foobaz"); + EXPECT_EQ(decompress_stream(input), "foobaz"); +} + +TEST(decompress_empty_member_between_members) { + const std::vector input{ + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0xff, 0x01, 0x04, + 0x00, 0xfb, 0xff, 0x6c, 0x65, 0x66, 0x74, 0x68, 0xe7, 0x67, 0x7a, 0x04, + 0x00, 0x00, 0x00, 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, + 0xff, 0x01, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0xff, + 0x01, 0x05, 0x00, 0xfa, 0xff, 0x72, 0x69, 0x67, 0x68, 0x74, 0x14, 0x75, + 0xca, 0xb4, 0x05, 0x00, 0x00, 0x00}; + EXPECT_EQ(decompress_one_shot(input), "leftright"); + EXPECT_EQ(decompress_stream(input), "leftright"); +} + +TEST(decompress_members_with_different_optional_header_fields) { + const std::vector input{ + 0x1f, 0x8b, 0x08, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x61, + 0x2e, 0x74, 0x78, 0x74, 0x00, 0x01, 0x06, 0x00, 0xf9, 0xff, 0x68, + 0x65, 0x6c, 0x6c, 0x6f, 0x20, 0xf6, 0xf9, 0x81, 0xed, 0x06, 0x00, + 0x00, 0x00, 0x1f, 0x8b, 0x08, 0x1f, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xff, 0x04, 0x00, 0xaa, 0xbb, 0xcc, 0xdd, 0x6e, 0x61, 0x6d, 0x65, + 0x2e, 0x74, 0x78, 0x74, 0x00, 0x63, 0x6f, 0x6d, 0x6d, 0x65, 0x6e, + 0x74, 0x00, 0x90, 0x3e, 0x01, 0x05, 0x00, 0xfa, 0xff, 0x77, 0x6f, + 0x72, 0x6c, 0x64, 0x43, 0x11, 0x77, 0x3a, 0x05, 0x00, 0x00, 0x00}; + EXPECT_EQ(decompress_one_shot(input), "hello world"); + EXPECT_EQ(decompress_stream(input), "hello world"); +} + +TEST(decompress_run_wrapping_the_window) { + const std::vector input{ + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0xff, 0xec, 0xc1, + 0x81, 0x00, 0x00, 0x00, 0x00, 0x80, 0x20, 0xd6, 0xfd, 0x25, 0x16, 0xa9, + 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x98, 0x3d, 0x38, 0x10, 0x00, 0x00, 0x00, 0x00, + 0x00, 0xf2, 0x7f, 0x6d, 0x04, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, + 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, + 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, + 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, + 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, + 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x95, 0xf6, 0xe0, 0x90, + 0x00, 0x00, 0x00, 0x00, 0x40, 0xd0, 0xff, 0xd7, 0x6e, 0xb0, 0x03, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x5c, 0x01, 0x9b, 0x53, 0x69, 0xe0, 0x40, + 0x0d, 0x03, 0x00}; + EXPECT_EQ(decompress_one_shot(input), std::string(200000, 'a')); + EXPECT_EQ(decompress_stream(input), std::string(200000, 'a')); +} + +TEST(decompress_ignores_trailing_text) { + const std::vector input{ + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0xff, 0x01, + 0x0b, 0x00, 0xf4, 0xff, 0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x20, 0x77, + 0x6f, 0x72, 0x6c, 0x64, 0x85, 0x11, 0x4a, 0x0d, 0x0b, 0x00, 0x00, + 0x00, 0x67, 0x61, 0x72, 0x62, 0x61, 0x67, 0x65, 0x20, 0x64, 0x61, + 0x74, 0x61, 0x20, 0x61, 0x66, 0x74, 0x65, 0x72, 0x20, 0x67, 0x7a, + 0x69, 0x70, 0x20, 0x65, 0x6e, 0x64}; + EXPECT_EQ(decompress_one_shot(input), "hello world"); + EXPECT_EQ(decompress_stream(input), "hello world"); +} + +TEST(decompress_ignores_single_trailing_identification_byte) { + const std::vector input{ + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0xff, 0x01, 0x0b, + 0x00, 0xf4, 0xff, 0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x20, 0x77, 0x6f, 0x72, + 0x6c, 0x64, 0x85, 0x11, 0x4a, 0x0d, 0x0b, 0x00, 0x00, 0x00, 0x1f}; + EXPECT_EQ(decompress_one_shot(input), "hello world"); + EXPECT_EQ(decompress_stream(input), "hello world"); +} + +TEST(decompress_ignores_trailing_first_identification_byte_without_second) { + const std::vector input{ + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0xff, + 0x01, 0x0b, 0x00, 0xf4, 0xff, 0x68, 0x65, 0x6c, 0x6c, 0x6f, + 0x20, 0x77, 0x6f, 0x72, 0x6c, 0x64, 0x85, 0x11, 0x4a, 0x0d, + 0x0b, 0x00, 0x00, 0x00, 0x1f, 0x8c, 0x6d, 0x6f, 0x72, 0x65}; + EXPECT_EQ(decompress_one_shot(input), "hello world"); + EXPECT_EQ(decompress_stream(input), "hello world"); +} + +TEST(decompress_ignores_member_after_trailing_garbage) { + const std::vector input{ + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0xff, 0x01, 0x05, + 0x00, 0xfa, 0xff, 0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x86, 0xa6, 0x10, 0x36, + 0x05, 0x00, 0x00, 0x00, 0x6a, 0x75, 0x6e, 0x6b, 0x1f, 0x8b, 0x08, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x04, 0xff, 0x01, 0x05, 0x00, 0xfa, 0xff, 0x77, + 0x6f, 0x72, 0x6c, 0x64, 0x43, 0x11, 0x77, 0x3a, 0x05, 0x00, 0x00, 0x00}; + EXPECT_EQ(decompress_one_shot(input), "hello"); + EXPECT_EQ(decompress_stream(input), "hello"); +} + +TEST(decompress_ignores_trailing_garbage_after_empty_member) { + const std::vector input{ + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, + 0xff, 0x01, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x6a, 0x75, 0x6e, 0x6b}; + EXPECT_EQ(decompress_one_shot(input), ""); + EXPECT_EQ(decompress_stream(input), ""); +} + +TEST(decompress_header_with_maximum_length_fextra) { + std::vector input{0x1f, 0x8b, 0x08, 0x04, 0x00, 0x00, + 0x00, 0x00, 0x00, 0xff, 0xff, 0xff}; + input.insert(input.end(), 65535, 0x1f); + input.insert(input.end(), {0x01, 0x0b, 0x00, 0xf4, 0xff, 0x68, 0x65, 0x6c, + 0x6c, 0x6f, 0x20, 0x77, 0x6f, 0x72, 0x6c, 0x64, + 0x85, 0x11, 0x4a, 0x0d, 0x0b, 0x00, 0x00, 0x00}); + EXPECT_EQ(decompress_one_shot(input), "hello world"); + EXPECT_EQ(decompress_stream(input), "hello world"); +} + +TEST(decompress_header_with_one_megabyte_fname) { + std::vector input{0x1f, 0x8b, 0x08, 0x08, 0x00, + 0x00, 0x00, 0x00, 0x00, 0xff}; + input.insert(input.end(), 1048576, 0x6e); + input.push_back(0x00); + input.insert(input.end(), {0x01, 0x0b, 0x00, 0xf4, 0xff, 0x68, 0x65, 0x6c, + 0x6c, 0x6f, 0x20, 0x77, 0x6f, 0x72, 0x6c, 0x64, + 0x85, 0x11, 0x4a, 0x0d, 0x0b, 0x00, 0x00, 0x00}); + EXPECT_EQ(decompress_one_shot(input), "hello world"); + EXPECT_EQ(decompress_stream(input), "hello world"); +} + +TEST(decompress_header_with_one_megabyte_fcomment) { + std::vector input{0x1f, 0x8b, 0x08, 0x10, 0x00, + 0x00, 0x00, 0x00, 0x00, 0xff}; + input.insert(input.end(), 1048576, 0x63); + input.push_back(0x00); + input.insert(input.end(), {0x01, 0x0b, 0x00, 0xf4, 0xff, 0x68, 0x65, 0x6c, + 0x6c, 0x6f, 0x20, 0x77, 0x6f, 0x72, 0x6c, 0x64, + 0x85, 0x11, 0x4a, 0x0d, 0x0b, 0x00, 0x00, 0x00}); + EXPECT_EQ(decompress_one_shot(input), "hello world"); + EXPECT_EQ(decompress_stream(input), "hello world"); +} + +TEST(decompress_header_with_fextra_spanning_stream_source_buffer) { + std::vector input{0x1f, 0x8b, 0x08, 0x04, 0x00, 0x00, + 0x00, 0x00, 0x00, 0xff, 0x20, 0x4e}; + input.insert(input.end(), 20000, 0x00); + input.insert(input.end(), {0x01, 0x0b, 0x00, 0xf4, 0xff, 0x68, 0x65, 0x6c, + 0x6c, 0x6f, 0x20, 0x77, 0x6f, 0x72, 0x6c, 0x64, + 0x85, 0x11, 0x4a, 0x0d, 0x0b, 0x00, 0x00, 0x00}); + EXPECT_EQ(decompress_one_shot(input), "hello world"); + EXPECT_EQ(decompress_stream(input), "hello world"); +} + +TEST(decompress_stored_block_of_maximum_length) { + std::vector input{0x1f, 0x8b, 0x08, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0xff, + 0x01, 0xff, 0xff, 0x00, 0x00}; + std::string expected; + for (std::size_t index = 0; index < 65535; ++index) { + const auto byte{static_cast((index ^ (index >> 8)) & 0xff)}; + input.push_back(byte); + expected.push_back(static_cast(byte)); + } + + input.insert(input.end(), {0xad, 0x58, 0x8d, 0x46, 0xff, 0xff, 0x00, 0x00}); + EXPECT_EQ(decompress_one_shot(input), expected); + EXPECT_EQ(decompress_stream(input), expected); +} + +TEST(decompress_consecutive_stored_blocks_of_maximum_length) { + std::vector input{0x1f, 0x8b, 0x08, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0xff, + 0x00, 0xff, 0xff, 0x00, 0x00}; + std::string expected; + for (std::size_t index = 0; index < 65535; ++index) { + const auto byte{static_cast((index ^ (index >> 8)) & 0xff)}; + input.push_back(byte); + expected.push_back(static_cast(byte)); + } + + input.insert(input.end(), {0x01, 0xff, 0xff, 0x00, 0x00}); + for (std::size_t index = 65535; index < 131070; ++index) { + const auto byte{static_cast((index ^ (index >> 8)) & 0xff)}; + input.push_back(byte); + expected.push_back(static_cast(byte)); + } + + input.insert(input.end(), {0x55, 0x65, 0xe3, 0x34, 0xfe, 0xff, 0x01, 0x00}); + EXPECT_EQ(decompress_one_shot(input), expected); + EXPECT_EQ(decompress_stream(input), expected); +} + +TEST(decompress_many_empty_stored_blocks_before_data) { + std::vector input{0x1f, 0x8b, 0x08, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0xff}; + for (std::size_t index = 0; index < 10000; ++index) { + input.insert(input.end(), {0x00, 0x00, 0x00, 0xff, 0xff}); + } + + input.insert(input.end(), + {0x01, 0x04, 0x00, 0xfb, 0xff, 0x64, 0x61, 0x74, 0x61, 0x63, + 0xf3, 0xf3, 0xad, 0x04, 0x00, 0x00, 0x00}); + EXPECT_EQ(decompress_one_shot(input), "data"); + EXPECT_EQ(decompress_stream(input), "data"); +} + +TEST(decompress_fixed_block_with_boundary_distances) { + std::vector input{0x1f, 0x8b, 0x08, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0xff, + 0x00, 0x00, 0x80, 0xff, 0x7f}; + std::string expected; + for (std::size_t index = 0; index < 32768; ++index) { + const auto byte{static_cast((index ^ (index >> 8)) & 0xff)}; + input.push_back(byte); + expected.push_back(static_cast(byte)); + } + + input.insert(input.end(), + {0x03, 0x02, 0x20, 0x06, 0x12, 0x40, 0x12, 0xc8, 0x06, 0xda, + 0x0f, 0x74, 0x00, 0x30, 0xff, 0x03, 0x0b, 0x00, 0xe0, 0xfa, + 0x7f, 0xe0, 0x06, 0x00, 0xe0, 0x05, 0x00, 0xc0, 0xfb, 0xff, + 0x01, 0x5a, 0xcb, 0xeb, 0x79, 0x27, 0x80, 0x00, 0x00}); + expected.append(std::string{"\x80\x80\x80\x80\x80\x80\x80\x80\x80\x80\x80" + "\x80\x80\x80\x80\xf0\xef\xee\xee\xed\xec\x69" + "\x6a\x6b\x6b\x64\x65\x4b\x4c\x4d\x4d\x4e\x4f" + "\x00\x01\x02\x24\x25\x26", + 39}); + EXPECT_EQ(decompress_one_shot(input), expected); + EXPECT_EQ(decompress_stream(input), expected); +} + +TEST(decompress_fixed_block_maximum_length_match_at_maximum_distance) { + std::vector input{0x1f, 0x8b, 0x08, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0xff, + 0x00, 0x00, 0x80, 0xff, 0x7f}; + std::string expected; + for (std::size_t index = 0; index < 32768; ++index) { + const auto byte{static_cast((index ^ (index >> 8)) & 0xff)}; + input.push_back(byte); + expected.push_back(static_cast(byte)); + } + + for (std::size_t index = 0; index < 258; ++index) { + expected.push_back(static_cast((index ^ (index >> 8)) & 0xff)); + } + + input.insert(input.end(), {0x1b, 0xbd, 0xff, 0x1f, 0x00, 0xc8, 0x0f, 0x86, + 0x20, 0x02, 0x81, 0x00, 0x00}); + EXPECT_EQ(decompress_one_shot(input), expected); + EXPECT_EQ(decompress_stream(input), expected); +} + +TEST(decompress_many_single_literal_fixed_blocks) { + std::vector input{0x1f, 0x8b, 0x08, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0xff}; + for (std::size_t index = 0; index < 2500; ++index) { + input.insert(input.end(), + {0xaa, 0x00, 0xa8, 0x02, 0xa0, 0x0a, 0x80, 0x2a, 0x00}); + } + + input.insert(input.end(), + {0x03, 0x00, 0xa3, 0xa4, 0x55, 0x0d, 0x10, 0x27, 0x00, 0x00}); + EXPECT_EQ(decompress_one_shot(input), std::string(10000, 'x')); + EXPECT_EQ(decompress_stream(input), std::string(10000, 'x')); +} + +TEST(decompress_single_fixed_block_with_maximum_compression_ratio) { + std::vector input{0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0xff, 0x4b, 0x1c, 0x05}; + for (std::size_t index = 0; index < 8192; ++index) { + input.insert(input.end(), {0xa3, 0x60, 0x14, 0x8c, 0x82, 0x51, 0x30, 0x0a, + 0x46, 0xc1, 0x28, 0x18, 0x05}); + } + + input.insert(input.end(), + {0x00, 0x3f, 0x79, 0xeb, 0xad, 0x03, 0x01, 0x02, 0x01}); + std::string expected; + expected.append(16908547, 'a'); + EXPECT_EQ(decompress_one_shot(input), expected); + EXPECT_EQ(decompress_stream(input), expected); +} + +TEST(decompress_many_empty_members_before_data) { + std::vector input; + for (std::size_t index = 0; index < 1000; ++index) { + input.insert(input.end(), {0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0xff, 0x01, 0x00, 0x00, 0xff, 0xff, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}); + } + + input.insert(input.end(), {0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0xff, 0x01, 0x01, 0x00, 0xfe, 0xff, 0x61, + 0x43, 0xbe, 0xb7, 0xe8, 0x01, 0x00, 0x00, 0x00}); + EXPECT_EQ(decompress_one_shot(input), "a"); + EXPECT_EQ(decompress_stream(input), "a"); +} + +TEST(decompress_ten_thousand_single_byte_members) { + std::vector input; + for (std::size_t index = 0; index < 10000; ++index) { + input.insert(input.end(), {0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0xff, 0x01, 0x01, 0x00, 0xfe, 0xff, 0x61, + 0x43, 0xbe, 0xb7, 0xe8, 0x01, 0x00, 0x00, 0x00}); + } + + EXPECT_EQ(decompress_one_shot(input, 1), std::string(10000, 'a')); + EXPECT_EQ(decompress_stream(input), std::string(10000, 'a')); +} + +TEST(decompress_ignores_trailing_zero_bytes) { + std::vector input{ + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x01, 0x0b, + 0x00, 0xf4, 0xff, 0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x20, 0x77, 0x6f, 0x72, + 0x6c, 0x64, 0x85, 0x11, 0x4a, 0x0d, 0x0b, 0x00, 0x00, 0x00}; + input.insert(input.end(), 1024, 0x00); + EXPECT_EQ(decompress_one_shot(input), "hello world"); + EXPECT_EQ(decompress_stream(input), "hello world"); +} + +TEST(decompress_output_exactly_at_maximum_size) { + const std::vector input{ + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x01, 0x0b, + 0x00, 0xf4, 0xff, 0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x20, 0x77, 0x6f, 0x72, + 0x6c, 0x64, 0x85, 0x11, 0x4a, 0x0d, 0x0b, 0x00, 0x00, 0x00}; + EXPECT_EQ(decompress_one_shot(input, 0, 11), "hello world"); + EXPECT_EQ(decompress_stream(input), "hello world"); +} + +TEST(decompress_empty_payload_with_zero_maximum_size) { + const std::vector input{ + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x01, 0x00, + 0x00, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; + EXPECT_EQ(decompress_one_shot(input, 0, 0), ""); + EXPECT_EQ(decompress_stream(input), ""); +} + +TEST(decompress_output_hint_larger_than_output) { + const std::vector input{ + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x01, 0x0b, + 0x00, 0xf4, 0xff, 0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x20, 0x77, 0x6f, 0x72, + 0x6c, 0x64, 0x85, 0x11, 0x4a, 0x0d, 0x0b, 0x00, 0x00, 0x00}; + EXPECT_EQ(decompress_one_shot(input, 1048576), "hello world"); + EXPECT_EQ(decompress_stream(input), "hello world"); +} + +TEST(decompress_output_hint_above_maximum_size_with_fitting_output) { + const std::vector input{ + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x01, 0x0b, + 0x00, 0xf4, 0xff, 0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x20, 0x77, 0x6f, 0x72, + 0x6c, 0x64, 0x85, 0x11, 0x4a, 0x0d, 0x0b, 0x00, 0x00, 0x00}; + EXPECT_EQ(decompress_one_shot(input, 1048576, 11), "hello world"); + EXPECT_EQ(decompress_stream(input), "hello world"); +} + +TEST(decompress_output_hint_of_one_grows_to_larger_output) { + const std::vector input{ + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x4b, 0x1c, + 0x05, 0xa3, 0x60, 0x14, 0x8c, 0x82, 0x51, 0x30, 0x0a, 0x46, 0xc1, 0x28, + 0x18, 0x05, 0xa3, 0x60, 0x14, 0x8c, 0x82, 0x51, 0x30, 0x0a, 0x46, 0xc1, + 0x28, 0x18, 0x05, 0x00, 0xa1, 0x87, 0xcc, 0x71, 0x23, 0x11, 0x00, 0x00}; + EXPECT_EQ(decompress_one_shot(input, 1), std::string(4387, 'a')); + EXPECT_EQ(decompress_stream(input), std::string(4387, 'a')); +} + +TEST(decompress_with_largest_possible_maximum_size) { + const std::vector input{ + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x01, 0x0b, + 0x00, 0xf4, 0xff, 0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x20, 0x77, 0x6f, 0x72, + 0x6c, 0x64, 0x85, 0x11, 0x4a, 0x0d, 0x0b, 0x00, 0x00, 0x00}; + EXPECT_EQ( + decompress_one_shot(input, 1, std::numeric_limits::max()), + "hello world"); + EXPECT_EQ(decompress_stream(input), "hello world"); +} + +TEST(decompress_input_larger_than_quarter_of_maximum_size) { + const std::vector input{ + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x01, 0x0b, + 0x00, 0xf4, 0xff, 0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x20, 0x77, 0x6f, 0x72, + 0x6c, 0x64, 0x85, 0x11, 0x4a, 0x0d, 0x0b, 0x00, 0x00, 0x00}; + EXPECT_EQ(decompress_one_shot(input, 0, 40), "hello world"); + EXPECT_EQ(decompress_stream(input), "hello world"); +} + +TEST(decompress_growth_clamps_to_uneven_maximum_size) { + const std::vector input{ + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x01, 0x0b, + 0x00, 0xf4, 0xff, 0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x20, 0x77, 0x6f, 0x72, + 0x6c, 0x64, 0x85, 0x11, 0x4a, 0x0d, 0x0b, 0x00, 0x00, 0x00}; + EXPECT_EQ(decompress_one_shot(input, 3, 11), "hello world"); + EXPECT_EQ(decompress_stream(input), "hello world"); +} + +TEST(decompress_crafted_bomb_at_maximum_size) { + const std::vector input{ + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x4b, 0x1c, + 0x05, 0xa3, 0x60, 0x14, 0x8c, 0x82, 0x51, 0x30, 0x0a, 0x46, 0xc1, 0x28, + 0x18, 0x05, 0xa3, 0x60, 0x14, 0x8c, 0x82, 0x51, 0x30, 0x0a, 0x46, 0xc1, + 0x28, 0x18, 0x05, 0x00, 0xa1, 0x87, 0xcc, 0x71, 0x23, 0x11, 0x00, 0x00}; + EXPECT_EQ(decompress_one_shot(input, 0, 4387), std::string(4387, 'a')); + EXPECT_EQ(decompress_stream(input), std::string(4387, 'a')); +} + +TEST(decompress_members_totaling_exactly_maximum_size) { + const std::vector input{ + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x01, 0x0b, + 0x00, 0xf4, 0xff, 0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x20, 0x77, 0x6f, 0x72, + 0x6c, 0x64, 0x85, 0x11, 0x4a, 0x0d, 0x0b, 0x00, 0x00, 0x00, 0x1f, 0x8b, + 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x01, 0x0b, 0x00, 0xf4, + 0xff, 0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x20, 0x77, 0x6f, 0x72, 0x6c, 0x64, + 0x85, 0x11, 0x4a, 0x0d, 0x0b, 0x00, 0x00, 0x00}; + EXPECT_EQ(decompress_one_shot(input, 0, 22), "hello worldhello world"); + EXPECT_EQ(decompress_stream(input), "hello worldhello world"); +} + +TEST(decompress_members_growing_from_tiny_output_hint) { + const std::vector input{ + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0xff, 0x01, 0x05, + 0x00, 0xfa, 0xff, 0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x86, 0xa6, 0x10, 0x36, + 0x05, 0x00, 0x00, 0x00, 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x04, 0xff, 0x01, 0x06, 0x00, 0xf9, 0xff, 0x20, 0x77, 0x6f, 0x72, 0x6c, + 0x64, 0xcb, 0x42, 0x3b, 0x4a, 0x06, 0x00, 0x00, 0x00}; + EXPECT_EQ(decompress_one_shot(input, 1, 1024), "hello world"); + EXPECT_EQ(decompress_stream(input), "hello world"); +} + +TEST(decompress_large_member_after_small_member) { + // NOLINTNEXTLINE(cert-msc32-c,cert-msc51-cpp,bugprone-random-generator-seed) + std::mt19937 generator{20}; + std::uniform_int_distribution distribution{0, 255}; + std::string large; + for (std::size_t index = 0; index < 1048576; ++index) { + large.push_back(static_cast(distribution(generator))); + } + + auto input{compress("x", 1)}; + const auto large_compressed{compress(large, 6)}; + input.insert(input.end(), large_compressed.cbegin(), large_compressed.cend()); + EXPECT_EQ(decompress_one_shot(input, 1), "x" + large); + EXPECT_EQ(decompress_stream(input), "x" + large); }