UDocumentation UE5.7 10.02.2026 (Source)
API documentation for Unreal Engine 5.7
Peer.inl
Go to the documentation of this file.
1// Copyright Epic Games, Inc. All Rights Reserved.
2
3#pragma once
4
5#if !defined(IAS_HTTP_HAS_OPENSSL)
6# define IAS_HTTP_HAS_OPENSSL 0
7#endif
8
9#if IAS_HTTP_HAS_OPENSSL
10# if defined(IAS_HTTP_EXPLICIT_VERIFY_TIME)
11# if !IAS_HTTP_EXPLICIT_VERIFY_TIME
12# error Either define this to >=1 or not at all
13# endif
14# include <HAL/PlatformTime.h>
15# include <ctime>
16# endif
17# include <openssl/engine.h>
18# include <openssl/err.h>
19# include <openssl/ssl.h>
20#endif
21
22namespace UE::IoStore::HTTP
23{
24
26static FCertRoots GDefaultCertRoots;
27
29{
30 static const FCertRootsRef None = 0;
31 static const FCertRootsRef Default = ~0ull;
32};
33
34
35
37class FPeer
38{
39public:
40 FPeer() = default;
42 FWaitable GetWaitable() const { return Socket.GetWaitable(); }
43 FOutcome Send(const char* Data, int32 Size) { return Socket.Send(Data, Size); }
44 FOutcome Recv(char* Out, int32 MaxSize) { return Socket.Recv(Out, MaxSize); }
45 bool IsValid() const { return Socket.IsValid(); }
46
47private:
48 FSocket Socket;
49};
50
56
57
58
59#if IAS_HTTP_HAS_OPENSSL
60
62static void Ssl_ContextDestroy(UPTRINT Handle)
63{
64 auto* Context = (SSL_CTX*)Handle;
66}
67
69static UPTRINT Ssl_ContextCreate(FMemoryView PemData)
70{
71 if (static bool InitOnce = false; !InitOnce)
72 {
73 // While OpenSSL will lazily initialise itself, the defaults used will fail
74 // initialisation on some platforms. So we have a go here. We do not register
75 // anything for clean-up as we do not know if anyone else has done so.
77 OPENSSL_init_ssl(InitOpts, nullptr);
78 InitOnce = true;
79 }
80
81 auto* Method = TLS_client_method();
82 SSL_CTX* Context = SSL_CTX_new(Method);
83 checkf(Context != nullptr, TEXT("ERR_get_error() == %d"), ERR_get_error());
84
86
87 const void* Data = PemData.GetData();
88 uint32 Size = uint32(PemData.GetSize());
89 BIO* Bio = BIO_new_mem_buf(Data, Size);
90
91 uint32 NumAdded = 0;
92 while (true)
93 {
94 X509* FiveOhNine = PEM_read_bio_X509(Bio, nullptr, 0, nullptr);
95 if (FiveOhNine == nullptr)
96 {
97 break;
98 }
99
101 int32 Result = X509_STORE_add_cert(Store, FiveOhNine);
102 NumAdded += (Result == 1);
103
105 }
106
107 BIO_free(Bio);
108
109 if (NumAdded == 0)
110 {
112 return 0;
113 }
114
115#if defined(IAS_HTTP_EXPLICIT_VERIFY_TIME)
117 {
119 std::tm Utc = {};
120 FPlatformTime::UtcTime(
121 Utc.tm_year, Utc.tm_mon,
122 AliasTown,
123 Utc.tm_mday, Utc.tm_hour, Utc.tm_min,
125 );
126
127 Utc.tm_year -= 1900;
128 Utc.tm_mon -= 1;
129
130 time_t Now = std::mktime(&Utc);
131
133 }
134#endif
135
136 return UPTRINT(Context);
137}
138
140static int32 Ssl_ContextCertNum(UPTRINT Handle)
141{
142 auto* Context = (SSL_CTX*)Handle;
145 return sk_X509_OBJECT_num(Objects);
146}
147
149static int32 Ssl_BioWrite(BIO* Bio, const char* Data, size_t Size, size_t* BytesWritten)
150{
151 *BytesWritten = 0;
153
154 auto* Peer = (FPeer*)BIO_get_data(Bio);
155 FOutcome Outcome = Peer->Send(Data, int32(Size));
156 if (Outcome.IsWaiting())
157 {
159 return 0;
160 }
161
162 if (Outcome.IsError())
163 {
164 return -1;
165 }
166
167 *BytesWritten = Outcome.GetResult();
168 return 1;
169}
170
172static int32 Ssl_BioRead(BIO* Bio, char* Data, size_t Size, size_t* BytesRead)
173{
174 *BytesRead = 0;
176
177 auto* Peer = (FPeer*)BIO_get_data(Bio);
178 FOutcome Outcome = Peer->Recv(Data, int32(Size));
179 if (Outcome.IsWaiting())
180 {
182 return 0;
183 }
184
185 if (Outcome.IsError())
186 {
187 return -1;
188 }
189
190 *BytesRead = Outcome.GetResult();
191 return 1;
192}
193
195static long Ssl_BioControl(BIO*, int Cmd, long, void*)
196{
197 return (Cmd == BIO_CTRL_FLUSH) ? 1 : 0;
198}
199
201static SSL* Ssl_Create(FCertRootsRef Certs, const char* HostName=nullptr)
202{
203 static_assert(OPENSSL_VERSION_NUMBER >= 0x10100000L, "version supporting autoinit required");
204
205 if (Certs == ECertRootsRefType::Default)
206 {
207 Certs = FCertRoots::Explicit(GDefaultCertRoots);
208 check(Certs != 0);
209 }
210 auto* Context = (SSL_CTX*)Certs;
211
212 static BIO_METHOD* BioMethod = nullptr;
213 if (BioMethod == nullptr)
214 {
216 BioMethod = BIO_meth_new(BioId, "IasBIO");
220 }
221
223
224 // SSL_MODE_ENABLE_PARTIAL_WRITE ??!!!
225
226 SSL* Ssl = SSL_new(Context);
228 SSL_set0_rbio(Ssl, Bio);
229 SSL_set0_wbio(Ssl, Bio);
231
232 if (HostName != nullptr)
233 {
234 SSL_set_tlsext_host_name(Ssl, HostName);
235 }
236
237 return Ssl;
238}
239
241static void Ssl_Destroy(SSL* Ssl)
242{
243 SSL_free(Ssl);
244}
245
247static void Ssl_AssociatePeer(SSL* Ssl, FPeer* Peer)
248{
249 BIO* Bio = SSL_get_rbio(Ssl);
250 check(Bio == SSL_get_wbio(Ssl));
251 BIO_set_data(Bio, Peer);
252}
253
255static void Ssl_SetupAlpn(SSL* Ssl, int32 HttpVersion)
256{
258 switch (HttpVersion)
259 {
260 case 1: AlpnProtos = "\x08" "http/1.1"; break;
261 case 2: AlpnProtos = "\x02" "h2"; break;
262 default: break;
263 }
264
265 check(!AlpnProtos.IsEmpty());
266 SSL_set_alpn_protos(Ssl, (uint8*)(AlpnProtos.GetData()), int32(AlpnProtos.Len()));
267}
268
270static int32 Ssl_GetProtocolVersion(SSL* Ssl)
271{
272 int32 Proto = 1;
273
274 const char* AlpnProto = nullptr;
277 if (AlpnProto == nullptr)
278 {
279 return Proto;
280 }
281
283 FAnsiStringView Candidates[] = {
284 "http/1.1",
285 "h2",
286 };
287 for (uint32 i = 0; i < UE_ARRAY_COUNT(Candidates); ++i)
288 {
289 const FAnsiStringView& Candidate = Candidates[i];
290 if (AlpnProtoLen != uint32(Candidate.Len()))
291 {
292 continue;
293 }
294
295 if (Candidate != Needle)
296 {
297 continue;
298 }
299
300 Proto = i + 1;
301 break;
302 }
303
304 return Proto;
305}
306
308static FOutcome Ssl_GetOutcome(SSL* Ssl, int32 SslResult, const char* Message="tls error")
309{
312 {
313 return FOutcome::Error(Message, Error);
314 }
315 return FOutcome::Waiting();
316}
317
319static FOutcome Ssl_Handshake(SSL* Ssl)
320{
322 if (Result == 0) return FOutcome::Error("unsuccessful tls handshake");
323 if (Result != 1) return Ssl_GetOutcome(Ssl, Result, "tls handshake error");
324
325 if (Result = SSL_get_verify_result(Ssl); Result != X509_V_OK)
326 {
327 return FOutcome::Error("x509 verification error", Result);
328 }
329
330 return FOutcome::Ok();
331}
332
334static FOutcome Ssl_Write(SSL* Ssl, const char* Data, int32 Size)
335{
336 int32 Result = SSL_write(Ssl, Data, Size);
337 return (Result > 0) ? FOutcome::Ok(Result) : Ssl_GetOutcome(Ssl, Result);
338}
339
341static FOutcome Ssl_Read(SSL* Ssl, char* Out, int32 MaxSize)
342{
343 int32 Result = SSL_read(Ssl, Out, MaxSize);
344 return (Result > 0) ? FOutcome::Ok(Result) : Ssl_GetOutcome(Ssl, Result);
345}
346
347#else
348
349struct SSL;
350static void Ssl_ContextDestroy(...) {}
351static UPTRINT Ssl_ContextCreate(...) { return 0; }
352static int32 Ssl_ContextCertNum(...) { return 0; }
353static SSL* Ssl_Create(...) { return nullptr; }
354static void Ssl_Destroy(...) {}
355static void Ssl_AssociatePeer(...) {}
356static void Ssl_SetupAlpn(...) {}
357static int32 Ssl_GetProtocolVersion(...) { return 1; }
358static FOutcome Ssl_Handshake(...) { return FOutcome::Error("!impl"); }
359static FOutcome Ssl_Write(...) { return FOutcome::Error("!impl"); }
360static FOutcome Ssl_Read(...) { return FOutcome::Error("!impl"); }
361
362#endif // IAS_HTTP_HAS_OPENSSL
363
364
365
368{
369 if (Handle != 0)
370 {
371 Ssl_ContextDestroy(Handle);
372 }
373}
374
377{
378 Handle = Ssl_ContextCreate(PemData);
379}
380
383{
384 if (Handle == 0)
385 {
386 return -1;
387 }
388
389 return Ssl_ContextCertNum(Handle);
390}
391
394{
395 check(GDefaultCertRoots.IsValid() != CertRoots.IsValid());
396 GDefaultCertRoots = MoveTemp(CertRoots);
397}
398
404
410
413{
414 check(CertRoots.IsValid());
415 return CertRoots.Handle;
416}
417
418
419
422 : public FPeer
423{
424public:
425 FTlsPeer() = default;
426 ~FTlsPeer();
427 FTlsPeer(FTlsPeer&& Rhs) { Move(MoveTemp(Rhs)); }
428 FTlsPeer& operator = (FTlsPeer&& Rhs) { return Move(MoveTemp(Rhs)); }
429 FTlsPeer(FSocket InSocket, FCertRootsRef Certs=ECertRootsRefType::None, const char* HostName=nullptr);
430 bool IsUsingTls() const;
432 FOutcome Send(const char* Data, int32 Size);
433 FOutcome Recv(char* Out, int32 MaxSize);
434
435protected:
436 FTlsPeer& Move(FTlsPeer&& Rhs);
437 SSL* Ssl = nullptr;
438};
439
443{
444 if (Certs == ECertRootsRefType::None)
445 {
446 return;
447 }
448
449 Ssl = Ssl_Create(Certs, HostName);
450 Ssl_AssociatePeer(Ssl, this);
451}
452
455{
456 if (Ssl != nullptr)
457 {
458 Ssl_Destroy(Ssl);
459 }
460}
461
464{
465 return (Ssl != nullptr);
466}
467
470{
471 Swap(Ssl, Rhs.Ssl);
472 if (Ssl != nullptr) Ssl_AssociatePeer(Ssl, this);
473 if (Rhs.Ssl != nullptr) Ssl_AssociatePeer(Rhs.Ssl, &Rhs);
474
475 FPeer::operator = (MoveTemp(Rhs));
476
477 return *this;
478}
479
482{
483 if (Ssl == nullptr)
484 {
485 return FOutcome::Ok();
486 }
487
488 return Ssl_Handshake(Ssl);
489}
490
493{
494 if (Ssl == nullptr)
495 {
496 return FPeer::Send(Data, Size);
497 }
498
499 return Ssl_Write(Ssl, Data, Size);
500}
501
504{
505 if (Ssl == nullptr)
506 {
507 return FPeer::Recv(Out, MaxSize);
508 }
509
510 return Ssl_Read(Ssl, Out, MaxSize);
511}
512
513} // namespace UE::IoStore::HTTP
514
515
516
518#include "TransactionTwo.inl"
519#include "TransactionOne.inl"
520
521namespace UE::IoStore::HTTP
522{
523
526 : public FTlsPeer
527{
528public:
536
537 FHttpPeer() = default;
538 ~FHttpPeer();
539 FHttpPeer(FHttpPeer&& Rhs) = delete;
540 FHttpPeer(const FHttpPeer& Rhs) = delete;
542 FHttpPeer& operator = (const FHttpPeer& Rhs) = delete;
543 FHttpPeer(FParams&& Params);
544 uint32 GetVersion() const;
548
549private:
550 void* PeerData = nullptr;
551 uint8 Proto = 0;
552};
553
556: FTlsPeer(MoveTemp(Params.Socket), Params.Certs, Params.HostName)
557, Proto(uint8(Params.HttpVersion))
558{
559 if (Ssl != nullptr)
560 {
561 Ssl_SetupAlpn(Ssl, Proto);
562 }
563}
564
567{
568 switch (Proto)
569 {
570 case 1: GoAwayOne(*this, PeerData); break;
571 case 2: GoAwayTwo(*this, PeerData); break;
572 default: break;
573 }
574}
575
578{
579 Swap(PeerData, Rhs.PeerData);
580 Swap(Proto, Rhs.Proto);
581
583
584 return *this;
585}
586
589{
590 return Proto;
591}
592
595{
596 if (Ssl != nullptr)
597 {
599 {
600 return Outcome;
601 }
602
603 int32 Negotiated = Ssl_GetProtocolVersion(Ssl);
604 if (Negotiated != 1 && Negotiated != 2)
605 {
606 return FOutcome::Error("Unexpected negotiated protocol version", Negotiated);
607 }
608 Proto = uint8(Negotiated);
609
610 /* intentional fallthrough */
611 }
612
614 switch (Proto)
615 {
616 case 1: Outcome = HandshakeOne(*this, PeerData); break;
617 case 2: Outcome = HandshakeTwo(*this, PeerData); break;
618 default: return FOutcome::Error("Unsupported");
619 }
620
621 if (!Outcome.IsOk())
622 {
623 return Outcome;
624 }
625
626 return FOutcome::Ok(Proto);
627}
628
631{
632 switch (Proto)
633 {
634 case 1: return CreateTransactOne(PeerData);
635 case 2: return CreateTransactTwo(PeerData);
636 default: return FTransactRef();
637 }
638}
639
642{
643 switch (Proto)
644 {
645 case 1: return TickOne(*this, PeerData);
646 case 2: return TickTwo(*this, PeerData);
647 default: return FOutcome::Error("Unsupported");
648 }
649}
650
651} // namespace UE::IoStore::HTTP
#define check(expr)
Definition AssertionMacros.h:314
#define checkf(expr, format,...)
Definition AssertionMacros.h:315
#define TEXT(x)
Definition Platform.h:1272
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::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
struct ssl_ctx_st SSL_CTX
Definition ISslCertificateManager.h:11
#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
uint32_t uint32
Definition binka_ue_file_header.h:6
Definition SSL.Build.cs:6
Definition Client.h:62
bool IsValid() const
Definition Client.h:69
UE_API int32 Num() const
Definition Peer.inl:382
UE_API ~FCertRoots()
Definition Peer.inl:367
static UE_API FCertRootsRef Explicit(const FCertRoots &CertRoots)
Definition Peer.inl:412
static UE_API FCertRootsRef NoTls()
Definition Peer.inl:400
static UE_API FCertRootsRef Default()
Definition Peer.inl:406
static UE_API void SetDefault(FCertRoots &&CertRoots)
Definition Peer.inl:393
Definition Peer.inl:527
~FHttpPeer()
Definition Peer.inl:566
FTransactRef Transact()
Definition Peer.inl:630
FOutcome GetPendingTransactId()
Definition Peer.inl:641
uint32 GetVersion() const
Definition Peer.inl:588
FHttpPeer(FHttpPeer &&Rhs)=delete
FOutcome Handshake()
Definition Peer.inl:594
FHttpPeer & operator=(FHttpPeer &&Rhs)
Definition Peer.inl:577
FHttpPeer(const FHttpPeer &Rhs)=delete
Definition Misc.inl:73
static FOutcome Ok(int32 Result=0)
Definition Misc.inl:113
bool IsOk() const
Definition Misc.inl:86
static FOutcome Error(const char *Message, int32 Code=-1)
Definition Misc.inl:151
static FOutcome None()
Definition Misc.inl:80
static FOutcome Waiting(int32 Result=0)
Definition Misc.inl:122
Definition Peer.inl:38
bool IsValid() const
Definition Peer.inl:45
FWaitable GetWaitable() const
Definition Peer.inl:42
FOutcome Send(const char *Data, int32 Size)
Definition Peer.inl:43
FOutcome Recv(char *Out, int32 MaxSize)
Definition Peer.inl:44
Definition Socket.inl:367
FOutcome Recv(char *Dest, uint32 Size)
Definition Socket.inl:506
FOutcome Send(const char *Data, uint32 Size)
Definition Socket.inl:473
FWaitable GetWaitable() const
Definition Socket.inl:374
bool IsValid() const
Definition Socket.inl:373
Definition Peer.inl:423
bool IsUsingTls() const
Definition Peer.inl:463
SSL * Ssl
Definition Peer.inl:437
FOutcome Send(const char *Data, int32 Size)
Definition Peer.inl:492
FOutcome Recv(char *Out, int32 MaxSize)
Definition Peer.inl:503
~FTlsPeer()
Definition Peer.inl:454
FTlsPeer & operator=(FTlsPeer &&Rhs)
Definition Peer.inl:428
FOutcome Handshake()
Definition Peer.inl:481
FTlsPeer(FTlsPeer &&Rhs)
Definition Peer.inl:427
FTlsPeer & Move(FTlsPeer &&Rhs)
Definition Peer.inl:469
Definition TransactionOne.inl:945
Definition Socket.inl:130
Definition HttpServerHttpVersion.h:7
FORCEINLINE void Store(const volatile T *Element, T Value)
Definition Atomic.h:89
Definition Client.h:20
FOutcome HandshakeOne(FTlsPeer &, void *)
Definition TransactionOne.inl:880
EHttpVersion
Definition Client.h:24
UPTRINT FCertRootsRef
Definition Client.h:56
FTransactRef CreateTransactOne(void *&)
Definition TransactionOne.inl:1021
FOutcome TickOne(FTlsPeer &, void *)
Definition TransactionOne.inl:886
FOutcome TickTwo(FTlsPeer &, void *)
Definition TransactionTwo.inl:914
void GoAwayOne(FTlsPeer &, void *)
Definition TransactionOne.inl:892
void GoAwayTwo(FTlsPeer &, void *)
Definition TransactionTwo.inl:915
FTransactRef CreateTransactTwo(void *PeerData)
Definition TransactionOne.inl:1029
FOutcome HandshakeTwo(FTlsPeer &, void *&)
Definition TransactionTwo.inl:913
UE_STRING_CLASS Result(Forward< LhsType >(Lhs), RhsLen)
Definition String.cpp.inl:732
static const FCertRootsRef Default
Definition Peer.inl:31
static const FCertRootsRef None
Definition Peer.inl:30
FSocket Socket
Definition Peer.inl:531
FCertRootsRef Certs
Definition Peer.inl:532
const char * HostName
Definition Peer.inl:533