From a86eda5434725544acd1d21a3cc1873a573e9b62 Mon Sep 17 00:00:00 2001 From: teppy999 Date: Mon, 26 Jun 2023 17:28:07 -0400 Subject: [PATCH] Implemented a lot of LuprexSockets --- .../Integration/IntegrationGameModeBase.cpp | 12 +- Source/Integration/LuprexSockets.cpp | 562 +++++++++++++++++- Source/Integration/LuprexSockets.hpp | 4 + 3 files changed, 559 insertions(+), 19 deletions(-) diff --git a/Source/Integration/IntegrationGameModeBase.cpp b/Source/Integration/IntegrationGameModeBase.cpp index ada78bea..64346113 100644 --- a/Source/Integration/IntegrationGameModeBase.cpp +++ b/Source/Integration/IntegrationGameModeBase.cpp @@ -6,16 +6,6 @@ #include #include -//// Main loop. -//while (!engw.get_stop_driver(&engw)) { -// handle_lua_source(); -// handle_console_output(); -// handle_new_outgoing_sockets(); -// handle_socket_input_output(); -// handle_console_input(); -// handle_console_output(); -// engw.play_invoke_event_update(&engw, drvutil::get_monotonic_clock()); -//} AIntegrationGameModeBase::AIntegrationGameModeBase() { @@ -180,7 +170,7 @@ void AIntegrationGameModeBase::BeginPlay() } std::string_view srcpakv = srcpak.view(); char* argv[1]; - argv[0] = const_cast("lpxserver"); + argv[0] = const_cast("lpxclient"); Luprex.play_initialize(&Luprex, 1, argv, srcpakv.size(), srcpakv.data(), ""); if (Luprex.error[0]) { diff --git a/Source/Integration/LuprexSockets.cpp b/Source/Integration/LuprexSockets.cpp index e42a1302..b7c662a4 100644 --- a/Source/Integration/LuprexSockets.cpp +++ b/Source/Integration/LuprexSockets.cpp @@ -1,5 +1,11 @@ #include "LuprexSockets.hpp" +#include "enginewrapper.hpp" +#include "drvutil.hpp" +#include "Sockets.h" +#include "SocketTypes.h" +#include "SocketSubsystem.h" +#include "AddressInfoTypes.h" #define UI UI_ST THIRD_PARTY_INCLUDES_START @@ -13,7 +19,14 @@ THIRD_PARTY_INCLUDES_START #include THIRD_PARTY_INCLUDES_END #undef UI +#define WIN32_LEAN_AND_MEAN +#include +#include +#include +#include +#include +#define MAX_BIO_BUFFER (128 * 1024) enum EChanState { CHAN_INACTIVE, @@ -23,9 +36,25 @@ enum EChanState { CHAN_SSL_READWRITE, }; +class FLpxSocketsI; + +// A port-listening socket. +class FLpxListener +{ +public: + FLpxSocketsI *LSI; + int BoundPort; + FSocket* Socket; + + FLpxListener(FLpxSocketsI *lsi, int bp, FSocket* sock); + ~FLpxListener(); +}; + // A communication socket. class FLpxChannel { +public: + FLpxSocketsI* LSI; int ChannelID; FSocket* Socket; SSL* SSLState; @@ -49,25 +78,55 @@ class FLpxChannel EChanState State; uint32_t NBytes; const char* Bytes; -}; -// A port-listening socket. -class FLpxListener -{ - int BoundPort; - FSocket* Socket; + void Close(std::string_view error); + + // Copy data from the socket into the recv bio. + // + // If it detects an error or EOF, sets the RecentError flag. + // + void TransferSocketToRecvBIO(); + + // Copy data from the send bio to the socket. + // + // If it detects an error or EOF, sets the RecentError flag. + // + void TransferSendBIOToSocket(); + + // Check if an SSL error is serious. If so, close the channel. + // + // The 'retval' is the return value of the SSL function that returned an + // error. + // + // All errors are considered serious except for SSL_ERROR_WANT_READ, which + // is not serious because it is transient. However, if you get an + // SSL_ERROR_WANT_READ when there's tons of data available in the read + // buffer, that's inexplicable and therefore serious. + // + void CloseChannelIfSSLErrorIsSerious(int retval); + + FLpxChannel(FLpxSocketsI *lsi, FSocket* sock, int chid, SSL_CTX* ctx, EChanState state); + ~FLpxChannel() { Close(""); } }; class FLpxSocketsI : public FLpxSockets { public: + // Fatal error status. + std::string FatalError; + // We don't own the wrapper, we just have a pointer to it. // We require a guarantee that it outlives us. EngineWrapper* Luprex; - TArray Channels; - TArray Listeners; + // A general-purpose character buffer. + char ChBuf[DRV_SHORTSTRING_SIZE]; + TArray Channels; + TArray Listeners; + + // Pointer to the socket subsystem. + ISocketSubsystem* Subsys; SSL_CTX* ServerCTX; SSL_CTX* ClientSecureCTX; @@ -76,23 +135,510 @@ public: FLpxSocketsI(EngineWrapper* w); virtual ~FLpxSocketsI() override; + // Error handling. + void SetError(const std::string& s); + virtual std::string GetError() override { return FatalError; } + bool AnyError() { return !FatalError.empty(); } + + // Return true if we're listening on port P. + bool ListeningOnPort(int p); + + // Open a connection and return a socket. + FSocket* OpenConnection(const std::string& host, const std::string& port, std::string& err); + + // Handle various phases of the operation. + void HandleListenPorts(); + void HandleNewOutgoingSockets(); + void HandleSocketInputOutput(); + + // Main update routine. virtual void Update() override; }; + +static std::string strerror_str(int errnum) { + char buf[256]; + int status = strerror_s(buf, 256, errnum); + if (status != 0) + { + snprintf(buf, 256, "unknown errno %d", errnum); + } + return buf; +} + +static const char* dummy_cert = +"-----BEGIN CERTIFICATE-----\n" +"MIIDezCCAmOgAwIBAgIUajKmxrLMr9zBMlphrTJU5qKG8FgwDQYJKoZIhvcNAQEL\n" +"BQAwTDELMAkGA1UEBhMCVVMxFTATBgNVBAgMDFBlbm5zeWx2YW5pYTESMBAGA1UE\n" +"CgwJbG9jYWxob3N0MRIwEAYDVQQDDAlsb2NhbGhvc3QwIBcNMjIwMzIyMTczMzA4\n" +"WhgPMjEyMjAyMjYxNzMzMDhaMEwxCzAJBgNVBAYTAlVTMRUwEwYDVQQIDAxQZW5u\n" +"c3lsdmFuaWExEjAQBgNVBAoMCWxvY2FsaG9zdDESMBAGA1UEAwwJbG9jYWxob3N0\n" +"MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA5OWIaKqYae4nPxvu5EP3\n" +"VilcjApYcMT4+2ypfQoB6PEep5lwguA929rNsTKnhGsEiQAZ0eZPEZN7VhUwf/hz\n" +"26jIyTT43ELkt6k97wwSZSXuT65RpSiemwEs6g2mMwzpgP6nv+yam4HjE9AKiHGN\n" +"YeTV72Nw1EN70t6IjIf4jsJRXqDJkUx5sSSD6j0WBTOhzozIDgZHTDwiLhatE66m\n" +"SNoD8oWC0PscbUgOJkFpbaCAS8RJmpsdgkTFae2rzL9cOFLGw6OgV/BV1J1s0ks8\n" +"+veoMMtIO6fese+OZ+DyQbuGaoaltZUXzY6QjD5l34m2mGplelT7BrpcqJTBHwmh\n" +"CwIDAQABo1MwUTAdBgNVHQ4EFgQUXQM5TVfJ9gpUXg8fZ8yfuUVcBP8wHwYDVR0j\n" +"BBgwFoAUXQM5TVfJ9gpUXg8fZ8yfuUVcBP8wDwYDVR0TAQH/BAUwAwEB/zANBgkq\n" +"hkiG9w0BAQsFAAOCAQEAqYX/ZGv0Qh/xdXppjnqojm8mH0giDW4tvwMqHcW3YRa3\n" +"9J2yYot+rHjU5g4n6HEmWDBE0eqLz9n3Y3fkFzT8RWZwBaST965CgsfGofyuA2hC\n" +"Ddn4Am3B5tTPmi8WWRZg8amhpGVD/mwkoVFIK0M337b1aZUJYPE+Kc9WetSL2KqB\n" +"EhqSQpkAWhVadzP85dq2T9EDjAvhlFTFlDEBx1GDUcc8M0KQ9NEvLT7LgoUcbMiT\n" +"PerlSZQTB0crchXTRSERgiwu80r7D6STn/RcPL9Fg5PkA94/d87jGbmV4sxSRsvM\n" +"z+DnJGjHrV1J/jHPrnVvVLpigBlGno3C5O/sRw3gcQ==\n" +"-----END CERTIFICATE-----\n"; + +static const char* dummy_key = +"-----BEGIN PRIVATE KEY-----\n" +"MIIEwAIBADANBgkqhkiG9w0BAQEFAASCBKowggSmAgEAAoIBAQDk5Yhoqphp7ic/\n" +"G+7kQ/dWKVyMClhwxPj7bKl9CgHo8R6nmXCC4D3b2s2xMqeEawSJABnR5k8Rk3tW\n" +"FTB/+HPbqMjJNPjcQuS3qT3vDBJlJe5PrlGlKJ6bASzqDaYzDOmA/qe/7JqbgeMT\n" +"0AqIcY1h5NXvY3DUQ3vS3oiMh/iOwlFeoMmRTHmxJIPqPRYFM6HOjMgOBkdMPCIu\n" +"Fq0TrqZI2gPyhYLQ+xxtSA4mQWltoIBLxEmamx2CRMVp7avMv1w4UsbDo6BX8FXU\n" +"nWzSSzz696gwy0g7p96x745n4PJBu4ZqhqW1lRfNjpCMPmXfibaYamV6VPsGulyo\n" +"lMEfCaELAgMBAAECggEBAJa1AiFX4U4tva1xqNKmZV1XklWqIhzts7lnDBkF08gZ\n" +"qcNT5Z5mIpR09eVropwvEidZ56Yp63l5D0XYYbyAS1gfQ0QnGot7h7fdOKgB3MK4\n" +"PLY94gfKPNN17KqWHg2SvNNv1+cn04v78xUCb0zy5tHDp5Acexdm70ohtupARElJ\n" +"LSHdS7ebsqZUFXbbM3BpPEsQLi3PrzNs1DrKkZ3rR6eMGrsDqExXx8/foi9aZKsd\n" +"BGM2/kcTJ5aY6NhSv5iqO1oK46sbMrjVW/bYNsOyl0eFjwTRahn+Zhp/JMewZYeu\n" +"715g6kzbZNwEzBLgrhNPF6E2ycEr/C6z5bE78g5QCkECgYEA8s07UUY25bjYiWWy\n" +"W38pT7d/OXBSyKnq16N6MjVahl29r7nezFiDeLhLC0QiwXu/+qyxVZkB95MMGZXS\n" +"AsaKFNis3AJ6eR4SYyhpSScYKNvlKIiW37TtR4FDcy7y5LL6tFpiDDIGH3LuyWNo\n" +"d76142MBpv5aStnLGYU3pcZj43sCgYEA8VbNM4nqgSCQcbnHYjvsgphEMNSaoVie\n" +"xob2uigXdV6Te0ayoUFBnVNKVsRhk+sswuTV4k1pK/On+USVl2tQ16tcaVMjTfSD\n" +"HLYTJLmt6s4DcywWj5dfkbDoe5PulGXNZE960qXmOC62Lf0VMRwJ5x4FBRvGTjKC\n" +"zvekI2/kO7ECgYEAhBGeclb/BXXGUvY+TgadMf9d9KBkZ0IFu8Xwcd8TnoLe6vbv\n" +"ebery75zE228egIWKwREcYsIxuH1cvVLhrb35N73J7UxaTAyUD1rB598RL1XqPSj\n" +"HIwNhReK2NxwwnWYaQHA02FiczjRKjooWPojdcwk2fEArDZLg1YzLrj7HIECgYEA\n" +"htdx1Y8ESFtyeShMv5UtoxYCW6oeL3H9XH0CE6bc3IYYLvOkULbOO2HTEkGtJ2Fp\n" +"5AbJfiS0U4tS2dI5Jp4eUDH9cxexjRfFvd/5ODbKdnver5X9kQMJsbQ/YPSZg66R\n" +"oK9Lt7Bbvh5TScSy93psCgba1SzckspkDdGNkwMsaTECgYEAnFWaxormLUpXQRLs\n" +"tKzMMHgVnHlsHiqXH432zmT2fpGZHYoWbsGuQjjrHGnSiu3QbDhnzM6y/T2GRs6z\n" +"zHteIo/tzIyxg4MvJGJ9qANA7HoiKBdQ7G/I/NLJIyWAjj+e7/hgzKFcf+dpjpDq\n" +"HcKc9a4WXhC7yu79e5BnKWltHXY=\n" +"-----END PRIVATE KEY-----\n"; + +std::string SSLErrorString() { + // Get the last code. + int code = 0; + while (true) { + int icode = ERR_get_error(); + if (icode == 0) break; + code = icode; + } + + // Fetch and clear errno. + int terrno = errno; + errno = 0; + + if (code != 0) { + const char* rc = ERR_reason_error_string(code); + if (rc != nullptr) { + return rc; + } + else { + return strerror_str(ERR_GET_REASON(code)); + } + } + else if (terrno != 0) { + return strerror_str(terrno); + } + else { + return ""; + } +} + +static SSL_CTX* SSLNewContext(int verify) { + SSL_CTX* ctx = SSL_CTX_new(TLS_method()); + SSL_CTX_set_mode(ctx, SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER); + SSL_CTX_set_mode(ctx, SSL_MODE_ENABLE_PARTIAL_WRITE); + SSL_CTX_set_verify(ctx, verify, nullptr); + return ctx; +} + +static std::string SSLLoadCertificateAuthorities(SSL_CTX* ctx) { + HCERTSTORE hStore = CertOpenSystemStoreW(0, L"ROOT"); + + if (!hStore) { + return "Could not open system cert store."; + } + + PCCERT_CONTEXT pContext = NULL; + X509* x509; + X509_STORE* store = SSL_CTX_get_cert_store(ctx); + + while (true) + { + pContext = CertEnumCertificatesInStore(hStore, pContext); + if (pContext == nullptr) break; + const unsigned char* encoded_cert = pContext->pbCertEncoded; + x509 = d2i_X509(NULL, &encoded_cert, pContext->cbCertEncoded); + if (x509) { + X509_STORE_add_cert(store, x509); + X509_free(x509); + } + } + + CertCloseStore(hStore, 0); + return ""; +} + +static std::string SSLUseCertificateString(SSL_CTX* ctx, const char* str) { + ERR_clear_error(); + BIO* bio = BIO_new(BIO_s_mem()); + BIO_puts(bio, str); + X509* certificate = PEM_read_bio_X509(bio, NULL, NULL, NULL); + std::string result; + if (SSL_CTX_use_certificate(ctx, certificate) <= 0) + { + result = SSLErrorString(); + } + X509_free(certificate); + BIO_free(bio); + return result; +} + + +std::string SSLLoadDummyCert(SSL_CTX* ctx) +{ + std::string err1 = SSLUseCertificateString(ctx, dummy_cert); + std::string err2 = SSLUseCertificateString(ctx, dummy_key); + if (!err1.empty()) return err1; + if (!err1.empty()) return err2; + return ""; +} + + +// Return the amount of 'space left' in a BIO. This is a fiction, +// because MEM BIOs technically have unlimited capacity. We're +// artificially limiting them to a certain size because there's no +// reason to buffer huge amounts of data. +int BIOSpace(BIO* bio) { + int space = (MAX_BIO_BUFFER)-BIO_pending(bio); + if (space < 0) space = 0; + return space; +} + +// Discard the first nbytes in buffer. +// This is a terribly inefficient way to discard data that has +// already been processed. There has to be something better. +void BIODiscard(BIO* b, int nbytes, char* chbuf) { + while (nbytes > 0) { + int nread = nbytes; + if (nread > DRV_SHORTSTRING_SIZE) nread = DRV_SHORTSTRING_SIZE; + int ndropped = BIO_read(b, chbuf, nread); + check(ndropped == nread); + nbytes -= ndropped; + } +} + +FLpxChannel::FLpxChannel(FLpxSocketsI* lsi, FSocket* sock, int chid, SSL_CTX* ctx, EChanState st) +{ + LSI = lsi; + ChannelID = chid; + Socket = sock; + RecvBIO = BIO_new(BIO_s_mem()); + SendBIO = BIO_new(BIO_s_mem()); + RecentError.clear(); + RetryWriteNBytes = 0; + NeedAdvance = true; + + if (st == CHAN_PLAINTEXT) { + SSLState = nullptr; + } + else { + SSLState = SSL_new(ctx); + SSL_set_bio(SSLState, RecvBIO, SendBIO); + } + + State = st; + NBytes = 0; + Bytes = 0; +} + +void FLpxChannel::Close(std::string_view err) { + // Close and release the SSL channel. + // This frees the BIO objects as well. + if (SSLState != nullptr) { + SSL_free(SSLState); + SSLState = nullptr; + } + RecvBIO = nullptr; + SendBIO = nullptr; + + RecentError.clear(); + RetryWriteNBytes = 0; + NeedAdvance = false; + + // Close and release the socket. + if (Socket != nullptr) + { + Socket->Close(); + LSI->Subsys->DestroySocket(Socket); + Socket = nullptr; + } + + // Notify luprex that the channel has been closed. + if (ChannelID > 0) + { + LSI->Luprex->play_notify_close(LSI->Luprex, ChannelID, err.size(), err.data()); + } + + // Close everything else. + State = CHAN_INACTIVE; + ChannelID = -1; + NBytes = 0; + Bytes = nullptr; +} + +void FLpxChannel::TransferSocketToRecvBIO() { + if ((State == CHAN_INACTIVE) || (!RecentError.empty())) { + return; + } + int32 bytesread; + bool ok = Socket->Recv((uint8*)LSI->ChBuf, DRV_SHORTSTRING_SIZE, bytesread); + if (!ok) + { + RecentError = "EOF"; + return; + } + if (bytesread > 0) + { + int nstored = BIO_write(RecvBIO, LSI->ChBuf, bytesread); + check(nstored == bytesread); + NeedAdvance = true; + } +} + +void FLpxChannel::TransferSendBIOToSocket() { + if ((State == CHAN_INACTIVE) || (!RecentError.empty())) { + return; + } + + char* data; + int ndata = BIO_get_mem_data(SendBIO, &data); + if (ndata > DRV_SHORTSTRING_SIZE) ndata = DRV_SHORTSTRING_SIZE; + + // It is an error to call this function when there is nothing in the send BIO. + check(ndata > 0); + + int32 bytessent; + bool ok = Socket->Send((const uint8*)data, ndata, bytessent); + if (!ok) + { + RecentError = "Send failure on socket"; + return; + } + if (bytessent > 0) + { + BIODiscard(SendBIO, bytessent, LSI->ChBuf); + NeedAdvance = true; + } +} + +// Close the channel if there's a serious OpenSSL error. +// +void FLpxChannel::CloseChannelIfSSLErrorIsSerious(int retval) { + int error = SSL_get_error(SSLState, retval); + + // Should never have write errors, because we're + // using a memory BIO with unlimited capacity. + check(error != SSL_ERROR_WANT_WRITE); + + // If we get a read error, make sure it's plausible: + // if the recv bio is full, that makes no sense. + if (error == SSL_ERROR_WANT_READ) { + if (BIOSpace(RecvBIO) == 0) { + Close("ssl waiting for data, but there's tons of data"); + } + return; + } + + // Any other error is an actual error. Close + // the channel. + std::string errstr = SSLErrorString(); + if (errstr == "") errstr = "unknown error"; + Close(errstr); +} + +FLpxListener::FLpxListener(FLpxSocketsI *lsi, int bp, FSocket *sock) +{ + LSI = lsi; + BoundPort = bp; + Socket = sock; +} + +FLpxListener::~FLpxListener() +{ + if (Socket != nullptr) + { + Socket->Close(); + LSI->Subsys->DestroySocket(Socket); + Socket = nullptr; + } +} + +void FLpxSocketsI::SetError(const std::string& s) +{ + if (FatalError.empty()) { + FatalError = s; + } +} + FLpxSocketsI::FLpxSocketsI(EngineWrapper *w) { Luprex = w; ServerCTX = nullptr; ClientSecureCTX = nullptr; ClientInsecureCTX = nullptr; + + Subsys = ISocketSubsystem::Get(PLATFORM_SOCKETSUBSYSTEM); + if (Subsys == nullptr) + { + SetError("Cannot obtain the socket subsystem"); + } + + ServerCTX = SSLNewContext(SSL_VERIFY_NONE); + ClientSecureCTX = SSLNewContext(SSL_VERIFY_PEER); + ClientInsecureCTX = SSLNewContext(SSL_VERIFY_NONE); + + SetError(SSLLoadCertificateAuthorities(ClientSecureCTX)); + SetError(SSLLoadDummyCert(ServerCTX)); + + HandleListenPorts(); } FLpxSocketsI::~FLpxSocketsI() { + if (ServerCTX != nullptr) + { + SSL_CTX_free(ServerCTX); + } + if (ClientSecureCTX != nullptr) + { + SSL_CTX_free(ClientSecureCTX); + } + if (ClientInsecureCTX != nullptr) + { + SSL_CTX_free(ClientInsecureCTX); + } + + // Cleanup + //for (ChanInfo& chan : chans_) { + // close_channel(chan, ""); + //} +} + +bool FLpxSocketsI::ListeningOnPort(int p) +{ + for (const FLpxListener& l : Listeners) + { + if (l.BoundPort == p) return true; + } + return false; +} + +void FLpxSocketsI::HandleListenPorts() +{ + uint32_t nports; const uint32_t* ports; + Luprex->get_listen_ports(Luprex, &nports, &ports); + for (uint32_t i = 0; i < nports; i++) { + int port = ports[i]; + if (!ListeningOnPort(port)) + { + // TODO: Open a listening socket. + // Push the new port and socket onto Listeners. + } + } +} + +FSocket* FLpxSocketsI::OpenConnection(const std::string& host, const std::string& port, std::string& err) +{ + std::string hostport = host + ":" + port; + FString fshost(host.size(), (const UTF8CHAR *)host.c_str()); + FString fsport(port.size(), (const UTF8CHAR *)port.c_str()); + + FAddressInfoResult air = Subsys->GetAddressInfo(*fshost, *fsport, EAddressInfoFlags::Default, NAME_None, ESocketType::SOCKTYPE_Streaming); + + if (air.Results.Num() == 0) { + err = std::string("DNS Lookup failed for: ") + hostport; + return nullptr; + } + + const FAddressInfoResultData& dns = air.Results[0]; + const FInternetAddr& inetaddr = *dns.Address; + + std::string sdescription = host + ":" + port; + FString description(sdescription.c_str()); + + FSocket* Socket = Subsys->CreateSocket(NAME_Stream, description, inetaddr.GetProtocolType()); + if (Socket == nullptr) + { + err = std::string("Could not create socket for ") + hostport; + return nullptr; + } + bool connected = Socket->Connect(inetaddr); + if (!connected) + { + Subsys->DestroySocket(Socket); + err = std::string("Could not connect to ") + hostport; + return nullptr; + } + Socket->SetNonBlocking(true); + err = ""; + return Socket; +} + +void FLpxSocketsI::HandleNewOutgoingSockets() +{ + uint32_t nchids; const uint32_t* chids; + Luprex->get_new_outgoing(Luprex, &nchids, &chids); + for (uint32_t i = 0; i < nchids; i++) { + uint32_t chid = chids[i]; + std::string err, cert, host, port; + const char* target = Luprex->get_target(Luprex, chid); + drvutil::split_target(target, cert, host, port); + if (cert.empty() || host.empty() || port.empty()) { + std::string message = "invalid target: "; + message += target; + Luprex->play_notify_close(Luprex, chid, message.size(), message.c_str()); + continue; + } + SSL_CTX* ctx = nullptr; + if (cert == "cert") { + ctx = ClientSecureCTX; + } + else if (cert == "nocert") { + ctx = ClientInsecureCTX; + } + else { + std::string message = "invalid cert rule: "; + message += target; + Luprex->play_notify_close(Luprex, chid, message.size(), message.c_str()); + continue; + } + FSocket *sock = OpenConnection(host, port, err); + if (sock == nullptr) { + Luprex->play_notify_close(Luprex, chid, err.size(), err.c_str()); + continue; + } + Channels.Emplace(this, sock, chid, ctx, CHAN_SSL_CONNECTING); + } + Luprex->play_clear_new_outgoing(Luprex); +} + + + +void FLpxSocketsI::HandleSocketInputOutput() +{ + } void FLpxSocketsI::Update() { + HandleNewOutgoingSockets(); + HandleSocketInputOutput(); } FLpxSockets* FLpxSockets::Create(EngineWrapper* w) diff --git a/Source/Integration/LuprexSockets.hpp b/Source/Integration/LuprexSockets.hpp index 5b6b1af5..c6a00c07 100644 --- a/Source/Integration/LuprexSockets.hpp +++ b/Source/Integration/LuprexSockets.hpp @@ -1,6 +1,7 @@ #pragma once #include "CoreMinimal.h" +#include // Class FLpxSockets // @@ -37,6 +38,9 @@ public: // Delete the luprex socket system. Cleanly closes all sockets. virtual ~FLpxSockets() {} + // Obtain any error status report. + virtual std::string GetError() = 0; + // The update routine relays data into and out of // luprex via TCP/IP sockets. virtual void Update() = 0;