Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

netplay: introduce abstractions for client/server-side sockets and connection providers #4105

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 10 additions & 2 deletions lib/netplay/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,16 @@ add_dependencies(autorevision_netcodeversion autorevision) # Ensure ordering and
############################
# netplay library

file(GLOB HEADERS "*.h")
file(GLOB SRC "*.cpp")
file(GLOB_RECURSE HEADERS "*.h")
file(GLOB_RECURSE SRC "*.cpp")

if(MSVC AND CMAKE_VERSION VERSION_GREATER 3.7)
# Automatic detection of source groups via `source_group(TREE <root>)` syntax
# has been introduced in CMake 3.8.
# Please consult https://cmake.org/cmake/help/latest/command/source_group.html for additional info.
source_group(TREE "${CMAKE_CURRENT_LIST_DIR}" PREFIX "Sources" FILES ${SRC})
source_group(TREE "${CMAKE_CURRENT_LIST_DIR}" PREFIX "Headers" FILES ${HEADERS})
endif()

find_package (Threads REQUIRED)
find_package (ZLIB REQUIRED)
Expand Down
50 changes: 50 additions & 0 deletions lib/netplay/byteorder_funcs_wrapper.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
/*
This file is part of Warzone 2100.
Copyright (C) 1999-2004 Eidos Interactive
Copyright (C) 2005-2024 Warzone 2100 Project

Warzone 2100 is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.

Warzone 2100 is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with Warzone 2100; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/

#include "lib/netplay/byteorder_funcs_wrapper.h"

#include "lib/framework/wzglobal.h"

// bring in the original `htonl`/`htons`/`ntohs`/`htohl` functions
#if defined WZ_OS_WIN
# include <winsock.h>
#else // *NIX / *BSD variants
# include <arpa/inet.h>
#endif

uint32_t wz_htonl(uint32_t hostlong)
{
return htonl(hostlong);
}

uint16_t wz_htons(uint16_t hostshort)
{
return htons(hostshort);
}

uint32_t wz_ntohl(uint32_t netlong)
{
return ntohl(netlong);
}

uint16_t wz_ntohs(uint16_t netshort)
{
return ntohs(netshort);
}
34 changes: 34 additions & 0 deletions lib/netplay/byteorder_funcs_wrapper.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
/*
This file is part of Warzone 2100.
Copyright (C) 1999-2004 Eidos Interactive
Copyright (C) 2005-2024 Warzone 2100 Project

Warzone 2100 is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.

Warzone 2100 is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with Warzone 2100; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/

#pragma once

#include <stdint.h>

///
/// byteorder functions wrappers for WZ just to avoid polluting all places,
/// where these functions are needed, with conditional includes of <arpa/inet.h>
/// and winsock headers.
///

uint32_t wz_htonl(uint32_t hostlong);
uint16_t wz_htons(uint16_t hostshort);
uint32_t wz_ntohl(uint32_t netlong);
uint16_t wz_ntohs(uint16_t netshort);
120 changes: 120 additions & 0 deletions lib/netplay/client_connection.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
/*
This file is part of Warzone 2100.
Copyright (C) 2024 Warzone 2100 Project
Warzone 2100 is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
Warzone 2100 is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Warzone 2100; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/

#pragma once

#include <string>
#include <stddef.h>

#include "lib/framework/types.h" // bring in `ssize_t` for MSVC
#include "lib/netplay/net_result.h"

/// <summary>
/// Basic abstraction over client connection sockets.
///
/// These are capable of reading (`readAll` and `readNoInt`) and
/// writing data (via `writeAll()` + `flush()` combination).
///
/// The internal implementation may also implement advanced compression mechanisms
/// on top of these connections by providing non-trivial `enableCompression()` overload.
///
/// In this case, `writeAll()` should somehow accumulate the data into a write queue,
/// compressing the outcoming data on-the-fly; and `flush()` should empty the write queue
/// and actually post a message to the transmission queue, which, in turn, will be emptied
/// by the internal connection interface in a timely manner, when there are enough messages
/// to be sent over the network.
/// </summary>
class IClientConnection
{
public:

virtual ~IClientConnection() = default;

/// <summary>
/// Read exactly `size` bytes into `buf` buffer.
/// Supports setting a timeout value in milliseconds.
/// </summary>
/// <param name="buf">Destination buffer to read the data into.</param>
/// <param name="size">The size of data to be read in bytes.</param>
/// <param name="timeout">Timeout value in milliseconds.</param>
/// <returns>On success, returns the number of bytes read;
/// On failure, returns an `std::error_code` (having `GenericSystemErrorCategory` error category)
/// describing the actual error.</returns>
virtual net::result<ssize_t> readAll(void* buf, size_t size, unsigned timeout) = 0;
/// <summary>
/// Reads at most `max_size` bytes into `buf` buffer.
/// Raw count of bytes (after compression) is returned in `rawByteCount`.
/// </summary>
/// <param name="buf">Destination buffer to read the data into.</param>
/// <param name="max_size">The maximum number of bytes to read from the client socket.</param>
/// <param name="rawByteCount">Output parameter: Raw count of bytes (after compression).</param>
/// <returns>On success, returns the number of bytes read;
/// On failure, returns an `std::error_code` (having `GenericSystemErrorCategory` error category)
/// describing the actual error.</returns>
virtual net::result<ssize_t> readNoInt(void* buf, size_t max_size, size_t* rawByteCount) = 0;
/// <summary>
/// Nonblocking write of `size` bytes to the socket. The data will be written to a
/// separate write queue in asynchronous manner, possibly by a separate thread.
/// Raw count of bytes (after compression) will be returned in `rawByteCount`, which
/// will often be 0 until the socket is flushed.
///
/// The reason for this method to be async is that in some cases we want
/// client connections to have compression mechanism enabled. This naturally
/// introduces the 2-phase write process, which involves a write queue (accumulating
/// the data for compression on-the-fly) and a submission (transmission)
/// queue (for transmitting of compressed and assembled messages),
/// which is managed by the network backend implementation.
/// </summary>
/// <param name="buf">Source buffer to read the data from.</param>
/// <param name="size">The number of bytes to write to the socket.</param>
/// <param name="rawByteCount">Output parameter: raw count of bytes (after compression) written.</param>
/// <returns>The total number of bytes written.</returns>
virtual net::result<ssize_t> writeAll(const void* buf, size_t size, size_t* rawByteCount) = 0;
/// <summary>
/// This method indicates whether the socket has some data ready to be read (i.e.
/// whether the next `readAll/readNoInt` operation will execute without blocking or not).
/// </summary>
virtual bool readReady() const = 0;
/// <summary>
/// Actually sends the data written with `writeAll()`. Only useful with sockets
/// which have compression enabled.
/// Note that flushing too often makes compression less effective.
/// Raw count of bytes (after compression) is returned in `rawByteCount`.
/// </summary>
/// <param name="rawByteCount">Raw count of bytes (after compression) as written
/// to the submission queue by the flush operation.</param>
virtual void flush(size_t* rawByteCount) = 0;
/// <summary>
/// Enables compression for the current socket.
///
/// This makes all subsequent write operations asynchronous, plus
/// the written data will need to be flushed explicitly at some point.
/// </summary>
virtual void enableCompression() = 0;
/// <summary>
/// Enables or disables the use of Nagle algorithm for the socket.
///
/// For direct TCP connections this is equivalent to setting `TCP_NODELAY` to the
/// appropriate value (i.e.:
/// `enable == true` <=> `TCP_NODELAY == false`;
/// `enable == false` <=> `TCP_NODELAY == true`).
/// </summary>
virtual void useNagleAlgorithm(bool enable) = 0;
/// <summary>
/// Returns textual representation of the socket's connection address.
/// </summary>
virtual std::string textAddress() const = 0;
};
50 changes: 50 additions & 0 deletions lib/netplay/connection_address.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
/*
This file is part of Warzone 2100.
Copyright (C) 2024 Warzone 2100 Project
Warzone 2100 is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
Warzone 2100 is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Warzone 2100; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/

#pragma once

#include <stdint.h>

#include <memory>
#include <string>

#include "lib/netplay/net_result.h"



/// <summary>
/// Opaque class representing abstract connection address to use with various
/// network backend implementations. The internal representation is made
/// hidden on purpose since we don't want to actually leak internal data layout
/// to clients.
///
/// Instead, we would like to introduce "conversion routines" yielding
/// various representations for convenient consumption with various network
/// backends.
///
/// NOTE: this class may or may not represent a chain of resolved network addresses
/// instead of just a single one, much like a `addrinfo` structure.
///
/// Currently, only knows how to convert itself to `addrinfo` struct,
/// which is used with the `TCP_DIRECT` network backend.
///
/// New conversion routines should be introduced for other network backends,
/// if deemed necessary.
/// </summary>
struct IConnectionAddress
{
virtual ~IConnectionAddress() = default;
};
42 changes: 42 additions & 0 deletions lib/netplay/connection_poll_group.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
/*
This file is part of Warzone 2100.
Copyright (C) 2024 Warzone 2100 Project
Warzone 2100 is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
Warzone 2100 is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Warzone 2100; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/

#pragma once

class IClientConnection;

/// <summary>
/// Abstract representation of a poll group comprised of several client connections.
/// </summary>
class IConnectionPollGroup
{
public:

virtual ~IConnectionPollGroup() = default;

/// <summary>
/// Polls the sockets in the poll group for updates.
/// </summary>
/// <param name="timeout">Timeout value after which the internal implementation should abandon
/// polling the client connections and return.</param>
/// <returns>On success, returns the number of connection descriptors in the poll group.
/// On failure, `0` can returned if the timeout expired before any connection descriptors
/// became ready, or `-1` if there was an error during the internal poll operation.</returns>
virtual int checkSockets(unsigned timeout) = 0;

virtual void add(IClientConnection* conn) = 0;
virtual void remove(IClientConnection* conn) = 0;
};
57 changes: 57 additions & 0 deletions lib/netplay/connection_provider_registry.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
/*
This file is part of Warzone 2100.
Copyright (C) 2024 Warzone 2100 Project

Warzone 2100 is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.

Warzone 2100 is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with Warzone 2100; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/

#include <stdexcept>

#include "lib/netplay/connection_provider_registry.h"
#include "lib/netplay/tcp/tcp_connection_provider.h"

ConnectionProviderRegistry& ConnectionProviderRegistry::Instance()
{
static ConnectionProviderRegistry instance;
return instance;
}

WzConnectionProvider& ConnectionProviderRegistry::Get(ConnectionProviderType pt)
{
const auto it = registeredProviders_.find(pt);
if (it == registeredProviders_.end())
{
throw std::runtime_error("Attempt to get nonexistent connection provider");
}
return *it->second;
}

void ConnectionProviderRegistry::Register(ConnectionProviderType pt)
{
// No-op in case this provider has been already registered.
switch (pt)
{
case ConnectionProviderType::TCP_DIRECT:
registeredProviders_.emplace(pt, std::make_unique<tcp::TCPConnectionProvider>());
break;
default:
throw std::runtime_error("Unknown connection provider type");
}
}

void ConnectionProviderRegistry::Deregister(ConnectionProviderType pt)
{
registeredProviders_.erase(pt);
}
Loading
Loading