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,23 @@
#pragma once
#include <AzCore/Console/ILogger.h>
#include <AzNetworking/Serialization/ISerializer.h>
#include <AzNetworking/PacketLayer/IPacketHeader.h>
#include <AzNetworking/ConnectionLayer/IConnection.h>
#include "{{ (outputFile|basename).replace(".AutoPacketDispatcher.h", ".AutoPackets.h") }}"
{% for xml in dataFiles %}
namespace {{ xml.attrib['Name'] }}
{
//! Request dispatcher for incoming packets.
//! @param connection pointer to the connection that sent this request
//! @param packetHeader the header of the received packet
//! @param serializer serializer containing the raw packet payload
//! @param handler the handler used to handle the received packet
//! @return boolean true on successful dispatch, false if the request was not handled
template <typename HANDLER>
bool DispatchPacket(AzNetworking::IConnection* connection, const AzNetworking::IPacketHeader& packetHeader, AzNetworking::ISerializer& serializer, HANDLER& handler);
}
{% endfor %}
#include "{{ (outputFile|basename).replace(".AutoPacketDispatcher.h", ".AutoPacketDispatcher.inl") }}"
@@ -0,0 +1,25 @@
{% for xml in dataFiles %}
namespace {{ xml.attrib['Name'] }}
{
template <typename HANDLER>
inline bool DispatchPacket(AzNetworking::IConnection* connection, const AzNetworking::IPacketHeader& packetHeader, AzNetworking::ISerializer& serializer, HANDLER& handler)
{
switch (aznumeric_cast<int32_t>(packetHeader.GetPacketType()))
{
{% for Packet in xml.iter('Packet') %}
case aznumeric_cast<int32_t>({{ Packet.attrib['Name'] }}::Type):
{
AZLOG(Debug_DispatchPackets, "Received packet %s", "{{ Packet.attrib['Name'] }}");
{{ Packet.attrib['Name'] }} packet;
if (!serializer.Serialize(packet, "Packet"))
{
return false;
}
return handler.HandleRequest(connection, packetHeader, packet);
}
{% endfor %}
}
return false;
}
}
{% endfor %}
@@ -0,0 +1,111 @@
{#
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.
#}
{% macro ElementType(element) -%}
{% if ('Container' not in element.attrib) or (element.attrib['Container'] == 'None') %}
{{ element.attrib['Type'] }}{% elif element.attrib['Container'] == 'Array' %}
AZStd::array<{{ element.attrib['Type'] }}, {{ element.attrib['Count'] }}>{% elif element.attrib['Container'] == 'Vector' %}
AZStd::fixed_vector<{{ element.attrib['Type'] }}, {{ element.attrib['Count'] }}>{% endif %}
{%- endmacro %}
{% macro CamelCase(text) %}{{ text[0] | upper}}{{ text[1:] }}{% endmacro %}
{% macro DeclarePacket(name, packetNode, type) %}
//! @class {{ name }}
//! @brief {{ packetNode.attrib['Desc'] }}.
class {{ name }} final
: public AzNetworking::IPacket
{
public:
static constexpr AzNetworking::PacketType Type = aznumeric_cast<AzNetworking::PacketType>({{ type }});
{{ name }}() = default;
{% if (packetNode.getchildren()) | len > 0 %}
explicit {{ name }}
(
{% for Member in packetNode %}
{% if loop.first %} {% else %}, {% endif %}{{ ElementType(Member) }} {{ Member.attrib['Name'] }}
{% endfor %}
);
{% endif %}
~{{ name }}() override = default;
//! Equality operator, returns true if the current instance is equal to rhs.
//! @param rhs the {{ name }} instance to test for equality against
//! @return boolean true if equal, false if not
bool operator ==(const {{ name }}& rhs) const;
//! Inequality operator, returns true if the current instance is not equal to rhs.
//! @param rhs the {{ name }} instance to test for inequality against
//! @return boolean false if equal, true if not equal
bool operator !=(const {{ name }}& rhs) const;
{% for Member in packetNode.iter('Member') %}
//! Sets the value of {{ Member.attrib['Name'] }}.
//! @param value the value to set {{ Member.attrib['Name'] }} to
void Set{{ CamelCase(Member.attrib['Name']) }}(const {{ ElementType(Member) }}& value);
//! Gets the value of {{ Member.attrib['Name'] }}.
//! @return the value of {{ Member.attrib['Name'] }}
const {{ ElementType(Member) }}& Get{{ CamelCase(Member.attrib['Name']) }}() const;
//! Retrieves a non-const reference to the value of {{ Member.attrib['Name'] }}
//! @return a non-const reference to the value of {{ Member.attrib['Name'] }}
{{ ElementType(Member) }}& Modify{{ CamelCase(Member.attrib['Name']) }}();
{% endfor %}
//! IPacket interface
//! @{
AzNetworking::PacketType GetPacketType() const override;
AZStd::unique_ptr<AzNetworking::IPacket> Clone() const override;
bool Serialize(AzNetworking::ISerializer& serializer) override;
//! @}
{% if (packetNode.getchildren()) | len > 0 %}
private:
{% for Member in packetNode.iter('Member') %}
{{ ElementType(Member) }} m_{{ Member.attrib['Name'] }}{% if Member.attrib['Init'] %} = {{ Member.attrib['Init'] }}{% endif %};
{% endfor %}
{% endif %}
};
{% endmacro %}
#pragma once
#include <AzCore/RTTI/TypeInfo.h>
#include <AzNetworking/PacketLayer/IPacket.h>
#include <AzNetworking/Serialization/ISerializer.h>
#include <AzNetworking/ConnectionLayer/IConnection.h>
#include <AzNetworking/DataStructures/ByteBuffer.h>
{% for xml in dataFiles %}
{% for Include in xml.iter('Include') %}
#include <{{ Include.attrib['File'] }}>
{% endfor %}
{% endfor %}
{% for xml in dataFiles %}
namespace {{ xml.attrib['Name'] }}
{
enum class PacketType
{
START = aznumeric_cast<int32_t>({{ xml.attrib['PacketStart'] }})
{% for Packet in xml.iter('Packet') %}
, {{ Packet.attrib['Name'] }}
{% endfor %}
, MAX
};
{% for Packet in xml.iter('Packet') %}
{{ DeclarePacket(Packet.attrib['Name'], Packet, "PacketType::" + Packet.attrib['Name']) -}}
{% endfor %}
}
{% endfor %}
{% set inlineFile = "{0}.inl".format(((outputFile|basename)|splitext)[0]) %}
#include "{{ inlineFile }}"
@@ -0,0 +1,51 @@
{#
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.
#}
{% macro ElementType(element) -%}
{% if ('Container' not in element.attrib) or (element.attrib['Container'] == 'None') %}
{{ element.attrib['Type'] }}{% elif element.attrib['Container'] == 'Array' %}
AZStd::array<{{ element.attrib['Type'] }}, {{ element.attrib['Count'] }}>{% elif element.attrib['Container'] == 'Vector' %}
AZStd::fixed_vector<{{ element.attrib['Type'] }}, {{ element.attrib['Count'] }}>{% endif %}
{%- endmacro %}
{% macro CamelCase(text) %}{{ text[0] | upper}}{{ text[1:] }}{% endmacro %}
{% macro DeclareInlineMethods(packetNode, name) %}
inline bool {{ name }}::operator !=(const {{ name }} &rhs) const
{
return !(*this == rhs);
}
{% for Member in packetNode.iter('Member') %}
inline void {{ name }}::Set{{ CamelCase(Member.attrib['Name']) }}(const {{ ElementType(Member) }}& value)
{
m_{{ Member.attrib['Name'] }} = value;
}
inline const {{ ElementType(Member) }}& {{ name }}::Get{{ CamelCase(Member.attrib['Name']) }}() const
{
return m_{{ Member.attrib['Name'] }};
}
inline {{ ElementType(Member) }}& {{ name }}::Modify{{ CamelCase(Member.attrib['Name']) }}()
{
return m_{{ Member.attrib['Name'] }};
}
{% endfor %}
{% endmacro %}
#pragma once
{% for xml in dataFiles %}
namespace {{ xml.attrib['Name'] }}
{
{% for Packet in xml.iter('Packet') %}
{{ DeclareInlineMethods(Packet, Packet.attrib['Name']) -}}
{% endfor %}
}
{% endfor %}
@@ -0,0 +1,78 @@
{#
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.
#}
{% macro ElementType(element) -%}
{% if ('Container' not in element.attrib) or (element.attrib['Container'] == 'None') %}
{{ element.attrib['Type'] }}{% elif element.attrib['Container'] == 'Array' %}
AZStd::array<{{ element.attrib['Type'] }}, {{ element.attrib['Count'] }}>{% elif element.attrib['Container'] == 'Vector' %}
AZStd::fixed_vector<{{ element.attrib['Type'] }}, {{ element.attrib['Count'] }}>{% endif %}
{%- endmacro %}
{% macro DefinePacketMethods(packetNode, name) %}
{% if (packetNode.getchildren()) | len > 0 %}
{{ name }}::{{ name }}
(
{% for Member in packetNode.iter('Member') %}
{{ ElementType(Member) }} {{ Member.attrib['Name'] }}{% if not loop.last %},{% endif %}
{% endfor %}
)
{% for Member in packetNode.iter('Member') %}
{% if loop.first %}: {% else %}, {% endif %}m_{{ Member.attrib['Name'] }}({{ Member.attrib['Name'] }})
{% endfor %}
{
;
}
{% endif %}
AzNetworking::PacketType {{ name }}::GetPacketType() const
{
return Type;
}
bool {{ name }}::operator ==([[maybe_unused]] const {{ name }}& rhs) const
{
{% for Member in packetNode.iter('Member') %}
if (m_{{ Member.attrib['Name'] }} != rhs.m_{{ Member.attrib['Name'] }})
{
return false;
}
{% endfor %}
return true;
}
AZStd::unique_ptr<AzNetworking::IPacket> {{ name }}::Clone() const
{
AZStd::unique_ptr<{{ name }}> result = AZStd::make_unique<{{ name }}>();
{% for Member in packetNode.iter('Member') %}
result->m_{{ Member.attrib['Name'] }} = m_{{ Member.attrib['Name'] }};
{% endfor %}
return result;
}
bool {{ name }}::Serialize(AzNetworking::ISerializer& serializer)
{
{% for Member in packetNode.iter('Member') %}
serializer.Serialize(m_{{ Member.attrib['Name'] }}, "{{ Member.attrib['Name'] }}");
{% endfor %}
return serializer.IsValid();
}
{% endmacro %}
{% set includeFile = "{0}.h".format(((outputFile|basename)|splitext)[0]) %}
#include "{{ includeFile }}"
{% for xml in dataFiles %}
namespace {{ xml.attrib['Name'] }}
{
{% for Packet in xml.iter('Packet') %}
{{ DefinePacketMethods(Packet, Packet.attrib['Name']) -}}
{% endfor %}
}
{% endfor %}
@@ -0,0 +1,19 @@
<?xml version="1.0" encoding="utf-8"?>
<PacketGroup Name="CorePackets" PacketStart="0">
<Packet Name="InitiateConnectionPacket" Desc="This packet is used to initiate a new connection" />
<Packet Name="TerminateConnectionPacket" Desc="This packet is used to gracefully terminate an existing connection">
<Member Type="AzNetworking::DisconnectReason" Name="disconnectReason" Init="AzNetworking::DisconnectReason::None" />
</Packet>
<Packet Name="HeartbeatPacket" Desc="This packet is used to keep an established connection alive" />
<Packet Name="FragmentedPacket" Desc="This packet is used to segment a packet that exceeds a connections MTU">
<Member Type="AzNetworking::SequenceId" Name="unfragmentedSequence" Init="AzNetworking::InvalidSequenceId" />
<Member Type="AzNetworking::SequenceId" Name="fragmentSequence" Init="AzNetworking::InvalidSequenceId" />
<Member Type="uint8_t" Name="chunkIndex" Init="0" />
<Member Type="uint8_t" Name="chunkCount" Init="0" />
<Member Type="AzNetworking::ChunkBuffer" Name="chunkBuffer" />
</Packet>
</PacketGroup>
@@ -0,0 +1,33 @@
/*
* 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/AzNetworkingModule.h>
#include <AzNetworking/Framework/NetworkingSystemComponent.h>
namespace AzNetworking
{
AzNetworkingModule::AzNetworkingModule()
: AZ::Module()
{
m_descriptors.insert(m_descriptors.end(), {
NetworkingSystemComponent::CreateDescriptor(),
});
}
AZ::ComponentTypeList AzNetworkingModule::GetRequiredSystemComponents() const
{
return AZ::ComponentTypeList
{
azrtti_typeid<NetworkingSystemComponent>(),
};
}
}
@@ -0,0 +1,31 @@
/*
* 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/Module/Module.h>
namespace AzNetworking
{
class AzNetworkingModule
: public AZ::Module
{
public:
AZ_RTTI(AzNetworkingModule, "{4118D37D-233D-4CD5-ACE7-747FBAF2615D}", AZ::Module);
AZ_CLASS_ALLOCATOR(AzNetworkingModule, AZ::OSAllocator, 0);
AzNetworkingModule();
~AzNetworkingModule() override = default;
AZ::ComponentTypeList GetRequiredSystemComponents() const override;
};
}
@@ -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.
*
*/
#pragma once
#include <AzCore/Preprocessor/Enum.h>
namespace AzNetworking
{
AZ_ENUM_CLASS(ReliabilityType
, Reliable
, Unreliable
);
AZ_ENUM_CLASS(TerminationEndpoint
, Local
, Remote
);
AZ_ENUM_CLASS(ConnectionRole
, Connector
, Acceptor
);
AZ_ENUM_CLASS(ConnectionState
, Disconnected
, Disconnecting
, Connected
, Connecting
);
AZ_ENUM_CLASS(DisconnectReason
, None
, Unknown
, StreamError
, NetworkError
, Timeout
, ConnectTimeout
, ConnectionRetry
, HeartbeatTimeout
, TransportError
, TerminatedByClient
, TerminatedByServer
, TerminatedByUser
, TerminatedByMultipleLogin
, RemoteHostClosedConnection
, ReliableTransportFailure
, ReliableQueueFull
, ConnectionRejected
, ConnectionDeleted
, ServerNotReady
, ServerError
, ClientMigrated
, SslFailure
, VersionMismatch
, NonceRejected
, DtlsHandshakeError
, MAX
);
AZ_ENUM_CLASS(ConnectResult
, Rejected // Connection attempt was rejected
, Accepted // Connection was accepted
);
}
@@ -0,0 +1,101 @@
/*
* 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/ConnectionLayer/ConnectionMetrics.h>
#include <AzNetworking/Utilities/NetworkCommon.h>
#include <AzCore/std/string/fixed_string.h>
#include <AzCore/Console/IConsole.h>
#include <AzCore/Console/ILogger.h>
namespace AzNetworking
{
AZ_CVAR(float, net_rttIncreaseOnPacketLoss, 1.2f, nullptr, AZ::ConsoleFunctorFlags::Null, "Scalar amount to increase round trip time estimates by on packet loss");
AZ_CVAR(AZ::TimeMs, net_maxPacketTrackTimeMs, AZ::TimeMs{2000}, nullptr, AZ::ConsoleFunctorFlags::Null, "Maximum time to track any particular packetid before giving up");
void DatarateMetrics::LogPacket(uint32_t byteCount, AZ::TimeMs currentTimeMs)
{
const AZ::TimeMs deltaTimeMs = currentTimeMs - m_lastLoggedTimeMs;
m_atoms[m_activeAtom].m_bytesTransmitted += byteCount;
m_atoms[m_activeAtom].m_timeAccumulatorMs += deltaTimeMs;
if (m_atoms[m_activeAtom].m_timeAccumulatorMs >= m_maxSampleTimeMs)
{
SwapBuffers();
}
m_lastLoggedTimeMs = currentTimeMs;
}
float DatarateMetrics::GetBytesPerSecond() const
{
const uint32_t sampleAtom = 1 - m_activeAtom;
if (m_atoms[sampleAtom].m_timeAccumulatorMs == AZ::TimeMs{0})
{
return 0.0f;
}
const float bytesLogged = float(m_atoms[sampleAtom].m_bytesTransmitted);
const float sampleTime = float(m_atoms[sampleAtom].m_timeAccumulatorMs);
return (bytesLogged * 1000.0f) / sampleTime; // (* 1000) to convert from bytes per millisecond to bytes per second
}
void ConnectionComputeRtt::LogPacketSent(PacketId packetId, AZ::TimeMs currentTimeMs)
{
for (uint32_t i = 0; i < MaxTrackableEntries; i++)
{
// Locate first unused entry, if one exists
if (m_entries[i].m_packetId == InvalidPacketId)
{
m_entries[i].m_packetId = packetId;
m_entries[i].m_sendTimeMs = currentTimeMs;
return;
}
}
}
void ConnectionComputeRtt::LogPacketAcked(PacketId packetId, AZ::TimeMs currentTimeMs)
{
for (uint32_t i = 0; i < MaxTrackableEntries; i++)
{
if (m_entries[i].m_packetId == packetId)
{
const AZ::TimeMs milliseconds(currentTimeMs - m_entries[i].m_sendTimeMs);
const float timeToAck = static_cast<float>(milliseconds) * 0.001f;
m_roundTripTime = (timeToAck * 0.1f) + (m_roundTripTime * 0.9f);
m_entries[i].m_packetId = InvalidPacketId;
AZLOG(NET_Rtt, "Packet id %d acked after %d milliseconds, new latency %f seconds", (int)packetId, (int)milliseconds, m_roundTripTime);
return;
}
else if ((m_entries[i].m_packetId != InvalidPacketId) && (currentTimeMs - m_entries[i].m_sendTimeMs > net_maxPacketTrackTimeMs))
{
AZLOG(NET_Rtt, "Giving up on tracking packetid %d, timeout exceeded", (int)m_entries[i].m_packetId);
m_entries[i].m_packetId = InvalidPacketId;
}
}
}
void ConnectionComputeRtt::LogPacketTimeout(PacketId packetId)
{
for (uint32_t i = 0; i < MaxTrackableEntries; i++)
{
if (m_entries[i].m_packetId == packetId)
{
m_roundTripTime *= net_rttIncreaseOnPacketLoss;
m_entries[i].m_packetId = InvalidPacketId;
return;
}
}
}
}
@@ -0,0 +1,135 @@
/*
* 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/NetworkCommon.h>
#include <AzCore/Time/ITime.h>
namespace AzNetworking
{
//! @struct DatarateAtom
//! @brief basic unit for measuring socket datarate with connection to time.
struct DatarateAtom
{
DatarateAtom() = default;
uint32_t m_bytesTransmitted = 0;
AZ::TimeMs m_timeAccumulatorMs = AZ::TimeMs{0};
};
//! @class DatarateMetrics
//! @brief used to track datarate related metrics for a given connection with respect to time.
class DatarateMetrics
{
public:
DatarateMetrics() = default;
//! Constructor.
//! @param maxSampleTimeMs the period of time in milliseconds to attempt to smooth datarate over
DatarateMetrics(AZ::TimeMs maxSampleTimeMs);
//! Invoked whenever traffic is handled by the connection this instance is responsible for.
//! @param byteCount number of bytes sent through the connection
//! @param currentTimeMs current process time in milliseconds
void LogPacket(uint32_t byteCount, AZ::TimeMs currentTimeMs);
//! Retrieve a sample of the datarate being incurred by this connection in bytes per second.
//! @return datarate for traffic sent to or from the connection in bytes per second
float GetBytesPerSecond() const;
private:
//! Used internally to swap buffers used for metric gathering.
void SwapBuffers();
static constexpr AZ::TimeMs MaxSampleTimeMs = AZ::TimeMs{500};
AZ::TimeMs m_maxSampleTimeMs = MaxSampleTimeMs;
AZ::TimeMs m_lastLoggedTimeMs = MaxSampleTimeMs;
uint32_t m_activeAtom = 0;
DatarateAtom m_atoms[2];
};
//! @struct ConnectionPacketEntry
//! @brief basic data structure used to timestamp packet sequences.
struct ConnectionPacketEntry
{
ConnectionPacketEntry() = default;
//! Constructor.
//! @param packetId packet id of the packet this entry is tracking
//! @param sendTimeMs logged send time for the tracked packet in milliseconds
ConnectionPacketEntry(PacketId packetId, AZ::TimeMs sendTimeMs);
PacketId m_packetId = InvalidPacketId;
AZ::TimeMs m_sendTimeMs = AZ::TimeMs{0};
};
//! @class ConnectionComputeRtt
//! @brief helper class used to compute round trip time to an connection.
class ConnectionComputeRtt
{
public:
ConnectionComputeRtt() = default;
//! Invoked whenever traffic is sent through the connection this instance is responsible for.
//! @param packetId identifier of the packet being sent
//! @param currentTimeMs current process time in milliseconds
void LogPacketSent(PacketId packetId, AZ::TimeMs currentTimeMs);
//! Invoked whenever traffic is acknowledged from the connection this instance is responsible for.
//! @param packetId identifier of the packet being acked
//! @param currentTimeMs current process time in milliseconds
void LogPacketAcked(PacketId packetId, AZ::TimeMs currentTimeMs);
//! Invoked whenever traffic times out from the connection this instance is responsible for.
//! @param packetId identifier of the packet timing out
void LogPacketTimeout(PacketId packetId);
//! Retrieve a sample of the computed round trip time for this connection.
//! @return estimated round trip time (ping) for the given connection in seconds
float GetRoundTripTimeSeconds() const;
private:
static constexpr uint32_t MaxTrackableEntries = 4;
static constexpr float InitialRoundTripTime = 0.1f; //< Start off with a 100 millisecond estimate for Rtt
float m_roundTripTime = InitialRoundTripTime;
ConnectionPacketEntry m_entries[MaxTrackableEntries];
};
//! @struct ConnectionMetrics
//! @brief used to track general performance metrics for a given connection with respect to time.
struct ConnectionMetrics
{
ConnectionMetrics() = default;
ConnectionMetrics& operator=(const ConnectionMetrics& rhs) = default;
//! Resets all internal metrics to defaults.
void Reset();
uint32_t m_packetsSent = 0;
uint32_t m_packetsRecv = 0;
uint32_t m_packetsLost = 0;
uint32_t m_packetsAcked = 0;
DatarateMetrics m_sendDatarate;
DatarateMetrics m_recvDatarate;
ConnectionComputeRtt m_connectionRtt;
};
}
#include <AzNetworking/ConnectionLayer/ConnectionMetrics.inl>
@@ -0,0 +1,47 @@
/*
* 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 DatarateMetrics::DatarateMetrics(AZ::TimeMs maxSampleTimeMs)
: m_maxSampleTimeMs(maxSampleTimeMs)
, m_lastLoggedTimeMs{0}
, m_activeAtom(0)
{
;
}
inline void DatarateMetrics::SwapBuffers()
{
m_activeAtom = 1 - m_activeAtom;
m_atoms[m_activeAtom] = DatarateAtom();
}
inline ConnectionPacketEntry::ConnectionPacketEntry(PacketId packetId, AZ::TimeMs sendTimeMs)
: m_packetId(packetId)
, m_sendTimeMs(sendTimeMs)
{
;
}
inline float ConnectionComputeRtt::GetRoundTripTimeSeconds() const
{
return m_roundTripTime;
}
inline void ConnectionMetrics::Reset()
{
*this = ConnectionMetrics();
}
}
@@ -0,0 +1,131 @@
/*
* 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 <AzNetworking/Utilities/IpAddress.h>
#include <AzNetworking/ConnectionLayer/ConnectionEnums.h>
#include <AzNetworking/ConnectionLayer/ConnectionMetrics.h>
namespace AzNetworking
{
// Forwards
class IPacket;
//! This is a strong typedef for representing a remote host that may be triggering time changes for backward reconciliation.
AZ_TYPE_SAFE_INTEGRAL(ConnectionId, uint32_t);
static constexpr ConnectionId InvalidConnectionId = ConnectionId{ 0xFFFFFFFF };
struct ConnectionQuality
{
ConnectionQuality() = default;
ConnectionQuality(int32_t lossPercentage, AZ::TimeMs latencyMs, AZ::TimeMs varianceMs);
int32_t m_lossPercentage = 0;
AZ::TimeMs m_latencyMs = AZ::TimeMs{ 0 };
AZ::TimeMs m_varianceMs = AZ::TimeMs{ 0 };
};
enum class TrustZone
{
ExternalClientToServer // This connection is potentially opened to external and untrusted machines
, InternalServerToServer // This connection is only ever used for trusted server to server communication
};
//! @class IConnection
//! @brief interface class for network connections.
class IConnection
{
public:
//! Construct with a specific connectionId and remoteAddress.
//! @param connectionId the connection identifier to use for this connection
//! @param address the remote address this connection
IConnection(ConnectionId connectionId, const IpAddress& address);
virtual ~IConnection() = default;
//! A helper function that transmits a packet on this connection reliably.
//! @param packet packet to transmit
//! @return boolean true if the packet was transmitted (not an indication of delivery)
virtual bool SendReliablePacket(const IPacket& packet) = 0;
//! A helper function that transmits a packet on this connection unreliably.
//! @param packet packet to transmit
//! @return the unreliable packet identifier of the transmitted packet
virtual PacketId SendUnreliablePacket(const IPacket& packet) = 0;
//! Returns true if the given packet id was confirmed acknowledged by the remote endpoint, false otherwise.
//! @param packetId the packet id of the packet to confirm acknowledgment of
//! @return boolean true if the packet is confirmed acknowledged, false if the packet number is out of range, lost, or still pending acknowledgment
virtual bool WasPacketAcked(PacketId packetId) const = 0;
//! Retrieves the connection state for this IConnection instance.
//! @return the current connection state for this IConnection instance
virtual ConnectionState GetConnectionState() const = 0;
//! Retrieves the connection role of this connection instance, whether it was initiated or accepted.
//! @return whether this connection was initiated or accepted
virtual ConnectionRole GetConnectionRole() const = 0;
//! Disconnects the connection with the provided termination reason
//! @param reason reason for the disconnect
//! @param endpoint which endpoint initiated the disconnect, local or remote
//! @return boolean true on success
virtual bool Disconnect(DisconnectReason reason, TerminationEndpoint endpoint) = 0;
//! Sets connection maximum transmission unit for this connection.
//! Currently unsupported on TcpConnections
//! @param connectionMtu the max transmission unit for this connection
virtual void SetConnectionMtu(uint32_t connectionMtu) = 0;
//! Returns the connection maximum transmission unit.
//! Currently unsupported on TcpConnections
//! @return the max transmission unit for this connection
virtual uint32_t GetConnectionMtu() const = 0;
//! Sets connection quality values for testing poor connection conditions.
//! Currently unsupported on TcpConnections
//! @param connectionQuality simulated connection quality values to use
virtual void SetConnectionQuality(const ConnectionQuality& connectionQuality) = 0;
//! Returns the connection identifier for this connection instance.
//! @return the connection identifier for this connection instance
ConnectionId GetConnectionId() const;
//! Sets the remote address for this connection instance.
//! @param address the remote address to use for this connection instance
void SetRemoteAddress(const IpAddress& address);
//! Returns the remote address for this connection instance.
//! @return the remote address for this connection instance
const IpAddress& GetRemoteAddress() const;
//! Retrieves connection metric info.
//! @return reference to the connection metric info
const ConnectionMetrics& GetMetrics() const;
//! Retrieves connection metric info, non-const.
//! @return reference to the connection metric info
ConnectionMetrics& GetMetrics();
private:
// The following data members are here in the interface for performance reasons
ConnectionId m_connectionId;
IpAddress m_remoteAddress;
ConnectionMetrics m_connectionMetrics;
};
}
#include <AzNetworking/ConnectionLayer/IConnection.inl>
@@ -0,0 +1,56 @@
/*
* 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 ConnectionQuality::ConnectionQuality(int32_t lossPercentage, AZ::TimeMs latencyMs, AZ::TimeMs varianceMs)
: m_lossPercentage(lossPercentage)
, m_latencyMs(latencyMs)
, m_varianceMs(varianceMs)
{
;
}
inline IConnection::IConnection(ConnectionId connectionId, const IpAddress& address)
: m_connectionId(connectionId)
, m_remoteAddress(address)
{
;
}
inline ConnectionId IConnection::GetConnectionId() const
{
return m_connectionId;
}
inline void IConnection::SetRemoteAddress(const IpAddress& address)
{
m_remoteAddress = address;
}
inline const IpAddress& IConnection::GetRemoteAddress() const
{
return m_remoteAddress;
}
inline const ConnectionMetrics& IConnection::GetMetrics() const
{
return m_connectionMetrics;
}
inline ConnectionMetrics& IConnection::GetMetrics()
{
return m_connectionMetrics;
}
}
@@ -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 <AzNetworking/Utilities/IpAddress.h>
#include <AzNetworking/PacketLayer/IPacket.h>
#include <AzNetworking/PacketLayer/IPacketHeader.h>
#include <AzNetworking/ConnectionLayer/IConnection.h>
#include <AzNetworking/ConnectionLayer/ConnectionEnums.h>
namespace AzNetworking
{
//! @class IConnectionListener
//! @brief interface class for application layer dealing with connection level events.
class IConnectionListener
{
public:
virtual ~IConnectionListener() = default;
//! Invoked to validate any new incoming connection from a new endpoint.
//! @param remoteAddress the address of the remote endpoint initiating a connection
//! @param packetHeader packet header of the associated payload
//! @param serializer serializer instance containing the transmitted payload
//! @return the result of the application layers validation of the connect message
virtual ConnectResult ValidateConnect(const IpAddress& remoteAddress, const IPacketHeader& packetHeader, ISerializer& serializer) = 0;
//! Invoked when a new connection is successfully established.
//! @param connection pointer to the new connection instance
virtual void OnConnect(IConnection* connection) = 0;
//! Called on receipt of a packet from a connected connection.
//! @param connection pointer to the connection instance generating the event
//! @param packetHeader packet header of the associated payload
//! @param serializer serializer instance containing the transmitted payload
//! @return boolean true to signal success, false to disconnect with a transport error
virtual bool OnPacketReceived(IConnection* connection, const IPacketHeader& packetHeader, ISerializer& serializer) = 0;
//! Called when a packet is deemed lost by the remote connection.
//! @param connection pointer to the connection instance generating the event
//! @param packetId identifier of the lost packet
virtual void OnPacketLost(IConnection* connection, PacketId packetId) = 0;
//! Called on disconnection from an connection.
//! @param connection pointer to the connection instance generating the event
//! @param reason reason for the disconnect
//! @param endpoint whether the disconnection was initiated locally or remotely
virtual void OnDisconnect(IConnection* connection, DisconnectReason reason, TerminationEndpoint endpoint) = 0;
};
}
@@ -0,0 +1,51 @@
/*
* 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/ConnectionLayer/IConnection.h>
namespace AzNetworking
{
//! @class IConnectionSet
//! @brief interface class for managing a set of connections.
class IConnectionSet
{
public:
using ConnectionVisitor = AZStd::function<void(IConnection&)>;
virtual ~IConnectionSet() = default;
//! Will visit each active connection in the connection set and invoke the provided connection visitor.
//! @param visitor the visitor to visit each connection with
virtual void VisitConnections(const ConnectionVisitor& visitor) = 0;
//! Deletes a connection from this connection list instance by connection identifier.
//! @param connectionId connection identifier of the connection to delete
//! @return boolean true on success
virtual bool DeleteConnection(ConnectionId connectionId) = 0;
//! Retrieves a connection from this connection set by connection identifier.
//! @param connectionId connection identifier of the connection to retrieve
//! @return pointer to the requested connection instance on success, nullptr on failure
virtual IConnection* GetConnection(ConnectionId connectionId) const = 0;
//! Returns the next valid connection identifier for this connection list instance.
//! @return a valid connection identifier to give a new connection instance, or InvalidConnectionId on failure
virtual ConnectionId GetNextConnectionId() = 0;
//! Returns the current total connection count for this connection set
//! @return the current total connection count for this connection set
virtual uint32_t GetConnectionCount() const = 0;
};
}
@@ -0,0 +1,59 @@
/*
* 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/RTTI/TypeSafeIntegral.h>
namespace AzNetworking
{
AZ_TYPE_SAFE_INTEGRAL(SequenceId, uint16_t);
static constexpr SequenceId InvalidSequenceId = SequenceId{uint16_t(0xFFFF)};
//! Helper method that compares wrap-around sequence values to determine if one is more recent than another.
//! @param input1 first sequence value to compare against
//! @param input2 second sequence value to compare against
//! @return boolean true if input1 represents a newer sequence value than input2
template <typename TYPE>
bool SequenceMoreRecent(TYPE input1, TYPE input2);
//! Helper method that returns true when the sequences appears to have wrapped around.
//! @param input1 first sequence value to compare against
//! @param input2 second sequence value to compare against
//! @return boolean true if input1 represents a newer sequence value than input2 and input1 is numerically less than input2
template <typename TYPE>
bool SequenceRolledOver(TYPE input1, TYPE input2);
//! @class SequenceGenerator
//! @brief Generates wrapping sequence numbers.
class SequenceGenerator
{
public:
SequenceGenerator() = default;
//! Resets the sequence generator instance.
void Reset();
//! Returns the next sequence id for this generator instance.
//! @return the next sequence id for this generator instance
SequenceId GetNextSequenceId();
private:
SequenceId m_nextSequenceId = InvalidSequenceId;
};
}
AZ_TYPE_SAFE_INTEGRAL_SERIALIZEBINDING(AzNetworking::SequenceId);
#include <AzNetworking/ConnectionLayer/SequenceGenerator.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
{
template <typename TYPE>
inline bool SequenceMoreRecent(TYPE input1, TYPE input2)
{
constexpr TYPE HalfMaxSequence = static_cast<TYPE>(static_cast<TYPE>(~0) >> 1);
return ((input1 > input2) && (input1 - input2 <= HalfMaxSequence)) ||
((input2 > input1) && (input2 - input1 > HalfMaxSequence));
}
template <typename TYPE>
inline bool SequenceRolledOver(TYPE input1, TYPE input2)
{
constexpr TYPE HalfMaxSequence = static_cast<TYPE>(static_cast<TYPE>(~0) >> 1);
return (input2 > input1) && (input2 - input1 > HalfMaxSequence);
}
inline void SequenceGenerator::Reset()
{
m_nextSequenceId = InvalidSequenceId;
}
inline SequenceId SequenceGenerator::GetNextSequenceId()
{
++m_nextSequenceId;
if (m_nextSequenceId == InvalidSequenceId)
{
++m_nextSequenceId;
}
return m_nextSequenceId;
}
}
@@ -0,0 +1,109 @@
/*
* 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/containers/fixed_vector.h>
#include <AzNetworking/Serialization/ISerializer.h>
namespace AzNetworking
{
//! @class ByteBuffer
//! @brief serializeable byte buffer with efficient serialization.
template <AZStd::size_t SIZE>
class ByteBuffer
{
public:
ByteBuffer() = default;
~ByteBuffer() = default;
//! Returns the maximum number of elements this vector can reserve for use.
//! @return the maximum number of elements this vector can reserve for use
static constexpr AZStd::size_t GetCapacity();
//! Returns the number of elements reserved for usage in this vector.
//! @return the number of elements reserved for usage in this vector
AZStd::size_t GetSize() const;
//! Resizes the vector to the requested number of elements, does not initialize new elements.
//! @param newSize the number of elements to size the vector to
//! @return boolean true on success
bool Resize(AZStd::size_t newSize);
//! Const raw buffer access.
//! @return const pointer to the array's internal memory buffer
const uint8_t* GetBuffer() const;
//! Non-const raw buffer access.
//! @return non-const pointer to the array's internal memory buffer
uint8_t* GetBuffer();
//! Const raw end-of-buffer access.
//! @return const pointer to the first unused byte in the array's internal memory buffer
const uint8_t* GetBufferEnd() const;
//! Raw end-of-buffer access.
//! @return non-const pointer to the first unused byte in the array's internal memory buffer
uint8_t* GetBufferEnd();
//! Overwrites the data in this ByteBuffer with the data in the provided buffer.
//! @param buffer pointer to the buffer data to copy
//! @param bufferSize the number of bytes in the buffer to copy
//! @return boolean true on success, false for failure
bool CopyValues(const uint8_t* buffer, AZStd::size_t bufferSize);
//! Tests for equality to a raw byte buffer.
//! @param buffer pointer to the buffer data to copy
//! @param bufferSize the number of bytes in the buffer to copy
//! @return boolean true if rhs and buffer are identical, false otherwise
bool IsSame(const uint8_t* buffer, AZStd::size_t bufferSize) const;
//! Equality operator.
//! @param rhs the byte buffer to compare against
//! @return boolean true if rhs and lhs are identical, false otherwise
bool operator==(const ByteBuffer& rhs) const;
//! Inequality operator.
//! @param rhs the byte buffer to compare against
//! @return boolean true if rhs and lhs are not identical, false otherwise
bool operator!=(const ByteBuffer& rhs) 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:
AZStd::fixed_vector<uint8_t, SIZE> m_buffer;
};
// The maximum allowable packet size
static constexpr uint32_t MaxPacketSize = 16384;
static_assert((MaxPacketSize & (MaxPacketSize - 1)) == 0, "Maximum packet size is not aligned/a power of 2");
using TcpPacketEncodingBuffer = ByteBuffer<MaxPacketSize>;
// This is the max possible MTU for UDP packets, the actual transmitted packet size depends on connection MTU
static constexpr uint32_t MaxUdpTransmissionUnit = 1024;
static_assert(MaxPacketSize > MaxUdpTransmissionUnit, "UDP MTU must be smaller than maximum packet size");
static_assert((MaxUdpTransmissionUnit & (MaxUdpTransmissionUnit - 1)) == 0, "Maximum UPD MTU is not aligned/a power of 2");
using UdpPacketEncodingBuffer = ByteBuffer<MaxPacketSize>;
// This is a general packet encoding buffer that will fit within the constraints of either the TCP or UDP transport layers
using PacketEncodingBuffer = ByteBuffer<MaxPacketSize>;
// This is a packet encoding buffer specifically for MTU fragmentation
using ChunkBuffer = ByteBuffer<MaxUdpTransmissionUnit>;
}
#include <AzNetworking/DataStructures/ByteBuffer.inl>
@@ -0,0 +1,109 @@
/*
* 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.
*
*/
namespace AzNetworking
{
template <AZStd::size_t SIZE>
inline constexpr AZStd::size_t ByteBuffer<SIZE>::GetCapacity()
{
return SIZE;
}
template <AZStd::size_t SIZE>
inline AZStd::size_t ByteBuffer<SIZE>::GetSize() const
{
return m_buffer.size();
}
template <AZStd::size_t SIZE>
inline bool ByteBuffer<SIZE>::Resize(AZStd::size_t newSize)
{
if (newSize > GetCapacity())
{
return false;
}
m_buffer.resize_no_construct(newSize);
return true;
}
template <AZStd::size_t SIZE>
inline const uint8_t* ByteBuffer<SIZE>::GetBuffer() const
{
return m_buffer.data();
}
template <AZStd::size_t SIZE>
inline uint8_t* ByteBuffer<SIZE>::GetBuffer()
{
return m_buffer.data();
}
template <AZStd::size_t SIZE>
inline const uint8_t* ByteBuffer<SIZE>::GetBufferEnd() const
{
return GetBuffer() + GetSize();
}
template <AZStd::size_t SIZE>
inline uint8_t* ByteBuffer<SIZE>::GetBufferEnd()
{
return GetBuffer() + GetSize();
}
template <AZStd::size_t SIZE>
inline bool ByteBuffer<SIZE>::CopyValues(const uint8_t* buffer, AZStd::size_t bufferSize)
{
if (Resize(bufferSize))
{
memcpy(m_buffer.data(), buffer, bufferSize);
return true;
}
return false;
}
template <AZStd::size_t SIZE>
bool ByteBuffer<SIZE>::IsSame(const uint8_t* buffer, AZStd::size_t bufferSize) const
{
return (m_buffer.size() == bufferSize)
&& (memcmp(m_buffer.data(), buffer, bufferSize) == 0);
}
template <AZStd::size_t SIZE>
inline bool ByteBuffer<SIZE>::operator==(const ByteBuffer& rhs) const
{
return (m_buffer.size() == rhs.m_buffer.size())
&& (memcmp(m_buffer.data(), rhs.m_buffer.data(), m_buffer.size()) == 0);
}
template <AZStd::size_t SIZE>
inline bool ByteBuffer<SIZE>::operator!=(const ByteBuffer& rhs) const
{
return (m_buffer.size() != rhs.m_buffer.size())
|| (memcmp(m_buffer.data(), rhs.m_buffer.data(), m_buffer.size()) != 0);
}
template <AZStd::size_t SIZE>
inline bool ByteBuffer<SIZE>::Serialize(ISerializer& serializer)
{
static constexpr AZStd::size_t RequiredBytes = AZ::RequiredBytesForValue<SIZE>();
using SizeType = typename AZ::SizeType<RequiredBytes, false>::Type;
// Important since we need to know the original and new sizes for vector resize
SizeType size = static_cast<SizeType>(GetSize());
uint32_t outSize = size;
return serializer.Serialize(size, "Size")
&& Resize(size)
&& serializer.SerializeBytes(m_buffer.data(), static_cast<uint32_t>(GetCapacity()), false, outSize, "Buffer")
&& (outSize == size);
}
}
@@ -0,0 +1,120 @@
/*
* 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/DataStructures/IBitset.h>
#include <AzNetworking/Serialization/ISerializer.h>
#include <AzCore/Math/MathUtils.h>
#include <AzCore/std/containers/array.h>
#include <AzCore/std/string/string.h>
namespace AzNetworking
{
//! @class FixedSizeBitset
//! @brief fixed size data structure optimized for representing an array of bits.
// @nt @KB - TODO: The default choice of uint32_t potentially has a negative impact on bandwidth usage, since a large number of components probably don't use
// multiples of 32 network properties i.e. 1 network property sends 4 bytes when it only needs to send 1, 33 network properties send 8 bytes when it only
// needs to send 5. The more networked components we have, the larger this impact becomes. Consider optimizing this in the future
template <AZStd::size_t SIZE, typename ElementType = uint32_t>
class FixedSizeBitset
: public IBitset
{
public:
static const constexpr AZStd::size_t ElementTypeBits = sizeof(ElementType) * 8;
static const constexpr AZStd::size_t ElementCount = (SIZE + ElementTypeBits - 1) / ElementTypeBits;
using SelfType = FixedSizeBitset<SIZE, ElementType>;
using ContainerType = AZStd::array<ElementType, ElementCount>;
FixedSizeBitset();
//! Construct to a given initial state.
//! @param value value to initialize all bits to
FixedSizeBitset(bool value);
//! Construct from an array of raw input.
//! @param values raw input array to initialize bit vector to
FixedSizeBitset(const ElementType* values);
//! Construct from another bit set.
//! @param rhs bitset to copy
FixedSizeBitset(const FixedSizeBitset<SIZE, ElementType> &rhs);
//! Assignment from same type.
//! @param rhs instance to assign from
SelfType& operator =(const SelfType& rhs);
//! Bitwise OR assignment operator.
//! @param rhs instance to bitwise-or assign
//! @return reference to the LHS
SelfType& operator |=(const SelfType& rhs);
//! Equality operator.
//! @param rhs instance to compare against
//! @return boolean true if inputs are the same, false otherwise
bool operator ==(const SelfType& rhs) const;
//! Inequality operator.
//! @param rhs instance to compare against
//! @return boolean true if inputs are different, false otherwise
bool operator !=(const SelfType& rhs) const;
//! Initializes all internal bits to the provided value.
//! @param value value to initialize all bits to
void InitializeAll(bool value);
//! Sets the specified bit to the provided value.
//! @param index index of the bit to set
//! @param value value to set the bit to
void SetBit(uint32_t index, bool value) override;
//! Gets the current value of the specified bit.
//! @param index index of the bit to retrieve the value of
//! @return boolean true if the bit is set, false otherwise
bool GetBit(uint32_t index) const override;
//! Returns true if any of the bits are set.
//! @return boolean true if any bit is set, false otherwise
bool AnySet() const override;
//! Returns the number of bits that are represented in this fixed size bitset.
//! @return the number of bits that are represented in this fixed size bitset
uint32_t GetValidBitCount() const override;
//! Subtracts off the set bits of the passed in bitset.
//! @param rhs the bits that we want to remove from the current bitset
void Subtract(const FixedSizeBitset<SIZE, ElementType> &rhs);
//! Const raw-buffer access.
//! @return const pointer to the raw buffer the bit array is stored in
const ContainerType &GetContainer() const;
//! Non-const raw-buffer access.
//! @return non-const pointer to the raw buffer the bit array is stored in
ContainerType &GetContainer();
//! 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:
void ClearUnusedBits();
ContainerType m_container;
};
}
#include <AzNetworking/DataStructures/FixedSizeBitset.inl>
@@ -0,0 +1,184 @@
/*
* 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
{
template <AZStd::size_t SIZE, typename ElementType>
inline FixedSizeBitset<SIZE, ElementType>::FixedSizeBitset()
{
InitializeAll(false);
}
template <AZStd::size_t SIZE, typename ElementType>
inline FixedSizeBitset<SIZE, ElementType>::FixedSizeBitset(bool value)
{
InitializeAll(value);
}
template <AZStd::size_t SIZE, typename ElementType>
inline FixedSizeBitset<SIZE, ElementType>::FixedSizeBitset(const ElementType *values)
{
static const AZStd::size_t TotalBytes = m_container.size() * sizeof(ElementType);
memcpy(m_container.data(), values, TotalBytes);
ClearUnusedBits();
}
template <AZStd::size_t SIZE, typename ElementType>
inline FixedSizeBitset<SIZE, ElementType>::FixedSizeBitset(const SelfType& rhs)
{
*this = rhs;
}
template <AZStd::size_t SIZE, typename ElementType>
FixedSizeBitset<SIZE, ElementType>& FixedSizeBitset<SIZE, ElementType>::operator =(const SelfType& rhs)
{
m_container = rhs.m_container;
return *this;
}
template <AZStd::size_t SIZE, typename ElementType>
FixedSizeBitset<SIZE, ElementType>& FixedSizeBitset<SIZE, ElementType>::operator |=(const SelfType& rhs)
{
for (AZStd::size_t i = 0; i < m_container.size(); ++i)
{
m_container[i] |= rhs.m_container[i];
}
ClearUnusedBits();
return *this;
}
template <AZStd::size_t SIZE, typename ElementType>
bool FixedSizeBitset<SIZE, ElementType>::operator ==(const SelfType& rhs) const
{
for (AZStd::size_t i = 0; i < m_container.size(); ++i)
{
if (m_container[i] != rhs.m_container[i])
{
return false;
}
}
return true;
}
template <AZStd::size_t SIZE, typename ElementType>
bool FixedSizeBitset<SIZE, ElementType>::operator !=(const SelfType& rhs) const
{
return !(*this == rhs);
}
template <AZStd::size_t SIZE, typename ElementType>
inline void FixedSizeBitset<SIZE, ElementType>::InitializeAll(bool value)
{
const AZStd::size_t TotalBytes = m_container.size() * sizeof(ElementType);
memset(m_container.data(), value ? 0xFF : 0x00, TotalBytes);
ClearUnusedBits();
}
template <AZStd::size_t SIZE, typename ElementType>
inline void FixedSizeBitset<SIZE, ElementType>::SetBit(uint32_t index, bool value)
{
AZ_Assert(index < SIZE, "Out of bounds access (requested %u, size %u)", index, SIZE);
constexpr uint32_t ElementTypeBitsLogTwo = AZ::Log2(ElementTypeBits - 1);
const uint32_t element = index >> ElementTypeBitsLogTwo;
const ElementType offset = index & (ElementTypeBits - 1);
const ElementType mask = static_cast<ElementType>(0x01) << offset;
const ElementType current = m_container[element];
m_container[element] = (value) ? current | mask : current & static_cast<ElementType>(~mask);
}
template <AZStd::size_t SIZE, typename ElementType>
inline bool FixedSizeBitset<SIZE, ElementType>::GetBit(uint32_t index) const
{
AZ_Assert(index < SIZE, "Out of bounds access (requested %u, size %u)", index, SIZE);
constexpr uint32_t ElementTypeBitsLogTwo = AZ::Log2(ElementTypeBits - 1);
const uint32_t element = index >> ElementTypeBitsLogTwo;
const ElementType offset = index & (ElementTypeBits - 1);
return (static_cast<ElementType>(m_container[element] >> offset) & static_cast<ElementType>(0x01)) ? true : false;
}
template <AZStd::size_t SIZE, typename ElementType>
inline bool FixedSizeBitset<SIZE, ElementType>::AnySet() const
{
for (uint32_t i = 0; i < m_container.size(); ++i)
{
if (m_container[i] != 0)
{
return true;
}
}
return false;
}
template <AZStd::size_t SIZE, typename ElementType>
inline uint32_t FixedSizeBitset<SIZE, ElementType>::GetValidBitCount() const
{
return SIZE;
}
template <AZStd::size_t SIZE, typename ElementType>
inline void FixedSizeBitset<SIZE, ElementType>::Subtract(const SelfType& rhs)
{
for (uint32_t i = 0; i < m_container.size(); ++i)
{
m_container[i] &= ~rhs.m_container[i];
}
ClearUnusedBits();
}
template <AZStd::size_t SIZE, typename ElementType>
inline const typename FixedSizeBitset<SIZE, ElementType>::ContainerType& FixedSizeBitset<SIZE, ElementType>::GetContainer() const
{
return m_container;
}
template <AZStd::size_t SIZE, typename ElementType>
inline typename FixedSizeBitset<SIZE, ElementType>::ContainerType& FixedSizeBitset<SIZE, ElementType>::GetContainer()
{
return m_container;
}
template <AZStd::size_t SIZE, typename ElementType>
inline bool FixedSizeBitset<SIZE, ElementType>::Serialize(ISerializer& serializer)
{
constexpr uint32_t numBytesToSerialize = ElementCount * ElementTypeBits / 8;
uint8_t* bitsetContainer = reinterpret_cast<uint8_t*>(m_container.data());
bool success = true;
for (uint32_t i = 0; i < numBytesToSerialize; ++i)
{
if (!serializer.Serialize(bitsetContainer[i], GenerateIndexLabel<SIZE>(i).c_str()))
{
success = false;
break;
}
}
ClearUnusedBits();
return success;
}
template <>
inline void FixedSizeBitset<0>::ClearUnusedBits()
{
;
}
template <AZStd::size_t SIZE, typename ElementType>
inline void FixedSizeBitset<SIZE, ElementType>::ClearUnusedBits()
{
constexpr ElementType AllOnes = static_cast<ElementType>(~0);
constexpr ElementType LastUsedBits = (SIZE % ElementTypeBits);
constexpr ElementType ShiftAmount = (LastUsedBits == 0) ? 0 : ElementTypeBits - LastUsedBits;
constexpr ElementType ClearBitMask = AllOnes >> ShiftAmount;
m_container[m_container.size() - 1] &= ClearBitMask;
}
}
@@ -0,0 +1,60 @@
/*
* 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/DataStructures/IBitset.h>
#include <AzCore/base.h>
namespace AzNetworking
{
//! @class FixedSizeBitsetView
//! @brief Creates a view into a subset of an IBitset.
class FixedSizeBitsetView
: public IBitset
{
public:
//! Construct a bitset view.
//! @param bitset a bitset to create the view from
//! @param startOffset starting bit
//! @param count total number of bits to use
FixedSizeBitsetView(IBitset& bitset, uint32_t startOffset, uint32_t count);
//! Sets the specified bit to the provided value.
//! @param index index of the bit to set
//! @param value value to set the bit to
virtual void SetBit(uint32_t index, bool value) override;
//! Gets the current value of the specified bit.
//! @param index index of the bit to retrieve the value of
//! @return boolean true if the bit is set, false otherwise
virtual bool GetBit(uint32_t index) const override;
//! Gets the current value of the specified bit.
//! @param index index of the bit to retrieve the value of
//! @return boolean true if the bit is set, false otherwise
virtual bool AnySet() const override;
//! Returns the number of bits that are represented in this fixed size bitset.
//! @return the number of bits that are represented in this fixed size bitset
virtual uint32_t GetValidBitCount() const override;
private:
IBitset& m_bitset;
const uint32_t m_startOffset;
const uint32_t m_count;
};
}
#include <AzNetworking/DataStructures/FixedSizeBitsetView.inl>
@@ -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
namespace AzNetworking
{
inline FixedSizeBitsetView::FixedSizeBitsetView(IBitset& bitset, uint32_t startOffset, uint32_t count)
: m_bitset(bitset)
, m_startOffset(startOffset)
, m_count(startOffset < bitset.GetValidBitCount() && startOffset + count <= bitset.GetValidBitCount() ? count : 0)
{
AZ_Assert(startOffset + count <= bitset.GetValidBitCount(), "Out of bounds setup in BitsetSubset. Defaulting to 0 bit count.");
}
inline void FixedSizeBitsetView::SetBit(uint32_t index, bool value)
{
AZ_Assert(index < m_count, "Out of bounds access in BitsetSubset (requested %u, count %u)", index, m_count);
if (m_count)
{
m_bitset.SetBit(m_startOffset + index, value);
}
}
inline bool FixedSizeBitsetView::GetBit(uint32_t index) const
{
AZ_Assert(index < m_count, "Out of bounds access in BitsetSubset (requested %u, count %u)", index, m_count);
if (m_count)
{
return m_bitset.GetBit(m_startOffset + index);
}
return false;
}
inline bool FixedSizeBitsetView::AnySet() const
{
for (uint32_t i = 0; i < m_count; ++i)
{
if (GetBit(i))
{
return true;
}
}
return false;
}
inline uint32_t FixedSizeBitsetView::GetValidBitCount() const
{
return m_count;
}
}
@@ -0,0 +1,88 @@
/*
* 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/DataStructures/FixedSizeBitset.h>
namespace AzNetworking
{
//! @class FixedSizeVectorBitset
//! @brief fixed size data structure optimized for representing a resizable array of bits.
template <AZStd::size_t CAPACITY, typename ElementType = uint8_t>
class FixedSizeVectorBitset
: public IBitset
{
public:
using SelfType = FixedSizeVectorBitset<CAPACITY, ElementType>;
FixedSizeVectorBitset() = default;
//! Assignment from same type.
//! @param rhs instance to assign from
SelfType& operator =(const SelfType& rhs);
//! Bitwise OR assignment operator.
//! @param rhs instance to bitwise-or assign
//! @return reference to the LHS
SelfType& operator |=(const SelfType& rhs);
//! Sets the specified bit to the provided value.
//! @param index index of the bit to set
//! @param value value to set the bit to
void SetBit(uint32_t index, bool value) override;
//! Gets the current value of the specified bit.
//! @param index index of the bit to retrieve the value of
//! @return boolean true if the bit is set, false otherwise
bool GetBit(uint32_t index) const override;
//! Returns true if any of the bits are set.
//! @return boolean true if any bit is set, false otherwise
bool AnySet() const override;
//! Returns the number of bits that are represented in this fixed size bitset.
//! @return the number of bits that are represented in this fixed size bitset
uint32_t GetValidBitCount() const override;
//! Subtracts off the set bits of the passed in bitset.
//! @param rhs the bits that we want to remove from the current bitset
void Subtract(const SelfType& rhs);
uint32_t GetSize() const;
uint32_t GetCapacity() const;
bool Resize(uint32_t count);
bool AddBits(uint32_t count);
void Clear();
bool GetBack() const;
bool PushBack(bool value);
bool PopBack();
//! 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:
void ClearUnusedBits();
using BitsetType = FixedSizeBitset<CAPACITY, ElementType>;
uint32_t m_count = 0;
BitsetType m_bitset = false;
};
}
#include <AzNetworking/DataStructures/FixedSizeVectorBitset.inl>
@@ -0,0 +1,210 @@
/*
* 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
{
template <AZStd::size_t CAPACITY, typename ElementType>
inline FixedSizeVectorBitset<CAPACITY, ElementType>& FixedSizeVectorBitset<CAPACITY, ElementType>::operator =(const SelfType& rhs)
{
m_count = rhs.m_count;
m_bitset = rhs.m_bitset;
return *this;
}
template <AZStd::size_t CAPACITY, typename ElementType>
inline FixedSizeVectorBitset<CAPACITY, ElementType>& FixedSizeVectorBitset<CAPACITY, ElementType>::operator|=(const SelfType& rhs)
{
if (rhs.GetSize() > GetSize())
{
Resize(rhs.GetSize());
}
uint32_t usedElementSize = (GetSize() + BitsetType::ElementTypeBits - 1) / BitsetType::ElementTypeBits;
for (uint32_t i = 0; i < usedElementSize; ++i)
{
m_bitset.GetContainer()[i] |= rhs.m_bitset.GetContainer()[i];
}
return *this;
}
template <AZStd::size_t CAPACITY, typename ElementType>
inline void FixedSizeVectorBitset<CAPACITY, ElementType>::SetBit(uint32_t index, bool value)
{
if (index > m_count)
{
return;
}
m_bitset.SetBit(index, value);
}
template <AZStd::size_t CAPACITY, typename ElementType>
inline bool FixedSizeVectorBitset<CAPACITY, ElementType>::GetBit(uint32_t index) const
{
if (index > m_count)
{
return false;
}
return m_bitset.GetBit(index);
}
template <AZStd::size_t CAPACITY, typename ElementType>
inline bool FixedSizeVectorBitset<CAPACITY, ElementType>::AnySet() const
{
uint32_t usedElementSize = (GetSize() + BitsetType::ElementTypeBits - 1) / BitsetType::ElementTypeBits;
for (uint32_t i = 0; i < usedElementSize; ++i)
{
if (m_bitset.GetContainer()[i] != 0)
{
return true;
}
}
return false;
}
template <AZStd::size_t CAPACITY, typename ElementType>
inline uint32_t FixedSizeVectorBitset<CAPACITY, ElementType>::GetValidBitCount() const
{
return m_count;
}
template <AZStd::size_t CAPACITY, typename ElementType>
inline void FixedSizeVectorBitset<CAPACITY, ElementType>::Subtract(const SelfType& other)
{
if (other.GetSize() > GetSize())
{
Resize(other.GetSize());
}
uint32_t usedElementSize = (GetSize() + BitsetType::ElementTypeBits - 1) / BitsetType::ElementTypeBits;
for (uint32_t i = 0; i < usedElementSize; ++i)
{
m_bitset.GetContainer()[i] &= ~other.m_bitset.GetContainer()[i];
}
}
template <AZStd::size_t CAPACITY, typename ElementType>
inline uint32_t FixedSizeVectorBitset<CAPACITY, ElementType>::GetSize() const
{
return m_count;
}
template <AZStd::size_t CAPACITY, typename ElementType>
inline uint32_t FixedSizeVectorBitset<CAPACITY, ElementType>::GetCapacity() const
{
return CAPACITY;
}
template <AZStd::size_t CAPACITY, typename ElementType>
inline bool FixedSizeVectorBitset<CAPACITY, ElementType>::Resize(uint32_t count)
{
if (count > CAPACITY)
{
return false;
}
const bool shouldClear = m_count > count;
m_count = count;
if (shouldClear)
{
ClearUnusedBits();
}
return true;
}
template <AZStd::size_t CAPACITY, typename ElementType>
inline bool FixedSizeVectorBitset<CAPACITY, ElementType>::AddBits(uint32_t count)
{
return Resize(m_count + count);
}
template <AZStd::size_t CAPACITY, typename ElementType>
inline void FixedSizeVectorBitset<CAPACITY, ElementType>::Clear()
{
m_count = 0;
m_bitset.InitializeAll(false);
}
template <AZStd::size_t CAPACITY, typename ElementType>
inline bool FixedSizeVectorBitset<CAPACITY, ElementType>::GetBack() const
{
return m_bitset.GetBit(m_count - 1); // Asserts internally if out of range
}
template <AZStd::size_t CAPACITY, typename ElementType>
inline bool FixedSizeVectorBitset<CAPACITY, ElementType>::PushBack(bool value)
{
if (m_count >= CAPACITY)
{
return false;
}
m_bitset.SetBit(m_count, value);
++m_count;
return true;
}
template <AZStd::size_t CAPACITY, typename ElementType>
inline bool FixedSizeVectorBitset<CAPACITY, ElementType>::PopBack()
{
if (m_count <= 0)
{
return false;
}
m_bitset.SetBit(m_count - 1, false);
--m_count;
return true;
}
template <AZStd::size_t CAPACITY, typename ElementType>
inline bool FixedSizeVectorBitset<CAPACITY, ElementType>::Serialize(ISerializer& serializer)
{
static constexpr uint32_t NumBytesRequiredForSize = AZ::RequiredBytesForValue<CAPACITY>();
using SizeType = typename AZ::SizeType<NumBytesRequiredForSize, false>::Type;
// m_count is always less than CAPACITY, so should fit safely in SizeType
SizeType count = static_cast<SizeType>(m_count);
if (!serializer.Serialize(count, "Count"))
{
return false;
}
m_count = count;
const uint32_t numBytesToSerialize = (m_count + 7) / 8; // Round up nearest byte
uint8_t* bitsetContainer = reinterpret_cast<uint8_t*>(m_bitset.GetContainer().data());
for (uint32_t i = 0; i < numBytesToSerialize; ++i)
{
if (!serializer.Serialize(bitsetContainer[i], "Byte"))
{
return false;
}
}
ClearUnusedBits();
return true;
}
template <AZStd::size_t CAPACITY, typename ElementType>
inline void FixedSizeVectorBitset<CAPACITY, ElementType>::ClearUnusedBits()
{
constexpr ElementType AllOnes = static_cast<ElementType>(~0);
const ElementType LastUsedBits = (GetSize() % BitsetType::ElementTypeBits);
#pragma warning(push)
#pragma warning(disable : 4293) // shift count negative or too big, undefined behaviour
#pragma warning(disable : 6326) // constant constant comparison
const ElementType ShiftAmount = (LastUsedBits == 0) ? 0 : BitsetType::ElementTypeBits - LastUsedBits;
const ElementType ClearBitMask = AllOnes >> ShiftAmount;
#pragma warning(pop)
uint32_t usedElementSize = (GetSize() + BitsetType::ElementTypeBits - 1) / BitsetType::ElementTypeBits;
for (uint32_t i = usedElementSize + 1; i < CAPACITY; ++i)
{
m_bitset.GetContainer()[i] = 0;
}
m_bitset.GetContainer()[m_bitset.GetContainer().size() - 1] &= ClearBitMask;
}
}
@@ -0,0 +1,45 @@
/*
* 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>
namespace AzNetworking
{
//! @class IBitset
//! @brief Interface for a structure optimized for representing an array of bits.
class IBitset
{
public:
virtual ~IBitset() = default;
//! Sets the specified bit to the provided value.
//! @param index index of the bit to set
//! @param value value to set the bit to
virtual void SetBit(uint32_t index, bool value) = 0;
//! Gets the current value of the specified bit.
//! @param index index of the bit to retrieve the value of
//! @return boolean true if the bit is set, false otherwise
virtual bool GetBit(uint32_t index) const = 0;
//! Returns true if any bit is raised, false otherwise.
//! @return boolean true if any bit is raised, false if all bits are lowered
virtual bool AnySet() const = 0;
//! Returns the number of bits that are represented in this fixed size bitset.
//! @return the number of bits that are represented in this fixed size bitset
virtual uint32_t GetValidBitCount() const = 0;
};
}
@@ -0,0 +1,99 @@
/*
* 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/MathUtils.h>
#include <AzCore/std/containers/array.h>
#include <AzCore/RTTI/TypeSafeIntegral.h>
namespace AzNetworking
{
using BitsetChunk = uint32_t;
//! @class RingBufferBitset
//! @brief fixed size data structure optimized for storing and rotating large numbers of bits.
template <uint32_t SIZE>
class RingbufferBitset
{
public:
static constexpr uint32_t NumBitsetChunkedBits = AZ::Log2(BitsetChunk(~0));
static constexpr uint32_t RingbufferContainerSize = SIZE / NumBitsetChunkedBits;
static_assert((SIZE % NumBitsetChunkedBits) == 0, "RingbufferBitset must be a multiple of NumBitsetChunkedBits");
using RingbufferContainer = AZStd::array<BitsetChunk, RingbufferContainerSize>;
RingbufferBitset();
~RingbufferBitset() = default;
//! Resets the bitset to all empty.
void Reset();
//! Gets the current value of the specified bit.
//! @param index index of the bit to retrieve the value of
//! @return boolean true if the bit is set, false otherwise
bool GetBit(uint32_t index) const;
//! Sets the specified bit to the provided value.
//! @param index index of the bit to set
//! @param value value to set the bit to
void SetBit(uint32_t index, bool value);
//! Returns the number of accessible bits in this ringbuffer bitset.
//! @return the number of accessible bits in this ringbuffer bitset
uint32_t GetValidBitCount() const;
//! Pushes back the specified number of bits padding with zero bits.
//! @param numBits the number of bits to push into the bitset
void PushBackBits(uint32_t numBits);
//! Retrieves a single element from the ringbuffer bitset.
//! @param elementIndex the index of the ringbuffer element to retrieve
//! @return the requested bitset element
const BitsetChunk& GetBitsetElement(uint32_t elementIndex) const;
//! Returns the number of bits that are unused in the head element.
//! @return the number of bits that are unused in the head element
uint32_t GetUnusedHeadBits() const;
private:
//! Converts the provided absolute index into a internal ring buffer element index.
//! @param absoluteIndex the absolute index value to convert to a ring-buffer index
//! @return the converted ring-buffer index
uint32_t GetChunkIndexHelper(uint32_t absoluteIndex) const;
//! Increments the internal head index.
void IncrementHead();
uint32_t m_headElementOffset = 0; //< What index is the first element in our ring buffer
uint32_t m_unusedHeadBits = NumBitsetChunkedBits; //< How many bits are unused in the head RingbufferContainer element
RingbufferContainer m_container;
};
//! Helper method for extracting a bit from a specific element.
//! @param element the specific element to get the bit from
//! @param bitIndex the index of the bit to retrieve
//! @return boolean true if the bit is set, false otherwise
template <typename TYPE>
bool GetBitHelper(TYPE element, uint32_t bitIndex);
//! A helper method for setting bits.
//! @param element the specific element to set the bit within
//! @param bitIndex the index of the bit to set
//! @param value the value to set the bit to
template <typename TYPE>
void SetBitHelper(TYPE &element, uint32_t bitIndex, bool value);
}
#include <AzNetworking/DataStructures/RingBufferBitset.inl>
@@ -0,0 +1,140 @@
/*
* 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
{
template <uint32_t SIZE>
inline RingbufferBitset<SIZE>::RingbufferBitset()
{
Reset();
}
template <uint32_t SIZE>
inline void RingbufferBitset<SIZE>::Reset()
{
m_headElementOffset = 0;
m_unusedHeadBits = NumBitsetChunkedBits;
for (uint32_t i = 0; i < m_container.size(); ++i)
{
m_container[i] = 0;
}
}
template <uint32_t SIZE>
inline bool RingbufferBitset<SIZE>::GetBit(uint32_t index) const
{
const uint32_t usedHeadBits = NumBitsetChunkedBits - m_unusedHeadBits;
if (index < usedHeadBits)
{
return GetBitHelper(m_container[m_headElementOffset], index);
}
index -= usedHeadBits;
const uint32_t element = (index >> 5) + 1;
const uint32_t bitIndex = index & (NumBitsetChunkedBits - 1);
return GetBitHelper(m_container[GetChunkIndexHelper(element)], bitIndex);
}
template <uint32_t SIZE>
inline void RingbufferBitset<SIZE>::SetBit(uint32_t index, bool value)
{
const uint32_t usedHeadBits = NumBitsetChunkedBits - m_unusedHeadBits;
if (index < usedHeadBits)
{
return SetBitHelper(m_container[m_headElementOffset], index, value);
}
index -= usedHeadBits;
const uint32_t element = (index >> 5) + 1;
const uint32_t bitIndex = index & (NumBitsetChunkedBits - 1);
return SetBitHelper(m_container[GetChunkIndexHelper(element)], bitIndex, value);
}
template <uint32_t SIZE>
inline uint32_t RingbufferBitset<SIZE>::GetValidBitCount() const
{
return SIZE - NumBitsetChunkedBits;
}
template <uint32_t SIZE>
inline void RingbufferBitset<SIZE>::PushBackBits(uint32_t count)
{
const uint32_t headBitsToShift = std::min<uint32_t>(count, m_unusedHeadBits);
m_container[m_headElementOffset] <<= headBitsToShift;
m_unusedHeadBits -= headBitsToShift;
count -= headBitsToShift;
if (m_unusedHeadBits <= 0)
{
IncrementHead();
}
while (count > NumBitsetChunkedBits)
{
count -= NumBitsetChunkedBits;
IncrementHead();
}
m_unusedHeadBits -= count;
}
template <uint32_t SIZE>
inline const BitsetChunk& RingbufferBitset<SIZE>::GetBitsetElement(uint32_t elementIndex) const
{
return m_container[GetChunkIndexHelper(elementIndex)];
}
template <uint32_t SIZE>
inline uint32_t RingbufferBitset<SIZE>::GetUnusedHeadBits() const
{
return m_unusedHeadBits;
}
template <uint32_t SIZE>
inline uint32_t RingbufferBitset<SIZE>::GetChunkIndexHelper(uint32_t absoluteIndex) const
{
AZ_Assert(absoluteIndex < RingbufferContainerSize, "Out of bounds chunk index requested");
return ((m_headElementOffset + RingbufferContainerSize) - absoluteIndex) % RingbufferContainerSize;
}
template <uint32_t SIZE>
inline void RingbufferBitset<SIZE>::IncrementHead()
{
m_headElementOffset = (m_headElementOffset + 1) % RingbufferContainerSize;
m_unusedHeadBits = NumBitsetChunkedBits;
m_container[m_headElementOffset] = 0;
}
template <typename TYPE>
inline bool GetBitHelper(TYPE element, uint32_t bitIndex)
{
AZ_Assert(bitIndex < AZ::Log2(TYPE(~0)), "Out of bounds access (requested %u, size %u)", bitIndex, AZ::Log2(TYPE(~0)));
return ((element >> bitIndex) & 0x01) ? true : false;
}
template <typename TYPE>
inline void SetBitHelper(TYPE &element, uint32_t bitIndex, bool value)
{
AZ_Assert(bitIndex < AZ::Log2(TYPE(~0)), "Out of bounds access (requested %u, size %u)", bitIndex, AZ::Log2(TYPE(~0)));
const TYPE mask = TYPE(0x01 << bitIndex);
element = (value) ? (element | mask) : (element & (TYPE)(~mask));
}
}
@@ -0,0 +1,129 @@
/*
* 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/DataStructures/TimeoutQueue.h>
#include <AzCore/Console/ILogger.h>
#include <climits>
#include <cinttypes>
namespace AzNetworking
{
void TimeoutQueue::Reset()
{
m_timeoutItemMap.clear();
m_timeoutItemQueue = TimeoutItemQueue();
m_nextTimeoutId = TimeoutId{0};
}
TimeoutId TimeoutQueue::RegisterItem(uint64_t userData, AZ::TimeMs timeoutMs)
{
const TimeoutId timeoutId = m_nextTimeoutId;
const AZ::TimeMs timeoutTimeMs = AZ::GetElapsedTimeMs() + timeoutMs;
AZLOG(TimeoutQueue, "Pushing timeoutid %u with user data %" PRIu64 " to expire at time %u",
aznumeric_cast<uint32_t>(timeoutId),
userData,
aznumeric_cast<uint32_t>(timeoutTimeMs)
);
TimeoutQueueItem queueItem(timeoutId, timeoutTimeMs);
m_timeoutItemMap[timeoutId] = TimeoutItem(userData, timeoutMs);
m_timeoutItemQueue.push(queueItem);
++m_nextTimeoutId;
return timeoutId;
}
TimeoutQueue::TimeoutItem *TimeoutQueue::RetrieveItem(TimeoutId timeoutId)
{
TimeoutItemMap::iterator iter = m_timeoutItemMap.find(timeoutId);
if (iter != m_timeoutItemMap.end())
{
return &(iter->second);
}
return nullptr;
}
void TimeoutQueue::RemoveItem(TimeoutId timeoutId)
{
m_timeoutItemMap.erase(timeoutId);
}
void TimeoutQueue::UpdateTimeouts(ITimeoutHandler& timeoutHandler, int32_t maxTimeouts)
{
int32_t numTimeouts = 0;
if (maxTimeouts < 0)
{
maxTimeouts = INT_MAX;
}
AZ::TimeMs currentTimeMs = AZ::GetElapsedTimeMs();
while (m_timeoutItemQueue.size() > 0)
{
const TimeoutQueueItem queueItem = m_timeoutItemQueue.top();
const TimeoutId itemTimeoutId = queueItem.m_timeoutId;
const AZ::TimeMs itemTimeoutMs = queueItem.m_timeoutTimeMs;
// Head item has not timed out yet, we can terminate because we've run out of timed out items
if (itemTimeoutMs >= currentTimeMs)
{
break;
}
++numTimeouts;
if (numTimeouts >= maxTimeouts)
{
AZLOG_WARN("Terminating timeout queue iteration due to hitting timeout count limit: %d", numTimeouts);
break;
}
// Pop the item, we're either going to time it out or reinsert it
m_timeoutItemQueue.pop();
TimeoutItemMap::iterator iter = m_timeoutItemMap.find(itemTimeoutId);
if (iter == m_timeoutItemMap.end())
{
// Item has already been deleted, just continue
continue;
}
TimeoutItem mapItem = iter->second;
// Check to see if the item has been refreshed since it was inserted
if (mapItem.m_nextTimeoutTimeMs > currentTimeMs)
{
TimeoutQueueItem reQueueItem(itemTimeoutId, mapItem.m_nextTimeoutTimeMs);
m_timeoutItemQueue.push(reQueueItem);
continue;
}
// By this point, the item is definitely timed out
// Invoke the timeout function to see how to proceed
const TimeoutResult result = timeoutHandler.HandleTimeout(mapItem);
if (result == TimeoutResult::Refresh)
{
mapItem.UpdateTimeoutTime(currentTimeMs);
// Re-insert into priority queue
TimeoutQueueItem reQueueItem(itemTimeoutId, mapItem.m_nextTimeoutTimeMs);
m_timeoutItemQueue.push(reQueueItem);
continue;
}
AZLOG(TimeoutQueue, "Popping timeoutid %u with user data %" PRIu64 ", expire time %d, current time %u",
aznumeric_cast<uint32_t>(itemTimeoutId),
mapItem.m_userData,
aznumeric_cast<uint32_t>(mapItem.m_nextTimeoutTimeMs),
aznumeric_cast<uint32_t>(currentTimeMs));
m_timeoutItemMap.erase(itemTimeoutId);
}
}
}
@@ -0,0 +1,109 @@
/*
* 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/RTTI/TypeSafeIntegral.h>
#include <AzCore/std/containers/map.h>
#include <AzCore/std/containers/queue.h>
namespace AzNetworking
{
AZ_TYPE_SAFE_INTEGRAL(TimeoutId, uint32_t);
enum class TimeoutResult
{
Refresh,
Delete
};
class ITimeoutHandler;
//! @class TimeoutQueue
//! @brief class for managing timeout items.
class TimeoutQueue
{
public:
struct TimeoutItem
{
TimeoutItem() = default;
TimeoutItem(uint64_t userData, AZ::TimeMs timeoutMs);
void UpdateTimeoutTime(AZ::TimeMs currentTimeMs);
uint64_t m_userData = 0;
AZ::TimeMs m_timeoutMs = AZ::TimeMs{0};
AZ::TimeMs m_nextTimeoutTimeMs = AZ::TimeMs{0};
};
TimeoutQueue() = default;
~TimeoutQueue() = default;
//! Resets all internal state for this timeout queue.
void Reset();
//! Registers a new item with the TimeoutQueue.
//! @param userData value to register a timeout callback for
//! @param timeoutMs number of milliseconds to trigger the callback after
//! @return boolean true if registration was successful
TimeoutId RegisterItem(uint64_t userData, AZ::TimeMs timeoutMs);
//! Returns the provided timeout item if it exists, also refreshes the timeout value.
//! @param timeoutId the identifier of the item to fetch
//! @return pointer to the timeout item if it exists
TimeoutItem *RetrieveItem(TimeoutId timeoutId);
//! Removes an item from the TimeoutQueue.
//! @param timeoutId the identifier of the item to remove
void RemoveItem(TimeoutId timeoutId);
//! Updates timeouts for all items, invokes timeout handlers if required.
//! @param timeoutHandler listener instance to call back on for timeouts
//! @param maxTimeouts the maximum number of timeouts to process before breaking iteration
void UpdateTimeouts(ITimeoutHandler& timeoutHandler, int32_t maxTimeouts = -1);
private:
struct TimeoutQueueItem
{
TimeoutQueueItem(TimeoutId timeoutId, AZ::TimeMs timeoutTimeMs);
bool operator < (const TimeoutQueueItem &rhs) const;
TimeoutId m_timeoutId;
AZ::TimeMs m_timeoutTimeMs;
};
using TimeoutItemMap = AZStd::map<TimeoutId, TimeoutItem>;
using TimeoutItemQueue = AZStd::priority_queue<TimeoutQueueItem>;
TimeoutId m_nextTimeoutId = TimeoutId{ 0 };
TimeoutItemMap m_timeoutItemMap;
TimeoutItemQueue m_timeoutItemQueue;
};
//! @class ITimeoutHandler
//! @brief interface class for managing timeout items.
class ITimeoutHandler
{
public:
//! Handler callback for timed out items.
//! @param item containing registered timeout details
//! @return ETimeoutResult for whether to re-register or discard the timeout params
virtual TimeoutResult HandleTimeout(TimeoutQueue::TimeoutItem& item) = 0;
};
}
#include <AzNetworking/DataStructures/TimeoutQueue.inl>
@@ -0,0 +1,41 @@
/*
* 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 TimeoutQueue::TimeoutItem::TimeoutItem(uint64_t userData, AZ::TimeMs timeoutMs)
: m_userData(userData)
, m_timeoutMs(timeoutMs)
, m_nextTimeoutTimeMs(AZ::GetElapsedTimeMs() + timeoutMs)
{
;
}
inline void TimeoutQueue::TimeoutItem::UpdateTimeoutTime(AZ::TimeMs currentTimeMs)
{
m_nextTimeoutTimeMs = currentTimeMs + m_timeoutMs;
}
inline TimeoutQueue::TimeoutQueueItem::TimeoutQueueItem(TimeoutId timeoutId, AZ::TimeMs timeoutTimeMs)
: m_timeoutId(timeoutId)
, m_timeoutTimeMs(timeoutTimeMs)
{
;
}
inline bool TimeoutQueue::TimeoutQueueItem::operator <(const TimeoutQueueItem& rhs) const
{
return rhs.m_timeoutTimeMs < m_timeoutTimeMs;
}
}
@@ -0,0 +1,99 @@
/*
* 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/PlatformDef.h>
#include <AzCore/std/functional.h>
#include <AzCore/std/smart_ptr/unique_ptr.h>
#include <AzCore/RTTI/TypeSafeIntegral.h>
namespace AzNetworking
{
//! Collection of compression related error codes
enum class CompressorError
{
Ok, ///< No error, operation finished successfully
InsufficientBuffer, ///< Buffer size is insufficient for the operation to complete, increase the size and try again
CorruptData, ///< Malformed or hacked packet, potentially security issue
Uninitialized ///< Compressor or supplied buffers are uninitialized
};
//! Unique identifier of a given compressor
AZ_TYPE_SAFE_INTEGRAL(CompressorType, uint32_t);
//! @class ICompressor
//! @brief Packet data compressor interface.
class ICompressor
{
public:
virtual ~ICompressor() = default;
//! Initialize compressor.
virtual bool Init() = 0;
//! Unique identifier of a given compressor.
virtual CompressorType GetType() const = 0;
//! Returns max possible size of uncompressed data chunk needed to fit compressed data in maxCompSize bytes.
virtual AZStd::size_t GetMaxChunkSize(AZStd::size_t maxCompSize) const = 0;
//! Returns size of compressed buffer needed to uncompress uncompSize of bytes.
virtual AZStd::size_t GetMaxCompressedBufferSize(AZStd::size_t uncompSize) const = 0;
//! Finalizes the stream, and returns composed packet.
//! Chunk based compressors should loop internally in Compress() to compress all chunks of uncompData.
//! @param uncompData buffer to compress
//! @param uncompSize length of data to compress from uncompData
//! @param compData should be able to fit at least GetMaxCompressedBufferSize(uncompSize) bytes
//! @param compDataSize size of compData buffer
//! @param compSize length of compressed data written into compData
virtual CompressorError Compress
(
const void* uncompData,
AZStd::size_t uncompSize,
void* compData,
AZStd::size_t compDataSize,
AZStd::size_t& compSize
) = 0;
//! Decompress packet.
//! Chunk based decompressors should loop internally in Decompress() to decompress all chunks of compData.
//! @param compData buffer to decompress
//! @param compSize length of data to decompress from compData
//! @param uncompData should be able to fit at least GetDecompressedBufferSize(compressedDataSize)
//! @param uncompDataSize size of uncompData buffer.
//! @param consumedSize the number of bytes processed out of compData. (previously named chunkSize)
//! @param uncompSize length of decompressed data written into uncompData
virtual CompressorError Decompress
(
const void* compData,
AZStd::size_t compDataSize,
void* uncompData,
AZStd::size_t uncompDataSize,
AZStd::size_t& consumedSize,
AZStd::size_t& uncompSize
) = 0;
};
//! Abstract factory to instantiate compressors.
//! Used by the network interface to create a compressor
class ICompressorFactory
{
public:
virtual ~ICompressorFactory() = default;
virtual AZStd::unique_ptr<ICompressor> Create() = 0;
};
}
AZ_TYPE_SAFE_INTEGRAL_SERIALIZEBINDING(AzNetworking::CompressorType);
@@ -0,0 +1,119 @@
/*
* 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/Name/Name.h>
#include <AzCore/RTTI/RTTI.h>
#include <AzNetworking/Framework/NetworkInterfaceMetrics.h>
#include <AzNetworking/ConnectionLayer/IConnectionSet.h>
#include <AzNetworking/ConnectionLayer/IConnectionListener.h>
#include <AzCore/std/containers/vector.h>
namespace AzNetworking
{
//! @class INetworkInterface
//! @brief pure virtual network interface class to abstract client/server and tcp/udp concerns from application code.
class INetworkInterface
{
public:
AZ_RTTI(INetworkInterface, "{ECDA6FA2-4AA0-435E-881F-214C4B179A31}");
virtual ~INetworkInterface() = default;
//! Retrieves the name of this network interface instance.
//! @return the name of this network interface instance
virtual AZ::Name GetName() const = 0;
//! Retrieves the type of this network interface instance.
//! @return the type of this network interface instance
virtual ProtocolType GetType() const = 0;
//! Retrieves the trust zone for this network interface instance.
//! @return the trust zone for this network interface instance
virtual TrustZone GetTrustZone() const = 0;
//! Returns the port number this network interface is bound to.
//! @return the port number this network interface is bound to
virtual uint16_t GetPort() const = 0;
//! Returns the connection set for this network interface.
//! @return the connection set for this network interface
virtual IConnectionSet& GetConnectionSet() = 0;
//! Returns a reference to the connection listener for this network interface.
//! @return reference to the connection listener for this network interface
virtual IConnectionListener& GetConnectionListener() = 0;
//! Opens the network interface to allow it to accept new incoming connections.
//! @param port the listen port number this network interface will potentially bind to, 0 if it's a don't care
//! @return boolean true if the operation was successful, false if it failed
virtual bool Listen(uint16_t port) = 0;
//! Opens a new connection to the provided address.
//! @param remoteAddress the IpAddress of the remote process to open a connection to
//! @return the connectionId of the new connection, or InvalidConnectionId if the operation failed
virtual ConnectionId Connect(const IpAddress& remoteAddress) = 0;
//! Updates the INetworkInterface.
//! @param deltaTimeMs milliseconds since update was last invoked
virtual void Update(AZ::TimeMs deltaTimeMs) = 0;
//! A helper function that transmits a packet on this connection reliably.
//! Note that a packetId is not returned here, since retransmits may cause the packetId to change
//! @param connectionId identifier of the connection to send to
//! @param packet packet to transmit
//! @return boolean true if the packet was transmitted (not an indication of delivery)
virtual bool SendReliablePacket(ConnectionId connectionId, const IPacket& packet) = 0;
//! A helper function that transmits a packet on this connection unreliably.
//! @param connectionId identifier of the connection to send to
//! @param packet packet to transmit
//! @return the unreliable packet identifier of the transmitted packet
virtual PacketId SendUnreliablePacket(ConnectionId connectionId, const IPacket& packet) = 0;
//! Returns true if the given packet id was confirmed acknowledged by the remote endpoint, false otherwise.
//! @param connectionId identifier of the connection to send to
//! @param packetId the packet id of the packet to confirm acknowledgment of
//! @return boolean true if the packet is confirmed acknowledged, false if the packet number is out of range, lost, or still pending acknowledgment
virtual bool WasPacketAcked(ConnectionId connectionId, PacketId packetId) = 0;
//! Disconnects the specified connection.
//! @param connectionId identifier of the connection to terminate
//! @param reason reason for the disconnect
//! @return boolean true on success
virtual bool Disconnect(ConnectionId connectionId, DisconnectReason reason) = 0;
//! Const access to the metrics tracked by this network interface.
//! @return const reference to the metrics tracked by this network interface
const NetworkInterfaceMetrics& GetMetrics() const;
//! Non-const access to the metrics tracked by this network interface.
//! @return reference to the metrics tracked by this network interface
NetworkInterfaceMetrics& GetMetrics();
private:
NetworkInterfaceMetrics m_metrics;
};
inline const NetworkInterfaceMetrics& INetworkInterface::GetMetrics() const
{
return m_metrics;
}
inline NetworkInterfaceMetrics& INetworkInterface::GetMetrics()
{
return m_metrics;
}
}
@@ -0,0 +1,49 @@
/*
* 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/Framework/INetworkInterface.h>
#include <AzNetworking/ConnectionLayer/IConnection.h>
#include <AzNetworking/ConnectionLayer/IConnectionListener.h>
namespace AzNetworking
{
//! @class INetworking
//! @brief The interface for creating and working with network interfaces.
class INetworking
{
public:
AZ_RTTI(INetworking, "{6E47367B-3AA5-4CB8-A691-4910168F287A}");
virtual ~INetworking() = default;
//! Creates a new network interface instance with the provided parameters.
//! Caller does not assume ownership, instance should be destroyed by calling DestroyNetworkInterface
//! @param name the name to assign to this network interface
//! @param protocolType the type of interface to instantiate (Tcp or Udp)
//! @param trustZone the trust level associated with this network interface (client to server or server to server)
//! @param listener the connection listener responsible for handling connection events
//! @return pointer to the instantiated network interface, or nullptr on error
virtual INetworkInterface* CreateNetworkInterface(AZ::Name name, ProtocolType protocolType, TrustZone trustZone, IConnectionListener& listener) = 0;
//! Retrieves a network interface instance by name.
//! @param name the name of the network interface to retrieve
//! @return pointer to the requested network interface, or nullptr on error
virtual INetworkInterface* RetrieveNetworkInterface(AZ::Name name) = 0;
//! Destroys a network interface instance by name.
//! @param name the name of the network interface to destroy
//! @return boolean true on success or false on failure
virtual bool DestroyNetworkInterface(AZ::Name name) = 0;
};
}
@@ -0,0 +1,50 @@
/*
* 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>
namespace AzNetworking
{
struct NetworkInterfaceMetrics
{
//! Returns the total number of milliseconds spent updating this network interface.
AZ::TimeMs m_updateTimeMs = AZ::TimeMs{ 0 };
//! Returns the total number of connections bound to this network interface.
uint64_t m_connectionCount = 0;
//! Returns the total number of milliseconds spent sending data on this network interface.
AZ::TimeMs m_sendTimeMs = AZ::TimeMs{ 0 };
//! Returns the total number of packets sent on this socket.
uint64_t m_sendPackets = 0;
//! Returns the total number of bytes sent on this socket after compression.
uint64_t m_sendBytes = 0;
//! Returns the total number of bytes sent on this socket before compression.
uint64_t m_sendBytesUncompressed = 0;
//! Returns the total number of compressed packets sent on this socket that showed no gain over uncompressed size.
uint64_t m_sendCompressedPacketsNoGain = 0;
//! Returns the delta gain of bytes saved (+) or lost (-) due to compression.
int64_t m_sendBytesCompressedDelta = 0;
//! Returns the total number of packets that had to be resent on this network interface due to packet loss.
uint64_t m_resentPackets = 0;
//! Returns the total number of milliseconds spent processing received data on this network interface.
AZ::TimeMs m_recvTimeMs = AZ::TimeMs{ 0 };
//! Returns the total number of packets received on this socket.
uint64_t m_recvPackets = 0;
//! Returns the total number of bytes received on this socket after compression.
uint64_t m_recvBytes = 0;
//! Returns the total number of bytes received on this socket before compression.
uint64_t m_recvBytesUncompressed = 0;
//! Returns the total number of packets that were discarded due to timeslice budgets.
uint64_t m_discardedPackets = 0;
};
}
@@ -0,0 +1,157 @@
/*
* 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/Framework/NetworkingSystemComponent.h>
#include <AzNetworking/TcpTransport/TcpNetworkInterface.h>
#include <AzNetworking/UdpTransport/UdpNetworkInterface.h>
#include <AzCore/Interface/Interface.h>
#include <AzCore/Console/IConsole.h>
#include <AzCore/Console/ILogger.h>
#include <AzCore/Serialization/SerializeContext.h>
namespace AzNetworking
{
void NetworkingSystemComponent::Reflect(AZ::ReflectContext* context)
{
if (AZ::SerializeContext* serializeContext = azrtti_cast<AZ::SerializeContext*>(context))
{
serializeContext->Class<NetworkingSystemComponent, AZ::Component>()
->Version(1);
}
}
void NetworkingSystemComponent::GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& provided)
{
provided.push_back(AZ_CRC_CE("NetworkingService"));
}
void NetworkingSystemComponent::GetIncompatibleServices(AZ::ComponentDescriptor::DependencyArrayType& incompatible)
{
incompatible.push_back(AZ_CRC_CE("NetworkingService"));
}
NetworkingSystemComponent::NetworkingSystemComponent()
{
SocketLayerInit();
//EncryptionLayerInit();
AZ::Interface<INetworking>::Register(this);
m_listenThread = AZStd::make_unique<TcpListenThread>();
m_readerThread = AZStd::make_unique<UdpReaderThread>();
}
NetworkingSystemComponent::~NetworkingSystemComponent()
{
// Delete all our network interfaces first so they can unregister from the reader and listen threads
m_networkInterfaces.clear();
m_readerThread = nullptr;
m_listenThread = nullptr;
AZ::Interface<INetworking>::Unregister(this);
//EncryptionLayerShutdown();
SocketLayerShutdown();
}
void NetworkingSystemComponent::Activate()
{
AZ::TickBus::Handler::BusConnect();
}
void NetworkingSystemComponent::Deactivate()
{
AZ::TickBus::Handler::BusDisconnect();
}
void NetworkingSystemComponent::OnTick(float deltaTime, [[maybe_unused]] AZ::ScriptTimePoint time)
{
AZ::TimeMs elapsedMs = aznumeric_cast<AZ::TimeMs>(aznumeric_cast<int64_t>(deltaTime / 1000.0f));
m_readerThread->SwapBuffers();
for (auto& networkInterface : m_networkInterfaces)
{
networkInterface.second->Update(elapsedMs);
}
}
int NetworkingSystemComponent::GetTickOrder()
{
return AZ::TICK_PLACEMENT;
}
INetworkInterface* NetworkingSystemComponent::CreateNetworkInterface(AZ::Name name, ProtocolType protocolType, TrustZone trustZone, IConnectionListener& listener)
{
AZ_Assert(RetrieveNetworkInterface(name) == nullptr, "A network interface with this name already exists");
AZStd::unique_ptr<INetworkInterface> result = nullptr;
switch (protocolType)
{
case ProtocolType::Tcp:
result = AZStd::make_unique<TcpNetworkInterface>(name, listener, trustZone, *m_listenThread);
break;
case ProtocolType::Udp:
result = AZStd::make_unique<UdpNetworkInterface>(name, listener, trustZone, *m_readerThread);
break;
}
INetworkInterface* returnResult = result.get();
if (result != nullptr)
{
m_networkInterfaces.emplace(name, AZStd::move(result));
}
return returnResult;
}
INetworkInterface* NetworkingSystemComponent::RetrieveNetworkInterface(AZ::Name name)
{
auto networkInterface = m_networkInterfaces.find(name);
if (networkInterface != m_networkInterfaces.end())
{
return networkInterface->second.get();
}
return nullptr;
}
bool NetworkingSystemComponent::DestroyNetworkInterface(AZ::Name name)
{
return m_networkInterfaces.erase(name) > 0;
}
void NetworkingSystemComponent::DumpStats([[maybe_unused]] const AZ::ConsoleCommandContainer& arguments)
{
AZLOG_INFO("Total sockets monitored by TcpListenThread: %u", m_listenThread->GetSocketCount());
AZLOG_INFO("Total time spent updating TcpListenThread: %lld", aznumeric_cast<AZ::s64>(m_listenThread->GetUpdateTimeMs()));
AZLOG_INFO("Total sockets monitored by UdpReaderThread: %u", m_readerThread->GetSocketCount());
AZLOG_INFO("Total time spent updating UdpReaderThread: %lld", aznumeric_cast<AZ::s64>(m_readerThread->GetUpdateTimeMs()));
for (auto& networkInterface : m_networkInterfaces)
{
const char* protocol = networkInterface.second->GetType() == ProtocolType::Tcp ? "Tcp" : "Udp";
const char* trustZone = networkInterface.second->GetTrustZone() == TrustZone::ExternalClientToServer ? "ExternalClientToServer" : "InternalServerToServer";
const uint32_t port = aznumeric_cast<uint32_t>(networkInterface.second->GetPort());
AZLOG_INFO("%sNetworkInterface: %s - open to %s on port %u", protocol, networkInterface.second->GetName().GetCStr(), trustZone, port);
const NetworkInterfaceMetrics& metrics = networkInterface.second->GetMetrics();
AZLOG_INFO(" - Total time spent updating in milliseconds: %lld", aznumeric_cast<AZ::s64>(metrics.m_updateTimeMs));
AZLOG_INFO(" - Total number of connections: %llu", aznumeric_cast<AZ::u64>(metrics.m_connectionCount));
AZLOG_INFO(" - Total send time in milliseconds: %lld", aznumeric_cast<AZ::s64>(metrics.m_sendTimeMs));
AZLOG_INFO(" - Total sent packets: %llu", aznumeric_cast<AZ::s64>(metrics.m_sendPackets));
AZLOG_INFO(" - Total sent bytes after compression: %llu", aznumeric_cast<AZ::u64>(metrics.m_sendBytes));
AZLOG_INFO(" - Total sent bytes before compression: %llu", aznumeric_cast<AZ::u64>(metrics.m_sendBytesUncompressed));
AZLOG_INFO(" - Total sent compressed packets without benefit: %llu", aznumeric_cast<AZ::u64>(metrics.m_sendCompressedPacketsNoGain));
AZLOG_INFO(" - Total gain from packet compression: %lld", aznumeric_cast<AZ::s64>(metrics.m_sendBytesCompressedDelta));
AZLOG_INFO(" - Total packets resent: %llu", aznumeric_cast<AZ::u64>(metrics.m_resentPackets));
AZLOG_INFO(" - Total receive time in milliseconds: %lld", aznumeric_cast<AZ::s64>(metrics.m_recvTimeMs));
AZLOG_INFO(" - Total received packets: %llu", aznumeric_cast<AZ::u64>(metrics.m_recvPackets));
AZLOG_INFO(" - Total received bytes after compression: %llu", aznumeric_cast<AZ::u64>(metrics.m_recvBytes));
AZLOG_INFO(" - Total received bytes before compression: %llu", aznumeric_cast<AZ::u64>(metrics.m_recvBytesUncompressed));
AZLOG_INFO(" - Total packets discarded due to load: %llu", aznumeric_cast<AZ::u64>(metrics.m_discardedPackets));
}
}
}
@@ -0,0 +1,78 @@
/*
* 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/Name/Name.h>
#include <AzNetworking/Framework/INetworking.h>
#include <AzNetworking/Framework/INetworkInterface.h>
#include <AzNetworking/TcpTransport/TcpListenThread.h>
#include <AzNetworking/UdpTransport/UdpReaderThread.h>
#include <AzCore/Component/Component.h>
#include <AzCore/Component/TickBus.h>
#include <AzCore/Console/IConsole.h>
#include <AzCore/Console/ILogger.h>
#include <AzCore/std/containers/unordered_map.h>
namespace AzNetworking
{
//! Implementation of the networking system interface.
//! This class creates and manages the set of network interfaces used by the application.
class NetworkingSystemComponent final
: public AZ::Component
, public AZ::TickBus::Handler
, public INetworking
{
public:
AZ_COMPONENT(NetworkingSystemComponent, "{29914D25-5E8F-49C9-8C57-5125ABD3D489}");
static void Reflect(AZ::ReflectContext* context);
static void GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& provided);
static void GetIncompatibleServices(AZ::ComponentDescriptor::DependencyArrayType& incompatible);
NetworkingSystemComponent();
~NetworkingSystemComponent() override;
//! AZ::Component overrides.
//! @{
void Activate() override;
void Deactivate() override;
//! @}
//! AZ::TickBus::Handler overrides.
//! @{
void OnTick(float deltaTime, AZ::ScriptTimePoint time) override;
int GetTickOrder() override;
//! @}
//! INetworking overrides.
//! @{
INetworkInterface* CreateNetworkInterface(AZ::Name name, ProtocolType protocolType, TrustZone trustZone, IConnectionListener& listener) override;
INetworkInterface* RetrieveNetworkInterface(AZ::Name name) override;
bool DestroyNetworkInterface(AZ::Name name) override;
//! @}
//! Console commands.
//! @{
void DumpStats(const AZ::ConsoleCommandContainer& arguments);
//! @}
private:
AZ_CONSOLEFUNC(NetworkingSystemComponent, DumpStats, AZ::ConsoleFunctorFlags::Null, "Dumps stats for all instantiated network interfaces");
using NetworkInterfaces = AZStd::unordered_map<AZ::Name, AZStd::unique_ptr<INetworkInterface>>;
NetworkInterfaces m_networkInterfaces;
AZStd::unique_ptr<TcpListenThread> m_listenThread;
AZStd::unique_ptr<UdpReaderThread> m_readerThread;
};
}
@@ -0,0 +1,50 @@
/*
* 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/RTTI/TypeInfo.h>
#include <AzCore/RTTI/TypeSafeIntegral.h>
#include <AzCore/std/smart_ptr/unique_ptr.h>
namespace AzNetworking
{
class ISerializer;
AZ_TYPE_SAFE_INTEGRAL(PacketType, uint16_t);
//! @class IPacket
//! @brief Base class for all packets.
class IPacket
{
public:
AZ_TYPE_INFO(IPacket, "{1B33BFE8-8A4B-44E3-8C8D-3B924093227C}");
virtual ~IPacket() = default;
//! Returns the packet type.
//! @return packet type
virtual PacketType GetPacketType() const = 0;
//! Returns an identical copy of the current packet instance, caller assumes ownership.
//! @return copy of the current packet instance, caller assumes ownership
virtual AZStd::unique_ptr<IPacket> Clone() const = 0;
//! 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
virtual bool Serialize(ISerializer& serializer) = 0;
};
}
AZ_TYPE_SAFE_INTEGRAL_SERIALIZEBINDING(AzNetworking::PacketType);
@@ -0,0 +1,54 @@
/*
* 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/Preprocessor/Enum.h>
#include <AzNetworking/PacketLayer/IPacket.h>
#include <AzNetworking/DataStructures/FixedSizeBitset.h>
#include <AzNetworking/ConnectionLayer/ConnectionMetrics.h>
namespace AzNetworking
{
AZ_ENUM_CLASS(PacketFlag
, Compressed
, MAX
);
using PacketFlagBitset = FixedSizeBitset<1, uint8_t>;
static_assert(aznumeric_cast<int>(PacketFlag::MAX) <= 8, "PacketFlags are limited to 1 byte (8 flags)");
//! @class IPacketHeader
//! @brief A packet header that lets us deduce packet type for any incoming packet.
class IPacketHeader
{
public:
AZ_TYPE_INFO(IPacketHeader, "{90A0EFE3-01A4-4F04-87CF-E98E94D49648}");
virtual ~IPacketHeader() = default;
//! Returns the packet type.
//! @return packet type
virtual PacketType GetPacketType() const = 0;
//! Returns the packet id.
//! @return PacketId
virtual PacketId GetPacketId() const = 0;
//! Returns if the specified packet flag is set for this packet.
//! @return true if the flag is set for this packet
virtual bool IsPacketFlagSet(PacketFlag flag) const = 0;
//! Sets the specified packet flag for this packet.
virtual void SetPacketFlag(PacketFlag flag, bool value) = 0;
};
}
@@ -0,0 +1,58 @@
/*
* 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 AbstractValue
{
class BaseValue {};
template <typename T>
class ValueT
: public BaseValue
{
public:
ValueT() : m_value() {}
ValueT(const T& value) : m_value(value) {}
const T& GetValue() const { return m_value; }
private:
T m_value;
};
template <>
class ValueT<char*>
: public BaseValue
{
public:
ValueT() : m_value() {}
ValueT(const char* value) : m_value(value) {}
const char* GetValue() const { return m_value.c_str(); }
private:
AZStd::string m_value;
};
using Bool = ValueT<bool>;
using Char = ValueT<char>;
using Float = ValueT<float>;
using Double = ValueT<double>;
using String = ValueT<char*>;
using Int8 = ValueT<int8_t>;
using Int16 = ValueT<int16_t>;
using Int32 = ValueT<int32_t>;
using Int64 = ValueT<int64_t>;
using UInt8 = ValueT<uint8_t>;
using UInt16 = ValueT<uint16_t>;
using UInt32 = ValueT<uint32_t>;
using UInt64 = ValueT<uint64_t>;
}
@@ -0,0 +1,241 @@
/*
* 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/Serialization/ISerializer.h>
#include <AzNetworking/Utilities/NetworkCommon.h>
#include <AzCore/std/containers/stack.h>
#include <AzCore/std/containers/array.h>
#include <AzCore/std/containers/fixed_unordered_map.h>
#include <AzCore/std/containers/map.h>
#include <AzCore/std/string/string.h>
#include <limits>
namespace AzNetworking
{
// Generic AZ Containers
template <typename TYPE>
struct SerializeAzContainer
{
static bool Serialize(ISerializer& serializer, TYPE& container)
{
using ValueType = typename TYPE::value_type;
constexpr uint32_t max = std::numeric_limits<uint32_t>::max(); // Limit to uint32 max elements
const bool write = (serializer.GetSerializerMode() == SerializerMode::WriteToObject);
uint32_t size = static_cast<uint32_t>(container.size());
bool success = serializer.Serialize(size, "Size");
// Dynamic containers require different read/write serialization interfaces
if (write)
{
container.clear();
AzNetworking::AzContainerHelper::ReserveContainer<TYPE>(container, size);
ValueType element;
for (uint32_t i = 0; i < size; ++i)
{
success &= serializer.Serialize(element, GenerateIndexLabel<max>(i).c_str());
container.insert(container.end(), element);
}
}
else
{
uint32_t i = 0;
for (auto it = container.begin(); it != container.end(); ++it, ++i)
{
success &= serializer.Serialize(*it, GenerateIndexLabel<max>(i).c_str());
}
}
return success;
}
};
// fixed size array
template <typename TYPE, AZStd::size_t Size>
struct SerializeAzContainer<AZStd::array<TYPE, Size>>
{
static bool Serialize(ISerializer& serializer, AZStd::array<TYPE, Size>& container)
{
constexpr uint32_t max = static_cast<uint32_t>(Size);
static_assert(Size <= max, "Array size must be less than max.\n");
bool success = true;
int i = 0;
for (auto &elem : container)
{
success &= serializer.Serialize(elem, GenerateIndexLabel<max>(i++).c_str());
}
return success;
}
};
// fixed_unordered_map
template <typename Key, typename MappedType, AZStd::size_t FixedNumBuckets, AZStd::size_t FixedNumElements, class Hasher, class EqualKey>
struct SerializeAzContainer<AZStd::fixed_unordered_map<Key, MappedType, FixedNumBuckets, FixedNumElements, Hasher, EqualKey>>
{
using TYPE = AZStd::fixed_unordered_map<Key, MappedType, FixedNumBuckets, FixedNumElements, Hasher, EqualKey>;
static bool Serialize(ISerializer& serializer, TYPE& container)
{
using SizeType = typename TYPE::size_type;
using ValueType = typename TYPE::value_type;
static_assert(FixedNumElements >= FixedNumBuckets, "fixed_unordered_map buckets is less than elements.");
constexpr uint32_t max = static_cast<uint32_t>(FixedNumElements); // Elements is > Buckets
const bool write = (serializer.GetSerializerMode() == SerializerMode::WriteToObject);
SizeType size = container.size();
bool success = serializer.Serialize(size, "Size");
if (write)
{
container.clear();
ValueType element;
for (uint32_t i = 0; i < size; ++i)
{
success &= serializer.Serialize(element, GenerateIndexLabel<max>(i).c_str());
container.insert(element);
}
}
else
{
uint32_t i = 0;
for (auto it = container.begin(); it != container.end(); ++it, ++i)
{
success &= serializer.Serialize(*it, GenerateIndexLabel<max>(i).c_str());
}
}
return success;
}
};
// fixed_unordered_multimap
template <typename Key, typename MappedType, AZStd::size_t FixedNumBuckets, AZStd::size_t FixedNumElements, class Hasher, class EqualKey>
struct SerializeAzContainer<AZStd::fixed_unordered_multimap<Key, MappedType, FixedNumBuckets, FixedNumElements, Hasher, EqualKey>>
{
using TYPE = AZStd::fixed_unordered_multimap<Key, MappedType, FixedNumBuckets, FixedNumElements, Hasher, EqualKey>;
static bool Serialize(ISerializer& serializer, TYPE& container)
{
using SizeType = typename TYPE::size_type;
using ValueType = typename TYPE::value_type;
static_assert(FixedNumElements >= FixedNumBuckets, "fixed_unordered_multimap buckets is less than elements.");
constexpr uint32_t max = static_cast<uint32_t>(FixedNumElements); // Elements is > Buckets
const bool write = (serializer.GetSerializerMode() == SerializerMode::WriteToObject);
SizeType size = container.size();
bool success = serializer.Serialize(size, "Size");
if (write)
{
container.clear();
ValueType element;
for (uint32_t i = 0; i < size; ++i)
{
success &= serializer.Serialize(element, GenerateIndexLabel<max>(i).c_str());
container.insert(element);
}
}
else
{
uint32_t i = 0;
for (auto it = container.begin(); it != container.end(); ++it, ++i)
{
success &= serializer.Serialize(*it, GenerateIndexLabel<max>(i).c_str());
}
}
return success;
}
};
// multimap
template <class Key, class MappedType, class Compare, class Allocator>
struct SerializeAzContainer<AZStd::multimap<Key, MappedType, Compare, Allocator>>
{
using TYPE = AZStd::multimap<Key, MappedType, Compare, Allocator>;
static bool Serialize(ISerializer& serializer, TYPE& container)
{
using SizeType = typename TYPE::size_type;
using ValueType = typename TYPE::value_type;
constexpr uint32_t max = std::numeric_limits<uint32_t>::max(); // Limit to uint32 max elements
const bool write = (serializer.GetSerializerMode() == SerializerMode::WriteToObject);
SizeType size = static_cast<uint32_t>(container.size());
bool success = serializer.Serialize(size, "Size");
if (write)
{
container.clear();
ValueType element;
for (uint32_t i = 0; i < size; ++i)
{
success &= serializer.Serialize(element, GenerateIndexLabel<max>(i).c_str());
container.insert(element);
}
}
else
{
uint32_t i = 0;
for (auto it = container.begin(); it != container.end(); ++it, ++i)
{
success &= serializer.Serialize(*it, GenerateIndexLabel<max>(i).c_str());
}
}
return success;
}
};
// String
template<>
struct SerializeAzContainer<AZStd::string>
{
static bool Serialize(ISerializer& serializer, AZStd::string& value)
{
uint32_t size = aznumeric_cast<uint32_t>(value.length());
uint32_t outBytes = size;
bool success = serializer.Serialize(size, "Size");
value.resize_no_construct(size);
success &= serializer.SerializeBytes(reinterpret_cast<uint8_t*>(value.data()), size, true, outBytes, "String");
return success && outBytes == size;
}
};
// fixed_string
template <AZStd::size_t MaxElementCount>
struct SerializeAzContainer<AZStd::fixed_string<MaxElementCount>>
{
static bool Serialize(ISerializer& serializer, AZStd::fixed_string<MaxElementCount>& value)
{
using SizeType = typename AZ::SizeType<AZ::RequiredBytesForValue<MaxElementCount>(), false>::Type;
SizeType size = aznumeric_cast<SizeType>(value.length());
uint32_t outBytes = static_cast<uint32_t>(size);
bool success = serializer.Serialize(size, "Size");
value.resize_no_construct(size);
success &= serializer.SerializeBytes(reinterpret_cast<uint8_t*>(value.data()), static_cast<uint32_t>(size), true, outBytes, "String");
return success && outBytes == size;
}
};
// Az Containers
template <typename TYPE>
struct SerializeObjectHelper<TYPE, AZStd::enable_if_t<AzContainerHelper::IsIterableContainer<TYPE>::Value>>
{
static bool SerializeObject(ISerializer& serializer, TYPE& container)
{
return SerializeAzContainer<TYPE>::Serialize(serializer, container);
}
};
}
@@ -0,0 +1,430 @@
/*
* 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/Serialization/DeltaSerializer.h>
#include <AzNetworking/Serialization/NetworkInputSerializer.h>
#include <AzNetworking/Serialization/NetworkOutputSerializer.h>
#include <AzCore/std/string/conversions.h>
namespace AzNetworking
{
SerializerDelta::SerializerDelta()
: m_dirtyBits()
, m_deltaBytes()
{
;
}
uint32_t SerializerDelta::GetNumDirtyBits() const
{
return m_dirtyBits.GetSize();
}
bool SerializerDelta::GetDirtyBit(uint32_t index) const
{
return m_dirtyBits.GetBit(index);
}
bool SerializerDelta::InsertDirtyBit(bool dirtyBit)
{
return m_dirtyBits.PushBack(dirtyBit);
}
uint8_t* SerializerDelta::GetBufferPtr()
{
return m_deltaBytes.GetBuffer();
}
uint32_t SerializerDelta::GetBufferSize() const
{
return static_cast<uint32_t>(m_deltaBytes.GetSize());
}
uint32_t SerializerDelta::GetBufferCapacity() const
{
return static_cast<uint32_t>(m_deltaBytes.GetCapacity());
}
void SerializerDelta::SetBufferSize(uint32_t size)
{
m_deltaBytes.Resize(size);
}
bool SerializerDelta::Serialize(ISerializer& serializer)
{
return serializer.Serialize(m_dirtyBits, "DirtyBits")
&& serializer.Serialize(m_deltaBytes, "DeltaBytes");
}
DeltaSerializerCreate::DeltaSerializerCreate(SerializerDelta& delta)
: m_delta(delta)
, m_dataSerializer(m_delta.GetBufferPtr(), m_delta.GetBufferCapacity())
{
m_namePrefix.reserve(128);
}
DeltaSerializerCreate::~DeltaSerializerCreate()
{
// Delete any left over records that might be hanging around
for (auto iter : m_records)
{
delete iter.second;
}
m_records.clear();
}
SerializerMode DeltaSerializerCreate::GetSerializerMode() const
{
return SerializerMode::ReadFromObject;
}
bool DeltaSerializerCreate::Serialize(bool& value, const char* name)
{
uint32_t unused = 0;
return SerializeHelper(value, 0, false, unused, name);
}
bool DeltaSerializerCreate::Serialize(char& value, const char* name, [[maybe_unused]] char minValue, [[maybe_unused]] char maxValue)
{
uint32_t unused = 0;
return SerializeHelper(value, 0, false, unused, name);
}
bool DeltaSerializerCreate::Serialize(int8_t& value, const char* name, [[maybe_unused]] int8_t minValue, [[maybe_unused]] int8_t maxValue)
{
uint32_t unused = 0;
return SerializeHelper(value, 0, false, unused, name);
}
bool DeltaSerializerCreate::Serialize(int16_t& value, const char* name, [[maybe_unused]] int16_t minValue, [[maybe_unused]] int16_t maxValue)
{
uint32_t unused = 0;
return SerializeHelper(value, 0, false, unused, name);
}
bool DeltaSerializerCreate::Serialize(int32_t& value, const char* name, [[maybe_unused]] int32_t minValue, [[maybe_unused]] int32_t maxValue)
{
uint32_t unused = 0;
return SerializeHelper(value, 0, false, unused, name);
}
bool DeltaSerializerCreate::Serialize(int64_t& value, const char* name, [[maybe_unused]] int64_t minValue, [[maybe_unused]] int64_t maxValue)
{
uint32_t unused = 0;
return SerializeHelper(value, 0, false, unused, name);
}
bool DeltaSerializerCreate::Serialize(uint8_t& value, const char* name, [[maybe_unused]] uint8_t minValue, [[maybe_unused]] uint8_t maxValue)
{
uint32_t unused = 0;
return SerializeHelper(value, 0, false, unused, name);
}
bool DeltaSerializerCreate::Serialize(uint16_t& value, const char* name, [[maybe_unused]] uint16_t minValue, [[maybe_unused]] uint16_t maxValue)
{
uint32_t unused = 0;
return SerializeHelper(value, 0, false, unused, name);
}
bool DeltaSerializerCreate::Serialize(uint32_t& value, const char* name, [[maybe_unused]] uint32_t minValue, [[maybe_unused]] uint32_t maxValue)
{
uint32_t unused = 0;
return SerializeHelper(value, 0, false, unused, name);
}
bool DeltaSerializerCreate::Serialize(uint64_t& value, const char* name, [[maybe_unused]] uint64_t minValue, [[maybe_unused]] uint64_t maxValue)
{
uint32_t unused = 0;
return SerializeHelper(value, 0, false, unused, name);
}
bool DeltaSerializerCreate::Serialize(float& value, const char* name, [[maybe_unused]] float minValue, [[maybe_unused]] float maxValue)
{
uint32_t unused = 0;
return SerializeHelper(value, 0, false, unused, name);
}
bool DeltaSerializerCreate::Serialize(double& value, const char* name, [[maybe_unused]] double minValue, [[maybe_unused]] double maxValue)
{
uint32_t unused = 0;
return SerializeHelper(value, 0, false, unused, name);
}
bool DeltaSerializerCreate::SerializeBytes(uint8_t* buffer, uint32_t bufferCapacity, bool isString, uint32_t& outSize, const char* name)
{
return SerializeHelper(buffer, bufferCapacity, isString, outSize, name);
}
AZStd::string DeltaSerializerCreate::GetNextObjectName(const char* name)
{
AZStd::string objectName = name;
objectName += ".";
objectName += AZStd::to_string(m_objectCounter);
++m_objectCounter;
return objectName;
}
bool DeltaSerializerCreate::BeginObject(const char* name, [[maybe_unused]] const char* typeName)
{
m_nameLengthStack.push_back(m_namePrefix.length());
m_namePrefix += GetNextObjectName(name);
m_namePrefix += ".";
return true;
}
bool DeltaSerializerCreate::EndObject([[maybe_unused]] const char* name, [[maybe_unused]] const char* typeName)
{
const size_t prevLen = m_nameLengthStack.back();
m_nameLengthStack.pop_back();
m_namePrefix.resize(prevLen);
return true;
}
const uint8_t* DeltaSerializerCreate::GetBuffer() const
{
return nullptr;
}
uint32_t DeltaSerializerCreate::GetCapacity() const
{
return 0;
}
uint32_t DeltaSerializerCreate::GetSize() const
{
return 0;
}
template <typename T>
bool DeltaSerializerCreate::SerializeHelper(T& value, uint32_t bufferCapacity, bool isString, uint32_t& outSize, const char* name)
{
typedef AbstractValue::ValueT<T> ValueType;
const size_t prevLen = m_namePrefix.length();
m_namePrefix += GetNextObjectName(name);
const AZ::HashValue32 nameHash = AZ::TypeHash32(m_namePrefix.c_str());
m_namePrefix.resize(prevLen);
AbstractValue::BaseValue*& baseValue = m_records[nameHash];
// If we are in the gather records phase, just save off the value records
if (m_gatheringRecords)
{
if (baseValue != nullptr)
{
AZ_Assert(false, "Duplicate name encountered in delta serializer. This will cause data to be serialized incorrectly.");
return false;
}
baseValue = new ValueType(value);
}
else // If we are not gathering records, then we are comparing them
{
bool different = false;
if (baseValue)
{
// This record must match the same type that was pushed into the list during the gathering phase
ValueType* typedValue = static_cast<ValueType*>(baseValue);
// Are the two values different?
different = typedValue->GetValue() != value;
}
else
{
// No record? Then definitely different
different = true;
}
// Record a bit to track this information
if (!m_delta.InsertDirtyBit(different))
{
AZ_Assert(false, "Ran out of bits in DeltaSerializerCreate. You are probably trying to serialize an object with too many fields. Consider resizing the bitset in DeltaSerializerCreate");
return false;
}
// If different, also write the data into the delta's buffer
if (different)
{
if (!SerializeHelperImpl(value, bufferCapacity, isString, outSize, name))
{
return false;
}
}
}
return true;
}
template <typename T>
bool DeltaSerializerCreate::SerializeHelperImpl(T& value, uint32_t, bool, uint32_t&, const char* name)
{
ISerializer& ser = m_dataSerializer; // Use interface since it fills in defaulted type info parameters
return ser.Serialize(value, name);
}
bool DeltaSerializerCreate::SerializeHelperImpl(uint8_t* buffer, uint32_t bufferCapacity, bool isString, uint32_t& outSize, const char* name)
{
ISerializer& ser = m_dataSerializer; // Use interface since it fills in defaulted type info parameters
return ser.SerializeBytes(buffer, bufferCapacity, isString, outSize, name);
}
DeltaSerializerApply::DeltaSerializerApply(SerializerDelta& delta)
: m_delta(delta)
, m_dataSerializer(m_delta.GetBufferPtr(), m_delta.GetBufferSize())
{
;
}
SerializerMode DeltaSerializerApply::GetSerializerMode() const
{
return SerializerMode::WriteToObject;
}
bool DeltaSerializerApply::Serialize(bool& value, const char* name)
{
uint32_t unused = 0;
return SerializeHelper(value, 0, false, unused, name);
}
bool DeltaSerializerApply::Serialize(char& value, const char* name, [[maybe_unused]] char minValue, [[maybe_unused]] char maxValue)
{
uint32_t unused = 0;
return SerializeHelper(value, 0, false, unused, name);
}
bool DeltaSerializerApply::Serialize(int8_t& value, const char* name, [[maybe_unused]] int8_t minValue, [[maybe_unused]] int8_t maxValue)
{
uint32_t unused = 0;
return SerializeHelper(value, 0, false, unused, name);
}
bool DeltaSerializerApply::Serialize(int16_t& value, const char* name, [[maybe_unused]] int16_t minValue, [[maybe_unused]] int16_t maxValue)
{
uint32_t unused = 0;
return SerializeHelper(value, 0, false, unused, name);
}
bool DeltaSerializerApply::Serialize(int32_t& value, const char* name, [[maybe_unused]] int32_t minValue, [[maybe_unused]] int32_t maxValue)
{
uint32_t unused = 0;
return SerializeHelper(value, 0, false, unused, name);
}
bool DeltaSerializerApply::Serialize(int64_t& value, const char* name, [[maybe_unused]] int64_t minValue, [[maybe_unused]] int64_t maxValue)
{
uint32_t unused = 0;
return SerializeHelper(value, 0, false, unused, name);
}
bool DeltaSerializerApply::Serialize(uint8_t& value, const char* name, [[maybe_unused]] uint8_t minValue, [[maybe_unused]] uint8_t maxValue)
{
uint32_t unused = 0;
return SerializeHelper(value, 0, false, unused, name);
}
bool DeltaSerializerApply::Serialize(uint16_t& value, const char* name, [[maybe_unused]] uint16_t minValue, [[maybe_unused]] uint16_t maxValue)
{
uint32_t unused = 0;
return SerializeHelper(value, 0, false, unused, name);
}
bool DeltaSerializerApply::Serialize(uint32_t& value, const char* name, [[maybe_unused]] uint32_t minValue, [[maybe_unused]] uint32_t maxValue)
{
uint32_t unused = 0;
return SerializeHelper(value, 0, false, unused, name);
}
bool DeltaSerializerApply::Serialize(uint64_t& value, const char* name, [[maybe_unused]] uint64_t minValue, [[maybe_unused]] uint64_t maxValue)
{
uint32_t unused = 0;
return SerializeHelper(value, 0, false, unused, name);
}
bool DeltaSerializerApply::Serialize(float& value, const char* name, [[maybe_unused]] float minValue, [[maybe_unused]] float maxValue)
{
uint32_t unused = 0;
return SerializeHelper(value, 0, false, unused, name);
}
bool DeltaSerializerApply::Serialize(double& value, const char* name, [[maybe_unused]] double minValue, [[maybe_unused]] double maxValue)
{
uint32_t unused = 0;
return SerializeHelper(value, 0, false, unused, name);
}
bool DeltaSerializerApply::SerializeBytes(uint8_t* buffer, uint32_t bufferCapacity, bool isString, uint32_t& outSize, const char* name)
{
return SerializeHelper(buffer, bufferCapacity, isString, outSize, name);
}
bool DeltaSerializerApply::BeginObject([[maybe_unused]] const char *name, [[maybe_unused]] const char* typeName)
{
return true;
}
bool DeltaSerializerApply::EndObject([[maybe_unused]] const char *name, [[maybe_unused]] const char* typeName)
{
return true;
}
const uint8_t* DeltaSerializerApply::GetBuffer() const
{
return nullptr;
}
uint32_t DeltaSerializerApply::GetCapacity() const
{
return 0;
}
uint32_t DeltaSerializerApply::GetSize() const
{
return 0;
}
template <typename T>
bool DeltaSerializerApply::SerializeHelper(T& value, uint32_t bufferCapacity, bool isString, uint32_t& outSize, const char* name)
{
// If we have run out of delta records, something has gone wrong
if (m_nextDirtyBit >= m_delta.GetNumDirtyBits())
{
return false;
}
const bool hasRecord = m_delta.GetDirtyBit(m_nextDirtyBit);
++m_nextDirtyBit;
// No record in the delta for this field, just skip it
if (!hasRecord)
{
return true; // This isn't an error
}
// There is a record, so serialize the value out of the delta
return SerializeHelperImpl(value, bufferCapacity, isString, outSize, name);
}
template <typename T>
bool DeltaSerializerApply::SerializeHelperImpl(T& value, uint32_t, bool, uint32_t&, const char* name)
{
ISerializer& ser = m_dataSerializer; // Use interface since it fills in defaulted type info parameters
return ser.Serialize(value, name);
}
bool DeltaSerializerApply::SerializeHelperImpl(uint8_t* buffer, uint32_t bufferCapacity, bool isString, uint32_t& outSize, const char* name)
{
ISerializer& ser = m_dataSerializer; // Use interface since it fills in defaulted type info parameters
return ser.SerializeBytes(buffer, bufferCapacity, isString, outSize, name);
}
}
@@ -0,0 +1,175 @@
/*
* 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/Serialization/ISerializer.h>
#include <AzNetworking/Serialization/AbstractValue.h>
#include <AzNetworking/Serialization/NetworkInputSerializer.h>
#include <AzNetworking/Serialization/NetworkOutputSerializer.h>
#include <AzNetworking/DataStructures/FixedSizeVectorBitset.h>
#include <AzNetworking/DataStructures/ByteBuffer.h>
#include <AzCore/Utils/TypeHash.h>
#include <AzCore/std/containers/unordered_map.h>
namespace AzNetworking
{
//! SerializerDelta
//! Encodes information used by DeltaSerializer to create and apply serialization deltas
class SerializerDelta
{
public:
SerializerDelta();
uint32_t GetNumDirtyBits() const;
bool GetDirtyBit(uint32_t index) const;
bool InsertDirtyBit(bool dirtyBit);
uint8_t* GetBufferPtr();
uint32_t GetBufferSize() const;
uint32_t GetBufferCapacity() const;
void SetBufferSize(uint32_t size);
bool Serialize(ISerializer& serializer);
private:
FixedSizeVectorBitset<255> m_dirtyBits;
ByteBuffer<1024> m_deltaBytes;
};
//! A serializer that is used to produce a SerializerDelta between two objects.
//! This delta can be reapplied to the same base object to reconstruct the second object using
//! the DeltaSerializerApply serializer
//! NOTE: The objects serialized must have a consistent serialization footprint i.e. no changes in branches during serialization
class DeltaSerializerCreate
: public ISerializer
{
public:
DeltaSerializerCreate(SerializerDelta& delta);
~DeltaSerializerCreate();
template <typename TYPE>
bool CreateDelta(TYPE& base, TYPE& current);
// ISerializer interfaces
SerializerMode GetSerializerMode() const override;
bool Serialize( bool& value, const char* name) override;
bool Serialize( char& value, const char* name, char minValue, char maxValue) override;
bool Serialize( int8_t& value, const char* name, int8_t minValue, int8_t maxValue) override;
bool Serialize( int16_t& value, const char* name, int16_t minValue, int16_t maxValue) override;
bool Serialize( int32_t& value, const char* name, int32_t minValue, int32_t maxValue) override;
bool Serialize( int64_t& value, const char* name, int64_t minValue, int64_t maxValue) override;
bool Serialize( uint8_t& value, const char* name, uint8_t minValue, uint8_t maxValue) override;
bool Serialize(uint16_t& value, const char* name, uint16_t minValue, uint16_t maxValue) override;
bool Serialize(uint32_t& value, const char* name, uint32_t minValue, uint32_t maxValue) override;
bool Serialize(uint64_t& value, const char* name, uint64_t minValue, uint64_t maxValue) override;
bool Serialize( float& value, const char* name, float minValue, float maxValue) override;
bool Serialize( double& value, const char* name, double minValue, double maxValue) override;
bool SerializeBytes(uint8_t* buffer, uint32_t bufferCapacity, bool isString, uint32_t& outSize, const char* name) override;
bool BeginObject(const char *name, const char* typeName) override;
bool EndObject(const char *name, const char* typeName) override;
const uint8_t* GetBuffer() const override;
uint32_t GetCapacity() const override;
uint32_t GetSize() const override;
void ClearTrackedChangesFlag() override {}
bool GetTrackedChangesFlag() const override { return false; }
// ISerializer interfaces
private:
DeltaSerializerCreate(const DeltaSerializerCreate&) = delete;
DeltaSerializerCreate& operator=(const DeltaSerializerCreate&) = delete;
AZStd::string GetNextObjectName(const char* name);
template <typename T>
bool SerializeHelper(T& value, uint32_t bufferCapacity, bool isString, uint32_t& outSize, const char* name);
template <typename T>
bool SerializeHelperImpl(T& value, uint32_t, bool, uint32_t&, const char* name);
bool SerializeHelperImpl(uint8_t* buffer, uint32_t bufferCapacity, bool isString, uint32_t& outSize, const char* name);
private:
SerializerDelta& m_delta;
bool m_gatheringRecords = false;
uint32_t m_objectCounter = 0;
AZStd::string m_namePrefix;
AZStd::vector<size_t> m_nameLengthStack;
AZStd::unordered_map<AZ::HashValue32, AbstractValue::BaseValue*> m_records;
NetworkInputSerializer m_dataSerializer;
};
//! A serializer that is used to apply a SerializerDelta to a base object in order to reconstruct the second object.
//! NOTE: The objects serialized must have a consistent serialization footprint i.e. no changes in branches during serialization
class DeltaSerializerApply
: public ISerializer
{
public:
DeltaSerializerApply(SerializerDelta& delta);
template <typename TYPE>
bool ApplyDelta(TYPE& output);
// ISerializer interfaces
SerializerMode GetSerializerMode() const override;
bool Serialize( bool& value, const char* name) override;
bool Serialize( char& value, const char* name, char minValue, char maxValue) override;
bool Serialize( int8_t& value, const char* name, int8_t minValue, int8_t maxValue) override;
bool Serialize( int16_t& value, const char* name, int16_t minValue, int16_t maxValue) override;
bool Serialize( int32_t& value, const char* name, int32_t minValue, int32_t maxValue) override;
bool Serialize( int64_t& value, const char* name, int64_t minValue, int64_t maxValue) override;
bool Serialize( uint8_t& value, const char* name, uint8_t minValue, uint8_t maxValue) override;
bool Serialize(uint16_t& value, const char* name, uint16_t minValue, uint16_t maxValue) override;
bool Serialize(uint32_t& value, const char* name, uint32_t minValue, uint32_t maxValue) override;
bool Serialize(uint64_t& value, const char* name, uint64_t minValue, uint64_t maxValue) override;
bool Serialize( float& value, const char* name, float minValue, float maxValue) override;
bool Serialize( double& value, const char* name, double minValue, double maxValue) override;
bool SerializeBytes(uint8_t* buffer, uint32_t bufferCapacity, bool isString, uint32_t& outSize, const char* name) override;
bool BeginObject(const char *name, const char* typeName) override;
bool EndObject(const char *name, const char* typeName) override;
const uint8_t* GetBuffer() const override;
uint32_t GetCapacity() const override;
uint32_t GetSize() const override;
void ClearTrackedChangesFlag() override {}
bool GetTrackedChangesFlag() const override { return false; }
// ISerializer interfaces
private:
DeltaSerializerApply(const DeltaSerializerApply&) = delete;
DeltaSerializerApply& operator=(const DeltaSerializerApply&) = delete;
template <typename T>
bool SerializeHelper(T& value, uint32_t bufferCapacity, bool isString, uint32_t& outSize, const char* name);
template <typename T>
bool SerializeHelperImpl(T& value, uint32_t, bool, uint32_t&, const char* name);
bool SerializeHelperImpl(uint8_t* buffer, uint32_t bufferCapacity, bool isString, uint32_t& outSize, const char* name);
private:
SerializerDelta& m_delta;
uint32_t m_nextDirtyBit = 0;
NetworkOutputSerializer m_dataSerializer;
};
}
#include <AzNetworking/Serialization/DeltaSerializer.inl>
@@ -0,0 +1,45 @@
/*
* 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
{
template <typename TYPE>
bool DeltaSerializerCreate::CreateDelta(TYPE& base, TYPE& current)
{
// Gather value records from the base object
m_gatheringRecords = true;
if (!base.Serialize(*this))
{
return false;
}
m_objectCounter = 0;
// Compile deltas from the new object
m_gatheringRecords = false;
if (!current.Serialize(*this))
{
return false;
}
// Update the delta buffer size based on how much data was serialized
m_delta.SetBufferSize(m_dataSerializer.GetSize());
return true;
}
template <typename TYPE>
bool DeltaSerializerApply::ApplyDelta(TYPE& output)
{
return output.Serialize(*this);
}
}
@@ -0,0 +1,141 @@
/*
* 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/Serialization/HashSerializer.h>
#include <AzNetworking/Utilities/QuantizedValues.h>
namespace AzNetworking
{
// This gives us a hash sensitivity of around 1/128th of a unit, and will detect errors within a range of -16,777,216 to +16,777,216
static const int32_t FloatHashMinValue = (INT_MIN >> 7);
static const int32_t FloatHashMaxValue = (INT_MAX >> 7);
AZ::HashValue64 HashSerializer::GetHash() const
{
return m_hash;
}
SerializerMode HashSerializer::GetSerializerMode() const
{
return SerializerMode::ReadFromObject;
}
bool HashSerializer::Serialize(bool& value, const char*)
{
m_hash = AZ::TypeHash64(value, m_hash);
return true;
}
bool HashSerializer::Serialize(char& value, [[maybe_unused]] const char* name, [[maybe_unused]] char minValue, [[maybe_unused]] char maxValue)
{
m_hash = AZ::TypeHash64(value, m_hash);
return true;
}
bool HashSerializer::Serialize(int8_t& value, [[maybe_unused]] const char* name, [[maybe_unused]] int8_t minValue, [[maybe_unused]] int8_t maxValue)
{
m_hash = AZ::TypeHash64(value, m_hash);
return true;
}
bool HashSerializer::Serialize(int16_t& value, [[maybe_unused]] const char* name, [[maybe_unused]] int16_t minValue, [[maybe_unused]] int16_t maxValue)
{
m_hash = AZ::TypeHash64(value, m_hash);
return true;
}
bool HashSerializer::Serialize(int32_t& value, [[maybe_unused]] const char* name, [[maybe_unused]] int32_t minValue, [[maybe_unused]] int32_t maxValue)
{
m_hash = AZ::TypeHash64(value, m_hash);
return true;
}
bool HashSerializer::Serialize(int64_t& value, [[maybe_unused]] const char* name, [[maybe_unused]] int64_t minValue, [[maybe_unused]] int64_t maxValue)
{
m_hash = AZ::TypeHash64(value, m_hash);
return true;
}
bool HashSerializer::Serialize(uint8_t& value, [[maybe_unused]] const char* name, [[maybe_unused]] uint8_t minValue, [[maybe_unused]] uint8_t maxValue)
{
m_hash = AZ::TypeHash64(value, m_hash);
return true;
}
bool HashSerializer::Serialize(uint16_t& value, [[maybe_unused]] const char* name, [[maybe_unused]] uint16_t minValue, [[maybe_unused]] uint16_t maxValue)
{
m_hash = AZ::TypeHash64(value, m_hash);
return true;
}
bool HashSerializer::Serialize(uint32_t& value, [[maybe_unused]] const char* name, [[maybe_unused]] uint32_t minValue, [[maybe_unused]] uint32_t maxValue)
{
m_hash = AZ::TypeHash64(value, m_hash);
return true;
}
bool HashSerializer::Serialize(uint64_t& value, [[maybe_unused]] const char* name, [[maybe_unused]] uint64_t minValue, [[maybe_unused]] uint64_t maxValue)
{
m_hash = AZ::TypeHash64(value, m_hash);
return true;
}
bool HashSerializer::Serialize(float& value, [[maybe_unused]] const char* name, [[maybe_unused]] float minValue, [[maybe_unused]] float maxValue)
{
// This hashing serializer is used to detect desyncs between the predicted and authoritative state of all predictive values
// If either of these asserts triggers, it means desyncs *will not* be detected for the value being serialized
// You should consider using a quantized float for the failing value, or potentially adjust the min/max quantized values
AZ_Assert(value > FloatHashMinValue, "Out of range float value passed to hashing serializer, this will clamp the float value");
AZ_Assert(value < FloatHashMaxValue, "Out of range float value passed to hashing serializer, this will clamp the float value");
QuantizedValues<1, 4, FloatHashMinValue, FloatHashMaxValue> quantizedValue(value);
const int32_t hashableValue = quantizedValue.GetQuantizedIntegralValues()[0];
m_hash = AZ::TypeHash64(hashableValue, m_hash);
return true;
}
bool HashSerializer::Serialize(double& value, [[maybe_unused]] const char* name, [[maybe_unused]] double minValue, [[maybe_unused]] double maxValue)
{
m_hash = AZ::TypeHash64(value, m_hash);
return true;
}
bool HashSerializer::SerializeBytes(uint8_t* buffer, uint32_t , bool, uint32_t& outSize, [[maybe_unused]] const char* name)
{
m_hash = AZ::TypeHash64(buffer, outSize, m_hash);
return true;
}
bool HashSerializer::BeginObject([[maybe_unused]] const char* name, [[maybe_unused]] const char* typeName)
{
return true;
}
bool HashSerializer::EndObject([[maybe_unused]] const char* name, [[maybe_unused]] const char* typeName)
{
return true;
}
const uint8_t* HashSerializer::GetBuffer() const
{
return nullptr;
}
uint32_t HashSerializer::GetCapacity() const
{
return 0;
}
uint32_t HashSerializer::GetSize() const
{
return 0;
}
}
@@ -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 <AzNetworking/Serialization/ISerializer.h>
#include <AzCore/Utils/TypeHash.h>
namespace AzNetworking
{
//! @class HashSerializer
//! @brief Generate a 32bit integer hash for a serializable object.
//! NOTE: This hash is not designed to be cryptographically secure
class HashSerializer
: public ISerializer
{
public:
HashSerializer() = default;
AZ::HashValue64 GetHash() const;
// ISerializer interfaces
SerializerMode GetSerializerMode() const override;
bool Serialize( bool& value, const char* name) override;
bool Serialize( char& value, const char* name, char minValue, char maxValue) override;
bool Serialize( int8_t& value, const char* name, int8_t minValue, int8_t maxValue) override;
bool Serialize( int16_t& value, const char* name, int16_t minValue, int16_t maxValue) override;
bool Serialize( int32_t& value, const char* name, int32_t minValue, int32_t maxValue) override;
bool Serialize( int64_t& value, const char* name, int64_t minValue, int64_t maxValue) override;
bool Serialize( uint8_t& value, const char* name, uint8_t minValue, uint8_t maxValue) override;
bool Serialize(uint16_t& value, const char* name, uint16_t minValue, uint16_t maxValue) override;
bool Serialize(uint32_t& value, const char* name, uint32_t minValue, uint32_t maxValue) override;
bool Serialize(uint64_t& value, const char* name, uint64_t minValue, uint64_t maxValue) override;
bool Serialize( float& value, const char* name, float minValue, float maxValue) override;
bool Serialize( double& value, const char* name, double minValue, double maxValue) override;
bool SerializeBytes(uint8_t* buffer, uint32_t bufferCapacity, bool isString, uint32_t& outSize, const char* name) override;
bool BeginObject(const char *name, const char* typeName) override;
bool EndObject(const char *name, const char* typeName) override;
const uint8_t* GetBuffer() const override;
uint32_t GetCapacity() const override;
uint32_t GetSize() const override;
void ClearTrackedChangesFlag() override {}
bool GetTrackedChangesFlag() const override { return false; }
// ISerializer interfaces
private:
AZ::HashValue64 m_hash;
};
}
@@ -0,0 +1,202 @@
/*
* 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/limits.h>
namespace AzNetworking
{
class IBitset;
enum class SerializerMode
{
ReadFromObject,
WriteToObject
};
//! @class ISerializer
//! @brief Interface class for all serializers to derive from.
class ISerializer
{
public:
ISerializer() = default;
virtual ~ISerializer() = default;
//! Returns true if the serializer is valid and in a consistent state.
//! @return boolean true if the serializer is valid and in a consistent state
virtual bool IsValid() const;
//! Mark the serializer as invalid.
void Invalidate();
//! Returns an enum the represents the serializer mode.
//! returns WriteToObject if the serializer is writing values to the objects it visits, otherwise returns ReadFromObject
//! @return boolean true if the serializer is writing to objects that it visits
virtual SerializerMode GetSerializerMode() const = 0;
//! Serialize a boolean.
//! @param value boolean input value to serialize
//! @param name string name of the value being serialized
//! @return boolean true for success, false for serialization failure
virtual bool Serialize(bool& value, const char* name) = 0;
//! Serialize a character.
//! @param value character input value to serialize
//! @param name string name of the value being serialized
//! @param minValue the minimum value expected during serialization
//! @param maxValue the maximum value expected during serialization
//! @return boolean true for success, false for failure
virtual bool Serialize(char& value, const char* name, char minValue = AZStd::numeric_limits<char>::min(), char maxValue = AZStd::numeric_limits<char>::max()) = 0;
//! Serialize a signed byte.
//! @param value signed byte input value to serialize
//! @param name string name of the value being serialized
//! @param minValue the minimum value expected during serialization
//! @param maxValue the maximum value expected during serialization
//! @return boolean true for success, false for failure
virtual bool Serialize(int8_t& value, const char* name, int8_t minValue = AZStd::numeric_limits<int8_t>::min(), int8_t maxValue = AZStd::numeric_limits<int8_t>::max()) = 0;
//! Serialize a signed short.
//! @param value signed short input value to serialize
//! @param name string name of the value being serialized
//! @param minValue the minimum value expected during serialization
//! @param maxValue the maximum value expected during serialization
//! @return boolean true for success, false for failure
virtual bool Serialize(int16_t& value, const char* name, int16_t minValue = AZStd::numeric_limits<int16_t>::min(), int16_t maxValue = AZStd::numeric_limits<int16_t>::max()) = 0;
//! Serialize a signed integer.
//! @param value signed integer input value to serialize
//! @param name string name of the value being serialized
//! @param minValue the minimum value expected during serialization
//! @param maxValue the maximum value expected during serialization
//! @return boolean true for success, false for failure
virtual bool Serialize(int32_t& value, const char* name, int32_t minValue = AZStd::numeric_limits<int32_t>::min(), int32_t maxValue = AZStd::numeric_limits<int32_t>::max()) = 0;
//! Serialize a signed 64-bit integer.
//! @param value signed 64-bit integer input value to serialize
//! @param name string name of the value being serialized
//! @param minValue the minimum value expected during serialization
//! @param maxValue the maximum value expected during serialization
//! @return boolean true for success, false for failure
virtual bool Serialize(int64_t& value, const char* name, int64_t minValue = AZStd::numeric_limits<int64_t>::min(), int64_t maxValue = AZStd::numeric_limits<int64_t>::max()) = 0;
//! Serialize an unsigned byte.
//! @param value unsigned byte input value to serialize
//! @param name string name of the value being serialized
//! @param minValue the minimum value expected during serialization
//! @param maxValue the maximum value expected during serialization
//! @return boolean true for success, false for failure
virtual bool Serialize(uint8_t& value, const char* name, uint8_t minValue = AZStd::numeric_limits<uint8_t>::min(), uint8_t maxValue = AZStd::numeric_limits<uint8_t>::max()) = 0;
//! Serialize an unsigned short.
//! @param value signed integer short value to serialize
//! @param name string name of the value being serialized
//! @param minValue the minimum value expected during serialization
//! @param maxValue the maximum value expected during serialization
//! @return boolean true for success, false for failure
virtual bool Serialize(uint16_t& value, const char* name, uint16_t minValue = AZStd::numeric_limits<uint16_t>::min(), uint16_t maxValue = AZStd::numeric_limits<uint16_t>::max()) = 0;
//! Serialize an unsigned integer.
//! @param value signed integer input value to serialize
//! @param name string name of the value being serialized
//! @param minValue the minimum value expected during serialization
//! @param maxValue the maximum value expected during serialization
//! @return boolean true for success, false for failure
virtual bool Serialize(uint32_t& value, const char* name, uint32_t minValue = AZStd::numeric_limits<uint32_t>::min(), uint32_t maxValue = AZStd::numeric_limits<uint32_t>::max()) = 0;
//! Serialize an unsigned 64-bit integer.
//! @param value signed 64-bit integer input value to serialize
//! @param name string name of the value being serialized
//! @param minValue the minimum value expected during serialization
//! @param maxValue the maximum value expected during serialization
//! @return boolean true for success, false for failure
virtual bool Serialize(uint64_t& value, const char* name, uint64_t minValue = AZStd::numeric_limits<uint64_t>::min(), uint64_t maxValue = AZStd::numeric_limits<uint64_t>::max()) = 0;
//! Serialize a 32-bit floating point number.
//! @param value 32-bit floating point input value to serialize
//! @param name string name of the value being serialized
//! @param minValue the minimum value expected during serialization
//! @param maxValue the maximum value expected during serialization
//! @return boolean true for success, false for failure
virtual bool Serialize(float& value, const char* name, float minValue = AZStd::numeric_limits<float>::min(), float maxValue = AZStd::numeric_limits<float>::max()) = 0;
//! Serialize a 64-bit floating point number.
//! @param value 64-bit floating point input value to serialize
//! @param name string name of the value being serialized
//! @param minValue the minimum value expected during serialization
//! @param maxValue the maximum value expected during serialization
//! @return boolean true for success, false for failure
virtual bool Serialize(double& value, const char* name, double minValue = AZStd::numeric_limits<double>::min(), double maxValue = AZStd::numeric_limits<double>::max()) = 0;
//! Serialize a raw set of bytes.
//! @param buffer buffer to serialize
//! @param bufferCapacity size of the buffer
//! @param isString true if the data being serialized is a string
//! @param outSize bytes serialized
//! @param name string name of the object
//! @return boolean true for success, false for serialization failure
virtual bool SerializeBytes(uint8_t* buffer, uint32_t bufferCapacity, bool isString, uint32_t& outSize, const char* name) = 0;
//! Serialize interface for deducing whether or not TYPE is an enum or an object.
//! @param value object instance to serialize
//! @param name string name of the object
//! @param typeInfo basic type information for the value being serialized
//! @return boolean true for success, false for serialization failure
template <typename TYPE>
bool Serialize(TYPE& value, const char* name);
//! Begins serializing an object.
//! @param name string name of the object
//! @param typeInfo basic type information for the value being serialized
//! @return Result. In the case of Skip, Serialize is not called.
virtual bool BeginObject(const char* name, const char* typeName) = 0;
//! Ends serializing an object.
//! @param name string name of the object
//! @param typeInfo basic type information for the value being serialized
//! @return boolean true for success, false for serialization failure
virtual bool EndObject(const char* name, const char* typeName) = 0;
//! Returns a pointer to the internal serialization buffer.
//! @return pointer to the internal serialization buffer
virtual const uint8_t* GetBuffer() const = 0;
//! Returns the total capacity serialization buffer in bytes.
//! @return total capacity serialization buffer in bytes
virtual uint32_t GetCapacity() const = 0;
//! Returns the size of the data contained in the serialization buffer in bytes.
//! @return size of the data contained in the serialization buffer in bytes
virtual uint32_t GetSize() const = 0;
//! This is a helper for network serialization.
//! It clears the track changes flag internal to some serializers
virtual void ClearTrackedChangesFlag() = 0;
//! This is a helper for network serialization.
//! It allows the owner of the serializer to query whether or not the serializer modified the state of an object during serialization
//! @return boolean true if the track changes flag is raised
virtual bool GetTrackedChangesFlag() const = 0;
protected:
template <bool IsEnum, bool IsTypeSafeIntegral>
struct SerializeHelper;
bool m_serializerValid = true; //< Here for performance reasons
};
}
#include <AzNetworking/Serialization/ISerializer.inl>
@@ -0,0 +1,178 @@
/*
* 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/typetraits/underlying_type.h>
#include <AzCore/std/typetraits/conditional.h>
#include <AzCore/std/typetraits/is_same.h>
#include <AzCore/std/typetraits/is_enum.h>
#include <AzCore/RTTI/TypeInfo.h>
#include <AzCore/RTTI/TypeSafeIntegral.h>
namespace AzNetworking
{
// Identifies AZStd containers
struct AzContainerHelper
{
template <typename C>
struct IsIterableContainer
{
template <class TYPE>
static AZStd::false_type Evaluate(...);
template <class TYPE>
static AZStd::true_type Evaluate(int,
typename TYPE::value_type = typename TYPE::value_type(),
typename TYPE::const_iterator = C().begin(),
typename TYPE::const_iterator = C().end(),
typename TYPE::size_type = C().size());
static constexpr bool Value = AZStd::is_same<decltype(Evaluate<C>(0)), AZStd::true_type>::value;
};
template <typename TYPE>
struct HasReserveMethod
{
template <typename U>
static decltype(U().reserve()) Evaluate(int);
template <typename U>
static AZStd::false_type Evaluate(...);
static constexpr bool value = !AZStd::is_same<AZStd::false_type, decltype(Evaluate<TYPE>(0))>::value;
};
template <typename TYPE>
static typename AZStd::Utils::enable_if_c<HasReserveMethod<TYPE>::value>::type ReserveContainer(TYPE& value, typename TYPE::size_type size)
{
value.reserve(size);
}
template<typename TYPE>
static typename AZStd::Utils::enable_if_c<!HasReserveMethod<TYPE>::value>::type ReserveContainer(TYPE&, typename TYPE::size_type)
{
;
}
};
template <typename OBJECT_TYPE>
struct SerializeType
{
static bool Serialize(ISerializer& serializer, OBJECT_TYPE& value)
{
return value.Serialize(serializer);
}
};
// Base template
template <typename TYPE, typename = void>
struct SerializeObjectHelper
{
static bool SerializeObject(ISerializer& serializer, TYPE& value)
{
return value.Serialize(serializer);
}
};
// Non-containers
template <typename TYPE>
struct SerializeObjectHelper<TYPE, AZStd::enable_if_t<!AzContainerHelper::IsIterableContainer<TYPE>::value>>
{
static bool SerializeObject(ISerializer& serializer, TYPE& value)
{
return SerializeType<TYPE>::Serialize(serializer, value);
}
};
inline bool ISerializer::IsValid() const
{
return m_serializerValid;
}
inline void ISerializer::Invalidate()
{
m_serializerValid = false;
}
template <typename TYPE>
inline bool ISerializer::Serialize(TYPE& value, const char* name)
{
enum { IsEnum = AZStd::is_enum<TYPE>::value };
enum { IsTypeSafeIntegral = AZStd::is_type_safe_integral<TYPE>::value };
return SerializeHelper<IsEnum, IsTypeSafeIntegral>::Serialize(*this, value, name);
}
// SerializeHelper for objects and structures
template <>
struct ISerializer::SerializeHelper<false, false>
{
template <typename TYPE>
static bool Serialize(ISerializer& serializer, TYPE& value, const char* name)
{
if (serializer.BeginObject(name, "Type name unknown"))
{
if (SerializeObjectHelper<TYPE>::SerializeObject(serializer, value))
{
return serializer.EndObject(name, "Type name unknown");
}
}
return false;
}
};
template <>
struct ISerializer::SerializeHelper<true, false>
{
template <typename TYPE>
static bool Serialize(ISerializer& serializer, TYPE& value, const char* name)
{
using SizeType = typename AZStd::underlying_type<TYPE>::type;
SizeType& integralValue = reinterpret_cast<SizeType&>(value);
if (!serializer.Serialize(integralValue, name))
{
return false;
}
//auto enumMembers = AzEnumTraits<TYPE>::Members;
//if (AZStd::find(enumMembers.begin(), enumMembers.end(), static_cast<Type>(integralValue)) == enumMembers.end())
//{
// return false;
//}
return true;
}
};
template <>
struct ISerializer::SerializeHelper<true, true>
{
template <typename TYPE>
static bool Serialize(ISerializer& serializer, TYPE& value, const char* name)
{
using RawType = typename AZStd::underlying_type<TYPE>::type;
RawType& rawValue = reinterpret_cast<RawType&>(value);
if (!serializer.Serialize(rawValue, name))
{
return false;
}
return true;
}
};
}
#include <AzNetworking/Serialization/AzContainerSerializers.h>
@@ -0,0 +1,194 @@
/*
* 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/Serialization/NetworkInputSerializer.h>
#include <AzNetworking/AzNetworking_Traits_Platform.h>
#include <AzNetworking/Utilities/Endian.h>
#include <AzNetworking/Utilities/NetworkIncludes.h>
#include <memory>
namespace AzNetworking
{
NetworkInputSerializer::NetworkInputSerializer(uint8_t* buffer, uint32_t bufferCapacity)
: m_bufferSize(0)
, m_bufferCapacity(bufferCapacity)
, m_buffer(buffer)
{
;
}
SerializerMode NetworkInputSerializer::GetSerializerMode() const
{
return SerializerMode::ReadFromObject;
}
bool NetworkInputSerializer::Serialize(bool& value, [[maybe_unused]] const char* name)
{
uint8_t serializeValue = (value) ? 1 : 0;
return SerializeBytes((const uint8_t*)&serializeValue, sizeof(uint8_t));
}
bool NetworkInputSerializer::Serialize(char& value, [[maybe_unused]] const char* name, char minValue, char maxValue)
{
return SerializeBoundedValue<char>(minValue, maxValue, value);
}
bool NetworkInputSerializer::Serialize(int8_t& value, [[maybe_unused]] const char* name, int8_t minValue, int8_t maxValue)
{
return SerializeBoundedValue<int8_t>(minValue, maxValue, value);
}
bool NetworkInputSerializer::Serialize(int16_t& value, [[maybe_unused]] const char* name, int16_t minValue, int16_t maxValue)
{
return SerializeBoundedValue<int16_t>(minValue, maxValue, value);
}
bool NetworkInputSerializer::Serialize(int32_t& value, [[maybe_unused]] const char* name, int32_t minValue, int32_t maxValue)
{
return SerializeBoundedValue<int32_t>(minValue, maxValue, value);
}
bool NetworkInputSerializer::Serialize(int64_t& value, [[maybe_unused]] const char* name, int64_t minValue, int64_t maxValue)
{
return SerializeBoundedValue<int64_t>(minValue, maxValue, value);
}
bool NetworkInputSerializer::Serialize(uint8_t& value, [[maybe_unused]] const char* name, uint8_t minValue, uint8_t maxValue)
{
return SerializeBoundedValue<uint8_t>(minValue, maxValue, value);
}
bool NetworkInputSerializer::Serialize(uint16_t& value, [[maybe_unused]] const char* name, uint16_t minValue, uint16_t maxValue)
{
return SerializeBoundedValue<uint16_t>(minValue, maxValue, value);
}
bool NetworkInputSerializer::Serialize(uint32_t& value, [[maybe_unused]] const char* name, uint32_t minValue, uint32_t maxValue)
{
return SerializeBoundedValue<uint32_t>(minValue, maxValue, value);
}
bool NetworkInputSerializer::Serialize(uint64_t& value, [[maybe_unused]] const char* name, uint64_t minValue, uint64_t maxValue)
{
return SerializeBoundedValue<uint64_t>(minValue, maxValue, value);
}
bool NetworkInputSerializer::Serialize(float& value, [[maybe_unused]] const char* name, [[maybe_unused]] float minValue, [[maybe_unused]] float maxValue)
{
uint32_t hostOrder = *reinterpret_cast<uint32_t*>(&value);
uint32_t networkOrder = ntohl(hostOrder);
return SerializeBytes((const uint8_t*)&networkOrder, sizeof(float));
}
bool NetworkInputSerializer::Serialize(double& value, [[maybe_unused]] const char* name, [[maybe_unused]] double minValue, [[maybe_unused]] double maxValue)
{
uint64_t hostOrder = *reinterpret_cast<uint64_t*>(&value);
uint64_t networkOrder = ntohll(hostOrder);
return SerializeBytes((const uint8_t*)&networkOrder, sizeof(double));
}
bool NetworkInputSerializer::SerializeBytes(uint8_t* buffer, uint32_t bufferCapacity, [[maybe_unused]] bool isString, uint32_t& outSize, [[maybe_unused]] const char* name)
{
return SerializeBoundedValue<uint32_t>(0, bufferCapacity, outSize) && SerializeBytes(reinterpret_cast<uint8_t*>(buffer), outSize);
}
bool NetworkInputSerializer::BeginObject([[maybe_unused]] const char* name, [[maybe_unused]] const char* typeName)
{
return true;
}
bool NetworkInputSerializer::EndObject([[maybe_unused]] const char* name, [[maybe_unused]] const char* typeName)
{
return true;
}
const uint8_t* NetworkInputSerializer::GetBuffer() const
{
return m_buffer;
}
uint32_t NetworkInputSerializer::GetCapacity() const
{
return m_bufferCapacity;
}
uint32_t NetworkInputSerializer::GetSize() const
{
return m_bufferSize;
}
template <typename ORIGINAL_TYPE>
bool NetworkInputSerializer::SerializeBoundedValue(ORIGINAL_TYPE minValue, ORIGINAL_TYPE maxValue, ORIGINAL_TYPE inputValue)
{
m_serializerValid &= (inputValue >= minValue);
m_serializerValid &= (inputValue <= maxValue);
const uint64_t valueRange = static_cast<uint64_t>(maxValue - minValue);
if (valueRange <= AZStd::numeric_limits<uint8_t>::max())
{
return SerializeBoundedValueHelper<uint8_t>(static_cast<uint8_t>(inputValue - minValue));
}
else if (valueRange <= AZStd::numeric_limits<uint16_t>::max())
{
return SerializeBoundedValueHelper<uint16_t>(static_cast<uint16_t>(inputValue - minValue));
}
else if (valueRange <= AZStd::numeric_limits<uint32_t>::max())
{
return SerializeBoundedValueHelper<uint32_t>(static_cast<uint32_t>(inputValue - minValue));
}
return SerializeBoundedValueHelper<uint64_t>(static_cast<uint64_t>(inputValue - minValue));
}
inline uint8_t HostToNetwork(uint8_t value)
{
return value;
}
inline uint16_t HostToNetwork(uint16_t value)
{
return htons(value);
}
inline uint32_t HostToNetwork(uint32_t value)
{
return htonl(value);
}
inline uint64_t HostToNetwork(uint64_t value)
{
return htonll(value);
}
template <typename SERIALIZE_TYPE>
bool NetworkInputSerializer::SerializeBoundedValueHelper(SERIALIZE_TYPE serializeValue)
{
const SERIALIZE_TYPE networkOrder = HostToNetwork(serializeValue);
return m_serializerValid && SerializeBytes((const uint8_t*)&networkOrder, sizeof(SERIALIZE_TYPE));
}
bool NetworkInputSerializer::SerializeBytes(const uint8_t* data, uint32_t count)
{
const uint32_t currSize = m_bufferSize;
const uint32_t nextSize = m_bufferSize + count;
if (!m_serializerValid || (nextSize > m_bufferCapacity))
{
// Keep the failed boolean so we can verify serialization success
m_serializerValid = false;
return false;
}
uint8_t* writeBuffer = (uint8_t*)(m_buffer + currSize);
memcpy(writeBuffer, data, count);
m_bufferSize += count;
return true;
}
}
@@ -0,0 +1,81 @@
/*
* 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/Serialization/ISerializer.h>
namespace AzNetworking
{
//! @class NetworkInputSerializer
//! @brief Input serializer for writing an object model into a bytestream.
class NetworkInputSerializer final
: public ISerializer
{
public:
//! Constructor.
//! @param buffer input buffer to write to
//! @param bufferCapacity capacity of the buffer in bytes
NetworkInputSerializer(uint8_t* buffer, uint32_t bufferCapacity);
//! Copies the provided bytes into the serialization output buffer.
//! @param data pointer to the data buffer to copy
//! @param dataSize size of the data in bytes
//! @return boolean true on success, false if there was insufficient space to store all the data
bool CopyToBuffer(const uint8_t* data, uint32_t dataSize);
// ISerializer interfaces
SerializerMode GetSerializerMode() const override;
bool Serialize( bool& value, const char* name) override;
bool Serialize( char& value, const char* name, char minValue, char maxValue) override;
bool Serialize( int8_t& value, const char* name, int8_t minValue, int8_t maxValue) override;
bool Serialize( int16_t& value, const char* name, int16_t minValue, int16_t maxValue) override;
bool Serialize( int32_t& value, const char* name, int32_t minValue, int32_t maxValue) override;
bool Serialize( int64_t& value, const char* name, int64_t minValue, int64_t maxValue) override;
bool Serialize( uint8_t& value, const char* name, uint8_t minValue, uint8_t maxValue) override;
bool Serialize(uint16_t& value, const char* name, uint16_t minValue, uint16_t maxValue) override;
bool Serialize(uint32_t& value, const char* name, uint32_t minValue, uint32_t maxValue) override;
bool Serialize(uint64_t& value, const char* name, uint64_t minValue, uint64_t maxValue) override;
bool Serialize( float& value, const char* name, float minValue, float maxValue) override;
bool Serialize( double& value, const char* name, double minValue, double maxValue) override;
bool SerializeBytes(uint8_t* buffer, uint32_t bufferCapacity, bool isString, uint32_t& outSize, const char* name) override;
bool BeginObject(const char *name, const char* typeName) override;
bool EndObject(const char *name, const char* typeName) override;
const uint8_t* GetBuffer() const override;
uint32_t GetCapacity() const override;
uint32_t GetSize() const override;
void ClearTrackedChangesFlag() override {}
bool GetTrackedChangesFlag() const override { return false; }
// ISerializer interfaces
private:
//! Private copy operator, do not allow copying instances
NetworkInputSerializer& operator=(const NetworkInputSerializer&) = delete;
template <typename ORIGINAL_TYPE>
bool SerializeBoundedValue(ORIGINAL_TYPE minValue, ORIGINAL_TYPE maxValue, ORIGINAL_TYPE inputValue);
template <typename SERIALIZE_TYPE>
bool SerializeBoundedValueHelper(SERIALIZE_TYPE serializeValue);
bool SerializeBytes(const uint8_t* data, uint32_t count);
uint32_t m_bufferSize = 0;
const uint32_t m_bufferCapacity;
const uint8_t* m_buffer;
};
}
#include <AzNetworking/Serialization/NetworkInputSerializer.inl>
@@ -0,0 +1,21 @@
/*
* 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 bool NetworkInputSerializer::CopyToBuffer(const uint8_t* data, uint32_t dataSize)
{
return SerializeBytes(data, dataSize);
}
}
@@ -0,0 +1,204 @@
/*
* 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/Serialization/NetworkOutputSerializer.h>
#include <AzNetworking/AzNetworking_Traits_Platform.h>
#include <AzNetworking/Utilities/Endian.h>
#include <AzNetworking/Utilities/NetworkIncludes.h>
namespace AzNetworking
{
NetworkOutputSerializer::NetworkOutputSerializer(const uint8_t* buffer, uint32_t bufferCapacity)
: m_bufferPosition(0)
, m_bufferCapacity(bufferCapacity)
, m_buffer(buffer)
{
;
}
SerializerMode NetworkOutputSerializer::GetSerializerMode() const
{
return SerializerMode::WriteToObject;
}
bool NetworkOutputSerializer::Serialize(bool& value, [[maybe_unused]] const char* name)
{
uint8_t byteValue = 0;
SerializeBytes((uint8_t*)&byteValue, sizeof(byteValue));
value = (byteValue > 0);
return m_serializerValid;
}
bool NetworkOutputSerializer::Serialize(char& value, [[maybe_unused]] const char* name, char minValue, char maxValue)
{
return SerializeBoundedValue<char>(minValue, maxValue, value);
}
bool NetworkOutputSerializer::Serialize(int8_t& value, [[maybe_unused]] const char* name, int8_t minValue, int8_t maxValue)
{
return SerializeBoundedValue<int8_t>(minValue, maxValue, value);
}
bool NetworkOutputSerializer::Serialize(int16_t& value, [[maybe_unused]] const char* name, int16_t minValue, int16_t maxValue)
{
return SerializeBoundedValue<int16_t>(minValue, maxValue, value);
}
bool NetworkOutputSerializer::Serialize(int32_t& value, [[maybe_unused]] const char* name, int32_t minValue, int32_t maxValue)
{
return SerializeBoundedValue<int32_t>(minValue, maxValue, value);
}
bool NetworkOutputSerializer::Serialize(int64_t& value, [[maybe_unused]] const char* name, int64_t minValue, int64_t maxValue)
{
return SerializeBoundedValue<int64_t>(minValue, maxValue, value);
}
bool NetworkOutputSerializer::Serialize(uint8_t& value, [[maybe_unused]] const char* name, uint8_t minValue, uint8_t maxValue)
{
return SerializeBoundedValue<uint8_t>(minValue, maxValue, value);
}
bool NetworkOutputSerializer::Serialize(uint16_t& value, [[maybe_unused]] const char* name, uint16_t minValue, uint16_t maxValue)
{
return SerializeBoundedValue<uint16_t>(minValue, maxValue, value);
}
bool NetworkOutputSerializer::Serialize(uint32_t& value, [[maybe_unused]] const char* name, uint32_t minValue, uint32_t maxValue)
{
return SerializeBoundedValue<uint32_t>(minValue, maxValue, value);
}
bool NetworkOutputSerializer::Serialize(uint64_t& value, [[maybe_unused]] const char* name, uint64_t minValue, uint64_t maxValue)
{
return SerializeBoundedValue<uint64_t>(minValue, maxValue, value);
}
bool NetworkOutputSerializer::Serialize(float& value, [[maybe_unused]] const char* name, [[maybe_unused]] float minValue, [[maybe_unused]] float maxValue)
{
uint32_t networkOrder = 0;
m_serializerValid &= SerializeBytes((uint8_t*)&networkOrder, sizeof(float));
networkOrder = ntohl(networkOrder);
value = m_serializerValid ? *reinterpret_cast<float*>(&networkOrder) : value;
return m_serializerValid;
}
bool NetworkOutputSerializer::Serialize(double& value, [[maybe_unused]] const char* name, [[maybe_unused]] double minValue, [[maybe_unused]] double maxValue)
{
uint64_t networkOrder = 0;
m_serializerValid &= SerializeBytes((uint8_t *)&networkOrder, sizeof(double));
networkOrder = ntohll(networkOrder);
value = m_serializerValid ? *reinterpret_cast<double*>(&networkOrder) : value;
return m_serializerValid;
}
bool NetworkOutputSerializer::SerializeBytes(uint8_t* buffer, uint32_t bufferCapacity, [[maybe_unused]] bool isString, uint32_t& outSize, [[maybe_unused]] const char* name)
{
return SerializeBoundedValue<uint32_t>(0, bufferCapacity, outSize) && SerializeBytes(reinterpret_cast<uint8_t*>(buffer), outSize);
}
bool NetworkOutputSerializer::BeginObject([[maybe_unused]] const char* name, [[maybe_unused]] const char* typeName)
{
return true;
}
bool NetworkOutputSerializer::EndObject([[maybe_unused]] const char* name, [[maybe_unused]] const char* typeName)
{
return true;
}
const uint8_t* NetworkOutputSerializer::GetBuffer() const
{
return m_buffer;
}
uint32_t NetworkOutputSerializer::GetCapacity() const
{
return m_bufferCapacity;
}
uint32_t NetworkOutputSerializer::GetSize() const
{
return m_bufferPosition;
}
template <typename ORIGINAL_TYPE>
bool NetworkOutputSerializer::SerializeBoundedValue(ORIGINAL_TYPE minValue, ORIGINAL_TYPE maxValue, ORIGINAL_TYPE& outValue)
{
const uint64_t valueRange = static_cast<uint64_t>(maxValue - minValue);
if (valueRange <= AZStd::numeric_limits<uint8_t>::max())
{
outValue = static_cast<ORIGINAL_TYPE>(SerializeBoundedValueHelper<uint8_t>(static_cast<uint8_t>(maxValue - minValue))) + minValue;
}
else if (valueRange <= AZStd::numeric_limits<uint16_t>::max())
{
outValue = static_cast<ORIGINAL_TYPE>(SerializeBoundedValueHelper<uint16_t>(static_cast<uint16_t>(maxValue - minValue))) + minValue;
}
else if (valueRange <= AZStd::numeric_limits<uint32_t>::max())
{
outValue = static_cast<ORIGINAL_TYPE>(SerializeBoundedValueHelper<uint32_t>(static_cast<uint32_t>(maxValue - minValue))) + minValue;
}
else
{
outValue = static_cast<ORIGINAL_TYPE>(SerializeBoundedValueHelper<uint64_t>(static_cast<uint64_t>(maxValue - minValue))) + minValue;
}
return m_serializerValid;
}
inline uint8_t NetworkToHost(uint8_t value)
{
return value;
}
inline uint16_t NetworkToHost(uint16_t value)
{
return ntohs(value);
}
inline uint32_t NetworkToHost(uint32_t value)
{
return ntohl(value);
}
inline uint64_t NetworkToHost(uint64_t value)
{
return ntohll(value);
}
template <typename SERIALIZE_TYPE>
SERIALIZE_TYPE NetworkOutputSerializer::SerializeBoundedValueHelper(SERIALIZE_TYPE maxValue)
{
SERIALIZE_TYPE result = 0;
m_serializerValid &= SerializeBytes((uint8_t*)&result, sizeof(SERIALIZE_TYPE));
result = m_serializerValid ? NetworkToHost(result) : result;
m_serializerValid &= (result <= maxValue);
return result;
}
bool NetworkOutputSerializer::SerializeBytes(uint8_t* data, uint32_t count)
{
const uint32_t currSize = m_bufferPosition;
const uint32_t nextSize = m_bufferPosition + count;
if (!m_serializerValid || (nextSize > m_bufferCapacity))
{
// Keep the failed boolean so we can verify serialization success
m_serializerValid = false;
return false;
}
const uint8_t* readBuffer = (const uint8_t*)(m_buffer + currSize);
memcpy(data, readBuffer, count);
m_bufferPosition += count;
return true;
}
}
@@ -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.
*
*/
#pragma once
#include <AzNetworking/Serialization/ISerializer.h>
namespace AzNetworking
{
//! @class NetworkOutputSerializer
//! @brief Output serializer for inflating and writing out a bytestream into an object model.
class NetworkOutputSerializer
: public ISerializer
{
public:
//! Constructor.
//! @param buffer output buffer to read from
//! @param bufferCapacity capacity of the buffer in bytes
NetworkOutputSerializer(const uint8_t* buffer, uint32_t bufferCapacity);
//! Returns the unread portion of the data stream.
//! @return the unread portion of the data stream
const uint8_t* GetUnreadData() const;
//! Returns the number of bytes not yet consumed from the serialization buffer.
//! @return number of bytes not yet consumed from the serialization buffer
uint32_t GetUnreadSize() const;
//! Returns the number of bytes consumed by serialization.
//! @return number of bytes consumed by serialization
uint32_t GetReadSize() const;
// ISerializer interfaces
SerializerMode GetSerializerMode() const override;
bool Serialize( bool& value, const char* name) override;
bool Serialize( char& value, const char* name, char minValue, char maxValue) override;
bool Serialize( int8_t& value, const char* name, int8_t minValue, int8_t maxValue) override;
bool Serialize( int16_t& value, const char* name, int16_t minValue, int16_t maxValue) override;
bool Serialize( int32_t& value, const char* name, int32_t minValue, int32_t maxValue) override;
bool Serialize( int64_t& value, const char* name, int64_t minValue, int64_t maxValue) override;
bool Serialize( uint8_t& value, const char* name, uint8_t minValue, uint8_t maxValue) override;
bool Serialize(uint16_t& value, const char* name, uint16_t minValue, uint16_t maxValue) override;
bool Serialize(uint32_t& value, const char* name, uint32_t minValue, uint32_t maxValue) override;
bool Serialize(uint64_t& value, const char* name, uint64_t minValue, uint64_t maxValue) override;
bool Serialize( float& value, const char* name, float minValue, float maxValue) override;
bool Serialize( double& value, const char* name, double minValue, double maxValue) override;
bool SerializeBytes(uint8_t* buffer, uint32_t bufferCapacity, bool isString, uint32_t& outSize, const char* name) override;
bool BeginObject(const char *name, const char* typeName) override;
bool EndObject(const char *name, const char* typeName) override;
const uint8_t* GetBuffer() const override;
uint32_t GetCapacity() const override;
uint32_t GetSize() const override;
void ClearTrackedChangesFlag() override {}
bool GetTrackedChangesFlag() const override { return false; }
// ISerializer interfaces
private:
//! Private copy operator, do not allow copying instances.
NetworkOutputSerializer& operator=(const NetworkOutputSerializer&) = delete;
template <typename ORIGINAL_TYPE>
bool SerializeBoundedValue(ORIGINAL_TYPE minValue, ORIGINAL_TYPE maxValue, ORIGINAL_TYPE& outValue);
template <typename SERIALIZE_TYPE>
SERIALIZE_TYPE SerializeBoundedValueHelper(SERIALIZE_TYPE maxValue);
bool SerializeBytes(uint8_t* data, uint32_t count);
uint32_t m_bufferPosition = 0;
const uint32_t m_bufferCapacity;
const uint8_t* m_buffer;
};
}
#include <AzNetworking/Serialization/NetworkOutputSerializer.inl>
@@ -0,0 +1,31 @@
/*
* 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 const uint8_t* NetworkOutputSerializer::GetUnreadData() const
{
return (const uint8_t*)(m_buffer + m_bufferPosition);
}
inline uint32_t NetworkOutputSerializer::GetUnreadSize() const
{
return (m_bufferCapacity - m_bufferPosition);
}
inline uint32_t NetworkOutputSerializer::GetReadSize() const
{
return m_bufferPosition;
}
}
@@ -0,0 +1,67 @@
/*
* 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/DataStructures/ByteBuffer.h>
#include <AzNetworking/Serialization/ISerializer.h>
namespace AzNetworking
{
//! @class TrackChangedSerializer
//! @brief Output serializer that tracks if it actually writes changes to memory or not.
template <typename BASE_TYPE>
class TrackChangedSerializer final
: public BASE_TYPE
{
public:
//! Constructor.
//! @param buffer output buffer to read from
//! @param bufferCapacity capacity of the buffer in bytes
TrackChangedSerializer(const uint8_t* buffer, uint32_t bufferCapacity);
// ISerializer interfaces
SerializerMode GetSerializerMode() const override;
bool Serialize( bool& value, const char* name) override;
bool Serialize( char& value, const char* name, char minValue, char maxValue) override;
bool Serialize( int8_t& value, const char* name, int8_t minValue, int8_t maxValue) override;
bool Serialize( int16_t& value, const char* name, int16_t minValue, int16_t maxValue) override;
bool Serialize( int32_t& value, const char* name, int32_t minValue, int32_t maxValue) override;
bool Serialize( int64_t& value, const char* name, int64_t minValue, int64_t maxValue) override;
bool Serialize( uint8_t& value, const char* name, uint8_t minValue, uint8_t maxValue) override;
bool Serialize(uint16_t& value, const char* name, uint16_t minValue, uint16_t maxValue) override;
bool Serialize(uint32_t& value, const char* name, uint32_t minValue, uint32_t maxValue) override;
bool Serialize(uint64_t& value, const char* name, uint64_t minValue, uint64_t maxValue) override;
bool Serialize( float& value, const char* name, float minValue, float maxValue) override;
bool Serialize( double& value, const char* name, double minValue, double maxValue) override;
bool SerializeBytes(uint8_t* buffer, uint32_t bufferCapacity, bool isString, uint32_t& outSize, const char* name) override;
bool BeginObject(const char *name, const char* typeName) override;
bool EndObject(const char *name, const char* typeName) override;
const uint8_t* GetBuffer() const override;
uint32_t GetCapacity() const override;
uint32_t GetSize() const override;
void ClearTrackedChangesFlag() override;
bool GetTrackedChangesFlag() const override;
// ISerializer interfaces
private:
//! Private copy operator, do not allow copying instances
TrackChangedSerializer& operator=(const TrackChangedSerializer&) = delete;
bool m_hasChanged;
};
}
#include <AzNetworking/Serialization/TrackChangedSerializer.inl>
@@ -0,0 +1,193 @@
/*
* 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
{
template <typename BASE_TYPE>
TrackChangedSerializer<BASE_TYPE>::TrackChangedSerializer(const uint8_t* buffer, uint32_t bufferCapacity)
: BASE_TYPE(buffer, bufferCapacity)
, m_hasChanged(false)
{
;
}
template <typename BASE_TYPE>
SerializerMode TrackChangedSerializer<BASE_TYPE>::GetSerializerMode() const
{
return BASE_TYPE::GetSerializerMode();
}
template <typename BASE_TYPE>
bool TrackChangedSerializer<BASE_TYPE>::Serialize(bool& value, const char* name)
{
const bool cached = value;
const bool result = BASE_TYPE::Serialize(value, name);
m_hasChanged |= (cached != value);
return result;
}
template <typename BASE_TYPE>
bool TrackChangedSerializer<BASE_TYPE>::Serialize(char& value, const char* name, char minValue, char maxValue)
{
const char cached = value;
const bool result = BASE_TYPE::Serialize(value, name, minValue, maxValue);
m_hasChanged |= (cached != value);
return result;
}
template <typename BASE_TYPE>
bool TrackChangedSerializer<BASE_TYPE>::Serialize(int8_t& value, const char* name, int8_t minValue, int8_t maxValue)
{
const int8_t cached = value;
const bool result = BASE_TYPE::Serialize(value, name, minValue, maxValue);
m_hasChanged |= (cached != value);
return result;
}
template <typename BASE_TYPE>
bool TrackChangedSerializer<BASE_TYPE>::Serialize(int16_t& value, const char* name, int16_t minValue, int16_t maxValue)
{
const int16_t cached = value;
const bool result = BASE_TYPE::Serialize(value, name, minValue, maxValue);
m_hasChanged |= (cached != value);
return result;
}
template <typename BASE_TYPE>
bool TrackChangedSerializer<BASE_TYPE>::Serialize(int32_t& value, const char* name, int32_t minValue, int32_t maxValue)
{
const int32_t cached = value;
const bool result = BASE_TYPE::Serialize(value, name, minValue, maxValue);
m_hasChanged |= (cached != value);
return result;
}
template <typename BASE_TYPE>
bool TrackChangedSerializer<BASE_TYPE>::Serialize(int64_t& value, const char* name, int64_t minValue, int64_t maxValue)
{
const int64_t cached = value;
const bool result = BASE_TYPE::Serialize(value, name, minValue, maxValue);
m_hasChanged |= (cached != value);
return result;
}
template <typename BASE_TYPE>
bool TrackChangedSerializer<BASE_TYPE>::Serialize(uint8_t& value, const char* name, uint8_t minValue, uint8_t maxValue)
{
const uint8_t cached = value;
const bool result = BASE_TYPE::Serialize(value, name, minValue, maxValue);
m_hasChanged |= (cached != value);
return result;
}
template <typename BASE_TYPE>
bool TrackChangedSerializer<BASE_TYPE>::Serialize(uint16_t& value, const char* name, uint16_t minValue, uint16_t maxValue)
{
const uint16_t cached = value;
const bool result = BASE_TYPE::Serialize(value, name, minValue, maxValue);
m_hasChanged |= (cached != value);
return result;
}
template <typename BASE_TYPE>
bool TrackChangedSerializer<BASE_TYPE>::Serialize(uint32_t& value, const char* name, uint32_t minValue, uint32_t maxValue)
{
const uint32_t cached = value;
const bool result = BASE_TYPE::Serialize(value, name, minValue, maxValue);
m_hasChanged |= (cached != value);
return result;
}
template <typename BASE_TYPE>
bool TrackChangedSerializer<BASE_TYPE>::Serialize(uint64_t& value, const char* name, uint64_t minValue, uint64_t maxValue)
{
const uint64_t cached = value;
const bool result = BASE_TYPE::Serialize(value, name, minValue, maxValue);
m_hasChanged |= (cached != value);
return result;
}
template <typename BASE_TYPE>
bool TrackChangedSerializer<BASE_TYPE>::Serialize(float& value, const char* name, float minValue, float maxValue)
{
const float cached = value;
const bool result = BASE_TYPE::Serialize(value, name, minValue, maxValue);
m_hasChanged |= (cached != value);
return result;
}
template <typename BASE_TYPE>
bool TrackChangedSerializer<BASE_TYPE>::Serialize(double& value, const char* name, double minValue, double maxValue)
{
const double cached = value;
const bool result = BASE_TYPE::Serialize(value, name, minValue, maxValue);
m_hasChanged |= (cached != value);
return result;
}
template <typename BASE_TYPE>
bool TrackChangedSerializer<BASE_TYPE>::SerializeBytes(uint8_t* buffer, uint32_t bufferCapacity, bool isString, uint32_t& outSize, const char* name)
{
ByteBuffer<16384> cached;
if (!cached.CopyValues(buffer, outSize))
{
return false;
}
const bool result = BASE_TYPE::SerializeBytes(buffer, bufferCapacity, isString, outSize, name);
m_hasChanged |= (cached.IsSame(buffer, outSize));
return result;
}
template <typename BASE_TYPE>
bool TrackChangedSerializer<BASE_TYPE>::BeginObject(const char* name, const char* typeName)
{
return BASE_TYPE::BeginObject(name, typeName);
}
template <typename BASE_TYPE>
bool TrackChangedSerializer<BASE_TYPE>::EndObject(const char* name, const char* typeName)
{
return BASE_TYPE::EndObject(name, typeName);
}
template <typename BASE_TYPE>
const uint8_t* TrackChangedSerializer<BASE_TYPE>::GetBuffer() const
{
return BASE_TYPE::GetBuffer();
}
template <typename BASE_TYPE>
uint32_t TrackChangedSerializer<BASE_TYPE>::GetCapacity() const
{
return BASE_TYPE::GetCapacity();
}
template <typename BASE_TYPE>
uint32_t TrackChangedSerializer<BASE_TYPE>::GetSize() const
{
return BASE_TYPE::GetSize();
}
template <typename BASE_TYPE>
void TrackChangedSerializer<BASE_TYPE>::ClearTrackedChangesFlag()
{
m_hasChanged = false;
}
template <typename BASE_TYPE>
bool TrackChangedSerializer<BASE_TYPE>::GetTrackedChangesFlag() const
{
return m_hasChanged;
}
}
@@ -0,0 +1,412 @@
/*
* 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/TcpTransport/TcpConnection.h>
#include <AzNetworking/TcpTransport/TcpPacketHeader.h>
#include <AzNetworking/TcpTransport/TcpNetworkInterface.h>
#include <AzNetworking/Serialization/NetworkInputSerializer.h>
#include <AzNetworking/Serialization/NetworkOutputSerializer.h>
#include <AzNetworking/ConnectionLayer/IConnectionSet.h>
#include <AzNetworking/Framework/ICompressor.h>
#include <AzNetworking/Utilities/CompressionCommon.h>
#include <AzNetworking/AutoGen/CorePackets.AutoPackets.h>
#include <AzCore/Console/IConsole.h>
#include <AzCore/Console/ILogger.h>
namespace AzNetworking
{
AZ_CVAR(AZ::CVarFixedString, net_TcpCompressor, "MultiplayerCompressor", nullptr, AZ::ConsoleFunctorFlags::DontReplicate, "TCP compressor to use."); // WARN: similar to encryption this needs to be set once and only once before creating the network interface
TcpConnection::TcpConnection
(
ConnectionId connectionId,
const IpAddress& remoteAddress,
TcpNetworkInterface& networkInterface,
TcpSocket& socket,
TimeoutId timeoutId
)
: IConnection(connectionId, remoteAddress)
, m_networkInterface(networkInterface)
, m_socket(socket.CloneAndTakeOwnership())
, m_timeoutId(timeoutId)
, m_state(m_socket->IsOpen() ? ConnectionState::Connecting : ConnectionState::Disconnected)
, m_connectionRole(ConnectionRole::Acceptor)
, m_registeredSocketFd(InvalidSocketFd)
{
;
}
TcpConnection::TcpConnection
(
ConnectionId connectionId,
const IpAddress& remoteAddress,
TcpNetworkInterface& networkInterface,
TrustZone trustZone,
bool useEncryption
)
: IConnection(connectionId, remoteAddress)
, m_networkInterface(networkInterface)
, m_socket(nullptr)
, m_state(ConnectionState::Disconnected)
, m_connectionRole(ConnectionRole::Connector)
, m_registeredSocketFd(InvalidSocketFd)
{
const AZ::CVarFixedString compressor = static_cast<AZ::CVarFixedString>(net_TcpCompressor);
const char* compressorName = compressor.c_str();
m_compressor = CreateCompressor(compressorName);
if (useEncryption)
{
m_socket = AZStd::make_unique<TlsSocket>(trustZone);
}
else
{
m_socket = AZStd::make_unique<TcpSocket>();
}
}
TcpConnection::~TcpConnection()
{
if (m_state == ConnectionState::Connected)
{
m_networkInterface.GetConnectionListener().OnDisconnect(this, DisconnectReason::ConnectionDeleted, TerminationEndpoint::Local);
}
}
bool TcpConnection::Connect()
{
Disconnect(DisconnectReason::TerminatedByClient, TerminationEndpoint::Local);
if (!m_socket->Connect(GetRemoteAddress()))
{
m_networkInterface.GetConnectionListener().OnDisconnect(this, DisconnectReason::ConnectionRejected, TerminationEndpoint::Local);
return false;
}
m_state = ConnectionState::Connecting;
SendReliablePacket(CorePackets::InitiateConnectionPacket());
return true;
}
void TcpConnection::UpdateSend()
{
const uint32_t numSendBytes = m_sendRingbuffer.GetReadBufferSize();
if (numSendBytes <= 0)
{
return;
}
uint8_t* sendData = m_sendRingbuffer.GetReadBufferData();
const int32_t sentBytes = m_socket->Send(sendData, numSendBytes);
const DisconnectReason disconnectReason = GetDisconnectReasonForSocketResult(sentBytes);
if (disconnectReason != DisconnectReason::MAX)
{
Disconnect(disconnectReason, TerminationEndpoint::Remote);
return;
}
m_sendRingbuffer.AdvanceReadBuffer(sentBytes);
m_networkInterface.GetMetrics().m_sendBytes += numSendBytes;
m_networkInterface.GetMetrics().m_sendBytesUncompressed += numSendBytes;
}
bool TcpConnection::UpdateRecv()
{
const AZ::TimeMs startTimeMs = AZ::GetElapsedTimeMs();
GetMetrics().m_recvDatarate.LogPacket(0, startTimeMs);
// Read new data off the input socket
{
uint8_t* srcData = m_recvRingbuffer.ReserveBlockForWrite(MaxPacketSize);
if (srcData == nullptr)
{
AZLOG_ERROR("Receive ringbuffer full, dropped connection");
Disconnect(DisconnectReason::StreamError, TerminationEndpoint::Local);
return false;
}
const int32_t receivedBytes = m_socket->Receive(srcData, MaxPacketSize);
if (receivedBytes == 0)
{
// No data on the socket, can happen if we're not in select or epoll mode
return true;
}
const DisconnectReason disconnectReason = GetDisconnectReasonForSocketResult(receivedBytes);
if (disconnectReason != DisconnectReason::MAX)
{
Disconnect(disconnectReason, TerminationEndpoint::Remote);
return true;
}
m_recvRingbuffer.AdvanceWriteBuffer(receivedBytes);
m_networkInterface.GetMetrics().m_recvBytes += receivedBytes;
m_networkInterface.GetMetrics().m_recvBytesUncompressed += receivedBytes;
}
// Process received packets
for (;;)
{
TcpPacketHeader header(PacketType(0), 0);
TcpPacketEncodingBuffer buffer;
if (!ReceivePacketInternal(header, buffer, startTimeMs))
{
break;
}
TimeoutQueue::TimeoutItem* timeoutItem = m_networkInterface.m_connectionTimeoutQueue.RetrieveItem(GetTimeoutId());
if (timeoutItem == nullptr)
{
return true;
}
timeoutItem->UpdateTimeoutTime(startTimeMs);
NetworkOutputSerializer serializer(buffer.GetBuffer(), buffer.GetSize());
if (m_state == ConnectionState::Connecting)
{
const ConnectResult connectResult = m_networkInterface.GetConnectionListener().ValidateConnect(GetRemoteAddress(), header, serializer);
if (connectResult == ConnectResult::Rejected)
{
Disconnect(DisconnectReason::ConnectionRejected, TerminationEndpoint::Local);
}
else
{
m_state = ConnectionState::Connected;
}
}
if (m_state == ConnectionState::Connected)
{
m_networkInterface.GetConnectionListener().OnPacketReceived(this, header, serializer);
}
}
m_networkInterface.GetMetrics().m_recvTimeMs += AZ::GetElapsedTimeMs() - startTimeMs;
return true;
}
bool TcpConnection::SendReliablePacket(const IPacket& packet)
{
TcpPacketEncodingBuffer buffer;
{
NetworkInputSerializer serializer(buffer.GetBuffer(), buffer.GetCapacity());
if (!const_cast<IPacket&>(packet).Serialize(serializer))
{
return false;
}
buffer.Resize(serializer.GetSize());
}
const AZ::TimeMs currentTimeMs = AZ::GetElapsedTimeMs();
++m_lastSentPacketId;
return SendPacketInternal(packet.GetPacketType(), buffer, currentTimeMs);
}
PacketId TcpConnection::SendUnreliablePacket(const IPacket& packet)
{
if (SendReliablePacket(packet))
{
return m_lastSentPacketId;
}
return InvalidPacketId;
}
bool TcpConnection::WasPacketAcked(PacketId packetId) const
{
// Treat packetId as a sequence value to handle rollover
// Since this is Tcp, if the packet was sent we implicitly assume it was received
return !SequenceMoreRecent(packetId, m_lastSentPacketId);
}
ConnectionState TcpConnection::GetConnectionState() const
{
return m_state;
}
ConnectionRole TcpConnection::GetConnectionRole() const
{
return m_connectionRole;
}
bool TcpConnection::Disconnect(DisconnectReason reason, [[maybe_unused]] TerminationEndpoint endpoint)
{
if (m_state == ConnectionState::Disconnected)
{
return true;
}
m_networkInterface.GetConnectionListener().OnDisconnect(this, reason, endpoint);
m_networkInterface.RequestDisconnect(this, reason);
m_state = ConnectionState::Disconnected;
GetMetrics().Reset();
return true;
}
void TcpConnection::SetConnectionMtu([[maybe_unused]] uint32_t connectionMtu)
{
; // do nothing, unsupported on TCP connections
}
uint32_t TcpConnection::GetConnectionMtu() const
{
return 0; // do nothing, unsupported on TCP connections
}
void TcpConnection::SetConnectionQuality([[maybe_unused]] const ConnectionQuality& connectionQuality)
{
; // do nothing, unsupported on TCP connections
}
bool TcpConnection::SendPacketInternal(PacketType packetType, TcpPacketEncodingBuffer& payloadBuffer, AZ::TimeMs currentTimeMs)
{
AZ_Assert(payloadBuffer.GetCapacity() < AZStd::numeric_limits<uint16_t>::max(), "Buffer capacity should be representable using 2 bytes or less");
int32_t payloadSize = aznumeric_cast<int32_t>(payloadBuffer.GetSize());
bool shouldCompress = m_compressor && packetType != aznumeric_cast<PacketType>(CorePackets::PacketType::InitiateConnectionPacket);
// Create and serialize header...
TcpPacketEncodingBuffer headerBuffer;
{
TcpPacketHeader header(packetType, aznumeric_cast<uint16_t>(payloadBuffer.GetSize()));
header.SetPacketFlag(PacketFlag::Compressed, shouldCompress);
NetworkInputSerializer serializer(headerBuffer.GetBuffer(), headerBuffer.GetCapacity());
if (!header.Serialize(serializer))
{
return false;
}
headerBuffer.Resize(serializer.GetSize());
}
const uint16_t headerSize = aznumeric_cast<uint16_t>(headerBuffer.GetSize());
const uint8_t* srcData = reinterpret_cast<const uint8_t*>(payloadBuffer.GetBuffer());
uint8_t* dstData = reinterpret_cast<uint8_t*>(m_sendRingbuffer.ReserveBlockForWrite(headerSize + payloadSize));
if (dstData == nullptr)
{
AZLOG_ERROR("Send ringbuffer full, dropped packet");
return false;
}
// Compress send data
TcpPacketEncodingBuffer writeBuffer;
if (m_compressor && packetType != aznumeric_cast<PacketType>(CorePackets::PacketType::InitiateConnectionPacket))
{
const AZStd::size_t maxSizeNeeded = m_compressor->GetMaxCompressedBufferSize(payloadBuffer.GetSize());
AZStd::size_t compressionMemBytesUsed = 0;
CompressorError compErr = m_compressor->Compress(payloadBuffer.GetBuffer(), payloadBuffer.GetSize(), writeBuffer.GetBuffer(), maxSizeNeeded, compressionMemBytesUsed);
if (compErr != CompressorError::Ok)
{
AZLOG_ERROR("Failed to compress packet with error %d", aznumeric_cast<int32_t>(compErr));
return false;
}
if (compressionMemBytesUsed >= payloadSize)
{
// Track how many packets are being sent with no compression gain
m_networkInterface.GetMetrics().m_sendCompressedPacketsNoGain++;
}
// Track byte delta caused by compression
m_networkInterface.GetMetrics().m_sendBytesCompressedDelta += (payloadSize - compressionMemBytesUsed);
writeBuffer.Resize(aznumeric_cast<int32_t>(compressionMemBytesUsed));
payloadSize = writeBuffer.GetSize();
srcData = writeBuffer.GetBuffer();
}
// Copy the header data to the ring buffer
{
memcpy(dstData, headerBuffer.GetBuffer(), headerSize);
}
// Write payload...
{
memcpy(dstData + headerSize, srcData, payloadSize);
}
m_sendRingbuffer.AdvanceWriteBuffer(headerSize + payloadSize);
GetMetrics().m_packetsSent++;
GetMetrics().m_sendDatarate.LogPacket(headerSize + payloadSize, currentTimeMs);
m_networkInterface.GetMetrics().m_sendPackets++;
UpdateSend();
return true;
}
bool TcpConnection::ReceivePacketInternal(TcpPacketHeader& outHeader, TcpPacketEncodingBuffer& outBuffer, AZ::TimeMs currentTimeMs)
{
NetworkOutputSerializer serializer(m_recvRingbuffer.GetReadBufferData(), m_recvRingbuffer.GetReadBufferSize());
if (!outHeader.Serialize(serializer))
{
return false;
}
uint16_t packetSize = outHeader.GetPacketSize();
const uint32_t unreadSize = serializer.GetUnreadSize();
if (packetSize > unreadSize)
{
// We don't have all the data required for this packet yet
return false;
}
if (packetSize > outBuffer.GetCapacity())
{
// If we can't fit the packet, do not allow the copy to proceed as that would overwrite invalid memory
return false;
}
outBuffer.Resize(packetSize);
const uint8_t* srcData = serializer.GetUnreadData();
if (m_compressor && outHeader.IsPacketFlagSet(PacketFlag::Compressed))
{
if (!DecompressPacket(srcData, packetSize, outBuffer))
{
AZLOG_WARN("Failed to decompress packet!");
return false;
}
srcData = outBuffer.GetBuffer();
packetSize = aznumeric_cast<uint16_t>(outBuffer.GetSize());
}
uint8_t* dstData = outBuffer.GetBuffer();
memcpy(dstData, srcData, packetSize);
m_recvRingbuffer.AdvanceReadBuffer(serializer.GetReadSize() + packetSize);
GetMetrics().m_packetsRecv++;
GetMetrics().m_recvDatarate.LogPacket(packetSize, currentTimeMs);
m_networkInterface.GetMetrics().m_recvPackets++;
return true;
}
bool TcpConnection::DecompressPacket(const uint8_t* packetBuffer, AZStd::size_t packetSize, TcpPacketEncodingBuffer& packetBufferOut) const
{
if (!m_compressor) // should probably have some compression handshake than relying on existence of compressor
{
AZLOG_ERROR("Decompress called without a compressor.");
return false;
}
AZStd::size_t uncompSize = 0;
AZStd::size_t bytesConsumed = 0;
const CompressorError compErr = m_compressor->Decompress(packetBuffer, packetSize, packetBufferOut.GetBuffer(), packetBufferOut.GetCapacity(), bytesConsumed, uncompSize);
if (compErr != CompressorError::Ok)
{
AZLOG_ERROR("Decompress failed with error %d this will lead to data read errors!", compErr);
return false;
}
if (packetSize != bytesConsumed)
{
AZLOG_ERROR("Decompress must consume entire buffer [%zu != %zu]!", bytesConsumed, packetSize);
return false;
}
packetBufferOut.Resize(aznumeric_cast<uint32_t>(uncompSize)); // Decompress will fail if larger than buffer size, so this cast is safe
return true;
}
}
@@ -0,0 +1,164 @@
/*
* 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/DataStructures/ByteBuffer.h>
#include <AzNetworking/DataStructures/TimeoutQueue.h>
#include <AzNetworking/ConnectionLayer/IConnection.h>
#include <AzNetworking/ConnectionLayer/IConnectionListener.h>
#include <AzNetworking/ConnectionLayer/ConnectionMetrics.h>
#include <AzNetworking/TcpTransport/TcpSocket.h>
#include <AzNetworking/TcpTransport/TlsSocket.h>
#include <AzNetworking/TcpTransport/TcpRingBuffer.h>
#include <AzNetworking/TcpTransport/TcpPacketHeader.h>
namespace AzNetworking
{
class TcpNetworkInterface;
class ICompressor;
// 20 byte IPv4 header + 20 byte TCP header
static constexpr uint32_t TcpPacketHeaderSize = 20 + 20;
//! @class TcpConnection
//! @brief connection layer for TCP connection management.
class TcpConnection final
: public IConnection
{
public:
//! Construct with an existing socket, used when accepting an incoming connection
//! @param connectionId connection identifier of this connection instance
//! @param remoteAddress IP address of the remote endpoint
//! @param networkInterface TcpNetworkInterface that owns this connection instance
//! @param socket TCP socket to take ownership of and use for sending and receiving data
//! @param timeoutId timeout identifier of this connection instance
TcpConnection
(
ConnectionId connectionId,
const IpAddress& remoteAddress,
TcpNetworkInterface& networkInterface,
TcpSocket& socket,
TimeoutId timeoutId
);
//! Construct a new socket with optional encryption, used when initiating a new connection
//! @param connectionId connection identifier of this connection instance
//! @param remoteAddress IP address of the remote endpoint
//! @param networkInterface TcpNetworkInterface that owns this connection instance
//! @param trustZone for encrypted connections, the level of trust we associate with this connection (internal or external)
//! @param useEncryption if true connections will be made over TLS
TcpConnection
(
ConnectionId connectionId,
const IpAddress& remoteAddress,
TcpNetworkInterface& networkInterface,
TrustZone trustZone,
bool useEncryption
);
~TcpConnection() override;
//! Returns the TcpSocket bound to this TcpConnection.
//! @return the TcpSocket bound to this TcpConnection
TcpSocket* GetTcpSocket() const;
//! Sets the timeout identifier for this TcpConnection.
//! @param timeoutId the timeout identifier to use for this TcpConnection
void SetTimeoutId(TimeoutId timeoutId);
//! Returns the timeout identifier for this TcpConnection.
//! @return the timeout identifier for this TcpConnection
TimeoutId GetTimeoutId() const;
//! Returns true if this connection instance is in an open state, and is capable of actively sending and receiving packets.
//! @return boolean true if this connection instance is in an open state
bool IsOpen() const;
//! Connects to the provided remote address.
//! @return boolean true on success
bool Connect();
//! Handles any new outgoing network traffic.
void UpdateSend();
//! Handles any new incoming network traffic.
//! @return boolean true if the socket is still active, false if it has been remotely terminated
bool UpdateRecv();
//! IConnection interface.
// @{
bool SendReliablePacket(const IPacket& packet) override;
PacketId SendUnreliablePacket(const IPacket& packet) override;
bool WasPacketAcked(PacketId packetId) const override;
ConnectionState GetConnectionState() const override;
ConnectionRole GetConnectionRole() const override;
bool Disconnect(DisconnectReason reason, TerminationEndpoint endpoint) override;
void SetConnectionMtu(uint32_t connectionMtu) override;
uint32_t GetConnectionMtu() const override;
void SetConnectionQuality(const ConnectionQuality& connectionQuality) override;
// @}
//! Sets the registered socket file descriptor for this TcpConnection in the associated ConnectionSet instance.
//! @param registeredSocketFd the socket file descriptor for this TcpConnection in the associated ConnectionSet instance
void SetRegisteredSocketFd(SocketFd registeredSocketFd);
//! Returns the socket file descriptor for this TcpConnection in the associated ConnectionSet instance.
//! @return the socket file descriptor for this TcpConnection in the associated ConnectionSet instance
SocketFd GetRegisteredSocketFd() const;
private:
//! Transmits a packet to the connected connection.
//! @param packetType packet type of the buffer being transmitted
//! @param payloadBuffer packet buffer to transmit
//! @param currentTimeMs current process time in milliseconds
//! @return boolean true if the packet was transmitted (NOT AN INDICATION OF DELIVERY)
bool SendPacketInternal(PacketType packetType, TcpPacketEncodingBuffer& payloadBuffer, AZ::TimeMs currentTimeMs);
//! Receives a packet from the connected connection.
//! @param outHeader header of the received packet
//! @param outBuffer encoded buffer of the received packet
//! @param currentTimeMs current process time in milliseconds
//! @return boolean true if a packet has been received, false otherwise
bool ReceivePacketInternal(TcpPacketHeader& outHeader, TcpPacketEncodingBuffer& outBuffer, AZ::TimeMs currentTimeMs);
//! Decompresses an incoming packet data buffer.
//! @param packetBuffer the compressed packet buffer to decode
//! @param packetSize the size of the compressed packet buffer
//! @param packetBufferOut the decoded data
//! @return boolean true on success, false on failure
bool DecompressPacket(const uint8_t* packetBuffer, AZStd::size_t packetSize, TcpPacketEncodingBuffer& packetBufferOut) const;
//! Private copy operator, do not allow copying instances
TcpConnection& operator=(const TcpConnection&) = delete;
TcpNetworkInterface& m_networkInterface;
AZStd::unique_ptr<TcpSocket> m_socket;
AZStd::unique_ptr<ICompressor> m_compressor;
TimeoutId m_timeoutId;
PacketId m_lastSentPacketId = InvalidPacketId;
ConnectionState m_state = ConnectionState::Disconnected;
ConnectionRole m_connectionRole = ConnectionRole::Connector;
SocketFd m_registeredSocketFd;
static const uint32_t SendRingbufferSize = 1024 * 1024; // 1 MB send buffer
TcpRingBuffer<SendRingbufferSize> m_sendRingbuffer;
static const uint32_t RecvRingbufferSize = 1024 * 1024; // 1 MB recv buffer
TcpRingBuffer<RecvRingbufferSize> m_recvRingbuffer;
};
}
#include <AzNetworking/TcpTransport/TcpConnection.inl>
@@ -0,0 +1,46 @@
/*
* 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 TcpSocket* TcpConnection::GetTcpSocket() const
{
return m_socket.get();
}
inline void TcpConnection::SetTimeoutId(TimeoutId timeoutId)
{
m_timeoutId = timeoutId;
}
inline TimeoutId TcpConnection::GetTimeoutId() const
{
return m_timeoutId;
}
inline bool TcpConnection::IsOpen() const
{
return m_socket->IsOpen();
}
inline void TcpConnection::SetRegisteredSocketFd(SocketFd registeredSocketFd)
{
m_registeredSocketFd = registeredSocketFd;
}
inline SocketFd TcpConnection::GetRegisteredSocketFd() const
{
return m_registeredSocketFd;
}
}
@@ -0,0 +1,131 @@
/*
* 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/TcpTransport/TcpConnectionSet.h>
#include <AzCore/Console/ILogger.h>
namespace AzNetworking
{
bool TcpConnectionSet::AddConnection(AZStd::unique_ptr<TcpConnection> connection)
{
AZ_Assert(connection, "Adding a nullptr TcpConnection instance to the connection set");
if (!connection)
{
return false;
}
AZLOG(TcpConnectionSet, "Adding new Tcp connection (%u : %d)",
aznumeric_cast<uint32_t>(connection->GetConnectionId()),
aznumeric_cast<int32_t>(connection->GetTcpSocket()->GetSocketFd())
);
// Check for errors here, don't want to clobber an existing connection...
AZ_Assert(GetConnection(connection->GetConnectionId()) == nullptr, "ConnectionId already exists in connection set");
AZ_Assert(GetConnection(connection->GetRegisteredSocketFd()) == nullptr, "Socket file descriptor already exists in connection set");
connection->SetRegisteredSocketFd(connection->GetTcpSocket()->GetSocketFd());
m_socketFdMap[connection->GetRegisteredSocketFd()] = connection.get();
m_connectionIdMap[connection->GetConnectionId()] = AZStd::move(connection);
return true;
}
bool TcpConnectionSet::DeleteConnection(SocketFd socketFd)
{
AZLOG(TcpConnectionSet, "Deleting Tcp connection by socketId (%u)", socketFd);
TcpConnection* connection = GetConnection(socketFd);
if (connection == nullptr)
{
return false;
}
AZLOG(TcpConnectionSet, "Deleting Tcp connection (%u : %d)",
aznumeric_cast<uint32_t>(connection->GetConnectionId()),
aznumeric_cast<int32_t>(connection->GetRegisteredSocketFd())
);
AZ_Assert(connection->GetRegisteredSocketFd() == socketFd, "Connection list is corrupt, mismatched socket file descriptors detected");
m_socketFdMap.erase(connection->GetRegisteredSocketFd());
connection->SetRegisteredSocketFd(InvalidSocketFd);
m_connectionIdMap.erase(connection->GetConnectionId());
return true;
}
void TcpConnectionSet::VisitConnections(const ConnectionVisitor& visitor)
{
for (auto& connection : m_connectionIdMap)
{
visitor(*connection.second);
}
}
bool TcpConnectionSet::DeleteConnection(ConnectionId connectionId)
{
AZLOG(TcpConnectionSet, "Deleting Tcp connection by connectionId (%u)", static_cast<uint32_t>(connectionId));
TcpConnection* connection = static_cast<TcpConnection*>(GetConnection(connectionId));
if (connection == nullptr)
{
return false;
}
AZLOG(TcpConnectionSet, "Deleting Tcp connection (%u : %d)",
aznumeric_cast<uint32_t>(connectionId),
aznumeric_cast<int32_t>(connection->GetRegisteredSocketFd())
);
AZ_Assert(connection->GetConnectionId() == connectionId, "Connection list is corrupt, mismatched connection identifiers detected");
m_socketFdMap.erase(connection->GetRegisteredSocketFd());
connection->SetRegisteredSocketFd(InvalidSocketFd);
m_connectionIdMap.erase(connectionId);
return true;
}
IConnection* TcpConnectionSet::GetConnection(ConnectionId connectionId) const
{
ConnectionIdMap::const_iterator lookup = m_connectionIdMap.find(connectionId);
if (lookup != m_connectionIdMap.end())
{
return lookup->second.get();
}
return nullptr;
}
ConnectionId TcpConnectionSet::GetNextConnectionId()
{
// In the case of wrap-around, don't return a connectionId that's in-use or is the invalid connection Id
do
{
++m_nextConnectionId;
if (m_nextConnectionId == InvalidConnectionId)
{
m_nextConnectionId = ConnectionId(0);
}
} while (m_connectionIdMap.count(m_nextConnectionId) > 0);
return m_nextConnectionId;
}
uint32_t TcpConnectionSet::GetConnectionCount() const
{
return aznumeric_cast<uint32_t>(m_connectionIdMap.size());
}
TcpConnection* TcpConnectionSet::GetConnection(SocketFd socketFd) const
{
SocketFdMap::const_iterator lookup = m_socketFdMap.find(socketFd);
if (lookup != m_socketFdMap.end())
{
return lookup->second;
}
return nullptr;
}
const TcpConnectionSet::SocketFdMap& TcpConnectionSet::GetSocketFdMap() const
{
return m_socketFdMap;
}
}
@@ -0,0 +1,69 @@
/*
* 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/RTTI/TypeSafeIntegral.h>
#include <AzNetworking/ConnectionLayer/IConnectionSet.h>
#include <AzNetworking/TcpTransport/TcpConnection.h>
#include <AzCore/std/containers/unordered_map.h>
namespace AzNetworking
{
//! @class TcpConnectionSet
//! @brief Tracks current TCP connections and allows fast lookups by socket fd and connection identifier.
class TcpConnectionSet final
: public IConnectionSet
{
public:
using ConnectionIdMap = AZStd::unordered_map<ConnectionId, AZStd::unique_ptr<TcpConnection>>;
using SocketFdMap = AZStd::unordered_map<SocketFd, TcpConnection*>;
TcpConnectionSet() = default;
virtual ~TcpConnectionSet() = default;
//! Adds a new connection to this connection list instance.
//! @param connection pointer to the connection instance to add
//! @return boolean true on success
bool AddConnection(AZStd::unique_ptr<TcpConnection> connection);
//! Deletes a connection from this connection list instance by socket fd.
//! @param socketFD socket file descriptor of the connection to delete
//! @return boolean true on success
bool DeleteConnection(SocketFd socketFd);
//! IConnectionSet interface.
//! @{
void VisitConnections(const ConnectionVisitor& visitor) override;
bool DeleteConnection(ConnectionId connectionId) override;
IConnection* GetConnection(ConnectionId connectionId) const override;
ConnectionId GetNextConnectionId() override;
uint32_t GetConnectionCount() const override;
//! @}
//! Retrieves a connection from this connection list instance by socket fd.
//! @param socketFD socket file descriptor of the connection to retrieve
//! @return pointer to the requested connection instance on success, nullptr on failure
TcpConnection* GetConnection(SocketFd socketFd) const;
//! Returns the set of SocketFds that should be bound to this connection list instance.
//! @return the set of SocketFds that should be bound to this connection list instance
const SocketFdMap& GetSocketFdMap() const;
private:
ConnectionId m_nextConnectionId = InvalidConnectionId;
ConnectionIdMap m_connectionIdMap;
SocketFdMap m_socketFdMap;
};
}
@@ -0,0 +1,196 @@
/*
* 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/TcpTransport/TcpListenThread.h>
#include <AzNetworking/TcpTransport/TcpNetworkInterface.h>
#include <AzNetworking/TcpTransport/TcpSocketManager.h>
#include <AzCore/Console/ILogger.h>
namespace AzNetworking
{
static constexpr AZ::TimeMs ListenThreadUpdateRateMs{ 10 };
TcpListenThread::TcpListenThread()
: TimedThread("AzNetworking::TcpListenThread", ListenThreadUpdateRateMs)
{
;
}
TcpListenThread::~TcpListenThread()
{
Stop();
Join();
}
bool TcpListenThread::Listen(TcpNetworkInterface& tcpNetworkInterface)
{
bool existsCheck = false;
auto visitor = [&tcpNetworkInterface, &existsCheck](ListenPort& listenPort)
{
if (listenPort.m_tcpNetworkInterface == &tcpNetworkInterface)
{
existsCheck = true;
}
};
m_listenPorts.Visit(visitor);
if (existsCheck)
{
AZLOG_ERROR("Attempted to insert the same network interface twice");
return false;
}
++m_listenPortCount;
ListenPort listenPort;
listenPort.m_listenPort = tcpNetworkInterface.GetPort();
listenPort.m_tcpNetworkInterface = &tcpNetworkInterface;
m_listenPorts.PushBackItem(listenPort);
AZLOG_INFO("TcpListenThread opening port: %d for incoming traffic", aznumeric_cast<int32_t>(listenPort.m_listenPort));
// Start the listen thread if we have ports to listen on
if (!IsRunning())
{
Start();
}
return true;
}
bool TcpListenThread::StopListening(TcpNetworkInterface& tcpNetworkInterface)
{
--m_listenPortCount;
auto visitor = [this, &tcpNetworkInterface](ListenPort& listenPort)
{
if (listenPort.m_tcpNetworkInterface == &tcpNetworkInterface)
{
// This kills any ability to route new incoming connections to the network interface
listenPort.m_tcpNetworkInterface = nullptr;
}
};
m_listenPorts.Visit(visitor);
// Stops the listen thread if there are no more listen sockets active
if (IsRunning() && (m_listenPortCount == 0))
{
Stop();
Join();
}
return true;
}
uint32_t TcpListenThread::GetSocketCount() const
{
return m_listenPortCount;
}
AZ::TimeMs TcpListenThread::GetUpdateTimeMs() const
{
return m_updateTimeMs;
}
void TcpListenThread::OnStart()
{
AZLOG_INFO("Starting TcpListenThread");
}
void TcpListenThread::OnStop()
{
AZLOG_INFO("Stopping TcpListenThread");
}
void TcpListenThread::OnUpdate(AZ::TimeMs updateRateMs)
{
// Don't proceed with any processing if our network state is not valid
if (!EnsureSocketState())
{
return;
}
AZ::TimeMs startTimeMs = AZ::GetElapsedTimeMs();
struct sockaddr_in newConnection;
const int32_t connectionLength = aznumeric_cast<int32_t>(sizeof(newConnection));
memset(&newConnection, 0, connectionLength);
auto readCallback = [this, newConnection, connectionLength](SocketFd socketFd)
{
auto visitor = [this, newConnection, connectionLength, socketFd](ListenPort& listenPort)
{
if (listenPort.m_listenSocket.GetSocketFd() == socketFd)
{
HandleSocketAccept((void*)&newConnection, connectionLength, listenPort);
}
};
m_listenPorts.Visit(visitor);
};
auto writeCallback = [](SocketFd) {};
m_tcpSocketManager.ProcessEvents(updateRateMs, readCallback, writeCallback);
auto cleanupUnused = [this](AZ::ThreadSafeDeque<ListenPort>::DequeType& deque)
{
AZStd::remove_if(deque.begin(), deque.end(), [](ListenPort& listenPort) { return listenPort.m_tcpNetworkInterface == nullptr; });
};
m_listenPorts.VisitDeque(cleanupUnused);
m_updateTimeMs += AZ::GetElapsedTimeMs() - startTimeMs;
}
bool TcpListenThread::EnsureSocketState()
{
bool result = true;
auto visitor = [this, &result](ListenPort& listenPort)
{
if (listenPort.m_tcpNetworkInterface && !listenPort.m_listenSocket.IsOpen())
{
if (!listenPort.m_listenSocket.Listen(listenPort.m_listenPort))
{
listenPort.m_listenSocket.Close();
result = false;
}
else
{
result &= m_tcpSocketManager.AddSocket(listenPort.m_listenSocket.GetSocketFd());
}
}
};
m_listenPorts.Visit(visitor);
return result;
}
bool TcpListenThread::HandleSocketAccept(void* newConnection, int32_t newConnectionLength, ListenPort& listenPort)
{
struct sockaddr* newConnectionSockAddr = (struct sockaddr*)newConnection;
const int32_t socketFdInt = aznumeric_cast<int32_t>(listenPort.m_listenSocket.GetSocketFd());
socklen_t newConnectionLengthSocklen = aznumeric_cast<socklen_t>(newConnectionLength);
const SocketFd newSocketFd = aznumeric_cast<SocketFd>(::accept(socketFdInt, newConnectionSockAddr, &newConnectionLengthSocklen));
if (newSocketFd <= SocketFd{ 0 })
{
const int32_t error = GetLastNetworkError();
AZLOG_WARN("Failed to accept incoming connection (%d:%s)", error, GetNetworkErrorDesc(error));
return false;
}
// Hand new connection off to a worker thread
struct sockaddr_in* newConnectionSockAddrIn = (struct sockaddr_in*)newConnection;
TcpNetworkInterface::PendingConnection pendingConnection(
newSocketFd,
newConnectionSockAddrIn->sin_addr.s_addr,
newConnectionSockAddrIn->sin_port,
listenPort.m_listenPort
);
listenPort.m_tcpNetworkInterface->QueueNewConnection(pendingConnection);
return true;
}
}
@@ -0,0 +1,76 @@
/*
* 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/TcpTransport/TcpSocket.h>
#include <AzNetworking/TcpTransport/TcpSocketManager.h>
#include <AzNetworking/Framework/INetworkInterface.h>
#include <AzNetworking/Utilities/TimedThread.h>
#include <AzCore/Threading/ThreadSafeDeque.h>
namespace AzNetworking
{
class TcpNetworkInterface;
//! @class TcpListenThread
//! @brief A class for managing a TCP listen socket and accepting new incoming connections.
class TcpListenThread final
: public TimedThread
{
public:
TcpListenThread();
~TcpListenThread() override;
//! Opens a new listen socket capable of accepting incoming connections for the provided TcpNetworkInterface.
//! @param tcpNetworkInterface the TcpNetworkInterface being opened to incoming connections
//! @return boolean true if the operation was successful, false if it failed
bool Listen(TcpNetworkInterface& tcpNetworkInterface);
//! Stops listening for incoming connections for the provided TcpNetworkInterface.
//! @param tcpNetworkInterface the TcpNetworkInterface being closed to new incoming connections
//! @return boolean true if the operation was successful, false if it failed
bool StopListening(TcpNetworkInterface& tcpNetworkInterface);
//! Returns the number of active listen ports bound to this thread.
//! @return the number of active listen ports bound to this thread
uint32_t GetSocketCount() const;
//! Gets the total elapsed time spent updating the background thread in milliseconds
//! @return the total elapsed time spent updating the background thread in milliseconds
AZ::TimeMs GetUpdateTimeMs() const;
private:
AZ_DISABLE_COPY_MOVE(TcpListenThread);
struct ListenPort
{
TcpSocket m_listenSocket;
TcpNetworkInterface* m_tcpNetworkInterface = nullptr;
uint16_t m_listenPort;
};
void OnStart() override;
void OnStop() override;
void OnUpdate(AZ::TimeMs updateRateMs) override;
bool EnsureSocketState();
bool HandleSocketAccept(void* newConnection, int32_t newConnectionLength, ListenPort& listenPort);
uint32_t m_listenPortCount = 0;
TcpSocketManager m_tcpSocketManager;
AZ::ThreadSafeDeque<ListenPort> m_listenPorts;
AZ::TimeMs m_updateTimeMs = AZ::TimeMs{ 0 };
};
}
@@ -0,0 +1,315 @@
/*
* 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/TcpTransport/TcpNetworkInterface.h>
#include <AzNetworking/TcpTransport/TcpSocketManager.h>
#include <AzNetworking/AutoGen/CorePackets.AutoPackets.h>
#include <AzNetworking/Utilities/NetworkCommon.h>
#include <AzCore/Console/IConsole.h>
#include <AzCore/Console/ILogger.h>
namespace AzNetworking
{
#if AZ_TRAIT_USE_OPENSSL
AZ_CVAR(bool, net_TcpUseEncryption, false, nullptr, AZ::ConsoleFunctorFlags::DontReplicate, "Enable encryption on Tcp based connections");
#else
static const bool net_TcpUseEncryption = false;
#endif
AZ_CVAR(bool, net_TcpTimeoutConnections, true, nullptr, AZ::ConsoleFunctorFlags::DontReplicate, "Boolean value on whether we should timeout Tcp connections");
AZ_CVAR(AZ::TimeMs, net_TcpHearthbeatTimeMs, AZ::TimeMs{ 2 * 1000 }, nullptr, AZ::ConsoleFunctorFlags::Null, "Tcp connection heartbeat frequency");
AZ_CVAR(AZ::TimeMs, net_TcpTimeoutTimeMs, AZ::TimeMs{ 10 * 1000 }, nullptr, AZ::ConsoleFunctorFlags::Null, "Time in milliseconds before we timeout an idle Tcp connection");
TcpNetworkInterface::TcpNetworkInterface(AZ::Name name, IConnectionListener& connectionListener, TrustZone trustZone, TcpListenThread& listenThread)
: m_name(name)
, m_trustZone(trustZone)
, m_connectionListener(connectionListener)
, m_listenThread(listenThread)
{
;
}
TcpNetworkInterface::~TcpNetworkInterface()
{
FlushQueuedRemoves();
m_listenThread.StopListening(*this);
}
AZ::Name TcpNetworkInterface::GetName() const
{
return m_name;
}
ProtocolType TcpNetworkInterface::GetType() const
{
return ProtocolType::Tcp;
}
TrustZone TcpNetworkInterface::GetTrustZone() const
{
return m_trustZone;
}
uint16_t TcpNetworkInterface::GetPort() const
{
return m_port;
}
IConnectionSet& TcpNetworkInterface::GetConnectionSet()
{
return m_connectionSet;
}
IConnectionListener& TcpNetworkInterface::GetConnectionListener()
{
return m_connectionListener;
}
bool TcpNetworkInterface::Listen(uint16_t port)
{
m_port = port;
return m_listenThread.Listen(*this);
}
ConnectionId TcpNetworkInterface::Connect(const IpAddress& remoteAddress)
{
const ConnectionId connectionId = m_connectionSet.GetNextConnectionId();
AZStd::unique_ptr<TcpConnection> connection = AZStd::make_unique<TcpConnection>(connectionId, remoteAddress, *this, m_trustZone, net_TcpUseEncryption);
AZ_Assert(connection->GetConnectionRole() == ConnectionRole::Connector, "Invalid role for connection");
connection->Connect();
TcpSocket* tcpSocket = connection->GetTcpSocket();
if (tcpSocket == nullptr)
{
return InvalidConnectionId;
}
if (!(tcpSocket->IsOpen() && m_tcpSocketManager.AddSocket(tcpSocket->GetSocketFd())))
{
tcpSocket->Close();
AZLOG_ERROR("Failed to bind new incoming connection to socket manager, failed fd: %d", static_cast<int32_t>(tcpSocket->GetSocketFd()));
return InvalidConnectionId;
}
AZLOG_INFO("Adding new socket %d", static_cast<int32_t>(tcpSocket->GetSocketFd()));
const TimeoutId newTimeoutId = m_connectionTimeoutQueue.RegisterItem(static_cast<uint64_t>(tcpSocket->GetSocketFd()), net_TcpHearthbeatTimeMs);
connection->SetTimeoutId(newTimeoutId);
connection->SendReliablePacket(CorePackets::InitiateConnectionPacket());
m_connectionListener.OnConnect(connection.get());
m_connectionSet.AddConnection(AZStd::move(connection));
return connectionId;
}
void TcpNetworkInterface::Update([[maybe_unused]] AZ::TimeMs deltaTimeMs)
{
const AZ::TimeMs startTimeMs = AZ::GetElapsedTimeMs();
// Time out any stale connections
{
ConnectionTimeoutFunctor functor(*this);
m_connectionTimeoutQueue.UpdateTimeouts(functor);
}
AcceptNewConnections();
auto readCallback = [this, startTimeMs](SocketFd socketFd) { HandleConnectionRecv(socketFd, startTimeMs); };
auto writeCallback = [this](SocketFd socketFd) { HandleConnectionSend(socketFd); };
m_tcpSocketManager.ProcessEvents(AZ::TimeMs{ 0 }, readCallback, writeCallback);
FlushQueuedRemoves();
// Update metrics
GetMetrics().m_connectionCount = m_connectionSet.GetConnectionCount();
GetMetrics().m_updateTimeMs += AZ::GetElapsedTimeMs() - startTimeMs;
}
bool TcpNetworkInterface::SendReliablePacket(ConnectionId connectionId, const IPacket& packet)
{
IConnection* connection = m_connectionSet.GetConnection(connectionId);
if (connection == nullptr)
{
return false;
}
return connection->SendReliablePacket(packet);
}
PacketId TcpNetworkInterface::SendUnreliablePacket(ConnectionId connectionId, const IPacket& packet)
{
IConnection* connection = m_connectionSet.GetConnection(connectionId);
if (connection == nullptr)
{
return InvalidPacketId;
}
return connection->SendUnreliablePacket(packet);
}
bool TcpNetworkInterface::WasPacketAcked(ConnectionId connectionId, PacketId packetId)
{
IConnection* connection = m_connectionSet.GetConnection(connectionId);
if (connection == nullptr)
{
return false;
}
return connection->WasPacketAcked(packetId);
}
bool TcpNetworkInterface::Disconnect(ConnectionId connectionId, DisconnectReason reason)
{
IConnection* connection = m_connectionSet.GetConnection(connectionId);
if (connection == nullptr)
{
return false;
}
return connection->Disconnect(reason, TerminationEndpoint::Local);
}
void TcpNetworkInterface::QueueNewConnection(const PendingConnection& pendingConnection)
{
m_pendingConnections.PushBackItem(pendingConnection);
}
bool TcpNetworkInterface::HandleConnectionRecv(SocketFd socketFd, [[maybe_unused]] AZ::TimeMs currentTimeMs)
{
TcpConnection* connection = m_connectionSet.GetConnection(socketFd);
if (connection == nullptr)
{
return false;
}
const bool result = connection->UpdateRecv();
if (!result)
{
connection->Disconnect(DisconnectReason::RemoteHostClosedConnection, TerminationEndpoint::Remote);
}
return result;
}
bool TcpNetworkInterface::HandleConnectionSend(SocketFd socketFd)
{
TcpConnection* connection = m_connectionSet.GetConnection(socketFd);
if (connection == nullptr)
{
return false;
}
connection->UpdateSend();
return true;
}
void TcpNetworkInterface::RequestDisconnect(TcpConnection* connection, DisconnectReason reason)
{
m_pendingRemoves.emplace_back(PendingRemove{ connection->GetRegisteredSocketFd(), reason });
}
void TcpNetworkInterface::AcceptNewConnections()
{
if (m_pendingConnections.Size() <= 0)
{
// Early out to avoid the deque below invoking a heap allocation
// This is a performance optimization only, due to the expense of heap allocation calls on windows
return;
}
AZ::ThreadSafeDeque<PendingConnection>::DequeType pendingConnections;
m_pendingConnections.Swap(pendingConnections);
for (auto pendingConnection : pendingConnections)
{
IpAddress remoteAddress = IpAddress(ByteOrder::Network, pendingConnection.m_remoteIpAddress, pendingConnection.m_remotePort);
if (net_TcpUseEncryption)
{
TlsSocket newSocket = TlsSocket(pendingConnection.m_socketFd, m_trustZone);
AddConnectionHelper(m_connectionSet.GetNextConnectionId(), remoteAddress, newSocket);
}
else
{
TcpSocket newSocket = TcpSocket(pendingConnection.m_socketFd);
AddConnectionHelper(m_connectionSet.GetNextConnectionId(), remoteAddress, newSocket);
}
}
}
void TcpNetworkInterface::AddConnectionHelper(ConnectionId connectionId, const IpAddress& remoteAddress, TcpSocket& tcpSocket)
{
if (!(tcpSocket.IsOpen() && m_tcpSocketManager.AddSocket(tcpSocket.GetSocketFd())))
{
tcpSocket.Close();
AZLOG_ERROR("Failed to bind new incoming connection to socket manager, failed fd: %d", static_cast<int32_t>(tcpSocket.GetSocketFd()));
return;
}
AZLOG(NET_TcpTraffic, "Adding new socket %d", static_cast<int32_t>(tcpSocket.GetSocketFd()));
const TimeoutId timeoutId = m_connectionTimeoutQueue.RegisterItem(static_cast<uint64_t>(tcpSocket.GetSocketFd()), net_TcpTimeoutTimeMs);
AZStd::unique_ptr<TcpConnection> connection = AZStd::make_unique<TcpConnection>(connectionId, remoteAddress, *this, tcpSocket, timeoutId);
AZ_Assert(connection->GetConnectionRole() == ConnectionRole::Acceptor, "Invalid role for connection");
GetConnectionListener().OnConnect(connection.get());
m_connectionSet.AddConnection(AZStd::move(connection));
}
void TcpNetworkInterface::FlushQueuedRemoves()
{
for (uint32_t i = 0; i < m_pendingRemoves.size(); ++i)
{
const SocketFd socketFd = m_pendingRemoves[i].m_socketFd;
const DisconnectReason reason = m_pendingRemoves[i].m_reason;
TcpConnection* connection = m_connectionSet.GetConnection(socketFd);
if (connection == nullptr)
{
continue;
}
AZLOG_INFO("Removing socket %d due to %s", static_cast<int32_t>(socketFd), AZStd::string(ToString(reason)).c_str());
m_tcpSocketManager.ClearSocket(socketFd);
m_connectionSet.DeleteConnection(socketFd);
}
m_pendingRemoves.resize_no_construct(0);
}
TcpNetworkInterface::PendingConnection::PendingConnection(SocketFd socketFd, uint32_t remoteIpAddress, uint16_t remotePort, uint16_t listenPort)
: m_socketFd(socketFd)
, m_remoteIpAddress(remoteIpAddress)
, m_remotePort(remotePort)
, m_listenPort(listenPort)
{
;
}
TcpNetworkInterface::ConnectionTimeoutFunctor::ConnectionTimeoutFunctor(TcpNetworkInterface& networkInterface)
: m_networkInterface(networkInterface)
{
;
}
TimeoutResult TcpNetworkInterface::ConnectionTimeoutFunctor::HandleTimeout(TimeoutQueue::TimeoutItem& item)
{
const SocketFd socketFd = static_cast<SocketFd>(item.m_userData);
TcpConnection* tcpConnection = m_networkInterface.m_connectionSet.GetConnection(socketFd);
if (tcpConnection == nullptr)
{
// We've already deleted this connection
return TimeoutResult::Delete;
}
if (tcpConnection->GetConnectionRole() == ConnectionRole::Connector)
{
tcpConnection->SendReliablePacket(CorePackets::HeartbeatPacket());
}
else if (net_TcpTimeoutConnections)
{
tcpConnection->Disconnect(DisconnectReason::Timeout, TerminationEndpoint::Local);
return TimeoutResult::Delete;
}
return TimeoutResult::Refresh;
}
}
@@ -0,0 +1,132 @@
/*
* 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/TcpTransport/TcpPacketHeader.h>
#include <AzNetworking/TcpTransport/TcpConnectionSet.h>
#include <AzNetworking/TcpTransport/TcpListenThread.h>
#include <AzNetworking/ConnectionLayer/IConnection.h>
#include <AzNetworking/Framework/INetworkInterface.h>
#include <AzCore/Threading/ThreadSafeDeque.h>
namespace AzNetworking
{
class IConnectionListener;
//! @class TcpNetworkInterface
//! @brief This class implements a TCP network interface.
class TcpNetworkInterface final
: public INetworkInterface
{
public:
//! @struct PendingConnection
//! @brief helper structure for transferring new pending connections from the listen thread to network interface.
struct PendingConnection
{
PendingConnection(SocketFd socketFd, uint32_t remoteIpAddress, uint16_t remotePort, uint16_t listenPort);
SocketFd m_socketFd;
uint32_t m_remoteIpAddress;
uint16_t m_remotePort;
uint16_t m_listenPort;
};
//! Constructor.
//! @param name the name of this network interface instance.
//! @param connectionListener reference to the connection listener responsible for handling all connection events
//! @param trustZone the trust level assigned to this network interface, server to server or client to server
//! @param listenThread the listen thread to bind to this network interface
TcpNetworkInterface(AZ::Name name, IConnectionListener& connectionListener, TrustZone trustZone, TcpListenThread& listenThread);
~TcpNetworkInterface() override;
//! INetworkInterface interface.
//! @{
AZ::Name GetName() const override;
ProtocolType GetType() const override;
TrustZone GetTrustZone() const override;
uint16_t GetPort() const override;
IConnectionSet& GetConnectionSet() override;
IConnectionListener& GetConnectionListener() override;
bool Listen(uint16_t port) override;
ConnectionId Connect(const IpAddress& remoteAddress) override;
void Update(AZ::TimeMs deltaTimeMs) override;
bool SendReliablePacket(ConnectionId connectionId, const IPacket& packet) override;
PacketId SendUnreliablePacket(ConnectionId connectionId, const IPacket& packet) override;
bool WasPacketAcked(ConnectionId connectionId, PacketId packetId) override;
bool Disconnect(ConnectionId connectionId, DisconnectReason reason) override;
//! @}
//! Queues a new incoming connection for this network interface.
//! @param pendingConnection info on the new incoming connection
void QueueNewConnection(const PendingConnection& pendingConnection);
private:
//! Performs connection receive updates for a single socket.
//! @param socketFd socket descriptor with new incoming data
//! @param currentTimeMs current time in milliseconds for metrics management
bool HandleConnectionRecv(SocketFd socketFd, AZ::TimeMs currentTimeMs);
//! Performs connection send updates for a single socket.
//! @param socketFd socket descriptor to send data to
bool HandleConnectionSend(SocketFd socketFd);
//! Internal helper to cleanly remove a connection from the network interface.
//! @param connection pointer to the connection to disconnect
//! @param reason reason for the disconnect
void RequestDisconnect(TcpConnection* connection, DisconnectReason reason);
//! Internal method to activate all pending connections.
void AcceptNewConnections();
//! Method that correctly adds a new connection to the network interface.
//! @param connectionId connection id of the new connection
//! @param remoteAddress address of the remote endpoint
//! @param tcpSocket underlying TCP socket connected to the remote endpoint
void AddConnectionHelper(ConnectionId connectionId, const IpAddress& remoteAddress, TcpSocket& tcpSocket);
//! Deletes all connections queued for removal from the network interface.
void FlushQueuedRemoves();
AZ_DISABLE_COPY_MOVE(TcpNetworkInterface);
struct ConnectionTimeoutFunctor final
: public ITimeoutHandler
{
ConnectionTimeoutFunctor(TcpNetworkInterface& networkInterface);
TimeoutResult HandleTimeout(TimeoutQueue::TimeoutItem& item) override;
private:
AZ_DISABLE_COPY_MOVE(ConnectionTimeoutFunctor);
TcpNetworkInterface& m_networkInterface;
};
struct PendingRemove
{
SocketFd m_socketFd;
DisconnectReason m_reason;
};
AZ::Name m_name;
TrustZone m_trustZone;
uint16_t m_port = 0;
IConnectionListener& m_connectionListener;
TcpConnectionSet m_connectionSet;
TcpSocketManager m_tcpSocketManager;
AZ::ThreadSafeDeque<PendingConnection> m_pendingConnections;
AZStd::vector<PendingRemove> m_pendingRemoves;
TimeoutQueue m_connectionTimeoutQueue;
TcpListenThread& m_listenThread;
friend class TcpConnection; // For access to private RequestDisconnect() method
};
}
@@ -0,0 +1,24 @@
/*
* 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/TcpTransport/TcpPacketHeader.h>
namespace AzNetworking
{
bool TcpPacketHeader::Serialize(ISerializer& serializer)
{
serializer.Serialize(m_packetFlags, "Flags");
serializer.Serialize(m_packetType, "Type");
serializer.Serialize(m_packetSize, "Size");
return serializer.IsValid();
}
}
@@ -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 <AzNetworking/PacketLayer/IPacket.h>
#include <AzNetworking/PacketLayer/IPacketHeader.h>
#include <AzNetworking/Serialization/ISerializer.h>
namespace AzNetworking
{
//! @class TcpPacketHeader
//! @brief packet header class.
class TcpPacketHeader final
: public IPacketHeader
{
public:
AZ_RTTI(TcpPacketHeader, "{6D92B9BE-C5E4-4571-B0FA-8F29042BE93B}", IPacketHeader);
//! Construct with a packet type and size.
//! @param packetType type of packet
//! @param packetSize size of the packet in bytes, not including header size
TcpPacketHeader(PacketType packetType, uint16_t packetSize);
virtual ~TcpPacketHeader() = default;
//! IPacketHeader interface.
// @{
PacketType GetPacketType() const override;
PacketId GetPacketId() const override;
bool IsPacketFlagSet(PacketFlag flag) const override;
void SetPacketFlag(PacketFlag flag, bool value) override;
// @}
//! Gets the size of the packet being received.
//! @return size of the packet in bytes, not including header size
uint16_t GetPacketSize() 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:
PacketType m_packetType;
uint16_t m_packetSize;
// TCP Packet Flags are serialized with the header as the entire header is never compressed
PacketFlagBitset m_packetFlags;
};
}
#include <AzNetworking/TcpTransport/TcpPacketHeader.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 TcpPacketHeader::TcpPacketHeader(PacketType packetType, uint16_t packetSize)
: m_packetType(packetType)
, m_packetSize(packetSize)
{
;
}
inline PacketType TcpPacketHeader::GetPacketType() const
{
return m_packetType;
}
inline PacketId TcpPacketHeader::GetPacketId() const
{
return InvalidPacketId;
}
inline uint16_t TcpPacketHeader::GetPacketSize() const
{
return m_packetSize;
}
inline bool TcpPacketHeader::IsPacketFlagSet(PacketFlag flag) const
{
return m_packetFlags.GetBit(aznumeric_cast<uint32_t>(flag));
}
inline void TcpPacketHeader::SetPacketFlag(PacketFlag flag, bool value)
{
m_packetFlags.SetBit(aznumeric_cast<uint32_t>(flag), value);
}
}
@@ -0,0 +1,59 @@
/*
* 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/TcpTransport/TcpRingBufferImpl.h>
namespace AzNetworking
{
//! @class TcpRingBuffer
//! @brief statically sized ringbuffer class for reading from or writing to data streams like a TCP socket connection.
template <uint32_t SIZE>
class TcpRingBuffer
{
public:
TcpRingBuffer();
~TcpRingBuffer() = default;
//! Returns a pointer into writable memory guaranteed to be of at least numBytes in length.
//! @param numBytes maximum number of bytes to be written to the ring-buffer
//! @return pointer to the requested memory, nullptr if the requested size is too large for the ringbuffer to store contiguously
uint8_t* ReserveBlockForWrite(uint32_t numBytes);
//! Returns the start of ringbuffer read memory.
//! @return pointer to the start of ringbuffer read memory
uint8_t* GetReadBufferData() const;
//! Returns the size of ringbuffer read memory in bytes.
//! @return the size of ringbuffer read memory in bytes
uint32_t GetReadBufferSize() const;
//! Advances the ringbuffer write offset by the requested number of bytes.
//! @param numBytes number of bytes to advance the ringbuffer write pointer by
//! @return boolean true on success
bool AdvanceWriteBuffer(uint32_t numBytes);
//! Advances the ringbuffer read offset by the requested number of bytes.
//! @param numBytes number of bytes to advance the ringbuffer read pointer by
//! @return boolean true on success
bool AdvanceReadBuffer(uint32_t numBytes);
private:
AZStd::array<uint8_t, SIZE> m_buffer;
TcpRingBufferImpl m_impl;
};
}
#include <AzNetworking/TcpTransport/TcpRingBuffer.inl>
@@ -0,0 +1,53 @@
/*
* 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
{
template <uint32_t SIZE>
inline TcpRingBuffer<SIZE>::TcpRingBuffer()
: m_impl(m_buffer.data(), m_buffer.size())
{
;
}
template <uint32_t SIZE>
inline uint8_t* TcpRingBuffer<SIZE>::ReserveBlockForWrite(uint32_t numBytes)
{
return m_impl.ReserveBlockForWrite(numBytes);
}
template <uint32_t SIZE>
inline uint8_t* TcpRingBuffer<SIZE>::GetReadBufferData() const
{
return m_impl.GetReadBufferData();
}
template <uint32_t SIZE>
inline uint32_t TcpRingBuffer<SIZE>::GetReadBufferSize() const
{
return m_impl.GetReadBufferSize();
}
template <uint32_t SIZE>
inline bool TcpRingBuffer<SIZE>::AdvanceWriteBuffer(uint32_t numBytes)
{
return m_impl.AdvanceWriteBuffer(numBytes);
}
template <uint32_t SIZE>
inline bool TcpRingBuffer<SIZE>::AdvanceReadBuffer(uint32_t numBytes)
{
return m_impl.AdvanceReadBuffer(numBytes);
}
}
@@ -0,0 +1,66 @@
/*
* 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/TcpTransport/TcpRingBufferImpl.h>
namespace AzNetworking
{
TcpRingBufferImpl::TcpRingBufferImpl(uint8_t* buffer, uint32_t bufferSize)
: m_bufferStart(buffer)
, m_bufferEnd(buffer + bufferSize)
, m_writePtr(buffer)
, m_readPtr(buffer)
{
;
}
uint8_t* TcpRingBufferImpl::ReserveBlockForWrite(uint32_t numBytes)
{
// If we don't have enough space remaining, pack the ring buffer
if (GetFreeBytes() < numBytes)
{
const uint32_t numUsedBytes = GetUsedBytes();
memmove(m_bufferStart, m_readPtr, numUsedBytes);
m_writePtr = m_bufferStart + numUsedBytes;
m_readPtr = m_bufferStart;
}
if (GetFreeBytes() < numBytes)
{
return nullptr;
}
return m_writePtr;
}
bool TcpRingBufferImpl::AdvanceWriteBuffer(uint32_t numBytes)
{
if (GetFreeBytes() < numBytes)
{
return false;
}
m_writePtr += numBytes;
return true;
}
bool TcpRingBufferImpl::AdvanceReadBuffer(uint32_t numBytes)
{
if (numBytes > GetUsedBytes())
{
return false;
}
m_readPtr += numBytes;
return true;
}
}
@@ -0,0 +1,73 @@
/*
* 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 <string.h> // For memmove
namespace AzNetworking
{
//! @class TcpRingBufferImpl
//! @brief helper class to move ring buffer implementation details out of template header code.
class TcpRingBufferImpl
{
public:
//! Construct with a buffer and size.
//! @param buffer input buffer to use as ring-buffer storage
//! @param bufferSize size of the input buffer in bytes
TcpRingBufferImpl(uint8_t* buffer, uint32_t bufferSize);
virtual ~TcpRingBufferImpl() = default;
//! Returns a pointer into writable memory guaranteed to be of at least numBytes in length.
//! @param numBytes maximum number of bytes to be written to the ring-buffer
//! @return pointer to the requested memory, nullptr if the requested size is too large for the ringbuffer to store contiguously
uint8_t* ReserveBlockForWrite(uint32_t numBytes);
//! Returns the start of ringbuffer read memory.
//! @return pointer to the start of ringbuffer read memory
uint8_t* GetReadBufferData() const;
//! Returns the size of ringbuffer read memory in bytes.
//! @return the size of ringbuffer read memory in bytes
uint32_t GetReadBufferSize() const;
//! Advances the ringbuffer write offset by the requested number of bytes.
//! @param numBytes number of bytes to advance the ringbuffer write pointer by
//! @return boolean true on success
bool AdvanceWriteBuffer(uint32_t numBytes);
//! Advances the ringbuffer read offset by the requested number of bytes.
//! @param numBytes number of bytes to advance the ringbuffer read pointer by
//! @return boolean true on success
bool AdvanceReadBuffer(uint32_t numBytes);
private:
//! Returns the number of contiguous bytes free for writing.
//! @return number of contiguous bytes free for writing
uint32_t GetFreeBytes() const;
//! Returns the number of bytes of data valid for reading.
//! @return number of bytes of data valid for reading
uint32_t GetUsedBytes() const;
uint8_t* m_bufferStart;
uint8_t* m_bufferEnd;
uint8_t* m_writePtr;
uint8_t* m_readPtr;
};
}
#include <AzNetworking/TcpTransport/TcpRingBufferImpl.inl>
@@ -0,0 +1,37 @@
/*
* 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* TcpRingBufferImpl::GetReadBufferData() const
{
return m_readPtr;
}
inline uint32_t TcpRingBufferImpl::GetReadBufferSize() const
{
return GetUsedBytes();
}
inline uint32_t TcpRingBufferImpl::GetFreeBytes() const
{
return static_cast<uint32_t>(m_bufferEnd - m_writePtr);
}
inline uint32_t TcpRingBufferImpl::GetUsedBytes() const
{
return static_cast<uint32_t>(m_writePtr - m_readPtr);
}
}
@@ -0,0 +1,237 @@
/*
* 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/AzNetworking_Traits_Platform.h>
#include <AzNetworking/TcpTransport/TcpSocket.h>
#include <AzNetworking/Utilities/Endian.h>
#include <AzNetworking/Utilities/NetworkIncludes.h>
#include <AzCore/Console/ILogger.h>
namespace AzNetworking
{
TcpSocket::TcpSocket()
: m_socketFd(InvalidSocketFd)
{
;
}
TcpSocket::TcpSocket(SocketFd socketFd)
: m_socketFd(socketFd)
{
if (m_socketFd != InvalidSocketFd)
{
if (!(SetSocketNonBlocking(m_socketFd) && SetSocketNoDelay(m_socketFd)))
{
Close();
}
}
}
TcpSocket::~TcpSocket()
{
Close();
}
bool TcpSocket::IsEncrypted() const
{
return false;
}
TcpSocket* TcpSocket::CloneAndTakeOwnership()
{
TcpSocket* result = new TcpSocket(m_socketFd);
m_socketFd = InvalidSocketFd;
return result;
}
bool TcpSocket::Listen(uint16_t port)
{
Close();
if (!SocketCreateInternal())
{
return false;
}
if (!BindSocketForListenInternal(port))
{
return false;
}
if (!(SetSocketNonBlocking(m_socketFd) && SetSocketNoDelay(m_socketFd)))
{
return false;
}
return true;
}
bool TcpSocket::Connect(const IpAddress& address)
{
Close();
if (!SocketCreateInternal())
{
return false;
}
if (!BindSocketForConnectInternal(address))
{
return false;
}
if (!(SetSocketNonBlocking(m_socketFd) && SetSocketNoDelay(m_socketFd)))
{
return false;
}
return true;
}
void TcpSocket::Close()
{
CloseSocket(m_socketFd);
m_socketFd = InvalidSocketFd;
}
int32_t TcpSocket::Send(const uint8_t* data, uint32_t size) const
{
AZ_Assert(size > 0, "Invalid data size for send");
AZ_Assert(data != nullptr, "NULL data pointer passed to send");
if (!IsOpen())
{
return SocketOpResultErrorNotOpen;
}
return SendInternal(data, size);
}
int32_t TcpSocket::Receive(uint8_t* outData, uint32_t size) const
{
AZ_Assert(size > 0, "Invalid data size for send");
AZ_Assert(outData != nullptr, "NULL data pointer passed to send");
if (!IsOpen())
{
return SocketOpResultErrorNotOpen;
}
return ReceiveInternal(outData, size);
}
int32_t TcpSocket::SendInternal(const uint8_t* data, uint32_t size) const
{
const int32_t sentBytes = send(aznumeric_cast<int32_t>(m_socketFd), (const char*)data, size, 0);
if (sentBytes < 0)
{
const int32_t error = GetLastNetworkError();
if (ErrorIsWouldBlock(error)) // Filter would block messages
{
return 0;
}
AZLOG_WARN("Failed to write to socket (%d:%s)", error, GetNetworkErrorDesc(error));
}
return sentBytes;
}
int32_t TcpSocket::ReceiveInternal(uint8_t* outData, uint32_t size) const
{
const int32_t receivedBytes = recv(aznumeric_cast<int32_t>(m_socketFd), (char*)outData, (int32_t)size, 0);
if (receivedBytes < 0)
{
const int32_t error = GetLastNetworkError();
if (ErrorIsWouldBlock(error)) // Filter would block messages
{
return 0;
}
AZLOG_ERROR("Failed to read from socket (%d:%s)", error, GetNetworkErrorDesc(error));
}
else if (receivedBytes == 0)
{
// Clean disconnect, force the endpoint to disconnect and cleanup
return SocketOpResultDisconnected;
}
return receivedBytes;
}
bool TcpSocket::BindSocketForListenInternal(uint16_t port)
{
// Handle binding
{
sockaddr_in hints;
hints.sin_family = AF_INET;
hints.sin_addr.s_addr = INADDR_ANY;
hints.sin_port = htons(port);
if (::bind(aznumeric_cast<int32_t>(m_socketFd), (const sockaddr*)&hints, sizeof(hints)) != 0)
{
const int32_t error = GetLastNetworkError();
AZLOG_ERROR("Failed to bind socket (%d:%s)", error, GetNetworkErrorDesc(error));
return false;
}
if (::listen(aznumeric_cast<int32_t>(m_socketFd), SOMAXCONN) != 0)
{
const int32_t error = GetLastNetworkError();
AZLOG_ERROR("Failed to listen on socket (%d:%s)", error, GetNetworkErrorDesc(error));
return false;
}
}
return true;
}
bool TcpSocket::BindSocketForConnectInternal(const IpAddress& address)
{
struct sockaddr_in dest;
memset(&dest, 0, sizeof(dest));
dest.sin_family = AF_INET;
dest.sin_addr.s_addr = address.GetAddress(ByteOrder::Network);
dest.sin_port = address.GetPort(ByteOrder::Network);
if (::connect(static_cast<int32_t>(m_socketFd), (struct sockaddr*)&dest, sizeof(dest)) != 0)
{
const int32_t error = GetLastNetworkError();
AZLOG_ERROR("Failed to connect to remote endpoint (%s) (%d:%s)", address.GetString().c_str(), error, GetNetworkErrorDesc(error));
return false;
}
return true;
}
bool TcpSocket::SocketCreateInternal()
{
AZ_Assert(!IsOpen(), "Open called on an active socket");
if (IsOpen())
{
return false;
}
// Open the socket
{
m_socketFd = (SocketFd)::socket(AF_INET, SOCK_STREAM, 0);
if (!IsOpen())
{
const int32_t error = GetLastNetworkError();
AZLOG_ERROR("Failed to create socket (%d:%s)", error, GetNetworkErrorDesc(error));
m_socketFd = InvalidSocketFd;
return false;
}
}
return true;
}
}
@@ -0,0 +1,95 @@
/*
* 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>
namespace AzNetworking
{
//! @class TcpSocket
//! @brief wrapper class for managing TCP sockets.
class TcpSocket
{
public:
TcpSocket();
//! Construct with an existing socket file descriptor.
//! @param socketFd existing socket file descriptor, this TcpSocket instance will assume ownership
TcpSocket(SocketFd socketFd);
virtual ~TcpSocket();
//! Creates a new socket instance, transferring all ownership from the current instance to the new instance.
//! @return new socket instance
virtual TcpSocket* CloneAndTakeOwnership();
//! Returns true if this is an encrypted socket, false if not.
//! @return boolean true if this is an encrypted socket, false if not
virtual bool IsEncrypted() const;
//! Opens the TCP socket and binds it in listen mode.
//! @param port the port number to open the TCP socket and begin listening on, 0 will bind to any available port
//! @return boolean true on success
virtual bool Listen(uint16_t port);
//! Opens the TCP socket and connects to the requested remote address.
//! @param address the remote endpoint to connect to
//! @return boolean true on success
virtual bool Connect(const IpAddress& address);
//! Closes an open socket.
virtual void Close();
//! Returns true if the socket is currently in an open state.
//! @return boolean true if the socket is in a connected state
bool IsOpen() const;
//! Sets the underlying socket file descriptor.
//! @param socketFd the new underlying socket file descriptor to use for this TcpSocket instance
void SetSocketFd(SocketFd socketFd);
//! Returns the underlying socket file descriptor.
//! @return the underlying socket file descriptor
SocketFd GetSocketFd() const;
//! Sends a chunk of data to the connected endpoint.
//! @param address the address to send the payload to
//! @param data pointer to the data to send
//! @param size size of the payload in bytes
//! @return number of bytes sent, <= 0 on error
int32_t Send(const uint8_t* data, uint32_t size) const;
//! Receives a payload from the TCP socket.
//! @param outAddress on success, the address of the endpoint that sent the data
//! @param outData on success, address to write the received data to
//! @param size maximum size the output buffer supports for receiving
//! @return number of bytes received, <= 0 on error
int32_t Receive(uint8_t* outData, uint32_t size) const;
protected:
virtual int32_t SendInternal(const uint8_t* data, uint32_t size) const;
virtual int32_t ReceiveInternal(uint8_t* outData, uint32_t size) const;
bool BindSocketForListenInternal(uint16_t port);
bool BindSocketForConnectInternal(const IpAddress& address);
bool SocketCreateInternal();
SocketFd m_socketFd;
};
}
#include <AzNetworking/TcpTransport/TcpSocket.inl>
@@ -0,0 +1,31 @@
/*
* 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 bool TcpSocket::IsOpen() const
{
return (m_socketFd > SocketFd{ 0 });
}
inline void TcpSocket::SetSocketFd(SocketFd socketFd)
{
m_socketFd = socketFd;
}
inline SocketFd TcpSocket::GetSocketFd() const
{
return m_socketFd;
}
}
@@ -0,0 +1,94 @@
/*
* 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/Platform.h>
#include <AzCore/Time/ITime.h>
#include <AzNetworking/AzNetworking_Traits_Platform.h>
#include <AzNetworking/Utilities/NetworkCommon.h>
#include <AzNetworking/Utilities/NetworkIncludes.h>
#if AZ_TRAIT_USE_SOCKET_SERVER_EPOLL
# include <sys/epoll.h>
#endif
namespace AzNetworking
{
//! @class TcpSocketManager
//! @brief internal helper implementation that manages basic details related to handling large numbers of TCP sockets efficiently.
class TcpSocketManager
{
public:
using SocketEventCallback = AZStd::function<void(SocketFd)>;
TcpSocketManager();
//! Adds the provided socket to the internal socket management mechanism.
//! @param socketFd the socket file descriptor to add
//! @return boolean true on success, false otherwise
bool AddSocket(SocketFd socketFd);
//! Removes the requested socket from the internal socket management mechanism.
//! @param socketFd the socket file descriptor to remove
//! @return boolean true on success, false otherwise
bool ClearSocket(SocketFd socketFd);
//! Processes any pending events for the set of sockets currently managed by this instance.
//! @param maxBlockMs the maximum milliseconds to block while gathering events
//! @param readCallback functor to invoke if a socket has pending data to read
//! @param writeCallback functor to invoke if a socket is ready for writing
void ProcessEvents(AZ::TimeMs maxBlockMs, const SocketEventCallback& readCallback, const SocketEventCallback& writeCallback);
private:
//! Internal helper for adding a socketFd to the socket manager
//! @param socketFd the socket file descriptor to add
void AddSocketHelper(SocketFd socketFd);
//! Internal helper for removing a socketFd from the socket manager
//! @param socketFd the socket file descriptor to remove
void ClearSocketHelper(SocketFd socketFd);
AZ_DISABLE_COPY_MOVE(TcpSocketManager);
#if AZ_TRAIT_USE_SOCKET_SERVER_EPOLL
SocketFd m_epollFd = InvalidSocketFd;
#elif AZ_TRAIT_USE_SOCKET_SERVER_SELECT
fd_set m_sourceFdSet;
fd_set m_readerFdSet;
fd_set m_writerFdSet;
SocketFd m_maxFd = SocketFd{ 0 };
#endif
AZStd::vector<SocketFd> m_socketFds;
};
inline void TcpSocketManager::AddSocketHelper(SocketFd socketFd)
{
auto element = AZStd::find(m_socketFds.begin(), m_socketFds.end(), socketFd);
if (element == m_socketFds.end())
{
m_socketFds.push_back(socketFd);
}
}
inline void TcpSocketManager::ClearSocketHelper(SocketFd socketFd)
{
auto element = AZStd::find(m_socketFds.begin(), m_socketFds.end(), socketFd);
if (element != m_socketFds.end())
{
m_socketFds.erase(element);
}
}
}
@@ -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.
*
*/
#include <AzNetworking/TcpTransport/TcpSocketManager.h>
#include <AzCore/Console/ILogger.h>
#if AZ_TRAIT_USE_SOCKET_SERVER_EPOLL
namespace AzNetworking
{
static constexpr uint32_t MaxEpollEvents = 256;
TcpSocketManager::TcpSocketManager()
// Don't propagate fd's to child processes, not that we should ever be spawning children
: m_epollFd(static_cast<SocketFd>(epoll_create1(EPOLL_CLOEXEC)))
{
if (m_epollFd == InvalidSocketFd)
{
const int32_t error = GetLastNetworkError();
AZLOG_ERROR("Failed to create epollFd, terminating application (%d:%s)", error, GetNetworkErrorDesc(error));
AZ_Assert(false, "Failed to create epollFd, terminating application");
exit(EXIT_FAILURE);
}
}
bool TcpSocketManager::AddSocket(SocketFd socketFd)
{
if (socketFd < SocketFd{ 0 })
{
return false;
}
struct epoll_event fdEvents;
fdEvents.events = EPOLLIN | EPOLLOUT | EPOLLET;
fdEvents.data.fd = static_cast<int32_t>(socketFd);
if (epoll_ctl(static_cast<int32_t>(m_epollFd), EPOLL_CTL_ADD, static_cast<int32_t>(socketFd), &fdEvents) < 0)
{
const int32_t error = GetLastNetworkError();
AZLOG_ERROR("Call to epoll_ctl to bind socket failed (%d:%s)", error, GetNetworkErrorDesc(error));
return false;
}
AddSocketHelper(socketFd);
return true;
}
bool TcpSocketManager::ClearSocket(SocketFd socketFd)
{
ClearSocketHelper(socketFd);
return true;
}
void TcpSocketManager::ProcessEvents(AZ::TimeMs maxBlockMs, const SocketEventCallback& readCallback, const SocketEventCallback& writeCallback)
{
struct epoll_event socketEvents[MaxEpollEvents];
const int32_t numEpollEvents = epoll_wait(static_cast<int32_t>(m_epollFd), socketEvents, MaxEpollEvents, -1);
if (numEpollEvents < 0)
{
const int32_t error = GetLastNetworkError();
AZLOG_ERROR("epoll_wait returned an error (%d:%s)", error, GetNetworkErrorDesc(error));
}
if (numEpollEvents > 0)
{
for (int32_t event = 0; event < numEpollEvents; ++event)
{
const SocketFd socketFd = static_cast<SocketFd>(socketEvents[event].data.fd);
if (socketEvents[event].events & EPOLLIN)
{
readCallback(socketFd);
}
if (socketEvents[event].events & EPOLLOUT)
{
writeCallback(socketFd);
}
}
}
}
}
#endif
@@ -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.
*
*/
#include <AzNetworking/TcpTransport/TcpSocketManager.h>
#include <AzCore/Console/ILogger.h>
#if !AZ_TRAIT_USE_SOCKET_SERVER_EPOLL && !AZ_TRAIT_USE_SOCKET_SERVER_SELECT
namespace AzNetworking
{
TcpSocketManager::TcpSocketManager()
{
;
}
bool TcpSocketManager::AddSocket(SocketFd socketFd)
{
AddSocketHelper(socketFd);
return true;
}
bool TcpSocketManager::ClearSocket(SocketFd socketFd)
{
ClearSocketHelper(socketFd);
return true;
}
void TcpSocketManager::ProcessEvents(AZ::TimeMs maxBlockMs, const SocketEventCallback& readCallback, const SocketEventCallback& writeCallback)
{
// No edge triggering, just brute force iterate all socketFds and invoke the callbacks
for (auto socketFd : m_socketFds)
{
readCallback(socketFd);
writeCallback(socketFd);
}
}
}
#endif
@@ -0,0 +1,77 @@
/*
* 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/TcpTransport/TcpSocketManager.h>
#include <AzCore/Console/ILogger.h>
#if AZ_TRAIT_USE_SOCKET_SERVER_SELECT
namespace AzNetworking
{
TcpSocketManager::TcpSocketManager()
{
FD_ZERO(&m_sourceFdSet);
FD_ZERO(&m_readerFdSet);
FD_ZERO(&m_writerFdSet);
}
bool TcpSocketManager::AddSocket(SocketFd socketFd)
{
if (socketFd <= SocketFd{ 0 })
{
return false;
}
FD_SET(static_cast<int32_t>(socketFd), &m_sourceFdSet);
m_maxFd = AZStd::max<SocketFd>(m_maxFd, socketFd);
AddSocketHelper(socketFd);
return true;
}
bool TcpSocketManager::ClearSocket(SocketFd socketFd)
{
FD_CLR(static_cast<int32_t>(socketFd), &m_sourceFdSet);
ClearSocketHelper(socketFd);
return true;
}
void TcpSocketManager::ProcessEvents(AZ::TimeMs maxBlockMs, const SocketEventCallback& readCallback, const SocketEventCallback& writeCallback)
{
m_readerFdSet = m_sourceFdSet;
m_writerFdSet = m_sourceFdSet;
struct timeval tv = { 0, static_cast<int32_t>(maxBlockMs) * 1000 };
const int32_t selectResult = ::select(static_cast<int32_t>(m_maxFd) + 1, &m_readerFdSet, &m_writerFdSet, nullptr, &tv);
if (selectResult < 0)
{
const int32_t error = GetLastNetworkError();
AZLOG_ERROR("select returned an error (%d:%s)", error, GetNetworkErrorDesc(error));
}
for (auto socketFd : m_socketFds)
{
// Sockets with pending data awaiting receipt
if (FD_ISSET(static_cast<int32_t>(socketFd), &m_readerFdSet))
{
readCallback(socketFd);
}
// Sockets with free space for sending data
if (FD_ISSET(static_cast<int32_t>(socketFd), &m_writerFdSet))
{
writeCallback(socketFd);
}
}
}
}
#endif
@@ -0,0 +1,233 @@
/*
* 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/TcpTransport/TlsSocket.h>
#include <AzCore/Console/ILogger.h>
#if AZ_TRAIT_USE_OPENSSL
# include <openssl/ssl.h>
# include <openssl/err.h>
#endif
namespace AzNetworking
{
TlsSocket::TlsSocket(TrustZone trustZone)
: TcpSocket()
, m_sslContext(nullptr)
, m_sslSocket(nullptr)
, m_trustZone(trustZone)
{
;
}
TlsSocket::TlsSocket(SocketFd socketFd, TrustZone trustZone)
: TcpSocket(socketFd)
, m_sslContext(nullptr)
, m_sslSocket(nullptr)
, m_trustZone(trustZone)
{
m_sslContext = CreateSslContext(SslContextType::TlsGeneric, trustZone);
if (m_sslContext == nullptr)
{
AZLOG_ERROR("Listen call failed, SSL context creation failed");
Close();
return;
}
m_sslSocket = CreateSslForAccept(m_socketFd, m_sslContext);
if (m_sslSocket == nullptr)
{
AZLOG_ERROR("Listen call failed, SSL socket wrapper creation failed");
Close();
return;
}
if (!(SetSocketNonBlocking(m_socketFd) && SetSocketNoDelay(m_socketFd)))
{
return;
}
}
TlsSocket::~TlsSocket()
{
FreeSslContext(m_sslContext);
AzNetworking::Close(m_sslSocket);
}
bool TlsSocket::IsEncrypted() const
{
return true;
}
TcpSocket* TlsSocket::CloneAndTakeOwnership()
{
TlsSocket* result = new TlsSocket(m_socketFd, m_trustZone);
result->m_sslContext = m_sslContext;
result->m_sslSocket = m_sslSocket;
m_socketFd = InvalidSocketFd;
m_sslContext = nullptr;
m_sslSocket = nullptr;
return result;
}
bool TlsSocket::Listen(uint16_t port)
{
Close();
m_sslContext = CreateSslContext(SslContextType::TlsServer, m_trustZone);
if (m_sslContext == nullptr)
{
AZLOG_ERROR("Listen call failed, SSL context creation failed");
Close();
return false;
}
if (!SocketCreateInternal())
{
Close();
return false;
}
if (!BindSocketForListenInternal(port))
{
Close();
return false;
}
m_sslSocket = CreateSslForAccept(GetSocketFd(), m_sslContext);
if (m_sslSocket == nullptr)
{
AZLOG_ERROR("Listen call failed, SSL socket wrapper creation failed");
Close();
return false;
}
if (!(SetSocketNonBlocking(m_socketFd) && SetSocketNoDelay(m_socketFd)))
{
Close();
return false;
}
return true;
}
bool TlsSocket::Connect(const IpAddress& address)
{
Close();
m_sslContext = CreateSslContext(SslContextType::TlsClient, m_trustZone);
if (m_sslContext == nullptr)
{
AZLOG_ERROR("Listen call failed, SSL context creation failed");
Close();
return false;
}
if (!SocketCreateInternal())
{
Close();
return false;
}
if (!BindSocketForConnectInternal(address))
{
Close();
return false;
}
m_sslSocket = CreateSslForConnect(GetSocketFd(), m_sslContext);
if (m_sslSocket == nullptr)
{
AZLOG_ERROR("Listen call failed, SSL socket wrapper creation failed");
Close();
return false;
}
if (!(SetSocketNonBlocking(m_socketFd) && SetSocketNoDelay(m_socketFd)))
{
Close();
return false;
}
return true;
}
void TlsSocket::Close()
{
FreeSslContext(m_sslContext);
AzNetworking::Close(m_sslSocket);
TcpSocket::Close();
}
int32_t TlsSocket::SendInternal(const uint8_t* data, uint32_t size) const
{
if (m_sslSocket == nullptr)
{
AZLOG_ERROR("Trying to send on an open socketfd, but with a nullptr ssl socket wrapper!");
return SocketOpResultErrorNoSsl;
}
#if AZ_TRAIT_USE_OPENSSL
const int32_t sentBytes = SSL_write(m_sslSocket, data, size);
if (sentBytes < 0)
{
const int32_t sslError = SSL_get_error(m_sslSocket, sentBytes);
if (SslErrorIsWouldBlock(sslError)) // Filter would block messages
{
return SocketOpResultSuccess;
}
const int32_t osError = GetLastNetworkError();
AZLOG_ERROR("Failed to read from socket (%d:%s) (%d:%s)", sslError, ERR_error_string(sslError, nullptr), osError, GetNetworkErrorDesc(osError));
}
return sentBytes;
#else
return 0;
#endif
}
int32_t TlsSocket::ReceiveInternal(uint8_t* outData, uint32_t size) const
{
if (m_sslSocket == nullptr)
{
AZLOG_ERROR("Trying to receive on an open socketfd, but with a nullptr ssl socket wrapper!");
return SocketOpResultErrorNoSsl;
}
#if AZ_TRAIT_USE_OPENSSL
int32_t receivedBytes = SSL_read(m_sslSocket, outData, size);
if (receivedBytes < 0)
{
const int32_t sslError = SSL_get_error(m_sslSocket, receivedBytes);
if (SslErrorIsWouldBlock(sslError)) // Filter would block messages
{
return SocketOpResultSuccess;
}
const int32_t osError = GetLastNetworkError();
AZLOG_ERROR("Failed to read from socket (%d:%s) (%d:%s)", sslError, ERR_error_string(sslError, nullptr), osError, GetNetworkErrorDesc(osError));
}
else if (receivedBytes == 0)
{
// Clean disconnect, force the endpoint to disconnect and cleanup
return SocketOpResultDisconnected;
}
return receivedBytes;
#else
return 0;
#endif
}
}
@@ -0,0 +1,67 @@
/*
* 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/TcpTransport/TcpSocket.h>
#include <AzNetworking/Utilities/EncryptionCommon.h>
namespace AzNetworking
{
//! @class TlsSocket
//! @brief wrapper class for managing encrypted Tcp sockets.
class TlsSocket final
: public TcpSocket
{
public:
TlsSocket(TrustZone trustZone);
//! Construct with an existing socket file descriptor.
//! @param socketFd existing socket file descriptor, this TlsSocket instance will assume ownership
//! @param trustZone for encrypted connections, the level of trust we associate with this connection (internal or external)
TlsSocket(SocketFd socketFd, TrustZone trustZone);
~TlsSocket();
//! Returns true if this is an encrypted socket, false if not.
//! @return boolean true if this is an encrypted socket, false if not
bool IsEncrypted() const override;
//! Creates a new socket instance, transferring all ownership from the current instance to the new instance.
//! @return new socket instance
TcpSocket* CloneAndTakeOwnership() override;
//! Opens the TCP socket and binds it in listen mode.
//! @param port the port number to open the TCP socket and begin listening on, 0 will bind to any available port
//! @return boolean true on success
bool Listen(uint16_t port) override;
//! Opens the TCP socket and connects to the requested remote address.
//! @param address the remote endpoint to connect to
//! @return boolean true on success
bool Connect(const IpAddress& address) override;
//! Closes an open socket.
void Close() override;
protected:
int32_t SendInternal(const uint8_t* data, uint32_t size) const override;
int32_t ReceiveInternal(uint8_t* outData, uint32_t size) const override;
SSL_CTX* m_sslContext;
SSL* m_sslSocket;
TrustZone m_trustZone;
};
}
@@ -0,0 +1,234 @@
/*
* 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/UdpTransport/DtlsEndpoint.h>
#include <AzNetworking/UdpTransport/DtlsSocket.h>
#include <AzNetworking/Utilities/EncryptionCommon.h>
#include <AzNetworking/Utilities/NetworkIncludes.h>
#include <AzCore/Console/IConsole.h>
#include <AzCore/Console/ILogger.h>
#if AZ_TRAIT_USE_OPENSSL
# include <openssl/ssl.h>
# include <openssl/err.h>
#endif
namespace AzNetworking
{
AZ_CVAR(bool, net_UseDtlsCookies, false, nullptr, AZ::ConsoleFunctorFlags::Null, "Enables DTLS cookie exchange during the connection handshake");
DtlsEndpoint::DtlsEndpoint()
: m_state(HandshakeState::None)
, m_sslSocket(nullptr)
, m_readBio(nullptr)
, m_writeBio(nullptr)
{
;
}
DtlsEndpoint::~DtlsEndpoint()
{
Close(m_sslSocket); // Note this also closes any attached BIO instances
m_readBio = nullptr;
m_writeBio = nullptr;
}
DtlsEndpoint::ConnectResult DtlsEndpoint::Connect(const DtlsSocket& socket, const IpAddress& address, [[maybe_unused]] UdpPacketEncodingBuffer& outDtlsData)
{
const ConnectResult result = ConstructEndpointInternal(socket, address);
#if AZ_TRAIT_USE_OPENSSL
if (result != ConnectResult::Failed)
{
// This SSL should be configured to initiate connections
SSL_set_connect_state(m_sslSocket);
m_state = HandshakeState::Connecting;
return PerformHandshakeInternal(outDtlsData);
}
#endif
return result;
}
DtlsEndpoint::ConnectResult DtlsEndpoint::Accept(const DtlsSocket& socket, const IpAddress& address, const UdpPacketEncodingBuffer& dtlsData)
{
if (dtlsData.GetSize() <= 0)
{
AZLOG_WARN("Encryption is enabled on accepting endpoint, but connector provided an empty DTLS handshake blob. Check that encryption is properly disabled on *BOTH* endpoints");
return DtlsEndpoint::ConnectResult::Failed;
}
const ConnectResult result = ConstructEndpointInternal(socket, address);
#if AZ_TRAIT_USE_OPENSSL
if (result != ConnectResult::Failed)
{
// This SSL should be configured to accept connections
SSL_set_accept_state(m_sslSocket);
m_state = HandshakeState::Accepting;
const uint8_t* encryptedData = dtlsData.GetBuffer();
const uint32_t encryptedSize = dtlsData.GetSize();
BIO_write(m_readBio, encryptedData, encryptedSize);
return CompleteHandshake(socket);
}
#endif
return result;
}
bool DtlsEndpoint::IsConnecting() const
{
return ((m_state == HandshakeState::Connecting)
|| (m_state == HandshakeState::Accepting)
|| (m_state == HandshakeState::Failed)); // In all cases caller should call CompleteHandshake() next and check the return value
}
DtlsEndpoint::ConnectResult DtlsEndpoint::CompleteHandshake(const UdpSocket& socket)
{
UdpPacketEncodingBuffer responseData;
const ConnectResult result = PerformHandshakeInternal(responseData);
if ((result != ConnectResult::Failed) && (responseData.GetSize() > 0))
{
struct sockaddr_in dest;
memset(&dest, 0, sizeof(dest));
dest.sin_family = AF_INET;
dest.sin_addr.s_addr = m_address.GetAddress(ByteOrder::Network);
dest.sin_port = m_address.GetPort(ByteOrder::Network);
sendto(static_cast<int32_t>(socket.GetSocketFd()), reinterpret_cast<char*>(responseData.GetBuffer()), responseData.GetSize(), 0, (sockaddr*)&dest, sizeof(dest));
AZLOG(NET_DebugDtls, "Replying to DTLS handshake datagram, %u bytes", static_cast<int32_t>(responseData.GetSize()));
}
return result;
}
const uint8_t* DtlsEndpoint::DecodePacket
(
[[maybe_unused]] const UdpSocket& socket,
[[maybe_unused]] const uint8_t* encryptedData,
[[maybe_unused]] int32_t encryptedSize,
[[maybe_unused]] uint8_t* outDecodedData,
[[maybe_unused]] int32_t& outDecodedSize
)
{
if (m_sslSocket == nullptr)
{
// If the ssl socket is nullptr, it means encryption is not enabled, just passthrough the received data
outDecodedSize = encryptedSize;
return encryptedData;
}
#if AZ_TRAIT_USE_OPENSSL
BIO_write(m_readBio, encryptedData, encryptedSize);
if (IsConnecting())
{
CompleteHandshake(socket);
outDecodedSize = 0;
}
// CompleteHandshake() above may have failed and destroyed the SSL context, check here that state is valid so we don't crash on SSL_read
if (m_state != HandshakeState::Failed)
{
outDecodedSize = SSL_read(m_sslSocket, outDecodedData, encryptedSize);
}
#endif
return outDecodedData;
}
DtlsEndpoint::ConnectResult DtlsEndpoint::ConstructEndpointInternal([[maybe_unused]] const DtlsSocket& socket, [[maybe_unused]] const IpAddress& address)
{
if (m_sslSocket != nullptr)
{
AZLOG_WARN("An existing SSL socket was open during a call to connect, closing old socket");
Close(m_sslSocket); // Note this also closes any attached BIO instances
}
#if AZ_TRAIT_USE_OPENSSL
m_address = address;
m_sslSocket = SSL_new(socket.m_sslContext);
m_readBio = BIO_new(BIO_s_mem());
BIO_set_mem_eof_return(m_readBio, -1);
m_writeBio = BIO_new(BIO_s_mem());
BIO_set_mem_eof_return(m_writeBio, -1);
SSL_set_bio(m_sslSocket, m_readBio, m_writeBio);
if (net_UseDtlsCookies)
{
SSL_set_options(m_sslSocket, SSL_OP_COOKIE_EXCHANGE);
}
if (m_sslSocket == nullptr)
{
AZLOG_ERROR("SSL_new failed, could not create SSL socket wrapper instance");
PrintSslErrorStack();
return ConnectResult::Failed;
}
#endif
return ConnectResult::Pending;
}
DtlsEndpoint::ConnectResult DtlsEndpoint::PerformHandshakeInternal([[maybe_unused]] UdpPacketEncodingBuffer& outHandshakeData)
{
if (m_state == HandshakeState::Failed)
{
return ConnectResult::Failed;
}
ConnectResult connectResult = ConnectResult::Pending;
#if AZ_TRAIT_USE_OPENSSL
if (SSL_is_init_finished(m_sslSocket))
{
const char* stateString = GetEnumString(m_state);
AZLOG(NET_DebugDtls, "dtls handshake is completed, unblocking connection for game traffic, prior state: %s", stateString);
m_state = HandshakeState::Complete;
connectResult = ConnectResult::Complete;
}
ERR_clear_error();
const int32_t result = SSL_do_handshake(m_sslSocket);
if (result <= 0)
{
const int32_t error = SSL_get_error(m_sslSocket, result);
if ((error != SSL_ERROR_WANT_READ)
&& (error != SSL_ERROR_WANT_WRITE))
{
AZLOG_ERROR("SSL handshake negotiation failed (%d), terminating connection", error);
PrintSslErrorStack();
Close(m_sslSocket);
m_readBio = nullptr;
m_writeBio = nullptr;
m_state = HandshakeState::Failed;
connectResult = ConnectResult::Failed;
}
}
// Need to do this... connection negotiation may have left data in the write bio that we need to send out
if (BIO_ctrl_pending(m_writeBio) > 0)
{
const uint32_t maxBufferSize = outHandshakeData.GetCapacity();
outHandshakeData.Resize(maxBufferSize);
const int32_t dataSize = BIO_read(m_writeBio, outHandshakeData.GetBuffer(), maxBufferSize);
outHandshakeData.Resize(dataSize);
}
#else
connectResult = ConnectResult::Complete;
#endif
return connectResult;
}
const char* GetEnumString(DtlsEndpoint::HandshakeState value)
{
switch (value)
{
case DtlsEndpoint::HandshakeState::None:
return "None";
case DtlsEndpoint::HandshakeState::Connecting:
return "Connecting";
case DtlsEndpoint::HandshakeState::Accepting:
return "Accepting";
case DtlsEndpoint::HandshakeState::Complete:
return "Complete";
case DtlsEndpoint::HandshakeState::Failed:
return "Failed";
}
return "UNKNOWN";
}
}
@@ -0,0 +1,110 @@
/*
* 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/DataStructures/ByteBuffer.h>
// OpenSSL forward declarations
typedef struct ssl_st SSL;
typedef struct ssl_ctx_st SSL_CTX;
typedef struct bio_st BIO;
namespace AzNetworking
{
class UdpSocket;
class DtlsSocket;
//! @class DtlsEndpoint
//! @brief Helper class defining an encrypted DTLS endpoint.
//! Note that multiple connections are multiplexed onto a single DTLS socket
class DtlsEndpoint final
{
friend class DtlsSocket;
public:
enum class ConnectResult
{
Failed,
Pending,
Complete
};
enum class HandshakeState
{
None, // Not an active dtls endpoint, Connect has not been called
Connecting, // This is a connecting endpoint, initiating the connection
Accepting, // This is an accepting endpoint
Complete, // Handshake is complete, connection is established and encrypted
Failed // Handshake failed
};
DtlsEndpoint();
~DtlsEndpoint();
//! Opens a connection with the remote encrypted endpoint.
//! @param socket the dtls socket being used for data transmission
//! @param address the address of the remote endpoint being connected to
//! @param outDtlsData data buffer to store the dtls handshake packet
//! @return a connect result specifying whether the connection is still pending, failed, or complete
ConnectResult Connect(const DtlsSocket& socket, const IpAddress& address, UdpPacketEncodingBuffer& outDtlsData);
//! Accepts a connection from the remote encrypted endpoint.
//! @param socket the dtls socket being used for data transmission
//! @param address the address of the remote endpoint connecting to us
//! @param dtlsData data buffer containing the initial dtls handshake packet
//! @return a connect result specifying whether the connection is still pending, failed, or complete
ConnectResult Accept(const DtlsSocket& socket, const IpAddress& address, const UdpPacketEncodingBuffer& dtlsData);
//! Returns whether or not the endpoint is still negotiating the dtls handshake.
//! @return true if the endpoint is still in a connecting state
bool IsConnecting() const;
//! Attempts to complete the dtls handshake and establish an encrypted connection.
//! @param socket the dtls socket being used for data transmission
//! @return a connect result specifying whether the connection is still pending, failed, or complete
ConnectResult CompleteHandshake(const UdpSocket& socket);
//! If the endpoint has encryption enabled, this will decrypt the transmitted data and return the result.
//! @note sizes have to be signed since OpenSSL often returns negative values to represent error results
//! @param socket the DTLS socket being used for data transmission
//! @param encryptedData the potentially encrypted data received from the socket
//! @param encryptedSize the size of the received raw data
//! @param outDecodedData an appropriately sized output buffer to store decrypted data
//! @param outDecodedSize the size of the output buffer
//! @return pointer to the decoded data
const uint8_t* DecodePacket(const UdpSocket& socket, const uint8_t* encryptedData, int32_t encryptedSize, uint8_t* outDecodedData, int32_t& outDecodedSize);
private:
//! Performs internal common dtls endpoint setup.
//! @param socket the dtls socket being used for data transmission
//! @param address the address of the remote endpoint connecting to us
//! @return a connect result specifying whether the connection is still pending, failed, or complete
ConnectResult ConstructEndpointInternal(const DtlsSocket& socket, const IpAddress& address);
//! Attempts to complete the dtls handshake and establish an encrypted connection.
//! @param outHandshakeData buffer to store any required outgoing dtls handshake data
//! @return a connect result specifying whether the connection is still pending, failed, or complete
ConnectResult PerformHandshakeInternal(UdpPacketEncodingBuffer& outHandshakeData);
HandshakeState m_state;
IpAddress m_address;
SSL* m_sslSocket;
BIO* m_readBio;
BIO* m_writeBio;
};
const char* GetEnumString(DtlsEndpoint::HandshakeState value);
}
@@ -0,0 +1,99 @@
/*
* 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/UdpTransport/DtlsSocket.h>
#include <AzCore/Console/ILogger.h>
#if AZ_TRAIT_USE_OPENSSL
# include <openssl/ssl.h>
# include <openssl/err.h>
#endif
namespace AzNetworking
{
DtlsSocket::~DtlsSocket()
{
FreeSslContext(m_sslContext);
}
bool DtlsSocket::IsEncrypted() const
{
return true;
}
DtlsEndpoint::ConnectResult DtlsSocket::ConnectDtlsEndpoint(DtlsEndpoint& dtlsEndpoint, const IpAddress& address, UdpPacketEncodingBuffer& outDtlsData) const
{
return dtlsEndpoint.Connect(*this, address, outDtlsData);
}
DtlsEndpoint::ConnectResult DtlsSocket::AcceptDtlsEndpoint(DtlsEndpoint& dtlsEndpoint, const IpAddress& address, const UdpPacketEncodingBuffer& dtlsData) const
{
return dtlsEndpoint.Accept(*this, address, dtlsData);
}
bool DtlsSocket::Open(uint16_t port, CanAcceptConnections canAccept, TrustZone trustZone)
{
Close();
const SslContextType contextType = (canAccept == UdpSocket::CanAcceptConnections::True) ? SslContextType::DtlsGeneric : SslContextType::DtlsClient;
m_sslContext = CreateSslContext(contextType, trustZone);
if (m_sslContext == nullptr)
{
AZLOG_ERROR("SSL context creation call failed");
PrintSslErrorStack();
Close();
return false;
}
if (!UdpSocket::Open(port, canAccept, trustZone))
{
AZLOG_ERROR("UDP socket creation failed");
PrintSslErrorStack();
Close();
return false;
}
return true;
}
void DtlsSocket::Close()
{
FreeSslContext(m_sslContext);
UdpSocket::Close();
}
int32_t DtlsSocket::SendInternal(const IpAddress& address, const uint8_t* data, uint32_t size, bool encrypt, DtlsEndpoint& dtlsEndpoint) const
{
if (!encrypt)
{
// If the packet has requested to remain unencrypted then just send directly
return UdpSocket::SendInternal(address, data, size, encrypt, dtlsEndpoint);
}
if (dtlsEndpoint.m_sslSocket == nullptr)
{
AZLOG_ERROR("Trying to send on an open socketfd, but with a nullptr ssl socket wrapper!");
return SocketOpResultErrorNoSsl;
}
#if AZ_TRAIT_USE_OPENSSL
uint8_t encrpytedSendBuffer[MaxUdpTransmissionUnit];
// Write out the packet we were requested to send
const int32_t sentBytesRaw = SSL_write(dtlsEndpoint.m_sslSocket, data, size);
const int32_t sentBytesEnc = BIO_read(dtlsEndpoint.m_writeBio, encrpytedSendBuffer, sizeof(encrpytedSendBuffer));
return UdpSocket::SendInternal(address, encrpytedSendBuffer, sentBytesEnc, encrypt, dtlsEndpoint);
#else
return 0;
#endif
}
}
@@ -0,0 +1,69 @@
/*
* 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/UdpTransport/UdpSocket.h>
#include <AzNetworking/ConnectionLayer/IConnection.h>
#include <AzNetworking/Utilities/EncryptionCommon.h>
namespace AzNetworking
{
class DtlsEndpoint;
//! @class DtlsSocket
//! @brief wrapper class for managing encrypted Udp sockets.
class DtlsSocket final
: public UdpSocket
{
friend class DtlsEndpoint;
public:
DtlsSocket() = default;
~DtlsSocket();
//! Returns true if this is an encrypted socket, false if not.
//! @return boolean true if this is an encrypted socket, false if not
bool IsEncrypted() const override;
//! Creates an encryption socket wrapper.
//! @param dtlsEndpoint the encryption wrapper instance to create a connection over
//! @param address the IP address of the endpoint to connect to
//! @param outDtlsData data buffer to store the dtls handshake packet
//! @return a connect result specifying whether the connection is still pending, failed, or complete
DtlsEndpoint::ConnectResult ConnectDtlsEndpoint(DtlsEndpoint& dtlsEndpoint, const IpAddress& address, UdpPacketEncodingBuffer& outDtlsData) const override;
//! Accepts an encryption socket wrapper.
//! @param dtlsEndpoint the encryption wrapper instance to create a connection over
//! @param address the IP address of the endpoint to connect to
//! @param dtlsData data buffer containing the dtls handshake packet
//! @return a connect result specifying whether the connection is still pending, failed, or complete
DtlsEndpoint::ConnectResult AcceptDtlsEndpoint(DtlsEndpoint& dtlsEndpoint, const IpAddress& address, const UdpPacketEncodingBuffer& dtlsData) const override;
//! Opens the UDP socket on the given port.
//! @param port the port number to open the UDP socket on, 0 will bind to any available port
//! @param canAccept if true, the socket will be opened in a way that allows accepting incoming connections
//! @param trustZone for encrypted connections, the level of trust we associate with this connection (internal or external)
//! @return boolean true on success
bool Open(uint16_t port, UdpSocket::CanAcceptConnections canAccept, TrustZone trustZone) override;
//! Closes an open socket.
void Close() override;
private:
int32_t SendInternal(const IpAddress& address, const uint8_t* data, uint32_t size, bool encrypt, DtlsEndpoint& dtlsEndpoint) const override;
SSL_CTX* m_sslContext = nullptr;
};
}
@@ -0,0 +1,288 @@
/*
* 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/UdpTransport/UdpConnection.h>
#include <AzNetworking/UdpTransport/UdpPacketTracker.h>
#include <AzNetworking/UdpTransport/UdpNetworkInterface.h>
#include <AzNetworking/Serialization/NetworkInputSerializer.h>
#include <AzNetworking/Serialization/NetworkOutputSerializer.h>
#include <AzNetworking/Serialization/TrackChangedSerializer.h>
#include <AzNetworking/AutoGen/CorePackets.AutoPackets.h>
#include <AzNetworking/Utilities/NetworkCommon.h>
#include <AzCore/Console/IConsole.h>
#include <AzCore/Console/ILogger.h>
namespace AzNetworking
{
AZ_CVAR(uint32_t, net_UdpMaxUnackedPacketCount, 10, nullptr, AZ::ConsoleFunctorFlags::DontReplicate, "Maximum packets to receive before forcing a heartbeat packet for acking");
// Track every 8th packet to determine Rtt
// Only reason we're doing every 8th packet instead of every packet is to reduce per-packet overhead
static const uint32_t PacketRttMask = 0x07;
static_assert(AZ::IsPowerOfTwo(PacketRttMask + 1), "Sequence mask should be of the form 2^N - 1");
static bool IncludePacketInRtt(PacketId packetId)
{
return ((static_cast<uint32_t>(packetId) & PacketRttMask) == 0);
}
const char* GetEnumString(PacketTimeoutResult value)
{
switch (value)
{
case PacketTimeoutResult::Acked:
return "PacketTimeoutResult::Acked";
case PacketTimeoutResult::Lost:
return "PacketTimeoutResult::Lost";
case PacketTimeoutResult::Pending:
return "PacketTimeoutResult::Pending";
}
return "INVALID";
}
UdpConnection::UdpConnection(ConnectionId connectionId, const IpAddress& remoteAddress, UdpNetworkInterface& networkInterface, ConnectionRole connectionRole)
: IConnection(connectionId, remoteAddress)
, m_networkInterface(networkInterface)
, m_lastSentPacketMs(AZ::GetElapsedTimeMs())
, m_connectionRole(connectionRole)
{
;
}
UdpConnection::~UdpConnection()
{
if (m_state == ConnectionState::Connected)
{
m_networkInterface.GetConnectionListener().OnDisconnect(this, DisconnectReason::ConnectionDeleted, TerminationEndpoint::Local);
}
}
DtlsEndpoint::ConnectResult UdpConnection::CompleteHandshake()
{
const DtlsEndpoint::ConnectResult result = m_dtlsEndpoint.CompleteHandshake(*(m_networkInterface.m_socket));
if (result == DtlsEndpoint::ConnectResult::Failed)
{
Disconnect(DisconnectReason::NetworkError, TerminationEndpoint::Local);
}
return result;
}
void UdpConnection::UpdateHeartbeat([[maybe_unused]] AZ::TimeMs currentTimeMs)
{
if (m_unackedPacketCount >= net_UdpMaxUnackedPacketCount)
{
AZLOG(NET_Acks, "Unacked packet count exceeded, sending client heartbeat (curr %u : max %u)", m_unackedPacketCount, static_cast<uint32_t>(net_UdpMaxUnackedPacketCount));
// This simply times out unreliable chunks that haven't completed within our timeout delay
m_fragmentQueue.Update();
SendUnreliablePacket(CorePackets::HeartbeatPacket());
}
}
bool UdpConnection::SendReliablePacket(const IPacket& packet)
{
const SequenceId reliableSequenceId = m_reliableQueue.GetNextSequenceId();
return (m_networkInterface.SendPacket(*this, packet, reliableSequenceId) != InvalidPacketId);
}
PacketId UdpConnection::SendUnreliablePacket(const IPacket& packet)
{
return m_networkInterface.SendPacket(*this, packet, InvalidSequenceId);
}
bool UdpConnection::WasPacketAcked(PacketId packetId) const
{
return m_packetTracker.GetPacketAckStatus(packetId) == PacketAckState::Acked;
}
ConnectionState UdpConnection::GetConnectionState() const
{
return m_state;
}
ConnectionRole UdpConnection::GetConnectionRole() const
{
return m_connectionRole;
}
bool UdpConnection::Disconnect(DisconnectReason reason, TerminationEndpoint endpoint)
{
if (m_state == ConnectionState::Disconnecting)
{
AZStd::string reasonString = ToString(reason);
AZLOG_ERROR("Disconnecting an already disconnecting connection due to %s", reasonString.c_str());
return false;
}
m_state = ConnectionState::Disconnecting;
if (endpoint == TerminationEndpoint::Local
&& reason != DisconnectReason::NetworkError
&& reason != DisconnectReason::DtlsHandshakeError
&& reason != DisconnectReason::Unknown
&& reason != DisconnectReason::RemoteHostClosedConnection
&& reason != DisconnectReason::TransportError
&& reason != DisconnectReason::SslFailure)
{
// If disconnect initiated from Local, inform the remote endpoint
CorePackets::TerminateConnectionPacket terminationPacket(reason);
SendUnreliablePacket(terminationPacket);
}
m_networkInterface.RequestDisconnect(this, reason, endpoint);
return true;
}
void UdpConnection::SetConnectionMtu(uint32_t connectionMtu)
{
m_connectionMtu = connectionMtu;
}
uint32_t UdpConnection::GetConnectionMtu() const
{
return m_connectionMtu;
}
void UdpConnection::ProcessAcked(PacketId packetId, AZ::TimeMs currentTimeMs)
{
GetMetrics().m_packetsAcked++;
m_reliableQueue.OnPacketAcked(m_networkInterface, *this, packetId);
// Compute Rtt adjustments
if (IncludePacketInRtt(packetId))
{
GetMetrics().m_connectionRtt.LogPacketAcked(packetId, currentTimeMs);
}
}
void UdpConnection::ProcessSent(PacketId packetId, [[maybe_unused]] const IPacket& packet,
uint32_t packetSize, [[maybe_unused]] ReliabilityType reliability)
{
const AZ::TimeMs currentTimeMs = AZ::GetElapsedTimeMs();
if (IncludePacketInRtt(packetId))
{
GetMetrics().m_connectionRtt.LogPacketSent(packetId, currentTimeMs);
}
GetMetrics().m_packetsSent++;
GetMetrics().m_sendDatarate.LogPacket(packetSize, currentTimeMs);
m_lastSentPacketMs = currentTimeMs;
m_unackedPacketCount = 0;
}
PacketTimeoutResult UdpConnection::ProcessTimeout(PacketId packetId, ReliabilityType reliability)
{
if (IncludePacketInRtt(packetId))
{
GetMetrics().m_connectionRtt.LogPacketTimeout(packetId);
}
const PacketAckState ackState = m_packetTracker.GetPacketAckStatus(packetId);
switch (ackState)
{
case PacketAckState::Acked:
return PacketTimeoutResult::Acked;
case PacketAckState::Nacked:
GetMetrics().m_packetsLost++;
if (reliability == ReliabilityType::Reliable)
{
m_reliableQueue.OnPacketLost(m_networkInterface, *this, packetId);
}
return PacketTimeoutResult::Lost;
case PacketAckState::Unknown_TooNew:
return PacketTimeoutResult::Pending;
case PacketAckState::Unknown_TooOld:
// TODO: Disconnect?
AZLOG_ERROR("PacketId %u timeout fell outside the ack history window", static_cast<uint32_t>(packetId));
break;
default:
AZLOG_ERROR("PacketId %u ack state was unhandled (%s)", static_cast<uint32_t>(packetId), GetEnumString(ackState));
break;
}
return PacketTimeoutResult::Lost;
}
bool UdpConnection::ProcessReceived(UdpPacketHeader& header, [[maybe_unused]] const NetworkOutputSerializer& serializer,
uint32_t packetSize, AZ::TimeMs currentTimeMs)
{
if (!m_packetTracker.ProcessReceived(this, header))
{
return false;
}
GetMetrics().m_packetsRecv++;
GetMetrics().m_recvDatarate.LogPacket(packetSize, currentTimeMs);
if (header.GetIsReliable() && !m_reliableQueue.OnPacketReceived(header))
{
return false;
}
m_unackedPacketCount++;
UpdateHeartbeat(currentTimeMs);
return true;
}
bool UdpConnection::HandleCorePacket(IConnectionListener& connectionListener, UdpPacketHeader& header, ISerializer& serializer)
{
switch (static_cast<CorePackets::PacketType>(header.GetPacketType()))
{
case CorePackets::PacketType::InitiateConnectionPacket:
{
AZLOG(NET_CorePackets, "Received core packet %s", "InitiateConnection");
return true;
}
break;
case CorePackets::PacketType::TerminateConnectionPacket:
{
AZLOG(NET_CorePackets, "Received core packet %s", "TerminateConnection");
CorePackets::TerminateConnectionPacket packet;
if (!serializer.Serialize(packet, "Packet"))
{
return false;
}
Disconnect(packet.GetDisconnectReason(), TerminationEndpoint::Remote);
return true;
}
break;
case CorePackets::PacketType::HeartbeatPacket:
{
AZLOG(NET_CorePackets, "Received core packet %s", "Heartbeat");
CorePackets::HeartbeatPacket packet;
if (!serializer.Serialize(packet, "Packet"))
{
return false;
}
// Do nothing, we've already processed our ack packets
return true;
}
break;
case CorePackets::PacketType::FragmentedPacket:
AZLOG(NET_CorePackets, "Received core packet %s", "Fragment");
return m_fragmentQueue.ProcessReceivedChunk(this, connectionListener, header, serializer);
default:
AZ_Assert(false, "Unhandled core packet type!");
}
return false;
}
}
@@ -0,0 +1,165 @@
/*
* 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/ConnectionLayer/IConnection.h>
#include <AzNetworking/ConnectionLayer/IConnectionListener.h>
#include <AzNetworking/UdpTransport/DtlsEndpoint.h>
#include <AzNetworking/UdpTransport/UdpPacketTracker.h>
#include <AzNetworking/UdpTransport/UdpReliableQueue.h>
#include <AzNetworking/UdpTransport/UdpFragmentQueue.h>
#include <AzCore/Console/ILogger.h>
namespace AzNetworking
{
// Forwards
class UdpPacketHeader;
enum class PacketTimeoutResult
{
Acked,
Lost,
Pending
};
const char* GetEnumString(PacketTimeoutResult value);
//! @class UdpConnection
//! @brief Connection class for udp endpoints.
class UdpConnection
: public IConnection
{
friend class UdpNetworkInterface;
public:
//! Constructor
//! @param connectionId the connection identifier to use for this connection
//! @param remoteAddress the remote address this connection
//! @param networkInterface reference of the network interface that owns this connection instance
//! @param connectionRole whether this connection was the connector or acceptor
UdpConnection(ConnectionId connectionId, const IpAddress& remoteAddress, UdpNetworkInterface& networkInterface, ConnectionRole connectionRole);
~UdpConnection() override;
//! Helper to complete dtls handshake logic on a newly established connection
//! @return the current result code for the dtls handshake operation, failed, pending, or complete
DtlsEndpoint::ConnectResult CompleteHandshake();
//! Updates the connection heartbeat if active.
//! @param currentTimeMs current wall clock time in milliseconds
void UpdateHeartbeat(AZ::TimeMs currentTimeMs);
//! IConnection interface.
// @{
bool SendReliablePacket(const IPacket& packet) override;
PacketId SendUnreliablePacket(const IPacket& packet) override;
bool WasPacketAcked(PacketId packetId) const override;
ConnectionState GetConnectionState() const override;
ConnectionRole GetConnectionRole() const override;
bool Disconnect(DisconnectReason reason, TerminationEndpoint endpoint) override;
void SetConnectionMtu(uint32_t connectionMtu) override;
uint32_t GetConnectionMtu() const override;
void SetConnectionQuality(const ConnectionQuality& connectionQuality) override;
// @}
//! Gets connection quality values for testing poor connection conditions.
//! @return connection quality values for this IConnection instance
const ConnectionQuality& GetConnectionQuality() const;
//! Returns a suitable encryption endpoint for this connection type.
//! @return reference to the connections encryption endpoint
DtlsEndpoint& GetDtlsEndpoint();
//! Retrieves packet delivery tracker instance for the specified connection.
//! @return reference to the requested packet tracker instance
const UdpPacketTracker& GetPacketTracker() const;
//! Retrieves packet delivery tracker instance for the specified connection.
//! @return reference to the requested packet tracker instance
UdpPacketTracker& GetPacketTracker();
//! Returns the number of unacked reliable messages still pending in the reliable queue.
//! @return the number of unacked reliable messages still pending in the reliable queue
uint32_t GetReliableQueueSize() const;
//! Acks a packetId.
//! @param packetId the PacketId of the packet being acked
//! @param currentTimeMs current wall clock time in milliseconds
void ProcessAcked(PacketId packetId, AZ::TimeMs currentTimeMs);
//! Sets the timeout identifier for this connection instance.
//! @param timeoutId the timeoutId to use for this connection instance
void SetTimeoutId(TimeoutId timeoutId);
//! Retrieves the timeout identifier for this connection instance.
//! @return the timeout identifier for this connection instance
TimeoutId GetTimeoutId() const;
protected:
//! Prepare a reliable packet for transmission.
//! @param packetId identifier of the packet being sent
//! @param packet reference to the packet being transmitted
//! @return boolean true on success, false on failure
bool PrepareReliablePacketForSend(PacketId packetId, SequenceId reliableSequenceId, const IPacket& packet);
//! Process a packet for sending.
//! @param packetId identifier of the packet being sent
//! @param packet reference to the packet being transmitted
//! @param packetSize packet size in bytes
//! @param reliability whether or not to guarantee delivery
void ProcessSent(PacketId packetId, const IPacket& packet, uint32_t packetSize, ReliabilityType reliability);
//! Process a timed out packet header.
//! @param packetId identifier of the packet that timed out
//! @param reliability whether or not the packet that timed out was marked reliable
//! @return PacketTimeoutResult::Acked if the packet was confirmed to be received prior to timeout, PacketTimeoutResult::Lost if not
PacketTimeoutResult ProcessTimeout(PacketId packetId, ReliabilityType reliability);
//! Process a received packet header.
//! @param header the packet header received to process
//! @param serializer the output serializer containing the transmitted packet data
//! @param packetSize the size of the received packet in bytes
//! @param currentTimeMs current wall clock time in milliseconds
//! @return boolean true on successful handling of the received header
bool ProcessReceived(UdpPacketHeader& header, const NetworkOutputSerializer& serializer, uint32_t packetSize, AZ::TimeMs currentTimeMs);
//! Handle a core network packet.
//! @param listener a connection listener to receive connection related events
//! @param header the packet header received to process
//! @param serializer the output serializer containing the transmitted packet data
//! @return boolean true on successful handling of the received header
bool HandleCorePacket(IConnectionListener& listener, UdpPacketHeader& header, ISerializer& serializer);
AZ_DISABLE_COPY_MOVE(UdpConnection);
UdpNetworkInterface& m_networkInterface;
UdpPacketTracker m_packetTracker;
UdpReliableQueue m_reliableQueue;
UdpFragmentQueue m_fragmentQueue;
ConnectionState m_state = ConnectionState::Disconnected;
ConnectionRole m_connectionRole = ConnectionRole::Connector;
ConnectionQuality m_connectionQuality;
DtlsEndpoint m_dtlsEndpoint;
AZ::TimeMs m_lastSentPacketMs;
uint32_t m_unackedPacketCount = 0;
uint32_t m_connectionMtu = MaxUdpTransmissionUnit;
TimeoutId m_timeoutId;
uint32_t m_timeoutCounter = 0;
};
}
#include <AzNetworking/UdpTransport/UdpConnection.inl>
@@ -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
namespace AzNetworking
{
inline void UdpConnection::SetConnectionQuality(const ConnectionQuality& connectionQuality)
{
m_connectionQuality = connectionQuality;
}
inline const ConnectionQuality& UdpConnection::GetConnectionQuality() const
{
return m_connectionQuality;
}
inline DtlsEndpoint& UdpConnection::GetDtlsEndpoint()
{
return m_dtlsEndpoint;
}
inline const UdpPacketTracker& UdpConnection::GetPacketTracker() const
{
return m_packetTracker;
}
inline UdpPacketTracker& UdpConnection::GetPacketTracker()
{
return m_packetTracker;
}
inline uint32_t UdpConnection::GetReliableQueueSize() const
{
return m_reliableQueue.GetQueueSize();
}
inline void UdpConnection::SetTimeoutId(TimeoutId timeoutId)
{
m_timeoutId = timeoutId;
}
inline TimeoutId UdpConnection::GetTimeoutId() const
{
return m_timeoutId;
}
inline bool UdpConnection::PrepareReliablePacketForSend(PacketId packetId, SequenceId reliableSequenceId, const IPacket& packet)
{
return m_reliableQueue.PrepareForSend(packetId, reliableSequenceId, packet);
}
}
@@ -0,0 +1,123 @@
/*
* 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/UdpTransport/UdpConnectionSet.h>
#include <AzNetworking/UdpTransport/UdpConnection.h>
namespace AzNetworking
{
bool UdpConnectionSet::AddConnection(AZStd::unique_ptr<UdpConnection> connection)
{
AZ_Assert(connection, "Adding a nullptr UdpConnection instance to the connection set");
if (!connection)
{
return false;
}
AZLOG(UdpConnectionSet, "Adding new Udp connection (%u : %s)",
aznumeric_cast<uint32_t>(connection->GetConnectionId()),
connection->GetRemoteAddress().GetString().c_str()
);
// Check for errors here, don't want to clobber an existing connection...
AZ_Assert(GetConnection(connection->GetConnectionId()) == nullptr, "ConnectionId already exists in connection set");
AZ_Assert(GetConnection(connection->GetRemoteAddress()) == nullptr, "Remote address already exists in connection set");
m_remoteAddressMap[connection->GetRemoteAddress()] = connection.get();
m_connectionIdMap[connection->GetConnectionId()] = AZStd::move(connection);
return true;
}
bool UdpConnectionSet::DeleteConnection(const IpAddress& address)
{
AZLOG(UdpConnectionSet, "Deleting Udp connection by remote address (%s)", address.GetString().c_str());
UdpConnection* connection = GetConnection(address);
if (connection == nullptr)
{
return false;
}
AZLOG(UdpConnectionSet, "Deleting Udp connection (%u : %s)",
aznumeric_cast<uint32_t>(connection->GetConnectionId()),
connection->GetRemoteAddress().GetString().c_str()
);
AZ_Assert(connection->GetRemoteAddress() == address, "Connection list is corrupt, mismatched remote endpoint addresses detected");
m_remoteAddressMap.erase(connection->GetRemoteAddress());
m_connectionIdMap.erase(connection->GetConnectionId());
return true;
}
void UdpConnectionSet::VisitConnections(const ConnectionVisitor& visitor)
{
for (auto& connection : m_connectionIdMap)
{
visitor(*connection.second);
}
}
bool UdpConnectionSet::DeleteConnection(ConnectionId connectionId)
{
AZLOG(UdpConnectionSet, "Deleting Udp connection by connectionId (%u)", aznumeric_cast<uint32_t>(connectionId));
UdpConnection* connection = static_cast<UdpConnection*>(GetConnection(connectionId));
if (connection == nullptr)
{
return false;
}
AZLOG(UdpConnectionSet, "Deleting Udp connection (%u : %s)",
aznumeric_cast<uint32_t>(connectionId),
connection->GetRemoteAddress().GetString().c_str()
);
AZ_Assert(connection->GetConnectionId() == connectionId, "Connection list is corrupt, mismatched connection identifiers detected");
m_remoteAddressMap.erase(connection->GetRemoteAddress());
m_connectionIdMap.erase(connectionId);
return true;
}
IConnection* UdpConnectionSet::GetConnection(ConnectionId connectionId) const
{
ConnectionIdMap::const_iterator lookup = m_connectionIdMap.find(connectionId);
if (lookup != m_connectionIdMap.end())
{
return lookup->second.get();
}
return nullptr;
}
ConnectionId UdpConnectionSet::GetNextConnectionId()
{
// In the case of wrap-around, don't return a connectionId that's in-use or is the invalid connection Id
do
{
++m_nextConnectionId;
if (m_nextConnectionId == InvalidConnectionId)
{
m_nextConnectionId = ConnectionId(0);
}
} while (m_connectionIdMap.count(m_nextConnectionId) > 0);
return m_nextConnectionId;
}
uint32_t UdpConnectionSet::GetConnectionCount() const
{
return aznumeric_cast<uint32_t>(m_connectionIdMap.size());
}
UdpConnection* UdpConnectionSet::GetConnection(const IpAddress& address) const
{
RemoteAddressMap::const_iterator lookup = m_remoteAddressMap.find(address);
if (lookup != m_remoteAddressMap.end())
{
return lookup->second;
}
return nullptr;
}
}
@@ -0,0 +1,66 @@
/*
* 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/ConnectionLayer/IConnectionSet.h>
#include <AzCore/std/containers/unordered_map.h>
namespace AzNetworking
{
class UdpConnection;
//! @class UdpConnectionSet
//! @brief Tracks current UDP endpoints and allows fast lookups by connection identifier and remote address.
class UdpConnectionSet final
: public IConnectionSet
{
public:
using ConnectionIdMap = AZStd::unordered_map<ConnectionId, AZStd::unique_ptr<UdpConnection>>;
using RemoteAddressMap = AZStd::unordered_map<IpAddress, UdpConnection*>;
UdpConnectionSet() = default;
~UdpConnectionSet() override = default;
//! Adds a new connection to this connection list instance.
//! @param connection pointer to the connection instance to add
//! @return boolean true on success
bool AddConnection(AZStd::unique_ptr<UdpConnection> connection);
//! Deletes a connection from this connection list instance by endpoint remote address.
//! @param address address of the remote endpoint to delete
//! @return boolean true on success
bool DeleteConnection(const IpAddress& address);
//! IConnectionSet interface.
//! @{
void VisitConnections(const ConnectionVisitor& visitor) override;
bool DeleteConnection(ConnectionId connectionId) override;
IConnection* GetConnection(ConnectionId connectionId) const override;
ConnectionId GetNextConnectionId() override;
uint32_t GetConnectionCount() const override;
//! @}
//! Retrieves a connection from this connection list instance by endpoint remote address
//! @param address address of the remote endpoint of the connection to retrieve
//! @return pointer to the requested connection instance on success, nullptr on failure
UdpConnection* GetConnection(const IpAddress& address) const;
private:
ConnectionId m_nextConnectionId = InvalidConnectionId;
ConnectionIdMap m_connectionIdMap;
RemoteAddressMap m_remoteAddressMap;
};
}
@@ -0,0 +1,168 @@
/*
* 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/UdpTransport/UdpFragmentQueue.h>
#include <AzNetworking/UdpTransport/UdpConnection.h>
#include <AzNetworking/UdpTransport/UdpPacketHeader.h>
#include <AzNetworking/Serialization/NetworkOutputSerializer.h>
#include <AzNetworking/Utilities/NetworkCommon.h>
#include <AzCore/Console/IConsole.h>
#include <AzCore/Console/ILogger.h>
namespace AzNetworking
{
AZ_CVAR(AZ::TimeMs, net_UdpFragmentTimeoutMs, AZ::TimeMs{ 5000 }, nullptr, AZ::ConsoleFunctorFlags::Null, "Milliseconds to retain chunks of incomplete unreliable fragmented packets before timing them out");
void UdpFragmentQueue::Update()
{
m_timeoutQueue.UpdateTimeouts(*this);
}
void UdpFragmentQueue::Reset()
{
m_timeoutQueue.Reset();
m_sequenceGenerator.Reset();
m_packetFragments.clear();
m_latestReceivedFragmentSequence = InvalidSequenceId;
m_deliveredFragments.Reset();
}
SequenceId UdpFragmentQueue::GetNextFragmentedSequenceId()
{
return m_sequenceGenerator.GetNextSequenceId();
}
bool UdpFragmentQueue::ProcessReceivedChunk(UdpConnection* connection, IConnectionListener& connectionListener, UdpPacketHeader& header, ISerializer& serializer)
{
AZStd::unique_ptr<CorePackets::FragmentedPacket> packet = AZStd::make_unique<CorePackets::FragmentedPacket>();
if (!serializer.Serialize(*packet, "Packet"))
{
AZLOG(NET_FragmentQueue, "Fragment failed serialization");
return false;
}
const bool isReliable = header.GetIsReliable();
const SequenceId fragmentSequence = packet->GetFragmentSequence();
if (SequenceMoreRecent(fragmentSequence, m_latestReceivedFragmentSequence))
{
const SequenceId sequenceDelta = SequenceId(fragmentSequence - m_latestReceivedFragmentSequence);
m_latestReceivedFragmentSequence = fragmentSequence;
m_deliveredFragments.PushBackBits(static_cast<uint32_t>(sequenceDelta));
}
const SequenceId sequenceDelta = SequenceId(m_latestReceivedFragmentSequence - fragmentSequence);
if (static_cast<uint32_t>(sequenceDelta) >= m_deliveredFragments.GetValidBitCount())
{
// Too old to process
AZLOG(NET_FragmentQueue, "Fragment sequence ID is outside our tracked window");
return false;
}
if (m_deliveredFragments.GetBit(static_cast<uint32_t>(sequenceDelta)))
{
// Received packet is a duplicate of one already forwarded to gameplay
AZLOG(NET_FragmentQueue, "Received duplicate of fragmented packet %u, discarding", static_cast<uint32_t>(fragmentSequence));
return true;
}
const uint32_t chunkCount = packet->GetChunkCount();
const uint32_t chunkIndex = packet->GetChunkIndex();
// If this is the first time we've heard about this sequence, resize the vector appropriately
const bool isNewPacketFragment = m_packetFragments.find(fragmentSequence) == m_packetFragments.end();
PacketFragments& packetFragments = m_packetFragments[fragmentSequence];
if (isNewPacketFragment)
{
packetFragments.resize(chunkCount);
}
if ((chunkCount != packetFragments.size()) || (chunkIndex >= chunkCount))
{
// Either we disagree on the number of chunks, or chunkIndex is bigger than the expected size, bail and disconnect
AZLOG(NET_FragmentQueue, "Malformed chunk metadata in fragmented packet, chunkIndex %u, chunkCount %u, reservedSize %u", chunkIndex, chunkCount, static_cast<uint32_t>(packetFragments.size()));
return false;
}
packetFragments[chunkIndex] = AZStd::move(packet);
uint32_t totalPacketSize = 0;
for (uint32_t index = 0; index < packetFragments.size(); ++index)
{
if (packetFragments[index] == nullptr)
{
if (!isReliable)
{
m_timeoutQueue.RegisterItem(static_cast<uint64_t>(fragmentSequence), net_UdpFragmentTimeoutMs);
}
// We haven't received all chunks required to complete this packet yet
return true;
}
totalPacketSize += packetFragments[index]->GetChunkBuffer().GetSize();
}
// We now mark this sequence as delivered, so if by some chance all the individual chunks get redelivered again we don't double deliver the reconstructed packet
m_deliveredFragments.SetBit(static_cast<uint32_t>(sequenceDelta), true);
// All chunks have been received, reconstruct the original packet and deliver to the connection listener
UdpPacketEncodingBuffer buffer;
if (!buffer.Resize(totalPacketSize))
{
AZLOG_ERROR("Fragmented packet is too large to fit in UdpPacketEncodingBuffer");
return false;
}
uint8_t* bufferPointer = buffer.GetBuffer();
for (uint32_t index = 0; index < packetFragments.size(); ++index)
{
const uint32_t chunkSize = packetFragments[index]->GetChunkBuffer().GetSize();
memcpy(bufferPointer, packetFragments[index]->GetChunkBuffer().GetBuffer(), chunkSize);
bufferPointer += chunkSize;
}
// We can erase all the chunks now, packet is completed
m_packetFragments.erase(fragmentSequence);
NetworkOutputSerializer networkSerializer(buffer.GetBuffer(), buffer.GetSize());
{
ISerializer& serializer = networkSerializer; // To get the default typeinfo parameters in ISerializer
// First, serialize out the header
if (!header.SerializePacketFlags(networkSerializer))
{
AZLOG(NET_FragmentQueue, "Reconstructed fragmented packet failed packet flags serialization");
return false;
}
if (!serializer.Serialize(header, "Header"))
{
AZLOG(NET_FragmentQueue, "Reconstructed fragmented packet failed header serialization");
return false;
}
}
connection->GetPacketTracker().ProcessReceived(connection, header);
return connectionListener.OnPacketReceived(connection, header, networkSerializer);
}
TimeoutResult UdpFragmentQueue::HandleTimeout(TimeoutQueue::TimeoutItem& item)
{
const SequenceId fragmentSequence = static_cast<SequenceId>(item.m_userData & 0xFF);
AZLOG(NET_FragmentQueue, "Timing out unreliable fragmented packet %u", static_cast<uint32_t>(fragmentSequence));
m_packetFragments.erase(fragmentSequence);
return TimeoutResult::Delete;
}
}
@@ -0,0 +1,72 @@
/*
* 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/PacketLayer/IPacket.h>
#include <AzNetworking/AutoGen/CorePackets.AutoPackets.h>
#include <AzNetworking/ConnectionLayer/SequenceGenerator.h>
#include <AzNetworking/DataStructures/RingBufferBitset.h>
#include <AzNetworking/DataStructures/TimeoutQueue.h>
#include <AzNetworking/UdpTransport/UdpSocket.h>
#include <AzCore/std/containers/unordered_map.h>
namespace AzNetworking
{
class IConnectionListener;
class UdpConnection;
class UdpPacketHeader;
//! @class UdpFragmentQueue
//! @brief Class for reconstructing packet chunks into the original unsegmented packet.
class UdpFragmentQueue
: public ITimeoutHandler
{
public:
//! Updates the UdpFragmentQueue timeout queue.
void Update();
//! Resets all internal state.
void Reset();
//! Returns the next (outgoing) fragmented sequenceId for this FragmentQueue instance.
SequenceId GetNextFragmentedSequenceId();
//! Processes a received chunk and delivers the final reconstructed packet if possible.
//! @param connection pointer to the connection this packet chunk was received on
//! @param connectionListener the connection listener for delivery of completed packets
//! @param header the chunk packet header
//! @param serializer the serializer containing the chunk body
//! @return boolean true if the chunk was processed, false if an error was encountered
bool ProcessReceivedChunk(UdpConnection* connection, IConnectionListener& connectionListener, UdpPacketHeader& header, ISerializer& serializer);
private:
//! Handler callback for timed out items.
//! @param item containing registered timeout details
//! @return ETimeoutResult for whether to re-register or discard the timeout params
virtual TimeoutResult HandleTimeout(TimeoutQueue::TimeoutItem& item) override;
TimeoutQueue m_timeoutQueue;
SequenceGenerator m_sequenceGenerator;
using PacketFragments = AZStd::vector<AZStd::unique_ptr<CorePackets::FragmentedPacket>>;
AZStd::unordered_map<SequenceId, PacketFragments> m_packetFragments;
static constexpr uint32_t PacketWindowAckCount = 16384; // The total number of packet id's to track
using PacketAckContainer = RingbufferBitset<PacketWindowAckCount>;
SequenceId m_latestReceivedFragmentSequence = InvalidSequenceId;
PacketAckContainer m_deliveredFragments;
};
}
@@ -0,0 +1,731 @@
/*
* 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/UdpTransport/UdpNetworkInterface.h>
#include <AzNetworking/UdpTransport/UdpConnection.h>
#include <AzNetworking/UdpTransport/DtlsSocket.h>
#include <AzNetworking/UdpTransport/UdpSocket.h>
#include <AzNetworking/Serialization/NetworkInputSerializer.h>
#include <AzNetworking/Serialization/NetworkOutputSerializer.h>
#include <AzNetworking/Framework/ICompressor.h>
#include <AzNetworking/Utilities/CompressionCommon.h>
#include <AzNetworking/Utilities/NetworkCommon.h>
#include <AzCore/Console/IConsole.h>
#include <AzCore/Console/ILogger.h>
namespace AzNetworking
{
#if AZ_TRAIT_USE_OPENSSL
AZ_CVAR(bool, net_UdpUseEncryption, false, nullptr, AZ::ConsoleFunctorFlags::DontReplicate, "Enable encryption on Udp based connections");
#else
static const bool net_UdpUseEncryption = false;
#endif
AZ_CVAR(bool, net_UdpTimeoutConnections, true, nullptr, AZ::ConsoleFunctorFlags::DontReplicate, "Boolean value on whether we should timeout Udp connections");
AZ_CVAR(AZ::TimeMs, net_UdpPacketTimeSliceMs, AZ::TimeMs{ 8 }, nullptr, AZ::ConsoleFunctorFlags::DontReplicate, "The number of milliseconds to allow for packet processing");
AZ_CVAR(AZ::TimeMs, net_UdpHearthbeatTimeMs, AZ::TimeMs{ 2 * 1000 }, nullptr, AZ::ConsoleFunctorFlags::Null, "Udp connection heartbeat frequency");
AZ_CVAR(AZ::TimeMs, net_UdpTimeoutTimeMs, AZ::TimeMs{ 10 * 1000 }, nullptr, AZ::ConsoleFunctorFlags::Null, "Time in milliseconds before we timeout an idle Udp connection");
AZ_CVAR(AZ::TimeMs, net_MinPacketTimeoutMs, AZ::TimeMs{ 200 }, nullptr, AZ::ConsoleFunctorFlags::DontReplicate, "Minimum time to wait before timing out an unacked packet");
AZ_CVAR(int32_t, net_MaxTimeoutsPerFrame, 1000, nullptr, AZ::ConsoleFunctorFlags::DontReplicate, "Maximum number of packet timeouts to allow to process in a single frame");
AZ_CVAR(float, net_RttFudgeScalar, 2.0f, nullptr, AZ::ConsoleFunctorFlags::DontReplicate, "Scalar value to multiply computed Rtt by to determine an optimal packet timeout threshold");
AZ_CVAR(uint32_t, net_FragmentedHeaderOverhead, 32, nullptr, AZ::ConsoleFunctorFlags::DontReplicate, "A fudge overhead value to take out of fragmented packet payloads");
AZ_CVAR(AZ::CVarFixedString, net_UdpCompressor, "MultiplayerCompressor", nullptr, AZ::ConsoleFunctorFlags::DontReplicate, "UDP compressor to use."); // WARN: similar to encryption this needs to be set once and only once before creating the network interface
static uint64_t ConstructTimeoutId(ConnectionId connectionId, PacketId packetId, ReliabilityType reliability)
{
const uint64_t intConnectionId = aznumeric_cast<uint64_t>(connectionId);
const uint64_t intPacketId = aznumeric_cast<uint64_t>(packetId);
const uint64_t intReliability = (reliability == ReliabilityType::Reliable) ? 1 : 0;
const uint64_t baseTimeoutId = ((intConnectionId << 32) | intPacketId) & 0x7FFFFFFFFFFFFFFF;
return (intReliability << 63) | baseTimeoutId;
}
static void DecodeTimeoutId(uint64_t timeoutId, ConnectionId& outConnectionId, PacketId& outPacketId, ReliabilityType& outReliability)
{
outConnectionId = ConnectionId(aznumeric_cast<uint32_t>(timeoutId >> 32) & 0x7FFFFFFF);
outPacketId = PacketId(aznumeric_cast<uint32_t>(timeoutId >> 0) & 0xFFFFFFFF);
outReliability = ((timeoutId & 0x8000000000000000) > 0) ? ReliabilityType::Reliable : ReliabilityType::Unreliable;
}
UdpNetworkInterface::UdpNetworkInterface(AZ::Name name, IConnectionListener& connectionListener, TrustZone trustZone, UdpReaderThread& readerThread)
: m_name(name)
, m_trustZone(trustZone)
, m_connectionListener(connectionListener)
, m_socket(net_UdpUseEncryption ? new DtlsSocket() : new UdpSocket())
, m_readerThread(readerThread)
{
const AZ::CVarFixedString compressor = static_cast<AZ::CVarFixedString>(net_UdpCompressor);
const char* compressorName = compressor.c_str();
m_compressor = CreateCompressor(compressorName);
}
UdpNetworkInterface::~UdpNetworkInterface()
{
m_readerThread.UnregisterSocket(m_socket.get());
}
AZ::Name UdpNetworkInterface::GetName() const
{
return m_name;
}
ProtocolType UdpNetworkInterface::GetType() const
{
return ProtocolType::Udp;
}
TrustZone UdpNetworkInterface::GetTrustZone() const
{
return m_trustZone;
}
uint16_t UdpNetworkInterface::GetPort() const
{
return m_port;
}
IConnectionSet& UdpNetworkInterface::GetConnectionSet()
{
return m_connectionSet;
}
IConnectionListener& UdpNetworkInterface::GetConnectionListener()
{
return m_connectionListener;
}
bool UdpNetworkInterface::Listen(uint16_t port)
{
if (m_socket->IsOpen())
{
AZ_Assert(false, "Listen cannot be invoked on an already opened network interface");
return false;
}
m_port = port;
m_allowIncomingConnections = true;
m_socket->Open(m_port, UdpSocket::CanAcceptConnections::True, m_trustZone);
m_readerThread.RegisterSocket(m_socket.get());
return true;
}
ConnectionId UdpNetworkInterface::Connect(const IpAddress& remoteAddress)
{
if (!m_socket->IsOpen())
{
m_socket->Open(m_port, UdpSocket::CanAcceptConnections::True, m_trustZone);
m_readerThread.RegisterSocket(m_socket.get());
}
const ConnectionId connectionId = m_connectionSet.GetNextConnectionId();
const TimeoutId timeoutId = m_connectionTimeoutQueue.RegisterItem(aznumeric_cast<uint64_t>(connectionId), net_UdpHearthbeatTimeMs);
AZStd::unique_ptr<UdpConnection> connection = AZStd::make_unique<UdpConnection>(connectionId, remoteAddress, *this, ConnectionRole::Connector);
UdpPacketEncodingBuffer dtlsData;
m_socket->ConnectDtlsEndpoint(connection->GetDtlsEndpoint(), remoteAddress, dtlsData);
// We're initiating this connection, so go to a connecting state until we receive some kind of response so that we know it's alive and valid
connection->m_state = ConnectionState::Connecting;
connection->SetConnectionMtu(MaxUdpTransmissionUnit);
connection->SetTimeoutId(timeoutId);
connection->SendReliablePacket(CorePackets::InitiateConnectionPacket());
m_connectionListener.OnConnect(connection.get());
m_connectionSet.AddConnection(AZStd::move(connection));
return connectionId;
}
void UdpNetworkInterface::Update([[maybe_unused]] AZ::TimeMs deltaTimeMs)
{
if (!m_socket->IsOpen())
{
return;
}
#ifdef ENABLE_LATENCY_DEBUG
m_socket->ProcessDeferredPackets();
#endif
const AZ::TimeMs startTimeMs = AZ::GetElapsedTimeMs();
const UdpReaderThread::ReceivedPackets* packets = m_readerThread.GetReceivedPackets(m_socket.get());
if (packets == nullptr)
{
AZ_Assert(false, "nullptr was retrieved for the received packet buffer, check that the socket has been registered with the reader thread");
return;
}
for (uint32_t i = 0; i < packets->size(); ++i)
{
const UdpReaderThread::ReceivedPacket& packet = (*packets)[i];
const AZ::TimeMs currentTimeMs = AZ::GetElapsedTimeMs();
// Don't exceed our timeslice, even if unprocessed data remains
if ((currentTimeMs - startTimeMs) > net_UdpPacketTimeSliceMs)
{
AZLOG_WARN("Processing time exceeded, discarding %d/%d received packets", aznumeric_cast<int32_t>(packets->size() - i), aznumeric_cast<int32_t>(packets->size()));
GetMetrics().m_discardedPackets += packets->size() - i;
break;
}
UdpConnection* connection = m_connectionSet.GetConnection(packet.m_address);
if (connection == nullptr)
{
AcceptConnection(packet);
continue;
}
const DisconnectReason disconnectReason = GetDisconnectReasonForSocketResult(packet.m_receivedBytes);
if (disconnectReason != DisconnectReason::MAX)
{
connection->Disconnect(disconnectReason, TerminationEndpoint::Local);
continue;
}
int32_t decodedPacketSize = 0;
m_decryptBuffer.Resize(m_decryptBuffer.GetCapacity());
const uint8_t* decodedPacketData = connection->GetDtlsEndpoint().DecodePacket(*m_socket, packet.m_buffer, packet.m_receivedBytes, m_decryptBuffer.GetBuffer(), decodedPacketSize);
m_decryptBuffer.Resize(decodedPacketSize);
if (decodedPacketSize == 0)
{
// OpenSSL may have consumed packets during handshake negotiation
continue;
}
else if (decodedPacketSize < 0)
{
// Something bad happened on the SSL read and we're now invalid, we should disconnect
connection->Disconnect(DisconnectReason::SslFailure, TerminationEndpoint::Local);
continue;
}
connection->GetMetrics().m_recvDatarate.LogPacket(packet.m_receivedBytes + UdpPacketHeaderSize, currentTimeMs);
connection->GetMetrics().m_packetsRecv++;
// Decode the packet flag bitset first since it's always uncompressed
UdpPacketHeader header;
{
NetworkOutputSerializer flagSerializer(decodedPacketData, decodedPacketSize);
if (!header.SerializePacketFlags(flagSerializer))
{
continue;
}
// Adjust decoded tracking to represent the payload now that we've grabbed the flags
decodedPacketData = flagSerializer.GetUnreadData();
decodedPacketSize = flagSerializer.GetUnreadSize();
GetMetrics().m_recvBytesUncompressed += flagSerializer.GetReadSize();
}
if (m_compressor && header.IsPacketFlagSet(PacketFlag::Compressed))
{
// Only the payload is compressed
if (!DecompressPacket(decodedPacketData, decodedPacketSize, m_decompressBuffer))
{
AZLOG_WARN("Failed to decompress packet!");
continue;
}
decodedPacketData = m_decompressBuffer.GetBuffer();
decodedPacketSize = m_decompressBuffer.GetSize();
GetMetrics().m_recvBytesUncompressed += decodedPacketSize;
}
TimeoutQueue::TimeoutItem* timeoutItem = m_connectionTimeoutQueue.RetrieveItem(connection->GetTimeoutId());
if (timeoutItem == nullptr)
{
connection->Disconnect(DisconnectReason::Unknown, TerminationEndpoint::Local);
continue;
}
else
{
// Deserialize the packet header
NetworkOutputSerializer packetSerializer(decodedPacketData, decodedPacketSize);
ISerializer& serializer = packetSerializer; // To get the default typeinfo parameters in ISerializer
if (!serializer.Serialize(header, "Header"))
{
continue;
}
// Note that the serializer passed in here is unused for UDP
if (!connection->ProcessReceived(header, packetSerializer, packet.m_receivedBytes + UdpPacketHeaderSize, currentTimeMs))
{
continue;
}
timeoutItem->UpdateTimeoutTime(startTimeMs);
bool handledPacket = false;
if (header.GetPacketType() < aznumeric_cast<PacketType>(CorePackets::PacketType::MAX))
{
handledPacket = connection->HandleCorePacket(m_connectionListener, header, packetSerializer);
}
else
{
handledPacket = m_connectionListener.OnPacketReceived(connection, header, packetSerializer);
}
if (handledPacket)
{
connection->UpdateHeartbeat(currentTimeMs);
if (connection->GetConnectionState() == ConnectionState::Connecting)
{
connection->m_state = ConnectionState::Connected;
}
}
else if (connection->GetConnectionState() != ConnectionState::Disconnecting)
{
connection->Disconnect(DisconnectReason::StreamError, TerminationEndpoint::Local);
}
}
}
const AZ::TimeMs receiveTimeMs = AZ::GetElapsedTimeMs() - startTimeMs;
// Time out any stale client connections
{
ConnectionTimeoutFunctor functor(*this);
m_connectionTimeoutQueue.UpdateTimeouts(functor);
}
// Time out any packets that haven't been acked within our timeout window
{
PacketTimeoutFunctor functor(*this);
m_packetTimeoutQueue.UpdateTimeouts(functor, static_cast<int32_t>(net_MaxTimeoutsPerFrame));
}
// Delete any connections we've disconnected
for (RemovedConnection& removedConnection : m_removedConnections)
{
m_connectionListener.OnDisconnect(removedConnection.m_connection, removedConnection.m_reason, removedConnection.m_endpoint);
m_connectionSet.DeleteConnection(removedConnection.m_connection->GetConnectionId()); // Will delete the connection
}
m_removedConnections.clear();
// Update metrics
GetMetrics().m_sendPackets = m_socket->GetSentPackets();
GetMetrics().m_sendBytes = m_socket->GetSentBytes();
GetMetrics().m_recvTimeMs += receiveTimeMs;
GetMetrics().m_recvPackets = m_socket->GetRecvPackets();
GetMetrics().m_recvBytes = m_socket->GetRecvBytes();
GetMetrics().m_connectionCount = m_connectionSet.GetConnectionCount();
GetMetrics().m_updateTimeMs += AZ::GetElapsedTimeMs() - startTimeMs;
}
bool UdpNetworkInterface::SendReliablePacket(ConnectionId connectionId, const IPacket& packet)
{
IConnection* connection = m_connectionSet.GetConnection(connectionId);
if (connection == nullptr)
{
return false;
}
return connection->SendReliablePacket(packet);
}
PacketId UdpNetworkInterface::SendUnreliablePacket(ConnectionId connectionId, const IPacket& packet)
{
IConnection* connection = m_connectionSet.GetConnection(connectionId);
if (connection == nullptr)
{
return InvalidPacketId;
}
return connection->SendUnreliablePacket(packet);
}
bool UdpNetworkInterface::WasPacketAcked(ConnectionId connectionId, PacketId packetId)
{
IConnection* connection = m_connectionSet.GetConnection(connectionId);
if (connection == nullptr)
{
return false;
}
return connection->WasPacketAcked(packetId);
}
bool UdpNetworkInterface::Disconnect(ConnectionId connectionId, DisconnectReason reason)
{
IConnection* connection = m_connectionSet.GetConnection(connectionId);
if (connection == nullptr)
{
return false;
}
return connection->Disconnect(reason, TerminationEndpoint::Local);
}
bool UdpNetworkInterface::IsEncrypted() const
{
return m_socket->IsEncrypted();
}
bool UdpNetworkInterface::IsOpen() const
{
return m_socket->IsOpen();
}
void UdpNetworkInterface::RegisterWithTimeoutQueue(ConnectionId connectionId, PacketId packetId, ReliabilityType reliability, const ConnectionMetrics& metrics)
{
const float avgRtt = metrics.m_connectionRtt.GetRoundTripTimeSeconds(); // Time is in seconds, timeout times are in milliseconds
const AZ::TimeMs expectedTimeoutMs = aznumeric_cast<AZ::TimeMs>(aznumeric_cast<int64_t>(avgRtt * 1000.0f * net_RttFudgeScalar));
const AZ::TimeMs packetTimeoutMs = AZStd::max<AZ::TimeMs>(expectedTimeoutMs, net_MinPacketTimeoutMs); // Consider packets lost after twice the current connection Rtt
AZLOG(NET_Debug, "Registering packetId %u with timeout %u", aznumeric_cast<uint32_t>(packetId), aznumeric_cast<uint32_t>(packetTimeoutMs));
m_packetTimeoutQueue.RegisterItem(ConstructTimeoutId(connectionId, packetId, reliability), packetTimeoutMs);
}
bool UdpNetworkInterface::DecompressPacket(const uint8_t* packetBuffer, size_t packetSize, UdpPacketEncodingBuffer& packetBufferOut) const
{
if (!m_compressor) // should probably have some compression handshake than relying on existence of compressor
{
AZLOG_ERROR("Decompress called without a compressor.");
return false;
}
AZStd::size_t uncompSize = 0;
AZStd::size_t bytesConsumed = 0;
packetBufferOut.Resize(packetBufferOut.GetCapacity());
const CompressorError compErr = m_compressor->Decompress(packetBuffer, packetSize, packetBufferOut.GetBuffer(), packetBufferOut.GetCapacity(), bytesConsumed, uncompSize);
packetBufferOut.Resize(aznumeric_cast<uint32_t>(uncompSize)); // Decompress will fail if larger than buffer size, so this cast is safe
if (compErr != CompressorError::Ok)
{
AZLOG_ERROR("Decompress failed with error %d this will lead to data read errors!", compErr);
return false;
}
if (packetSize != bytesConsumed)
{
AZLOG_ERROR("Decompress must consume entire buffer [%zu != %zu]!", bytesConsumed, packetSize);
return false;
}
return true;
}
PacketId UdpNetworkInterface::SendPacket(UdpConnection& connection, const IPacket& packet, SequenceId reliableSequence)
{
AZLOG(NET_DebugPacketSend, "Sending packet type %u to remote address %s", aznumeric_cast<uint32_t>(packet.GetPacketType()), connection.GetRemoteAddress().GetString().c_str());
// The ordering inside this function is incredibly important and fragile
const IpAddress& address = connection.GetRemoteAddress();
// We don't want to compress the initial InitiateConnectionPacket
// We can use this to transmit compression and encryption details in the future
const bool shouldCompress = packet.GetPacketType() != aznumeric_cast<PacketType>(CorePackets::PacketType::InitiateConnectionPacket);
if (address.GetAddress(ByteOrder::Host) == 0)
{
return InvalidPacketId;
}
const ReliabilityType reliabilityType = (reliableSequence == InvalidSequenceId) ? ReliabilityType::Unreliable : ReliabilityType::Reliable;
// Check if we need to fragment this packet first
// We don't ack aggregate packets that get fragmented, so we want to get this chunk out of the way before
// we start throwing PacketId's and SequenceId's into our other tracking data structures below
UdpPacketHeader header(connection.GetPacketTracker(), packet.GetPacketType(), reliableSequence);
const PacketId localPacketId = header.GetPacketId();
UdpPacketEncodingBuffer buffer;
{
buffer.Resize(buffer.GetCapacity());
NetworkInputSerializer networkSerializer(buffer.GetBuffer(), buffer.GetCapacity());
ISerializer& serializer = networkSerializer; // To get the default typeinfo parameters in ISerializer
if (!header.SerializePacketFlags(serializer))
{
AZLOG_ERROR("PacketId %u failed flag serialization and will not be sent", aznumeric_cast<uint32_t>(localPacketId));
return InvalidPacketId;
}
if (!serializer.Serialize(header, "Header"))
{
AZLOG_ERROR("PacketId %u failed header serialization and will not be sent", aznumeric_cast<uint32_t>(localPacketId));
return InvalidPacketId;
}
if (!serializer.Serialize(const_cast<IPacket&>(packet), "Payload"))
{
AZLOG_ERROR("PacketId %u failed payload serialization and will not be sent", aznumeric_cast<uint32_t>(localPacketId));
return InvalidPacketId;
}
buffer.Resize(serializer.GetSize());
}
uint32_t packetSize = buffer.GetSize();
uint8_t* packetData = buffer.GetBuffer();
// If the packet doesn't fit within our MTU, break it up
if (packetSize > connection.GetConnectionMtu())
{
// Each fragmented packet we send adds an extra fragmented packet header, need to deduct that from our chunk size, otherwise we infinitely loop
const uint32_t chunkSize = connection.GetConnectionMtu() - net_FragmentedHeaderOverhead;
const uint32_t numChunks = (packetSize + chunkSize - 1) / chunkSize; // We want to round up on the remainder
const uint8_t* chunkStart = packetData;
const SequenceId fragmentedSequence = connection.m_fragmentQueue.GetNextFragmentedSequenceId();
uint32_t bytesRemaining = packetSize;
ChunkBuffer chunkBuffer;
for (uint32_t chunkIndex = 0; chunkIndex < numChunks; ++chunkIndex)
{
const uint32_t nextChunkSize = AZStd::min(bytesRemaining, chunkSize);
chunkBuffer.CopyValues(chunkStart, nextChunkSize);
CorePackets::FragmentedPacket fragmentedPacket(ToSequenceId(localPacketId), fragmentedSequence, aznumeric_cast<uint8_t>(chunkIndex), aznumeric_cast<uint8_t>(numChunks), chunkBuffer);
const SequenceId chunkReliableId = (reliabilityType == ReliabilityType::Reliable) ? connection.m_reliableQueue.GetNextSequenceId() : InvalidSequenceId;
SendPacket(connection, fragmentedPacket, chunkReliableId);
bytesRemaining -= nextChunkSize;
chunkStart += nextChunkSize;
}
AZ_Assert(bytesRemaining == 0, "Non-zero bytes remaining (%u) after chunking a packet into fragments", bytesRemaining);
return localPacketId;
}
UdpPacketEncodingBuffer writeBuffer;
if (m_compressor && shouldCompress)
{
NetworkInputSerializer flagSerializer(writeBuffer.GetBuffer(), writeBuffer.GetCapacity());
ISerializer& serializer = flagSerializer; // To get the default typeinfo parameters in ISerializer
header.SetPacketFlag(PacketFlag::Compressed, true);
if (!header.SerializePacketFlags(serializer))
{
AZLOG_ERROR("PacketId %u failed flag serialization for compression and will not be sent", aznumeric_cast<uint32_t>(localPacketId));
return InvalidPacketId;
}
uint32_t flagSize = flagSerializer.GetSize();
AZ_Assert(flagSize == 1, "Flag bitfield should serialize to one byte");
// Compress the packet, make sure to offset by the size of the flag which is now serialized
const uint32_t payloadSize = buffer.GetSize() - flagSize;
uint8_t* payload = buffer.GetBuffer() + flagSize;
const AZStd::size_t maxSizeNeeded = m_compressor->GetMaxCompressedBufferSize(payloadSize);
AZStd::size_t compressionMemBytesUsed = 0;
CompressorError compErr = m_compressor->Compress(payload, payloadSize, writeBuffer.GetBuffer() + flagSize, maxSizeNeeded, compressionMemBytesUsed);
if (compErr != CompressorError::Ok)
{
AZLOG_ERROR("Failed to compress packet with error %d", aznumeric_cast<int32_t>(compErr));
return InvalidPacketId;
}
// Only use compression if there's actual gain
if (compressionMemBytesUsed < payloadSize)
{
writeBuffer.Resize(aznumeric_cast<int32_t>(flagSize + compressionMemBytesUsed));
packetSize = writeBuffer.GetSize();
packetData = writeBuffer.GetBuffer();
// Track byte delta caused by compression
GetMetrics().m_sendBytesCompressedDelta += (packetSize - compressionMemBytesUsed);
}
}
AZLOG(NET_Debug, "Sending local sequence id %d, remote sequence id %d, %s, reliable id: %d, ack vector %x",
aznumeric_cast<int32_t>(header.GetLocalSequenceId()),
aznumeric_cast<int32_t>(header.GetRemoteSequenceId()),
header.GetIsReliable() ? "reliable" : "unreliable",
aznumeric_cast<int32_t>(header.GetReliableSequenceId()),
aznumeric_cast<uint32_t>(header.GetSequenceWindow())
);
// If it's a reliable packet, make sure our reliable queue knows about it now because we might need to drop it if our connection is not set up
if (reliabilityType == ReliabilityType::Reliable)
{
if (!connection.PrepareReliablePacketForSend(localPacketId, reliableSequence, packet))
{
connection.Disconnect(DisconnectReason::ReliableQueueFull, TerminationEndpoint::Local);
}
}
// Okay, if we're still connecting, and the packet we're trying to send is not a retransmitted initiate connection packet, return an error, don't send yet
if (connection.GetDtlsEndpoint().IsConnecting() && (packet.GetPacketType() != aznumeric_cast<PacketType>(CorePackets::PacketType::InitiateConnectionPacket)))
{
const DtlsEndpoint::ConnectResult result = connection.CompleteHandshake();
if (result != DtlsEndpoint::ConnectResult::Complete) // DTLS handshake still in progress
{
// IMPORTANT that we register with the timeout queue here, otherwise we don't have the timer to pop for reliable packets
RegisterWithTimeoutQueue(connection.GetConnectionId(), localPacketId, reliabilityType, connection.GetMetrics());
AZLOG(NET_DebugDtls, "Connection is still in handshake negotiation, blocking packet send for packet type %d", (int)packet.GetPacketType());
return localPacketId;
}
}
AZLOG(NET_DebugDtls, "Connection is sending packet type %d", aznumeric_cast<int32_t>(packet.GetPacketType()));
const bool shouldEncrypt = packet.GetPacketType() == aznumeric_cast<PacketType>(CorePackets::PacketType::InitiateConnectionPacket);
if (m_socket->Send(address, packetData, packetSize, shouldEncrypt, connection.GetDtlsEndpoint(), connection.GetConnectionQuality()))
{
RegisterWithTimeoutQueue(connection.GetConnectionId(), localPacketId, reliabilityType, connection.GetMetrics());
connection.ProcessSent(localPacketId, packet, packetSize + UdpPacketHeaderSize, reliabilityType);
GetMetrics().m_sendBytesUncompressed += buffer.GetSize() + UdpPacketHeaderSize + (shouldEncrypt ? DtlsPacketHeaderSize : 0);
return localPacketId;
}
else
{
AZLOG_ERROR("PacketId %u failed to send on the socket", aznumeric_cast<uint32_t>(localPacketId));
}
return InvalidPacketId;
}
void UdpNetworkInterface::AcceptConnection(const UdpReaderThread::ReceivedPacket& connectPacket)
{
if (!m_allowIncomingConnections)
{
// This network interface is not set to allow incoming connections
return;
}
CorePackets::InitiateConnectionPacket packet;
{
NetworkOutputSerializer networkSerializer(connectPacket.m_buffer, connectPacket.m_receivedBytes);
// First, serialize out the header
UdpPacketHeader header;
if (!header.SerializePacketFlags(networkSerializer))
{
return;
}
if (!static_cast<ISerializer&>(networkSerializer).Serialize(header, "Header"))
{
return;
}
// Validate that this is really an InitiateConnection packet
if (header.GetPacketType() != aznumeric_cast<PacketType>(CorePackets::PacketType::InitiateConnectionPacket))
{
return;
}
// Next serialize the InitiateConnectionPacket itself
{
NetworkOutputSerializer tempPacketSerializer(networkSerializer.GetUnreadData(), networkSerializer.GetUnreadSize());
if (!static_cast<ISerializer&>(tempPacketSerializer).Serialize(packet, "Packet"))
{
return;
}
}
// Retrieve the connection type, and run application layer connection filtering (state checks, CIDR address filtering, etc..)
const ConnectResult connectResult = m_connectionListener.ValidateConnect(connectPacket.m_address, header, networkSerializer);
switch (connectResult)
{
case ConnectResult::Rejected:
return; // Failed validation, simply discard the connect message
case ConnectResult::Accepted:
break; // This is not actually an expected return from this code path, assume it's a client connection
}
}
// We've passed all our security checks, so now we're free to allocate memory and track the new connection
// How long should we sit in the timeout queue before heartbeating or disconnecting
const ConnectionId connectionId = m_connectionSet.GetNextConnectionId();
const TimeoutId timeoutId = m_connectionTimeoutQueue.RegisterItem(aznumeric_cast<uint64_t>(connectionId), net_UdpTimeoutTimeMs);
AZLOG(Debug_UdpConnect, "Accepted new Udp Connection");
AZStd::unique_ptr<UdpConnection> connection = AZStd::make_unique<UdpConnection>(connectionId, connectPacket.m_address, *this, ConnectionRole::Acceptor);
UdpPacketEncodingBuffer dtlsData;
m_socket->AcceptDtlsEndpoint(connection->GetDtlsEndpoint(), connectPacket.m_address, dtlsData);
// We're accepting this connection, so we can immediately transition to a connected state
connection->m_state = ConnectionState::Connected;
connection->SetTimeoutId(timeoutId);
m_connectionListener.OnConnect(connection.get());
m_connectionSet.AddConnection(AZStd::move(connection));
}
void UdpNetworkInterface::RequestDisconnect(UdpConnection* connection, DisconnectReason reason, TerminationEndpoint endpoint)
{
if (connection == nullptr)
{
return;
}
connection->m_state = ConnectionState::Disconnecting;
m_removedConnections.emplace_back(RemovedConnection{ connection, reason, endpoint });
}
UdpNetworkInterface::ConnectionTimeoutFunctor::ConnectionTimeoutFunctor(UdpNetworkInterface& networkInterface)
: m_networkInterface(networkInterface)
{
;
}
TimeoutResult UdpNetworkInterface::ConnectionTimeoutFunctor::HandleTimeout(TimeoutQueue::TimeoutItem& item)
{
const ConnectionId connectionId = ConnectionId(aznumeric_cast<uint32_t>(item.m_userData));
UdpConnection* udpConnection = static_cast<UdpConnection*>(m_networkInterface.m_connectionSet.GetConnection(connectionId));
if (udpConnection == nullptr)
{
// We've already deleted this connection
return TimeoutResult::Delete;
}
if (udpConnection->GetConnectionState() == ConnectionState::Connecting)
{
if (udpConnection->GetDtlsEndpoint().IsConnecting())
{
udpConnection->CompleteHandshake();
return TimeoutResult::Refresh;
}
}
if (udpConnection->GetConnectionRole() == ConnectionRole::Connector)
{
udpConnection->SendUnreliablePacket(CorePackets::HeartbeatPacket());
}
else if (net_UdpTimeoutConnections)
{
udpConnection->Disconnect(DisconnectReason::Timeout, TerminationEndpoint::Local);
return TimeoutResult::Delete;
}
return TimeoutResult::Refresh;
}
UdpNetworkInterface::PacketTimeoutFunctor::PacketTimeoutFunctor(UdpNetworkInterface& networkInterface)
: m_networkInterface(networkInterface)
{
;
}
TimeoutResult UdpNetworkInterface::PacketTimeoutFunctor::HandleTimeout(TimeoutQueue::TimeoutItem& item)
{
ConnectionId connectionId;
PacketId packetId;
ReliabilityType reliability;
DecodeTimeoutId(item.m_userData, connectionId, packetId, reliability);
UdpConnection* connection = static_cast<UdpConnection*>(m_networkInterface.m_connectionSet.GetConnection(connectionId));
if (connection == nullptr)
{
AZLOG(NET_Debug, "Failed to look up connection for timed out packetId %u", aznumeric_cast<uint32_t>(packetId));
return TimeoutResult::Delete;
}
const PacketTimeoutResult result = connection->ProcessTimeout(packetId, reliability);
AZLOG(NET_Debug, "Timeout triggered for packetId %u with result %s", aznumeric_cast<uint32_t>(packetId), GetEnumString(result));
switch (result)
{
case PacketTimeoutResult::Acked:
// Packet was already acked, just discard this timeout entry
return TimeoutResult::Delete;
case PacketTimeoutResult::Pending:
// Packet timed out before we received any info about it's sequence from the remote endpoint
// The connection latency may have increased, and our Rtt metrics may still be adjusting..
// Just throw it back into the timeout queue
return TimeoutResult::Refresh;
case PacketTimeoutResult::Lost:
// Packet timed out and was not acked, so we consider it lost
m_networkInterface.m_connectionListener.OnPacketLost(connection, packetId);
break;
}
return TimeoutResult::Delete;
}
}
@@ -0,0 +1,155 @@
/*
* 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/UdpTransport/UdpPacketHeader.h>
#include <AzNetworking/UdpTransport/UdpConnectionSet.h>
#include <AzNetworking/UdpTransport/UdpReaderThread.h>
#include <AzNetworking/ConnectionLayer/IConnection.h>
#include <AzNetworking/ConnectionLayer/ConnectionEnums.h>
#include <AzNetworking/Framework/INetworkInterface.h>
#include <AzNetworking/DataStructures/TimeoutQueue.h>
#include <AzCore/Threading/ThreadSafeDeque.h>
#include <AzCore/std/containers/vector.h>
namespace AzNetworking
{
class IConnectionListener;
class ICompressor;
// 20 byte IPv4 header + 8 byte UDP header
static const uint32_t UdpPacketHeaderSize = 20 + 8;
static const uint32_t DtlsPacketHeaderSize = 13; // DTLS1_RT_HEADER_LENGTH
//! @class UdpNetworkInterface
//! @brief This class implements a UDP network interface.
class UdpNetworkInterface final
: public INetworkInterface
{
public:
//! Constructor.
//! @param name the name of this network interface instance.
//! @param connectionListener reference to the connection listener responsible for handling all connection events
//! @param trustZone the trust level assigned to this network interface, server to server or client to server
//! @param readerThread pointer to the reader thread to be bound to this network interface
UdpNetworkInterface(AZ::Name name, IConnectionListener& connectionListener, TrustZone trustZone, UdpReaderThread& readerThread);
~UdpNetworkInterface() override;
//! INetworkInterface interface.
//! @{
AZ::Name GetName() const override;
ProtocolType GetType() const override;
TrustZone GetTrustZone() const override;
uint16_t GetPort() const override;
IConnectionSet& GetConnectionSet() override;
IConnectionListener& GetConnectionListener() override;
bool Listen(uint16_t port) override;
ConnectionId Connect(const IpAddress& remoteAddress) override;
void Update(AZ::TimeMs deltaTimeMs) override;
bool SendReliablePacket(ConnectionId connectionId, const IPacket& packet) override;
PacketId SendUnreliablePacket(ConnectionId connectionId, const IPacket& packet) override;
bool WasPacketAcked(ConnectionId connectionId, PacketId packetId) override;
bool Disconnect(ConnectionId connectionId, DisconnectReason reason) override;
//! @}
//! Returns true if this is an encrypted socket, false if not.
//! @return boolean true if this is an encrypted socket, false if not
bool IsEncrypted() const;
//! Returns true if this connection instance is in an open state, and is capable of actively sending and receiving packets.
//! @return boolean true if this connection instance is in an open state
bool IsOpen() const;
private:
//! Registers a packet with a timeout queue on the provided connection.
//! @param connectionId identifier of the connection to register
//! @param packetId packet id of the packet to register for the given connection
//! @param reliability whether or not to guarantee delivery
//! @param metrics reference to the connections metrics instance
void RegisterWithTimeoutQueue(ConnectionId connectionId, PacketId packetId, ReliabilityType reliability, const ConnectionMetrics& metrics);
//! Decompresses an incoming packet data buffer.
//! @param packetBuffer the compressed packet buffer to decode
//! @param packetSize the size of the compressed packet buffer
//! @param packetBufferOut the decoded data
//! @return boolean true on success, false on failure
bool DecompressPacket(const uint8_t* packetBuffer, size_t packetSize, UdpPacketEncodingBuffer& packetBufferOut) const;
//! Sends a packet to the remote connection.
//! @param connection the UdpConnection instance to send the packet on
//! @param packet serializable object to transmit
//! @param reliableSequence the reliable sequence number to use for this packet, providing InvalidSequenceId will cause the packet to be sent unreliably
//! @return packet id for the transmitted packet
PacketId SendPacket(UdpConnection& connection, const IPacket& packet, SequenceId reliableSequence);
//! Accepts an incoming udp connection.
//! @param connectPacket the initial connectPacket
void AcceptConnection(const UdpReaderThread::ReceivedPacket& connectPacket);
//! Internal helper to cleanly remove a connection from the network interface.
//! @param connection pointer to the connection to disconnect
//! @param reason reason for the disconnect
//! @param endpoint whether the disconnection was initiated locally or remotely
void RequestDisconnect(UdpConnection* connection, DisconnectReason reason, TerminationEndpoint endpoint);
AZ_DISABLE_COPY_MOVE(UdpNetworkInterface);
struct ConnectionTimeoutFunctor final
: public ITimeoutHandler
{
ConnectionTimeoutFunctor(UdpNetworkInterface& networkInterface);
TimeoutResult HandleTimeout(TimeoutQueue::TimeoutItem& item) override;
private:
AZ_DISABLE_COPY_MOVE(ConnectionTimeoutFunctor);
UdpNetworkInterface& m_networkInterface;
};
struct PacketTimeoutFunctor final
: public ITimeoutHandler
{
PacketTimeoutFunctor(UdpNetworkInterface& networkInterface);
TimeoutResult HandleTimeout(TimeoutQueue::TimeoutItem& item) override;
private:
AZ_DISABLE_COPY_MOVE(PacketTimeoutFunctor);
UdpNetworkInterface& m_networkInterface;
};
AZ::Name m_name;
TrustZone m_trustZone;
uint16_t m_port = 0;
bool m_allowIncomingConnections = false;
IConnectionListener& m_connectionListener;
UdpConnectionSet m_connectionSet;
TimeoutQueue m_connectionTimeoutQueue;
TimeoutQueue m_packetTimeoutQueue;
AZStd::unique_ptr<UdpSocket> m_socket;
AZStd::unique_ptr<ICompressor> m_compressor;
UdpReaderThread& m_readerThread;
struct RemovedConnection
{
UdpConnection* m_connection;
DisconnectReason m_reason;
TerminationEndpoint m_endpoint;
};
AZStd::vector<RemovedConnection> m_removedConnections;
UdpPacketEncodingBuffer m_decryptBuffer;
UdpPacketEncodingBuffer m_decompressBuffer;
friend class UdpReliableQueue;
friend class UdpConnection; // For access to private RequestDisconnect() method
};
}
@@ -0,0 +1,96 @@
/*
* 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/UdpTransport/UdpPacketHeader.h>
#include <AzNetworking/UdpTransport/UdpPacketTracker.h>
namespace AzNetworking
{
UdpPacketHeader::UdpPacketHeader()
: m_packetType(PacketType{ 0 })
, m_localSequence(InvalidSequenceId)
, m_remoteSequence(InvalidSequenceId)
, m_reliableSequence(InvalidSequenceId)
, m_sequenceWindow(0)
, m_localRolloverCount(InvalidSequenceRolloverCount)
{
;
}
UdpPacketHeader::UdpPacketHeader(UdpPacketTracker& packetTracker, PacketType packetType, SequenceId reliableSequence)
: m_packetType(packetType)
, m_localSequence(InvalidSequenceId)
, m_remoteSequence(packetTracker.GetLastReceivedSequenceId())
, m_reliableSequence(reliableSequence)
, m_sequenceWindow(packetTracker.GetSequencedAckHistory(m_sequenceWindow)) // m_sequenceWindow is being passed in uninitialized, okay here since it's just a uint32_t
, m_localRolloverCount(InvalidSequenceRolloverCount)
{
const PacketId packetId = packetTracker.GetNextPacketId();
m_localSequence = ToSequenceId(packetId);
m_localRolloverCount = ToRolloverCount(packetId);
}
UdpPacketHeader::UdpPacketHeader(PacketType packetType, PacketId packetId)
: m_packetType(packetType)
, m_localSequence(InvalidSequenceId)
, m_remoteSequence(InvalidSequenceId)
, m_reliableSequence(InvalidSequenceId)
, m_sequenceWindow(0)
, m_localRolloverCount(InvalidSequenceRolloverCount)
{
m_localSequence = ToSequenceId(packetId);
m_localRolloverCount = ToRolloverCount(packetId);
}
UdpPacketHeader::UdpPacketHeader
(
PacketType packetType,
SequenceId localSequence,
SequenceId remoteSequence,
SequenceId reliableSequence,
BitsetChunk sequenceWindow,
SequenceRolloverCount localRolloverCount
)
: m_packetType(packetType)
, m_localSequence(localSequence)
, m_remoteSequence(remoteSequence)
, m_reliableSequence(reliableSequence)
, m_sequenceWindow(sequenceWindow)
, m_localRolloverCount(localRolloverCount)
{
;
}
bool UdpPacketHeader::Serialize(ISerializer& serializer)
{
bool isReliable = GetIsReliable();
serializer.Serialize(m_packetType, "PacketType");
serializer.Serialize(m_localSequence, "LocalSequence");
serializer.Serialize(m_remoteSequence, "RemoteSequence");
serializer.Serialize(m_sequenceWindow, "SequenceWindow");
serializer.Serialize(isReliable, "IsReliable");
// If the packet is flagged as reliable, serialize the reliable sequence id
if (isReliable)
{
serializer.Serialize(m_reliableSequence, "ReliableSequence");
}
return serializer.IsValid();
}
bool UdpPacketHeader::SerializePacketFlags(ISerializer& serializer)
{
return serializer.Serialize(m_packetFlags, "PacketFlags");
}
}
@@ -0,0 +1,132 @@
/*
* 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/PacketLayer/IPacket.h>
#include <AzNetworking/PacketLayer/IPacketHeader.h>
#include <AzNetworking/Serialization/ISerializer.h>
#include <AzNetworking/ConnectionLayer/ConnectionMetrics.h>
#include <AzNetworking/DataStructures/RingBufferBitset.h>
namespace AzNetworking
{
class UdpPacketTracker;
//! @class UdpPacketHeader
//! @brief Udp packet header class.
class UdpPacketHeader final
: public IPacketHeader
{
friend class UdpPacketIdWindow;
public:
AZ_RTTI(UdpPacketHeader, "{21A11FF3-6829-4A59-9906-C06EF7F39AC1}", IPacketHeader);
//! Default constructor, for when receiving a header from a remote connection.
UdpPacketHeader();
//! Constructor for generating a new header to send to a remote connection.
//! @param packetTracker packet delivery tracker instance for the connection in question
//! @param packetType type of packet
//! @param reliableSequence reliable sequence value, or InvalidSequenceId if the packet is unreliable
UdpPacketHeader(UdpPacketTracker& packetTracker, PacketType packetType, SequenceId reliableSequence);
//! Constructor for generating generic header with just a packet id, used for dispatching a bulk message.
//! @param packetType type of packet
//! @param packetId packet id we're duplicating
UdpPacketHeader(PacketType packetType, PacketId packetId);
//! Full constructor for unit tests.
//! @param packetType type of packet
//! @param localSequence local sequence number, this is the sequence for this packet instance
//! @param remoteSequence remote sequence number for ack replication, this is the latest sequence we've received from the remote endpoint
//! @param reliableSequence if this is a reliable packet, this is the reliable sequence number
//! @param sequenceWindow this is the ack vector for ack replication, corresponding to remoteSequence
//! @param localRolloverCount this is the reconstructed rollover count, used to convert localSequence to a full PacketId
UdpPacketHeader
(
PacketType packetType,
SequenceId localSequence,
SequenceId remoteSequence,
SequenceId reliableSequence,
BitsetChunk sequenceWindow,
SequenceRolloverCount localRolloverCount
);
~UdpPacketHeader() override = default;
//! IPacketHeader interface.
// @{
PacketType GetPacketType() const override;
PacketId GetPacketId() const override;
bool IsPacketFlagSet(PacketFlag flag) const override;
void SetPacketFlag(PacketFlag flag, bool value) override;
// @}
//! Sets the packet flag bitset for this packet.
//! @param flags The packet flag bitset
void SetPacketFlags(PacketFlagBitset flags);
//! Returns whether or not this header is for a reliably transmitted packet.
//! @return whether or not this header is for a reliably transmitted packet
bool GetIsReliable() const;
//! Retrieve the local sequence from this packet header.
//! @return packet header local sequence
SequenceId GetLocalSequenceId() const;
//! Retrieve the remote sequence being acked.
//! @return packet header remote sequence
SequenceId GetRemoteSequenceId() const;
//! Retrieve the reliable sequence if this was a reliable packet, InvalidSequenceId otherwise.
//! @return packet header reliable sequence if this was a reliable packet, InvalidSequenceId otherwise
SequenceId GetReliableSequenceId() const;
//! Retrieve the sequence window from this packet header.
//! @return the sequence window from this packet header
BitsetChunk GetSequenceWindow() const;
//! Retrieve the sequence rollover count from this packet header.
//! @return the sequence rollover count from this packet header
SequenceRolloverCount GetSequenceRolloverCount() 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);
//! Specialized serialize method for UDP Packet Flags
//! @param serializer ISerializer instance to use for serialization
//! @return boolean true for success, false for serialization failure
bool SerializePacketFlags(ISerializer& serializer);
private:
void SetLocalRolloverCount(SequenceRolloverCount rolloverCount);
PacketType m_packetType;
SequenceId m_localSequence;
SequenceId m_remoteSequence;
SequenceId m_reliableSequence;
BitsetChunk m_sequenceWindow;
SequenceRolloverCount m_localRolloverCount;
// UDP Packet flags are not serialized by the Serialize method and must be serialized separately
PacketFlagBitset m_packetFlags;
};
}
#include <AzNetworking/UdpTransport/UdpPacketHeader.inl>
@@ -0,0 +1,77 @@
/*
* 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 PacketType UdpPacketHeader::GetPacketType() const
{
return m_packetType;
}
inline PacketId UdpPacketHeader::GetPacketId() const
{
AZ_Assert(m_localRolloverCount != InvalidSequenceRolloverCount, "UdpPacketHeader: header was not initialized properly, PacketId is invalid");
return MakePacketId(m_localRolloverCount, m_localSequence);
}
inline bool UdpPacketHeader::IsPacketFlagSet(PacketFlag flag) const
{
return m_packetFlags.GetBit(aznumeric_cast<uint32_t>(flag));
}
inline void UdpPacketHeader::SetPacketFlag(PacketFlag flag, bool value)
{
m_packetFlags.SetBit(aznumeric_cast<uint32_t>(flag), value);
}
inline void UdpPacketHeader::SetPacketFlags(PacketFlagBitset flags)
{
m_packetFlags = flags;
}
inline bool UdpPacketHeader::GetIsReliable() const
{
return (m_reliableSequence != InvalidSequenceId);
}
inline SequenceId UdpPacketHeader::GetLocalSequenceId() const
{
return m_localSequence;
}
inline SequenceId UdpPacketHeader::GetRemoteSequenceId() const
{
return m_remoteSequence;
}
inline SequenceId UdpPacketHeader::GetReliableSequenceId() const
{
return m_reliableSequence;
}
inline BitsetChunk UdpPacketHeader::GetSequenceWindow() const
{
return m_sequenceWindow;
}
inline SequenceRolloverCount UdpPacketHeader::GetSequenceRolloverCount() const
{
return m_localRolloverCount;
}
inline void UdpPacketHeader::SetLocalRolloverCount(SequenceRolloverCount rolloverCount)
{
m_localRolloverCount = rolloverCount;
}
}
@@ -0,0 +1,216 @@
/*
* 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/UdpTransport/UdpPacketIdWindow.h>
#include <AzNetworking/UdpTransport/UdpPacketHeader.h>
#include <AzNetworking/UdpTransport/UdpConnection.h>
#include <AzNetworking/DataStructures/FixedSizeBitset.h>
#include <AzCore/Console/ILogger.h>
namespace AzNetworking
{
const char* GetEnumString(PacketAckState value)
{
switch (value)
{
case PacketAckState::Acked:
return "PacketAckState::Acked";
case PacketAckState::Nacked:
return "PacketAckState::Nacked";
case PacketAckState::Unknown_TooNew:
return "PacketAckState::Unknown_TooNew";
case PacketAckState::Unknown_TooOld:
return "PacketAckState::Unknown_TooOld";
}
return "INVALID";
}
UdpPacketIdWindow::UdpPacketIdWindow()
: m_headSequenceId(SequenceId{ 0 })
, m_headPacketId(InvalidPacketId)
, m_sequenceRolloverCount(SequenceRolloverCount{ 0 })
{
;
}
void UdpPacketIdWindow::Reset()
{
m_headSequenceId = SequenceId{ 0 };
m_headPacketId = InvalidPacketId;
m_sequenceRolloverCount = SequenceRolloverCount{ 0 };
m_ackWindow.Reset();
}
PacketAckState UdpPacketIdWindow::GetPacketAckStatus(PacketId packetId) const
{
// If we haven't heard from the remote endpoint yet, treat packets as having fallen outside our window
if (m_headPacketId == InvalidPacketId)
{
return PacketAckState::Nacked;
}
// Check if the requested sequence id is newer than any acked packet received
if (packetId > m_headPacketId)
{
AZLOG(NET_Acks, "Requested ack status for %u, head %u, too new", aznumeric_cast<uint32_t>(packetId), aznumeric_cast<uint32_t>(m_headPacketId));
return PacketAckState::Unknown_TooNew;
}
const PacketId packetDelta = m_headPacketId - packetId;
// Sequence is so old, it's now out of bounds of our packet ack tracker
if (aznumeric_cast<uint32_t>(packetDelta) >= m_ackWindow.GetValidBitCount())
{
AZLOG(NET_Acks, "Requested ack status for %u, head %u, too old", aznumeric_cast<uint32_t>(packetId), aznumeric_cast<uint32_t>(m_headPacketId));
return PacketAckState::Unknown_TooOld;
}
const bool acked = m_ackWindow.GetBit(aznumeric_cast<uint32_t>(packetDelta));
return acked ? PacketAckState::Acked : PacketAckState::Nacked;
}
BitsetChunk& UdpPacketIdWindow::GetMostRecentAckState(BitsetChunk& outWindow) const
{
const uint64_t firstChunk = aznumeric_cast<uint64_t>(m_ackWindow.GetBitsetElement(0));
const uint64_t secondChunk = aznumeric_cast<uint64_t>(m_ackWindow.GetBitsetElement(1));
const uint32_t unusedHeadBits = m_ackWindow.GetUnusedHeadBits();
const uint32_t usedHeadBits = m_ackWindow.NumBitsetChunkedBits - unusedHeadBits;
outWindow = aznumeric_cast<BitsetChunk>((secondChunk << usedHeadBits) | firstChunk);
return outWindow;
}
bool UdpPacketIdWindow::UpdateForReceivedPacket(UdpPacketHeader& header)
{
const SequenceId receivedSequenceId = header.GetLocalSequenceId();
if (SequenceMoreRecent(receivedSequenceId, m_headSequenceId))
{
const SequenceId sequenceDelta = SequenceId(receivedSequenceId - m_headSequenceId);
m_ackWindow.PushBackBits(aznumeric_cast<uint32_t>(sequenceDelta));
m_ackWindow.SetBit(0, true);
// We've already determined that receivedSequenceId is 'newer' than m_headSequenceId
// So if receivedSequenceId is numerically less than m_headSequenceId, we have rolled over
if (receivedSequenceId < m_headSequenceId)
{
++m_sequenceRolloverCount;
}
// This is the newest packet, it's rollover count is always the most recent rollover count
header.SetLocalRolloverCount(m_sequenceRolloverCount);
m_headSequenceId = receivedSequenceId;
m_headPacketId = MakePacketId(m_sequenceRolloverCount, m_headSequenceId);
}
else
{
// This is an out of order packet, we've previously received a newer sequence value
const SequenceId sequenceDelta = m_headSequenceId - receivedSequenceId;
if (aznumeric_cast<uint32_t>(sequenceDelta) >= m_ackWindow.GetValidBitCount())
{
// Too old to process
AZLOG(NET_DebugUdp, "Discarding old packet, sequence is too old to process");
return false;
}
if (m_ackWindow.GetBit(aznumeric_cast<uint32_t>(sequenceDelta)))
{
// Received packet is a duplicate of one already processed
AZLOG(NET_DebugUdp, "Discarding packet due to duplicated sequence id");
return false;
}
m_ackWindow.SetBit(aznumeric_cast<uint32_t>(sequenceDelta), true);
// Received sequence is 'older' than head sequence
if (receivedSequenceId < m_headSequenceId)
{
// If the 'older' received sequence is numerically less than head sequence, the rollover count is unchanged
header.SetLocalRolloverCount(m_sequenceRolloverCount);
}
else
{
// If the 'older' received sequence is numerically greater than than head sequence, we've received a packet bound to the previous rollover count
header.SetLocalRolloverCount(m_sequenceRolloverCount - SequenceRolloverCount{ 1 });
}
}
return true;
}
void UdpPacketIdWindow::UpdateForRemoteAckStatus(UdpConnection* connection, UdpPacketHeader& header)
{
const SequenceId receivedSequenceId = header.GetRemoteSequenceId();
const BitsetChunk sequenceWindow = header.GetSequenceWindow();
// Reconstruct the ack state of the remote connection based on remote sequence number and ack bits
// Align the received and cached ack bit vectors
if (SequenceMoreRecent(receivedSequenceId, m_headSequenceId))
{
// We've already determined that receivedSequenceId is 'newer' than m_headSequenceId
// So if receivedSequenceId is numerically less than m_headSequenceId, we have rolled over
if (receivedSequenceId < m_headSequenceId)
{
++m_sequenceRolloverCount;
}
AZLOG
(
NET_DebugUdp,
"Updating ack data, old head sequence %u, new head sequence %u, ack vector %X",
aznumeric_cast<uint32_t>(m_headSequenceId),
aznumeric_cast<uint32_t>(receivedSequenceId),
sequenceWindow
);
const AZ::TimeMs currentTimeMs = AZ::GetElapsedTimeMs();
const SequenceId sequenceIdDelta = SequenceId(receivedSequenceId - m_headSequenceId);
const PacketId receivedPacketId = MakePacketId(m_sequenceRolloverCount, receivedSequenceId);
m_headSequenceId = receivedSequenceId;
m_headPacketId = receivedPacketId;
m_ackWindow.PushBackBits(aznumeric_cast<uint32_t>(sequenceIdDelta));
for (uint32_t bit = 0; bit < m_ackWindow.NumBitsetChunkedBits; ++bit)
{
if (!GetBitHelper(sequenceWindow, bit))
{
continue;
}
if (!m_ackWindow.GetBit(bit))
{
m_ackWindow.SetBit(bit, true);
AZLOG(NET_DebugUdp, "Acking packet ID %u", aznumeric_cast<uint32_t>(receivedPacketId) - bit);
if (connection != nullptr)
{
connection->ProcessAcked(receivedPacketId - aznumeric_cast<PacketId>(bit), currentTimeMs);
}
}
}
}
}
void UdpPacketIdWindow::PrintStatus() const
{
AZLOG_INFO
(
"%u - %08X:%08X:%08X:%08X",
aznumeric_cast<uint32_t>(m_headSequenceId),
aznumeric_cast<uint32_t>(m_ackWindow.GetBitsetElement(0)),
aznumeric_cast<uint32_t>(m_ackWindow.GetBitsetElement(1)),
aznumeric_cast<uint32_t>(m_ackWindow.GetBitsetElement(2)),
aznumeric_cast<uint32_t>(m_ackWindow.GetBitsetElement(3))
);
}
}

Some files were not shown because too many files have changed in this diff Show More