
#include "LuprexSockets.h"
#include "LuprexGameModeBase.h"
#include "lpx-enginewrapper.hpp"
#include "lpx-drvutil.hpp"
#include <system_error>
#include "Sockets.h"
#include "SocketTypes.h"
#include "SocketSubsystem.h"
#include "AddressInfoTypes.h"

#define UI UI_ST
THIRD_PARTY_INCLUDES_START
#include <openssl/ssl.h>
#include <openssl/rsa.h>
#include <openssl/x509.h>
#include <openssl/evp.h>
#include <openssl/err.h>
#include <openssl/bio.h>
#include <openssl/pem.h>
#include <openssl/conf.h>
THIRD_PARTY_INCLUDES_END
#undef UI

#ifdef __linux__
#else
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <wincrypt.h>
#endif

#include <cstdio>
#include <string>
#include <string_view>

#define MAX_BIO_BUFFER (128 * 1024)

namespace {

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";

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";

class FlxSocketsI;

/////////////////////////////////////////////////////////////////
//
// A port-listening socket.
//
/////////////////////////////////////////////////////////////////

class FlxListener
{
public:
    FlxSocketsI *LSI;
    int BoundPort;
    FSocket* Socket;

    FlxListener(FlxSocketsI *lsi, int bp, FSocket* sock);
    ~FlxListener();

    void AcceptConnection();
};

/////////////////////////////////////////////////////////////////
//
// A communication socket.
//
/////////////////////////////////////////////////////////////////

enum EChanState
{
    CHAN_INACTIVE,
    CHAN_SSL_CONNECTING,
    CHAN_SSL_ACCEPTING,
    CHAN_SSL_READWRITE,
};

class FlxChannel
{
public:
    FlxSocketsI* LSI;
    EChanState State;
    int ChannelID;
    FSocket* Socket;
    SSL* SSLState;
    BIO* RecvBIO;
    BIO* SendBIO;

    // True if the socket receive operation has ever failed.
    bool RecvFail;
    // True if the socket send operation has ever failed.
    bool SendFail;

    // Most recent probe of the Luprex output buffer for this channel.
    uint32_t NBytes;
    const char* Bytes;

    // OpenSSL has a rule: if you try to SSL_write and it returns
    // SSL_ERROR_WANT_READ, then you have to retry the write with the same
    // number of bytes.  In this event, we record how many bytes we
    // attempted to write, which will enable us to retry.
    int RetryWriteNBytes;

    void Close(std::string_view error);

    // Copy data from the socket into the recv bio.
    // If it detects an error or EOF, sets the RecvFail flag.
    // Once a RecvFail has occurred, further calls will be a No-OP.
    void TransferSocketToRecvBIO();

    // Copy data from the send bio to the socket.
    // If it detects an error or EOF, sets the SendFail flag.
    // Once a SendFail has occurred, further calls will be a No-Op.
    void TransferSendBIOToSocket();

    // Advance the channel
    // 
    void AdvanceConnecting();
    void AdvanceAccepting();
    void AdvanceReadWrite();
    void Advance();

    // 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);

    FlxChannel(FlxSocketsI *lsi, FSocket* sock, int chid, SSL_CTX* ctx, EChanState state);
    FlxChannel() : FlxChannel(nullptr, nullptr, 0, nullptr, CHAN_INACTIVE) {}
    ~FlxChannel() { }
};

/////////////////////////////////////////////////////////////////
//
// The Entire Socket System Implementation
//
/////////////////////////////////////////////////////////////////

class FlxSocketsI : public FlxSockets
{
public:
    // Fatal error status.
    std::string FatalError;

    // This pointer is NULL except when inside
    // one of the methods that accepts a LockedWrapper.
    EngineWrapper* Luprex = nullptr;

    // A general-purpose character buffer.
    char ChBuf[DRV_SHORTSTRING_SIZE];

    TArray<FlxChannel> Channels;
    TArray<FlxListener> Listeners;

    // Pointer to the socket subsystem.
    ISocketSubsystem* Subsys = nullptr;

