1 // Copyright 2018 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/istream_input_stream.h"
18
19 #include <errno.h>
20 #include <stdlib.h>
21
22 #include <algorithm>
23 #include <fstream>
24 #include <iostream>
25 #include <istream>
26 #include <memory>
27 #include <ostream>
28 #include <string>
29 #include <utility>
30
31 #include "gtest/gtest.h"
32 #include "absl/memory/memory.h"
33 #include "absl/status/status.h"
34 #include "absl/status/statusor.h"
35 #include "absl/strings/str_cat.h"
36 #include "absl/strings/string_view.h"
37 #include "tink/internal/test_file_util.h"
38 #include "tink/subtle/random.h"
39 #include "tink/util/test_util.h"
40
41 namespace crypto {
42 namespace tink {
43 namespace {
44
45 // Creates a new test file with the specified 'filename', writes 'size' random
46 // bytes to the file, and returns an istream for reading from the file.
47 // A copy of the bytes written to the file is returned in 'file_contents'.
GetTestIstream(absl::string_view filename,int size,std::string * file_contents)48 std::unique_ptr<std::istream> GetTestIstream(absl::string_view filename,
49 int size,
50 std::string* file_contents) {
51 std::string full_filename =
52 absl::StrCat(crypto::tink::test::TmpDir(), "/", filename);
53 (*file_contents) = subtle::Random::GetRandomBytes(size);
54 std::ofstream output (full_filename, std::ofstream::binary);
55 if (!output.write(file_contents->data(), size) || output.tellp() != size) {
56 std::clog << "Failed to write " << size << " bytes to file "
57 << full_filename << " error: " << errno << std::endl;
58
59 exit(1);
60 }
61 output.close();
62 auto test_istream = absl::make_unique<std::ifstream>(
63 full_filename, std::ofstream::binary);
64 return std::move(test_istream);
65 }
66
67 // Reads the specified 'input_stream' until no more bytes can be read,
68 // and puts the read bytes into 'contents'.
69 // Returns the status of the last input_stream->Next()-operation.
ReadTillEnd(util::IstreamInputStream * input_stream,std::string * contents)70 util::Status ReadTillEnd(util::IstreamInputStream* input_stream,
71 std::string* contents) {
72 contents->clear();
73 const void* buffer;
74 auto next_result = input_stream->Next(&buffer);
75 while (next_result.ok()) {
76 contents->append(static_cast<const char*>(buffer), next_result.value());
77 next_result = input_stream->Next(&buffer);
78 }
79 return next_result.status();
80 }
81
82 class IstreamInputStreamTest : public ::testing::Test {
83 };
84
TEST_F(IstreamInputStreamTest,testReadingStreams)85 TEST_F(IstreamInputStreamTest, testReadingStreams) {
86 for (int stream_size : {0, 10, 100, 1000, 10000, 100000, 1000000}) {
87 std::string file_contents;
88 std::string filename = absl::StrCat(
89 stream_size, "_", internal::GetTestFileNamePrefix(), "_file.bin");
90 auto input = GetTestIstream(filename, stream_size, &file_contents);
91 EXPECT_EQ(stream_size, file_contents.size());
92 auto input_stream = absl::make_unique<util::IstreamInputStream>(
93 std::move(input));
94 std::string stream_contents;
95 auto status = ReadTillEnd(input_stream.get(), &stream_contents);
96 EXPECT_EQ(absl::StatusCode::kOutOfRange, status.code());
97 EXPECT_EQ("EOF", status.message());
98 EXPECT_EQ(file_contents, stream_contents);
99 }
100 }
101
TEST_F(IstreamInputStreamTest,testCustomBufferSizes)102 TEST_F(IstreamInputStreamTest, testCustomBufferSizes) {
103 int stream_size = 100000;
104 for (int buffer_size : {1, 10, 100, 1000, 10000}) {
105 std::string file_contents;
106 std::string filename = absl::StrCat(
107 buffer_size, "_", internal::GetTestFileNamePrefix(), "_file.bin");
108 auto input = GetTestIstream(filename, stream_size, &file_contents);
109 EXPECT_EQ(stream_size, file_contents.size());
110 auto input_stream = absl::make_unique<util::IstreamInputStream>(
111 std::move(input), buffer_size);
112 const void* buffer;
113 auto next_result = input_stream->Next(&buffer);
114 EXPECT_TRUE(next_result.ok()) << next_result.status();
115 EXPECT_EQ(buffer_size, next_result.value());
116 EXPECT_EQ(file_contents.substr(0, buffer_size),
117 std::string(static_cast<const char*>(buffer), buffer_size));
118 }
119 }
120
TEST_F(IstreamInputStreamTest,testBackupAndPosition)121 TEST_F(IstreamInputStreamTest, testBackupAndPosition) {
122 int stream_size = 100000;
123 int buffer_size = 1234;
124 const void* buffer;
125 std::string file_contents;
126 std::string filename =
127 absl::StrCat(buffer_size, internal::GetTestFileNamePrefix(), "_file.bin");
128 auto input = GetTestIstream(filename, stream_size, &file_contents);
129 EXPECT_EQ(stream_size, file_contents.size());
130
131 // Prepare the stream and do the first call to Next().
132 auto input_stream = absl::make_unique<util::IstreamInputStream>(
133 std::move(input), buffer_size);
134 EXPECT_EQ(0, input_stream->Position());
135 auto next_result = input_stream->Next(&buffer);
136 EXPECT_TRUE(next_result.ok()) << next_result.status();
137 EXPECT_EQ(buffer_size, next_result.value());
138 EXPECT_EQ(buffer_size, input_stream->Position());
139 EXPECT_EQ(file_contents.substr(0, buffer_size),
140 std::string(static_cast<const char*>(buffer), buffer_size));
141
142 // BackUp several times, but in total fewer bytes than returned by Next().
143 int total_backup_size = 0;
144 for (auto backup_size : {0, 1, 5, 0, 10, 100, -42, 400, 20, -100}) {
145 input_stream->BackUp(backup_size);
146 total_backup_size += std::max(0, backup_size);
147 EXPECT_EQ(buffer_size - total_backup_size, input_stream->Position());
148 }
149 // Call Next(), it should return exactly the backed up bytes.
150 next_result = input_stream->Next(&buffer);
151 EXPECT_TRUE(next_result.ok()) << next_result.status();
152 EXPECT_EQ(total_backup_size, next_result.value());
153 EXPECT_EQ(buffer_size, input_stream->Position());
154 EXPECT_EQ(
155 file_contents.substr(buffer_size - total_backup_size, total_backup_size),
156 std::string(static_cast<const char*>(buffer), total_backup_size));
157
158 // BackUp() some bytes, again fewer than returned by Next().
159 total_backup_size = 0;
160 for (int backup_size : {0, 72, -94, 37, 82}) {
161 input_stream->BackUp(backup_size);
162 total_backup_size += std::max(0, backup_size);
163 EXPECT_EQ(buffer_size - total_backup_size, input_stream->Position());
164 }
165
166 // Call Next(), it should return exactly the backed up bytes.
167 next_result = input_stream->Next(&buffer);
168 EXPECT_TRUE(next_result.ok()) << next_result.status();
169 EXPECT_EQ(total_backup_size, next_result.value());
170 EXPECT_EQ(buffer_size, input_stream->Position());
171 EXPECT_EQ(
172 file_contents.substr(buffer_size - total_backup_size, total_backup_size),
173 std::string(static_cast<const char*>(buffer), total_backup_size));
174
175 // Call Next() again, it should return the second block.
176 next_result = input_stream->Next(&buffer);
177 EXPECT_TRUE(next_result.ok()) << next_result.status();
178 EXPECT_EQ(buffer_size, next_result.value());
179 EXPECT_EQ(2 * buffer_size, input_stream->Position());
180 EXPECT_EQ(file_contents.substr(buffer_size, buffer_size),
181 std::string(static_cast<const char*>(buffer), buffer_size));
182
183 // BackUp a few times, with total over the returned buffer_size.
184 total_backup_size = 0;
185 for (int backup_size :
186 {0, 72, -100, buffer_size/2, 200, -25, buffer_size, 42}) {
187 input_stream->BackUp(backup_size);
188 total_backup_size = std::min(buffer_size,
189 total_backup_size + std::max(0, backup_size));
190 EXPECT_EQ(2 * buffer_size - total_backup_size, input_stream->Position());
191 }
192
193 // Call Next() again, it should return the second block.
194 next_result = input_stream->Next(&buffer);
195 EXPECT_TRUE(next_result.ok()) << next_result.status();
196 EXPECT_EQ(buffer_size, next_result.value());
197 EXPECT_EQ(2 * buffer_size, input_stream->Position());
198 EXPECT_EQ(file_contents.substr(buffer_size, buffer_size),
199 std::string(static_cast<const char*>(buffer), buffer_size));
200 }
201
202 } // namespace
203 } // namespace tink
204 } // namespace crypto
205