blob: 801148bbba2bf84fed79f6a071acffa9ba16aa19 [file] [log] [blame]
// Copyright 2025 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "services/network/partial_decoder.h"
#include "base/functional/callback_forward.h"
#include "base/memory/scoped_refptr.h"
#include "base/memory/weak_ptr.h"
#include "base/run_loop.h"
#include "base/strings/string_view_util.h"
#include "base/test/bind.h"
#include "base/test/task_environment.h"
#include "net/base/io_buffer.h"
#include "net/base/mime_sniffer.h"
#include "net/base/net_errors.h"
#include "net/base/test_completion_callback.h"
#include "net/filter/filter_source_stream_test_util.h"
#include "services/network/pending_callback_chain.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace network {
using testing::_;
namespace {
static constexpr std::string_view kTestBody = "hello world";
class MockReader {
public:
MockReader() = default;
~MockReader() = default;
base::RepeatingCallback<int(net::IOBuffer*, int)> AsReadCallback() {
return base::BindRepeating(
[](base::WeakPtr<MockReader> self, net::IOBuffer* dest, int dest_size) {
CHECK(self);
return self->Read(dest, dest_size);
},
weak_ptr_factory_.GetWeakPtr());
}
MOCK_METHOD(int, Read, (net::IOBuffer * dest, int dest_size));
private:
base::WeakPtrFactory<MockReader> weak_ptr_factory_{this};
};
void CheckResult(std::unique_ptr<PartialDecoder> partial_decoder,
std::string_view expected_decoded_data,
base::span<const uint8_t> expected_compressed_data,
std::optional<net::Error> expected_completion_status) {
EXPECT_THAT(base::as_string_view(partial_decoder->decoded_data()),
expected_decoded_data);
auto result = std::move(*partial_decoder).TakeResult();
EXPECT_EQ(result.HasRawData(), !expected_decoded_data.empty());
std::vector<uint8_t> raw_bytes_out(expected_compressed_data.size());
EXPECT_EQ(result.ConsumeRawData(raw_bytes_out),
expected_compressed_data.size());
EXPECT_THAT(raw_bytes_out, expected_compressed_data);
EXPECT_THAT(result.completion_status(), expected_completion_status);
}
void CallOnReadRawDataCompletedAsync(
std::unique_ptr<PartialDecoder>& partial_decoder,
int result) {
base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
FROM_HERE, base::BindOnce(
[](PartialDecoder* partial_decoder, int result) {
partial_decoder->OnReadRawDataCompleted(result);
},
base::Unretained(partial_decoder.get()), result));
}
} // namespace
// Test fixture for PartialDecoder tests.
class PartialDecoderTest : public testing::Test {
public:
PartialDecoderTest() = default;
~PartialDecoderTest() override = default;
protected:
// Provides a task environment for the tests.
base::test::TaskEnvironment task_environment_;
};
TEST_F(PartialDecoderTest, GzipSimpleSyncRead) {
const auto compressed = net::CompressGzip(kTestBody);
MockReader reader;
EXPECT_CALL(reader, Read(_, _))
.WillOnce([&](net::IOBuffer* dest, int dest_size) {
CHECK_GE(dest_size, base::checked_cast<int>(compressed.size()));
dest->first(compressed.size()).copy_from(compressed);
return compressed.size();
});
auto partial_decoder = std::make_unique<PartialDecoder>(
reader.AsReadCallback(),
std::vector<net::SourceStreamType>({net::SourceStreamType::kGzip}),
net::kMaxBytesToSniff);
EXPECT_THAT(partial_decoder->ReadDecodedDataMore(
base::BindLambdaForTesting([](int) { FAIL(); })),
kTestBody.size());
EXPECT_FALSE(partial_decoder->read_in_progress());
CheckResult(std::move(partial_decoder), kTestBody, base::span(compressed),
/*expected_completion_status=*/std::nullopt);
}
TEST_F(PartialDecoderTest, GzipSimpleAsyncRead) {
const auto compressed = net::CompressGzip(kTestBody);
MockReader reader;
std::unique_ptr<PartialDecoder> partial_decoder;
EXPECT_CALL(reader, Read(_, _))
.WillOnce([&](net::IOBuffer* dest, int dest_size) {
CHECK_GE(dest_size, base::checked_cast<int>(compressed.size()));
dest->first(compressed.size()).copy_from(compressed);
CallOnReadRawDataCompletedAsync(partial_decoder, compressed.size());
return net::ERR_IO_PENDING;
});
partial_decoder = std::make_unique<PartialDecoder>(
reader.AsReadCallback(),
std::vector<net::SourceStreamType>({net::SourceStreamType::kGzip}),
net::kMaxBytesToSniff);
net::TestCompletionCallback callback;
EXPECT_EQ(partial_decoder->ReadDecodedDataMore(callback.callback()),
net::ERR_IO_PENDING);
EXPECT_TRUE(partial_decoder->read_in_progress());
EXPECT_THAT(callback.WaitForResult(), kTestBody.size());
CheckResult(std::move(partial_decoder), kTestBody, base::span(compressed),
/*expected_completion_status=*/std::nullopt);
}
TEST_F(PartialDecoderTest, GzipSyncTwoReads) {
const auto compressed = net::CompressGzip(kTestBody);
auto [first_chunk, second_chunk] =
base::span(compressed).split_at(compressed.size() / 2);
MockReader reader;
EXPECT_CALL(reader, Read(_, _))
.WillOnce([&](net::IOBuffer* dest, int dest_size) {
CHECK_GE(dest_size, base::checked_cast<int>(first_chunk.size()));
dest->first(first_chunk.size()).copy_from(first_chunk);
return first_chunk.size();
})
.WillOnce([&](net::IOBuffer* dest, int dest_size) {
CHECK_GE(dest_size, base::checked_cast<int>(first_chunk.size()));
dest->first(second_chunk.size()).copy_from(second_chunk);
return second_chunk.size();
});
auto partial_decoder = std::make_unique<PartialDecoder>(
reader.AsReadCallback(),
std::vector<net::SourceStreamType>({net::SourceStreamType::kGzip}),
net::kMaxBytesToSniff);
EXPECT_THAT(partial_decoder->ReadDecodedDataMore(
base::BindLambdaForTesting([](int) { FAIL(); })),
kTestBody.size());
EXPECT_FALSE(partial_decoder->read_in_progress());
CheckResult(std::move(partial_decoder), kTestBody, base::span(compressed),
/*expected_completion_status=*/std::nullopt);
}
TEST_F(PartialDecoderTest, GzipAsyncTwoReads) {
const auto compressed = net::CompressGzip(kTestBody);
auto [first_chunk, second_chunk] =
base::span(compressed).split_at(compressed.size() / 2);
MockReader reader;
std::unique_ptr<PartialDecoder> partial_decoder;
EXPECT_CALL(reader, Read(_, _))
.WillOnce([&](net::IOBuffer* dest, int dest_size) {
CHECK_GE(dest_size, base::checked_cast<int>(first_chunk.size()));
dest->first(first_chunk.size()).copy_from(first_chunk);
CallOnReadRawDataCompletedAsync(partial_decoder, first_chunk.size());
return net::ERR_IO_PENDING;
})
.WillOnce([&](net::IOBuffer* dest, int dest_size) {
CHECK_GE(dest_size, base::checked_cast<int>(second_chunk.size()));
dest->first(second_chunk.size()).copy_from(second_chunk);
CallOnReadRawDataCompletedAsync(partial_decoder, second_chunk.size());
return net::ERR_IO_PENDING;
});
partial_decoder = std::make_unique<PartialDecoder>(
reader.AsReadCallback(),
std::vector<net::SourceStreamType>({net::SourceStreamType::kGzip}),
net::kMaxBytesToSniff);
net::TestCompletionCallback callback;
EXPECT_EQ(partial_decoder->ReadDecodedDataMore(callback.callback()),
net::ERR_IO_PENDING);
EXPECT_TRUE(partial_decoder->read_in_progress());
EXPECT_THAT(callback.WaitForResult(), kTestBody.size());
CheckResult(std::move(partial_decoder), kTestBody, base::span(compressed),
/*expected_completion_status=*/std::nullopt);
}
TEST_F(PartialDecoderTest, SyncError) {
MockReader reader;
EXPECT_CALL(reader, Read(_, _))
.WillOnce(
[&](net::IOBuffer* dest, int dest_size) { return net::ERR_FAILED; });
auto partial_decoder = std::make_unique<PartialDecoder>(
reader.AsReadCallback(),
std::vector<net::SourceStreamType>({net::SourceStreamType::kGzip}),
net::kMaxBytesToSniff);
EXPECT_THAT(partial_decoder->ReadDecodedDataMore(
base::BindLambdaForTesting([](int) { FAIL(); })),
net::ERR_FAILED);
EXPECT_FALSE(partial_decoder->read_in_progress());
CheckResult(std::move(partial_decoder), /*expected_decoded_data=*/"",
/*expected_compressed_data=*/{}, net::ERR_FAILED);
}
TEST_F(PartialDecoderTest, AsyncError) {
MockReader reader;
std::unique_ptr<PartialDecoder> partial_decoder;
EXPECT_CALL(reader, Read(_, _))
.WillOnce([&](net::IOBuffer* dest, int dest_size) {
CallOnReadRawDataCompletedAsync(partial_decoder, net::ERR_FAILED);
return net::ERR_IO_PENDING;
});
partial_decoder = std::make_unique<PartialDecoder>(
reader.AsReadCallback(),
std::vector<net::SourceStreamType>({net::SourceStreamType::kGzip}),
net::kMaxBytesToSniff);
net::TestCompletionCallback callback;
EXPECT_EQ(partial_decoder->ReadDecodedDataMore(callback.callback()),
net::ERR_IO_PENDING);
EXPECT_TRUE(partial_decoder->read_in_progress());
EXPECT_THAT(callback.WaitForResult(), net::ERR_FAILED);
EXPECT_FALSE(partial_decoder->read_in_progress());
CheckResult(std::move(partial_decoder), /*expected_decoded_data=*/"",
/*expected_compressed_data=*/{}, net::ERR_FAILED);
}
TEST_F(PartialDecoderTest, NoDecoding) {
MockReader reader;
EXPECT_CALL(reader, Read(_, _))
.WillOnce([&](net::IOBuffer* dest, int dest_size) {
CHECK_GE(dest_size, base::checked_cast<int>(kTestBody.size()));
dest->first(kTestBody.size())
.copy_from(base::as_bytes(base::span(kTestBody)));
return kTestBody.size();
})
.WillOnce([&](net::IOBuffer* dest, int dest_size) { return net::OK; });
auto partial_decoder = std::make_unique<PartialDecoder>(
reader.AsReadCallback(),
// No decoding types specified.
std::vector<net::SourceStreamType>({}), net::kMaxBytesToSniff);
EXPECT_THAT(partial_decoder->ReadDecodedDataMore(
base::BindLambdaForTesting([](int) { FAIL(); })),
kTestBody.size());
EXPECT_FALSE(partial_decoder->read_in_progress());
// Read more.
EXPECT_THAT(partial_decoder->ReadDecodedDataMore(
base::BindLambdaForTesting([](int) { FAIL(); })),
net::OK);
CheckResult(std::move(partial_decoder), kTestBody,
base::as_bytes(base::span(kTestBody)), net::OK);
}
TEST_F(PartialDecoderTest, GzipPartialRead) {
const auto compressed = net::CompressGzip(kTestBody);
// Only read half of the compressed data.
static constexpr size_t kReadSize = kTestBody.size() / 2;
MockReader reader;
EXPECT_CALL(reader, Read(_, _))
.WillOnce([&](net::IOBuffer* dest, int dest_size) {
CHECK_GE(dest_size, base::checked_cast<int>(compressed.size()));
dest->first(compressed.size()).copy_from(compressed);
return compressed.size();
});
auto partial_decoder = std::make_unique<PartialDecoder>(
reader.AsReadCallback(),
std::vector<net::SourceStreamType>({net::SourceStreamType::kGzip}),
kReadSize);
EXPECT_THAT(partial_decoder->ReadDecodedDataMore(
base::BindLambdaForTesting([](int) { FAIL(); })),
kReadSize);
EXPECT_FALSE(partial_decoder->read_in_progress());
CheckResult(std::move(partial_decoder), kTestBody.substr(0u, kReadSize),
base::span(compressed),
/*expected_completion_status=*/std::nullopt);
}
TEST_F(PartialDecoderTest, NoDecodingPartialRead) {
// Use a small decoded buffer size to force partial reading.
const size_t kDecodedBufferSize = 5;
ASSERT_TRUE(kDecodedBufferSize < kTestBody.size())
<< "decoded buffer size should be smaller than test body size";
MockReader reader;
EXPECT_CALL(reader, Read(_, _))
.WillOnce([&](net::IOBuffer* dest, int dest_size) {
EXPECT_THAT(dest_size, kDecodedBufferSize);
dest->first(kDecodedBufferSize)
.copy_from(base::as_bytes(
base::span(kTestBody).first(kDecodedBufferSize)));
return kDecodedBufferSize;
});
auto partial_decoder = std::make_unique<PartialDecoder>(
reader.AsReadCallback(),
// No decoding types specified.
std::vector<net::SourceStreamType>({}), kDecodedBufferSize);
partial_decoder = std::make_unique<PartialDecoder>(
reader.AsReadCallback(), std::vector<net::SourceStreamType>({}),
kDecodedBufferSize);
EXPECT_THAT(partial_decoder->ReadDecodedDataMore(
base::BindLambdaForTesting([](int) { FAIL(); })),
kDecodedBufferSize);
EXPECT_FALSE(partial_decoder->read_in_progress());
CheckResult(std::move(partial_decoder),
kTestBody.substr(0u, kDecodedBufferSize),
base::as_bytes(base::span(kTestBody)).first(kDecodedBufferSize),
/*expected_completion_status=*/std::nullopt);
}
TEST_F(PartialDecoderTest, ConsumeRawDataInChunks) {
const auto compressed = net::CompressGzip(kTestBody);
auto [first_chunk, second_chunk] =
base::span(compressed).split_at(compressed.size() / 2);
MockReader reader;
EXPECT_CALL(reader, Read(_, _))
.WillOnce([&](net::IOBuffer* dest, int dest_size) {
CHECK_GE(dest_size, base::checked_cast<int>(first_chunk.size()));
dest->first(first_chunk.size()).copy_from(first_chunk);
return first_chunk.size();
})
.WillOnce([&](net::IOBuffer* dest, int dest_size) {
CHECK_GE(dest_size, base::checked_cast<int>(first_chunk.size()));
dest->first(second_chunk.size()).copy_from(second_chunk);
return second_chunk.size();
});
auto partial_decoder = std::make_unique<PartialDecoder>(
reader.AsReadCallback(),
std::vector<net::SourceStreamType>({net::SourceStreamType::kGzip}),
net::kMaxBytesToSniff);
EXPECT_THAT(partial_decoder->ReadDecodedDataMore(
base::BindLambdaForTesting([](int) { FAIL(); })),
kTestBody.size());
auto raw_data = std::move(*partial_decoder).TakeResult();
EXPECT_TRUE(raw_data.HasRawData());
// Consume raw data in chunks.
const int kChunkSize = 4;
std::vector<uint8_t> raw_bytes_out;
size_t total_consumed = 0;
while (total_consumed < compressed.size()) {
std::vector<uint8_t> chunk(kChunkSize);
size_t consumed = raw_data.ConsumeRawData(chunk);
EXPECT_GT(consumed, 0u);
raw_bytes_out.insert(raw_bytes_out.end(), chunk.begin(),
chunk.begin() + consumed);
total_consumed += consumed;
}
EXPECT_EQ(total_consumed, compressed.size());
EXPECT_THAT(raw_bytes_out, compressed);
// Verify that there is no more raw data.
EXPECT_FALSE(raw_data.HasRawData());
std::vector<uint8_t> chunk(kChunkSize);
EXPECT_EQ(raw_data.ConsumeRawData(chunk), 0u);
}
} // namespace network