    SSL_CTX* ServerCTX = nullptr;
    SSL_CTX* ClientSecureCTX = nullptr;
    SSL_CTX* ClientInsecureCTX = nullptr;

public:
    FlxSocketsI(FlxLockedWrapper &w);
    virtual ~FlxSocketsI() 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);

    // Handle various phases of the operation.
    void RemoveInactiveChannels();
    void HandleListenPorts();
    void HandleNewOutgoingSockets();
    void HandleSocketInputOutput();

    // Force Close Everything.
    virtual void ForceCloseEverything(FlxLockedWrapper& w);

    // Main update routine.
    virtual void Update(FlxLockedWrapper &w) override;
};

/////////////////////////////////////////////////////////////////
//
// General Support Functions
//
/////////////////////////////////////////////////////////////////


FSocket* OpenConnection(ISocketSubsystem *subsys, 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;
}

FSocket* ListenOnPort(ISocketSubsystem* subsys, int port, std::string& err)
{
    err = "";
    TSharedPtr<FInternetAddr> addr = subsys->CreateInternetAddr();
    bool ok;
    addr->SetIp(TEXT("127.0.0.1"), ok);
    if (!ok)
    {
        err = "Could not parse internet address";
        return nullptr;
    }
    addr->SetPort(port);
    FSocket* sock = subsys->CreateSocket(NAME_Stream, TEXT("Listener"), addr->GetProtocolType());
    if (sock == nullptr)
    {
        err = "Could not create socket for listening";
        return nullptr;
    }
    if (!sock->SetReuseAddr(true))
    {
        subsys->DestroySocket(sock);
        err = "Could not configure socket to reuse address";
        return nullptr;
    }
    if (!sock->Bind(*addr) || !sock->Listen(20))
    {
        subsys->DestroySocket(sock);
        err = "Could not bind socket to local port";
        return nullptr;
    }
    if (!sock->SetLinger(false))
    {
        subsys->DestroySocket(sock);
        err = "Could not configure socket to not linger";
        return nullptr;
    }
    if (!sock->SetNonBlocking(true))
    {
        subsys->DestroySocket(sock);
        err = "Could not configure socket for nonblocking";
        return nullptr;
    }
    return sock;
}


/////////////////////////////////////////////////////////////////
//
// OpenSSL-related Support Functions
//
/////////////////////////////////////////////////////////////////

void SSLClearErrors()
{
    ERR_clear_error();
    errno = 0;
}

std::string SSLFullErrorString()
{
    BIO* b = BIO_new(BIO_s_mem());
    ERR_print_errors(b);
    char* data;
    int ndata = BIO_get_mem_data(b, &data);
    std::string result(ndata, ' ');
    memcpy(&result[0], data, ndata);
    BIO_free(b);
    return result;
}

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 std::system_category().message(ERR_GET_REASON(code));
        }
    }
    else if (terrno != 0)
    {
        return std::system_category().message(terrno);
    }
    else
    {
        return "";
    }
}

SSL_CTX* SSLNewContext(int verify, const SSL_METHOD *method)
{
    check(method != nullptr);
    SSL_CTX* ctx = SSL_CTX_new(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);
    SSL_CTX_set_ecdh_auto(ctx, 1);
    return ctx;
}

#ifdef __linux__
std::string SSLLoadCertificateAuthorities(SSL_CTX* ctx)
{
    if (SSL_CTX_set_default_verify_paths(ctx) != 1)
    {
        return "Could not load default certificate authority paths.";
    }
    return "";
}
#else
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 "";
}
#endif

std::string SSLUseCertificateString(SSL_CTX* ctx, const char* str)
{
    SSLClearErrors();
    BIO* bio = BIO_new(BIO_s_mem());
    BIO_puts(bio, str);
    X509* certificate = PEM_read_bio_X509(bio, NULL, NULL, NULL);
    if (certificate == nullptr)
    {
        return "Could not parse PEM certificate string";
    }
    std::string result;
    if (SSL_CTX_use_certificate(ctx, certificate) <= 0)
    {
        result = SSLErrorString();
    }
    X509_free(certificate);
    BIO_free(bio);
    return result;
}

