1 /**
2 * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3 * SPDX-License-Identifier: Apache-2.0.
4 */
5 #include <aws/crt/Api.h>
6 #include <aws/crt/crypto/Hash.h>
7 #include <aws/crt/http/HttpConnection.h>
8 #include <aws/crt/http/HttpRequestResponse.h>
9 #include <aws/crt/io/Uri.h>
10
11 #include <aws/testing/aws_test_harness.h>
12
13 #include <condition_variable>
14 #include <fstream>
15 #include <iostream>
16 #include <mutex>
17
18 using namespace Aws::Crt;
19
20 #if !BYO_CRYPTO
21
s_VerifyFilesAreTheSame(Allocator * allocator,const char * fileName1,const char * fileName2)22 static int s_VerifyFilesAreTheSame(Allocator *allocator, const char *fileName1, const char *fileName2)
23 {
24 std::ifstream file1(fileName1, std::ios_base::binary);
25 std::ifstream file2(fileName2, std::ios_base::binary);
26
27 ASSERT_TRUE(file1);
28 ASSERT_TRUE(file2);
29
30 auto file1Hash = Crypto::Hash::CreateSHA256(allocator);
31 uint8_t buffer[1024];
32 AWS_ZERO_ARRAY(buffer);
33
34 while (file1.read((char *)buffer, sizeof(buffer)))
35 {
36 auto read = file1.gcount();
37 ByteCursor toHash = ByteCursorFromArray(buffer, (size_t)read);
38 ASSERT_TRUE(file1Hash.Update(toHash));
39 }
40
41 auto file2Hash = Crypto::Hash::CreateSHA256(allocator);
42
43 while (file2.read((char *)buffer, sizeof(buffer)))
44 {
45 auto read = file2.gcount();
46 ByteCursor toHash = ByteCursorFromArray(buffer, (size_t)read);
47 ASSERT_TRUE(file2Hash.Update(toHash));
48 }
49
50 uint8_t file1Digest[Crypto::SHA256_DIGEST_SIZE];
51 AWS_ZERO_ARRAY(file1Digest);
52 uint8_t file2Digest[Crypto::SHA256_DIGEST_SIZE];
53 AWS_ZERO_ARRAY(file2Digest);
54
55 ByteBuf file1DigestBuf = ByteBufFromEmptyArray(file1Digest, sizeof(file1Digest));
56 ByteBuf file2DigestBuf = ByteBufFromEmptyArray(file2Digest, sizeof(file2Digest));
57
58 ASSERT_TRUE(file1Hash.Digest(file1DigestBuf));
59 ASSERT_TRUE(file2Hash.Digest(file2DigestBuf));
60
61 ASSERT_BIN_ARRAYS_EQUALS(file2DigestBuf.buffer, file2DigestBuf.len, file1DigestBuf.buffer, file1DigestBuf.len);
62 return AWS_OP_SUCCESS;
63 }
64
s_TestHttpDownloadNoBackPressure(struct aws_allocator * allocator,ByteCursor urlCursor,bool h2Required)65 static int s_TestHttpDownloadNoBackPressure(struct aws_allocator *allocator, ByteCursor urlCursor, bool h2Required)
66 {
67 int result = AWS_OP_ERR;
68
69 {
70 Aws::Crt::ApiHandle apiHandle(allocator);
71 Aws::Crt::Io::TlsContextOptions tlsCtxOptions = Aws::Crt::Io::TlsContextOptions::InitDefaultClient();
72 Aws::Crt::Io::TlsContext tlsContext(tlsCtxOptions, Aws::Crt::Io::TlsMode::CLIENT, allocator);
73 ASSERT_TRUE(tlsContext);
74
75 Aws::Crt::Io::TlsConnectionOptions tlsConnectionOptions = tlsContext.NewConnectionOptions();
76
77 Io::Uri uri(urlCursor, allocator);
78
79 auto hostName = uri.GetHostName();
80 tlsConnectionOptions.SetServerName(hostName);
81 if (h2Required)
82 {
83 tlsConnectionOptions.SetAlpnList("h2");
84 }
85
86 Aws::Crt::Io::SocketOptions socketOptions;
87 socketOptions.SetConnectTimeoutMs(5000);
88
89 Aws::Crt::Io::EventLoopGroup eventLoopGroup(0, allocator);
90 ASSERT_TRUE(eventLoopGroup);
91
92 Aws::Crt::Io::DefaultHostResolver defaultHostResolver(eventLoopGroup, 8, 30, allocator);
93 ASSERT_TRUE(defaultHostResolver);
94
95 Aws::Crt::Io::ClientBootstrap clientBootstrap(eventLoopGroup, defaultHostResolver, allocator);
96 ASSERT_TRUE(clientBootstrap);
97 clientBootstrap.EnableBlockingShutdown();
98
99 std::shared_ptr<Http::HttpClientConnection> connection(nullptr);
100 bool errorOccured = true;
101 bool connectionShutdown = false;
102
103 std::condition_variable semaphore;
104 std::mutex semaphoreLock;
105
106 auto onConnectionSetup = [&](const std::shared_ptr<Http::HttpClientConnection> &newConnection, int errorCode) {
107 std::lock_guard<std::mutex> lockGuard(semaphoreLock);
108
109 if (!errorCode)
110 {
111 connection = newConnection;
112 errorOccured = false;
113 }
114 else
115 {
116 connectionShutdown = true;
117 }
118
119 semaphore.notify_one();
120 };
121
122 auto onConnectionShutdown = [&](Http::HttpClientConnection &, int errorCode) {
123 std::lock_guard<std::mutex> lockGuard(semaphoreLock);
124
125 connectionShutdown = true;
126 if (errorCode)
127 {
128 errorOccured = true;
129 }
130
131 semaphore.notify_one();
132 };
133
134 Http::HttpClientConnectionOptions httpClientConnectionOptions;
135 httpClientConnectionOptions.Bootstrap = &clientBootstrap;
136 httpClientConnectionOptions.OnConnectionSetupCallback = onConnectionSetup;
137 httpClientConnectionOptions.OnConnectionShutdownCallback = onConnectionShutdown;
138 httpClientConnectionOptions.SocketOptions = socketOptions;
139 httpClientConnectionOptions.TlsOptions = tlsConnectionOptions;
140 httpClientConnectionOptions.HostName = String((const char *)hostName.ptr, hostName.len);
141 httpClientConnectionOptions.Port = 443;
142
143 std::unique_lock<std::mutex> semaphoreULock(semaphoreLock);
144 ASSERT_TRUE(Http::HttpClientConnection::CreateConnection(httpClientConnectionOptions, allocator));
145 semaphore.wait(semaphoreULock, [&]() { return connection || connectionShutdown; });
146
147 ASSERT_FALSE(errorOccured);
148 ASSERT_FALSE(connectionShutdown);
149 ASSERT_TRUE(connection);
150 String fileName = h2Required ? "http_download_test_file_h2.txt" : "http_download_test_file_h1_1.txt";
151 Http::HttpVersion excepted = h2Required ? Http::HttpVersion::Http2 : Http::HttpVersion::Http1_1;
152 ASSERT_TRUE(connection->GetVersion() == excepted);
153
154 int responseCode = 0;
155 std::ofstream downloadedFile(fileName.c_str(), std::ios_base::binary);
156 ASSERT_TRUE(downloadedFile);
157
158 Http::HttpRequest request;
159 Http::HttpRequestOptions requestOptions;
160 requestOptions.request = &request;
161
162 bool streamCompleted = false;
163 requestOptions.onStreamComplete = [&](Http::HttpStream &, int errorCode) {
164 std::lock_guard<std::mutex> lockGuard(semaphoreLock);
165
166 streamCompleted = true;
167 if (errorCode)
168 {
169 errorOccured = true;
170 }
171
172 semaphore.notify_one();
173 };
174 requestOptions.onIncomingHeadersBlockDone = nullptr;
175 requestOptions.onIncomingHeaders =
176 [&](Http::HttpStream &stream, enum aws_http_header_block, const Http::HttpHeader *, std::size_t) {
177 responseCode = stream.GetResponseStatusCode();
178 };
179 requestOptions.onIncomingBody = [&](Http::HttpStream &, const ByteCursor &data) {
180 downloadedFile.write((const char *)data.ptr, data.len);
181 };
182
183 request.SetMethod(ByteCursorFromCString("GET"));
184 request.SetPath(uri.GetPathAndQuery());
185
186 Http::HttpHeader hostHeader;
187 hostHeader.name = ByteCursorFromCString("host");
188 hostHeader.value = uri.GetHostName();
189 request.AddHeader(hostHeader);
190
191 auto stream = connection->NewClientStream(requestOptions);
192 ASSERT_TRUE(stream->Activate());
193
194 semaphore.wait(semaphoreULock, [&]() { return streamCompleted; });
195 ASSERT_INT_EQUALS(200, responseCode);
196
197 connection->Close();
198 semaphore.wait(semaphoreULock, [&]() { return connectionShutdown; });
199
200 downloadedFile.flush();
201 downloadedFile.close();
202 result = s_VerifyFilesAreTheSame(allocator, fileName.c_str(), "http_test_doc.txt");
203 }
204
205 return result;
206 }
207
s_TestHttpDownloadNoBackPressureHTTP1_1(struct aws_allocator * allocator,void * ctx)208 static int s_TestHttpDownloadNoBackPressureHTTP1_1(struct aws_allocator *allocator, void *ctx)
209 {
210 (void)ctx;
211 ByteCursor cursor = ByteCursorFromCString("https://aws-crt-test-stuff.s3.amazonaws.com/http_test_doc.txt");
212 return s_TestHttpDownloadNoBackPressure(allocator, cursor, false /*h2Required*/);
213 }
214
AWS_TEST_CASE(HttpDownloadNoBackPressureHTTP1_1,s_TestHttpDownloadNoBackPressureHTTP1_1)215 AWS_TEST_CASE(HttpDownloadNoBackPressureHTTP1_1, s_TestHttpDownloadNoBackPressureHTTP1_1)
216
217 static int s_TestHttpDownloadNoBackPressureHTTP2(struct aws_allocator *allocator, void *ctx)
218 {
219 (void)ctx;
220 ByteCursor cursor = ByteCursorFromCString("https://d1cz66xoahf9cl.cloudfront.net/http_test_doc.txt");
221 return s_TestHttpDownloadNoBackPressure(allocator, cursor, true /*h2Required*/);
222 }
223
AWS_TEST_CASE(HttpDownloadNoBackPressureHTTP2,s_TestHttpDownloadNoBackPressureHTTP2)224 AWS_TEST_CASE(HttpDownloadNoBackPressureHTTP2, s_TestHttpDownloadNoBackPressureHTTP2)
225
226 static int s_TestHttpStreamUnActivated(struct aws_allocator *allocator, void *ctx)
227 {
228 (void)ctx;
229 {
230 Aws::Crt::ApiHandle apiHandle(allocator);
231 Aws::Crt::Io::TlsContextOptions tlsCtxOptions = Aws::Crt::Io::TlsContextOptions::InitDefaultClient();
232 Aws::Crt::Io::TlsContext tlsContext(tlsCtxOptions, Aws::Crt::Io::TlsMode::CLIENT, allocator);
233 ASSERT_TRUE(tlsContext);
234
235 Aws::Crt::Io::TlsConnectionOptions tlsConnectionOptions = tlsContext.NewConnectionOptions();
236
237 ByteCursor cursor = ByteCursorFromCString("https://aws-crt-test-stuff.s3.amazonaws.com/http_test_doc.txt");
238 Io::Uri uri(cursor, allocator);
239
240 auto hostName = uri.GetHostName();
241 tlsConnectionOptions.SetServerName(hostName);
242
243 Aws::Crt::Io::SocketOptions socketOptions;
244 socketOptions.SetConnectTimeoutMs(1000);
245
246 Aws::Crt::Io::EventLoopGroup eventLoopGroup(0, allocator);
247 ASSERT_TRUE(eventLoopGroup);
248
249 Aws::Crt::Io::DefaultHostResolver defaultHostResolver(eventLoopGroup, 8, 30, allocator);
250 ASSERT_TRUE(defaultHostResolver);
251
252 Aws::Crt::Io::ClientBootstrap clientBootstrap(eventLoopGroup, defaultHostResolver, allocator);
253 ASSERT_TRUE(clientBootstrap);
254 clientBootstrap.EnableBlockingShutdown();
255
256 std::shared_ptr<Http::HttpClientConnection> connection(nullptr);
257 bool errorOccured = true;
258 bool connectionShutdown = false;
259
260 std::condition_variable semaphore;
261 std::mutex semaphoreLock;
262
263 auto onConnectionSetup = [&](const std::shared_ptr<Http::HttpClientConnection> &newConnection, int errorCode) {
264 std::lock_guard<std::mutex> lockGuard(semaphoreLock);
265
266 if (!errorCode)
267 {
268 connection = newConnection;
269 errorOccured = false;
270 }
271 else
272 {
273 connectionShutdown = true;
274 }
275
276 semaphore.notify_one();
277 };
278
279 auto onConnectionShutdown = [&](Http::HttpClientConnection &, int errorCode) {
280 std::lock_guard<std::mutex> lockGuard(semaphoreLock);
281
282 connectionShutdown = true;
283 if (errorCode)
284 {
285 errorOccured = true;
286 }
287
288 semaphore.notify_one();
289 };
290
291 Http::HttpClientConnectionOptions httpClientConnectionOptions;
292 httpClientConnectionOptions.Bootstrap = &clientBootstrap;
293 httpClientConnectionOptions.OnConnectionSetupCallback = onConnectionSetup;
294 httpClientConnectionOptions.OnConnectionShutdownCallback = onConnectionShutdown;
295 httpClientConnectionOptions.SocketOptions = socketOptions;
296 httpClientConnectionOptions.TlsOptions = tlsConnectionOptions;
297 httpClientConnectionOptions.HostName = String((const char *)hostName.ptr, hostName.len);
298 httpClientConnectionOptions.Port = 443;
299
300 std::unique_lock<std::mutex> semaphoreULock(semaphoreLock);
301 ASSERT_TRUE(Http::HttpClientConnection::CreateConnection(httpClientConnectionOptions, allocator));
302 semaphore.wait(semaphoreULock, [&]() { return connection || connectionShutdown; });
303
304 ASSERT_FALSE(errorOccured);
305 ASSERT_FALSE(connectionShutdown);
306 ASSERT_TRUE(connection);
307
308 Http::HttpRequest request;
309 Http::HttpRequestOptions requestOptions;
310 requestOptions.request = &request;
311
312 requestOptions.onStreamComplete = [&](Http::HttpStream &, int) {
313 // do nothing.
314 };
315 requestOptions.onIncomingHeadersBlockDone = nullptr;
316 requestOptions.onIncomingHeaders =
317 [&](Http::HttpStream &, enum aws_http_header_block, const Http::HttpHeader *, std::size_t) {
318 // do nothing
319 };
320 requestOptions.onIncomingBody = [&](Http::HttpStream &, const ByteCursor &) {
321 // do nothing
322 };
323
324 request.SetMethod(ByteCursorFromCString("GET"));
325 request.SetPath(uri.GetPathAndQuery());
326
327 Http::HttpHeader hostHeader;
328 hostHeader.name = ByteCursorFromCString("host");
329 hostHeader.value = uri.GetHostName();
330 request.AddHeader(hostHeader);
331
332 // don't activate it and let it go out of scope.
333 auto stream = connection->NewClientStream(requestOptions);
334 stream = nullptr;
335 connection->Close();
336 semaphore.wait(semaphoreULock, [&]() { return connectionShutdown; });
337 }
338
339 return AWS_OP_SUCCESS;
340 }
341
AWS_TEST_CASE(HttpStreamUnActivated,s_TestHttpStreamUnActivated)342 AWS_TEST_CASE(HttpStreamUnActivated, s_TestHttpStreamUnActivated)
343
344 static int s_TestHttpCreateConnectionInvalidTlsConnectionOptions(struct aws_allocator *allocator, void *ctx)
345 {
346 (void)ctx;
347 {
348 Aws::Crt::ApiHandle apiHandle(allocator);
349
350 Aws::Crt::Io::TlsConnectionOptions invalidTlsConnectionOptions;
351 ASSERT_FALSE(invalidTlsConnectionOptions);
352
353 ByteCursor cursor = ByteCursorFromCString("https://aws-crt-test-stuff.s3.amazonaws.com/http_test_doc.txt");
354 Io::Uri uri(cursor, allocator);
355
356 auto hostName = uri.GetHostName();
357
358 Aws::Crt::Io::SocketOptions socketOptions;
359
360 Aws::Crt::Io::EventLoopGroup eventLoopGroup(0, allocator);
361 ASSERT_TRUE(eventLoopGroup);
362
363 Aws::Crt::Io::DefaultHostResolver defaultHostResolver(eventLoopGroup, 8, 30, allocator);
364 ASSERT_TRUE(defaultHostResolver);
365
366 Aws::Crt::Io::ClientBootstrap clientBootstrap(eventLoopGroup, defaultHostResolver, allocator);
367 ASSERT_TRUE(clientBootstrap);
368 clientBootstrap.EnableBlockingShutdown();
369
370 Http::HttpClientConnectionOptions httpClientConnectionOptions;
371 httpClientConnectionOptions.Bootstrap = &clientBootstrap;
372 httpClientConnectionOptions.OnConnectionSetupCallback = [](const std::shared_ptr<Http::HttpClientConnection> &,
373 int) {};
374 httpClientConnectionOptions.OnConnectionShutdownCallback = [](Http::HttpClientConnection &, int) {};
375 httpClientConnectionOptions.SocketOptions = socketOptions;
376 httpClientConnectionOptions.TlsOptions = invalidTlsConnectionOptions;
377 httpClientConnectionOptions.HostName = String((const char *)hostName.ptr, hostName.len);
378 httpClientConnectionOptions.Port = 443;
379
380 ASSERT_FALSE(Http::HttpClientConnection::CreateConnection(httpClientConnectionOptions, allocator));
381 }
382
383 return AWS_OP_SUCCESS;
384 }
385
386 AWS_TEST_CASE(HttpCreateConnectionInvalidTlsConnectionOptions, s_TestHttpCreateConnectionInvalidTlsConnectionOptions)
387
388 #endif // !BYO_CRYPTO
389