Reece Wilson
67905a4192
============================================================================= Network ]==================================================================== ============================================================================= [+] Added (very) early Aurora::IO::Net implementation [+] AuNet::EHostnameType [+] AuNet::EIPProtocol [+] AuNet::ENetworkError [+] AuNet::ETransportProtocol [+] AuNet::INetInterface [+] AuNet::INetSrvDatagram [+] AuNet::INetSrvResolve [+] AuNet::INetSrvSockets [+] AuNet::INetSrvWorkers [+] AuNet::INetWorker [+] AuNet::IPAddress [+] AuNet::IResolver [+] AuNet::ISocket [+] AuNet::IResolver [+] AuNet::ISocketBase [+] AuNet::ISocketChannel [+] AuNet::ISocketDriver [+] AuNet::ISocketDriverFactory [+] AuNet::ISocketServer [+] AuNet::ISocketServerDriver [+] AuNet::NetEndpoint [+] AuNet::NetError [+] AuNet::NetHostname (+implementation) ============================================================================= Protocol ]=================================================================== ============================================================================= [+] IProtocolInterceptor [+] IProtocolInterceptorEx [+] IProtocolStack (+implementation) ============================================================================= TLS ]======================================================================== ============================================================================= [+] ITLSContext [+] TLSProtocolRecv [+] TLSProtocolSend (+implementation) ============================================================================= IO Bug Fixes ]=============================================================== ============================================================================= [*] IOProcessor::SubmitIOWorkItem should signal the CvEvent, forcing at least once future tick (wont optimize with if in tick & not yet dispatched work items) [*] Split IOPipeWork in into IOPipeProcessor header [+] IOPipeWork::GetBuffer (internal reallocation) [*] Harden against IAsyncTransactions without a loop source [*] Missing null `if (processor->listener)` in IOProcessor [*] Solved some soft-lock conditions under Linux's LoopQueue (added deferred commits) [*] Quick hack: IOProcessor::HasItems() should OR the early can-tick check function. ============================================================================= Other ]====================================================================== ============================================================================= [+] Linux: LSSignalCatcher [+] `static void AuResetMember(Aurora::Memory::ByteBuffer &ref)` for AuROXTL [*] Attempt to enforce a normalization and don't overwrite-readptr-under-istreamwriters policy in ByteBuffer_ReadWrite (circular buffers) [*] Bad ECC ctors ============================================================================= Known issues ]=============================================================== ============================================================================= > Linux net is nowhere near done > UDP socket emulation layer isn't implemented > Ciphersuite API is a stub > Private key API is a stub > ...therefore no TLS servers > Missing thread safety precautions under net > Net implementation is still beri early
482 lines
15 KiB
C++
482 lines
15 KiB
C++
/***
|
|
Copyright (C) 2021 J Reece Wilson (a/k/a "Reece"). All rights reserved.
|
|
|
|
File: Net.hpp
|
|
Date: 2021-9-21
|
|
Author: Reece
|
|
***/
|
|
#pragma once
|
|
|
|
namespace Aurora::Async
|
|
{
|
|
struct IThreadPool;
|
|
}
|
|
|
|
namespace Aurora::IO::Net
|
|
{
|
|
}
|
|
|
|
#if 0
|
|
namespace Aurora::IO::Net
|
|
{
|
|
static const AuUInt16 kMagicPortAny = 0;
|
|
|
|
struct INetworkStream;
|
|
struct IBasicSocket;
|
|
struct ISocket;
|
|
struct IServer;
|
|
|
|
AUE_DEFINE(ESocketInfo, (
|
|
eByDns,
|
|
eByEndpoint
|
|
));
|
|
|
|
|
|
AUE_DEFINE(EHandleErrorClass, (
|
|
ePinError,
|
|
eUserDeny,
|
|
eBrokenPacket,
|
|
eInvalidCipher,
|
|
eBadCert
|
|
));
|
|
|
|
struct AUKN_SYM IPAddress
|
|
{
|
|
EIPProtocol ip;
|
|
union
|
|
{
|
|
AuUInt8 v4[4];
|
|
AuUInt16 v6[8];
|
|
};
|
|
|
|
//IPAddress();
|
|
//IPAddress(const AuString &parse);
|
|
//
|
|
//AuString ToString() const;
|
|
//bool IsValid() const;
|
|
//
|
|
//
|
|
//inline operator bool() const
|
|
//{
|
|
// return IsValid();
|
|
//}
|
|
//
|
|
//inline bool operator ==(const IPAddress &cmp) const
|
|
//{
|
|
// if (cmp.ip != this->ip) return false;
|
|
//
|
|
// if (cmp.ip == EIPProtocol::eIPProtocolV4)
|
|
// {
|
|
// return AuMemcmp(cmp.v4, this->v4, sizeof(this->v4)) == 0;
|
|
// }
|
|
// else
|
|
// {
|
|
// return AuMemcmp(cmp.v6, this->v6, sizeof(this->v6)) == 0;
|
|
// }
|
|
//}
|
|
};
|
|
|
|
struct SocketHostName
|
|
{
|
|
inline SocketHostName(const AuString &name) : info(ESocketInfo::eByDns), hostname(name), address()
|
|
{}
|
|
|
|
inline SocketHostName(const IPAddress &endpoint) : info(ESocketInfo::eByEndpoint), address(endpoint), hostname()
|
|
{}
|
|
|
|
const ESocketInfo info;
|
|
const AuString hostname;
|
|
const IPAddress address;
|
|
|
|
inline bool operator ==(const SocketHostName &cmp) const
|
|
{
|
|
if (cmp.info != this->info) return false;
|
|
if (cmp.info == ESocketInfo::eByEndpoint)
|
|
{
|
|
return false;// cmp.address == this->address;
|
|
}
|
|
else
|
|
{
|
|
return cmp.hostname == this->hostname;
|
|
}
|
|
}
|
|
};
|
|
|
|
struct IPEndpoint
|
|
|
|
|
|
struct ConnectionEndpoint
|
|
{
|
|
ETransportProtocol protocol;
|
|
IPEndpoint ip;
|
|
bool tls {};
|
|
bool compressed {};
|
|
// 0 - destination is a stateless datagram server
|
|
// 1 - destination is a psuedo-stateful server
|
|
AuUInt32 UDPTimeoutInMS;
|
|
};
|
|
|
|
struct ITLSHandshakeAuthenticate
|
|
{
|
|
virtual AuSPtr<Crypto::X509::DecodedCertificate> GetCertificate() = 0;
|
|
};
|
|
|
|
struct NetError
|
|
{
|
|
ENetworkError error { ENetworkError::kEnumInvalid };
|
|
AuUInt osError;
|
|
};
|
|
|
|
struct TLSHandshakeError
|
|
{
|
|
EHandleErrorClass error;
|
|
AuSPtr<ITLSHandshakeAuthenticate> session;
|
|
AuString message;
|
|
};
|
|
|
|
|
|
AUKN_INTERFACE(INetTask,
|
|
AUI_METHOD(void, DoWork, ())
|
|
);
|
|
|
|
AUKN_INTERFACE(IClientSubscriber,
|
|
//
|
|
AUI_METHOD(void, OnServerConnectSuccess, (const AuSPtr<ISocket> &, socket)),
|
|
AUI_METHOD(void, OnServerConnectFailed, (const AuSPtr<ISocket> &, socket)),
|
|
|
|
// DTLS/UDP/TCP/TLS -> TRUE = expects another datagram or read pump
|
|
// FALSE = end of socket life
|
|
AUI_METHOD(bool, OnSocketData, (const AuSPtr<ISocket> &, socket)),
|
|
|
|
//
|
|
AUI_METHOD(void, OnSocketError, (const AuSPtr<ISocket> &, socket)),
|
|
|
|
//
|
|
AUI_METHOD(void, OnSocketShutdown, (const AuSPtr<ISocket> &, socket))
|
|
);
|
|
|
|
AUKN_INTERFACE(IClientSubscriberTls,
|
|
AUI_METHOD(bool, OnVerifySocketCertificate, (const AuSPtr<IBasicSocket> &, socket, const AuSPtr<ITLSHandshakeAuthenticate>, session)),
|
|
AUI_METHOD(bool, OnTLSHandleError, (const AuSPtr<IBasicSocket> &, socket, const TLSHandshakeError &, error))
|
|
);
|
|
|
|
AUKN_INTERFACE(IServerSubscriber,
|
|
AUI_METHOD(bool, OnClientAccept, (const AuSPtr<IServer> &, server, const AuSPtr<ISocket> &, socket)),
|
|
AUI_METHOD(bool, OnClientDoS, (const AuSPtr<IServer> &, server, const AuSPtr<ISocket> &, socket)),
|
|
AUI_METHOD(void, OnClientError, (const AuSPtr<IServer> &, server, const AuSPtr<ISocket> &, socket)),
|
|
AUI_METHOD(void, OnClientShutdown, (const AuSPtr<IServer> &, server, const AuSPtr<ISocket> &, socket)),
|
|
AUI_METHOD(bool, OnReadFrame, (const AuSPtr<IServer> &, server, const AuList<AuSPtr<ISocket>> &, sockets)),
|
|
AUI_METHOD(void, OnShutdown, (const AuSPtr<IServer> &, server))
|
|
);
|
|
|
|
AUKN_INTERFACE(IServerSubscriberTls,
|
|
AUI_METHOD(bool, OnClientTLSReport, (const AuSPtr<IServer> &, server, const AuSPtr<ISocket> &, socket, const TLSHandshakeError &, error))
|
|
);
|
|
|
|
// TODO: We should introduce another std:: customer overloadable type reproducing hardcoded ascii and an int, basically std::error_code
|
|
// Maybe AuErrorCode = [std::, my_fav_stl::]error_code
|
|
// AuError = something more flexable
|
|
using Error_t = NetError;
|
|
|
|
struct IBasicSocketPrivateContext
|
|
{
|
|
// force vtbl
|
|
virtual ~IBasicSocketPrivateContext()
|
|
{}
|
|
};
|
|
|
|
struct SocketStatStream
|
|
{
|
|
AuUInt64 total;
|
|
AuUInt64 averageBytesPerSecond; // interpolated, behind 1 frame
|
|
AuUInt64 extrapolatedBytesPerSecond; // this uses an extrapolated time point to predict the network bandwidth of one whole second of data
|
|
};
|
|
|
|
struct SocketStat
|
|
{
|
|
AuUInt32 timeStartMs;
|
|
AuUInt32 uptimeMs;
|
|
AuUInt64 bandwidthCost;
|
|
|
|
AuUInt32 blamedConnectTimeMS;
|
|
AuUInt32 totalConnectTimeMS;
|
|
|
|
SocketStatStream rx;
|
|
SocketStatStream rt;
|
|
};
|
|
|
|
struct IBasicSocket
|
|
{
|
|
/**
|
|
* @brief Is socket still established, listening, or otherwise capable of performing non-recv operations?
|
|
* @return
|
|
*/
|
|
virtual bool IsActive() = 0;
|
|
|
|
/**
|
|
* @brief Returns the last relevant error
|
|
* @return
|
|
*/
|
|
virtual Error_t GetLastError() = 0;
|
|
|
|
/**
|
|
* @brief Initiates the shutdown sequence on the socket
|
|
*/
|
|
virtual void Shutdown() = 0;
|
|
|
|
/**
|
|
* @brief Exchanges the user-provided private context
|
|
* @param newContext
|
|
* @return
|
|
*/
|
|
virtual AuSPtr<IBasicSocketPrivateContext> SetContext(const AuSPtr<IBasicSocketPrivateContext> &newContext) = 0;
|
|
|
|
/**
|
|
* @brief Returns a user-provided private context
|
|
* @return
|
|
*/
|
|
virtual AuSPtr<IBasicSocketPrivateContext> GetContext() = 0;
|
|
|
|
/**
|
|
* @brief Returns local endpoint of the socket once established/listening
|
|
* @param out
|
|
* @return
|
|
*/
|
|
virtual bool GetLocalEndpoint(ConnectionEndpoint &out) = 0;
|
|
|
|
/**
|
|
* @brief Returns performance stats of the socket
|
|
* @return
|
|
*/
|
|
virtual SocketStat GetStats() = 0;
|
|
|
|
|
|
/**
|
|
* @brief Schedule work on the thread assigned to the socket
|
|
* @param task
|
|
* @return
|
|
*/
|
|
virtual bool DoWork(AuSPtr<INetTask> task) = 0;
|
|
};
|
|
|
|
AUKN_INTERFACE(ISocketSubmissionComplete,
|
|
AUI_METHOD(void, OnWriteFinished, (AuUInt, fence))
|
|
);
|
|
|
|
struct OverlappedOperation
|
|
{
|
|
AuUInt fence;
|
|
AuSPtr<ISocketSubmissionComplete> callback;
|
|
};
|
|
|
|
struct StreamConfig
|
|
{
|
|
AuUInt32 recvRingBufferLength;
|
|
};
|
|
|
|
struct SocketConfig
|
|
{
|
|
StreamConfig stream;
|
|
};
|
|
|
|
struct ISocketChannel
|
|
{
|
|
// If memory.ptr is a nullptr, this method immediately returns with the expected write length in memory.out
|
|
//
|
|
// If all is true and the internal buffer is not saturated enough yet, no data is read and
|
|
// zero readable bytes are returned.
|
|
//
|
|
// If all is false, copies memory.length into memory.ptr, up to memory.length
|
|
//
|
|
// psuedocode:
|
|
// If all is false,
|
|
// memory.outVariable = readableBytes
|
|
// if memory.ptr,
|
|
// begin copy from the internal upto max(memory.length, outVariable) into memory.ptr,
|
|
// end
|
|
// else
|
|
// return readExactly(memory)
|
|
//
|
|
// NOTE: BufferInputStreamAdhoc usage applys to async reads as well
|
|
virtual bool ReadAsync(const Memory::MemoryViewStreamWrite &memory, bool all = false) = 0;
|
|
|
|
// Atomic
|
|
// ReadAsync(memory, false)
|
|
// SeekAsync(-memory.outVariable)
|
|
virtual bool PeekAsync(const Memory::MemoryViewStreamWrite &memory) = 0;
|
|
|
|
// Attempts to seek backwards or forwards in the UDP or TCP packet
|
|
// If you are under the callstack of a HasXXXHasData callback, you are guaranteed (bufferedReadSize - streamPosition - streamRemaining) bytes backwards
|
|
//
|
|
virtual bool SeekAsync(int signedDistanceFromCur = 0) = 0;
|
|
|
|
// Writes max(cumulative memory.length per frame, (enableBufferedInput ? bufferedWriteSize : os page allowance)) to the sockets asynchronous stream
|
|
// Returns false when no data whatsoever was written, generic error condition
|
|
// Returns true when some data was collected, regardless of any errors that may have arose (defer to IServerSubscriber::OnClientError, IClientSubscriber::OnSocketError for error handling)
|
|
virtual bool WriteAsync(const AuSPtr<Memory::MemoryViewRead> &memory, const OverlappedOperation &fence) = 0;
|
|
virtual bool WriteAsync(const AuSPtr<Memory::MemoryViewRead> &memory) = 0;
|
|
|
|
|
|
/**
|
|
* Sets the internal application buffer size
|
|
* Noting that Linux's default network buffer looks like this:
|
|
* Minimum, Initial, Maximum: 10240 87380 12582912 | 10KB, 86KB, 12MB
|
|
*/
|
|
virtual AuUInt GetInternalInputRingBuffer() = 0;
|
|
virtual bool SetInternalInputRingBuffer(AuUInt bytes) = 0;
|
|
|
|
|
|
#if 0
|
|
/**
|
|
* Defines the maximum amount of bytes each recieve frame can ingest
|
|
*/
|
|
virtual void SetRecvPerFrameLength(AuUInt32 length) = 0;
|
|
virtual AuUInt32 GetRecvPerFrameLength() = 0;
|
|
#endif
|
|
};
|
|
|
|
|
|
struct ISocket : public IBasicSocket, public ISocketChannel
|
|
{
|
|
virtual bool GetRemoteEndpoint(ConnectionEndpoint &out) = 0;
|
|
};
|
|
|
|
struct ILocalClientSocket : public ISocket
|
|
{
|
|
// Completion will be notified by the following callbacks;
|
|
// IClientSubscriber::OnServerConnectSuccess,
|
|
// IClientSubscriber::OnServerConnectFailed
|
|
//
|
|
// ...on any worker thread under a generic pump or read only pump cycle
|
|
virtual void ConnectAsync() = 0;
|
|
};
|
|
|
|
struct TlsConnect
|
|
{
|
|
Aurora::Crypto::X509::Certificate serverCertificate;
|
|
AuSPtr<IBasicSocket> socket;
|
|
};
|
|
|
|
struct ServerInfo
|
|
{
|
|
ConnectionEndpoint listen;
|
|
AuUInt32 maxSessions;
|
|
SocketConfig clientDefaults;
|
|
AuSPtr<IServerSubscriber> serverSubscriber;
|
|
};
|
|
|
|
struct TLSServerInfo : ServerInfo
|
|
{
|
|
Aurora::Crypto::RSAPair cert;
|
|
AuSPtr<IServerSubscriberTls> tlsServerSubscriber;
|
|
};
|
|
|
|
struct ClientConfig : SocketConfig
|
|
{
|
|
IPEndpoint endpoint;
|
|
bool enableHasDataCallback {true};
|
|
AuSPtr<IClientSubscriber> clientSubscriber;
|
|
};
|
|
|
|
struct TLSClientConfig : ClientConfig
|
|
{
|
|
AuSPtr<IClientSubscriberTls> clientSubscriber;
|
|
};
|
|
|
|
struct IServer : public IBasicSocket
|
|
{
|
|
virtual void GetClients(AuList<AuSPtr<ISocket>> &clients) = 0;
|
|
virtual bool Listen() = 0;
|
|
virtual void ReconfigureDefaultStream(const StreamConfig &config) = 0;
|
|
};
|
|
|
|
struct ServiceEndpoint
|
|
{
|
|
ETransportProtocol protocol;
|
|
AuString hostname;
|
|
AuString service;
|
|
};
|
|
|
|
|
|
struct AUKN_SYM WorkRange
|
|
{
|
|
WorkRange();
|
|
|
|
AuUInt8 workerOffsetOrAny;
|
|
AuUInt8 workerCountOrAny;
|
|
};
|
|
|
|
struct AUKN_SYM WorkPoolGroup
|
|
{
|
|
WorkPoolGroup();
|
|
|
|
AuSPtr<Async::IThreadPool> pool; // optional -> defaults to async apps core loop
|
|
AuUInt8 asyncWorkGroup;
|
|
AuUInt8 asyncWorkPoolOffsetOrAny;
|
|
WorkRange range;
|
|
};
|
|
|
|
struct INetworkingPool
|
|
{
|
|
// A: Manual poll
|
|
virtual AuUInt32 PollWorker(AuUInt8 workerId, bool clientBound, bool writeBound) = 0;
|
|
virtual bool RunWorkerFrame(AuUInt8 workerId, AuUInt32 timeout) = 0;
|
|
|
|
// B: Ad-hoc loop-source IO
|
|
virtual bool BeginReadPollingOnWorkQueues(const WorkPoolGroup &workGroup) = 0;
|
|
virtual bool BeginSubmissionsOnOnWorkQueues(const WorkPoolGroup &workGroup) = 0;
|
|
virtual void StopPollingOnWorkQueues() = 0;
|
|
|
|
// C: Fixed timestep IO
|
|
virtual bool BeginNetworkFrequency(AuUInt32 MSTimeStep, const WorkPoolGroup &workGroup) = 0;
|
|
virtual void StopNetworkFrequency() = 0;
|
|
|
|
// Submit to worker thread
|
|
virtual bool DoWork(AuUInt8 index, AuSPtr<INetTask> task) = 0;
|
|
virtual bool DoWork(AuSPtr<INetTask> task) = 0;
|
|
|
|
// Worker count
|
|
virtual AuUInt8 GetWorkers() = 0;
|
|
};
|
|
|
|
struct NetworkPool
|
|
{
|
|
AuUInt8 workers {1};
|
|
};
|
|
|
|
|
|
struct INetworkDatagramService
|
|
{
|
|
virtual bool SendDatagramAsync(const ConnectionEndpoint &endpoint, const Memory::MemoryViewRead &memory) = 0;
|
|
virtual bool SendDatagramAsync(const AuSPtr<IServer> &datagramServer, const ConnectionEndpoint &endpoint, const Memory::MemoryViewRead &memory) = 0;
|
|
};
|
|
|
|
struct INetworkBasicResolve
|
|
{
|
|
virtual AuList<IPEndpoint> ResolveSocketSync(const SocketHostName &hostname, AuUInt16 port) = 0;
|
|
virtual AuList<IPEndpoint> ResolveServiceSync(const ServiceEndpoint &service) = 0;
|
|
};
|
|
|
|
struct ISocketService
|
|
{
|
|
virtual bool NewServer(const ServerInfo &listen, AuSPtr<IServer> &out) = 0;
|
|
virtual bool NewTlsServer(const TLSServerInfo &keys, AuSPtr<IServer> &out) = 0;
|
|
|
|
virtual bool NewClient(const ClientConfig &info, AuSPtr<ILocalClientSocket> &out) = 0;
|
|
virtual bool NewTlsClient(const TLSClientConfig &info, AuSPtr<ILocalClientSocket> &out) = 0;
|
|
};
|
|
|
|
struct INetworkInterface
|
|
{
|
|
virtual INetworkingPool *ToPool() = 0;
|
|
|
|
virtual INetworkDatagramService *ToDatagramService() = 0;
|
|
virtual INetworkBasicResolve * ToResolveService() = 0;
|
|
virtual ISocketService * ToSocketService() = 0;
|
|
|
|
virtual void Shutdown() = 0;
|
|
};
|
|
|
|
AUKN_SHARED_API(CreateNetworkPool, INetworkInterface, const NetworkPool & meta);
|
|
}
|
|
#endif
|