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

Implement QUIC protocol - part1 #26

Merged
merged 4 commits into from
Feb 28, 2024
Merged
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
4 changes: 3 additions & 1 deletion .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,8 @@
"cinttypes": "cpp",
"typeinfo": "cpp",
"valarray": "cpp",
"variant": "cpp"
"variant": "cpp",
"csignal": "cpp",
"source_location": "cpp"
}
}
7 changes: 6 additions & 1 deletion CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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)
Expand Down
4 changes: 3 additions & 1 deletion sdk/binary.h
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
#pragma once

#include "serialization.h"

namespace iggy {
namespace serialization {
/**
Expand Down Expand Up @@ -49,7 +51,7 @@ enum CommandCode {
* @class BinaryWireFormat
* @brief Simple binary serialization and deserialization for Iggy's protocol.
*/
class BinaryWireFormat : WireFormat {
class BinaryWireFormat : iggy::serialization::WireFormat {
public:
BinaryWireFormat() = default;
}
Expand Down
35 changes: 4 additions & 31 deletions sdk/client.h
Original file line number Diff line number Diff line change
Expand Up @@ -4,33 +4,10 @@
#include <stdexcept>
#include <string>
#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 {

/**
Expand All @@ -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.
Expand All @@ -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.
Expand Down
4 changes: 3 additions & 1 deletion sdk/json.h
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
#pragma once

#include "serialization.h"

namespace iggy {
namespace serialization {

Expand All @@ -13,7 +15,7 @@ namespace json {
* @class JsonWireFormat
* @brief Binary serialization and deserialization for Iggy's protocol.
*/
class JsonWireFormat : WireFormat {
class JsonWireFormat : iggy::serialization::WireFormat {
public:
JsonWireFormat() = default;
}
Expand Down
34 changes: 34 additions & 0 deletions sdk/net/address.cc
Original file line number Diff line number Diff line change
@@ -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<ada::url>(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;
}
}
47 changes: 47 additions & 0 deletions sdk/net/address.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
#pragma once

#include <ada.h>
#include <vector>
#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
25 changes: 25 additions & 0 deletions sdk/net/iggy.cc
Original file line number Diff line number Diff line change
@@ -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::protocol::ProtocolDefinition>& 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;
}
45 changes: 45 additions & 0 deletions sdk/net/iggy.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
#pragma once
#include <map>
#include <vector>
#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<ProtocolDefinition> 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<std::string, ProtocolDefinition> supportedProtocolLookup;

public:
IggyProtocolProvider();
const std::vector<ProtocolDefinition>& 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
24 changes: 24 additions & 0 deletions sdk/net/protocol.cc
Original file line number Diff line number Diff line change
@@ -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);
}
Loading