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,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;
};
}