From 66c90a87686409c64e33f08c65f4c9b5d199aedd Mon Sep 17 00:00:00 2001 From: Nathan Nichols Date: Mon, 14 Jun 2021 09:48:40 -0500 Subject: [PATCH] listener rebase --- reporting/server/HttpBase.h | 330 ++++++++++++++++++++++++++++++ reporting/server/HttpSession.h | 288 ++------------------------ reporting/server/SslHttpSession.h | 113 ++++++++++ reporting/server/WsBase.h | 0 reporting/server/WssSession.h | 0 server/listener.h | 161 +++++++++------ server/websocket_server_async.cpp | 14 +- 7 files changed, 567 insertions(+), 339 deletions(-) create mode 100644 reporting/server/HttpBase.h create mode 100644 reporting/server/SslHttpSession.h create mode 100644 reporting/server/WsBase.h create mode 100644 reporting/server/WssSession.h diff --git a/reporting/server/HttpBase.h b/reporting/server/HttpBase.h new file mode 100644 index 00000000..9899fa1c --- /dev/null +++ b/reporting/server/HttpBase.h @@ -0,0 +1,330 @@ +//------------------------------------------------------------------------------ +/* + This file is part of rippled: https://github.com/ripple/rippled + Copyright (c) 2021 Ripple Labs Inc. + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +#ifndef RIPPLE_REPORTING_HTTP_BASE_SESSION_H +#define RIPPLE_REPORTING_HTTP_BASE_SESSION_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace http = boost::beast::http; +namespace net = boost::asio; +namespace ssl = boost::asio::ssl; +using tcp = boost::asio::ip::tcp; + +static std::string defaultResponse = + "" + " Test page for reporting mode

" + " Test

This page shows xrpl reporting http(s) " + "connectivity is working.

