blob: ed75d25bb6b5c04d89fa670c40e9b98d01f666d5 [file] [log] [blame]
/*
* Copyright 2016 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// Author: jcrowell@google.com (Jeffrey Crowell)
#include "pagespeed/kernel/util/brotli_inflater.h"
#include "pagespeed/kernel/base/gtest.h"
#include "pagespeed/kernel/base/message_handler.h"
#include "pagespeed/kernel/base/message_handler_test_base.h"
#include "pagespeed/kernel/base/null_mutex.h"
#include "pagespeed/kernel/util/simple_random.h"
#include "pagespeed/kernel/base/stack_buffer.h"
#include "pagespeed/kernel/base/string.h"
#include "pagespeed/kernel/base/string_util.h"
#include "pagespeed/kernel/base/string_writer.h"
namespace net_instaweb {
namespace {
// Generated with command line tool "bro".
static const char kHello[] = "hello\n";
static const char kHelloBrotli[] = "\x8b\x02\x80\x68\x65\x6c\x6c\x6f\x0a\x03";
// Use the highest quality by default (11).
static const int kCompressionLevel = 11;
// Writer that returns false on Write().
class StringWriterReturningFalse : public StringWriter {
public:
explicit StringWriterReturningFalse(GoogleString* str) : StringWriter(str) {}
bool Write(const StringPiece& str,
net_instaweb::MessageHandler* message_handler) {
StringWriter::Write(str, message_handler);
return false;
}
};
TEST(BrotliInflater, TestBrotliDecompress) {
// Compress and decompress a simple string.
GoogleString decompressed;
TestMessageHandler handler;
StringWriter decompress_writer(&decompressed);
StringPiece compressed(kHelloBrotli, sizeof(kHelloBrotli));
EXPECT_TRUE(
BrotliInflater::Decompress(compressed, &handler, &decompress_writer));
EXPECT_STREQ(kHello, decompressed);
EXPECT_EQ(0, handler.messages().size());
}
TEST(BrotliInflater, TestFailedWriteBrotliDecompress) {
// Use a writer that returns failure on write.
GoogleString decompressed;
TestMessageHandler handler;
StringWriterReturningFalse decompress_writer(&decompressed);
StringPiece compressed(kHelloBrotli, sizeof(kHelloBrotli));
EXPECT_FALSE(
BrotliInflater::Decompress(compressed, &handler, &decompress_writer));
EXPECT_EQ(0, handler.messages().size());
}
TEST(BrotliInflater, TestCorruptInputBrotliDecompress) {
// Take "hello\n" but replace the first 2 bytes with "AB", so it will not be
// valid brotli.
const char kHelloBrotliCorrupt[] = "AB\x80\x68\x65\x6c\x6c\x6f\x0a\x03";
GoogleString decompressed;
TestMessageHandler handler;
StringWriterReturningFalse decompress_writer(&decompressed);
StringPiece compressed(kHelloBrotliCorrupt, sizeof(kHelloBrotliCorrupt));
EXPECT_FALSE(
BrotliInflater::Decompress(compressed, &handler, &decompress_writer));
ASSERT_GE(handler.messages().size(), 1);
const GoogleString& message = handler.messages()[0];
EXPECT_TRUE(message.find("PADDING_1") != GoogleString::npos ||
message == "Error: BROTLI_DECODER_RESULT_ERROR")
<< message;
}
TEST(BrotliInflater, TestTruncatedInputBrotliDecompress) {
// Take "hello\n" but truncate the string, so it will not be valid brotli.
const char kHelloBrotliCorrupt[] = "\x8b\x02\x80\x68\x65\x6c\x6c\x6f";
GoogleString decompressed;
TestMessageHandler handler;
StringWriterReturningFalse decompress_writer(&decompressed);
StringPiece compressed(kHelloBrotliCorrupt, sizeof(kHelloBrotliCorrupt));
EXPECT_FALSE(
BrotliInflater::Decompress(compressed, &handler, &decompress_writer));
EXPECT_STREQ("Warning: BROTLI_DECODER_RESULT_NEEDS_MORE_INPUT",
handler.messages()[0]);
}
TEST(BrotliInflater, TestCompressDecompressSmallString) {
// Compress and decompress a simple string, ensure that it remains unchanged.
StringPiece payload(kHello);
TestMessageHandler handler;
GoogleString compressed, decompressed;
StringWriter compressed_writer(&compressed);
EXPECT_TRUE(BrotliInflater::Compress(payload, kCompressionLevel, &handler,
&compressed_writer));
StringWriter decompressed_writer(&decompressed);
EXPECT_TRUE(
BrotliInflater::Decompress(compressed, &handler, &decompressed_writer));
EXPECT_STREQ(payload, decompressed);
EXPECT_EQ(0, handler.messages().size());
}
TEST(BrotliInflater, TestCompressDecompressLargeString) {
// Compress and decompress a long string of repeated characters that will
// exceed the buffer size.
TestMessageHandler handler;
GoogleString value(5 * kStackBufferSize, 'A');
StringPiece payload(value);
GoogleString compressed, decompressed;
StringWriter compressed_writer(&compressed);
EXPECT_TRUE(BrotliInflater::Compress(payload, kCompressionLevel, &handler,
&compressed_writer));
StringWriter decompressed_writer(&decompressed);
EXPECT_TRUE(
BrotliInflater::Decompress(compressed, &handler, &decompressed_writer));
EXPECT_STREQ(payload, decompressed);
EXPECT_EQ(0, handler.messages().size());
}
TEST(BrotliInflater, TestCompressDecompressLargeStringWithPoorCompression) {
// Compress and decompress a long string of random characters.
TestMessageHandler handler;
SimpleRandom random(new NullMutex);
GoogleString value = random.GenerateHighEntropyString(5 * kStackBufferSize);
StringPiece payload(value);
GoogleString compressed, decompressed;
StringWriter compressed_writer(&compressed);
EXPECT_TRUE(BrotliInflater::Compress(payload, kCompressionLevel, &handler,
&compressed_writer));
StringWriter decompressed_writer(&decompressed);
EXPECT_TRUE(
BrotliInflater::Decompress(compressed, &handler, &decompressed_writer));
EXPECT_STREQ(payload, decompressed);
EXPECT_EQ(0, handler.messages().size());
}
} // namespace
} // namespace net_instaweb