-
Notifications
You must be signed in to change notification settings - Fork 11
Description
Summary
Provide a service-based mechanism for callers to inspect an execution context at runtime, determine which TLS implementations are available, and obtain a polymorphic TLS stream using a selected implementation.
Motivation
Currently, TLS support requires compile-time knowledge of which implementation to use (boost::corosio::wolfssl::stream or boost::corosio::openssl::stream). This creates several problems:
- Applications can't adapt at runtime - A program compiled with both WolfSSL and OpenSSL support cannot let users choose which to use via configuration
- Library authors face a dilemma - Libraries built on corosio must either pick one TLS implementation or expose the choice to their users via templates
- No graceful degradation - Applications cannot fall back to an alternative TLS implementation if the preferred one isn't available
Proposed Solution
1. TLS Provider Service
A service registered with the execution context that manages TLS provider discovery and stream creation:
namespace boost::corosio::tls {
enum class provider
{
none, // Use default
openssl,
wolfssl,
// future: boringssl, libressl, schannel, secure_transport
};
class tls_provider_service : public capy::execution_context::service
{
public:
using key_type = tls_provider_service;
explicit tls_provider_service(capy::execution_context& ctx);
// Query available providers on this context
std::vector<provider> available_providers() const;
// Check if a specific provider is available
bool is_available(provider p) const;
// Get/set the default provider for this context
provider default_provider() const;
void set_default_provider(provider p);
// Factory function - creates a polymorphic TLS stream
// Uses default_provider() if p == provider::none
std::unique_ptr<tls_stream> create_stream(
socket sock,
context& ctx,
provider p = provider::none);
};
// Convenience function to acquire the service
tls_provider_service& use_service(capy::execution_context& ctx);
// Free function convenience wrappers
std::vector<provider> available_providers(capy::execution_context& ctx);
bool is_available(capy::execution_context& ctx, provider p);
} // namespace boost::corosio::tls2. Polymorphic TLS Stream (Already Exists)
The existing tls_stream class is already polymorphic:
class tls_stream : public io_stream
{
public:
// Async operations (polymorphic via virtual tls_stream_impl)
auto handshake(handshake_type type);
auto shutdown();
auto read_some(mutable_buffer buf); // inherited from io_stream
auto write_some(const_buffer buf); // inherited from io_stream
io_stream& next_layer() noexcept;
protected:
struct tls_stream_impl : io_stream_impl
{
virtual void handshake(...) = 0;
virtual void shutdown(...) = 0;
};
};Concrete implementations (wolfssl_stream, openssl_stream) derive from tls_stream and provide backend-specific tls_stream_impl.
3. Unified TLS Context
A type-erased context that can be configured for any provider:
namespace boost::corosio::tls {
class context
{
public:
explicit context(provider p = provider::none);
// Common configuration (works with any provider)
void set_verify_mode(verify_mode mode);
void load_system_certs();
void load_cert_file(std::string_view path);
void load_key_file(std::string_view path);
void set_server_name(std::string_view hostname);
provider current_provider() const;
};
} // namespace boost::corosio::tlsExample Usage
#include <boost/corosio/tls.hpp>
capy::task<> connect_with_tls(
corosio::io_context& ioc,
corosio::socket sock,
std::string_view host)
{
using namespace corosio::tls;
// Get the TLS provider service from the execution context
auto& tls_svc = use_service(ioc);
// Check what's available
auto providers = tls_svc.available_providers();
if (providers.empty())
throw std::runtime_error("No TLS providers available");
// Prefer WolfSSL if available, fall back to default
provider p = tls_svc.is_available(provider::wolfssl)
? provider::wolfssl
: tls_svc.default_provider();
// Create context
context ctx(p);
ctx.load_system_certs();
ctx.set_server_name(host);
// Create polymorphic stream via factory
auto stream = tls_svc.create_stream(std::move(sock), ctx, p);
// Use the stream through the base class interface
auto [ec] = co_await stream->handshake(tls_stream::client);
if (ec)
throw std::system_error(ec);
// Read/write through polymorphic interface
std::array<char, 1024> buf;
auto [ec2, n] = co_await stream->read_some(
capy::mutable_buffer(buf.data(), buf.size()));
// ...
}Setting a Context-Wide Default
void configure_tls(corosio::io_context& ioc)
{
auto& tls_svc = corosio::tls::use_service(ioc);
// Set WolfSSL as default if available, otherwise OpenSSL
if (tls_svc.is_available(corosio::tls::provider::wolfssl))
tls_svc.set_default_provider(corosio::tls::provider::wolfssl);
else if (tls_svc.is_available(corosio::tls::provider::openssl))
tls_svc.set_default_provider(corosio::tls::provider::openssl);
}Design Considerations
Service-Based Architecture
Tying provider discovery to execution_context follows the established Asio service pattern and allows:
- Per-context default provider configuration
- Future support for context-specific TLS settings
- Clean integration with existing service infrastructure
Polymorphic Stream Interface
The existing tls_stream class already provides a polymorphic interface via virtual methods in tls_stream_impl. The factory returns std::unique_ptr<tls_stream>, allowing users to work with any TLS backend through the common interface.
Virtual Dispatch Overhead
The polymorphic stream uses virtual dispatch for I/O operations. For applications where this overhead matters, the concrete types (wolfssl::stream, openssl::stream) remain available for direct use.
Context Lifetime
The tls::context must outlive all streams created from it, matching the existing concrete context semantics.
Provider-Specific Features
Some TLS providers have unique features (e.g., WolfSSL's FIPS mode). These remain accessible via the concrete types. The polymorphic interface covers the common subset.
Alternatives Considered
- Global free functions - Rejected: doesn't tie to execution context, can't have per-context defaults
- Template-based approach - Rejected: requires all user code to be templated, poor ergonomics
- Preprocessor selection only - Rejected: no runtime flexibility, can't respond to configuration
Tasks
- Define
tls::providerenum - Implement
tls_provider_servicewith provider registry - Implement
create_stream()factory method - Implement type-erased
tls::context - Add
use_service()and convenience free functions - Add documentation and examples
- Add tests for provider selection and factory