From 157db19885677257140a88506a14852c68b16e20 Mon Sep 17 00:00:00 2001 From: 0xG0nz0 <8682922+0xg0nz0@users.noreply.github.com> Date: Sun, 25 Feb 2024 19:48:40 +0000 Subject: [PATCH] Interim checkin with basic address and protocol classes --- .vscode/settings.json | 4 +- CMakeLists.txt | 7 +++- sdk/client.h | 35 ++-------------- sdk/net/address.cc | 34 ++++++++++++++++ sdk/net/address.h | 47 ++++++++++++++++++++++ sdk/net/iggy.cc | 25 ++++++++++++ sdk/net/iggy.h | 45 +++++++++++++++++++++ sdk/net/protocol.cc | 24 +++++++++++ sdk/net/protocol.h | 90 ++++++++++++++++++++++++++++++++++++++++++ sdk/net/quic/address.h | 1 + sdk/net/quic/conn.h | 1 + sdk/net/quic/stream.h | 1 + sdk/net/quic/tls.h | 1 + sdk/net/transport.h | 31 +++++++++++++++ vcpkg.json | 1 + 15 files changed, 314 insertions(+), 33 deletions(-) create mode 100644 sdk/net/address.cc create mode 100644 sdk/net/address.h create mode 100644 sdk/net/iggy.cc create mode 100644 sdk/net/iggy.h create mode 100644 sdk/net/protocol.cc create mode 100644 sdk/net/protocol.h create mode 100644 sdk/net/quic/address.h create mode 100644 sdk/net/quic/conn.h create mode 100644 sdk/net/quic/stream.h create mode 100644 sdk/net/quic/tls.h create mode 100644 sdk/net/transport.h diff --git a/.vscode/settings.json b/.vscode/settings.json index 6fe6f2b..8bc4828 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -76,6 +76,8 @@ "cinttypes": "cpp", "typeinfo": "cpp", "valarray": "cpp", - "variant": "cpp" + "variant": "cpp", + "csignal": "cpp", + "source_location": "cpp" } } diff --git a/CMakeLists.txt b/CMakeLists.txt index 1976ec5..4df7d9f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -10,11 +10,13 @@ if (CMAKE_VERSION VERSION_GREATER_EQUAL "3.24.0") endif() # set up library dependencies +find_package(ada CONFIG REQUIRED) find_package(libuv CONFIG REQUIRED) find_package(spdlog CONFIG REQUIRED) find_package(unofficial-sodium CONFIG REQUIRED) # these do not correctly support CMake +find_path(ADA_INCLUDE_DIR ada.h REQUIRED) find_path(SODIUM_INCLUDE_DIR sodium.h REQUIRED) # customize the builds of key networking components; WolfSSL is not @@ -59,9 +61,12 @@ add_library( sdk/client.cc sdk/model.cc sdk/serialization.cc + sdk/net/address.cc + sdk/net/protocol.cc + sdk/net/iggy.cc ) target_compile_features(iggy PRIVATE cxx_std_17) -target_include_directories(iggy PRIVATE ${SODIUM_INCLUDE_DIR} ${USOCKETS_INCLUDE_DIR}) +target_include_directories(iggy PRIVATE ${SODIUM_INCLUDE_DIR} ${ADA_INCLUDE_DIR}) if (BUILD_TESTS) add_subdirectory(tests) diff --git a/sdk/client.h b/sdk/client.h index f6513d6..68cfe1c 100644 --- a/sdk/client.h +++ b/sdk/client.h @@ -4,33 +4,10 @@ #include #include #include "model.h" +#include "net/iggy.h" +#include "net/transport.h" namespace iggy { -namespace transport { - -/** - * @enum Transport - * @brief Available network transports for the Iggy server. Not all currently supported by the C++ client. - */ -enum Transport { - /** - * @brief Modern networking protocol from Google built on top of UDP. - * - * @ref [Wikipedia](https://en.wikipedia.org/wiki/QUIC) - */ - QUIC, - - /** - * @brief Classic HTTP REST encoded as JSON. Not recommended for high performance applications. - */ - HTTP, - - /** - * @brief Binary protocol over TCP/IP. This is the default transport. - */ - TCP -}; -}; // namespace transport namespace client { /** @@ -52,10 +29,6 @@ class Credentials { ~Credentials() { sodium_memzero(&password[0], password.size()); } }; -const unsigned short DEFAULT_HTTP_PORT = 3000; -const unsigned short DEFAULT_TCP_PORT = 8090; -const unsigned short DEFAULT_QUIC_PORT = 8080; - /** * @struct Options * @brief A struct to hold various options. @@ -73,12 +46,12 @@ struct Options { /** * @brief The port the Iggy server is listening on; default depends on transport. Defaults to the DEFAULT_TCP_PORT. */ - unsigned short port = DEFAULT_TCP_PORT; + unsigned short port = iggy::net::DEFAULT_TCP_PORT; /** * @brief The network transport to use when connecting to the server. Defaults to TCP. */ - iggy::transport::Transport transport = iggy::transport::Transport::TCP; + iggy::net::transport::Transport transport = iggy::net::transport::Transport::TCP; /** * @brief The user credentials to use when connecting to the server. diff --git a/sdk/net/address.cc b/sdk/net/address.cc new file mode 100644 index 0000000..95c6241 --- /dev/null +++ b/sdk/net/address.cc @@ -0,0 +1,34 @@ +#include "address.h" + +const iggy::net::protocol::ProtocolDefinition& iggy::net::address::LogicalAddress::getProtocolDefinition() const { + return this->protocolProvider->getProtocolDefinition(this->getProtocol()); +} + +iggy::net::address::LogicalAddress::LogicalAddress(const std::string& url, const iggy::net::protocol::ProtocolProvider* protocolProvider) { + auto parse_result = ada::parse(url); + if (!parse_result) { + throw std::invalid_argument("Invalid URL: " + url); + } + auto value = parse_result.value(); + auto protocol = value.get_protocol(); + if (!protocolProvider->isSupported(protocol)) { + throw std::invalid_argument("Unsupported protocol: " + protocol); + } + this->url = value; + this->protocolProvider = protocolProvider; +} + +const unsigned short iggy::net::address::LogicalAddress::getPort() const { + if (url.get_port().empty()) { + return this->getProtocolDefinition().getDefaultPort(); + } else { + int port = std::stoi(url.get_port()); + + // this should not happen if ada::parse is working correctly + if (port < 0 || port > 65535) { + throw std::out_of_range("Port number out of range"); + } + + return port; + } +} diff --git a/sdk/net/address.h b/sdk/net/address.h new file mode 100644 index 0000000..ca24549 --- /dev/null +++ b/sdk/net/address.h @@ -0,0 +1,47 @@ +#pragma once + +#include +#include +#include "protocol.h" +#include "transport.h" + +namespace iggy { +namespace net { +namespace address { + +/*** + * @brief Logical address used in configuration and API to specify desired transport in a compact way, e.g. iggy:quic://localhost:8080. + */ +class LogicalAddress { +private: + ada::url url; + const iggy::net::protocol::ProtocolProvider* protocolProvider; + + const iggy::net::protocol::ProtocolDefinition& getProtocolDefinition() const; +public: + /** + * @brief Construct a logical address from a URL. + * @param url URL to parse. + * @param protocolProvider Context object providing supported protocols and default ports. + * @throws std::invalid_argument if the URL is invalid or the protocol is unknown. + */ + LogicalAddress(const std::string& url, const iggy::net::protocol::ProtocolProvider* protocolProvider); + + /** + * @brief Gets the protocol; you have a guarantee that it will be one of the supported protocols from ProtocolProvider. + */ + const std::string getProtocol() const noexcept { return url.get_protocol(); } + + /** + * @brief Gets the hostname to connect to or raw IP address. + */ + const std::string getHost() const noexcept { return url.get_hostname(); } + + /** + * @brief Gets the port to connect to; protocol default port will be substituted if not specified. + */ + const unsigned short getPort() const; +}; +}; // namespace address +}; // namespace net +}; // namespace iggy diff --git a/sdk/net/iggy.cc b/sdk/net/iggy.cc new file mode 100644 index 0000000..33aa2a5 --- /dev/null +++ b/sdk/net/iggy.cc @@ -0,0 +1,25 @@ +#include "iggy.h" + +iggy::net::IggyProtocolProvider::IggyProtocolProvider() { + for (const auto& protocol : this->supportedProtocols) { + this->supportedProtocolLookup[protocol.getName()] = protocol; + } +} + +const std::vector& iggy::net::IggyProtocolProvider::getSupportedProtocols() const { + return this->supportedProtocols; +} + +const iggy::net::protocol::ProtocolDefinition& iggy::net::IggyProtocolProvider::getProtocolDefinition(const std::string& protocol) const { + auto normalizedName = iggy::net::protocol::normalizeProtocolName(protocol); + auto it = this->supportedProtocolLookup.find(normalizedName); + if (it != this->supportedProtocolLookup.end()) { + return it->second; + } else { + throw std::invalid_argument("Unsupported protocol: " + protocol); + } +} + +const bool iggy::net::IggyProtocolProvider::isSupported(const std::string& protocol) const { + return this->supportedProtocolLookup.count(iggy::net::protocol::normalizeProtocolName(protocol)) > 0; +} diff --git a/sdk/net/iggy.h b/sdk/net/iggy.h new file mode 100644 index 0000000..7dfc6a3 --- /dev/null +++ b/sdk/net/iggy.h @@ -0,0 +1,45 @@ +#pragma once +#include +#include +#include "address.h" + +namespace iggy { +namespace net { + +const unsigned short DEFAULT_HTTP_PORT = 3000; +const unsigned short DEFAULT_TCP_PORT = 8090; +const unsigned short DEFAULT_QUIC_PORT = 8080; + +const std::string QUIC_PROTOCOL = "iggy:quic"; +const std::string TCP_PROTOCOL = "iggy:tcp"; +const std::string TCP_TLS_PROTOCOL = "iggy:tcp+tls"; +const std::string HTTP_PROTOCOL = "iggy:http"; +const std::string HTTP_TLS_PROTOCOL = "iggy:http+tls"; + +using iggy::net::protocol::MessageEncoding; +using iggy::net::protocol::ProtocolDefinition; + +/** + * @brief Provider that declares support and offers defaults for all Iggy C++ supported protocols. + * + * At this time we support iggy:quic, iggy:tcp (binary messaging) and iggy:http (with JSON messaging). + */ +class IggyProtocolProvider : iggy::net::protocol::ProtocolProvider { +private: + std::vector supportedProtocols = { + ProtocolDefinition(QUIC_PROTOCOL, DEFAULT_QUIC_PORT, iggy::net::transport::QUIC, true, MessageEncoding::BINARY), + ProtocolDefinition(TCP_PROTOCOL, DEFAULT_TCP_PORT, iggy::net::transport::TCP, false, MessageEncoding::BINARY), + ProtocolDefinition(TCP_TLS_PROTOCOL, DEFAULT_TCP_PORT, iggy::net::transport::TCP, true, MessageEncoding::BINARY), + ProtocolDefinition(HTTP_PROTOCOL, DEFAULT_HTTP_PORT, iggy::net::transport::HTTP, false, MessageEncoding::TEXT), + ProtocolDefinition(HTTP_TLS_PROTOCOL, DEFAULT_HTTP_PORT, iggy::net::transport::HTTP, true, MessageEncoding::TEXT)}; + std::map supportedProtocolLookup; + +public: + IggyProtocolProvider(); + const std::vector& getSupportedProtocols() const override; + const ProtocolDefinition& getProtocolDefinition(const std::string& protocol) const override; + const bool isSupported(const std::string& protocol) const override; +}; + +}; // namespace net +}; // namespace iggy diff --git a/sdk/net/protocol.cc b/sdk/net/protocol.cc new file mode 100644 index 0000000..8a2cdfc --- /dev/null +++ b/sdk/net/protocol.cc @@ -0,0 +1,24 @@ +#include "address.h" +#include "protocol.h" + +iggy::net::address::LogicalAddress iggy::net::protocol::ProtocolProvider::createAddress(const std::string& url) const { + return iggy::net::address::LogicalAddress(url, this); +} + +const std::string iggy::net::protocol::normalizeProtocolName(const std::string& protocol) { + // convert to lowercase + std::string lowerStr = protocol; + std::transform(lowerStr.begin(), lowerStr.end(), lowerStr.begin(), ::tolower); + + // trim whitespace from the start + auto start = lowerStr.find_first_not_of(" \t\n\r\f\v"); + if (start == std::string::npos) { + throw std::invalid_argument("Protocol name cannot be empty"); + } + + // trim whitespace from the end + auto end = lowerStr.find_last_not_of(" \t\n\r\f\v"); + + // return the trimmed, lowercase string + return lowerStr.substr(start, end - start + 1); +} \ No newline at end of file diff --git a/sdk/net/protocol.h b/sdk/net/protocol.h new file mode 100644 index 0000000..b773462 --- /dev/null +++ b/sdk/net/protocol.h @@ -0,0 +1,90 @@ +#pragma once + +#include +#include +#include "transport.h" + +namespace iggy { +namespace net { +namespace address { +class LogicalAddress; +}; + +namespace protocol { + +/** + * @brief Enumerates the supported message encodings. + */ +enum MessageEncoding { BINARY = 0, TEXT = 1 }; + +/** + * @brief Normalizes the protocol name to a canonical form. + */ +const std::string normalizeProtocolName(const std::string& protocol); + + +/** + * @brief Metadata about a protocol including its default port, transport, TLS support and message encoding. + */ +class ProtocolDefinition { +private: + std::string name; + unsigned short defaultPort; + iggy::net::transport::Transport transport; + bool tlsSupported; + MessageEncoding messageEncoding; + +public: + ProtocolDefinition(const std::string& name, + unsigned short defaultPort, + iggy::net::transport::Transport transport, + bool tlsSupported, + MessageEncoding messageEncoding) + : name(iggy::net::protocol::normalizeProtocolName(name)) + , defaultPort(defaultPort) + , transport(transport) + , tlsSupported(tlsSupported) + , messageEncoding(messageEncoding) {} + + ProtocolDefinition() = default; + ProtocolDefinition(const ProtocolDefinition& other) = default; + + const std::string& getName() const { return name; } + unsigned short getDefaultPort() const { return defaultPort; } + iggy::net::transport::Transport getTransport() const { return transport; }; + bool isTlsSupported() const { return tlsSupported; } + MessageEncoding getMessageEncoding() const { return messageEncoding; } +}; + +/** + * @brief Interface to plug in library-specific information on supported protocols. + */ +class ProtocolProvider { +public: + ProtocolProvider() = default; + virtual ~ProtocolProvider() = default; + + /** + * @brief Factory method to create a logical address from a URL. + */ + iggy::net::address::LogicalAddress createAddress(const std::string& url) const; + + /** + * @brief Enumerates all the supported protocols in the provider. + */ + virtual const std::vector& getSupportedProtocols() const = 0; + + /** + * @brief Given a normalized protocol name returns the definition with protocol metadata. + */ + virtual const ProtocolDefinition& getProtocolDefinition(const std::string& protocol) const; + + /** + * @brief Tests whether the given protocol is supported by this provider. + */ + virtual const bool isSupported(const std::string& protocol) const; +}; + +}; // namespace protocol +}; // namespace net +}; // namespace iggy diff --git a/sdk/net/quic/address.h b/sdk/net/quic/address.h new file mode 100644 index 0000000..6f70f09 --- /dev/null +++ b/sdk/net/quic/address.h @@ -0,0 +1 @@ +#pragma once diff --git a/sdk/net/quic/conn.h b/sdk/net/quic/conn.h new file mode 100644 index 0000000..6f70f09 --- /dev/null +++ b/sdk/net/quic/conn.h @@ -0,0 +1 @@ +#pragma once diff --git a/sdk/net/quic/stream.h b/sdk/net/quic/stream.h new file mode 100644 index 0000000..6f70f09 --- /dev/null +++ b/sdk/net/quic/stream.h @@ -0,0 +1 @@ +#pragma once diff --git a/sdk/net/quic/tls.h b/sdk/net/quic/tls.h new file mode 100644 index 0000000..6f70f09 --- /dev/null +++ b/sdk/net/quic/tls.h @@ -0,0 +1 @@ +#pragma once diff --git a/sdk/net/transport.h b/sdk/net/transport.h new file mode 100644 index 0000000..4aca7d7 --- /dev/null +++ b/sdk/net/transport.h @@ -0,0 +1,31 @@ +#pragma once + +namespace iggy { +namespace net { +namespace transport { + +/** + * @brief Available network transports in the client library. + */ +enum Transport { + /** + * @brief Modern networking protocol from Google built on top of UDP. + * + * @ref [Wikipedia](https://en.wikipedia.org/wiki/QUIC) + */ + QUIC, + + /** + * @brief Classic HTTP REST encoded as JSON. Not recommended for high performance applications. + */ + HTTP, + + /** + * @brief Binary protocol over TCP/IP. + */ + TCP +}; + +}; // namespace net +}; // namespace transport +}; // namespace iggy diff --git a/vcpkg.json b/vcpkg.json index d0964c1..810113e 100644 --- a/vcpkg.json +++ b/vcpkg.json @@ -1,5 +1,6 @@ { "dependencies": [ + "ada-url", "icu", "libsodium", "libuv",