UDocumentation UE5.7 10.02.2026 (Source)
API documentation for Unreal Engine 5.7
Test.inl
Go to the documentation of this file.
1// Copyright Epic Games, Inc. All Rights Reserved.
2
3#pragma once
4
5#if !defined(NO_UE_INCLUDES)
6#include <HAL/FileManager.h>
7#include <Misc/Paths.h>
8#endif
9
10namespace UE::IoStore::HTTP
11{
12
13#if !(UE_BUILD_SHIPPING|UE_BUILD_TEST)
14
16static void MiscTest()
17{
18#define CRLF "\r\n"
19#if 0
20 struct {
23 } FmtTestCases[] = {
24 { "", -1 },
25 { "abcd", -1 },
26 { "abcd\r", -1 },
27 { CRLF "\r\r", -1 },
28 { CRLF CRLF, 4 },
29 { "abc" CRLF CRLF, 7 },
30 };
31 for (const auto [Input, Output] : FmtTestCases)
32 {
33 check(FindMessageTerminal(Input.GetData(), Input.Len()) == Output);
34 }
35#endif // 0
36
37 FMessageOffsets MsgOut;
38 check(ParseMessage("", MsgOut) == -1);
39 check(ParseMessage("MR", MsgOut) == -1);
40 check(ParseMessage("HTTP/1.1", MsgOut) == -1);
41 check(ParseMessage("HTTP/1.1 ", MsgOut) == -1);
42 check(ParseMessage("HTTP/1.1 1" CRLF, MsgOut) > 0);
43 check(ParseMessage("HTTP/1.1 1" CRLF, MsgOut) > 0);
44 check(ParseMessage("HTTP/1.1 100 " CRLF, MsgOut) > 0);
45 check(ParseMessage("HTTP/1.1 100 Message of some sort " CRLF, MsgOut) > 0);
46 check(ParseMessage("HTTP/1.1 100 _Message with a \r in it" CRLF, MsgOut) == -1);
47
48 bool AllIsWell = true;
49 auto NotExpectedToBeCalled = [&AllIsWell] (auto, auto)
50 {
51 AllIsWell = false;
52 return false;
53 };
54
55 EnumerateHeaders("", NotExpectedToBeCalled); check(AllIsWell);
56 EnumerateHeaders(CRLF, NotExpectedToBeCalled); check(AllIsWell);
57 EnumerateHeaders("foo", NotExpectedToBeCalled); check(AllIsWell);
58 EnumerateHeaders(" foo", NotExpectedToBeCalled); check(AllIsWell);
59 EnumerateHeaders(" foo ", NotExpectedToBeCalled); check(AllIsWell);
60 EnumerateHeaders("foo:bar", NotExpectedToBeCalled); check(AllIsWell);
61
62 auto IsBar = [&] (auto, auto Value) { return AllIsWell = (Value == "bar"); };
63 EnumerateHeaders("foo: bar" CRLF, IsBar); check(AllIsWell);
64 EnumerateHeaders("foo: bar \t" CRLF, IsBar); check(AllIsWell);
65 EnumerateHeaders("foo:\tbar " CRLF, IsBar); check(AllIsWell);
66 EnumerateHeaders("foo:bar " CRLF, IsBar); check(AllIsWell);
67 EnumerateHeaders("foo:bar" CRLF "!", IsBar); check(AllIsWell);
68 EnumerateHeaders("foo:bar" CRLF " ", IsBar); check(AllIsWell);
69 EnumerateHeaders("foo:bar" CRLF "n:ej", IsBar); check(AllIsWell);
70
71 check(CrudeToInt(FAnsiStringView("")) < 0);
72 check(CrudeToInt(FAnsiStringView("X")) < 0);
73 check(CrudeToInt(FAnsiStringView("/")) < 0);
74 check(CrudeToInt(FAnsiStringView(":")) < 0);
75 check(CrudeToInt(FAnsiStringView("-1")) < -1);
76 check(CrudeToInt(FAnsiStringView("0")) == 0);
77 check(CrudeToInt(FAnsiStringView("9")) == 9);
78 check(CrudeToInt(FAnsiStringView("493")) == 493);
79
80 check(CrudeToInt<16>(FAnsiStringView("56")) == 0x56);
88 check(CrudeToInt<16>(FAnsiStringView("49e")) == 0x49e);
89 check(CrudeToInt<16>(FAnsiStringView("aBcD")) == 0xabcd);
90 check(CrudeToInt<16>(FAnsiStringView("eEeE")) == 0xeeee);
91
92 FUrlOffsets UrlOut;
93
94 check(ParseUrl("", UrlOut) == -1);
95 check(ParseUrl("abc://asd/", UrlOut) == -1);
96 check(ParseUrl("http://", UrlOut) == -1);
97 check(ParseUrl("http://:/", UrlOut) == -1);
98 check(ParseUrl("http://@:/", UrlOut) == -1);
99 check(ParseUrl("http://foo:ba:r/", UrlOut) == -1);
100 check(ParseUrl("http://foo@ba:r/", UrlOut) == -1);
101 check(ParseUrl("http://foo@ba:r", UrlOut) == -1);
102 check(ParseUrl("http://foo@ba:/", UrlOut) == -1);
103 check(ParseUrl("http://foo@ba@9/", UrlOut) == -1);
104 check(ParseUrl("http://@ba:9/", UrlOut) == -1);
105 check(ParseUrl(
106 "http://zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz"
107 "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz"
108 "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz"
109 "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz"
110 "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz.com",
111 UrlOut) == -1);
112
113 check(ParseUrl("http://ab-c.com/", UrlOut) > 0);
114 check(ParseUrl("http://a@bc.com/", UrlOut) > 0);
115 check(ParseUrl("https://abc.com", UrlOut) > 0);
116 check(ParseUrl("https://abc.com:999", UrlOut) > 0);
117 check(ParseUrl("https://abc.com:999/", UrlOut) > 0);
118 check(ParseUrl("https://foo:bar@abc.com:999", UrlOut) > 0);
119 check(ParseUrl("https://foo:bar@abc.com:999/", UrlOut) > 0);
120 check(ParseUrl("https://foo_bar@abc.com:999", UrlOut) > 0);
121 check(ParseUrl("https://foo_bar@abc.com:999/", UrlOut) > 0);
122
123 for (int32 i : { 0x10, 0x20, 0x40, 0x7f, 0xff })
124 {
125 char Url[] = "http://sweden.denmark.finland.norway.iceland:123";
126 char Buffer[512];
127 std::memset(Buffer, i, sizeof(Buffer));
128 std::memcpy(Buffer, Url, sizeof(Url) - 1);
129 check(ParseUrl(FAnsiStringView(Buffer, sizeof(Url) - 1), UrlOut) > 0);
130 check(UrlOut.Port.Get(Url) == "123");
131 }
132
133 FAnsiStringView Url = "http://abc:123@bc.com:999/";
134 check(ParseUrl(Url, UrlOut) > 0);
135 check(UrlOut.SchemeLength == 4);
136 check(UrlOut.UserInfo.Get(Url) == "abc:123");
137 check(UrlOut.HostName.Get(Url) == "bc.com");
138 check(UrlOut.Port.Get(Url) == "999");
139 check(UrlOut.Path == 25);
140#undef CRLF
141
142 static const char* OutcomeMsg = "\x4d\x52";
143 check(FOutcome::Error(OutcomeMsg, -5).IsOk() == false);
144 check(FOutcome::Error(OutcomeMsg, -5).IsWaiting() == false);
145 check(FOutcome::Error(OutcomeMsg, -5).IsError());
146 check(FOutcome::Error(OutcomeMsg, -5).GetErrorCode() == -5);
147 check(FOutcome::Error(OutcomeMsg, 5).GetErrorCode() == 5);
148 check(FOutcome::Error(OutcomeMsg, -5).GetMessage() == OutcomeMsg);
149
150 check(FOutcome::Ok( 0).IsOk());
151 check(FOutcome::Ok(-13).IsWaiting() == false);
152 check(FOutcome::Ok( 13).IsError() == false);
153
154 check(FOutcome::Waiting().IsOk() == false);
156 check(FOutcome::Waiting().IsError() == false);
157 check(FOutcome::Waiting().IsWaitData() == true);
158 check(FOutcome::Waiting().IsWaitBuffer() == false);
159 check(FOutcome::Waiting().IsWaitStream() == false);
160
161 check(FOutcome::WaitBuffer().IsWaitData() == false);
162 check(FOutcome::WaitBuffer().IsWaitBuffer() == true);
163 check(FOutcome::WaitBuffer().IsWaitStream() == false);
164
165 check(FOutcome::WaitStream().IsWaitData() == false);
166 check(FOutcome::WaitStream().IsWaitBuffer() == false);
167 check(FOutcome::WaitStream().IsWaitStream() == true);
168}
169
171static void ThrottleTest(FAnsiStringView TestUrl)
172{
173 enum { TheMax = 0x7fff'fffful };
174 check(FThrottler().GetAllowance() >= TheMax);
175
176 FThrottler Throttler;
177 uint64 OneSecond = Throttler.CycleFreq;
178
179 // timing test
181 for (uint32 SizeKiB : { 64, 128, 192 })
182 {
183 const uint32 ThrottleKiB = 64;
184
186 Url << TestUrl;
187 Url << (SizeKiB << 10);
188
191
192 FRequest Request = Loop.Request("GET", Url).Accept("*/*");
193 Loop.Send(MoveTemp(Request), [&] (const FTicketStatus& Status) {
194 check(Status.GetId() != FTicketStatus::EId::Error);
195 if (Status.GetId() == FTicketStatus::EId::Response)
196 {
197 Status.GetResponse().SetDestination(&RecvData);
198 }
199 });
200
201 int32 Timeout = -1;
202 if (SizeKiB < 128) Timeout = 123;
203 if (SizeKiB > 128) Timeout = 4567;
204
206 while (Loop.Tick(Timeout));
208 Time /= OneSecond;
209
210 // It's dangerous stuff testing elapsed time you know. The +1 is because
211 // throttling assumes one second has already passed when initialised.
212#if PLATFORM_WINDOWS
213 check(Time + 1 == (SizeKiB / ThrottleKiB));
214#endif
215
217 }
218}
219
220#if IAS_HTTP_HAS_OPENSSL
221
223static void TlsLoadRootCerts()
224{
225#if !defined(NO_UE_INCLUDES)
227 FString PemPath = FPaths::EngineDir() / TEXT("Content/Certificates/ThirdParty/cacert.pem");
228 FArchive* Reader = Ifm.CreateFileReader(*PemPath);
229
230 uint32 Size = uint32(Reader->TotalSize());
232 FMutableMemoryView PemView = PemData.GetMutableView();
233 Reader->Serialize(PemView.GetData(), Size);
234
235 FCertRoots CaRoots(PemData.GetView());
237
238 delete Reader;
239#endif // NO_UE_INCLUDES
240}
241
243static void TlsTest()
244{
245 FEventLoop Loop;
246
247 auto WaitForLoopIdle = [&] {
248 for (; Loop.Tick(-1); FPlatformProcess::SleepNoStats(0.02f));
249 };
250
251 auto OkSink = [Dest=FIoBuffer()] (const FTicketStatus& Status) mutable {
252 check(Status.GetId() != FTicketStatus::EId::Error);
253 if (Status.GetId() == FTicketStatus::EId::Response)
254 {
255 FResponse& Response = Status.GetResponse();
257 Response.SetDestination(&Dest);
258 return;
259 }
260 check(Status.GetId() == FTicketStatus::EId::Content);
261 };
262
263 static const ANSICHAR* Url = "http://epicgames.com";
264
265 {
266 FEventLoop::FRequestParams RequestParams = { .bAutoRedirect = true };
267 FRequest Request = Loop.Request("HEAD", Url, &RequestParams);
268 Loop.Send(MoveTemp(Request), OkSink);
270 }
271
272 {
273 FCertRoots NotACert(FMemoryView("493", 3));
274 check(NotACert.IsValid() == false);
275 }
276}
277
278#endif // IAS_HTTP_HAS_OPENSSL
279
281static void RedirectTest(const ANSICHAR* TestHost, FCertRootsRef VerifyCert)
282{
283 FEventLoop Loop;
284
285 auto WaitForLoopIdle = [&] {
286 for (; Loop.Tick(-1); FPlatformProcess::SleepNoStats(0.02f));
287 };
288
289 FEventLoop::FRequestParams RequestParams = {
290 .bAutoRedirect = true,
291 };
292
293 enum ReTyp { ReAbs, ReAbsTls, ReRel, ReRelTls };
294 enum { RecvDataSize = 48 };
295
297 auto BuildUrl = [&] (ReTyp Typ, uint32 Code) -> const auto&
298 {
299 bool bTls = (Typ & 1);
300 Builder.Reset();
301 Builder << ((bTls) ? "https://" : "http://");
302 Builder << TestHost;
303 Builder << ":" << (bTls ? 4939 : 9493);
304 Builder << "/redirect";
305 Builder << ((Typ <= ReAbsTls) ? "/abs/" : "/rel/");
306 Builder << Code;
307 Builder << "/data/" << uint32(RecvDataSize);
308 return Builder;
309 };
310
311 UPTRINT SinkParam = 0xaa'493'493'493'493'bbull;
312
314 auto OkSink = [Dest=FIoBuffer(), SinkParam, &RecvCount] (const FTicketStatus& Status) mutable {
315 check(Status.GetParam() == SinkParam);
316 check(Status.GetId() != FTicketStatus::EId::Error);
317 if (Status.GetId() == FTicketStatus::EId::Response)
318 {
319 FResponse& Response = Status.GetResponse();
320 check(Response.GetStatusCode() == 200);
321 Response.SetDestination(&Dest);
322 return;
323 }
324 check(Status.GetId() == FTicketStatus::EId::Content);
325 RecvCount += uint32(Dest.GetSize());
326 };
327
328 uint32 TestCodes[] = { 301, 302, 307, 308 };
329
330 for (auto ReTest : { ReAbs, ReAbsTls, ReRel, ReRelTls })
331 {
332#if !IAS_HTTP_HAS_OPENSSL
333 if (ReTest & 1)
334 {
335 continue;
336 }
337#endif
338
339 RequestParams.VerifyCert = (ReTest & 1) ? VerifyCert : 0;
340 RecvCount = 0;
341 for (uint32 Code : TestCodes)
342 {
343 FRequest Request = Loop.Get(BuildUrl(ReTest, Code), &RequestParams);
344 if (Code > TestCodes[1])
345 {
346 Request.Header("TestCodeHeader", "Header-Of-Test-Codes");
347 }
348 Loop.Send(MoveTemp(Request), OkSink, SinkParam);
349 }
351 check(RecvCount == RecvDataSize * UE_ARRAY_COUNT(TestCodes));
352 }
353
354 RequestParams = FEventLoop::FRequestParams();
355 RequestParams.bAutoRedirect = true;
356
357 for (auto ReTest : { ReAbs, ReAbsTls, ReRel, ReRelTls })
358 {
359#if !IAS_HTTP_HAS_OPENSSL
360 if (ReTest & 1)
361 {
362 continue;
363 }
364#endif
365
366 FConnectionPool::FParams Params;
367 Params.SetHostFromUrl(BuildUrl(ReTest, 0));
368 Params.VerifyCert = (ReTest & 1) ? VerifyCert : 0;
369 Params.ConnectionCount = 4;
370 FConnectionPool Pool(Params);
371
372 RecvCount = 0;
374 for (uint32 TestCount : { 4, 267, 55, 17, 1024, 13, 26, 39, 52, 493 })
375 {
378 Path << "/redirect/abs/307/data/";
379 Path << TestCount;
380 Loop.Send(Loop.Get(Path.ToString(), Pool, &RequestParams), OkSink, SinkParam);
381 }
384 }
385}
386
388static void ChunkedTest(const ANSICHAR* TestHost)
389{
390 FEventLoop Loop;
391
392 auto WaitForLoopIdle = [&]
393 {
394 while (Loop.Tick(0))
395 ;
396 };
397
399
400 // TestServer proxy doesn't support chunked transfer so find the actual httpd
401 int32 HttpdPort = -1;
402 Url << "http://" << TestHost << ":9493/port";
403 Loop.Send(Loop.Get(Url), [&HttpdPort, Dest=FIoBuffer()] (const FTicketStatus& Status) mutable
404 {
405 if (Status.GetId() == FTicketStatus::EId::Response)
406 {
407 Status.GetResponse().SetDestination(&Dest);
408 return;
409 }
410
411 check(Status.GetId() == FTicketStatus::EId::Content);
412 HttpdPort = int32(CrudeToInt({ (char*)Dest.GetView().GetData(), int32(Dest.GetSize()) }));
413 });
415 check(HttpdPort > -1);
416
418 {
419 Url.Reset();
420 Url << "http://" << TestHost << ":" << HttpdPort << "/chunked/" << PayloadSize << UrlSuffix;
421 return Url;
422 };
423
424 struct FTestState
425 {
426 int32 Size = 0;
427 uint32 Hash = 0x493;
428 uint32 ExpectedHash = 0;
429 int32 ExpectedSize = -1;
430 };
431 auto ChunkedSink = [State=FTestState(), Dest=FIoBuffer()] (const FTicketStatus& Status) mutable
432 {
433 if (Status.GetId() == FTicketStatus::EId::Response)
434 {
435 FResponse& Response = Status.GetResponse();
437 check(Response.GetStatusCode() == 200);
438
439 State.ExpectedHash = uint32(CrudeToInt(Response.GetHeader("X-TestServer-Hash")));
440 State.ExpectedSize = uint32(CrudeToInt(Response.GetHeader("X-TestServer-Size")));
441
442 uint32 DestSize = ((State.ExpectedHash & 0x3f) / 7) * 67;
443 Dest = FIoBuffer(DestSize);
444
445 Response.SetDestination(&Dest);
446 return;
447 }
448
449 check(Status.GetId() == FTicketStatus::EId::Content);
450
451 FMemoryView View = Dest.GetView();
452 State.Size += uint32(View.GetSize());
453 for (uint32 i = 0, n = uint32(View.GetSize()); i < n; ++i)
454 {
455 uint8 c = ((const uint8*)(View.GetData()))[i];
456 State.Hash = (State.Hash + c) * 0x493;
457 }
458
459 if (View.GetSize() == 0)
460 {
461 check(State.Hash == State.ExpectedHash);
462 check(State.Size == State.ExpectedSize);
463 }
464 };
465
466 // General soak test
468 "",
469 // "/ext", /* chunk-ext support was removed */
470 };
472 {
473 for (uint32 Mixer : { 1, 2, 3, 17, 71, 4931, 0xa9e })
474 {
475 for (uint32 SizeToGet : { 4,8,32,64,1,2,3,5,7,11,13,17,19,41,43,47,59,67,71,83,89,103,109 })
476 {
477 BuildUrl(SizeToGet * Mixer, UrlSuffix);
478 FRequest Request = Loop.Get(Url);
479 Loop.Send(MoveTemp(Request), ChunkedSink);
480 }
482 }
483 }
484
485 // Rudimentary coverage for transfers with trailing headers.
487 auto ExpectError = [&ErrorMarks, Dest=FIoBuffer()] (const FTicketStatus& Status) mutable
488 {
489 if (Status.GetId() == FTicketStatus::EId::Response)
490 {
491 FResponse& Response = Status.GetResponse();
492 Response.SetDestination(&Dest);
493 return;
494 }
495
496 if (Status.GetId() != FTicketStatus::EId::Error)
497 {
498 return;
499 }
500
501 FAnsiStringView Reason = Status.GetError().Reason;
502 ErrorMarks |= Reason.Contains("ERRTRAIL", ESearchCase::CaseSensitive) ? 1 : 0;
503 ErrorMarks |= Reason.Contains("ERRNOCHUNK", ESearchCase::CaseSensitive) ? 2 : 0;
504 ErrorMarks |= Reason.Contains("ERREXT", ESearchCase::CaseSensitive) ? 4 : 0;
505 };
506 ErrorMarks = 0;
507 BuildUrl(16 << 10, "/trailer");
508 Loop.Send(Loop.Get(Url), ExpectError);
510 check(ErrorMarks == 1);
511
512 // Chunk extensions are not supported
513 {
514 ErrorMarks = 0;
515 BuildUrl(493, "/ext");
516 Loop.Send(Loop.Get(Url), ExpectError);
518 check(ErrorMarks == 4);
519 }
520
521 // Disabling of chunked transfers
522 {
523 ErrorMarks = 0;
524 FEventLoop::FRequestParams RequestParams = { .bAllowChunked = false };
525 BuildUrl(16 << 10, "");
526 Loop.Send(Loop.Get(Url, &RequestParams), ExpectError);
528 check(ErrorMarks == 2);
529 }
530}
531
533static void SeedHttp(const ANSICHAR* TestHost, uint32 Seed)
534{
536 Url << "http://" << TestHost << ":9493/seed/" << Seed;
537 FEventLoop Loop;
538 FRequest Request = Loop.Request("GET", Url, nullptr);
539 Loop.Send(MoveTemp(Request), [] (const FTicketStatus&) {});
540 for (; Loop.Tick(-1); FPlatformProcess::SleepNoStats(0.02f));
541}
542
544static void HttpTest(const ANSICHAR* TestHost, FCertRootsRef VerifyCert)
545{
546 const uint32 DefaultPort = (VerifyCert != 0) ? 4939 : 9493;
547
549 auto BuildUrl = [&] (const ANSICHAR* Suffix=nullptr, uint32 Port=0) -> const auto&
550 {
551 Port = Port ? Port : DefaultPort;
552 Ret.Reset();
553 Ret << ((Port == 4939) ? "https://" : "http://");
554 Ret << TestHost;
555 Ret << ":" << Port;
556 return (Suffix != nullptr) ? (Ret << Suffix) : Ret;
557 };
558
559 struct
560 {
561 FIoBuffer Dest;
562 uint64 Hash = 0;
563 } Content[64];
564
565 auto HashSink = [&] (const FTicketStatus& Status) -> FIoBuffer*
566 {
567 check(Status.GetId() != FTicketStatus::EId::Error);
568
569 uint32 Index = Status.GetIndex();
570
571 if (Status.GetId() == FTicketStatus::EId::Response)
572 {
573 FResponse& Response = Status.GetResponse();
575 check(Response.GetStatusCode() == 200);
576 check(Response.GetContentLength() == Status.GetContentLength());
577
578 FAnsiStringView HashView = Response.GetHeader("X-TestServer-Hash");
579 Content[Index].Hash = CrudeToInt(HashView);
581
582 Content[Index].Dest = FIoBuffer();
583 Response.SetDestination(&(Content[Index].Dest));
584 return nullptr;
585 }
586
587 uint32 ReceivedHash = 0x493;
588 FMemoryView ContentView = Content[Index].Dest.GetView();
589 check(ContentView.GetSize() == Status.GetContentLength());
590 for (uint32 i = 0; i < Status.GetContentLength(); ++i)
591 {
592 uint8 c = ((const uint8*)(ContentView.GetData()))[i];
593 ReceivedHash = (ReceivedHash + c) * 0x493;
594 }
595 check(Content[Index].Hash == ReceivedHash);
596 Content[Index].Hash = 0;
597
598 return nullptr;
599 };
600
601 auto NullSink = [] (const FTicketStatus&) {};
602
603 auto NoErrorSink = [&] (const FTicketStatus& Status)
604 {
605 check(Status.GetId() != FTicketStatus::EId::Error);
606 if (Status.GetId() != FTicketStatus::EId::Response)
607 {
608 return;
609 }
610
611 uint32 Index = Status.GetIndex();
612
613 FResponse& Response = Status.GetResponse();
614 Content[Index].Dest = FIoBuffer();
615 Response.SetDestination(&(Content[Index].Dest));
616 };
617
618 FEventLoop Loop;
619 volatile bool LoopStop = false;
620 volatile bool LoopTickDelay = false;
621 auto LoopTask = UE::Tasks::Launch(TEXT("IasHttpTest.Loop"), [&] () {
622 uint32 DelaySeed = 493;
623 while (!LoopStop)
624 {
625 while (Loop.Tick())
626 {
627 if (!LoopTickDelay)
628 {
629 continue;
630 }
631
632 float DelayFloat = float(DelaySeed % 75) / 1000.0f;
633 FPlatformProcess::SleepNoStats(DelayFloat);
634 DelaySeed *= 0xa93;
635 }
636
637 FPlatformProcess::SleepNoStats(0.005f);
638 }
639 });
640
641 auto WaitForLoopIdle = [&Loop] ()
642 {
643 FPlatformProcess::SleepNoStats(0.25f);
644 while (!Loop.IsIdle())
645 {
646 FPlatformProcess::SleepNoStats(0.03f);
647 }
648 };
649
650 FEventLoop::FRequestParams ReqParamObj = { .VerifyCert = VerifyCert };
651 const FEventLoop::FRequestParams* ReqParams = nullptr;
652 if (VerifyCert != 0)
653 {
655 }
656
657 // unused request
658 {
659 FRequest Request = Loop.Request("GET", BuildUrl("/data"));
660 }
661
662 // foundational
663 {
664 FRequest Request = Loop.Request("GET", BuildUrl("/data/67"), ReqParams);
665 Request.Accept(EMimeType::Json);
666 Loop.Send(MoveTemp(Request), HashSink);
668 }
669
670 // convenience
671 {
672 FRequest Request = Loop.Get(BuildUrl("/data"), ReqParams).Accept(EMimeType::Json);
673
674 Loop.Send(Loop.Get(BuildUrl("/data"), ReqParams).Accept(EMimeType::Json), HashSink),
675 Loop.Send(MoveTemp(Request), HashSink),
676 Loop.Send(Loop.Get("http://epicgames.com"), NoErrorSink),
677
679 }
680
681 // convenience
682 {
683 FRequest Request = Loop.Get(BuildUrl("/data"), ReqParams).Accept(EMimeType::Json);
684 Loop.Send(MoveTemp(Request), HashSink);
686 }
687
688 // pool
689 for (uint16 i = 1; i < 64; ++i)
690 {
691 FConnectionPool::FParams Params;
692 Params.SetHostFromUrl(BuildUrl());
693 Params.VerifyCert = VerifyCert;
694 Params.ConnectionCount = (i % 2) + 1;
695 FConnectionPool Pool(Params);
696 for (int32 j = 0; j < i; ++j)
697 {
699 Path << "/data?pool=" << i << "x" << j;
700 FRequest Request = Loop.Get(Path, Pool);
701 Loop.Send(MoveTemp(Request), HashSink);
702 }
704 }
705
706 // head barage
707 {
708 FConnectionPool::FParams Params;
709 Params.SetHostFromUrl(BuildUrl());
710 Params.VerifyCert = VerifyCert;
711 Params.ConnectionCount = 1;
712 FConnectionPool Pool(Params);
713 for (int32 i = 0; i < 61; ++i)
714 {
716 Path << "/data?head";
717 FRequest Request = Loop.Request("HEAD", Path, Pool);
718 Loop.Send(MoveTemp(Request), NullSink);
719 }
721 }
722
723 // fatal timeout
724 for (int32 i = 0; i < 14; ++i)
725 {
726 bool bExpectFailTimeout = !!(i & 1);
727 auto Sink = [bExpectFailTimeout, Dest=FIoBuffer()] (const FTicketStatus& Status) mutable
728 {
729 if (Status.GetId() == FTicketStatus::EId::Response)
730 {
731 FResponse& Response = Status.GetResponse();
732 Response.SetDestination(&Dest);
733 return;
734 }
735
736 check(Status.GetId() == FTicketStatus::EId::Error);
737
738 const char* Reason = Status.GetError().Reason;
739 bool IsFailTimeout = (FCStringAnsi::Strstr(Reason, "FailTimeout") != nullptr);
741 };
742
743 auto ErrorSink = [] (const FTicketStatus& Status)
744 {
745 check(Status.GetId() == FTicketStatus::EId::Error);
746 };
747
748 FConnectionPool::FParams Params;
749 Params.SetHostFromUrl(BuildUrl());
750 Params.VerifyCert = VerifyCert;
751 FConnectionPool Pool(Params);
752
753 FEventLoop Loop2;
754 Loop2.Send(Loop2.Get("/data?stall=1", Pool), Sink);
755
756 // Requests are pipelined. The second one will get sent during the stall so
757 // we expect it to fail. The subsequent ones are expected to succeed.
758 Loop2.Send(Loop2.Get("/data", Pool), ErrorSink);
759 Loop2.Send(Loop2.Get("/data", Pool), HashSink);
760 Loop2.Send(Loop2.Get("/data", Pool), HashSink);
761
762 int32 PollTimeoutMs = -1;
764 {
765 Loop2.SetFailTimeout(1000);
766
767 if ((i & 3) == 1)
768 {
769 PollTimeoutMs = 1000;
770 }
771 }
772 while (Loop2.Tick(PollTimeoutMs));
773
774 Loop2.Send(Loop2.Get("/data/23", Pool), NoErrorSink);
775 while (Loop2.Tick(PollTimeoutMs));
776 }
777
778 // no connect
779 {
780 FRequest Requests[] = {
781 Loop.Request("GET", BuildUrl(nullptr, 10930)),
782 Loop.Request("GET", "http://thisdoesnotexistihope/"),
783 };
784 Loop.Send(MoveTemp(Requests[0]), NullSink);
785 Loop.Send(MoveTemp(Requests[1]), NullSink);
787 }
788
789 // head and large requests
790 {
791 auto MixTh = [Th=uint32(0)] () mutable { return (Th = (Th * 75) + 74) & 255; };
792
793 char AsciiData[257];
794 for (char& c : AsciiData)
795 {
796 c = 0x41 + (MixTh() % 26);
797 c += (MixTh() & 2) ? 0x20 : 0;
798 }
799
800 for (int32 i = 0; (i += 69493) < 2 << 20;)
801 {
802 FRequest Request = Loop.Request("HEAD", BuildUrl("/data"), ReqParams);
803 for (int32 j = i; j > 0;)
804 {
807 Request.Header(Name, Value);
808 j -= Name.Len() + Value.Len();
809
810 }
811
812 Loop.Send(MoveTemp(Request), [] (const FTicketStatus& Status) {
813 if (Status.GetId() == FTicketStatus::EId::Response)
814 {
815 FResponse& Response = Status.GetResponse();
816 check(Response.GetStatusCode() == 431); // "too many headers"
817 }
818 });
819
821 }
822 }
823
824 // stress 1
825 {
826 const uint32 StressLoad = 32;
827
828 struct {
829 const ANSICHAR* Uri;
830 bool Disconnect;
831 } StressUrls[] = {
832 { "/data?slowly=1", false },
833 { "/data?disconnect=1", true },
834 };
835
836 uint64 Errors = 0;
837 auto ErrorSink = [&] (const FTicketStatus& Status)
838 {
839 FTicket Ticket = Status.GetTicket();
840 uint32 Index = uint32(63 - FMath::CountLeadingZeros64(uint64(Ticket)));
841
842 if (Status.GetId() == FTicketStatus::EId::Error)
843 {
844 Errors |= 1ull << Index;
845 return;
846 }
847
848 else if (Status.GetId() == FTicketStatus::EId::Response)
849 {
850 FResponse& Response = Status.GetResponse();
851 Content[Index].Dest = FIoBuffer();
852 Response.SetDestination(&(Content[Index].Dest));
853 return;
854 }
855
856 check(false);
857 };
858
859 for (const auto& [StressUri, ExpectDisconnect] : StressUrls)
860 {
862
863 const auto& StressUrl = BuildUrl(StressUri);
864 for (bool AddDelay : {false, true})
865 {
867 for (FTicket& Ticket : Tickets)
868 {
869 Ticket = Loop.Send(Loop.Get(StressUrl, ReqParams).Header("Accept", "*/*"), Sink);
870 }
871
873
875 }
876
877 LoopTickDelay = false;
878 }
879 }
880
881 // stress 2
882 {
883 const uint32 StressLoad = 3;
884 const uint32 StressTaskCount = 7;
885 static_assert(StressLoad * StressTaskCount <= 32);
886
887 FAnsiStringView Url = BuildUrl("/data");
888
889 auto StressTaskEntry = [&] {
890 for (uint32 i = 0; i < StressLoad; ++i)
891 {
892 FTicket Ticket = Loop.Send(Loop.Get(Url, ReqParams), HashSink);
893 if (!Ticket)
894 {
895 FPlatformProcess::SleepNoStats(0.01f);
896 --i;
897 }
898 }
899 };
900
902 for (auto& StressTask : StressTasks)
903 {
904 StressTask = UE::Tasks::Launch(TEXT("StressTask"), [&] { StressTaskEntry(); });
905 }
906 for (auto& StressTask : StressTasks)
907 {
909 }
910
912 }
913
914 // tamper
915 for (int32 i = 1; i <= 100; ++i)
916 {
918 TamperUrl << "/data?tamper=" << i;
919 FAnsiStringView Url = BuildUrl(TamperUrl.ToString());
920
921 for (int j = 0; j < 13; ++j)
922 {
923 FRequest Request = Loop.Request("GET", Url, ReqParams);
924 Loop.Send(MoveTemp(Request), NullSink);
925 }
926
928 }
929
930 LoopStop = true;
931 LoopTask.Wait();
932
933 check(Loop.IsIdle());
934
935#if IS_PROGRAM
936 if (VerifyCert == 0)
937 {
938 ThrottleTest(BuildUrl("/data/"));
939 }
940#endif
941
942 // pre-generated headers
943 // request-with-body
944 // proxy
945 // gzip / deflate
946 // loop multi-req.
947 // url auth credentials
948 // transfer-file / splice / sendfile
949 // (header field parser)
950 // (form-data)
951 // (cookies)
952 // (cache)
953 // (websocket)
954 // (ipv6)
955 // (utf-8 host names)
956}
957
960{
961#if PLATFORM_WINDOWS
963 if (WSAStartup(MAKEWORD(2, 2), &WsaData) == 0x0a9e0493)
964 return;
966#endif
967
968 MiscTest();
969
970#if IAS_HTTP_HAS_OPENSSL
972 {
974 CaUrl << "http://" << TestHost << ":9493/ca";
975
977
979 FRequest Request = Loop.Get(CaUrl);
980 Request.Header("X-TestServer-M", "Girders");
981 Request.Header("X-TestServer-R", "RedRigs");
982 Loop.Send(MoveTemp(Request), [&] (const FTicketStatus& Status) {
983 check(Status.GetId() != FTicketStatus::EId::Error);
984
985 if (Status.GetId() == FTicketStatus::EId::Response)
986 {
987 FResponse& Response = Status.GetResponse();
989 }
990 });
991 for (; Loop.Tick(-1); FPlatformProcess::SleepNoStats(0.02f));
992
994 }
995 FCertRootsRef TestServerCertRef = FCertRoots::Explicit(TestServerCaChain);
996#else
997 FCertRootsRef TestServerCertRef = FCertRoots::NoTls();
998#endif // IAS_HTTP_HAS_OPENSSL
999
1000 SeedHttp(TestHost, Seed);
1001 HttpTest(TestHost, FCertRoots::NoTls());
1002 ChunkedTest(TestHost);
1003 RedirectTest(TestHost, TestServerCertRef);
1004
1005#if IAS_HTTP_HAS_OPENSSL
1006 HttpTest(TestHost, TestServerCertRef);
1008 TlsTest();
1009#endif
1010}
1011
1012#endif // !SHIP|TEST
1013
1014} // namespace UE::IoStore::HTTP
#define check(expr)
Definition AssertionMacros.h:314
#define TEXT(x)
Definition Platform.h:1272
FPlatformTypes::int64 int64
A 64-bit signed integer.
Definition Platform.h:1127
FPlatformTypes::int32 int32
A 32-bit signed integer.
Definition Platform.h:1125
FPlatformTypes::UPTRINT UPTRINT
An unsigned integer the same size as a pointer.
Definition Platform.h:1146
FPlatformTypes::ANSICHAR ANSICHAR
An ANSI character. Normally a signed type.
Definition Platform.h:1131
FPlatformTypes::uint64 uint64
A 64-bit unsigned integer.
Definition Platform.h:1117
UE_FORCEINLINE_HINT TSharedRef< CastToType, Mode > StaticCastSharedRef(TSharedRef< CastFromType, Mode > const &InSharedRef)
Definition SharedPointer.h:127
auto Response
Definition ExternalRpcRegistry.cpp:598
TMemoryView< const void > FMemoryView
Definition MemoryFwd.h:11
#define ON_SCOPE_EXIT
Definition ScopeExit.h:73
TStringView< ANSICHAR > FAnsiStringView
Definition StringFwd.h:46
#define CRLF
#define UE_ARRAY_COUNT(array)
Definition UnrealTemplate.h:212
UE_INTRINSIC_CAST UE_REWRITE constexpr std::remove_reference_t< T > && MoveTemp(T &&Obj) noexcept
Definition UnrealTemplate.h:520
uint32 Size
Definition VulkanMemory.cpp:4034
uint8_t uint8
Definition binka_ue_file_header.h:8
uint16_t uint16
Definition binka_ue_file_header.h:7
uint32_t uint32
Definition binka_ue_file_header.h:6
Definition Archive.h:1208
virtual void Serialize(void *V, int64 Length)
Definition Archive.h:1689
virtual int64 TotalSize()
Definition Archive.h:155
Definition IoBuffer.h:15
static CORE_API FString EngineDir()
Definition Paths.cpp:199
Definition FileManager.h:57
static CORE_API IFileManager & Get()
Definition FileManagerGeneric.cpp:1072
constexpr DataType * GetData() const
Definition MemoryView.h:68
constexpr uint64 GetSize() const
Definition MemoryView.h:74
void Reset()
Definition StringBuilder.h:190
Definition StringBuilder.h:509
bool Contains(ViewType Search, ESearchCase::Type SearchCase=ESearchCase::CaseSensitive) const
Definition StringView.h:394
Definition Client.h:62
static UE_API void SetDefault(FCertRoots &&CertRoots)
Definition Peer.inl:393
Definition Client.h:227
UE_API void Throttle(uint32 KiBPerSec)
Definition Loop.inl:1181
static FOutcome Ok(int32 Result=0)
Definition Misc.inl:113
static FOutcome WaitStream(int32 Result=0)
Definition Misc.inl:141
static FOutcome Error(const char *Message, int32 Code=-1)
Definition Misc.inl:151
static FOutcome WaitBuffer(int32 Result=0)
Definition Misc.inl:131
static FOutcome Waiting(int32 Result=0)
Definition Misc.inl:122
Definition Client.h:126
Definition Client.h:150
UE_API void SetDestination(FIoBuffer *Buffer)
Definition Api.inl:149
Definition Misc.inl:502
Definition Client.h:195
UE_API FResponse & GetResponse() const
Definition Api.inl:196
UE_API EId GetId() const
Definition Api.inl:160
bool Wait(FTimespan Timeout) const
Definition Task.h:76
bool IsOk(int32 StatusCode)
Definition IHttpResponse.h:38
@ Suffix
Definition MirrorDataTable.h:34
@ CaseSensitive
Definition CString.h:23
const FColor Path(255, 255, 255)
const TCHAR * Name
Definition OodleDataCompression.cpp:30
FUniformParams Params
Definition MeshPaintVirtualTexture.cpp:162
const SIZE_T PayloadSize
Definition UDPPing.cpp:1293
State
Definition PacketHandler.h:88
Definition Client.h:20
IOSTOREHTTPCLIENT_API void IasHttpTest(const ANSICHAR *TestHost="localhost", uint32 Seed=493)
Definition Test.inl:959
UPTRINT FCertRootsRef
Definition Client.h:56
uint64 FTicket
Definition Client.h:57
TFunction< void(const FTicketStatus &)> FTicketSink
Definition Client.h:223
TTask< TInvokeResult_T< TaskBodyType > > Launch(const TCHAR *DebugName, TaskBodyType &&TaskBody, ETaskPriority Priority=ETaskPriority::Normal, EExtendedTaskPriority ExtendedPriority=EExtendedTaskPriority::None, ETaskFlags Flags=ETaskFlags::None)
Definition Task.h:266
U16 Index
Definition radfft.cpp:71
static uint64 Cycles64()
Definition AndroidPlatformTime.h:34
CORE_API FString ToString() const
Definition Color.cpp:584
static UE_FORCEINLINE_HINT const CharType * Strstr(const CharType *String, const CharType *Find)
Definition CString.h:1066