blob: 2f7a172edcfd6a9f461ea85984e32ba0c6815ffb [file] [log] [blame]
// Copyright 2018 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/util/file_input_stream.h"
#include <fcntl.h>
#include <algorithm>
#include <cstdint>
#include <cstring>
#include <iostream>
#include <ostream>
#include <string>
#include "gmock/gmock.h"
#include "gtest/gtest.h"
#include "absl/memory/memory.h"
#include "absl/status/status.h"
#include "absl/strings/str_cat.h"
#include "absl/strings/string_view.h"
#include "tink/internal/test_file_util.h"
#include "tink/subtle/random.h"
#include "tink/util/status.h"
#include "tink/util/test_matchers.h"
#include "tink/util/test_util.h"
namespace crypto {
namespace tink {
namespace {
using ::crypto::tink::test::IsOk;
using ::crypto::tink::test::IsOkAndHolds;
using ::crypto::tink::test::StatusIs;
constexpr int kDefaultTestStreamSize = 100 * 1024; // 100 KB.
// Opens test file `filename` and returns a file descriptor to it.
util::StatusOr<int> OpenTestFileToRead(absl::string_view filename) {
std::string full_filename = absl::StrCat(test::TmpDir(), "/", filename);
int fd = open(full_filename.c_str(), O_RDONLY);
if (fd == -1) {
return util::Status(absl::StatusCode::kInternal,
absl::StrCat("Cannot open file ", full_filename,
" error: ", std::strerror(errno)));
}
return fd;
}
// Reads the specified `input_stream` until no more bytes can be read,
// and puts the read bytes into `contents`.
// Returns the status of the last input_stream->Next()-operation.
util::Status ReadAll(util::FileInputStream* input_stream,
std::string* contents) {
contents->clear();
const void* buffer;
auto next_result = input_stream->Next(&buffer);
while (next_result.ok()) {
contents->append(static_cast<const char*>(buffer), next_result.value());
next_result = input_stream->Next(&buffer);
}
return next_result.status();
}
using FileInputStreamTestDefaultBufferSize = testing::TestWithParam<int>;
TEST_P(FileInputStreamTestDefaultBufferSize, ReadAllfFromInputStreamSucceeds) {
int stream_size = GetParam();
SCOPED_TRACE(absl::StrCat("stream_size = ", stream_size));
std::string file_contents = subtle::Random::GetRandomBytes(stream_size);
std::string filename = absl::StrCat(
stream_size, "_", internal::GetTestFileNamePrefix(), "_file.bin");
ASSERT_THAT(internal::CreateTestFile(filename, file_contents), IsOk());
util::StatusOr<int> input_fd = OpenTestFileToRead(filename);
ASSERT_THAT(input_fd.status(), IsOk());
EXPECT_EQ(stream_size, file_contents.size());
auto input_stream = absl::make_unique<util::FileInputStream>(*input_fd);
std::string stream_contents;
auto status = ReadAll(input_stream.get(), &stream_contents);
EXPECT_THAT(status, StatusIs(absl::StatusCode::kOutOfRange));
EXPECT_EQ(status.message(), "EOF");
EXPECT_EQ(file_contents, stream_contents);
}
INSTANTIATE_TEST_SUITE_P(FileInputStreamTest,
FileInputStreamTestDefaultBufferSize,
testing::ValuesIn({0, 10, 100, 1000, 10000, 100000,
1000000}));
using FileInputStreamTestCustomBufferSizes = testing::TestWithParam<int>;
TEST_P(FileInputStreamTestCustomBufferSizes,
ReadAllWithCustomBufferSizeSucceeds) {
int buffer_size = GetParam();
SCOPED_TRACE(absl::StrCat("buffer_size = ", buffer_size));
std::string file_contents =
subtle::Random::GetRandomBytes(kDefaultTestStreamSize);
std::string filename = absl::StrCat(
buffer_size, "_", internal::GetTestFileNamePrefix(), "_file.bin");
ASSERT_THAT(internal::CreateTestFile(filename, file_contents), IsOk());
util::StatusOr<int> input_fd = OpenTestFileToRead(filename);
ASSERT_THAT(input_fd.status(), IsOk());
EXPECT_EQ(kDefaultTestStreamSize, file_contents.size());
auto input_stream =
absl::make_unique<util::FileInputStream>(*input_fd, buffer_size);
const void* buffer;
auto next_result = input_stream->Next(&buffer);
ASSERT_THAT(next_result, IsOk());
EXPECT_EQ(buffer_size, next_result.value());
EXPECT_EQ(file_contents.substr(0, buffer_size),
std::string(static_cast<const char*>(buffer), buffer_size));
}
INSTANTIATE_TEST_SUITE_P(FileInputStreamTest,
FileInputStreamTestCustomBufferSizes,
testing::ValuesIn({1, 10, 100, 1000, 10000}));
TEST(FileInputStreamTest, NextFailsIfFdIsInvalid) {
int buffer_size = 4 * 1024;
auto input_stream = absl::make_unique<util::FileInputStream>(-1, buffer_size);
const void* buffer = nullptr;
EXPECT_THAT(input_stream->Next(&buffer).status(),
StatusIs(absl::StatusCode::kInternal));
}
TEST(FileInputStreamTest, NextFailsIfDataIsNull) {
int buffer_size = 4 * 1024;
std::string file_contents =
subtle::Random::GetRandomBytes(kDefaultTestStreamSize);
std::string filename = absl::StrCat(
buffer_size, "_", internal::GetTestFileNamePrefix(), "_file.bin");
ASSERT_THAT(internal::CreateTestFile(filename, file_contents), IsOk());
util::StatusOr<int> input_fd = OpenTestFileToRead(filename);
ASSERT_THAT(input_fd.status(), IsOk());
EXPECT_EQ(kDefaultTestStreamSize, file_contents.size());
auto input_stream =
absl::make_unique<util::FileInputStream>(*input_fd, buffer_size);
EXPECT_THAT(input_stream->Next(nullptr).status(),
StatusIs(absl::StatusCode::kInvalidArgument));
}
TEST(FileInputStreamTest, NextReadsExactlyOneBlockOfData) {
int buffer_size = 4 * 1024;
std::string file_contents =
subtle::Random::GetRandomBytes(kDefaultTestStreamSize);
std::string filename = absl::StrCat(
buffer_size, "_", internal::GetTestFileNamePrefix(), "_file.bin");
ASSERT_THAT(internal::CreateTestFile(filename, file_contents), IsOk());
util::StatusOr<int> input_fd = OpenTestFileToRead(filename);
ASSERT_THAT(input_fd.status(), IsOk());
EXPECT_EQ(kDefaultTestStreamSize, file_contents.size());
auto input_stream =
absl::make_unique<util::FileInputStream>(*input_fd, buffer_size);
auto expected_file_content_block =
absl::string_view(file_contents).substr(0, buffer_size);
const void* buffer = nullptr;
util::StatusOr<int> next_result = input_stream->Next(&buffer);
ASSERT_THAT(next_result, IsOkAndHolds(buffer_size));
// Check that we advanced of buffer_size bytes.
EXPECT_EQ(input_stream->Position(), buffer_size);
EXPECT_EQ(absl::string_view(static_cast<const char*>(buffer), buffer_size),
expected_file_content_block);
}
TEST(FileInputStreamTest, BackupForNegativeOrZeroBytesIsANoop) {
int buffer_size = 4 * 1024;
std::string file_contents =
subtle::Random::GetRandomBytes(kDefaultTestStreamSize);
std::string filename = absl::StrCat(
buffer_size, "_", internal::GetTestFileNamePrefix(), "_file.bin");
ASSERT_THAT(internal::CreateTestFile(filename, file_contents), IsOk());
util::StatusOr<int> input_fd = OpenTestFileToRead(filename);
ASSERT_THAT(input_fd.status(), IsOk());
EXPECT_EQ(kDefaultTestStreamSize, file_contents.size());
auto input_stream =
absl::make_unique<util::FileInputStream>(*input_fd, buffer_size);
EXPECT_EQ(input_stream->Position(), 0);
auto expected_file_content_block =
absl::string_view(file_contents).substr(0, buffer_size);
const void* buffer = nullptr;
ASSERT_THAT(input_stream->Next(&buffer), IsOkAndHolds(buffer_size));
// Check that we advanced of buffer_size bytes.
EXPECT_EQ(input_stream->Position(), buffer_size);
EXPECT_EQ(absl::string_view(static_cast<const char*>(buffer), buffer_size),
expected_file_content_block);
// The calls below are noops.
input_stream->BackUp(0);
EXPECT_EQ(input_stream->Position(), buffer_size);
input_stream->BackUp(-12);
EXPECT_EQ(input_stream->Position(), buffer_size);
// A subsequent call to `Next` returns the 2nd block.
auto expected_2nd_file_content_block =
absl::string_view(file_contents).substr(buffer_size, buffer_size);
ASSERT_THAT(input_stream->Next(&buffer), IsOkAndHolds(buffer_size));
// Check that we advanced of buffer_size bytes.
EXPECT_EQ(input_stream->Position(), 2 * buffer_size);
EXPECT_EQ(absl::string_view(static_cast<const char*>(buffer), buffer_size),
expected_2nd_file_content_block);
}
TEST(FileInputStreamTest, BackupForLessThanOneBlockOfData) {
int buffer_size = 4 * 1024;
std::string file_contents =
subtle::Random::GetRandomBytes(kDefaultTestStreamSize);
std::string filename = absl::StrCat(
buffer_size, "_", internal::GetTestFileNamePrefix(), "_file.bin");
ASSERT_THAT(internal::CreateTestFile(filename, file_contents), IsOk());
util::StatusOr<int> input_fd = OpenTestFileToRead(filename);
ASSERT_THAT(input_fd.status(), IsOk());
EXPECT_EQ(kDefaultTestStreamSize, file_contents.size());
auto input_stream =
absl::make_unique<util::FileInputStream>(*input_fd, buffer_size);
auto expected_file_content_block =
absl::string_view(file_contents).substr(0, buffer_size);
const void* buffer = nullptr;
ASSERT_THAT(input_stream->Next(&buffer), IsOkAndHolds(buffer_size));
// Check that we advanced of buffer_size bytes.
EXPECT_EQ(input_stream->Position(), buffer_size);
EXPECT_EQ(absl::string_view(static_cast<const char*>(buffer), buffer_size),
expected_file_content_block);
int64_t position_after_next = input_stream->Position();
// Number of bytes that were backed up.
int num_backed_up_bytes = 0;
input_stream->BackUp(0); // This should be a noop.
EXPECT_EQ(input_stream->Position(), position_after_next);
input_stream->BackUp(-12); // This should be a noop.
EXPECT_EQ(input_stream->Position(), position_after_next);
input_stream->BackUp(10);
num_backed_up_bytes += 10;
EXPECT_EQ(input_stream->Position(),
position_after_next - num_backed_up_bytes);
input_stream->BackUp(5);
num_backed_up_bytes += 5;
EXPECT_EQ(input_stream->Position(),
position_after_next - num_backed_up_bytes);
// A subsequent call to Next should return only the backed up bytes.
auto expected_backed_up_bytes =
absl::string_view(file_contents)
.substr(buffer_size - num_backed_up_bytes, num_backed_up_bytes);
ASSERT_THAT(input_stream->Next(&buffer),
IsOkAndHolds(expected_backed_up_bytes.size()));
EXPECT_EQ(absl::string_view(static_cast<const char*>(buffer),
expected_backed_up_bytes.size()),
expected_backed_up_bytes);
}
// When backing up of a number of bytes larger than the size of a block, backup
// of one block.
TEST(FileInputStreamTest, BackupAtMostOfOneBlock) {
int buffer_size = 4 * 1024;
std::string file_contents =
subtle::Random::GetRandomBytes(kDefaultTestStreamSize);
std::string filename = absl::StrCat(
buffer_size, "_", internal::GetTestFileNamePrefix(), "_file.bin");
ASSERT_THAT(internal::CreateTestFile(filename, file_contents), IsOk());
util::StatusOr<int> input_fd = OpenTestFileToRead(filename);
ASSERT_THAT(input_fd.status(), IsOk());
EXPECT_EQ(kDefaultTestStreamSize, file_contents.size());
auto input_stream =
absl::make_unique<util::FileInputStream>(*input_fd, buffer_size);
// Read two blocks of size buffer_size, then back up of more than buffer_size
// bytes.
auto expected_1st_file_content_block =
absl::string_view(file_contents).substr(0, buffer_size);
const void* buffer = nullptr;
ASSERT_THAT(input_stream->Next(&buffer), IsOkAndHolds(buffer_size));
// Check that we advanced of buffer_size bytes.
EXPECT_EQ(input_stream->Position(), buffer_size);
EXPECT_EQ(absl::string_view(static_cast<const char*>(buffer), buffer_size),
expected_1st_file_content_block);
auto expected_2nd_file_content_block =
absl::string_view(file_contents).substr(buffer_size, buffer_size);
ASSERT_THAT(input_stream->Next(&buffer), IsOkAndHolds(buffer_size));
// Check that we advanced of buffer_size bytes.
EXPECT_EQ(input_stream->Position(), 2 * buffer_size);
EXPECT_EQ(absl::string_view(static_cast<const char*>(buffer), buffer_size),
expected_2nd_file_content_block);
int64_t position_after_next = input_stream->Position();
EXPECT_EQ(input_stream->Position(), position_after_next);
input_stream->BackUp(10);
EXPECT_EQ(input_stream->Position(), position_after_next - 10);
input_stream->BackUp(buffer_size);
EXPECT_EQ(input_stream->Position(), position_after_next - buffer_size);
// This call to Next is expected to read the second block again.
ASSERT_THAT(input_stream->Next(&buffer), IsOkAndHolds(buffer_size));
EXPECT_EQ(absl::string_view(static_cast<const char*>(buffer), buffer_size),
expected_2nd_file_content_block);
}
} // namespace
} // namespace tink
} // namespace crypto