std::string SSLUsePrivateKeyString(SSL_CTX* ctx, const char* str)
{
    SSLClearErrors();
    BIO* bio = BIO_new(BIO_s_mem());
    BIO_puts(bio, str);
    EVP_PKEY* pkey = PEM_read_bio_PrivateKey(bio, NULL, NULL, NULL);
    if (pkey == nullptr)
    {
        return "Could not parse PEM private key string";
    }
    std::string result;
    if (SSL_CTX_use_PrivateKey(ctx, pkey) <= 0)
    {
        result = SSLErrorString();
    }
    EVP_PKEY_free(pkey);
    BIO_free(bio);
    return result;
}

std::string SSLLoadDummyCert(SSL_CTX* ctx)
{
    std::string err1 = SSLUseCertificateString(ctx, dummy_cert);
    std::string err2 = SSLUsePrivateKeyString(ctx, dummy_key);
    if (!err1.empty()) return err1;
    if (!err2.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;
    }
}


/////////////////////////////////////////////////////////////////
//
// Channel Methods
//
/////////////////////////////////////////////////////////////////

FlxChannel::FlxChannel(FlxSocketsI* 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());
    RecvFail = false;
    SendFail = false;
    NBytes = 0;
    Bytes = nullptr;
    RetryWriteNBytes = 0;

    SSLState = SSL_new(ctx);
    SSL_set_bio(SSLState, RecvBIO, SendBIO);

    State = st;
}

void FlxChannel::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;
    }

    // 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)
    {
        if (LSI->Luprex->engine)
        {
            LSI->Luprex->play_notify_close(LSI->Luprex, ChannelID, err.size(), err.data());
        }
        ChannelID = -1;
    }

    RecvBIO = nullptr;
    SendBIO = nullptr;
    RecvFail = false;
    SendFail = false;
    NBytes = 0;
    Bytes = nullptr;
    RetryWriteNBytes = 0;
    ChannelID = -1;
    State = CHAN_INACTIVE;
}

void FlxChannel::TransferSocketToRecvBIO()
{
    if ((State == CHAN_INACTIVE) || RecvFail)
    {
        return;
    }

    int32 bytesread;
    bool ok = Socket->Recv((uint8*)LSI->ChBuf, DRV_SHORTSTRING_SIZE, bytesread);
    if (!ok)
    {
        RecvFail = true;
        return;
    }
    if (bytesread > 0)
    {
        int nstored = BIO_write(RecvBIO, LSI->ChBuf, bytesread);
        check(nstored == bytesread);
    }
}

void FlxChannel::TransferSendBIOToSocket()
{
    if ((State == CHAN_INACTIVE) || SendFail)
    {
        return;
    }

    char* data;
    int ndata = BIO_get_mem_data(SendBIO, &data);
    if (ndata > DRV_SHORTSTRING_SIZE) ndata = DRV_SHORTSTRING_SIZE;

    if (ndata == 0) return;

    int32 bytessent;
    bool ok = Socket->Send((const uint8*)data, ndata, bytessent);
    if (!ok)
    {
        SendFail = true;
        return;
    }
    if (bytessent > 0)
    {
        BIODiscard(SendBIO, bytessent, LSI->ChBuf);
    }
}

void FlxChannel::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 = SSLFullErrorString();
    if (errstr == "") errstr = "unknown error";
    Close(errstr);
}

void FlxChannel::AdvanceConnecting()
{
    int retval = SSL_connect(SSLState);
    if (retval == 1)
    {
        State = CHAN_SSL_READWRITE;
    }
    else
    {
        CloseChannelIfSSLErrorIsSerious(retval);
    }
}

void FlxChannel::AdvanceAccepting()
{
    int retval = SSL_accept(SSLState);
    if (retval == 1)
    {
        State = CHAN_SSL_READWRITE;
    }
    else
    {
        CloseChannelIfSSLErrorIsSerious( retval);
    }
}

