Initial commit

This commit is contained in:
alexpete
2021-03-05 11:26:34 -08:00
commit a10351f38d
27091 changed files with 5521199 additions and 0 deletions
@@ -0,0 +1,87 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#include <AzNetworking/Utilities/CidrAddress.h>
#include <AzNetworking/Utilities/IpAddress.h>
#include <AzCore/std/containers/vector.h>
#include <AzCore/StringFunc/StringFunc.h>
#include <AzCore/Console/ILogger.h>
namespace AzNetworking
{
CidrAddress::CidrAddress(const AZStd::string& cidrAddress)
{
[[maybe_unused]] const bool success = ParseAddress(cidrAddress);
AZ_Assert(success, "Failed to construct CidrAddress from malformed input string");
}
bool CidrAddress::ParseAddress(const AZStd::string& cidrAddress)
{
AZStd::string ip(cidrAddress);
// Extract the CIDR mask from the string. If there is no mask defined then this is an explicit IP (even though it should end in /32)
const AZStd::string::size_type maskLocation = cidrAddress.rfind("/");
if (maskLocation != AZStd::string::npos)
{
// Determine the mask
const int32_t mask = AZStd::min(atoi(cidrAddress.substr(maskLocation + 1).c_str()), 32);
m_mask = (mask > 0) ? ~((1 << (32 - mask)) - 1) : 0;
// remove the mask from the string so it's only the ip
ip = cidrAddress.substr(0, maskLocation);
}
// Convert ip to a AzNetworking uint32
AZStd::fixed_vector<uint8_t, 4> ipv4Quads;
auto ParseIpv4 = [&ipv4Quads](AZStd::string_view token)
{
AZStd::fixed_string<32> ipv4Segment(token);
char* segmentEnd;
uint8_t octet = aznumeric_cast<uint8_t>(strtoul(ipv4Segment.c_str(), &segmentEnd, 10));
ipv4Quads.push_back(octet);
};
AZ::StringFunc::TokenizeVisitor(ip, ParseIpv4, '.');
if (ipv4Quads.size() == 4)
{
m_ip = AzNetworking::IpAddress(ipv4Quads[0], ipv4Quads[1], ipv4Quads[2], ipv4Quads[3], 0).GetAddress(ByteOrder::Host) & m_mask;
return true;
}
AZLOG_ERROR("CIDR input string (%s) malformed, input should be formatted as aaa.bbb.ccc.ddd/mask", cidrAddress.c_str());
return false;
}
bool CidrAddress::IsMatch(const IpAddress& address) const
{
const uint32_t uintAddress = address.GetAddress(ByteOrder::Host);
return IsMatch(uintAddress);
}
bool CidrAddress::IsMatch(uint32_t address) const
{
// Apply the mask to query address and see if it matches the internal address
const uint32_t maskedAddress = (address & m_mask);
return maskedAddress == m_ip;
}
uint32_t CidrAddress::GetIp() const
{
return m_ip;
}
uint32_t CidrAddress::GetMask() const
{
return m_mask;
}
}
@@ -0,0 +1,61 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#pragma once
#include <AzCore/std/string/string.h>
namespace AzNetworking
{
class IpAddress;
//! @class CidrAddress
//! @brief Helper class that implements Classless Inter-Domain Routing (CIDR) IP address filtering.
class CidrAddress
{
public:
CidrAddress() = default;
//! Construct from an input string.
//! @param cidrAddress the input string to parse as a CIDR address (dotted quad/mask, ie 127.0.0.1/32)
CidrAddress(const AZStd::string& cidrAddress);
//! Parse the address from the provided string.
//! @param cidrAddress the input string to parse as a CIDR address (dotted quad/mask, ie 127.0.0.1/32)
//! @return boolean true if the input was successfully parsed, false on error
bool ParseAddress(const AZStd::string& cidrAddress);
//! Check to see if a AzNetworking uint IP matches this CIDR address instance.
//! @param address input IpAddress to validate
//! @return boolean true if the address matches, false if it fails
bool IsMatch(const IpAddress& address) const;
//! Check to see if a AzNetworking uint IP matches this CIDR address instance.
//! @param address input IPv4 address as a uint32_t ** must be in host byte order **
//! @return boolean true if the address matches, false if it fails
bool IsMatch(uint32_t address) const;
//! Returns the IP of this CIDR address in host byte order.
//! @return the IP of this CIDR address in host byte order
uint32_t GetIp() const;
//! Returns the mask of this CIDR address.
//! @return the mask of this CIDR address
uint32_t GetMask() const;
protected:
uint32_t m_ip = 0;
uint32_t m_mask = 0xFFFFFFFF; //< Default is /32, which is an explicit address
};
}
@@ -0,0 +1,44 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#include <AzCore/Console/IConsole.h>
#include <AzNetworking/Framework/ICompressor.h>
#include <AzNetworking/Utilities/CompressionCommon.h>
#ifdef ENABLE_MULTIPLAYER_COMPRESSION
// Requires the MultiplayerCompression Gem be enabled and set as a dependency of the Multiplayer Gem
#include <MultiplayerCompression/MultiplayerCompressionBus.h>
#endif
namespace AzNetworking
{
AZStd::unique_ptr<ICompressor> CreateCompressor(AZStd::string_view compressorName)
{
CompressorType compressorType = aznumeric_cast<AzNetworking::CompressorType>(static_cast<AZ::u32>(AZ::Crc32(compressorName)));
#ifdef ENABLE_MULTIPLAYER_COMPRESSION
// Requires the MultiplayerCompression Gem be enabled and set as a dependency of the Multiplayer Gem
CompressorType lz4Type;
MultiplayerCompression::MultiplayerCompressionRequestBus::BroadcastResult(lz4Type, &MultiplayerCompression::MultiplayerCompressionRequests::GetType);
if (lz4Type == compressorType)
{
AZStd::shared_ptr<ICompressorFactory> compressorFactory;
MultiplayerCompression::MultiplayerCompressionRequestBus::BroadcastResult(compressorFactory, &MultiplayerCompression::MultiplayerCompressionRequests::GetCompressionFactory);
return compressorFactory->Create();
}
#endif
AZ_Warning("CompressionCommon", false, "No compressor was found matching %.*s, check that related Gems are enabled.",
aznumeric_cast<int>(compressorName.size()), compressorName.data());
return nullptr;
}
}
@@ -0,0 +1,25 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#pragma once
// forward declare
class ICompressor;
// Common helper methods needed for both TLS and DTLS transport implementations
namespace AzNetworking
{
//! Helper function to create a compressor, uses enabled Gems that supply compressors
//! @param compressorName The string name of the compressor type to create, this is Crc32'd to select by CompressorType
//! @return A unique_ptr to a Compressor implementation or nullptr on failure to match
AZStd::unique_ptr<ICompressor> CreateCompressor(AZStd::string_view compressorName);
}
@@ -0,0 +1,623 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#include <AzNetworking/Utilities/EncryptionCommon.h>
#include <AzNetworking/Utilities/NetworkCommon.h>
#include <AzNetworking/AzNetworking_Traits_Platform.h>
#include <AzCore/IO/FileIO.h>
#include <AzCore/IO/SystemFile.h> // For AZ_MAX_PATH_LEN
#include <AzCore/Console/Console.h>
#include <AzCore/Console/ILogger.h>
#include <openssl/ssl.h>
#include <openssl/err.h>
#include <openssl/rand.h>
#include <openssl/x509.h>
#define OPENSSL_THREAD_DEFINES
#include <openssl/opensslconf.h>
#if defined(OPENSSL_THREADS)
// thread support enabled
#else
# error OpenSSL threading support is not enabled
#endif
namespace AzNetworking
{
AZ_CVAR(AZ::CVarFixedString, net_SslExternalCertificateFile, "testing.pem", nullptr, AZ::ConsoleFunctorFlags::DontReplicate, "The filename of the EXTERNAL (server to client) certificate chain in PEM format (default is for debugging purposes)");
AZ_CVAR(AZ::CVarFixedString, net_SslExternalPrivateKeyFile, "testkey.pem", nullptr, AZ::ConsoleFunctorFlags::DontReplicate, "The filename of the EXTERNAL (server to client) private key file in PEM format (default is for debugging purposes)");
AZ_CVAR(AZ::CVarFixedString, net_SslExternalContextPassword, "12345", nullptr, AZ::ConsoleFunctorFlags::DontReplicate | AZ::ConsoleFunctorFlags::IsInvisible, "The password required for the EXTERNAL (server to client) private certificate (default is for debugging purposes)");
AZ_CVAR(AZ::CVarFixedString, net_SslInternalCertificateFile, "servercert.pem", nullptr, AZ::ConsoleFunctorFlags::DontReplicate, "The filename of the INTERNAL (server to server only) certificate chain in PEM format (default is for debugging purposes)");
AZ_CVAR(AZ::CVarFixedString, net_SslInternalPrivateKeyFile, "serverkey.pem", nullptr, AZ::ConsoleFunctorFlags::DontReplicate, "The filename of the INTERNAL (server to server only) private key file in PEM format (default is for debugging purposes)");
AZ_CVAR(AZ::CVarFixedString, net_SslInternalContextPassword, "12345", nullptr, AZ::ConsoleFunctorFlags::DontReplicate | AZ::ConsoleFunctorFlags::IsInvisible, "The password required for the INTERNAL (server to server only) private certificate (default is for debugging purposes)");
AZ_CVAR(AZ::CVarFixedString, net_SslCertCiphers, "ECDHE-RSA-AES256-GCM-SHA384", nullptr, AZ::ConsoleFunctorFlags::DontReplicate, "The cipher suite to use when using cert based key exchange");
AZ_CVAR(int32_t, net_SslMaxCertDepth, 3, nullptr, AZ::ConsoleFunctorFlags::DontReplicate, "The maximum depth allowed for cert chaining validation");
AZ_CVAR(AZ::TimeMs, net_RotateCookieTimer, AZ::TimeMs{50}, nullptr, AZ::ConsoleFunctorFlags::DontReplicate, "Number of milliseconds to wait before generating a new DTLS cookie for handshaking");
AZ_CVAR(bool, net_SslEnablePinning, true, nullptr, AZ::ConsoleFunctorFlags::DontReplicate, "If enabled, the public certificates on the local and remote endpoints will be compared to ensure they match exactly");
AZ_CVAR(bool, net_SslValidateExpiry, true, nullptr, AZ::ConsoleFunctorFlags::DontReplicate, "If enabled, expiration dates on the certificate will be checked for validity");
AZ_CVAR(bool, net_SslAllowSelfSigned, true, nullptr, AZ::ConsoleFunctorFlags::DontReplicate, "If enabled, self-signed certs will not cause a validation error if they are otherwise considered trusted");
void PrintSslErrorStack()
{
#if AZ_TRAIT_USE_OPENSSL
const int32_t errorCode = ERR_get_error();
const int32_t systemError = GetLastNetworkError();
switch (errorCode)
{
case SSL_ERROR_NONE:
AZLOG_ERROR("%X - SSL_ERROR_NONE: last system error is (%d:%s)", errorCode, systemError, GetNetworkErrorDesc(systemError));
break;
case SSL_ERROR_ZERO_RETURN:
AZLOG_ERROR("%X - SSL_ERROR_ZERO_RETURN: connection has been closed", errorCode);
break;
case SSL_ERROR_WANT_READ:
AZLOG_ERROR("%X - SSL_ERROR_WANT_READ: socket is non-blocking, read buffer is empty", errorCode);
break;
case SSL_ERROR_WANT_WRITE:
AZLOG_ERROR("%X - SSL_ERROR_WANT_WRITE: socket is non-blocking, write buffer is full", errorCode);
break;
case SSL_ERROR_WANT_CONNECT:
AZLOG_ERROR("%X - SSL_ERROR_WANT_CONNECT: socket is non-blocking, connect failed and should be retried", errorCode);
break;
case SSL_ERROR_WANT_ACCEPT:
AZLOG_ERROR("%X - SSL_ERROR_WANT_ACCEPT: socket is non-blocking, accept failed and should be retried", errorCode);
break;
case SSL_ERROR_WANT_X509_LOOKUP:
AZLOG_ERROR("%X - SSL_ERROR_WANT_X509_LOOKUP: operation did not complete, SSL_CTX_set_client_cert_cb() has asked to be called again, operation should be retried", errorCode);
break;
case SSL_ERROR_SYSCALL:
AZLOG_ERROR("%X - SSL_ERROR_SYSCALL: system error, check errno (%d:%s)", errorCode, systemError, GetNetworkErrorDesc(systemError));
break;
case SSL_ERROR_SSL:
AZLOG_ERROR("%X - SSL_ERROR_SSL: lib %s, func %s, reason %s", errorCode, ERR_lib_error_string(errorCode), ERR_func_error_string(errorCode), ERR_reason_error_string(errorCode));
break;
default:
AZLOG_ERROR("%X - Unknown error code: lib %s, func %s, reason %s", errorCode, ERR_lib_error_string(errorCode), ERR_func_error_string(errorCode), ERR_reason_error_string(errorCode));
break;
}
#endif
}
#if AZ_TRAIT_USE_OPENSSL
static const uint32_t MaxCookieHistory = 8;
static bool g_encryptionInitialized = false;
static int32_t g_azNetworkingTrustDataIndex = 0;
static AZ::TimeMs g_lastCookieTimestamp = AZ::TimeMs{ 0 };
static uint64_t g_validCookieArray[MaxCookieHistory];
static uint32_t g_cookieReplaceIndex = 0;
static void GetCertificatePaths(TrustZone trustZone, AZStd::string& certificatePath, AZStd::string& privateKeyPath)
{
const AZ::CVarFixedString certificateFile = (trustZone == TrustZone::ExternalClientToServer) ? net_SslExternalCertificateFile : net_SslInternalCertificateFile;
const AZ::CVarFixedString privateKeyFile = (trustZone == TrustZone::ExternalClientToServer) ? net_SslExternalPrivateKeyFile : net_SslInternalPrivateKeyFile;
AZStd::string assetDir;
if (AZ::IO::FileIOBase::GetInstance() != nullptr)
{
char buffer[AZ_MAX_PATH_LEN];
AZ::IO::FileIOBase::GetInstance()->ResolvePath("@assets@/", buffer, sizeof(buffer));
assetDir = AZStd::string(buffer);
}
certificatePath = assetDir + certificateFile.c_str();
privateKeyPath = assetDir + privateKeyFile.c_str();
}
static bool ValidatePinnedCertificate(X509* remoteCert, TrustZone trustZone)
{
// Get the remote certificate
AZStd::vector<uint8_t> remoteCertificate;
{
if (remoteCert == nullptr)
{
AZLOG_ERROR("Failed to retrieve the remote certificate, pinned certificate validation failed");
PrintSslErrorStack();
return false;
}
const int32_t remoteCertLength = i2d_X509_PUBKEY(X509_get_X509_PUBKEY(remoteCert), nullptr);
if (remoteCertLength <= 0)
{
AZLOG_ERROR("Failed to retrieve the remote certificate, pinned certificate validation failed");
PrintSslErrorStack();
return false;
}
remoteCertificate.resize(remoteCertLength);
uint8_t* remoteCertificateData = remoteCertificate.data();
i2d_X509_PUBKEY(X509_get_X509_PUBKEY(remoteCert), &remoteCertificateData);
}
// Load our local copy of the certificate (what we expect the remote endpoint to present)
AZStd::vector<uint8_t> localCertificate;
{
AZStd::string certificatePath;
AZStd::string unusedPrivateKeyPath;
GetCertificatePaths(trustZone, certificatePath, unusedPrivateKeyPath);
AZ::IO::FileIOStream stream(certificatePath.c_str(), AZ::IO::OpenMode::ModeWrite);
const AZ::IO::SizeType publicCertLength = stream.GetLength();
if (publicCertLength <= 0)
{
AZLOG_ERROR("Local public certificate is not valid, returned %d bytes", static_cast<int32_t>(publicCertLength));
return false;
}
AZStd::vector<uint8_t> publicCertData;
publicCertData.resize(publicCertLength);
AZ::IO::SizeType bytesRead = stream.Read(publicCertLength, publicCertData.data());
publicCertData.resize(bytesRead);
stream.Close();
const uint8_t* publicCertBytes = publicCertData.data();
BIO* certBio = BIO_new(BIO_s_mem());
BIO_write(certBio, publicCertBytes, static_cast<int32_t>(publicCertLength));
X509* localCert = PEM_read_bio_X509(certBio, nullptr, nullptr, nullptr);
if (localCert == nullptr)
{
BIO_free(certBio);
AZLOG_ERROR("Failed to convert public cert to X509, pinned certificate validation failed");
PrintSslErrorStack();
return false;
}
const int32_t localCertLength = i2d_X509_PUBKEY(X509_get_X509_PUBKEY(localCert), nullptr);
if (localCertLength <= 0)
{
BIO_free(certBio);
X509_free(localCert);
AZLOG_ERROR("Failed to retrieve the local certificate, pinned certificate validation failed");
PrintSslErrorStack();
return false;
}
localCertificate.resize(localCertLength);
uint8_t* localCertificateData = localCertificate.data();
i2d_X509_PUBKEY(X509_get_X509_PUBKEY(localCert), &localCertificateData);
BIO_free(certBio);
X509_free(localCert);
}
// Validate the certificates match
{
const int32_t localSize = static_cast<int32_t>(localCertificate.size());
const int32_t remoteSize = static_cast<int32_t>(remoteCertificate.size());
if (localSize != remoteSize)
{
AZLOG_ERROR("Validation failed, certs are different sizes; local %d bytes, remote %d bytes", localSize, remoteSize);
return false;
}
if (memcmp(localCertificate.data(), remoteCertificate.data(), localSize) != 0)
{
AZLOG_ERROR("Validation failed, certificate content mismatch");
return false;
}
}
return true;
}
// Validates a certificate chain, returns OpenSslResultSuccess on success, OpenSslResultFailure on fail
static int32_t ValidateCertificateCallback(int32_t preverifyOk, X509_STORE_CTX* context)
{
int32_t result = preverifyOk; // Start by assigning pre-verification to our result
X509* err_cert = X509_STORE_CTX_get_current_cert(context);
if (result != OpenSslResultSuccess)
{
int32_t error = X509_STORE_CTX_get_error(context);
AZLOG_WARN("OpenSSL preverification failed with (%d: %s)", error, X509_verify_cert_error_string(error));
switch (error)
{
case X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT:
case X509_V_ERR_SELF_SIGNED_CERT_IN_CHAIN:
if (net_SslAllowSelfSigned)
{
AZLOG_WARN("net_SslAllowSelfSigned is *enabled*, clearing X509 certificate validation failure");
X509_STORE_CTX_set_error(context, X509_V_OK);
result = OpenSslResultSuccess;
}
break;
}
}
// Catch a too long certificate chain. The depth limit set using SSL_CTX_set_verify_depth() is by purpose set to "limit+1" so that
// whenever the "depth>verify_depth" condition is met, we have violated the limit and want to log this error condition.
// We must do it here, because the CHAIN_TOO_LONG error would not be found explicitly; only errors introduced by cutting off the additional certificates would be logged.
const int32_t depth = X509_STORE_CTX_get_error_depth(context);
if (depth > net_SslMaxCertDepth)
{
result = OpenSslResultFailure;
X509_STORE_CTX_set_error(context, X509_V_ERR_CERT_CHAIN_TOO_LONG);
}
// Validate certificate before and after times
if (net_SslValidateExpiry)
{
const ASN1_TIME *notBeforeTime = X509_get_notBefore(err_cert);
const int32_t beforeTimeResult = X509_cmp_current_time(notBeforeTime);
if (beforeTimeResult >= 0)
{
AZLOG_WARN("OpenSSL preverification failed, certificate is not yet valid (before time result %d)", beforeTimeResult);
X509_STORE_CTX_set_error(context, X509_V_ERR_CERT_NOT_YET_VALID);
result = OpenSslResultFailure;
}
const ASN1_TIME *notAfterTime = X509_get_notAfter(err_cert);
const int32_t afterTimeResult = X509_cmp_current_time(notAfterTime);
if (afterTimeResult <= 0)
{
AZLOG_WARN("OpenSSL preverification failed, certificate has expired (after time result %d)", afterTimeResult);
X509_STORE_CTX_set_error(context, X509_V_ERR_CERT_HAS_EXPIRED);
result = OpenSslResultFailure;
}
}
if (net_SslEnablePinning)
{
SSL* sslSocket = reinterpret_cast<SSL*>(X509_STORE_CTX_get_ex_data(context, SSL_get_ex_data_X509_STORE_CTX_idx()));
SSL_CTX* sslContext = SSL_get_SSL_CTX(sslSocket);;
TrustZone trustZone = static_cast<TrustZone>(reinterpret_cast<uintptr_t>(SSL_CTX_get_ex_data(sslContext, g_azNetworkingTrustDataIndex)));
if (!ValidatePinnedCertificate(err_cert, trustZone))
{
X509_STORE_CTX_set_error(context, X509_V_ERR_UNSPECIFIED);
result = OpenSslResultFailure;
}
}
AZLOG_INFO("Certificate validation %s", (result == OpenSslResultSuccess) ? "passed" : "failed");
return result;
}
// Generates a cookie, returns OpenSslResultSuccess on success, OpenSslResultFailure on fail
static int32_t GenerateCookieCallback([[maybe_unused]] SSL* sslSocket, uint8_t* cookieData, uint32_t* cookieLength)
{
const AZ::TimeMs currentTime = AZ::GetElapsedTimeMs();
if ((currentTime - g_lastCookieTimestamp) > net_RotateCookieTimer)
{
uint64_t newCookie = CryptoRand64();
g_cookieReplaceIndex = (g_cookieReplaceIndex + 1) % MaxCookieHistory;
g_validCookieArray[g_cookieReplaceIndex] = newCookie;
g_lastCookieTimestamp = currentTime;
}
if (cookieLength != nullptr)
{
*cookieLength = sizeof(g_validCookieArray[g_cookieReplaceIndex]);
}
if (cookieData != nullptr)
{
memcpy(cookieData, reinterpret_cast<uint8_t*>(&g_validCookieArray[g_cookieReplaceIndex]), sizeof(g_validCookieArray[g_cookieReplaceIndex]));
}
return OpenSslResultSuccess;
}
// Verifies a cookie, returns OpenSslResultSuccess on success, OpenSslResultFailure on fail
static int32_t VerifyCookieCallback([[maybe_unused]] SSL* sslSocket, const uint8_t* cookieData, uint32_t cookieLength)
{
if (cookieLength != sizeof(uint64_t))
{
// This should be logged somehow, but since this is part of DOS prevention I don't think I should *ACTUALLY* log here..
return OpenSslResultFailure;
}
uint64_t cookie = *reinterpret_cast<const uint64_t*>(cookieData);
for (uint32_t i = 0; i < MaxCookieHistory; ++i)
{
if (g_validCookieArray[i] == cookie)
{
return OpenSslResultSuccess;
}
}
// This should be logged somehow, but since this is part of DOS prevention I don't think I should *ACTUALLY* log here..
return OpenSslResultFailure;
}
#endif
bool EncryptionLayerInit()
{
#if AZ_TRAIT_USE_OPENSSL
if (!g_encryptionInitialized)
{
SSL_library_init();
SSL_load_error_strings();
ERR_load_BIO_strings();
OpenSSL_add_all_algorithms();
g_azNetworkingTrustDataIndex = SSL_get_ex_new_index(0, const_cast<void*>(reinterpret_cast<const void*>("AzNetworking TrustZone data index")), nullptr, nullptr, nullptr);
g_encryptionInitialized = true;
}
#endif
return true;
}
bool EncryptionLayerShutdown()
{
#if AZ_TRAIT_USE_OPENSSL
if (g_encryptionInitialized)
{
ERR_free_strings();
EVP_cleanup();
sk_SSL_COMP_free(SSL_COMP_get_compression_methods());
CRYPTO_cleanup_all_ex_data();
g_encryptionInitialized = false;
}
#endif
return true;
}
#if AZ_TRAIT_USE_OPENSSL
//! @struct ScopedSslContextFree
//! @brief helper structure that manages RAII teardown of the SSL context if an error condition is encountered during creation
//! If the destructor is hit and the context is non-null, this means an error has occurred and we are bailing from the create operation
//! To release the context with no error, call ReleaseSslContextWithoutFree()
struct ScopedSslContextFree
{
ScopedSslContextFree(SSL_CTX* sslContext) : m_sslContext(sslContext) {}
~ScopedSslContextFree()
{
if (m_sslContext != nullptr)
{
PrintSslErrorStack();
SSL_CTX_free(m_sslContext);
}
}
void ReleaseSslContextWithoutFree()
{
m_sslContext = nullptr;
}
SSL_CTX* m_sslContext = nullptr;
};
#endif
SSL_CTX* CreateSslContext([[maybe_unused]] SslContextType contextType, [[maybe_unused]] TrustZone trustZone)
{
#if AZ_TRAIT_USE_OPENSSL
if (!g_encryptionInitialized)
{
AZLOG_ERROR("SSL library has not been initialized for the current dll");
return nullptr;
}
SSL_CTX* context = nullptr;
switch (contextType)
{
case SslContextType::TlsGeneric:
context = SSL_CTX_new(TLS_method());
break;
case SslContextType::TlsClient:
context = SSL_CTX_new(TLS_client_method());
break;
case SslContextType::TlsServer:
context = SSL_CTX_new(TLS_server_method());
break;
case SslContextType::DtlsGeneric:
context = SSL_CTX_new(DTLS_method());
break;
case SslContextType::DtlsClient:
context = SSL_CTX_new(DTLS_client_method());
break;
case SslContextType::DtlsServer:
context = SSL_CTX_new(DTLS_server_method());
break;
}
if (context == nullptr)
{
PrintSslErrorStack();
return nullptr;
}
ScopedSslContextFree scopedFree(context);
void* trustLevelPtr = reinterpret_cast<void*>(static_cast<uintptr_t>(trustZone));
if (SSL_CTX_set_ex_data(context, g_azNetworkingTrustDataIndex, trustLevelPtr) != OpenSslResultSuccess)
{
AZLOG_ERROR("Failed to store the trust level on created SSL context");
return nullptr;
}
// Enable automatic retries for sends if renegotiation is required, makes our code simpler
SSL_CTX_set_mode(context, SSL_MODE_AUTO_RETRY);
AZStd::string certificatePath;
AZStd::string privateKeyPath;
GetCertificatePaths(trustZone, certificatePath, privateKeyPath);
// Returns 1 on success, and so returns.. not 1 on error? actual error is in error stack
if (SSL_CTX_use_certificate_chain_file(context, certificatePath.c_str()) != OpenSslResultSuccess)
{
AZLOG_ERROR("Failed to load the certificate chain file");
return nullptr;
}
// If we're accepting connections, set up password to access the private certificate
// @KB TODO: plaintext passwords are bad...
if ((contextType == SslContextType::TlsGeneric)
|| (contextType == SslContextType::TlsServer)
|| (contextType == SslContextType::DtlsGeneric)
|| (contextType == SslContextType::DtlsServer))
{
const AZ::CVarFixedString contextPassword = (trustZone == TrustZone::ExternalClientToServer) ? net_SslExternalContextPassword : net_SslInternalContextPassword;
SSL_CTX_set_default_passwd_cb(context, NULL);
SSL_CTX_set_default_passwd_cb_userdata(context, (void*)contextPassword.c_str());
if (SSL_CTX_use_PrivateKey_file(context, privateKeyPath.c_str(), SSL_FILETYPE_PEM) != OpenSslResultSuccess)
{
AZLOG_ERROR("Failed to load private certificate");
return nullptr;
}
}
// Validate the clients certificate
SSL_CTX_set_verify(context, SSL_VERIFY_PEER | SSL_VERIFY_CLIENT_ONCE, ValidateCertificateCallback);
// Set up ciphers using DH key exchange
const AZ::CVarFixedString ciphers = net_SslCertCiphers;
if (SSL_CTX_set_cipher_list(context, ciphers.c_str()) != OpenSslResultSuccess)
{
AZLOG_ERROR("Failed to set supported ciphers");
return nullptr;
}
// DTLS cookie callbacks for UDP based spoof DOS mitigation
SSL_CTX_set_cookie_generate_cb(context, GenerateCookieCallback);
SSL_CTX_set_cookie_verify_cb(context, VerifyCookieCallback);
SSL_CTX_set_ecdh_auto(context, 1);
scopedFree.ReleaseSslContextWithoutFree();
return context;
#else
return nullptr;
#endif
}
void FreeSslContext([[maybe_unused]] SSL_CTX*& context)
{
#if AZ_TRAIT_USE_OPENSSL
if (context == nullptr)
{
return;
}
SSL_CTX_free(context);
context = nullptr;
#endif
}
SSL* CreateSslForAccept([[maybe_unused]] SocketFd socketFd, [[maybe_unused]] SSL_CTX* context)
{
#if AZ_TRAIT_USE_OPENSSL
SSL* socket = SSL_new(context);
if (socket == nullptr)
{
AZLOG_ERROR("SSL_new failed, could not create SSL socket wrapper instance");
PrintSslErrorStack();
return nullptr;
}
if (SSL_set_fd(socket, static_cast<int32_t>(socketFd)) != OpenSslResultSuccess)
{
AZLOG_ERROR("SSL_set_fd failed, could not bind SSL socket wrapper to socket");
PrintSslErrorStack();
Close(socket);
return nullptr;
}
// This socket should be configured to accept connections
SSL_set_accept_state(socket);
return socket;
#else
return nullptr;
#endif
}
SSL* CreateSslForConnect([[maybe_unused]] SocketFd socketFd, [[maybe_unused]] SSL_CTX* context)
{
#if AZ_TRAIT_USE_OPENSSL
SSL* socket = SSL_new(context);
if (socket == nullptr)
{
AZLOG_ERROR("SSL_new failed, could not create SSL socket wrapper instance");
PrintSslErrorStack();
return nullptr;
}
if (SSL_set_fd(socket, static_cast<int32_t>(socketFd)) != OpenSslResultSuccess)
{
AZLOG_ERROR("SSL_set_fd failed, could not bind SSL socket wrapper to socket");
PrintSslErrorStack();
Close(socket);
return nullptr;
}
// This socket should be configured to initiate connections
SSL_set_connect_state(socket);
return socket;
#else
return nullptr;
#endif
}
void Close([[maybe_unused]] SSL*& sslSocket)
{
#if AZ_TRAIT_USE_OPENSSL
if (sslSocket == nullptr)
{
return;
}
// SSL_shutdown can do very bad things if the SSL context is in a bad state
// Further, the documentation around safely using SSL_shutdown is extremely confusing and doesn't provide a functional example
// We never terminate encryption on a connection to send further data in plain-text, we explicitly close TCP sockets and UDP virtual connections are deleted
// Therefore just removing the call to SSL_shutdown for now
//SSL_shutdown(sslSocket);
SSL_free(sslSocket);
sslSocket = nullptr;
#endif
}
bool SslErrorIsWouldBlock(int32_t errorCode)
{
return (errorCode == SSL_ERROR_WANT_READ)
|| (errorCode == SSL_ERROR_WANT_WRITE);
}
uint32_t CryptoRand32()
{
#if AZ_TRAIT_USE_OPENSSL
uint8_t buffer[sizeof(uint32_t)];
RAND_bytes(buffer, sizeof(buffer));
return *reinterpret_cast<uint32_t*>(buffer);
#else
return 0;
#endif
}
uint64_t CryptoRand64()
{
#if AZ_TRAIT_USE_OPENSSL
uint8_t buffer[sizeof(uint64_t)];
RAND_bytes(buffer, sizeof(buffer));
return *reinterpret_cast<uint64_t*>(buffer);
#else
return 0;
#endif
}
}
@@ -0,0 +1,92 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#pragma once
#include <AzNetworking/Utilities/IpAddress.h>
#include <AzNetworking/Utilities/NetworkCommon.h>
#include <AzNetworking/ConnectionLayer/IConnection.h>
#include <AzNetworking/AzNetworking_Traits_Platform.h>
// Forward declarations
typedef struct ssl_st SSL;
typedef struct ssl_ctx_st SSL_CTX;
// Common helper methods needed for both TLS and DTLS transport implementations
namespace AzNetworking
{
// Constants that map to OpenSSL's success and failure return codes
static const int32_t OpenSslResultFailure = 0;
static const int32_t OpenSslResultSuccess = 1;
//! Helper function to dump the OpenSSL error stack to the console.
void PrintSslErrorStack();
//! Initializes the encryption layer, required for TLS and DTLS usage.
//! Should be called once at program initialization, prior to creating any encrypted network connections
//! @return boolean true on success
bool EncryptionLayerInit();
//! Shuts down the encryption layer, required for TLS and DTLS usage.
//! Should be called once at program halt, after stopping all encrypted network connections
//! @return boolean true on success
bool EncryptionLayerShutdown();
enum class SslContextType
{
TlsGeneric // Can initiate or accept connections using a streaming protocol (intended for Tcp)
, TlsClient // Initiates a connection to a server over a streaming protocol (intended for Tcp)
, TlsServer // Accepts connections from clients over a streaming protocol (intended for Tcp)
, DtlsGeneric // Can initiate or accept connections over a datagram protocol, streaming ciphers are not valid (intended for Udp)
, DtlsClient // Initiates a connection to a server over a datagram protocol, streaming ciphers are not valid (intended for Udp)
, DtlsServer // Accepts connections from clients over a datagram protocol, streaming ciphers are not valid (intended for Udp)
};
//! Returns a new SSL context given a remote endpoints certificate, intended to initiate a connection to a secure remote endpoint.
//! @param contextType the type of context to create (connection initiating or connection accepting, datagram or streaming)
//! @param trustZone the level of trust we associate with this connection, used to determine which certificate file should be used (internal or external)
//! @return pointer to the new SSL context, nullptr on error
SSL_CTX* CreateSslContext(SslContextType contextType, TrustZone trustZone);
//! Call to clean up an SSL context.
//! @param context pointer to the context to clean up
void FreeSslContext(SSL_CTX*& context);
//! Accepts an incoming connection using the provided context.
//! @param socketFd the socket file descriptor of the incoming connection
//! @param context the SSL context instance to use
//! @return pointer to the new SSL socket instance, nullptr on error
SSL* CreateSslForAccept(SocketFd socketFd, SSL_CTX* context);
//! Initiates a secure connection to a remote endpoint using the provided context.
//! @param socketFd the socket file descriptor to initiate connections on
//! @param context the SSL context instance to use
//! @return pointer to the new SSL socket instance, nullptr on error
SSL* CreateSslForConnect(SocketFd socketFd, SSL_CTX* context);
//! Terminates and closes the provided SSL socket instance.
//! @param sslSocket the SSL socket instance to close
void Close(SSL*& sslSocket);
//! Returns true if the platform specific error code maps to a 'would block' error.
//! @return true if the platform specific error code maps to a 'would block' error
bool SslErrorIsWouldBlock(int32_t errorCode);
//! Returns a 32-bit random number using the crypto random generator.
//! note that 4 bytes is a really small number of bytes for crypto purposes!
//! @return 32-bit unsigned random number
uint32_t CryptoRand32();
//! Returns a 64-bit random number using the crypto random generator.
//! @return 64-bit unsigned random number
uint64_t CryptoRand64();
}
@@ -0,0 +1,32 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#pragma once
#include <AzNetworking/AzNetworking_Traits_Platform.h>
#include <AzCore/PlatformIncl.h>
#include <AzNetworking/Utilities/Endian_Platform.h>
#if AZ_TRAIT_NEEDS_HTONLL
static const uint64_t htonll(uint64_t value)
{
const uint32_t hiValue = htonl(static_cast<uint32_t>(value >> 32));
const uint32_t loValue = htonl(static_cast<uint32_t>(value & 0x00000000FFFFFFFF));
return static_cast<uint64_t>(hiValue) << 32 | static_cast<uint64_t>(loValue);
}
static const uint64_t ntohll(uint64_t value)
{
return htonll(value);
}
#endif
@@ -0,0 +1,79 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#include <AzNetworking/Utilities/IpAddress.h>
#include <AzNetworking/AzNetworking_Traits_Platform.h>
#include <AzNetworking/Serialization/ISerializer.h>
#include <AzNetworking/Utilities/Endian.h>
#include <AzNetworking/Utilities/NetworkIncludes.h>
namespace AzNetworking
{
namespace Platform
{
void InitIpAddress(uint32_t& ipv4Address, uint16_t& port,
const char* hostname, const char* service, ProtocolType type);
}
IpAddress::IpAddress(const char* hostname, const char* service, ProtocolType type)
{
Platform::InitIpAddress(m_ipv4Address, m_port, hostname, service, type);
}
IpAddress::IpAddress(const char* hostname, uint16_t port, ProtocolType type)
: IpAddress(hostname, nullptr, type)
{
m_port = port;
}
IpAddress::IpAddress(uint8_t quadA, uint8_t quadB, uint8_t quadC, uint8_t quadD, uint16_t port)
: m_ipv4Address((quadA << 24) | (quadB << 16) | (quadC << 8) | quadD)
, m_port(port)
{
;
}
IpAddress::IpAddress(ByteOrder byteOrder, uint32_t address, uint16_t port)
: m_ipv4Address((byteOrder == ByteOrder::Network) ? ntohl(address) : address)
, m_port((byteOrder == ByteOrder::Network) ? ntohs(port) : port)
{
;
}
uint32_t IpAddress::GetAddress(ByteOrder byteOrder) const
{
return (byteOrder == ByteOrder::Network) ? htonl(m_ipv4Address) : m_ipv4Address;
}
uint16_t IpAddress::GetPort(ByteOrder byteOrder) const
{
return (byteOrder == ByteOrder::Network) ? htons(m_port) : m_port;
}
IpAddress::IpString IpAddress::GetString() const
{
return IpString::format("%hhu.%hhu.%hhu.%hhu:%hu", GetQuadA(), GetQuadB(), GetQuadC(), GetQuadD(), GetPort(ByteOrder::Host));
}
IpAddress::IpString IpAddress::GetIpString() const
{
return IpString::format("%hhu.%hhu.%hhu.%hhu", GetQuadA(), GetQuadB(), GetQuadC(), GetQuadD());
}
bool IpAddress::Serialize(ISerializer& serializer)
{
serializer.Serialize(m_ipv4Address, "Ipv4Address");
serializer.Serialize(m_port, "Port");
return serializer.IsValid();
}
}
@@ -0,0 +1,146 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#pragma once
#include <stdint.h>
#include <AzCore/std/hash.h>
#include <AzCore/std/string/fixed_string.h>
namespace AzNetworking
{
class ISerializer;
enum class ByteOrder
{
Host
, Network
};
enum class ProtocolType
{
Tcp
, Udp
};
//! @class IpAddress
//! @brief Wrapper for dealing with internet Ip addresses.
class IpAddress
{
public:
static constexpr uint32_t MaxIpStringLength = 32;
using IpString = AZStd::fixed_string<MaxIpStringLength>;
IpAddress() = default;
//! Construct from a string hostname.
//! @param hostname hostname to convert to an IpAddress "amazon.com" or "127.0.0.1"
//! @param service service or port number "80" or "http"
IpAddress(const char* hostname, const char* service, ProtocolType type);
//! Construct from a string hostname.
//! @param hostname hostname to convert to an IpAddress "amazon.com" or "127.0.0.1"
//! @param port the port number
IpAddress(const char* hostname, uint16_t port, ProtocolType type);
//! Construct from a set of Ipv4 quads.
//! @param quadA first quad of the address
//! @param quadB second quad of the address
//! @param quadC third quad of the address
//! @param quadD forth quad of the address
//! @param port port number given in host byte order
IpAddress(uint8_t quadA, uint8_t quadB, uint8_t quadC, uint8_t quadD, uint16_t port);
//! Construct from an IPv4Address and port number given in the provided byte order.
//! @param byteOrder the byte order of the provided parameters
//! @param address IPv4Address given in host byte order
//! @param port port number given in host byte order
IpAddress(ByteOrder byteOrder, uint32_t address, uint16_t port);
virtual ~IpAddress() = default;
//! Returns the address in requested byte order
//! @return internal IPv4Address in requested byte order
uint32_t GetAddress(ByteOrder byteOrder) const;
//! Returns the port number in requested byte order
//! @return internal port number in requested byte order
uint16_t GetPort(ByteOrder byteOrder) const;
//! Return the first dotted quad of the internal Ipv4Address
//! @return first dotted quad of the internal Ipv4Address
uint8_t GetQuadA() const;
//! Return the second dotted quad of the internal Ipv4Address
//! @return second dotted quad of the internal Ipv4Address
uint8_t GetQuadB() const;
//! Return the third dotted quad of the internal Ipv4Address
//! @return third dotted quad of the internal Ipv4Address
uint8_t GetQuadC() const;
//! Return the forth dotted quad of the internal Ipv4Address
//! @return forth dotted quad of the internal Ipv4Address
uint8_t GetQuadD() const;
//! Returns the address in a human readable string form.
//! @return the address in a human readable string form
IpString GetString() const;
//! Returns just the ip address with no port number in a human readable string form.
//! @return just the ip address with no port number in a human readable string form
IpString GetIpString() const;
//! Equality operator.
//! @param rhs base type value to compare against
//! @return boolean true if this == rhs
bool operator ==(const IpAddress& rhs) const;
//! Inequality operator
//! @param rhs base type value to compare against
//! @return boolean true if this != rhs
bool operator !=(const IpAddress& rhs) const;
//! Strictly less than operator.
//! @param rhs base type value to compare against
//! @return boolean true if this < rhs
bool operator < (const IpAddress& rhs) const;
//! Less than equal to operator.
//! @param rhs base type value to compare against
//! @return boolean true if this <= rhs
bool operator <= (const IpAddress& rhs) const;
//! Strictly greater than operator.
//! @param rhs base type value to compare against
//! @return boolean true if this > rhs
bool operator > (const IpAddress& rhs) const;
//! Greater than equal to operator.
//! @param rhs base type value to compare against
//! @return boolean true if this >= rhs
bool operator >= (const IpAddress& rhs) const;
//! Serializes the ipAddress using the provided serializer instance.
//! @param serializer ISerializer instance to use for serialization
//! @return boolean true for success, false for serialization failure
bool Serialize(ISerializer& serializer);
private:
uint32_t m_ipv4Address = 0;
uint16_t m_port = 0;
};
}
#include <AzNetworking/Utilities/IpAddress.inl>
@@ -0,0 +1,91 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#pragma once
namespace AzNetworking
{
inline uint8_t IpAddress::GetQuadA() const
{
return uint8_t((m_ipv4Address >> 24) & 0xFF);
}
inline uint8_t IpAddress::GetQuadB() const
{
return uint8_t((m_ipv4Address >> 16) & 0xFF);
}
inline uint8_t IpAddress::GetQuadC() const
{
return uint8_t((m_ipv4Address >> 8) & 0xFF);
}
inline uint8_t IpAddress::GetQuadD() const
{
return uint8_t((m_ipv4Address) & 0xFF);
}
inline bool IpAddress::operator ==(const IpAddress& rhs) const
{
return (m_ipv4Address == rhs.m_ipv4Address) && (m_port == rhs.m_port);
}
inline bool IpAddress::operator !=(const IpAddress& rhs) const
{
return (m_ipv4Address != rhs.m_ipv4Address) || (m_port != rhs.m_port);
}
inline bool IpAddress::operator < (const IpAddress& rhs) const
{
if (m_ipv4Address == rhs.m_ipv4Address)
{
return m_port < rhs.m_port;
}
return m_ipv4Address < rhs.m_ipv4Address;
}
inline bool IpAddress::operator <=(const IpAddress& rhs) const
{
if (m_ipv4Address == rhs.m_ipv4Address)
{
return m_port <= rhs.m_port;
}
return m_ipv4Address <= rhs.m_ipv4Address;
}
inline bool IpAddress::operator > (const IpAddress& rhs) const
{
return !(*this <= rhs);
}
inline bool IpAddress::operator >=(const IpAddress& rhs) const
{
return !(*this < rhs);
}
}
namespace AZStd
{
template <>
struct hash<AzNetworking::IpAddress>
{
inline size_t operator()(const AzNetworking::IpAddress& key) const
{
const uint64_t address = key.GetAddress(AzNetworking::ByteOrder::Host);
const uint64_t port = key.GetPort(AzNetworking::ByteOrder::Host);
const uint64_t hashValue = (port << 32) | address;
return AZStd::hash<uint64_t>()(hashValue);
}
};
}
@@ -0,0 +1,126 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#include <AzNetworking/Utilities/NetworkCommon.h>
#include <AzNetworking/Utilities/NetworkIncludes.h>
#include <AzCore/Console/ILogger.h>
namespace AzNetworking
{
namespace Platform
{
bool SocketLayerInit();
bool SocketLayerShutdown();
bool SetSocketNonBlocking(SocketFd socketFd);
void CloseSocket(SocketFd socketFd);
int32_t GetLastNetworkError();
bool ErrorIsWouldBlock(int32_t errorCode);
bool ErrorIsForciblyClosed(int32_t errorCode, bool& ignoreError);
const char* GetNetworkErrorDesc(int32_t errorCode);
}
DisconnectReason GetDisconnectReasonForSocketResult(int32_t socketResult)
{
switch (socketResult)
{
case SocketOpResultError:
return DisconnectReason::Unknown;
case SocketOpResultDisconnected:
return DisconnectReason::RemoteHostClosedConnection;
case SocketOpResultErrorNotOpen:
return DisconnectReason::TransportError;
case SocketOpResultErrorNoSsl:
return DisconnectReason::SslFailure;
}
return DisconnectReason::MAX;
}
bool SocketLayerInit()
{
return Platform::SocketLayerInit();
}
bool SocketLayerShutdown()
{
return Platform::SocketLayerShutdown();
}
bool SetSocketNonBlocking(SocketFd socketFd)
{
return Platform::SetSocketNonBlocking(socketFd);
}
bool SetSocketNoDelay(SocketFd socketFd)
{
// Disable flow control
int flag = 1;
if (setsockopt(int32_t(socketFd), IPPROTO_TCP, TCP_NODELAY, (char *)&flag, sizeof(int)) != SocketOpResultSuccess)
{
const int32_t error = GetLastNetworkError();
AZLOG_ERROR("Failed to disable flow control for socket (%d:%s)", error, GetNetworkErrorDesc(error));
return false;
}
return true;
}
bool SetSocketBufferSizes(SocketFd socketFd, int32_t sendSize, int32_t recvSize)
{
if (setsockopt(int32_t(socketFd), SOL_SOCKET, SO_SNDBUF, (const char *)&sendSize, sizeof(sendSize)) != SocketOpResultSuccess)
{
const int32_t error = GetLastNetworkError();
AZLOG_ERROR("Failed to set socket receive buffer size for socket (%d:%s)", error, GetNetworkErrorDesc(error));
return false;
}
if (setsockopt(int32_t(socketFd), SOL_SOCKET, SO_RCVBUF, (const char *)&recvSize, sizeof(recvSize)) != SocketOpResultSuccess)
{
const int32_t error = GetLastNetworkError();
AZLOG_ERROR("Failed to set socket receive buffer size for socket (%d:%s)", error, GetNetworkErrorDesc(error));
return false;
}
return true;
}
void CloseSocket(SocketFd socketFd)
{
if (int32_t(socketFd) <= 0)
{
return;
}
Platform::CloseSocket(socketFd);
}
int32_t GetLastNetworkError()
{
return Platform::GetLastNetworkError();
}
bool ErrorIsWouldBlock(int32_t errorCode)
{
return Platform::ErrorIsWouldBlock(errorCode);
}
bool ErrorIsForciblyClosed(int32_t errorCode, bool& ignoreError)
{
return Platform::ErrorIsForciblyClosed(errorCode, ignoreError);
}
const char* GetNetworkErrorDesc(int32_t errorCode)
{
return Platform::GetNetworkErrorDesc(errorCode);
}
}
@@ -0,0 +1,122 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#pragma once
#include <AzCore/Time/ITime.h>
#include <AzCore/Console/IConsole.h>
#include <AzCore/RTTI/TypeSafeIntegral.h>
#include <AzCore/std/string/string.h>
#include <AzCore/std/string/fixed_string.h>
#include <AzNetworking/AzNetworking_Traits_Platform.h>
#include <AzNetworking/ConnectionLayer/ConnectionEnums.h>
#include <AzNetworking/ConnectionLayer/SequenceGenerator.h>
namespace AzNetworking
{
AZ_TYPE_SAFE_INTEGRAL(SequenceRolloverCount, uint16_t);
static constexpr SequenceRolloverCount InvalidSequenceRolloverCount = SequenceRolloverCount{uint16_t(0xFFFF)};
AZ_TYPE_SAFE_INTEGRAL(PacketId, uint32_t);
static constexpr PacketId InvalidPacketId = PacketId{0xFFFFFFFF};
//! Helper type to contain socket file descriptors.
AZ_TYPE_SAFE_INTEGRAL(SocketFd, int32_t);
static constexpr SocketFd InvalidSocketFd = SocketFd{-1};
//! Constants for wrapping os specific socket call results.
static const int32_t SocketOpResultSuccess = 0;
static const int32_t SocketOpResultError = -1;
static const int32_t SocketOpResultDisconnected = -2;
static const int32_t SocketOpResultErrorNotOpen = -3;
static const int32_t SocketOpResultErrorNoSsl = -4;
//! Returns a valid disconnect reason if the provided socket result requires a disconnect.
DisconnectReason GetDisconnectReasonForSocketResult(int32_t socketResult);
//! Helper method that creates an PacketId from a SequenceRolloverCount and SequenceId.
//! @param rolloverCount the number of rollovers detected for this sequenced variable
//! @param sequenceId sequence value to build the PacketId with
//! @return PacketId
PacketId MakePacketId(SequenceRolloverCount rolloverCount, SequenceId sequenceId);
//! Helper method that creates a SequenceId from an PacketId.
//! @param packetId packet id used in the conversion process
//! @return SequenceId
SequenceId ToSequenceId(PacketId packetId);
//! Helper method that extracts the rollover count from an PacketId.
//! @param packetId packet id used in the conversion process
//! @return SequenceRolloverCount
SequenceRolloverCount ToRolloverCount(PacketId packetId);
//! Initializes the network layer, required on some platforms.
//! Should be called once at program initialization, prior to performing any network operations
//! @return boolean true on success
bool SocketLayerInit();
//! Shuts down the network layer, required on some platforms.
//! Should be called once at program halt, after stopping all network resources
//! @return boolean true on success
bool SocketLayerShutdown();
//! Sets appropriate socket options to make the input socket non-blocking.
//! @param socketFd identifier of socket to set to non-blocking mode
//! @return boolean true on success
bool SetSocketNonBlocking(SocketFd socketFd);
//! Disables Tcp flow control for the provided socket.
//! @param socketFd identifier of the socket to disable flow control for
//! @return boolean true on success
bool SetSocketNoDelay(SocketFd socketFd);
//! Changes network socket receive buffer size.
//! @param socketFd identifier of the socket to change the receive buffer size of
//! @param sendSize requested send buffer size
//! @param recvSize requested receive buffer size
//! @return boolean true on success
bool SetSocketBufferSizes(SocketFd socketFd, int32_t sendSize, int32_t recvSize);
//! Closes the provided socket.
//! @param socketFd identifier of socket to close
void CloseSocket(SocketFd socketFd);
//! Returns the global error code from the last performed network operation, value is platform specific.
//! @return platform specific error result for the last performed network operation
int32_t GetLastNetworkError();
//! Returns true if the platform specific error code maps to a 'would block' error.
//! @return true if the platform specific error code maps to a 'would block' error
bool ErrorIsWouldBlock(int32_t errorCode);
//! Returns true if the platform specific error code maps to a 'forcibly closed' error.
//! @param ignoreError out value to indicate if the error should be ignored
//! @return true if the platform specific error code maps to a 'forcibly closed' error
bool ErrorIsForciblyClosed(int32_t errorCode, bool& ignoreError);
//! Returns a string description for the provided platform specific network error code.
//! @param errorCode platform specific error code to return the string result for
//! @return string description for the provided error code
const char *GetNetworkErrorDesc(int32_t errorCode);
//! Generates a string label suitable for container, doesn't allocate or use format strings.
//! @param value the integral index to generate a string label for
//! @return string label for the provided index
template <AZStd::size_t MAX_VALUE>
constexpr auto GenerateIndexLabel(AZStd::size_t value);
}
AZ_TYPE_SAFE_INTEGRAL_SERIALIZEBINDING(AzNetworking::SequenceRolloverCount);
AZ_TYPE_SAFE_INTEGRAL_SERIALIZEBINDING(AzNetworking::PacketId);
AZ_TYPE_SAFE_INTEGRAL_CVARBINDING(AZ::TimeMs);
#include <AzNetworking/Utilities/NetworkCommon.inl>
@@ -0,0 +1,48 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#pragma once
namespace AzNetworking
{
inline PacketId MakePacketId(SequenceRolloverCount rolloverCount, SequenceId sequenceId)
{
return PacketId(uint16_t(sequenceId) | (static_cast<uint32_t>(uint16_t(rolloverCount)) << 16));
}
inline SequenceId ToSequenceId(PacketId packetId)
{
return SequenceId(uint32_t(packetId) & 0xFFFF); // just grab the first two bytes
}
inline SequenceRolloverCount ToRolloverCount(PacketId packetId)
{
return SequenceRolloverCount(uint32_t(packetId) >> 16); // shift out the sequence portion of the packet id
}
template <AZStd::size_t MAX_VALUE>
inline constexpr auto GenerateIndexLabel(AZStd::size_t value)
{
constexpr AZStd::size_t NumHexDigits = AZ::RequiredBytesForValue<MAX_VALUE>() * 2;
constexpr char NibbleTable[16] = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' };
AZStd::fixed_string<NumHexDigits + 1> result;
result.resize_no_construct(NumHexDigits + 1);
for (AZStd::size_t i = 0; i < NumHexDigits; ++i)
{
result[NumHexDigits - i - 1] = NibbleTable[value & 0x0F];
value >>= 4;
}
result[NumHexDigits] = '\0'; // Guarantee null termination
return result;
}
}
@@ -0,0 +1,15 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#pragma once
#include <AzNetworking/Utilities/NetworkIncludes_Platform.h>
@@ -0,0 +1,207 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#pragma once
#include <AzCore/Math/Vector2.h>
#include <AzCore/Math/Vector3.h>
#include <AzCore/Math/Quaternion.h>
#include <AzNetworking/Serialization/ISerializer.h>
#include <AzNetworking/Utilities/NetworkCommon.h>
namespace AzNetworking
{
template <uint32_t NUM_ELEMENTS>
struct QuantizedValuesHelper;
template <>
struct QuantizedValuesHelper<1>
{
using ValueType = float;
using SimdType = AZ::Simd::Vec1;
static ValueType FloatsToValue(SimdType::FloatType vector, const float* quantizedValues);
static SimdType::FloatType ValueToSimd(const ValueType& value);
static float SelectElement(const ValueType& value, int32_t index);
};
template <>
struct QuantizedValuesHelper<2>
{
using ValueType = AZ::Vector2;
using SimdType = AZ::Simd::Vec2;
static ValueType FloatsToValue(SimdType::FloatType vector, const float* quantizedValues);
static SimdType::FloatType ValueToSimd(const ValueType& value);
static float SelectElement(const ValueType& value, int32_t index);
};
template <>
struct QuantizedValuesHelper<3>
{
using ValueType = AZ::Vector3;
using SimdType = AZ::Simd::Vec3;
static ValueType FloatsToValue(SimdType::FloatType vector, const float* quantizedValues);
static SimdType::FloatType ValueToSimd(const ValueType& value);
static float SelectElement(const ValueType& value, int32_t index);
};
template <>
struct QuantizedValuesHelper<4>
{
using ValueType = AZ::Quaternion;
using SimdType = AZ::Simd::Vec4;
static ValueType FloatsToValue(SimdType::FloatType vector, const float* quantizedValues);
static SimdType::FloatType ValueToSimd(const ValueType& value);
static float SelectElement(const ValueType& value, int32_t index);
};
template <AZStd::size_t BYTE_COUNT> struct MaxSerializeValue { };
template <> struct MaxSerializeValue<4> { static constexpr AZStd::size_t Value = 0xFFFFFFFF - 1; };
template <> struct MaxSerializeValue<3> { static constexpr AZStd::size_t Value = 0x00FFFFFF - 1; };
template <> struct MaxSerializeValue<2> { static constexpr AZStd::size_t Value = 0x0000FFFF - 1; };
template <> struct MaxSerializeValue<1> { static constexpr AZStd::size_t Value = 0x000000FF - 1; };
template <AZStd::size_t NUM_ELEMENTS, AZStd::size_t NUM_BYTES, int32_t MIN_VALUE, int32_t MAX_VALUE>
class QuantizedValues
{
public:
using SelfType = QuantizedValues<NUM_ELEMENTS, NUM_BYTES, MIN_VALUE, MAX_VALUE>;
using SimdTypef = typename QuantizedValuesHelper<NUM_ELEMENTS>::SimdType::FloatType;
using SimdTypei = typename QuantizedValuesHelper<NUM_ELEMENTS>::SimdType::Int32Type;
using ValueType = typename QuantizedValuesHelper<NUM_ELEMENTS>::ValueType;
//! Default constructor.
QuantizedValues();
//! Copy construct from same type.
//! @param value instance to construct from
QuantizedValues(const SelfType& value);
//! Copy construct from float.
//! @param value vector value to construct from
explicit QuantizedValues(const ValueType& value);
//! Assignment from same type.
//! @param rhs instance to assign from
SelfType& operator =(const SelfType& rhs);
//! Assignment from value value.
//! @param rhs value to assign from
SelfType& operator =(const ValueType& rhs);
//! Const underlying type operator.
//! @return underlying value
operator ValueType() const;
//! Sum operator.
//! @param value second operand
//! @return result of operation
ValueType operator +(const ValueType& value) const;
//! Difference operator.
//! @param value second operand
//! @return result of operation
ValueType operator -(const ValueType& value) const;
//! Product operator.
//! @param value second operand
//! @return result of operation
ValueType operator *(const ValueType& value) const;
//! Division operator.
//! @param value second operand
//! @return result of operation
ValueType operator /(const ValueType& value) const;
//! Sum operator.
//! @param value second operand
//! @return result of operation
SelfType& operator +=(const ValueType& value);
//! Difference operator.
//! @param value second operand
//! @return result of operation
SelfType& operator -=(const ValueType& value);
//! Product operator.
//! @param value second operand
//! @return result of operation
SelfType& operator *=(const ValueType& value);
//! Division operator.
//! @param value second operand
//! @return result of operation
SelfType& operator /=(const ValueType& value);
//! Equality operator.
//! @param rhs base type value to compare against
//! @return boolean true if this == rhs
bool operator ==(const SelfType& rhs) const;
//! Equality operator.
//! @param rhs base type value to compare against
//! @return boolean true if this == rhs
bool operator ==(const ValueType& rhs) const;
//! Inequality operator.
//! @param rhs base type value to compare against
//! @return boolean true if this != rhs
bool operator !=(const SelfType& rhs) const;
//! Inequality operator.
//! @param rhs base type value to compare against
//! @return boolean true if this != rhs
bool operator !=(const ValueType& rhs) const;
//! Retrieves the quantized integral value used during serialization of this QuantizedValues instance.
//! @return the quantized integral value used during serialization of this QuantizedValues instance
const uint32_t* GetQuantizedIntegralValues() const;
//! Base serialize method for all serializable structures or classes to implement.
//! @param serializer ISerializer instance to use for serialization
//! @return boolean true for success, false for serialization failure
bool Serialize(ISerializer& serializer);
private:
//! Helper method to convert and store an un-quantized value.
//! @param value the input value to convert and store
void Set(const ValueType& value);
//! Takes a quantized integral value and stores the floating point representation.
void DecodeQuantizedValues();
# if defined AZ_COMPILER_MSVC
# pragma warning(push)
# pragma warning(disable:4201) // anonymous union
# endif
union
{
float m_quantizedValues[NUM_ELEMENTS];
SimdTypef m_quantizedVector;
};
union
{
uint32_t m_serializeValues[NUM_ELEMENTS];
SimdTypei m_serializeVector;
};
# if defined AZ_COMPILER_MSVC
# pragma warning(pop)
# endif
template <AZStd::size_t NUM_ELEMENTS2, AZStd::size_t NUM_BYTES2, int32_t MIN_VALUE2, int32_t MAX_VALUE2>
friend struct QuantizedValuesConversionHelper;
};
}
#include <AzNetworking/Utilities/QuantizedValues.inl>
@@ -0,0 +1,336 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#pragma once
namespace AzNetworking
{
inline QuantizedValuesHelper<1>::ValueType QuantizedValuesHelper<1>::FloatsToValue(SimdType::FloatType, const float* quantizedValues)
{
return quantizedValues[0];
}
inline typename QuantizedValuesHelper<1>::SimdType::FloatType QuantizedValuesHelper<1>::ValueToSimd(const ValueType& value)
{
return SimdType::Splat(value);
}
inline float QuantizedValuesHelper<1>::SelectElement(const ValueType& value, [[maybe_unused]] int32_t index)
{
return value;
}
inline typename QuantizedValuesHelper<2>::ValueType QuantizedValuesHelper<2>::FloatsToValue(SimdType::FloatType vector, [[maybe_unused]] const float* quantizedValues)
{
return ValueType(vector);
}
inline typename QuantizedValuesHelper<2>::SimdType::FloatType QuantizedValuesHelper<2>::ValueToSimd(const ValueType& value)
{
return value.GetSimdValue();
}
inline float QuantizedValuesHelper<2>::SelectElement(const ValueType& value, int32_t index)
{
return value.GetElement(index);
}
inline typename QuantizedValuesHelper<3>::ValueType QuantizedValuesHelper<3>::FloatsToValue(SimdType::FloatType vector, [[maybe_unused]] const float* quantizedValues)
{
return ValueType(vector);
}
inline typename QuantizedValuesHelper<3>::SimdType::FloatType QuantizedValuesHelper<3>::ValueToSimd(const ValueType& value)
{
return value.GetSimdValue();
}
inline float QuantizedValuesHelper<3>::SelectElement(const ValueType& value, int32_t index)
{
return value.GetElement(index);
}
inline typename QuantizedValuesHelper<4>::ValueType QuantizedValuesHelper<4>::FloatsToValue(SimdType::FloatType vector, [[maybe_unused]] const float* quantizedValues)
{
return ValueType(vector);
}
inline typename QuantizedValuesHelper<4>::SimdType::FloatType QuantizedValuesHelper<4>::ValueToSimd(const ValueType& value)
{
return value.GetSimdValue();
}
inline float QuantizedValuesHelper<4>::SelectElement(const ValueType& value, int32_t index)
{
return value.GetElement(index);
}
template <AZStd::size_t NUM_ELEMENTS, AZStd::size_t NUM_BYTES, int32_t MIN_VALUE, int32_t MAX_VALUE>
inline QuantizedValues<NUM_ELEMENTS, NUM_BYTES, MIN_VALUE, MAX_VALUE>::QuantizedValues()
{
m_quantizedVector = QuantizedValuesHelper<NUM_ELEMENTS>::SimdType::ZeroFloat();
m_serializeVector = QuantizedValuesHelper<NUM_ELEMENTS>::SimdType::ZeroInt();
}
template <AZStd::size_t NUM_ELEMENTS, AZStd::size_t NUM_BYTES, int32_t MIN_VALUE, int32_t MAX_VALUE>
inline QuantizedValues<NUM_ELEMENTS, NUM_BYTES, MIN_VALUE, MAX_VALUE>::QuantizedValues(const SelfType& value)
{
m_quantizedVector = value.m_quantizedVector;
m_serializeVector = value.m_serializeVector;
}
template <AZStd::size_t NUM_ELEMENTS, AZStd::size_t NUM_BYTES, int32_t MIN_VALUE, int32_t MAX_VALUE>
inline QuantizedValues<NUM_ELEMENTS, NUM_BYTES, MIN_VALUE, MAX_VALUE>::QuantizedValues(const ValueType& value)
{
Set(value);
}
template <AZStd::size_t NUM_ELEMENTS, AZStd::size_t NUM_BYTES, int32_t MIN_VALUE, int32_t MAX_VALUE>
inline QuantizedValues<NUM_ELEMENTS, NUM_BYTES, MIN_VALUE, MAX_VALUE>& QuantizedValues<NUM_ELEMENTS, NUM_BYTES, MIN_VALUE, MAX_VALUE>::operator =(const SelfType& rhs)
{
m_quantizedVector = rhs.m_quantizedVector;
m_serializeVector = rhs.m_serializeVector;
return *this;
}
template <AZStd::size_t NUM_ELEMENTS, AZStd::size_t NUM_BYTES, int32_t MIN_VALUE, int32_t MAX_VALUE>
inline QuantizedValues<NUM_ELEMENTS, NUM_BYTES, MIN_VALUE, MAX_VALUE>& QuantizedValues<NUM_ELEMENTS, NUM_BYTES, MIN_VALUE, MAX_VALUE>::operator = (const ValueType& rhs)
{
Set(rhs);
return *this;
}
template <AZStd::size_t NUM_ELEMENTS, AZStd::size_t NUM_BYTES, int32_t MIN_VALUE, int32_t MAX_VALUE>
inline QuantizedValues<NUM_ELEMENTS, NUM_BYTES, MIN_VALUE, MAX_VALUE>::operator ValueType() const
{
return QuantizedValuesHelper<NUM_ELEMENTS>::FloatsToValue(m_quantizedVector, m_quantizedValues);
}
template <AZStd::size_t NUM_ELEMENTS, AZStd::size_t NUM_BYTES, int32_t MIN_VALUE, int32_t MAX_VALUE>
inline typename QuantizedValues<NUM_ELEMENTS, NUM_BYTES, MIN_VALUE, MAX_VALUE>::ValueType QuantizedValues<NUM_ELEMENTS, NUM_BYTES, MIN_VALUE, MAX_VALUE>::operator +(const ValueType& value) const
{
return QuantizedValuesHelper<NUM_ELEMENTS>::FloatsToValue(m_quantizedVector, m_quantizedValues) + value;
}
template <AZStd::size_t NUM_ELEMENTS, AZStd::size_t NUM_BYTES, int32_t MIN_VALUE, int32_t MAX_VALUE>
inline typename QuantizedValues<NUM_ELEMENTS, NUM_BYTES, MIN_VALUE, MAX_VALUE>::ValueType QuantizedValues<NUM_ELEMENTS, NUM_BYTES, MIN_VALUE, MAX_VALUE>::operator -(const ValueType& value) const
{
return QuantizedValuesHelper<NUM_ELEMENTS>::FloatsToValue(m_quantizedVector, m_quantizedValues) - value;
}
template <AZStd::size_t NUM_ELEMENTS, AZStd::size_t NUM_BYTES, int32_t MIN_VALUE, int32_t MAX_VALUE>
inline typename QuantizedValues<NUM_ELEMENTS, NUM_BYTES, MIN_VALUE, MAX_VALUE>::ValueType QuantizedValues<NUM_ELEMENTS, NUM_BYTES, MIN_VALUE, MAX_VALUE>::operator *(const ValueType& value) const
{
return QuantizedValuesHelper<NUM_ELEMENTS>::FloatsToValue(m_quantizedVector, m_quantizedValues) * value;
}
template <AZStd::size_t NUM_ELEMENTS, AZStd::size_t NUM_BYTES, int32_t MIN_VALUE, int32_t MAX_VALUE>
inline typename QuantizedValues<NUM_ELEMENTS, NUM_BYTES, MIN_VALUE, MAX_VALUE>::ValueType QuantizedValues<NUM_ELEMENTS, NUM_BYTES, MIN_VALUE, MAX_VALUE>::operator /(const ValueType& value) const
{
return QuantizedValuesHelper<NUM_ELEMENTS>::FloatsToValue(m_quantizedVector, m_quantizedValues) / value;
}
template <AZStd::size_t NUM_ELEMENTS, AZStd::size_t NUM_BYTES, int32_t MIN_VALUE, int32_t MAX_VALUE>
inline QuantizedValues<NUM_ELEMENTS, NUM_BYTES, MIN_VALUE, MAX_VALUE>& QuantizedValues<NUM_ELEMENTS, NUM_BYTES, MIN_VALUE, MAX_VALUE>::operator +=(const ValueType& value)
{
Set(QuantizedValuesHelper<NUM_ELEMENTS>::FloatsToValue(m_quantizedVector, m_quantizedValues) + value);
return *this;
}
template <AZStd::size_t NUM_ELEMENTS, AZStd::size_t NUM_BYTES, int32_t MIN_VALUE, int32_t MAX_VALUE>
inline QuantizedValues<NUM_ELEMENTS, NUM_BYTES, MIN_VALUE, MAX_VALUE>& QuantizedValues<NUM_ELEMENTS, NUM_BYTES, MIN_VALUE, MAX_VALUE>::operator -=(const ValueType& value)
{
Set(QuantizedValuesHelper<NUM_ELEMENTS>::FloatsToValue(m_quantizedVector, m_quantizedValues) - value);
return *this;
}
template <AZStd::size_t NUM_ELEMENTS, AZStd::size_t NUM_BYTES, int32_t MIN_VALUE, int32_t MAX_VALUE>
inline QuantizedValues<NUM_ELEMENTS, NUM_BYTES, MIN_VALUE, MAX_VALUE>& QuantizedValues<NUM_ELEMENTS, NUM_BYTES, MIN_VALUE, MAX_VALUE>::operator *=(const ValueType& value)
{
Set(QuantizedValuesHelper<NUM_ELEMENTS>::FloatsToValue(m_quantizedVector, m_quantizedValues) * value);
return *this;
}
template <AZStd::size_t NUM_ELEMENTS, AZStd::size_t NUM_BYTES, int32_t MIN_VALUE, int32_t MAX_VALUE>
inline QuantizedValues<NUM_ELEMENTS, NUM_BYTES, MIN_VALUE, MAX_VALUE>& QuantizedValues<NUM_ELEMENTS, NUM_BYTES, MIN_VALUE, MAX_VALUE>::operator /=(const ValueType& value)
{
Set(QuantizedValuesHelper<NUM_ELEMENTS>::FloatsToValue(m_quantizedVector, m_quantizedValues) / value);
return *this;
}
template <AZStd::size_t NUM_ELEMENTS, AZStd::size_t NUM_BYTES, int32_t MIN_VALUE, int32_t MAX_VALUE>
inline bool QuantizedValues<NUM_ELEMENTS, NUM_BYTES, MIN_VALUE, MAX_VALUE>::operator ==(const SelfType& rhs) const
{
for (AZStd::size_t i = 0; i < NUM_ELEMENTS; ++i)
{
if (m_serializeValues[i] != rhs.m_serializeValues[i])
{
return false;
}
}
return true;
}
template <AZStd::size_t NUM_ELEMENTS, AZStd::size_t NUM_BYTES, int32_t MIN_VALUE, int32_t MAX_VALUE>
inline bool QuantizedValues<NUM_ELEMENTS, NUM_BYTES, MIN_VALUE, MAX_VALUE>::operator ==(const ValueType& rhs) const
{
SelfType selfType(rhs);
return (*this == selfType);
}
template <AZStd::size_t NUM_ELEMENTS, AZStd::size_t NUM_BYTES, int32_t MIN_VALUE, int32_t MAX_VALUE>
inline bool QuantizedValues<NUM_ELEMENTS, NUM_BYTES, MIN_VALUE, MAX_VALUE>::operator !=(const SelfType& rhs) const
{
for (AZStd::size_t i = 0; i < NUM_ELEMENTS; ++i)
{
if (m_serializeValues[i] != rhs.m_serializeValues[i])
{
return true;
}
}
return false;
}
template <AZStd::size_t NUM_ELEMENTS, AZStd::size_t NUM_BYTES, int32_t MIN_VALUE, int32_t MAX_VALUE>
inline bool QuantizedValues<NUM_ELEMENTS, NUM_BYTES, MIN_VALUE, MAX_VALUE>::operator !=(const ValueType& rhs) const
{
SelfType selfType(rhs);
return (*this != selfType);
}
template <AZStd::size_t NUM_ELEMENTS, AZStd::size_t NUM_BYTES, int32_t MIN_VALUE, int32_t MAX_VALUE>
inline const uint32_t* QuantizedValues<NUM_ELEMENTS, NUM_BYTES, MIN_VALUE, MAX_VALUE>::GetQuantizedIntegralValues() const
{
return m_serializeValues;
}
template <AZStd::size_t NUM_ELEMENTS, AZStd::size_t NUM_BYTES, int32_t MIN_VALUE, int32_t MAX_VALUE>
inline bool QuantizedValues<NUM_ELEMENTS, NUM_BYTES, MIN_VALUE, MAX_VALUE>::Serialize(ISerializer& serializer)
{
using SerializeType = typename AZ::SizeType<NUM_BYTES, false>::Type;
for (AZStd::size_t i = 0; i < NUM_ELEMENTS; ++i)
{
SerializeType serializedValue = static_cast<SerializeType>(m_serializeValues[i]);
#ifdef AZ_COMPILER_MSVC
# pragma warning(push)
# pragma warning(disable: 4127) // conditional expression is constant
#endif
if (NUM_BYTES == 3)
#ifdef AZ_COMPILER_MSVC
# pragma warning(pop)
#endif
{
uint8_t lowByte = static_cast<uint8_t>((serializedValue & 0x000000FF) );
uint8_t midByte = static_cast<uint8_t>((serializedValue & 0x0000FF00) >> 8);
uint8_t hiByte = static_cast<uint8_t>((serializedValue & 0x00FF0000) >> 16);
serializer.Serialize(lowByte, GenerateIndexLabel<NUM_ELEMENTS>(i * 3 + 0).c_str());
serializer.Serialize(midByte, GenerateIndexLabel<NUM_ELEMENTS>(i * 3 + 1).c_str());
serializer.Serialize(hiByte, GenerateIndexLabel<NUM_ELEMENTS>(i * 3 + 2).c_str());
serializedValue = lowByte | (midByte << 8) | (hiByte << 16);
}
else
{
serializer.Serialize(serializedValue, GenerateIndexLabel<NUM_ELEMENTS>(i).c_str());
}
AZ_Assert((serializer.GetSerializerMode() == SerializerMode::WriteToObject) || (static_cast<SerializeType>(m_serializeValues[i]) == serializedValue),
"If we're reading, the temporary serialized value must match the instance value");
m_serializeValues[i] = serializedValue;
}
if ((serializer.GetSerializerMode() == SerializerMode::WriteToObject))
{
DecodeQuantizedValues();
}
return serializer.IsValid();
}
template <AZStd::size_t NUM_ELEMENTS, AZStd::size_t NUM_BYTES, int32_t MIN_VALUE, int32_t MAX_VALUE>
struct QuantizedValuesConversionHelper
{
using SelfType = QuantizedValues<NUM_ELEMENTS, NUM_BYTES, MIN_VALUE, MAX_VALUE>;
using SimdTypef = typename QuantizedValuesHelper<NUM_ELEMENTS>::SimdType::FloatType;
using SimdTypei = typename QuantizedValuesHelper<NUM_ELEMENTS>::SimdType::Int32Type;
using ValueType = typename QuantizedValuesHelper<NUM_ELEMENTS>::ValueType;
static constexpr uint32_t MaxSerializedIntValue = MaxSerializeValue<NUM_BYTES>::Value;
static inline void Set(SelfType& quantizedValues, const ValueType& value)
{
using SimdType = typename QuantizedValuesHelper<NUM_ELEMENTS>::SimdType;
const SimdTypef maximumInt = SimdType::Splat(static_cast<float>(MaxSerializedIntValue));
const SimdTypef minimumFloat = SimdType::Splat(static_cast<float>(MIN_VALUE));
const SimdTypef convertToInt = SimdType::Splat(static_cast<float>(MaxSerializedIntValue) / static_cast<float>(MAX_VALUE - MIN_VALUE));
const SimdTypef floatData = QuantizedValuesHelper<NUM_ELEMENTS>::ValueToSimd(value);
const SimdTypef readjusted = SimdType::Mul(convertToInt, SimdType::Sub(floatData, minimumFloat));
quantizedValues.m_serializeVector = SimdType::ConvertToInt(SimdType::Clamp(readjusted, SimdType::ZeroFloat(), maximumInt));
}
static inline void DecodeQuantizedValues(SelfType& quantizedValues)
{
using SimdType = typename QuantizedValuesHelper<NUM_ELEMENTS>::SimdType;
const SimdTypef minimumFloat = SimdType::Splat(static_cast<float>(MIN_VALUE));
const SimdTypef convertToFloat = SimdType::Splat(static_cast<float>(MAX_VALUE - MIN_VALUE) / static_cast<float>(MaxSerializedIntValue));
const SimdTypef quantized = SimdType::ConvertToFloat(quantizedValues.m_serializeVector); // Convert the integral value back into a float
quantizedValues.m_quantizedVector = SimdType::Add(minimumFloat, SimdType::Mul(quantized, convertToFloat));
}
};
template <AZStd::size_t NUM_ELEMENTS, int32_t MIN_VALUE, int32_t MAX_VALUE>
struct QuantizedValuesConversionHelper<NUM_ELEMENTS, 4, MIN_VALUE, MAX_VALUE>
{
using SelfType = QuantizedValues<NUM_ELEMENTS, 4, MIN_VALUE, MAX_VALUE>;
using SimdTypef = typename QuantizedValuesHelper<NUM_ELEMENTS>::SimdType::FloatType;
using SimdTypei = typename QuantizedValuesHelper<NUM_ELEMENTS>::SimdType::Int32Type;
using ValueType = typename QuantizedValuesHelper<NUM_ELEMENTS>::ValueType;
static constexpr uint32_t MaxSerializedIntValue = MaxSerializeValue<4>::Value;
static inline void Set(SelfType& quantizedValues, const ValueType& value)
{
constexpr double maximumInt = static_cast<double>(MaxSerializedIntValue);
constexpr double minimumFloat = static_cast<double>(MIN_VALUE);
constexpr double convertToInt = maximumInt / static_cast<double>(MAX_VALUE - MIN_VALUE);
for (int32_t i = 0; i < static_cast<int32_t>(NUM_ELEMENTS); ++i)
{
const double readjusted = convertToInt * (QuantizedValuesHelper<NUM_ELEMENTS>::SelectElement(value, i) - minimumFloat);
quantizedValues.m_serializeValues[i] = static_cast<uint32_t>(AZStd::clamp(readjusted, 0.0, maximumInt));
}
}
static inline void DecodeQuantizedValues(SelfType& quantizedValues)
{
constexpr double maximumInt = static_cast<double>(MaxSerializedIntValue);
constexpr double minimumFloat = static_cast<double>(MIN_VALUE);
constexpr double convertToFloat = static_cast<double>(MAX_VALUE - MIN_VALUE) / maximumInt;
for (int32_t i = 0; i < static_cast<int32_t>(NUM_ELEMENTS); ++i)
{
const double quantized = static_cast<double>(quantizedValues.m_serializeValues[i]);
quantizedValues.m_quantizedValues[i] = static_cast<float>(minimumFloat + quantized * convertToFloat);
}
}
};
template <AZStd::size_t NUM_ELEMENTS, AZStd::size_t NUM_BYTES, int32_t MIN_VALUE, int32_t MAX_VALUE>
inline void QuantizedValues<NUM_ELEMENTS, NUM_BYTES, MIN_VALUE, MAX_VALUE>::Set(const ValueType& value)
{
QuantizedValuesConversionHelper<NUM_ELEMENTS, NUM_BYTES, MIN_VALUE, MAX_VALUE>::Set(*this, value);
DecodeQuantizedValues();
}
template <AZStd::size_t NUM_ELEMENTS, AZStd::size_t NUM_BYTES, int32_t MIN_VALUE, int32_t MAX_VALUE>
inline void QuantizedValues<NUM_ELEMENTS, NUM_BYTES, MIN_VALUE, MAX_VALUE>::DecodeQuantizedValues()
{
QuantizedValuesConversionHelper<NUM_ELEMENTS, NUM_BYTES, MIN_VALUE, MAX_VALUE>::DecodeQuantizedValues(*this);
}
}
@@ -0,0 +1,74 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#include <AzNetworking/Utilities/TimedThread.h>
#include <AzCore/Console/ILogger.h>
namespace AzNetworking
{
TimedThread::TimedThread(const char* name, AZ::TimeMs updateRate)
: m_updateRate(updateRate)
{
m_threadDesc.m_name = name;
}
TimedThread::~TimedThread()
{
AZ_Assert(!IsRunning(), "You must stop and join your thread before destructing it");
}
void TimedThread::Start()
{
m_running = true;
m_joinable = true;
m_thread = AZStd::thread([this]()
{
OnStart();
while (m_running)
{
const AZ::TimeMs startTimeMs = AZ::GetElapsedTimeMs();
OnUpdate(m_updateRate);
const AZ::TimeMs updateTimeMs = AZ::GetElapsedTimeMs() - startTimeMs;
if (m_updateRate > updateTimeMs)
{
AZStd::chrono::milliseconds sleepTimeMs(static_cast<int64_t>(m_updateRate - updateTimeMs));
AZStd::this_thread::sleep_for(sleepTimeMs);
}
else if (m_updateRate < updateTimeMs)
{
AZLOG_INFO("TimedThread bled %d ms", aznumeric_cast<int32_t>(updateTimeMs - m_updateRate));
}
}
OnStop();
}, &m_threadDesc);
}
void TimedThread::Stop()
{
m_running = false;
}
void TimedThread::Join()
{
if (m_joinable && m_thread.joinable())
{
m_thread.join();
m_joinable = false;
}
}
bool TimedThread::IsRunning() const
{
return m_running;
}
}
@@ -0,0 +1,64 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#pragma once
#include <AzCore/std/parallel/thread.h>
#include <AzCore/Time/ITime.h>
namespace AzNetworking
{
//! @class TimedThread
//! @brief A thread wrapper class that makes it easy to have a time throttled thread.
class TimedThread
{
public:
TimedThread(const char* name, AZ::TimeMs updateRate);
virtual ~TimedThread();
//! Starts the thread.
void Start();
//! Stops the thread.
void Stop();
//! Joins the thread.
void Join();
//! Returns true if the thread is running.
//! @return boolean true if the thread is running, false otherwise
bool IsRunning() const;
protected:
//! Invoked on thread start.
virtual void OnStart() = 0;
//! Invoked on thread stop.
virtual void OnStop() = 0;
//! Invoked on thread update.
//! @param updateRateMs The amount of time the thread can spend in OnUpdate in ms
virtual void OnUpdate(AZ::TimeMs updateRateMs) = 0;
private:
AZ_DISABLE_COPY_MOVE(TimedThread);
AZ::TimeMs m_updateRate;
AZStd::thread_desc m_threadDesc;
AZStd::thread m_thread;
AZStd::atomic<bool> m_joinable = false;
AZStd::atomic<bool> m_running = false;
};
}