AuroraRuntime/Include/Aurora/IO/Net/Net.hpp
Reece Wilson 67905a4192 [+] Network + Protocol + TLS - Initial Commit
=============================================================================
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
2022-08-28 20:02:06 +01:00

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