diff --git a/CMakeLists.txt b/CMakeLists.txt index 721328ee8..c3f6ec8a9 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -260,6 +260,13 @@ if (NOT BROTLI_DISABLE_TESTS AND BROTLI_BUILD_TOOLS) include(CTest) enable_testing() + add_executable(brotli_base64_test tests/base64_test.c) + target_link_libraries(brotli_base64_test brotlienc brotlidec) + add_test(NAME "${BROTLI_TEST_PREFIX}base64" + COMMAND ${BROTLI_WRAPPER} $) + set_tests_properties("${BROTLI_TEST_PREFIX}base64" PROPERTIES + ENVIRONMENT "QEMU_LD_PREFIX=${BROTLI_WRAPPER_LD_PREFIX}") + set(ROUNDTRIP_INPUTS tests/testdata/alice29.txt tests/testdata/asyoulik.txt diff --git a/c/enc/metablock.c b/c/enc/metablock.c index 3f64497ed..e767077ab 100644 --- a/c/enc/metablock.c +++ b/c/enc/metablock.c @@ -332,7 +332,8 @@ void BrotliBuildMetaBlock(MemoryManager* m, const uint8_t* ringbuffer, if (BROTLI_IS_OOM(m) || BROTLI_IS_NULL(mb->literal_histograms)) return; BrotliClusterHistogramsLiteral(m, literal_histograms, literal_histograms_size, - kMaxNumberOfHistograms, mb->literal_histograms, + kMaxNumberOfHistograms - (base64_applied ? 1 : 0), + mb->literal_histograms, &mb->literal_histograms_size, mb->literal_context_map); if (BROTLI_IS_OOM(m)) return; BROTLI_FREE(m, literal_histograms); @@ -722,7 +723,10 @@ static BROTLI_INLINE void BrotliBuildMetaBlockGreedyInternal( if (BROTLI_IS_OOM(m)) return; } - if (num_base64_regions > 0 && mb->literal_split.num_types < 256) { + if (num_base64_regions > 0 && + mb->literal_split.num_types < BROTLI_MAX_NUMBER_OF_BLOCK_TYPES && + mb->literal_histograms_size <= + BROTLI_MAX_NUMBER_OF_BLOCK_TYPES - num_contexts) { ForceBase64LiteralSplits(m, &mb->literal_split, base64_regions, num_base64_regions, pos, mb); if (BROTLI_IS_OOM(m)) return; diff --git a/tests/base64_test.c b/tests/base64_test.c new file mode 100644 index 000000000..69b5f6b88 --- /dev/null +++ b/tests/base64_test.c @@ -0,0 +1,116 @@ +/* Copyright 2026 Google Inc. All Rights Reserved. + + Distributed under MIT license. + See file LICENSE for detail or copy at https://opensource.org/licenses/MIT +*/ + +#include +#include + +#include +#include +#include + +static size_t BuildInput(uint8_t** output) { + static const char kTrigger[] = ";base64,"; + static const char kAlphabet[] = + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + const size_t num_chunks = 1500; + const size_t chunk_size = 700; + const size_t base64_run = 512; + const size_t trigger_len = sizeof(kTrigger) - 1; + const size_t total = trigger_len + base64_run + num_chunks * chunk_size; + uint8_t* buffer = (uint8_t*)malloc(total); + size_t position = 0; + uint32_t random = 12345; + size_t i; + size_t chunk; + + if (buffer == NULL) return 0; + memcpy(buffer + position, kTrigger, trigger_len); + position += trigger_len; + for (i = 0; i < base64_run; ++i) { + random = random * 1103515245u + 12345u; + buffer[position++] = (uint8_t)kAlphabet[(random >> 16) % 64]; + } + + for (chunk = 0; chunk < num_chunks; ++chunk) { + int bias = (int)(chunk % 8) * 8; + for (i = 0; i < chunk_size; ++i) { + int low6; + random = random * 1103515245u + 12345u; + low6 = bias + (int)((random >> 16) % 8); + if ((i & 1) == 0) { + buffer[position++] = (uint8_t)(0xC0 | (low6 & 0x3F)); + } else { + buffer[position++] = (uint8_t)(0x80 | (low6 & 0x3F)); + } + } + } + + *output = buffer; + return position; +} + +static BROTLI_BOOL Compress(const uint8_t* input, size_t input_size, + uint8_t** output, size_t* output_size) { + size_t output_capacity = input_size + input_size / 2 + 1024; + BrotliEncoderState* state = + BrotliEncoderCreateInstance(NULL, NULL, NULL); + const uint8_t* next_input = input; + size_t available_input = input_size; + uint8_t* next_output; + size_t available_output; + BROTLI_BOOL result; + + *output = (uint8_t*)malloc(output_capacity); + if (state == NULL || *output == NULL) { + BrotliEncoderDestroyInstance(state); + return BROTLI_FALSE; + } + next_output = *output; + available_output = output_capacity; + if (!BrotliEncoderSetParameter(state, BROTLI_PARAM_QUALITY, 9) || + !BrotliEncoderSetParameter(state, BROTLI_PARAM_BASE64_MODE, 1)) { + BrotliEncoderDestroyInstance(state); + return BROTLI_FALSE; + } + + result = BrotliEncoderCompressStream( + state, BROTLI_OPERATION_FINISH, &available_input, &next_input, + &available_output, &next_output, NULL); + result = TO_BROTLI_BOOL(result && available_input == 0 && + BrotliEncoderIsFinished(state)); + *output_size = output_capacity - available_output; + BrotliEncoderDestroyInstance(state); + return result; +} + +int main(void) { + uint8_t* input = NULL; + uint8_t* compressed = NULL; + uint8_t* decoded = NULL; + size_t input_size = BuildInput(&input); + size_t compressed_size = 0; + size_t decoded_size = input_size; + BROTLI_BOOL ok = BROTLI_FALSE; + + if (input_size == 0 || + !Compress(input, input_size, &compressed, &compressed_size)) { + goto cleanup; + } + decoded = (uint8_t*)malloc(input_size); + if (decoded == NULL) goto cleanup; + if (BrotliDecoderDecompress(compressed_size, compressed, &decoded_size, + decoded) != BROTLI_DECODER_RESULT_SUCCESS) { + goto cleanup; + } + ok = TO_BROTLI_BOOL(decoded_size == input_size && + memcmp(input, decoded, input_size) == 0); + +cleanup: + free(input); + free(compressed); + free(decoded); + return ok ? 0 : 1; +}