blob: 1ac3f074cfe7ebbc5f3f4ab0ff7a2bfe0d5b2695 [file] [log] [blame]
// Copyright 2019 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.
//
///////////////////////////////////////////////////////////////////////////////
#include "tink/streamingaead/buffered_input_stream.h"
#include <sstream>
#include "gmock/gmock.h"
#include "gtest/gtest.h"
#include "absl/memory/memory.h"
#include "absl/strings/str_cat.h"
#include "absl/strings/string_view.h"
#include "tink/input_stream.h"
#include "tink/subtle/random.h"
#include "tink/subtle/test_util.h"
#include "tink/util/istream_input_stream.h"
#include "tink/util/status.h"
#include "tink/util/test_matchers.h"
namespace crypto {
namespace tink {
namespace streamingaead {
namespace {
using crypto::tink::test::IsOk;
using crypto::tink::test::StatusIs;
using subtle::test::ReadFromStream;
using testing::HasSubstr;
static int kBufferSize = 4096;
// Creates an InputStream with the specified contents.
std::unique_ptr<InputStream> GetInputStream(absl::string_view contents) {
// Prepare ciphertext source stream.
auto string_stream = absl::make_unique<std::stringstream>(std::string(contents));
std::unique_ptr<InputStream> input_stream(
absl::make_unique<util::IstreamInputStream>(
std::move(string_stream), kBufferSize));
return input_stream;
}
// Attempts to read 'count' bytes from 'input_stream', and writes the read
// bytes to 'output'.
util::Status ReadFromStream(
InputStream* input_stream, int count, std::string* output) {
if (input_stream == nullptr || output == nullptr || count < 0) {
return util::Status(util::error::INTERNAL, "Illegal read from a stream");
}
const void* buffer;
output->clear();
int bytes_to_read = count;
while (bytes_to_read > 0) {
auto next_result = input_stream->Next(&buffer);
if (next_result.status().error_code() == util::error::OUT_OF_RANGE) {
// End of stream.
return util::Status::OK;
}
if (!next_result.ok()) return next_result.status();
auto read_bytes = next_result.ValueOrDie();
auto used_bytes = std::min(read_bytes, bytes_to_read);
if (used_bytes > 0) {
output->append(std::string(reinterpret_cast<const char*>(buffer), used_bytes));
bytes_to_read -= used_bytes;
if (bytes_to_read == 0) input_stream->BackUp(read_bytes - used_bytes);
}
}
return util::Status::OK;
}
TEST(BufferedInputStreamTest, ReadingAndRewinding) {
for (auto input_size : {0, 1, 10, 100, 1000, 10000, 100000}) {
std::string contents = subtle::Random::GetRandomBytes(input_size);
auto input_stream = GetInputStream(contents);
auto buf_stream = absl::make_unique<BufferedInputStream>(
std::move(input_stream));
for (auto read_size : {0, 1, 10, 123, 300}) {
SCOPED_TRACE(absl::StrCat("input_size = ", input_size,
", read_size = ", read_size));
// Read a prefix of the stream.
std::string prefix;
auto status = ReadFromStream(buf_stream.get(), read_size, &prefix);
EXPECT_THAT(status, IsOk());
EXPECT_EQ(std::min(read_size, input_size), buf_stream->Position());
EXPECT_EQ(contents.substr(0, read_size), prefix);
// Read the rest of the stream.
std::string rest;
status = ReadFromStream(buf_stream.get(), &rest);
EXPECT_THAT(status, IsOk());
EXPECT_EQ(input_size, buf_stream->Position());
EXPECT_EQ(contents, prefix + rest);
// Try reading again, should get an empty std::string.
status = ReadFromStream(buf_stream.get(), &rest);
EXPECT_THAT(status, IsOk());
EXPECT_EQ("", rest);
// Rewind and read again, again in two parts.
status = buf_stream->Rewind();
EXPECT_EQ(0, buf_stream->Position());
EXPECT_THAT(status, IsOk());
status = ReadFromStream(buf_stream.get(), read_size, &prefix);
EXPECT_THAT(status, IsOk());
EXPECT_EQ(std::min(read_size, input_size), buf_stream->Position());
EXPECT_EQ(contents.substr(0, read_size), prefix);
status = ReadFromStream(buf_stream.get(), &rest);
EXPECT_THAT(status, IsOk());
EXPECT_EQ(input_size, buf_stream->Position());
EXPECT_EQ(contents, prefix + rest);
// Rewind so that the next read iteration starts from the beginning.
status = buf_stream->Rewind();
EXPECT_THAT(status, IsOk());
EXPECT_EQ(0, buf_stream->Position());
}
}
}
TEST(BufferedInputStreamTest, SingleBackup) {
for (auto input_size : {0, 1, 10, 100, 1000, 10000, 100000}) {
std::string contents = subtle::Random::GetRandomBytes(input_size);
for (auto read_size : {0, 1, 10, 123, 300, 1024}) {
SCOPED_TRACE(absl::StrCat("input_size = ", input_size,
", read_size = ", read_size));
auto input_stream = GetInputStream(contents);
auto buf_stream = absl::make_unique<BufferedInputStream>(
std::move(input_stream));
// Read a part of the stream.
std::string prefix;
auto status = ReadFromStream(buf_stream.get(), read_size, &prefix);
EXPECT_THAT(status, IsOk());
EXPECT_EQ(std::min(read_size, input_size), buf_stream->Position());
EXPECT_EQ(contents.substr(0, read_size), prefix);
// Read the next block of the stream, and then back it up.
const void* buf;
int pos = buf_stream->Position();
auto next_result = buf_stream->Next(&buf);
if (read_size < input_size) {
EXPECT_THAT(next_result.status(), IsOk());
auto next_size = next_result.ValueOrDie();
EXPECT_LE(next_size, kBufferSize);
EXPECT_EQ(pos + next_size, buf_stream->Position());
buf_stream->BackUp(next_size);
EXPECT_EQ(pos, buf_stream->Position());
buf_stream->BackUp(input_size);
EXPECT_EQ(pos, buf_stream->Position());
} else {
EXPECT_EQ(util::error::OUT_OF_RANGE, next_result.status().error_code());
}
// Read the rest of the input.
std::string rest;
status = ReadFromStream(buf_stream.get(), &rest);
EXPECT_THAT(status, IsOk());
EXPECT_EQ(input_size, buf_stream->Position());
EXPECT_EQ(contents, prefix + rest);
// Rewind and read prefix again.
status = buf_stream->Rewind();
EXPECT_EQ(0, buf_stream->Position());
EXPECT_THAT(status, IsOk());
status = ReadFromStream(buf_stream.get(), read_size, &prefix);
EXPECT_THAT(status, IsOk());
EXPECT_EQ(std::min(read_size, input_size), buf_stream->Position());
EXPECT_EQ(contents.substr(0, read_size), prefix);
// The next buffer should contain the rest of the input, if any.
pos = buf_stream->Position();
next_result = buf_stream->Next(&buf);
if (read_size < input_size) {
EXPECT_THAT(next_result.status(), IsOk());
auto next_size = next_result.ValueOrDie();
EXPECT_EQ(input_size - pos, next_size);
EXPECT_EQ(input_size, buf_stream->Position());
buf_stream->BackUp(next_size);
EXPECT_EQ(pos, buf_stream->Position());
buf_stream->BackUp(input_size);
EXPECT_EQ(pos, buf_stream->Position());
} else {
EXPECT_EQ(util::error::OUT_OF_RANGE, next_result.status().error_code());
}
// Read the rest of the input.
status = ReadFromStream(buf_stream.get(), &rest);
EXPECT_THAT(status, IsOk());
EXPECT_EQ(input_size, buf_stream->Position());
EXPECT_EQ(contents, prefix + rest);
}
}
}
TEST(BufferedInputStreamTest, MultipleBackups) {
int input_size = 70000;
std::string contents = subtle::Random::GetRandomBytes(input_size);
auto input_stream = GetInputStream(contents);
auto buf_stream = absl::make_unique<BufferedInputStream>(
std::move(input_stream));
const void* buffer;
EXPECT_EQ(0, buf_stream->Position());
auto next_result = buf_stream->Next(&buffer);
EXPECT_THAT(next_result.status(), IsOk());
auto next_size = next_result.ValueOrDie();
EXPECT_EQ(contents.substr(0, next_size),
std::string(static_cast<const char*>(buffer), next_size));
// BackUp several times, but in total fewer bytes than returned by Next().
int total_backup_size = 0;
for (auto backup_size : {0, 1, 5, 0, 10, 100, -42, 400, 20, -100}) {
buf_stream->BackUp(backup_size);
total_backup_size += std::max(0, backup_size);
EXPECT_EQ(next_size - total_backup_size, buf_stream->Position());
}
EXPECT_GT(next_size, total_backup_size);
// Call Next(), it should return exactly the backed up bytes.
next_result = buf_stream->Next(&buffer);
EXPECT_THAT(next_result.status(), IsOk());
EXPECT_EQ(total_backup_size, next_result.ValueOrDie());
EXPECT_EQ(next_size, buf_stream->Position());
EXPECT_EQ(
contents.substr(next_size - total_backup_size, total_backup_size),
std::string(static_cast<const char*>(buffer), total_backup_size));
}
TEST(BufferedInputStreamTest, DisableRewindingInitially) {
for (auto input_size : {0, 10, 100, 1000, 10000}) {
std::string contents = subtle::Random::GetRandomBytes(input_size);
for (auto read_size : {0, 1, 10, 123, 300, 1024}) {
SCOPED_TRACE(absl::StrCat("input_size = ", input_size,
", read_size = ", read_size));
auto input_stream = GetInputStream(contents);
auto buf_stream = absl::make_unique<BufferedInputStream>(
std::move(input_stream));
// Disable rewinding, and attempt rewind.
EXPECT_EQ(0, buf_stream->Position());
buf_stream->DisableRewinding();
auto status = buf_stream->Rewind();
EXPECT_THAT(status, StatusIs(util::error::INVALID_ARGUMENT,
HasSubstr("rewinding is disabled")));
// Read a prefix of the stream.
std::string prefix;
status = ReadFromStream(buf_stream.get(), read_size, &prefix);
EXPECT_THAT(status, IsOk());
EXPECT_EQ(std::min(read_size, input_size), buf_stream->Position());
EXPECT_EQ(contents.substr(0, read_size), prefix);
// Attempt rewidning again.
status = buf_stream->Rewind();
EXPECT_THAT(status, StatusIs(util::error::INVALID_ARGUMENT,
HasSubstr("rewinding is disabled")));
// Read the rest of the input.
std::string rest;
status = ReadFromStream(buf_stream.get(), &rest);
EXPECT_THAT(status, IsOk());
EXPECT_EQ(input_size, buf_stream->Position());
EXPECT_EQ(contents, prefix + rest);
}
}
}
TEST(BufferedInputStreamTest, DisableRewindingAfterRewind) {
for (auto input_size : {0, 10, 100, 1000, 10000}) {
std::string contents = subtle::Random::GetRandomBytes(input_size);
for (auto read_size : {0, 1, 10, 123, 300, 1024}) {
SCOPED_TRACE(absl::StrCat("input_size = ", input_size,
", read_size = ", read_size));
auto input_stream = GetInputStream(contents);
auto buf_stream = absl::make_unique<BufferedInputStream>(
std::move(input_stream));
// Read a prefix of the stream.
std::string prefix;
auto status = ReadFromStream(buf_stream.get(), read_size, &prefix);
EXPECT_THAT(status, IsOk());
EXPECT_EQ(std::min(read_size, input_size), buf_stream->Position());
EXPECT_EQ(contents.substr(0, read_size), prefix);
// Rewind, and disable rewinding.
status = buf_stream->Rewind();
EXPECT_THAT(status, IsOk());
EXPECT_EQ(0, buf_stream->Position());
buf_stream->DisableRewinding();
status = buf_stream->Rewind();
EXPECT_THAT(status, StatusIs(util::error::INVALID_ARGUMENT,
HasSubstr("rewinding is disabled")));
// Read the prefix again.
status = ReadFromStream(buf_stream.get(), read_size, &prefix);
EXPECT_THAT(status, IsOk());
EXPECT_EQ(std::min(read_size, input_size), buf_stream->Position());
EXPECT_EQ(contents.substr(0, read_size), prefix);
// Read the rest of the input.
std::string rest;
status = ReadFromStream(buf_stream.get(), &rest);
EXPECT_THAT(status, IsOk());
EXPECT_EQ(input_size, buf_stream->Position());
EXPECT_EQ(contents, prefix + rest);
}
}
}
} // namespace
} // namespace streamingaead
} // namespace tink
} // namespace crypto