"; + +inline void +httpFail(boost::beast::error_code ec, char const* what) +{ + // ssl::error::stream_truncated, also known as an SSL "short read", + // indicates the peer closed the connection without performing the + // required closing handshake (for example, Google does this to + // improve performance). Generally this can be a security issue, + // but if your communication protocol is self-terminated (as + // it is with both HTTP and WebSocket) then you may simply + // ignore the lack of close_notify. + // + // https://github.com/boostorg/beast/issues/38 + // + // https://security.stackexchange.com/questions/91435/how-to-handle-a-malicious-ssl-tls-shutdown + // + // When a short read would cut off the end of an HTTP message, + // Beast returns the error boost::beast::http::error::partial_message. + // Therefore, if we see a short read here, it has occurred + // after the message has been completed, so it is safe to ignore it. + + if(ec == net::ssl::error::stream_truncated) + return; + + std::cerr << what << ": " << ec.message() << "\n"; +} + +bool +validRequest(boost::json::object const& req) +{ + if (!req.contains("method") || !req.at("method").is_string()) + return false; + + if (!req.contains("params")) + return true; + + if (!req.at("params").is_array()) + return false; + + auto array = req.at("params").as_array(); + + if (array.size() != 1) + return false; + + if (!array.at(0).is_object()) + return false; + + return true; +} + +// This function produces an HTTP response for the given +// request. The type of the response object depends on the +// contents of the request, so the interface requires the +// caller to pass a generic lambda for receiving the response. +template< + class Body, class Allocator, + class Send> +void +handle_request( + boost::beast::http::request>&& req, + Send&& send, + ReportingETL& etl) +{ + auto const response = + [&req]( + http::status status, + std::string content_type, + std::string message) + { + http::response res{status, req.version()}; + res.set(http::field::server, "xrpl-reporting-server-v0.0.0"); + res.set(http::field::content_type, content_type); + res.keep_alive(req.keep_alive()); + res.body() = std::string(message); + res.prepare_payload(); + return res; + }; + + + if(req.method() == http::verb::get + && req.body() == "") + { + send(response(http::status::ok, "text/html", defaultResponse)); + return; + } + + if(req.method() != http::verb::post) + { + send(response( + http::status::bad_request, + "text/html", + "Expected a POST request")); + + return; + } + + try + { + BOOST_LOG_TRIVIAL(info) << "Received request: " << req.body(); + + boost::json::object request; + try + { + request = boost::json::parse(req.body()).as_object(); + } + catch (std::runtime_error const& e) + { + send(response( + http::status::bad_request, + "text/html", + "Cannot parse json in body")); + + return; + } + + if(!validRequest(request)) + { + send(response( + http::status::bad_request, + "text/html", + "Malformed request")); + + return; + } + + boost::json::object wsStyleRequest = request.contains("params") + ? request.at("params").as_array().at(0).as_object() + : boost::json::object{}; + + wsStyleRequest["command"] = request["method"]; + + std::cout << "Transfromed to ws style stuff" << std::endl; + + auto builtResponse = buildResponse(wsStyleRequest, etl, nullptr); + + send(response( + http::status::ok, + "application/json", + boost::json::serialize(builtResponse))); + + return; + } + catch (std::exception const& e) + { + std::cout << e.what() << std::endl; + send(response( + http::status::internal_server_error, + "text/html", + "Internal server error occurred" + )); + + return; + } +} + +// From Boost Beast examples http_server_flex.cpp +template +class HttpBase +{ + // Access the derived class, this is part of + // the Curiously Recurring Template Pattern idiom. + Derived& + derived() + { + return static_cast(*this); + } + + struct send_lambda + { + HttpBase& self_; + + explicit + send_lambda(HttpBase& self) + : self_(self) + { + } + + template + void + operator()(http::message&& msg) const + { + // The lifetime of the message has to extend + // for the duration of the async operation so + // we use a shared_ptr to manage it. + auto sp = std::make_shared< + http::message>(std::move(msg)); + + // Store a type-erased version of the shared + // pointer in the class to keep it alive. + self_.res_ = sp; + + // Write the response + http::async_write( + self_.derived().stream(), + *sp, + boost::beast::bind_front_handler( + &HttpBase::on_write, + self_.derived().shared_from_this(), + sp->need_eof())); + } + }; + + http::request req_; + std::shared_ptr res_; + ReportingETL& etl_; + send_lambda lambda_; + +protected: + boost::beast::flat_buffer buffer_; + +public: + HttpBase(ReportingETL& etl, boost::beast::flat_buffer buffer) + : etl_(etl) + , lambda_(*this) + , buffer_(std::move(buffer)) + {} + + void + do_read() + { + // Make the request empty before reading, + // otherwise the operation behavior is undefined. + req_ = {}; + + // Set the timeout. + boost::beast::get_lowest_layer(derived().stream()).expires_after(std::chrono::seconds(30)); + + // Read a request + http::async_read( + derived().stream(), + buffer_, + req_, + boost::beast::bind_front_handler( + &HttpBase::on_read, + derived().shared_from_this())); + } + + void + on_read( + boost::beast::error_code ec, + std::size_t bytes_transferred) + { + boost::ignore_unused(bytes_transferred); + + // This means they closed the connection + if(ec == http::error::end_of_stream) + return derived().do_close(); + + if(ec) + return httpFail(ec, "read"); + + // Send the response + handle_request(std::move(req_), lambda_, etl_); + } + + void + on_write( + bool close, + boost::beast::error_code ec, + std::size_t bytes_transferred) + { + boost::ignore_unused(bytes_transferred); + + if(ec) + return httpFail(ec, "write"); + + if(close) + { + // This means we should close the connection, usually because + // the response indicated the "Connection: close" semantic. + return derived().do_close(); + } + + // We're done with the response so delete it + res_ = nullptr; + + // Read another request + do_read(); + } +}; + +#endif //RIPPLE_REPORTING_HTTP_BASE_SESSION_H \ No newline at end of file diff --git a/reporting/server/HttpSession.h b/reporting/server/HttpSession.h index 849112ec..315aef79 100644 --- a/reporting/server/HttpSession.h +++ b/reporting/server/HttpSession.h @@ -20,192 +20,34 @@ #ifndef RIPPLE_REPORTING_HTTP_SESSION_H #define RIPPLE_REPORTING_HTTP_SESSION_H -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include +#include namespace http = boost::beast::http; namespace net = boost::asio; namespace ssl = boost::asio::ssl; using tcp = boost::asio::ip::tcp; -static std::string defaultResponse = - "" - " Test page for reporting mode

