docs(doxygen): enforce warn if undocumented (#5337)
This commit is contained in:
226
src/nvhttp.cpp
226
src/nvhttp.cpp
@@ -45,10 +45,19 @@ namespace nvhttp {
|
||||
namespace fs = std::filesystem;
|
||||
namespace pt = boost::property_tree;
|
||||
|
||||
crypto::cert_chain_t cert_chain;
|
||||
crypto::cert_chain_t cert_chain; ///< Certificate chain presented by Sunshine's GameStream HTTPS server.
|
||||
|
||||
/**
|
||||
* @brief HTTPS server backend that adds Sunshine's client-certificate verification.
|
||||
*/
|
||||
class SunshineHTTPSServer: public SimpleWeb::ServerBase<SunshineHTTPS> {
|
||||
public:
|
||||
/**
|
||||
* @brief Initialize the HTTPS server with Sunshine's certificate and key files.
|
||||
*
|
||||
* @param certification_file Path to the server certificate file.
|
||||
* @param private_key_file Path to the matching private key file.
|
||||
*/
|
||||
SunshineHTTPSServer(const std::string &certification_file, const std::string &private_key_file):
|
||||
ServerBase<SunshineHTTPS>::ServerBase(443),
|
||||
context(boost::asio::ssl::context::tls_server) {
|
||||
@@ -59,12 +68,15 @@ namespace nvhttp {
|
||||
context.use_private_key_file(private_key_file, boost::asio::ssl::context::pem);
|
||||
}
|
||||
|
||||
std::function<int(SSL *)> verify;
|
||||
std::function<void(std::shared_ptr<Response>, std::shared_ptr<Request>)> on_verify_failed;
|
||||
std::function<int(SSL *)> verify; ///< Callback that validates a client's TLS certificate after handshake.
|
||||
std::function<void(std::shared_ptr<Response>, std::shared_ptr<Request>)> on_verify_failed; ///< Handler used to return the pairing challenge when client verification fails.
|
||||
|
||||
protected:
|
||||
boost::asio::ssl::context context;
|
||||
boost::asio::ssl::context context; ///< TLS server context configured with Sunshine's certificate and protocol policy.
|
||||
|
||||
/**
|
||||
* @brief Enable client-certificate verification after the listening socket is bound.
|
||||
*/
|
||||
void after_bind() override {
|
||||
if (verify) {
|
||||
context.set_verify_mode(boost::asio::ssl::verify_peer | boost::asio::ssl::verify_fail_if_no_peer_cert | boost::asio::ssl::verify_client_once);
|
||||
@@ -76,6 +88,9 @@ namespace nvhttp {
|
||||
}
|
||||
|
||||
// This is Server<HTTPS>::accept() with SSL validation support added
|
||||
/**
|
||||
* @brief Accept a pending connection and arm the server for the next client.
|
||||
*/
|
||||
void accept() override {
|
||||
auto connection = create_connection(*io_service, context);
|
||||
|
||||
@@ -120,44 +135,85 @@ namespace nvhttp {
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief HTTPS server type used for GameStream endpoints requiring TLS.
|
||||
*/
|
||||
using https_server_t = SunshineHTTPSServer;
|
||||
/**
|
||||
* @brief Plain HTTP server type used for GameStream endpoints without TLS.
|
||||
*/
|
||||
using http_server_t = SimpleWeb::Server<SimpleWeb::HTTP>;
|
||||
|
||||
/**
|
||||
* @brief Internal HTTPS credential paths for the configuration server.
|
||||
*/
|
||||
struct conf_intern_t {
|
||||
std::string servercert;
|
||||
std::string pkey;
|
||||
} conf_intern;
|
||||
std::string servercert; ///< Server certificate PEM string.
|
||||
std::string pkey; ///< Private key PEM string or path.
|
||||
} conf_intern; ///< TLS credential paths loaded from Sunshine's runtime configuration.
|
||||
|
||||
/**
|
||||
* @brief Certificate entry associated with a client name and UUID.
|
||||
*/
|
||||
struct named_cert_t {
|
||||
std::string name;
|
||||
std::string uuid;
|
||||
std::string cert;
|
||||
bool enabled = true;
|
||||
std::string name; ///< Human-readable name for this item.
|
||||
std::string uuid; ///< Persistent Moonlight client UUID associated with the certificate.
|
||||
std::string cert; ///< Certificate PEM string or path.
|
||||
bool enabled = true; ///< Whether this persisted client entry may connect.
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Persisted pairing data for one Moonlight client.
|
||||
*/
|
||||
struct client_t {
|
||||
std::vector<named_cert_t> named_devices;
|
||||
std::vector<named_cert_t> named_devices; ///< Persisted Moonlight clients allowed to pair or reconnect.
|
||||
};
|
||||
|
||||
// uniqueID, session
|
||||
std::unordered_map<std::string, pair_session_t> map_id_sess;
|
||||
client_t client_root;
|
||||
std::atomic<uint32_t> session_id_counter;
|
||||
std::unordered_map<std::string, pair_session_t> map_id_sess; ///< Pairing sessions keyed by temporary unique ID.
|
||||
client_t client_root; ///< In-memory representation of the paired-client database.
|
||||
std::atomic<uint32_t> session_id_counter; ///< Monotonic counter used to allocate GameStream session IDs.
|
||||
|
||||
// Set by TLS verify callback, read by launch/resume handler (single-threaded HTTPS server)
|
||||
std::string last_verified_client_cert; // NOSONAR(cpp:S5421) - intentionally mutable global
|
||||
std::string last_verified_client_cert; ///< Last client certificate accepted by the TLS verify callback. // NOSONAR(cpp:S5421) - intentionally mutable global
|
||||
|
||||
/**
|
||||
* @brief Case-insensitive map used for HTTP headers and query parameters.
|
||||
*/
|
||||
using args_t = SimpleWeb::CaseInsensitiveMultimap;
|
||||
/**
|
||||
* @brief Shared HTTPS response object passed to GameStream handlers.
|
||||
*/
|
||||
using resp_https_t = std::shared_ptr<typename SimpleWeb::ServerBase<SunshineHTTPS>::Response>;
|
||||
/**
|
||||
* @brief Shared HTTPS request object received by GameStream handlers.
|
||||
*/
|
||||
using req_https_t = std::shared_ptr<typename SimpleWeb::ServerBase<SunshineHTTPS>::Request>;
|
||||
/**
|
||||
* @brief Shared HTTP response object passed to redirect and discovery handlers.
|
||||
*/
|
||||
using resp_http_t = std::shared_ptr<typename SimpleWeb::ServerBase<SimpleWeb::HTTP>::Response>;
|
||||
/**
|
||||
* @brief Shared HTTP request object received by redirect and discovery handlers.
|
||||
*/
|
||||
using req_http_t = std::shared_ptr<typename SimpleWeb::ServerBase<SimpleWeb::HTTP>::Request>;
|
||||
|
||||
/**
|
||||
* @brief Certificate operations supported by the pairing API.
|
||||
*/
|
||||
enum class op_e {
|
||||
ADD, ///< Add certificate
|
||||
REMOVE ///< Remove certificate
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Read a named query argument from the HTTP request map.
|
||||
*
|
||||
* @param args Parsed query-string argument map.
|
||||
* @param name Query parameter name to read.
|
||||
* @param default_value Value returned when the parameter is absent.
|
||||
* @return Query parameter value, default value, or an empty string.
|
||||
*/
|
||||
std::string get_arg(const args_t &args, const char *name, const char *default_value = nullptr) {
|
||||
auto it = args.find(name);
|
||||
if (it == std::end(args)) {
|
||||
@@ -170,6 +226,9 @@ namespace nvhttp {
|
||||
return it->second;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Persist the current state to its backing store.
|
||||
*/
|
||||
void save_state() {
|
||||
pt::ptree root;
|
||||
|
||||
@@ -207,6 +266,9 @@ namespace nvhttp {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Load state from its backing store.
|
||||
*/
|
||||
void load_state() {
|
||||
if (!fs::exists(config::nvhttp.file_state)) {
|
||||
BOOST_LOG(info) << "File "sv << config::nvhttp.file_state << " doesn't exist"sv;
|
||||
@@ -272,6 +334,12 @@ namespace nvhttp {
|
||||
client_root = client;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Add authorized client data.
|
||||
*
|
||||
* @param name Human-readable name to assign.
|
||||
* @param cert Certificate data or object used by the operation.
|
||||
*/
|
||||
void add_authorized_client(const std::string &name, std::string &&cert) {
|
||||
client_t &client = client_root;
|
||||
named_cert_t named_cert;
|
||||
@@ -285,6 +353,13 @@ namespace nvhttp {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Create launch session.
|
||||
*
|
||||
* @param host_audio Host audio.
|
||||
* @param args Arguments forwarded to the callable or parser.
|
||||
* @return Constructed launch session object.
|
||||
*/
|
||||
std::shared_ptr<rtsp_stream::launch_session_t> make_launch_session(bool host_audio, const args_t &args) {
|
||||
auto launch_session = std::make_shared<rtsp_stream::launch_session_t>();
|
||||
|
||||
@@ -348,6 +423,13 @@ namespace nvhttp {
|
||||
map_id_sess.erase(sess.client.uniqueID);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Return the GameStream pairing failure response.
|
||||
*
|
||||
* @param sess Pairing session that owns the request state.
|
||||
* @param tree XML property tree used for the response body.
|
||||
* @param status_msg Status msg.
|
||||
*/
|
||||
void fail_pair(pair_session_t &sess, pt::ptree &tree, const std::string status_msg) {
|
||||
tree.put("root.paired", 0);
|
||||
tree.put("root.<xmlattr>.status_code", 400);
|
||||
@@ -355,6 +437,13 @@ namespace nvhttp {
|
||||
remove_session(sess); // Security measure, delete the session when something went wrong and force a re-pair
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Return the server certificate text for pairing responses.
|
||||
*
|
||||
* @param sess Pairing session that owns the request state.
|
||||
* @param tree XML property tree used for the response body.
|
||||
* @param pin PIN supplied by the client during pairing.
|
||||
*/
|
||||
void getservercert(pair_session_t &sess, pt::ptree &tree, const std::string &pin) {
|
||||
if (sess.last_phase != PAIR_PHASE::NONE) {
|
||||
fail_pair(sess, tree, "Out of order call to getservercert");
|
||||
@@ -379,6 +468,13 @@ namespace nvhttp {
|
||||
tree.put("root.<xmlattr>.status_code", 200);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Handle the client-challenge phase of GameStream pairing.
|
||||
*
|
||||
* @param sess Pairing session that owns the request state.
|
||||
* @param tree XML property tree used for the response body.
|
||||
* @param challenge Client challenge bytes from the pairing request.
|
||||
*/
|
||||
void clientchallenge(pair_session_t &sess, pt::ptree &tree, const std::string &challenge) {
|
||||
if (sess.last_phase != PAIR_PHASE::GETSERVERCERT) {
|
||||
fail_pair(sess, tree, "Out of order call to clientchallenge");
|
||||
@@ -422,6 +518,13 @@ namespace nvhttp {
|
||||
tree.put("root.<xmlattr>.status_code", 200);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Handle the server-challenge response phase of GameStream pairing.
|
||||
*
|
||||
* @param sess Pairing session that owns the request state.
|
||||
* @param tree XML property tree used for the response body.
|
||||
* @param encrypted_response Encrypted response.
|
||||
*/
|
||||
void serverchallengeresp(pair_session_t &sess, pt::ptree &tree, const std::string &encrypted_response) {
|
||||
if (sess.last_phase != PAIR_PHASE::CLIENTCHALLENGE) {
|
||||
fail_pair(sess, tree, "Out of order call to serverchallengeresp");
|
||||
@@ -451,6 +554,14 @@ namespace nvhttp {
|
||||
tree.put("root.<xmlattr>.status_code", 200);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Handle the client pairing-secret phase of GameStream pairing.
|
||||
*
|
||||
* @param sess Pairing session that owns the request state.
|
||||
* @param add_cert Add cert.
|
||||
* @param tree XML property tree used for the response body.
|
||||
* @param client_pairing_secret Client pairing secret.
|
||||
*/
|
||||
void clientpairingsecret(pair_session_t &sess, std::shared_ptr<safe::queue_t<crypto::x509_t>> &add_cert, pt::ptree &tree, const std::string &client_pairing_secret) {
|
||||
if (sess.last_phase != PAIR_PHASE::SERVERCHALLENGERESP) {
|
||||
fail_pair(sess, tree, "Out of order call to clientpairingsecret");
|
||||
@@ -504,16 +615,27 @@ namespace nvhttp {
|
||||
template<class T>
|
||||
struct tunnel;
|
||||
|
||||
/**
|
||||
* @brief HTTPS tunnel session used for encrypted client requests.
|
||||
*/
|
||||
template<>
|
||||
struct tunnel<SunshineHTTPS> {
|
||||
static auto constexpr to_string = "HTTPS"sv;
|
||||
static auto constexpr to_string = "HTTPS"sv; ///< To string.
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Plain HTTP server wrapper used for non-TLS endpoints.
|
||||
*/
|
||||
template<>
|
||||
struct tunnel<SimpleWeb::HTTP> {
|
||||
static auto constexpr to_string = "NONE"sv;
|
||||
static auto constexpr to_string = "NONE"sv; ///< To string.
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Write req details to the log.
|
||||
*
|
||||
* @param request HTTP request data from the client.
|
||||
*/
|
||||
template<class T>
|
||||
void print_req(std::shared_ptr<typename SimpleWeb::ServerBase<T>::Request> request) {
|
||||
BOOST_LOG(debug) << "TUNNEL :: "sv << tunnel<T>::to_string;
|
||||
@@ -534,6 +656,12 @@ namespace nvhttp {
|
||||
BOOST_LOG(debug) << " [--] "sv;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Return a GameStream HTTP not-found response.
|
||||
*
|
||||
* @param response HTTP response object to populate.
|
||||
* @param request HTTP request data from the client.
|
||||
*/
|
||||
template<class T>
|
||||
void not_found(std::shared_ptr<typename SimpleWeb::ServerBase<T>::Response> response, std::shared_ptr<typename SimpleWeb::ServerBase<T>::Request> request) {
|
||||
print_req<T>(request);
|
||||
@@ -553,6 +681,13 @@ namespace nvhttp {
|
||||
response->close_connection_after_response = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Dispatch the top-level GameStream pairing request by phase.
|
||||
*
|
||||
* @param add_cert Add cert.
|
||||
* @param response HTTP response object to populate.
|
||||
* @param request HTTP request data from the client.
|
||||
*/
|
||||
template<class T>
|
||||
void pair(std::shared_ptr<safe::queue_t<crypto::x509_t>> &add_cert, std::shared_ptr<typename SimpleWeb::ServerBase<T>::Response> response, std::shared_ptr<typename SimpleWeb::ServerBase<T>::Request> request) {
|
||||
print_req<T>(request);
|
||||
@@ -684,6 +819,11 @@ namespace nvhttp {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get codec mode flags.
|
||||
*
|
||||
* @return Moonlight codec capability bitmask for the currently probed encoders.
|
||||
*/
|
||||
uint32_t get_codec_mode_flags() {
|
||||
uint32_t codec_mode_flags = SCM_H264;
|
||||
if (video::last_encoder_probe_supported_yuv444_for_codec[0]) {
|
||||
@@ -717,6 +857,12 @@ namespace nvhttp {
|
||||
return codec_mode_flags;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Build the GameStream server-info response.
|
||||
*
|
||||
* @param response HTTP response object to populate.
|
||||
* @param request HTTP request data from the client.
|
||||
*/
|
||||
template<class T>
|
||||
void serverinfo(std::shared_ptr<typename SimpleWeb::ServerBase<T>::Response> response, std::shared_ptr<typename SimpleWeb::ServerBase<T>::Request> request) {
|
||||
print_req<T>(request);
|
||||
@@ -801,6 +947,12 @@ namespace nvhttp {
|
||||
return named_cert_nodes;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Build the GameStream application list response.
|
||||
*
|
||||
* @param response HTTP response object to populate.
|
||||
* @param request HTTP request data from the client.
|
||||
*/
|
||||
void applist(resp_https_t response, req_https_t request) {
|
||||
print_req<SunshineHTTPS>(request);
|
||||
|
||||
@@ -829,6 +981,13 @@ namespace nvhttp {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Launch the requested application for a GameStream session.
|
||||
*
|
||||
* @param host_audio Host audio.
|
||||
* @param response HTTP response object to populate.
|
||||
* @param request HTTP request data from the client.
|
||||
*/
|
||||
void launch(bool &host_audio, resp_https_t response, req_https_t request) {
|
||||
print_req<SunshineHTTPS>(request);
|
||||
|
||||
@@ -940,6 +1099,13 @@ namespace nvhttp {
|
||||
revert_display_configuration = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Resume an existing GameStream session.
|
||||
*
|
||||
* @param host_audio Host audio.
|
||||
* @param response HTTP response object to populate.
|
||||
* @param request HTTP request data from the client.
|
||||
*/
|
||||
void resume(bool &host_audio, resp_https_t response, req_https_t request) {
|
||||
print_req<SunshineHTTPS>(request);
|
||||
|
||||
@@ -1031,6 +1197,12 @@ namespace nvhttp {
|
||||
rtsp_stream::launch_session_raise(launch_session);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Check whether cel.
|
||||
*
|
||||
* @param response HTTP response object to populate.
|
||||
* @param request HTTP request data from the client.
|
||||
*/
|
||||
void cancel(resp_https_t response, req_https_t request) {
|
||||
print_req<SunshineHTTPS>(request);
|
||||
|
||||
@@ -1056,6 +1228,12 @@ namespace nvhttp {
|
||||
display_device::revert_configuration();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Return an application asset requested by the client.
|
||||
*
|
||||
* @param response HTTP response object to populate.
|
||||
* @param request HTTP request data from the client.
|
||||
*/
|
||||
void appasset(resp_https_t response, req_https_t request) {
|
||||
print_req<SunshineHTTPS>(request);
|
||||
|
||||
@@ -1074,6 +1252,12 @@ namespace nvhttp {
|
||||
conf_intern.servercert = cert;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Check whether a paired client certificate is allowed to connect.
|
||||
*
|
||||
* @param cert_pem PEM-encoded client certificate to look up.
|
||||
* @return True when the client certificate belongs to an enabled device.
|
||||
*/
|
||||
bool is_client_enabled(const std::string_view cert_pem);
|
||||
|
||||
void start() {
|
||||
@@ -1266,6 +1450,9 @@ namespace nvhttp {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get cert by UUID.
|
||||
*/
|
||||
std::string get_cert_by_uuid(const std::string_view uuid) {
|
||||
for (const auto &named_cert : client_root.named_devices) {
|
||||
if (named_cert.uuid == uuid) {
|
||||
@@ -1275,6 +1462,9 @@ namespace nvhttp {
|
||||
return {};
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Check whether a paired client certificate is allowed to connect.
|
||||
*/
|
||||
bool is_client_enabled(const std::string_view cert_pem) {
|
||||
const client_t &client = client_root;
|
||||
for (const auto &named_cert : client.named_devices) {
|
||||
|
||||
Reference in New Issue
Block a user