/*** 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 GetCertificate() = 0; }; struct NetError { ENetworkError error { ENetworkError::kEnumInvalid }; AuUInt osError; }; struct TLSHandshakeError { EHandleErrorClass error; AuSPtr session; AuString message; }; AUKN_INTERFACE(INetTask, AUI_METHOD(void, DoWork, ()) ); AUKN_INTERFACE(IClientSubscriber, // AUI_METHOD(void, OnServerConnectSuccess, (const AuSPtr &, socket)), AUI_METHOD(void, OnServerConnectFailed, (const AuSPtr &, socket)), // DTLS/UDP/TCP/TLS -> TRUE = expects another datagram or read pump // FALSE = end of socket life AUI_METHOD(bool, OnSocketData, (const AuSPtr &, socket)), // AUI_METHOD(void, OnSocketError, (const AuSPtr &, socket)), // AUI_METHOD(void, OnSocketShutdown, (const AuSPtr &, socket)) ); AUKN_INTERFACE(IClientSubscriberTls, AUI_METHOD(bool, OnVerifySocketCertificate, (const AuSPtr &, socket, const AuSPtr, session)), AUI_METHOD(bool, OnTLSHandleError, (const AuSPtr &, socket, const TLSHandshakeError &, error)) ); AUKN_INTERFACE(IServerSubscriber, AUI_METHOD(bool, OnClientAccept, (const AuSPtr &, server, const AuSPtr &, socket)), AUI_METHOD(bool, OnClientDoS, (const AuSPtr &, server, const AuSPtr &, socket)), AUI_METHOD(void, OnClientError, (const AuSPtr &, server, const AuSPtr &, socket)), AUI_METHOD(void, OnClientShutdown, (const AuSPtr &, server, const AuSPtr &, socket)), AUI_METHOD(bool, OnReadFrame, (const AuSPtr &, server, const AuList> &, sockets)), AUI_METHOD(void, OnShutdown, (const AuSPtr &, server)) ); AUKN_INTERFACE(IServerSubscriberTls, AUI_METHOD(bool, OnClientTLSReport, (const AuSPtr &, server, const AuSPtr &, 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 SetContext(const AuSPtr &newContext) = 0; /** * @brief Returns a user-provided private context * @return */ virtual AuSPtr 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 task) = 0; }; AUKN_INTERFACE(ISocketSubmissionComplete, AUI_METHOD(void, OnWriteFinished, (AuUInt, fence)) ); struct OverlappedOperation { AuUInt fence; AuSPtr 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, const OverlappedOperation &fence) = 0; virtual bool WriteAsync(const AuSPtr &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 socket; }; struct ServerInfo { ConnectionEndpoint listen; AuUInt32 maxSessions; SocketConfig clientDefaults; AuSPtr serverSubscriber; }; struct TLSServerInfo : ServerInfo { Aurora::Crypto::RSAPair cert; AuSPtr tlsServerSubscriber; }; struct ClientConfig : SocketConfig { IPEndpoint endpoint; bool enableHasDataCallback {true}; AuSPtr clientSubscriber; }; struct TLSClientConfig : ClientConfig { AuSPtr clientSubscriber; }; struct IServer : public IBasicSocket { virtual void GetClients(AuList> &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 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 task) = 0; virtual bool DoWork(AuSPtr 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 &datagramServer, const ConnectionEndpoint &endpoint, const Memory::MemoryViewRead &memory) = 0; }; struct INetworkBasicResolve { virtual AuList ResolveSocketSync(const SocketHostName &hostname, AuUInt16 port) = 0; virtual AuList ResolveServiceSync(const ServiceEndpoint &service) = 0; }; struct ISocketService { virtual bool NewServer(const ServerInfo &listen, AuSPtr &out) = 0; virtual bool NewTlsServer(const TLSServerInfo &keys, AuSPtr &out) = 0; virtual bool NewClient(const ClientConfig &info, AuSPtr &out) = 0; virtual bool NewTlsClient(const TLSClientConfig &info, AuSPtr &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