1 // Copyright 2019 Google Inc.
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 // http://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14 //
15 ///////////////////////////////////////////////////////////////////////////////
16
17 #include "tink/util/file_random_access_stream.h"
18
19 #include <fcntl.h>
20 #include <unistd.h>
21
22 #include <cstring>
23 #include <iostream>
24 #include <ostream>
25 #include <string>
26 #include <thread> // NOLINT(build/c++11)
27 #include <utility>
28
29 #include "gtest/gtest.h"
30 #include "absl/memory/memory.h"
31 #include "absl/status/status.h"
32 #include "absl/strings/str_cat.h"
33 #include "absl/strings/string_view.h"
34 #include "tink/internal/test_file_util.h"
35 #include "tink/subtle/random.h"
36 #include "tink/util/buffer.h"
37 #include "tink/util/test_matchers.h"
38 #include "tink/util/test_util.h"
39
40 namespace crypto {
41 namespace tink {
42 namespace util {
43 namespace {
44
45 using ::crypto::tink::test::IsOk;
46
47 // Opens test file `filename` and returns a file descriptor to it.
OpenTestFileToRead(absl::string_view filename)48 util::StatusOr<int> OpenTestFileToRead(absl::string_view filename) {
49 std::string full_filename = absl::StrCat(test::TmpDir(), "/", filename);
50 int fd = open(full_filename.c_str(), O_RDONLY);
51 if (fd == -1) {
52 return util::Status(absl::StatusCode::kInternal,
53 absl::StrCat("Cannot open file ", full_filename,
54 " error: ", std::strerror(errno)));
55 }
56 return fd;
57 }
58
59 // Reads the entire 'ra_stream' in chunks of size 'chunk_size',
60 // until no more bytes can be read, and puts the read bytes into 'contents'.
61 // Returns the status of the last ra_stream->Next()-operation.
ReadAll(RandomAccessStream * ra_stream,int chunk_size,std::string * contents)62 util::Status ReadAll(RandomAccessStream* ra_stream, int chunk_size,
63 std::string* contents) {
64 contents->clear();
65 auto buffer = std::move(Buffer::New(chunk_size).value());
66 int64_t position = 0;
67 auto status = ra_stream->PRead(position, chunk_size, buffer.get());
68 while (status.ok()) {
69 contents->append(buffer->get_mem_block(), buffer->size());
70 position = contents->size();
71 status = ra_stream->PRead(position, chunk_size, buffer.get());
72 }
73 if (status.code() == absl::StatusCode::kOutOfRange) { // EOF
74 EXPECT_EQ(0, buffer->size());
75 }
76 return status;
77 }
78
79 // Reads from 'ra_stream' a chunk of 'count' bytes starting offset 'position',
80 // and compares the read bytes to the corresponding bytes in 'file_contents'.
ReadAndVerifyChunk(RandomAccessStream * ra_stream,int64_t position,int count,absl::string_view file_contents)81 void ReadAndVerifyChunk(RandomAccessStream* ra_stream,
82 int64_t position,
83 int count,
84 absl::string_view file_contents) {
85 SCOPED_TRACE(absl::StrCat("stream_size = ", file_contents.size(),
86 ", position = ", position,
87 ", count = ", count));
88 auto buffer = std::move(Buffer::New(count).value());
89 int stream_size = ra_stream->size().value();
90 EXPECT_EQ(file_contents.size(), stream_size);
91 auto status = ra_stream->PRead(position, count, buffer.get());
92 EXPECT_TRUE(status.ok());
93 int read_count = buffer->size();
94 int expected_count = count;
95 if (position + count > stream_size) {
96 expected_count = stream_size - position;
97 }
98 EXPECT_EQ(expected_count, read_count);
99 EXPECT_EQ(0, memcmp(&file_contents[position],
100 buffer->get_mem_block(), read_count));
101 }
102
TEST(FileRandomAccessStreamTest,ReadingStreams)103 TEST(FileRandomAccessStreamTest, ReadingStreams) {
104 for (auto stream_size : {1, 10, 100, 1000, 10000, 1000000}) {
105 SCOPED_TRACE(absl::StrCat("stream_size = ", stream_size));
106 std::string file_contents = subtle::Random::GetRandomBytes(stream_size);
107 std::string filename = absl::StrCat(
108 stream_size, crypto::tink::internal::GetTestFileNamePrefix(),
109 "_file.bin");
110 ASSERT_THAT(crypto::tink::internal::CreateTestFile(filename, file_contents),
111 IsOk());
112 util::StatusOr<int> input_fd = OpenTestFileToRead(filename);
113 ASSERT_THAT(input_fd.status(), IsOk());
114 EXPECT_EQ(stream_size, file_contents.size());
115 auto ra_stream = absl::make_unique<util::FileRandomAccessStream>(*input_fd);
116 std::string stream_contents;
117 auto status =
118 ReadAll(ra_stream.get(), 1 + (stream_size / 10), &stream_contents);
119 EXPECT_EQ(absl::StatusCode::kOutOfRange, status.code());
120 EXPECT_EQ("EOF", status.message());
121 EXPECT_EQ(file_contents, stream_contents);
122 EXPECT_EQ(stream_size, ra_stream->size().value());
123 }
124 }
125
TEST(FileRandomAccessStreamTest,ReadingStreamsTillLastByte)126 TEST(FileRandomAccessStreamTest, ReadingStreamsTillLastByte) {
127 for (auto stream_size : {1, 10, 100, 1000, 10000}) {
128 SCOPED_TRACE(absl::StrCat("stream_size = ", stream_size));
129 std::string file_contents = subtle::Random::GetRandomBytes(stream_size);
130 std::string filename = absl::StrCat(
131 stream_size, crypto::tink::internal::GetTestFileNamePrefix(),
132 "_file.bin");
133 ASSERT_THAT(crypto::tink::internal::CreateTestFile(filename, file_contents),
134 IsOk());
135 util::StatusOr<int> input_fd = OpenTestFileToRead(filename);
136 ASSERT_THAT(input_fd.status(), IsOk());
137 EXPECT_EQ(stream_size, file_contents.size());
138 auto ra_stream = absl::make_unique<util::FileRandomAccessStream>(*input_fd);
139 auto buffer = std::move(Buffer::New(stream_size).value());
140
141 // Read from the beginning till the last byte.
142 auto status = ra_stream->PRead(/* position = */ 0,
143 stream_size, buffer.get());
144 EXPECT_TRUE(status.ok());
145 EXPECT_EQ(stream_size, ra_stream->size().value());
146 EXPECT_EQ(0, memcmp(&file_contents[0],
147 buffer->get_mem_block(), stream_size));
148 }
149 }
150
TEST(FileRandomAccessStreamTest,ConcurrentReads)151 TEST(FileRandomAccessStreamTest, ConcurrentReads) {
152 for (auto stream_size : {100, 1000, 10000, 100000}) {
153 std::string file_contents = subtle::Random::GetRandomBytes(stream_size);
154 std::string filename = absl::StrCat(
155 stream_size, crypto::tink::internal::GetTestFileNamePrefix(),
156 "_file.bin");
157 ASSERT_THAT(crypto::tink::internal::CreateTestFile(filename, file_contents),
158 IsOk());
159 util::StatusOr<int> input_fd = OpenTestFileToRead(filename);
160 ASSERT_THAT(input_fd.status(), IsOk());
161 EXPECT_EQ(stream_size, file_contents.size());
162 auto ra_stream = absl::make_unique<util::FileRandomAccessStream>(*input_fd);
163 std::thread read_0(ReadAndVerifyChunk,
164 ra_stream.get(), 0, stream_size / 2, file_contents);
165 std::thread read_1(ReadAndVerifyChunk,
166 ra_stream.get(), stream_size / 4, stream_size / 2, file_contents);
167 std::thread read_2(ReadAndVerifyChunk,
168 ra_stream.get(), stream_size / 2, stream_size / 2, file_contents);
169 std::thread read_3(ReadAndVerifyChunk,
170 ra_stream.get(), 3 * stream_size / 4, stream_size / 2, file_contents);
171 read_0.join();
172 read_1.join();
173 read_2.join();
174 read_3.join();
175 }
176 }
177
TEST(FileRandomAccessStreamTest,NegativeReadPosition)178 TEST(FileRandomAccessStreamTest, NegativeReadPosition) {
179 for (auto stream_size : {0, 10, 100, 1000, 10000}) {
180 std::string file_contents = subtle::Random::GetRandomBytes(stream_size);
181 std::string filename = absl::StrCat(
182 stream_size, crypto::tink::internal::GetTestFileNamePrefix(),
183 "_file.bin");
184 ASSERT_THAT(crypto::tink::internal::CreateTestFile(filename, file_contents),
185 IsOk());
186 util::StatusOr<int> input_fd = OpenTestFileToRead(filename);
187 ASSERT_THAT(input_fd.status(), IsOk());
188 auto ra_stream = absl::make_unique<util::FileRandomAccessStream>(*input_fd);
189 int count = 42;
190 auto buffer = std::move(Buffer::New(count).value());
191 for (auto position : {-100, -10, -1}) {
192 SCOPED_TRACE(absl::StrCat("stream_size = ", stream_size,
193 " position = ", position));
194
195 auto status = ra_stream->PRead(position, count, buffer.get());
196 EXPECT_EQ(absl::StatusCode::kInvalidArgument, status.code());
197 }
198 }
199 }
200
TEST(FileRandomAccessStreamTest,NotPositiveReadCount)201 TEST(FileRandomAccessStreamTest, NotPositiveReadCount) {
202 for (auto stream_size : {0, 10, 100, 1000, 10000}) {
203 std::string file_contents = subtle::Random::GetRandomBytes(stream_size);
204 std::string filename = absl::StrCat(
205 stream_size, crypto::tink::internal::GetTestFileNamePrefix(),
206 "_file.bin");
207 ASSERT_THAT(crypto::tink::internal::CreateTestFile(filename, file_contents),
208 IsOk());
209 util::StatusOr<int> input_fd = OpenTestFileToRead(filename);
210 ASSERT_THAT(input_fd.status(), IsOk());
211 auto ra_stream = absl::make_unique<util::FileRandomAccessStream>(*input_fd);
212 auto buffer = std::move(Buffer::New(42).value());
213 int64_t position = 0;
214 for (auto count : {-100, -10, -1, 0}) {
215 SCOPED_TRACE(absl::StrCat("stream_size = ", stream_size,
216 " count = ", count));
217 auto status = ra_stream->PRead(position, count, buffer.get());
218 EXPECT_EQ(absl::StatusCode::kInvalidArgument, status.code());
219 }
220 }
221 }
222
TEST(FileRandomAccessStreamTest,ReadPositionAfterEof)223 TEST(FileRandomAccessStreamTest, ReadPositionAfterEof) {
224 for (auto stream_size : {0, 10, 100, 1000, 10000}) {
225 std::string file_contents = subtle::Random::GetRandomBytes(stream_size);
226 std::string filename = absl::StrCat(
227 stream_size, crypto::tink::internal::GetTestFileNamePrefix(),
228 "_file.bin");
229 ASSERT_THAT(crypto::tink::internal::CreateTestFile(filename, file_contents),
230 IsOk());
231 util::StatusOr<int> input_fd = OpenTestFileToRead(filename);
232 ASSERT_THAT(input_fd.status(), IsOk());
233 auto ra_stream = absl::make_unique<util::FileRandomAccessStream>(*input_fd);
234 int count = 42;
235 auto buffer = std::move(Buffer::New(count).value());
236 for (auto position : {stream_size + 1, stream_size + 10}) {
237 SCOPED_TRACE(absl::StrCat("stream_size = ", stream_size,
238 " position = ", position));
239
240 auto status = ra_stream->PRead(position, count, buffer.get());
241 EXPECT_EQ(absl::StatusCode::kOutOfRange, status.code());
242 EXPECT_EQ(0, buffer->size());
243 }
244 }
245 }
246
247 } // namespace
248 } // namespace util
249 } // namespace tink
250 } // namespace crypto
251