Files
UnrealEngine/Engine/Plugins/Experimental/QuicMessaging/Source/QuicMessagingTransport/Private/QuicEndpointManager.cpp
2025-05-18 13:04:45 +08:00

1026 lines
25 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "QuicEndpointManager.h"
#include "QuicMessagingTransportPrivate.h"
#include "QuicEndpoint.h"
#include "QuicClient.h"
#include "QuicServer.h"
#include "QuicBuffers.h"
#include "QuicCertificate.h"
#include "QuicIncludes.h"
#include "QuicEndpointConfig.h"
#include "Math/NumericLimits.h"
#include <stdexcept>
/** The protocol name used in the Application Layer Protocol Negotation. */
const QUIC_BUFFER Alpn = { sizeof("unrealquic") - 1, (uint8_t*)"unrealquic" };
FQuicEndpointManager::FQuicEndpointManager(
TSharedRef<FQuicEndpointConfig> InEndpointConfig)
: MsQuic(nullptr)
, Registration(nullptr)
, CurrentConfiguration(nullptr)
, LocalNodeId(InEndpointConfig->LocalNodeId)
, ServerEndpoint(FIPv4Endpoint::Any)
, EndpointMode(EEndpointMode::Client)
, EndpointConfig(InEndpointConfig)
, ReconnectAttempts(TMap<FIPv4Endpoint, uint32>())
, Endpoints(TMap<FIPv4Endpoint, FQuicEndpointPtr>())
, KnownNodes(TMap<FGuid, FQuicNodeInfo>())
{
QUIC_STATUS Status;
EndpointMode = EEndpointMode::Client;
if (QUIC_FAILED(Status = MsQuicOpen2(&MsQuic)))
{
UE_LOG(LogQuicMessagingTransport, Error,
TEXT("[EndpointManager] Could not open MsQuic: %s."),
*QuicUtils::ConvertResult(Status));
return;
}
constexpr QUIC_REGISTRATION_CONFIG RegistrationConfig
= { "QuicMessaging", QUIC_EXECUTION_PROFILE_LOW_LATENCY };
if (QUIC_FAILED(Status = MsQuic->RegistrationOpen(&RegistrationConfig,
&Registration)))
{
UE_LOG(LogQuicMessagingTransport, Error,
TEXT("[EndpointManager] Could not open registration: %s."),
*QuicUtils::ConvertResult(Status));
return;
}
// Setup the discovery timeout tick
if (EndpointConfig->DiscoveryTimeoutSec > 0)
{
DiscoveryTimeoutTickHandle = FTSTicker::GetCoreTicker().AddTicker(
TEXT("QuicDiscoveryTimeoutTick"), 1, [this](float DeltaSeconds) {
CheckDiscoveryTimeout();
return true;
});
}
UE_LOG(LogQuicMessagingTransport, Verbose,
TEXT("[EndpointManager] Started endpoint manager."));
}
FQuicEndpointManager::~FQuicEndpointManager()
{
Shutdown();
if (DiscoveryTimeoutTickHandle.IsValid())
{
FTSTicker::GetCoreTicker().RemoveTicker(DiscoveryTimeoutTickHandle);
}
if (MsQuic)
{
if (CurrentConfiguration)
{
MsQuic->ConfigurationClose(CurrentConfiguration);
}
if (Registration)
{
MsQuic->RegistrationShutdown(Registration, QUIC_CONNECTION_SHUTDOWN_FLAG_SILENT, 0);
MsQuic->RegistrationClose(Registration);
}
MsQuicClose(MsQuic);
}
UE_LOG(LogQuicMessagingTransport, Verbose,
TEXT("[EndpointManager] Stopped endpoint manager."));
}
void FQuicEndpointManager::Shutdown()
{
FScopeLock EndpointsLock(&EndpointsCS);
if (Endpoints.IsEmpty())
{
return;
}
for (auto It = Endpoints.CreateIterator(); It; ++It)
{
const TSharedPtr<FQuicEndpoint, ESPMode::ThreadSafe>& Endpoint = It->Value;
if (Endpoint.IsValid())
{
if (!Endpoint->IsStopping())
{
Endpoint->SendBye();
Endpoint->StopEndpoint();
CheckLoseNode(It->Key);
}
}
if (EndpointMode != EEndpointMode::Server)
{
It.RemoveCurrent();
}
}
UE_LOG(LogQuicMessagingTransport, Verbose,
TEXT("[EndpointManager] Shutdown completed."));
}
HQUIC FQuicEndpointManager::LoadClientConfiguration() const
{
QUIC_SETTINGS QuicSettings = { {0} };
/** Client idle timeout disabled */
QuicSettings.IdleTimeoutMs = 0;
QuicSettings.IsSet.IdleTimeoutMs = 1;
/** Client connection timeout (5 seconds) */
QuicSettings.DisconnectTimeoutMs = 5000;
QuicSettings.IsSet.DisconnectTimeoutMs = 1;
/** Allow peer to open max unidirectional streams */
QuicSettings.PeerUnidiStreamCount = std::numeric_limits<uint16_t>::max();
QuicSettings.IsSet.PeerUnidiStreamCount = 1;
/** Disable sendbuffering to avoid copying outbound data and delays in sending. */
QuicSettings.SendBufferingEnabled = 0;
QuicSettings.IsSet.SendBufferingEnabled = 1;
/**
* Default client config
*/
QUIC_CREDENTIAL_CONFIG CredConfig;
FMemory::Memset(&CredConfig, 0, sizeof(CredConfig));
CredConfig.Type = QUIC_CREDENTIAL_TYPE_NONE;
CredConfig.Flags = QUIC_CREDENTIAL_FLAG_CLIENT;
const TSharedRef<FQuicClientConfig> ClientEndpointConfig
= StaticCastSharedRef<FQuicClientConfig, FQuicEndpointConfig>(EndpointConfig);
if (ClientEndpointConfig->ClientVerificationMode == EQuicClientVerificationMode::Pass)
{
CredConfig.Flags |= QUIC_CREDENTIAL_FLAG_NO_CERTIFICATE_VALIDATION;
}
/**
* Initialize client config object
*/
HQUIC ClientConfiguration;
QUIC_STATUS Status = QUIC_STATUS_SUCCESS;
if (QUIC_FAILED(Status = MsQuic->ConfigurationOpen(Registration, &Alpn, 1,
&QuicSettings, sizeof(QuicSettings), NULL, &ClientConfiguration)))
{
UE_LOG(LogQuicMessagingTransport, Error,
TEXT("[EndpointManager] Could not open client configuration: %s."),
*QuicUtils::ConvertResult(Status));
return nullptr;
}
/**
* Load TLS credential part of configuration
*/
if (QUIC_FAILED(Status = MsQuic->ConfigurationLoadCredential(ClientConfiguration, &CredConfig)))
{
UE_LOG(LogQuicMessagingTransport, Error,
TEXT("[EndpointManager] Could not load client config credentials: %s."),
*QuicUtils::ConvertResult(Status));
return nullptr;
}
return ClientConfiguration;
}
HQUIC FQuicEndpointManager::LoadServerConfiguration(
FString CertificatePath, FString PrivateKeyPath) const
{
QUIC_SETTINGS QuicSettings = { {0} };
/** Server idle timeout disabled */
QuicSettings.IdleTimeoutMs = 0;
QuicSettings.IsSet.IdleTimeoutMs = 1;
/** Server connection timeout (5 seconds) */
QuicSettings.DisconnectTimeoutMs = 5000;
QuicSettings.IsSet.DisconnectTimeoutMs = 1;
/** Server resumption level (allow and 0-RTT) */
QuicSettings.ServerResumptionLevel = QUIC_SERVER_RESUME_AND_ZERORTT;
QuicSettings.IsSet.ServerResumptionLevel = 1;
/** Allow peer to open max unidirectional streams */
QuicSettings.PeerUnidiStreamCount = std::numeric_limits<uint16_t>::max();
QuicSettings.IsSet.PeerUnidiStreamCount = 1;
/** KeepAlive interval (300 milliseconds) */
QuicSettings.KeepAliveIntervalMs = 300;
QuicSettings.IsSet.KeepAliveIntervalMs = 1;
/** Disable sendbuffering to avoid copying outbound data and delays in sending. */
QuicSettings.SendBufferingEnabled = 0;
QuicSettings.IsSet.SendBufferingEnabled = 1;
/**
* Credentials
*/
QuicCertificateUtils::QUIC_CREDENTIAL_CONFIG_HELPER Config;
FMemory::Memset(&Config, 0, sizeof(Config));
Config.CredConfig.Flags = QUIC_CREDENTIAL_FLAG_NONE;
auto CertFile = StringCast<ANSICHAR>(*CertificatePath);
auto KeyFile = StringCast<ANSICHAR>(*PrivateKeyPath);
Config.CertificateFile.CertificateFile = CertFile.Get();
Config.CertificateFile.PrivateKeyFile = KeyFile.Get();
Config.CredConfig.Type = QUIC_CREDENTIAL_TYPE_CERTIFICATE_FILE;
Config.CredConfig.CertificateFile = &Config.CertificateFile;
/**
* Initialize server config object
*/
HQUIC ServerConfiguration;
QUIC_STATUS Status = QUIC_STATUS_SUCCESS;
if (QUIC_FAILED(Status = MsQuic->ConfigurationOpen(Registration, &Alpn, 1,
&QuicSettings, sizeof(QuicSettings), NULL, &ServerConfiguration)))
{
UE_LOG(LogQuicMessagingTransport, Error,
TEXT("[EndpointManager] Could not open server configuration: %s."),
*QuicUtils::ConvertResult(Status));
return nullptr;
}
if (QUIC_FAILED(Status = MsQuic->ConfigurationLoadCredential(ServerConfiguration, &Config.CredConfig)))
{
UE_LOG(LogQuicMessagingTransport, Error,
TEXT("[EndpointManager] Could not load server config credentials: %s."),
*QuicUtils::ConvertResult(Status));
return nullptr;
}
return ServerConfiguration;
}
void FQuicEndpointManager::InitializeServer()
{
const TSharedRef<FQuicServerConfig> ServerEndpointConfig
= StaticCastSharedRef<FQuicServerConfig, FQuicEndpointConfig>(EndpointConfig);
ServerEndpoint = ServerEndpointConfig->Endpoint;
if (ServerEndpointConfig->AuthenticationMode == EAuthenticationMode::Disabled)
{
UE_LOG(LogQuicMessagingTransport, Warning,
TEXT("[EndpointManager] Authentication disabled."));
}
else
{
UE_LOG(LogQuicMessagingTransport, Verbose,
TEXT("[EndpointManager] Authentication enabled."));
}
if (ServerEndpointConfig->ConnCooldownMode == EConnectionCooldownMode::Enabled)
{
UE_LOG(LogQuicMessagingTransport, Verbose,
TEXT("[EndpointManager] Connection cooldown active: "
"MaxAttempts: %d, TimePeriod: %ds, Cooldown: %ds, MaxCooldown: %ds."),
ServerEndpointConfig->ConnCooldownMaxAttempts,
ServerEndpointConfig->ConnCooldownPeriodSec,
ServerEndpointConfig->ConnCooldownSec,
ServerEndpointConfig->ConnCooldownMaxSec);
}
{
FScopeLock EndpointsLock(&EndpointsCS);
if (!Endpoints.IsEmpty())
{
for (auto It = Endpoints.CreateIterator(); It; ++It)
{
const TSharedPtr<FQuicEndpoint, ESPMode::ThreadSafe>& Endpoint = It->Value;
if (Endpoint.IsValid())
{
if (!Endpoint->IsStopping())
{
Endpoint->StopEndpoint();
}
}
It.RemoveCurrent();
}
}
}
EndpointMode = EEndpointMode::Server;
FString PrivateKey = ServerEndpointConfig->PrivateKey;
FString Certificate = ServerEndpointConfig->Certificate;
// Generate private key and certificate if none provided
if (PrivateKey == "" || Certificate == "")
{
if (!QuicCertificateUtils::CreateSelfSigned())
{
UE_LOG(LogQuicMessagingTransport, Error,
TEXT("[EndpointManager] Could not generate self-signed certificate."));
return;
}
TTuple<FString, FString> SelfSigned = QuicCertificateUtils::GetSelfSignedPaths();
Certificate = SelfSigned.Key;
PrivateKey = SelfSigned.Value;
UE_LOG(LogQuicMessagingTransport, Verbose,
TEXT("[EndpointManager] Generated self-signed certificate."));
}
CurrentConfiguration = LoadServerConfiguration(Certificate, PrivateKey);
if (!CurrentConfiguration)
{
UE_LOG(LogQuicMessagingTransport, Error,
TEXT("[EndpointManager] Server could not be started."));
return;
}
const TSharedPtr<FQuicEndpoint, ESPMode::ThreadSafe> LocalEndpoint
= MakeShared<FQuicServer, ESPMode::ThreadSafe>(
ServerEndpointConfig, CurrentConfiguration,
MsQuic, Registration, TEXT("QuicServerEndpoint"),Alpn);
LocalEndpoint->OnMessageInbound().BindRaw(this, &FQuicEndpointManager::ValidateInboundMessage);
LocalEndpoint->OnSerializeHeader().BindRaw(this, &FQuicEndpointManager::SerializeHeader);
LocalEndpoint->OnDeserializeHeader().BindRaw(this, &FQuicEndpointManager::DeserializeHeader);
LocalEndpoint->OnEndpointDisconnected().BindRaw(this, &FQuicEndpointManager::EndpointDisconnected);
LocalEndpoint->OnEndpointConnected().BindRaw(this, &FQuicEndpointManager::EndpointConnected);
LocalEndpoint->OnStatisticsUpdated().BindRaw(this, &FQuicEndpointManager::EndpointStatisticsUpdated);
LocalEndpoint->Start();
{
FScopeLock EndpointsLock(&EndpointsCS);
Endpoints.Add(ServerEndpointConfig->Endpoint, LocalEndpoint);
}
UE_LOG(LogQuicMessagingTransport, Verbose,
TEXT("[EndpointManager] Configured endpoint manager to run in server mode."));
}
void FQuicEndpointManager::AddClient(const FIPv4Endpoint& ClientEndpoint)
{
if (EndpointMode == EEndpointMode::Server)
{
return;
}
{
FScopeLock EndpointsLock(&EndpointsCS);
if (const FQuicEndpointPtr* Endpoint = Endpoints.Find(ClientEndpoint))
{
UE_LOG(LogQuicMessagingTransport, Verbose,
TEXT("[EndpointManager] AddClient: endpoint %s still connected, removing it."),
*ClientEndpoint.ToString());
if (!(*Endpoint)->IsStopping())
{
(*Endpoint)->StopEndpoint();
}
Endpoints.Remove(ClientEndpoint);
}
}
const TSharedRef<FQuicClientConfig> ClientEndpointConfig
= StaticCastSharedRef<FQuicClientConfig, FQuicEndpointConfig>(EndpointConfig);
ClientEndpointConfig->Endpoint = ClientEndpoint;
if (!CurrentConfiguration)
{
CurrentConfiguration = LoadClientConfiguration();
if (!CurrentConfiguration)
{
UE_LOG(LogQuicMessagingTransport, Error,
TEXT("[EndpointManager] Client could not be started."));
return;
}
}
const TSharedPtr<FQuicEndpoint, ESPMode::ThreadSafe> RemoteEndpoint
= MakeShared<FQuicClient, ESPMode::ThreadSafe>(
ClientEndpointConfig, CurrentConfiguration,
MsQuic, Registration, TEXT("QuicClientEndpoint"));
RemoteEndpoint->OnMessageInbound().BindRaw(this, &FQuicEndpointManager::ValidateInboundMessage);
RemoteEndpoint->OnSerializeHeader().BindRaw(this, &FQuicEndpointManager::SerializeHeader);
RemoteEndpoint->OnDeserializeHeader().BindRaw(this, &FQuicEndpointManager::DeserializeHeader);
RemoteEndpoint->OnEndpointDisconnected().BindRaw(this, &FQuicEndpointManager::EndpointDisconnected);
RemoteEndpoint->OnStatisticsUpdated().BindRaw(this, &FQuicEndpointManager::EndpointStatisticsUpdated);
RemoteEndpoint->Start();
{
FScopeLock EndpointsLock(&EndpointsCS);
Endpoints.Add(ClientEndpoint, RemoteEndpoint);
}
UE_LOG(LogQuicMessagingTransport, Verbose,
TEXT("[EndpointManager] Added client endpoint %s."),
*ClientEndpoint.ToString());
}
void FQuicEndpointManager::RemoveClient(const FIPv4Endpoint& ClientEndpoint)
{
{
FScopeLock EndpointsLock(&EndpointsCS);
if (const FQuicEndpointPtr* Endpoint = Endpoints.Find(ClientEndpoint))
{
if (!(*Endpoint)->IsStopping())
{
(*Endpoint)->StopEndpoint();
}
Endpoints.Remove(ClientEndpoint);
UE_LOG(LogQuicMessagingTransport, Verbose,
TEXT("[EndpointManager] Removed client endpoint %s."),
*ClientEndpoint.ToString());
}
}
CheckLoseNode(ClientEndpoint);
}
/*
* TODO(vri): Fix reconnect mechanism
* Tracked in [UCS-5152] Finalize QuicMessaging plugin
*/
void FQuicEndpointManager::ReconnectClient(const FIPv4Endpoint ClientEndpoint)
{
UE_LOG(LogQuicMessagingTransport, Verbose,
TEXT("[EndpointManager] Reconnecting client %s."),
*ClientEndpoint.ToString());
if (!ReconnectAttempts.Contains(ClientEndpoint))
{
ReconnectAttempts.Add(ClientEndpoint, 0);
}
if (ReconnectAttempts[ClientEndpoint] == 3)
{
return;
}
ReconnectAttempts[ClientEndpoint]++;
AddClient(ClientEndpoint);
}
bool FQuicEndpointManager::IsEndpointAuthenticated(const FGuid& NodeId) const
{
FScopeLock NodesLock(&NodesCS);
const FQuicNodeInfo* SearchNode = KnownNodes.Find(NodeId);
if (!SearchNode)
{
return false;
}
return SearchNode->bIsAuthenticated;
}
void FQuicEndpointManager::SetEndpointAuthenticated(const FGuid& NodeId)
{
FScopeLock NodesLock(&NodesCS);
FQuicNodeInfo* SearchNode = KnownNodes.Find(NodeId);
if (!SearchNode)
{
UE_LOG(LogQuicMessagingTransport, Error,
TEXT("[EndpointManager] Could not find node to set as authenticated: %s"),
*NodeId.ToString());
return;
}
SearchNode->bIsAuthenticated = true;
// Mark the handler as authenticated as well
if (EndpointMode == EEndpointMode::Server)
{
FScopeLock EndpointsLock(&EndpointsCS);
const FQuicEndpointPtr* LocalEndpoint = Endpoints.Find(SearchNode->LocalEndpoint);
if (!LocalEndpoint)
{
UE_LOG(LogQuicMessagingTransport, Error,
TEXT("[EndpointManager] Could not find server endpoint to set handler as authenticated."));
return;
}
(*LocalEndpoint)->SetHandlerAuthenticated(SearchNode->Endpoint);
}
}
void FQuicEndpointManager::DisconnectNode(const FGuid& NodeId)
{
FScopeLock NodesLock(&NodesCS);
const FQuicNodeInfo* SearchNode = KnownNodes.Find(NodeId);
if (!SearchNode)
{
UE_LOG(LogQuicMessagingTransport, Error,
TEXT("[EndpointManager] Could not find node to disconnect: %s"),
*NodeId.ToString());
return;
}
DisconnectEndpoint(SearchNode->LocalEndpoint);
}
void FQuicEndpointManager::DisconnectEndpoint(const FIPv4Endpoint& Endpoint)
{
// It's a client, so just remove it
if (EndpointMode == EEndpointMode::Client)
{
RemoveClient(Endpoint);
return;
}
FScopeLock EndpointsLock(&EndpointsCS);
const FQuicEndpointPtr* LocalEndpoint = Endpoints.Find(Endpoint);
if (!LocalEndpoint)
{
UE_LOG(LogQuicMessagingTransport, Error,
TEXT("[EndpointManager] Could not find server endpoint to disconnect handler."));
return;
}
(*LocalEndpoint)->DisconnectHandler(Endpoint);
}
void FQuicEndpointManager::EnqueueOutboundMessages(FQuicPayloadPtr Data,
const TArray<TTuple<FGuid, uint32>>& MessageMetas, const EQuicMessageType MessageType)
{
if (!Data.IsValid())
{
return;
}
if (Data->IsEmpty())
{
return;
}
for (const TTuple<FGuid, uint32>& MessageMeta : MessageMetas)
{
const FGuid& Recipient = MessageMeta.Key;
FIPv4Endpoint LocalAddress;
FIPv4Endpoint RecipientAddress;
{
FScopeLock NodesLock(&NodesCS);
const FQuicNodeInfo* RecipientNode = KnownNodes.Find(Recipient);
if (!RecipientNode)
{
UE_LOG(LogQuicMessagingTransport, Error,
TEXT("[EndpointManager] Could not find message recipient node %s"),
*Recipient.ToString());
continue;
}
LocalAddress = RecipientNode->LocalEndpoint;
RecipientAddress = RecipientNode->Endpoint;
}
FMessageHeader MessageHeader(MessageType, Recipient, LocalNodeId, Data->Num());
const FQuicPayloadPtr MetaBuffer = SerializeHeaderDelegate.Execute(MessageHeader);
const FOutboundMessage OutboundMessage(
RecipientAddress, Data, MetaBuffer);
const FIPv4Endpoint& SendingEndpointAddress
= (EndpointMode == EEndpointMode::Client) ? RecipientAddress : LocalAddress;
FScopeLock EndpointsLock(&EndpointsCS);
const FQuicEndpointPtr* SendingEndpoint = Endpoints.Find(SendingEndpointAddress);
if (!SendingEndpoint)
{
UE_LOG(LogQuicMessagingTransport, Error,
TEXT("[EndpointManager] Could not find sending endpoint to enqueue message on: %s"),
*SendingEndpointAddress.ToString());
continue;
}
(*SendingEndpoint)->EnqueueOutboundMessage(OutboundMessage);
}
}
void FQuicEndpointManager::ValidateInboundMessage(const FInboundMessage InboundMessage)
{
// Message was generated locally
if (InboundMessage.MessageHeader.RecipientId == InboundMessage.MessageHeader.SenderId)
{
return;
}
// Sender guid is not initialized
if (InboundMessage.MessageHeader.SenderId == FGuid())
{
return;
}
// Incorrect message segment for server side
if (EndpointMode == EEndpointMode::Server
&& InboundMessage.MessageHeader.MessageType == EQuicMessageType::AuthenticationResponse)
{
return;
}
// Incorrect message segment for client side
if (EndpointMode == EEndpointMode::Client
&& InboundMessage.MessageHeader.MessageType == EQuicMessageType::Authentication)
{
return;
}
// Node discovery
if (!KnownNodes.Contains(InboundMessage.MessageHeader.SenderId))
{
// Sometimes the first transmitted message (EQuicMessageType::Hello) is broken
if (!IsMessageValid(InboundMessage))
{
SendEndpointHello(InboundMessage);
return;
}
EndpointNodeDiscovered(InboundMessage.MessageHeader.SenderId,
InboundMessage.Sender, InboundMessage.Receiver);
SendEndpointHello(InboundMessage);
}
if (InboundMessage.MessageHeader.MessageType == EQuicMessageType::Bye)
{
EndpointNodeLost(InboundMessage.MessageHeader.SenderId, InboundMessage.Sender);
EndpointDisconnected(InboundMessage.Sender, EQuicEndpointError::Normal);
}
// Don't have to pass these messages on
if (InboundMessage.MessageHeader.MessageType == EQuicMessageType::Hello
|| InboundMessage.MessageHeader.MessageType == EQuicMessageType::Bye)
{
return;
}
MessageValidatedDelegate.ExecuteIfBound(InboundMessage);
}
void FQuicEndpointManager::SendEndpointHello(const FInboundMessage& InboundMessage)
{
FScopeLock EndpointsLock(&EndpointsCS);
if (EndpointMode == EEndpointMode::Client)
{
if (const FQuicEndpointPtr* Client = Endpoints.Find(InboundMessage.Sender))
{
(*Client)->SendHello(InboundMessage.Sender);
}
}
}
bool FQuicEndpointManager::IsMessageValid(const FInboundMessage InboundMessage)
{
if (InboundMessage.MessageHeader.MessageType < EQuicMessageType::Hello
|| InboundMessage.MessageHeader.MessageType > EQuicMessageType::Bye)
{
return false;
}
return true;
}
FQuicPayloadPtr FQuicEndpointManager::SerializeHeader(
FMessageHeader& MessageHeader) const
{
return SerializeHeaderDelegate.Execute(MessageHeader);
}
FMessageHeader FQuicEndpointManager::DeserializeHeader(
const FQuicPayloadPtr HeaderData) const
{
return DeserializeHeaderDelegate.Execute(HeaderData);
}
void FQuicEndpointManager::EndpointNodeDiscovered(const FGuid NodeId,
const FIPv4Endpoint& NodeEndpoint, const FIPv4Endpoint& LocalEndpoint)
{
{
FScopeLock NodesLock(&NodesCS);
if (FQuicNodeInfo* ExistingNode = KnownNodes.Find(NodeId))
{
UE_LOG(LogQuicMessagingTransport, Display,
TEXT("[EndpointManager] Discovered node already known."));
if (ExistingNode->Endpoint != NodeEndpoint)
{
ExistingNode->Endpoint = NodeEndpoint;
}
return;
}
const FQuicNodeInfo NodeInfo(NodeEndpoint, NodeId, LocalEndpoint);
KnownNodes.Add(NodeId, NodeInfo);
}
EndpointNodeDiscoveredDelegate.ExecuteIfBound(NodeId);
if (EndpointMode == EEndpointMode::Client)
{
ClientConnectionChangedDelegate.ExecuteIfBound(
NodeId, NodeEndpoint, EQuicClientConnectionChange::Connected);
}
}
void FQuicEndpointManager::EndpointNodeLost(const FGuid NodeId, const FIPv4Endpoint LostEndpoint)
{
{
FScopeLock NodesLock(&NodesCS);
const FQuicNodeInfo* ExistingNode = KnownNodes.Find(NodeId);
if (!ExistingNode)
{
UE_LOG(LogQuicMessagingTransport, Warning,
TEXT("[EndpointManager] Node to forget not found."));
return;
}
if (ExistingNode->Endpoint != LostEndpoint)
{
return;
}
KnownNodes.Remove(NodeId);
}
EndpointNodeLostDelegate.ExecuteIfBound(NodeId);
if (EndpointMode == EEndpointMode::Client)
{
ClientConnectionChangedDelegate.ExecuteIfBound(
NodeId, LostEndpoint, EQuicClientConnectionChange::Disconnected);
}
}
TOptional<FGuid> FQuicEndpointManager::GetKnownNodeId(const FIPv4Endpoint& Endpoint) const
{
FScopeLock NodesLock(&NodesCS);
for (const auto& NodePair : KnownNodes)
{
if (NodePair.Value.Endpoint == Endpoint)
{
return TOptional<FGuid>(NodePair.Key);
}
}
return TOptional<FGuid>();
}
bool FQuicEndpointManager::IsEndpointKnown(const FIPv4Endpoint Endpoint) const
{
FScopeLock NodesLock(&NodesCS);
bool EndpointKnown = false;
for (const auto& NodePair : KnownNodes)
{
if (NodePair.Value.Endpoint == Endpoint)
{
EndpointKnown = true;
break;
}
}
return EndpointKnown;
}
TArray<FGuid> FQuicEndpointManager::GetKnownNodeIds() const
{
FScopeLock NodesLock(&NodesCS);
TArray<FGuid> KnownNodeIds;
KnownNodes.GetKeys(KnownNodeIds);
return KnownNodeIds;
}
TArray<FIPv4Endpoint> FQuicEndpointManager::GetKnownEndpoints() const
{
FScopeLock NodesLock(&NodesCS);
TArray<FIPv4Endpoint> KnownEndpoints;
for (const auto& NodePair : KnownNodes)
{
KnownEndpoints.Add(NodePair.Value.Endpoint);
}
return KnownEndpoints;
}
FMessageTransportStatistics FQuicEndpointManager::GetStats(FGuid NodeId) const
{
FScopeLock NodesLock(&NodesCS);
if (FQuicNodeInfo const* NodeInfo = KnownNodes.Find(NodeId))
{
return NodeInfo->Statistics;
}
return {};
}
void FQuicEndpointManager::EndpointDisconnected(
const FIPv4Endpoint DisconnectedEndpoint, EQuicEndpointError Reason)
{
UE_LOG(LogQuicMessagingTransport, Verbose,
TEXT("[EndpointManager] Endpoint disconnected: %s / Reason: %d."),
*DisconnectedEndpoint.ToString(), Reason);
{
FScopeLock EndpointsLock(&EndpointsCS);
// Shutdown disconnected client
if (const FQuicEndpointPtr* Endpoint = Endpoints.Find(DisconnectedEndpoint))
{
if (!(*Endpoint)->IsStopping())
{
(*Endpoint)->StopEndpoint();
Endpoints.Remove(DisconnectedEndpoint);
}
}
}
CheckLoseNode(DisconnectedEndpoint);
// Attempt reconnect for aborted client side connections
if (EndpointMode == EEndpointMode::Client && Reason == EQuicEndpointError::ConnectionAbort)
{
ReconnectClient(DisconnectedEndpoint);
}
}
void FQuicEndpointManager::CheckLoseNode(const FIPv4Endpoint LostEndpoint)
{
FScopeLock NodesLock(&NodesCS);
// Search for and mark node as lost
for (const auto& NodePair : KnownNodes)
{
if (NodePair.Value.Endpoint == LostEndpoint)
{
NodesLock.Unlock();
EndpointNodeLost(NodePair.Value.NodeId, LostEndpoint);
break;
}
}
}
void FQuicEndpointManager::EndpointConnected(const FIPv4Endpoint& ConnectedEndpoint)
{
if (EndpointConfig->DiscoveryTimeoutSec == 0)
{
return;
}
FScopeLock DiscoveryTimeoutsLock(&DiscoveryTimeoutCS);
FDiscoveryTimeoutEntry TimeoutEntry;
TimeoutEntry.Endpoint = ConnectedEndpoint;
TimeoutEntry.Timestamp = FPlatformTime::Seconds() + EndpointConfig->DiscoveryTimeoutSec;
DiscoveryTimeouts.Add(TimeoutEntry);
}
void FQuicEndpointManager::CheckDiscoveryTimeout()
{
FScopeLock DiscoveryTimeoutsLock(&DiscoveryTimeoutCS);
if (DiscoveryTimeouts.IsEmpty())
{
return;
}
const double Now = FPlatformTime::Seconds();
for (auto Entry = DiscoveryTimeouts.CreateIterator(); Entry; ++Entry)
{
if (Entry->Timestamp < Now)
{
return;
}
if (!IsEndpointKnown(Entry->Endpoint))
{
DisconnectEndpoint(Entry->Endpoint);
}
Entry.RemoveCurrent();
}
}
void FQuicEndpointManager::EndpointStatisticsUpdated(
const FMessageTransportStatistics TransportStats)
{
FScopeLock NodesLock(&NodesCS);
for (auto& NodePair : KnownNodes)
{
if (NodePair.Value.Endpoint.ToString() == TransportStats.IPv4AsString)
{
NodePair.Value.Statistics = TransportStats;
}
}
}