" - " Test

This page shows xrpl reporting http(s) " - "connectivity is working.

"; - -inline void -httpFail(boost::beast::error_code ec, char const* what) -{ - // ssl::error::stream_truncated, also known as an SSL "short read", - // indicates the peer closed the connection without performing the - // required closing handshake (for example, Google does this to - // improve performance). Generally this can be a security issue, - // but if your communication protocol is self-terminated (as - // it is with both HTTP and WebSocket) then you may simply - // ignore the lack of close_notify. - // - // https://github.com/boostorg/beast/issues/38 - // - // https://security.stackexchange.com/questions/91435/how-to-handle-a-malicious-ssl-tls-shutdown - // - // When a short read would cut off the end of an HTTP message, - // Beast returns the error boost::beast::http::error::partial_message. - // Therefore, if we see a short read here, it has occurred - // after the message has been completed, so it is safe to ignore it. - - if(ec == net::ssl::error::stream_truncated) - return; - - std::cerr << what << ": " << ec.message() << "\n"; -} - -// This function produces an HTTP response for the given -// request. The type of the response object depends on the -// contents of the request, so the interface requires the -// caller to pass a generic lambda for receiving the response. -template< - class Body, class Allocator, - class Send> -void -handle_request( - boost::beast::http::request>&& req, - Send&& send, - ReportingETL& etl) -{ - - auto const response = - [&req]( - http::status status, - std::string content_type, - std::string message) - { - http::response res{status, req.version()}; - res.set(http::field::server, "xrpl-reporting-server-v0.0.0"); - res.set(http::field::content_type, content_type); - res.keep_alive(req.keep_alive()); - res.body() = std::string(message); - res.prepare_payload(); - return res; - }; - - - if(req.method() == http::verb::get - && req.body() == "") - { - send(response(http::status::ok, "text/html", defaultResponse)); - return; - } - - if(req.method() != http::verb::post) - { - send(response( - http::status::bad_request, - "text/html", - "Expected a POST request")); - - return; - } - - try - { - std::cout << "GOT BODY: " << req.body() << std::endl; - auto request = boost::json::parse(req.body()).as_object(); - - std::cout << "GOT REQUEST: " << request << std::endl; - - auto builtResponse = buildResponse(request, etl, nullptr); - - send(response( - http::status::ok, - "application/json", - boost::json::serialize(builtResponse))); - - return; - } - catch (std::exception const& e) - { - std::cout << e.what() << std::endl; - send(response( - http::status::internal_server_error, - "text/html", - "Internal server error occurred" - )); - - return; - } -} - - // Handles an HTTP server connection -class HttpSession : public std::enable_shared_from_this +class HttpSession : public HttpBase + , public std::enable_shared_from_this { - struct send_lambda - { - HttpSession& self_; - - explicit - send_lambda(HttpSession& self) - : self_(self) - { - } - - template - void - operator()(http::message&& msg) const - { - // The lifetime of the message has to extend - // for the duration of the async operation so - // we use a shared_ptr to manage it. - auto sp = std::make_shared< - http::message>(std::move(msg)); - - // Store a type-erased version of the shared - // pointer in the class to keep it alive. - self_.res_ = sp; - - // Write the response - http::async_write( - self_.stream_, - *sp, - boost::beast::bind_front_handler( - &HttpSession::on_write, - self_.shared_from_this(), - sp->need_eof())); - } - }; - - boost::beast::ssl_stream stream_; - boost::beast::flat_buffer buffer_; - http::request req_; - std::shared_ptr res_; - send_lambda lambda_; - ReportingETL& etl_; + boost::beast::tcp_stream stream_; public: // Take ownership of the socket explicit HttpSession( tcp::socket&& socket, - ssl::context& ctx, - ReportingETL& etl) - : stream_(std::move(socket), ctx) - , lambda_(*this) - , etl_(etl) + ReportingETL& etl, + boost::beast::flat_buffer buffer) + : HttpBase(etl, std::move(buffer)) + , stream_(std::move(socket)) + {} + + boost::beast::tcp_stream& + stream() { + return stream_; } // Start the asynchronous operation @@ -219,112 +61,16 @@ public: net::dispatch( stream_.get_executor(), boost::beast::bind_front_handler( - &HttpSession::on_run, + &HttpBase::do_read, shared_from_this())); } - void - on_run() - { - // Set the timeout. - boost::beast::get_lowest_layer(stream_).expires_after( - std::chrono::seconds(30)); - - // Perform the SSL handshake - stream_.async_handshake( - ssl::stream_base::server, - boost::beast::bind_front_handler( - &HttpSession::on_handshake, - shared_from_this())); - } - - void - on_handshake(boost::beast::error_code ec) - { - if(ec) - return httpFail(ec, "handshake"); - - do_read(); - } - - void - do_read() - { - // Make the request empty before reading, - // otherwise the operation behavior is undefined. - req_ = {}; - - // Set the timeout. - boost::beast::get_lowest_layer(stream_).expires_after(std::chrono::seconds(30)); - - // Read a request - http::async_read(stream_, buffer_, req_, - boost::beast::bind_front_handler( - &HttpSession::on_read, - shared_from_this())); - } - - void - on_read( - boost::beast::error_code ec, - std::size_t bytes_transferred) - { - boost::ignore_unused(bytes_transferred); - - // This means they closed the connection - if(ec == http::error::end_of_stream) - return do_close(); - - if(ec) - return httpFail(ec, "read"); - - // Send the response - handle_request(std::move(req_), lambda_, etl_); - } - - void - on_write( - bool close, - boost::beast::error_code ec, - std::size_t bytes_transferred) - { - boost::ignore_unused(bytes_transferred); - - if(ec) - return httpFail(ec, "write"); - - if(close) - { - // This means we should close the connection, usually because - // the response indicated the "Connection: close" semantic. - return do_close(); - } - - // We're done with the response so delete it - res_ = nullptr; - - // Read another request - do_read(); - } - void do_close() { - // Set the timeout. - boost::beast::get_lowest_layer(stream_).expires_after(std::chrono::seconds(30)); - - // Perform the SSL shutdown - stream_.async_shutdown( - boost::beast::bind_front_handler( - &HttpSession::on_shutdown, - shared_from_this())); - } - - void - on_shutdown(boost::beast::error_code ec) - { - if(ec) - return httpFail(ec, "shutdown"); + // Send a TCP shutdown + boost::beast::error_code ec; + stream_.socket().shutdown(tcp::socket::shutdown_send, ec); // At this point the connection is closed gracefully } diff --git a/reporting/server/SslHttpSession.h b/reporting/server/SslHttpSession.h new file mode 100644 index 00000000..102a1aa7 --- /dev/null +++ b/reporting/server/SslHttpSession.h @@ -0,0 +1,113 @@ +//------------------------------------------------------------------------------ +/* + This file is part of rippled: https://github.com/ripple/rippled + Copyright (c) 2021 Ripple Labs Inc. + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +#ifndef RIPPLE_REPORTING_HTTPS_SESSION_H +#define RIPPLE_REPORTING_HTTPS_SESSION_H + +#include + +namespace http = boost::beast::http; +namespace net = boost::asio; +namespace ssl = boost::asio::ssl; +using tcp = boost::asio::ip::tcp; + +// Handles an HTTPS server connection +class SslHttpSession : public HttpBase + , public std::enable_shared_from_this +{ + boost::beast::ssl_stream stream_; + +public: + // Take ownership of the socket + explicit + SslHttpSession( + tcp::socket&& socket, + ssl::context& ctx, + ReportingETL& etl, + boost::beast::flat_buffer buffer) + : HttpBase(etl, std::move(buffer)) + , stream_(std::move(socket), ctx) + {} + + boost::beast::ssl_stream& + stream() + { + return stream_; + } + + // Start the asynchronous operation + void + run() + { + auto self = shared_from_this(); + // We need to be executing within a strand to perform async operations + // on the I/O objects in this session. + net::dispatch(stream_.get_executor(), [self]() { + // Set the timeout. + boost::beast::get_lowest_layer(self->stream()).expires_after( + std::chrono::seconds(30)); + + // Perform the SSL handshake + // Note, this is the buffered version of the handshake. + self->stream_.async_handshake( + ssl::stream_base::server, + self->buffer_.data(), + boost::beast::bind_front_handler( + &SslHttpSession::on_handshake, + self)); + }); + } + + void + on_handshake( + boost::beast::error_code ec, + std::size_t bytes_used) + { + if(ec) + return httpFail(ec, "handshake"); + + buffer_.consume(bytes_used); + + do_read(); + } + + void + do_close() + { + // Set the timeout. + boost::beast::get_lowest_layer(stream_).expires_after(std::chrono::seconds(30)); + + // Perform the SSL shutdown + stream_.async_shutdown( + boost::beast::bind_front_handler( + &SslHttpSession::on_shutdown, + shared_from_this())); + } + + void + on_shutdown(boost::beast::error_code ec) + { + if(ec) + return httpFail(ec, "shutdown"); + + // At this point the connection is closed gracefully + } +}; + +#endif // RIPPLE_REPORTING_HTTPS_SESSION_H \ No newline at end of file diff --git a/reporting/server/WsBase.h b/reporting/server/WsBase.h new file mode 100644 index 00000000..e69de29b diff --git a/reporting/server/WssSession.h b/reporting/server/WssSession.h new file mode 100644 index 00000000..e69de29b diff --git a/server/listener.h b/server/listener.h index a888766c..8bfa539b 100644 --- a/server/listener.h +++ b/server/listener.h @@ -30,84 +30,126 @@ class SubscriptionManager; -// Accepts incoming connections and launches the sessions -template -class Listener : public std::enable_shared_from_this> +// Detects SSL handshakes +class detect_session : public std::enable_shared_from_this { - using std::enable_shared_from_this>::shared_from_this; - - boost::asio::io_context& ioc_; - boost::asio::ip::tcp::acceptor acceptor_; - std::shared_ptr backend_; - std::shared_ptr subscriptions_; - std::shared_ptr balancer_; - DOSGuard& dosGuard_; - ssl::context& ctx_; + boost::beast::tcp_stream stream_; + std::optional& ctx_; + ReportingETL& etl_; + boost::beast::flat_buffer buffer_; public: - static void - make_listener( - boost::asio::io_context& ioc, - boost::asio::ip::tcp::endpoint endpoint, - std::shared_ptr backend, - std::shared_ptr subscriptions, - std::shared_ptr balancer, - DOSGuard& dosGuard) + detect_session( + tcp::socket&& socket, + std::optional& ctx, + ReportingETL& etl) + : stream_(std::move(socket)) + , ctx_(ctx) + , etl_(etl) { - std::make_shared( - ioc, endpoint, backend, subscriptions, balancer, dosGuard) - ->run(); } - listener( - boost::asio::io_context& ioc, - boost::asio::ip::tcp::endpoint endpoint, - std::shared_ptr backend, - std::shared_ptr subscriptions, - std::shared_ptr balancer, - DOSGuard& dosGuard) + // Launch the detector + void + run() + { + // Set the timeout. + boost::beast::get_lowest_layer(stream_).expires_after(std::chrono::seconds(30)); + + if (!ctx_) + { + // Launch plain session + std::make_shared( + stream_.release_socket(), + etl_, + std::move(buffer_))->run(); + } + + // Detect a TLS handshake + async_detect_ssl( + stream_, + buffer_, + boost::beast::bind_front_handler( + &detect_session::on_detect, + shared_from_this())); + } + + void + on_detect(boost::beast::error_code ec, bool result) + { + if(ec) + return httpFail(ec, "detect"); + + if(result) + { + // Launch SSL session + std::make_shared( + stream_.release_socket(), + *ctx_, + etl_, + std::move(buffer_))->run(); + return; + } + + // Launch plain session + std::make_shared( + stream_.release_socket(), + etl_, + std::move(buffer_))->run(); + } +}; + +// Accepts incoming connections and launches the sessions +class Listener : public std::enable_shared_from_this +{ + net::io_context& ioc_; + std::optional& ctx_; + tcp::acceptor acceptor_; + ReportingETL& etl_; + +public: + Listener( + net::io_context& ioc, + std::optional& ctx, + tcp::endpoint endpoint, + ReportingETL& etl) : ioc_(ioc) - , acceptor_(ioc) - , backend_(backend) - , subscriptions_(subscriptions) - , balancer_(balancer) - , dosGuard_(dosGuard) + , ctx_(ctx) + , acceptor_(net::make_strand(ioc)) + , etl_(etl) { boost::beast::error_code ec; // Open the acceptor acceptor_.open(endpoint.protocol(), ec); - if (ec) + if(ec) { - BOOST_LOG_TRIVIAL(error) << "Could not open acceptor: " - << ec.message(); + httpFail(ec, "open"); return; } // Allow address reuse - acceptor_.set_option(boost::asio::socket_base::reuse_address(true), ec); - if (ec) + acceptor_.set_option(net::socket_base::reuse_address(true), ec); + if(ec) { - BOOST_LOG_TRIVIAL(error) << "Could not set option for acceptor: " - << ec.message(); + httpFail(ec, "set_option"); return; } // Bind to the server address acceptor_.bind(endpoint, ec); - if (ec) + if(ec) { - BOOST_LOG_TRIVIAL(error) << "Could not bind acceptor: " - << ec.message(); + httpFail(ec, "bind"); return; } // Start listening for connections - acceptor_.listen(boost::asio::socket_base::max_listen_connections, ec); - if (ec) + acceptor_.listen( + net::socket_base::max_listen_connections, ec); + if(ec) { - BOOST_LOG_TRIVIAL(error) << "Acceptor could not start listening: " - << ec.message(); + httpFail(ec, "listen"); return; } } @@ -126,27 +168,26 @@ private: { // The new connection gets its own strand acceptor_.async_accept( - boost::asio::make_strand(ioc_), + net::make_strand(ioc_), boost::beast::bind_front_handler( - &Listener::on_accept, shared_from_this())); + &Listener::on_accept, + shared_from_this())); } void - on_accept(boost::beast::error_code ec, boost::asio::ip::tcp::socket socket) + on_accept(boost::beast::error_code ec, tcp::socket socket) { - if (ec) + if(ec) { - BOOST_LOG_TRIVIAL(error) << "Failed to accept: " - << ec.message(); + httpFail(ec, "accept"); } else { - session::make_session( + // Create the detector session and run it + std::make_shared( std::move(socket), - backend_, - subscriptions_, - balancer_, - dosGuard_); + ctx_, + etl_)->run(); } // Accept another connection diff --git a/server/websocket_server_async.cpp b/server/websocket_server_async.cpp index 41857603..78dcaa87 100644 --- a/server/websocket_server_async.cpp +++ b/server/websocket_server_async.cpp @@ -152,7 +152,7 @@ int main(int argc, char* argv[]) { // Check command line arguments. - if (argc != 5 and argc != 6) + if (argc < 3 || argc > 6) { std::cerr << "Usage: websocket-server-async " @@ -164,7 +164,10 @@ main(int argc, char* argv[]) auto const threads = std::max(1, std::atoi(argv[1])); auto const config = parse_config(argv[2]); - auto ctx = parse_certs(argv[3], argv[4]); + + std::optional ctx = {}; + if (argc == 4 || argc == 5) + ctx = parse_certs(argv[3], argv[4]); if (argc > 5) { @@ -174,17 +177,12 @@ main(int argc, char* argv[]) { initLogLevel(2); } + if (!config) { std::cerr << "couldnt parse config. Exiting..." << std::endl; return EXIT_FAILURE; } - if (!ctx) - { - std::cerr << "could not parse certs, Exiting..." << std::endl; - return EXIT_FAILURE; - } - boost::asio::io_context ioc{threads};