xref: /aosp_15_r20/external/tink/cc/util/file_random_access_stream_test.cc (revision e7b1675dde1b92d52ec075b0a92829627f2c52a5)
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