void FlxChannel::AdvanceReadWrite()
{
    // Read as much as we can, which of course will be limited
    // by the fact that the recv_bio contains finite data.
    while (true)
    {
        int read_result = SSL_read(SSLState, LSI->ChBuf, DRV_SHORTSTRING_SIZE);
        if (read_result > 0)
        {
            LSI->Luprex->play_recv_incoming(LSI->Luprex, ChannelID, read_result, LSI->ChBuf);
        }
        else
        {
            CloseChannelIfSSLErrorIsSerious(read_result);
            break;
        }
    }

    // The read process could have generated an error which could
    // have closed the channel.  If so, don't try writing.
    if (State == CHAN_INACTIVE)
    {
        return;
    }

    // Try to write data.
    while (NBytes)
    {
        uint32_t wbytes;
        if (RetryWriteNBytes > 0)
        {
            wbytes = RetryWriteNBytes;
            check(wbytes < NBytes);
        }
        else
        {
            wbytes = NBytes;
            if (wbytes > DRV_SHORTSTRING_SIZE) wbytes = DRV_SHORTSTRING_SIZE;
        }
        if (wbytes == 0) break;
        int write_result = SSL_write(SSLState, Bytes, wbytes);
        if (write_result > 0)
        {
            LSI->Luprex->play_sent_outgoing(LSI->Luprex, ChannelID, write_result);
            RetryWriteNBytes = 0;
            NBytes -= write_result;
            Bytes += write_result;
        }
        else
        {
            CloseChannelIfSSLErrorIsSerious(write_result);
            RetryWriteNBytes = wbytes;
            break;
        }
    }
}

void FlxChannel::Advance()
{
    check(State != CHAN_INACTIVE);

    // Pump from the socket into the RECV BIO.
    TransferSocketToRecvBIO();

    // Get a pointer to the Luprex outgoing bytes.
    LSI->Luprex->get_outgoing(LSI->Luprex, ChannelID, &NBytes, &Bytes);

    // If all outgoing buffers are empty, and Luprex has released
    // the channel, close the channel.
    if (NBytes == 0)
    {
        if (LSI->Luprex->get_channel_released(LSI->Luprex, ChannelID))
        {
            if (BIO_pending(SendBIO) == 0)
            {
                Close("");
                return;
            }
        }
    }

    SSLClearErrors();
    switch (State)
    {
    case CHAN_SSL_CONNECTING:
        AdvanceConnecting();
        break;
    case CHAN_SSL_ACCEPTING:
        AdvanceAccepting();
        break;
    case CHAN_SSL_READWRITE:
        AdvanceReadWrite();
        break;
    default:
        checkf(false, TEXT("EChanState is invalid"));
        break;
    }

    // Pump from the Send BIO to the socket.
    TransferSendBIOToSocket();

    if (RecvFail || SendFail)
    {
        Close("Connection aborted");
    }
}


/////////////////////////////////////////////////////////////////
//
// Listener Methods
//
/////////////////////////////////////////////////////////////////

FlxListener::FlxListener(FlxSocketsI *lsi, int bp, FSocket *sock)
{
    LSI = lsi;
    BoundPort = bp;
    Socket = sock;
}

FlxListener::~FlxListener()
{
    if (Socket != nullptr)
    {
        Socket->Close();
        LSI->Subsys->DestroySocket(Socket);
        Socket = nullptr;
    }
}

void FlxListener::AcceptConnection()
{
    FSocket* csocket = Socket->Accept(TEXT("Incoming Connection"));
    if (csocket == nullptr)
    {
        return;
    }
    csocket->SetNonBlocking(true);
    int ChannelID = LSI->Luprex->play_notify_accept(LSI->Luprex, BoundPort);
    LSI->Channels.Emplace(LSI, csocket, ChannelID, LSI->ServerCTX, CHAN_SSL_ACCEPTING);
}

/////////////////////////////////////////////////////////////////
//
// The Socket System Implementation
//
/////////////////////////////////////////////////////////////////


void FlxSocketsI::SetError(const std::string& s)
{
    if (FatalError.empty())
    {
        FatalError = s;
    }
}


