// Copyright Epic Games, Inc. All Rights Reserved. #pragma once #include "Containers/Array.h" #include "Containers/ArrayView.h" #include "Containers/Map.h" #include "Memory/SharedBuffer.h" #include "Misc/Guid.h" #include "Serialization/CompactBinaryContainerSerialization.h" #include "Serialization/CompactBinarySerialization.h" #include "Serialization/CompactBinaryWriter.h" #include "UObject/SoftObjectPath.h" class FSocket; namespace UE::CompactBinaryTCP { class FCompactBinaryTCPImpl; struct FReceiveBuffer; struct FSendBuffer; // Our Socket API restricts us to int32 Count field (Max == 1 << 31 - 1), but we further // restrict that by a factor of two to prevent impact from any OS limitations less than that // and to allow room for the 16 byte PacketHeader. constexpr uint64 MaxOSPacketSize = (1 << 30) - 1; enum class EConnectionStatus { /** Connection is still okay or operation succeeded. */ Okay, /** Connection failed and no further use of the Socket is possible. */ Terminated, /** The Socket is still active but the data received was invalid and recovery is not possible. */ FormatError, /** The operation failed and the Socket is no longer usable. */ Failed, /** The Socket is busy and the operation needs to be retried later */ Incomplete, }; const TCHAR* DescribeStatus(EConnectionStatus Status); /** The atom of data that can be sent through CompactBinaryTCP: guid identifier and compact binary payload. */ struct FMarshalledMessage { FGuid MessageType; FCbObject Object; }; /** * Attempt to read messages from the socket. Returns all messages currently available and returns. Partially * transferred messages are stored in Buffer and can be completed by further calls to TryReadPacket. * * @param MaxPacketSize If non-zero, all messages are assumed to be <= and a FormatError is given if they are larger. * * @return EConnectionStatus::Okay on success and EConnectionStatus::Terminated or EConnection::FormatError on error. * If EConnectionStatus::Okay is returned, Messages may be empty (no message yet received) or populated. */ EConnectionStatus TryReadPacket(FSocket* Socket, FReceiveBuffer& Buffer, TArray& Messages, uint64 MaxPacketSize=0); /** * Attempt to write messages to the socket. If the socket is busy the new messages are stored in Buffer and will be * sent on a future call to TryWritePacket. * * @param MaxPacketSize When combining multiple messages into a single packet, messages will stopped being added and * will be pushed into the next packet if the packet size is > this amount. If 0, will use the maximum size allowed * by the socket, which is >= 2^30. If a single message has larger size than MaxPacketSize, EConnectionStatus::Failed * is return and the socket will no longer be usable. * * @return EConnectionStatus::Okay on success and all messages have been sent. EConnectionStatus::Incomplete if some * messages could not yet be sent. EConnectionStatus::Failed if connection failed and socket is no longer usable. */ EConnectionStatus TryWritePacket(FSocket* Socket, FSendBuffer& Buffer, TArray&& AppendMessages, uint64 MaxPacketSize = 0); EConnectionStatus TryWritePacket(FSocket* Socket, FSendBuffer& Buffer, FMarshalledMessage&& AppendMessage, uint64 MaxPacketSize = 0); void QueueMessage(FSendBuffer& Buffer, FMarshalledMessage&& Message); /** Attempt to finish sending packets that were previously queued by TryWritePacket */ EConnectionStatus TryFlushBuffer(FSocket* Socket, FSendBuffer& Buffer, uint64 MaxPacketSize = 0); /** * Holds the state of the CompactBinaryTCP communication read from a Socket. Expects the socket to be a * series of packets of messages and keeps track of the next packet when a packet is as yet only partially received. */ struct FReceiveBuffer { public: void Reset(); private: FUniqueBuffer Payload; uint64 BytesRead = 0; bool bParsedHeader = false; friend class UE::CompactBinaryTCP::FCompactBinaryTCPImpl; }; /** * Holds the state of the CompactBinaryTCP communication written to a Socket. Allows the socket to be a * series of packets of messages and keeps track of a partially sent packet and of pending messages. */ struct FSendBuffer { public: void Reset(); private: TArray PendingMessages; FUniqueBuffer Payload; uint64 BytesWritten = 0; bool bCreatedPayload = false; bool bSentHeader = false; friend class UE::CompactBinaryTCP::FCompactBinaryTCPImpl; }; } template inline bool LoadFromCompactBinary(FCbFieldView Field, TSet& OutValue) { OutValue.Reset(); OutValue.Reserve(Field.AsArrayView().Num()); bool bOk = !Field.HasError(); for (const FCbFieldView& ElementField : Field) { KeyType Key; if (LoadFromCompactBinary(ElementField, Key)) { OutValue.Add(MoveTemp(Key)); } else { bOk = false; } } return bOk; } template () << std::declval())>* = nullptr> inline FCbWriter& operator<<(FCbWriter& Writer, const TSet& Value) { Writer.BeginArray(); for (const KeyType& Key : Value) { Writer << Key; } Writer.EndArray(); return Writer; } template inline bool LoadFromCompactBinary(FCbFieldView Field, TMap& OutValue) { OutValue.Reset(); OutValue.Reserve(Field.AsArrayView().Num()); bool bOk = !Field.HasError(); for (const FCbFieldView& PairField : Field) { bOk &= PairField.IsObject(); KeyType Key; if (LoadFromCompactBinary(PairField["K"], Key)) { ValueType& Value = OutValue.FindOrAdd(MoveTemp(Key)); bOk = LoadFromCompactBinary(PairField["V"], Value) & bOk; } else { bOk = false; } } return bOk; } template () << std::declval())>* = nullptr, std::void_t() << std::declval())>* = nullptr> inline FCbWriter& operator<<(FCbWriter& Writer, const TMap& Value) { Writer.BeginArray(); for (const auto& Pair : Value) { Writer.BeginObject(); Writer << "K" << Pair.Key; Writer << "V" << Pair.Value; Writer.EndObject(); } Writer.EndArray(); return Writer; } inline FCbWriter& operator<<(FCbWriter& Writer, const UE::CompactBinaryTCP::FMarshalledMessage& Value) { Writer.BeginObject(); Writer << "T" << Value.MessageType; Writer << "V" << Value.Object; Writer.EndObject(); return Writer; } UNREALED_API bool LoadFromCompactBinary(FCbFieldView Field, UE::CompactBinaryTCP::FMarshalledMessage& Value); // FSoftObjectPath has an implicit constructor from FString for backwards compatibility; if we // try to create an operator<< for it, it will cause operator<< to be ambiguous. // This prevents us from using it directly in containers. To hack around this, // containers of FSoftObjectPath need to be reinterpret_casted to containers of FSoftObjectPathSerializationWrapper struct FSoftObjectPathSerializationWrapper { FSoftObjectPath Inner; bool operator==(const FSoftObjectPathSerializationWrapper& Other) const { return Inner == Other.Inner; } friend uint32 GetTypeHash(const FSoftObjectPathSerializationWrapper& Wrapper) { return GetTypeHash(Wrapper.Inner); } }; FCbWriter& operator<<(FCbWriter& Writer, const FSoftObjectPathSerializationWrapper& Path); bool LoadFromCompactBinary(FCbFieldView Field, FSoftObjectPathSerializationWrapper& Path);