FlxSocketsI::FlxSocketsI(FlxLockedWrapper &w)
{
    // We retain this pointer only so long as we have the wrapper lock.
    TGuardValue<EngineWrapper*> GuardLuprex(Luprex, w.Get());

    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, TLS_server_method());
    ClientSecureCTX = SSLNewContext(SSL_VERIFY_PEER, TLS_client_method());
    ClientInsecureCTX = SSLNewContext(SSL_VERIFY_NONE, TLS_client_method());

    SetError(SSLLoadCertificateAuthorities(ClientSecureCTX));
    SetError(SSLLoadDummyCert(ServerCTX));

    STACK_OF(SSL_CIPHER)* ciphers = SSL_CTX_get_ciphers(ServerCTX);
    std::string names = "";
    for (int i = 0; i < sk_SSL_CIPHER_num(ciphers); i++)
    {
        const SSL_CIPHER* sslc = sk_SSL_CIPHER_value(ciphers, i);
        const char* name = SSL_CIPHER_get_name(sslc);
        names = names + " " + name;
    }
    HandleListenPorts();
}

void FlxSocketsI::ForceCloseEverything(FlxLockedWrapper& w)
{
    // We retain this pointer only so long as we have the wrapper lock.
    TGuardValue<EngineWrapper*> GuardLuprex(Luprex, w.Get());

    // Close all channels
    for (FlxChannel& chan : Channels)
    {
        chan.Close("Force Close Everything");
    }

    // Delete any channels released by the above.
    RemoveInactiveChannels();

    // All channels should be gone now.
    check(Channels.IsEmpty());
}

FlxSocketsI::~FlxSocketsI()
{
    checkf(Channels.IsEmpty(), TEXT("Must call ForceCloseEverything before destructor"));

    if (ServerCTX != nullptr)
    {
        SSL_CTX_free(ServerCTX);
    }
    if (ClientSecureCTX != nullptr)
    {
        SSL_CTX_free(ClientSecureCTX);
    }
    if (ClientInsecureCTX != nullptr)
    {
        SSL_CTX_free(ClientInsecureCTX);
    }
}

bool FlxSocketsI::ListeningOnPort(int p)
{
    for (const FlxListener& l : Listeners)
    {
        if (l.BoundPort == p) return true;
    }
    return false;
}

void FlxSocketsI::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))
        {
            std::string err;
            FSocket* sock = ListenOnPort(Subsys, port, err);
            if (sock == nullptr)
            {
                SetError(err);
            }
            else
            {
                Listeners.Emplace(this, port, sock);
            }
        }
    }
}


void FlxSocketsI::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(Subsys, 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 FlxSocketsI::RemoveInactiveChannels()
{
    int i = 0;
    int n = Channels.Num();
    while (true)
    {
        while ((n > 0) && (Channels[n - 1].State == CHAN_INACTIVE))
        {
            n -= 1;
        }
        while ((i < n) && (Channels[i].State != CHAN_INACTIVE))
        {
            i += 1;
        }
        if (i >= n) break;
        check(i < (n - 1));
        std::swap(Channels[i], Channels[n - 1]);
        i += 1;
        n -= 1;
    }
    Channels.SetNum(n);
}


void FlxSocketsI::HandleSocketInputOutput()
{
    for (FlxListener& listener : Listeners)
    {
        listener.AcceptConnection();
    }

    // Peek output buffers and determine channel release flags.
    for (FlxChannel& chan : Channels)
    {
        chan.Advance();
    }

    // Delete any channels released by the above.
    RemoveInactiveChannels();
}


void FlxSocketsI::Update(FlxLockedWrapper &w)
{
    // We retain this pointer only so long as we have the wrapper lock.
    TGuardValue<EngineWrapper*> GuardLuprex(Luprex, w.Get());

    HandleNewOutgoingSockets();
    HandleSocketInputOutput();
}


} // anonymous namespace

FlxSockets* FlxSockets::Create(FlxLockedWrapper &w)
{
	return new FlxSocketsI(w);
}
