refactor: resolve Sonar cpp:S1172 findings (#5916)

This commit is contained in:
Dave Lane authored and GitHub committed 2026-10-13 16:42:40 -04:00
1 parent 7c124b4392
commit 3710a231df
15 files changed
+294 -112

No files matched your search

+50 -50
View File
@@ -690,11 +690,11 @@ namespace confighttp {
/**
* @brief Send a 404 Not Found response.
*
* @param response The HTTP response object.
* @param request The HTTP request object.
* @param error_message The error message to include in the response.
*/
void not_found(const resp_https_t &response, [[maybe_unused]] const req_https_t &request, const std::string &error_message) {
void not_found(const resp_https_t &response, const std::string &error_message) {
constexpr auto code = SimpleWeb::StatusCode::client_error_not_found;
nlohmann::json tree;
@@ -711,11 +711,11 @@ namespace confighttp {
/**
* @brief Send a 400 Bad Request response.
*
* @param response The HTTP response object.
* @param request The HTTP request object.
* @param error_message The error message to include in the response.
*/
void bad_request(const resp_https_t &response, [[maybe_unused]] const req_https_t &request, const std::string &error_message) {
void bad_request(const resp_https_t &response, const std::string &error_message) {
constexpr auto code = SimpleWeb::StatusCode::client_error_bad_request;
nlohmann::json tree;
@@ -737,7 +737,7 @@ namespace confighttp {
bool check_content_type(const resp_https_t &response, const req_https_t &request, const std::string_view &contentType) {
const auto requestContentType = request->header.find("content-type");
if (requestContentType == request->header.end()) {
bad_request(response, request, "Content type not provided");
bad_request(response, "Content type not provided");
return false;
}
// Extract the media type part before any parameters (e.g., charset)
@@ -754,7 +754,7 @@ namespace confighttp {
boost::algorithm::to_lower(expectedContentType);
if (actualContentType != expectedContentType) {
bad_request(response, request, "Content type mismatch");
bad_request(response, "Content type mismatch");
return false;
}
return true;
@@ -821,7 +821,7 @@ namespace confighttp {
if (token_it == csrf_tokens.end()) {
auto address = net::addr_to_normalized_string(request->remote_endpoint().address());
BOOST_LOG(error) << "Web UI: ["sv << address << "] -- CSRF token validation failed: no token found for client"sv;
bad_request(response, request, "Invalid CSRF token");
bad_request(response, "Invalid CSRF token");
return false;
}
@@ -829,14 +829,14 @@ namespace confighttp {
csrf_tokens.erase(token_it);
auto address = net::addr_to_normalized_string(request->remote_endpoint().address());
BOOST_LOG(error) << "Web UI: ["sv << address << "] -- CSRF token validation failed: token expired"sv;
bad_request(response, request, "CSRF token expired");
bad_request(response, "CSRF token expired");
return false;
}
if (token_it->second.token != provided_token) {
auto address = net::addr_to_normalized_string(request->remote_endpoint().address());
BOOST_LOG(error) << "Web UI: ["sv << address << "] -- CSRF token validation failed: token mismatch"sv;
bad_request(response, request, "Invalid CSRF token");
bad_request(response, "Invalid CSRF token");
return false;
}
@@ -894,7 +894,7 @@ namespace confighttp {
auto address = net::addr_to_normalized_string(request->remote_endpoint().address());
BOOST_LOG(error) << "Web UI: ["sv << address << "] -- CSRF protection blocked request from origin: "sv << blocked_origin;
BOOST_LOG(error) << "Web UI: To allow this origin, add it to the 'csrf_allowed_origins' option in your Sunshine configuration"sv;
bad_request(response, request, "Missing CSRF token");
bad_request(response, "Missing CSRF token");
return false;
}
@@ -908,7 +908,7 @@ namespace confighttp {
/**
* @brief Validates the application index and sends an error response if invalid.
*/
bool check_app_index(const resp_https_t &response, const req_https_t &request, int index) {
bool check_app_index(const resp_https_t &response, int index) {
std::string file = file_handler::read_file(config::stream.file_apps.c_str());
nlohmann::json file_tree = nlohmann::json::parse(file);
if (const auto &apps = file_tree["apps"]; index < 0 || index >= static_cast<int>(apps.size())) {
@@ -918,7 +918,7 @@ namespace confighttp {
} else {
error_message = std::format("'index' {} out of range, max index is {}", index, max_index);
}
bad_request(response, request, error_message);
bad_request(response, error_message);
return false;
}
return true;
@@ -955,7 +955,7 @@ namespace confighttp {
};
if (has_server_prefix("/api") || has_server_prefix("/assets") || has_server_prefix("/images")) {
not_found(response, request);
not_found(response);
return;
}
@@ -1025,11 +1025,11 @@ namespace confighttp {
// Don't do anything if the file does not exist or is outside the assets directory
if (!isChildPath(filePath, nodeModulesPath)) {
BOOST_LOG(warning) << "Someone requested a path " << filePath << " that is outside the assets folder";
bad_request(response, request);
bad_request(response);
return;
}
if (!fs::exists(filePath)) {
not_found(response, request);
not_found(response);
return;
}
@@ -1039,7 +1039,7 @@ namespace confighttp {
auto mimeType = mime_types.find(relPath.extension().string().substr(1));
// check if the extension is in the map at the x position
if (mimeType == mime_types.end()) {
bad_request(response, request);
bad_request(response);
return;
}
@@ -1130,7 +1130,7 @@ namespace confighttp {
send_response(response, file_tree);
} catch (std::exception &e) {
BOOST_LOG(warning) << "GetApps: "sv << e.what();
bad_request(response, request, e.what());
bad_request(response, e.what());
}
}
@@ -1230,7 +1230,7 @@ namespace confighttp {
send_response(response, output_tree);
} catch (std::exception &e) {
BOOST_LOG(warning) << "SaveApp: "sv << e.what();
bad_request(response, request, e.what());
bad_request(response, e.what());
}
}
@@ -1284,7 +1284,7 @@ namespace confighttp {
nlohmann::json new_apps = nlohmann::json::array();
const int index = std::stoi(request->path_match[1]);
if (!check_app_index(response, request, index)) {
if (!check_app_index(response, index)) {
return;
}
@@ -1307,7 +1307,7 @@ namespace confighttp {
send_response(response, output_tree);
} catch (std::exception &e) {
BOOST_LOG(warning) << "DeleteApp: "sv << e.what();
bad_request(response, request, e.what());
bad_request(response, e.what());
}
}
@@ -1384,7 +1384,7 @@ namespace confighttp {
send_response(response, output_tree);
} catch (nlohmann::json::exception &e) {
BOOST_LOG(warning) << "Update Client: "sv << e.what();
bad_request(response, request, e.what());
bad_request(response, e.what());
}
}
@@ -1434,7 +1434,7 @@ namespace confighttp {
send_response(response, output_tree);
} catch (std::exception &e) {
BOOST_LOG(warning) << "Unpair: "sv << e.what();
bad_request(response, request, e.what());
bad_request(response, e.what());
}
}
@@ -1562,7 +1562,7 @@ namespace confighttp {
send_response(response, output_tree);
} catch (std::exception &e) {
BOOST_LOG(warning) << "SaveConfig: "sv << e.what();
bad_request(response, request, e.what());
bad_request(response, e.what());
}
}
@@ -1584,7 +1584,7 @@ namespace confighttp {
try {
const int index = std::stoi(request->path_match[1]);
if (!check_app_index(response, request, index)) {
if (!check_app_index(response, index)) {
return;
}
@@ -1607,7 +1607,7 @@ namespace confighttp {
// Check if we got the default image path (means validation failed or no image configured)
if (validated_path == DEFAULT_APP_IMAGE_PATH) {
BOOST_LOG(debug) << "Application at index " << index << " does not have a valid cover image";
not_found(response, request, "Cover image not found");
not_found(response, "Cover image not found");
return;
}
@@ -1615,7 +1615,7 @@ namespace confighttp {
std::ifstream in(validated_path, std::ios::binary);
if (!in) {
BOOST_LOG(warning) << "Unable to read cover image file: " << validated_path;
bad_request(response, request, "Unable to read cover image file");
bad_request(response, "Unable to read cover image file");
return;
}
@@ -1627,7 +1627,7 @@ namespace confighttp {
response->write(SimpleWeb::StatusCode::success_ok, in, headers);
} catch (std::exception &e) {
BOOST_LOG(warning) << "GetCover: "sv << e.what();
bad_request(response, request, e.what());
bad_request(response, e.what());
}
}
@@ -1661,7 +1661,7 @@ namespace confighttp {
std::string key = input_tree.value("key", "");
if (key.empty()) {
bad_request(response, request, "Cover key is required");
bad_request(response, "Cover key is required");
return;
}
std::string url = input_tree.value("url", "");
@@ -1672,11 +1672,11 @@ namespace confighttp {
std::basic_string path = coverdir + http::url_escape(key) + ".png";
if (!url.empty()) {
if (http::url_get_host(url) != "images.igdb.com") {
bad_request(response, request, "Only images.igdb.com is allowed");
bad_request(response, "Only images.igdb.com is allowed");
return;
}
if (!http::download_file(url, path)) {
bad_request(response, request, "Failed to download cover");
bad_request(response, "Failed to download cover");
return;
}
} else {
@@ -1690,7 +1690,7 @@ namespace confighttp {
send_response(response, output_tree);
} catch (std::exception &e) {
BOOST_LOG(warning) << "UploadCover: "sv << e.what();
bad_request(response, request, e.what());
bad_request(response, e.what());
}
}
@@ -1786,14 +1786,14 @@ namespace confighttp {
std::string error_message = std::accumulate(errors.begin(), errors.end(), std::string(), [](const std::string &a, const std::string &b) {
return a.empty() ? b : a + ", " + b;
});
bad_request(response, request, error_message);
bad_request(response, error_message);
return;
}
send_response(response, output_tree);
} catch (std::exception &e) {
BOOST_LOG(warning) << "SavePassword: "sv << e.what();
bad_request(response, request, e.what());
bad_request(response, e.what());
}
}
@@ -1853,7 +1853,7 @@ namespace confighttp {
const nlohmann::json input_tree = nlohmann::json::parse(ss);
const std::string pairing_id = input_tree.value("pairing_id", "");
if (!nvhttp::is_valid_pairing_id(pairing_id)) {
bad_request(response, request, "pairing_id must contain exactly 32 hexadecimal characters");
bad_request(response, "pairing_id must contain exactly 32 hexadecimal characters");
return;
}
@@ -1862,7 +1862,7 @@ namespace confighttp {
send_response(response, output_tree);
} catch (nlohmann::json::exception &e) {
BOOST_LOG(warning) << "CancelPairing: "sv << e.what();
bad_request(response, request, e.what());
bad_request(response, e.what());
}
}
@@ -1907,15 +1907,15 @@ namespace confighttp {
const std::string pairing_id = input_tree.value("pairing_id", "");
const std::string pin = input_tree.value("pin", "");
if (!nvhttp::is_valid_pairing_id(pairing_id)) {
bad_request(response, request, "pairing_id must contain exactly 32 hexadecimal characters");
bad_request(response, "pairing_id must contain exactly 32 hexadecimal characters");
return;
}
if (!nvhttp::is_valid_pairing_pin(pin)) {
bad_request(response, request, "PIN must contain exactly 4 numeric digits");
bad_request(response, "PIN must contain exactly 4 numeric digits");
return;
}
if (!nvhttp::is_valid_pairing_name(name)) {
bad_request(response, request, "Client name must contain between 1 and 128 bytes");
bad_request(response, "Client name must contain between 1 and 128 bytes");
return;
}
@@ -1923,7 +1923,7 @@ namespace confighttp {
send_response(response, output_tree);
} catch (std::exception &e) {
BOOST_LOG(warning) << "SavePin: "sv << e.what();
bad_request(response, request, e.what());
bad_request(response, e.what());
}
}
@@ -2072,18 +2072,18 @@ namespace confighttp {
try {
const auto input = nlohmann::json::parse(request->content.string());
if (!input.is_object() || !input.contains("id") || !input["id"].is_string()) {
bad_request(response, request, "A permission ID is required");
bad_request(response, "A permission ID is required");
return;
}
const bool requested = platf::request_permission(input["id"].get<std::string>());
if (!requested) {
bad_request(response, request, "Unknown or unavailable permission");
bad_request(response, "Unknown or unavailable permission");
return;
}
send_response(response, {{"status", true}});
} catch (const nlohmann::json::exception &) {
bad_request(response, request, "Invalid permission request");
bad_request(response, "Invalid permission request");
}
}
@@ -2229,7 +2229,7 @@ namespace confighttp {
auto license_key = input_tree.value("license_key", "");
input_tree["license_key"] = "";
if (license_key.empty()) {
bad_request(response, request, "License key is required");
bad_request(response, "License key is required");
return;
}
@@ -2240,7 +2240,7 @@ namespace confighttp {
} else if (action == "deactivate") {
result = lvh::deactivate_license();
} else {
bad_request(response, request, "Unknown license action");
bad_request(response, "Unknown license action");
return;
}
@@ -2257,7 +2257,7 @@ namespace confighttp {
#endif
send_response(response, build_virtualhid_license_status(result));
} catch (const nlohmann::json::exception &) {
bad_request(response, request, "Invalid license request");
bad_request(response, "Invalid license request");
}
}
@@ -2425,7 +2425,7 @@ namespace confighttp {
}
if (!fs::is_directory(dir_path, ec)) {
bad_request(response, request, "Path is not a directory");
bad_request(response, "Path is not a directory");
return;
}
@@ -2444,7 +2444,7 @@ namespace confighttp {
send_response(response, output_tree);
} catch (const fs::filesystem_error &e) {
BOOST_LOG(warning) << "BrowseDirectory: "sv << e.what();
bad_request(response, request, e.what());
bad_request(response, e.what());
}
}
@@ -2467,9 +2467,9 @@ namespace confighttp {
};
};
// Default resource handlers
const https_handler_t bad_request_handler = [](const resp_https_t &response, const req_https_t &request) {
bad_request(response, request);
// Unsupported methods use the common HTTP handler signature and do not inspect the request.
const https_handler_t bad_request_handler = [](const resp_https_t &response, const req_https_t &) {
bad_request(response);
};
// error by default
server.default_resource["DELETE"] = bad_request_handler;
+5 -6
View File
@@ -41,8 +41,8 @@ namespace confighttp {
void send_unauthorized(const resp_https_t &response, const req_https_t &request);
void send_redirect(const resp_https_t &response, const req_https_t &request, const char *path);
bool authenticate(const resp_https_t &response, const req_https_t &request);
void not_found(const resp_https_t &response, const req_https_t &request, const std::string &error_message = "Not Found");
void bad_request(const resp_https_t &response, const req_https_t &request, const std::string &error_message = "Bad Request");
void not_found(const resp_https_t &response, const std::string &error_message = "Not Found");
void bad_request(const resp_https_t &response, const std::string &error_message = "Bad Request");
/**
* @brief Check content type.
*
@@ -64,14 +64,13 @@ namespace confighttp {
bool validate_csrf_token(const resp_https_t &response, const req_https_t &request, const std::string &client_id);
std::string get_client_id(const req_https_t &request);
/**
* @brief Check app index.
* @brief Validate an application index and report an invalid index through the response.
*
* @param response HTTP response object to populate.
* @param request HTTP request data from the client.
* @param index Zero-based index of the item being addressed.
* @return True when the request passes validation and processing may continue.
* @return True when the index identifies an existing application.
*/
bool check_app_index(const resp_https_t &response, const req_https_t &request, int index);
bool check_app_index(const resp_https_t &response, int index);
/**
* @brief Serve the Web UI single-page application entry document.
*
+4 -4
View File
@@ -73,20 +73,20 @@ void on_signal(int sig, FN &&fn) {
}
/**
* @brief Cmd to func.
* @brief Dispatch commands through a common signature, ignoring arguments each command does not need.
*/
std::map<std::string_view, std::function<int(const char *name, int argc, char **argv)>> cmd_to_func {
{"creds"sv, [](const char *name, int argc, char **argv) {
return args::creds(name, argc, argv);
}},
{"help"sv, [](const char *name, int argc, char **argv) {
{"help"sv, [](const char *name, int, char **) {
return args::help(name);
}},
{"version"sv, [](const char *name, int argc, char **argv) {
{"version"sv, [](const char *, int, char **) {
return args::version();
}},
#ifdef _WIN32
{"restore-nvprefs-undo"sv, [](const char *name, int argc, char **argv) {
{"restore-nvprefs-undo"sv, [](const char *, int, char **) {
return args::restore_nvprefs_undo();
}},
#endif
+2 -1
View File
@@ -90,11 +90,12 @@ namespace nvhttp {
/**
* @brief Enable client-certificate verification after the listening socket is bound.
* @details Ignore the TLS preverification result and context so failed authorization can receive an HTTP error.
*/
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);
context.set_verify_callback([](int verified, boost::asio::ssl::verify_context &ctx) {
context.set_verify_callback([](int, boost::asio::ssl::verify_context &) {
// To respond with an error message, a connection must be established
return 1;
});
+9 -10
View File
@@ -119,16 +119,15 @@ namespace platf {
/**
* @brief Create a microphone capture stream for the requested layout.
* @details Capture always uses the canonical channel layout; Opus mapping is applied by the encoder.
*
* @param mapping Opus channel mapping table for the requested layout. Ignored: capture always
* uses the canonical layout, as on the WASAPI and macOS backends.
* @param channels Number of audio channels in the stream.
* @param sample_rate Audio sample rate in hertz.
* @param frame_size Number of samples captured per audio frame.
* @param source_name Source name.
* @return Microphone capture object for the requested audio layout, or nullptr on failure.
*/
std::unique_ptr<mic_t> microphone([[maybe_unused]] const std::uint8_t *mapping, int channels, std::uint32_t sample_rate, std::uint32_t frame_size, std::string source_name) {
std::unique_ptr<mic_t> microphone(int channels, std::uint32_t sample_rate, std::uint32_t frame_size, std::string source_name) {
// Capture in the canonical channel layout, not the one described by the Opus mapping.
//
// The Opus mapping is applied by the multistream encoder in audio.cpp and is undone by
@@ -282,12 +281,12 @@ namespace platf {
/**
* @brief Forward a PulseAudio integer callback value into a Sunshine alarm.
* @details The PulseAudio callback signature supplies a context that is not needed to ring the alarm.
*
* @param ctx PulseAudio context that emitted the callback.
* @param i Integer value returned by the PulseAudio operation.
* @param userdata Caller-provided pointer passed through the callback.
*/
void cb_i(ctx_t::pointer ctx, std::uint32_t i, void *userdata) {
void cb_i(ctx_t::pointer, std::uint32_t i, void *userdata) {
auto alarm = (safe::alarm_raw_t<int> *) userdata;
alarm->ring(i);
@@ -307,12 +306,12 @@ namespace platf {
/**
* @brief Record completion of a PulseAudio asynchronous operation.
* @details The PulseAudio callback signature supplies a context that is not needed to report completion.
*
* @param ctx Native context object used by the operation or callback.
* @param status Native status code returned by the platform API.
* @param userdata Caller-provided pointer passed through the callback.
*/
void success_cb(ctx_t::pointer ctx, int status, void *userdata) {
void success_cb(ctx_t::pointer, int status, void *userdata) {
assert(userdata != nullptr);
auto alarm = (safe::alarm_raw_t<int> *) userdata;
@@ -625,8 +624,8 @@ namespace platf {
/**
* @brief Create a microphone capture stream for the requested layout.
* @details The common audio interface supplies an Opus mapping that canonical capture does not need.
*
* @param mapping Opus channel mapping table for the requested layout.
* @param channels Number of audio channels in the stream.
* @param sample_rate Audio sample rate in hertz.
* @param frame_size Number of samples captured per audio frame.
@@ -634,7 +633,7 @@ namespace platf {
* @param host_audio_enabled Whether host playback should remain enabled during capture.
* @return Microphone capture object for the requested audio layout.
*/
std::unique_ptr<mic_t> microphone(const std::uint8_t *mapping, int channels, std::uint32_t sample_rate, std::uint32_t frame_size, bool continuous_audio, [[maybe_unused]] bool host_audio_enabled) override {
std::unique_ptr<mic_t> microphone(const std::uint8_t *, int channels, std::uint32_t sample_rate, std::uint32_t frame_size, bool continuous_audio, [[maybe_unused]] bool host_audio_enabled) override {
// Sink choice priority:
// 1. Config sink
// 2. Last sink swapped to (Usually virtual in this case)
@@ -650,7 +649,7 @@ namespace platf {
sink_name = get_default_sink_name();
}
return ::platf::microphone(mapping, channels, sample_rate, frame_size, get_monitor_name(sink_name));
return ::platf::microphone(channels, sample_rate, frame_size, get_monitor_name(sink_name));
}
bool is_sink_available(const std::string &sink) override {
+5 -1
View File
@@ -471,7 +471,11 @@ namespace platf {
return "00:00:00:00:00:00"s;
}
bp::child run_command(bool elevated, bool interactive, const std::string &cmd, boost::filesystem::path &working_dir, const bp::environment &env, FILE *file, std::error_code &ec, bp::group *group) {
/**
* @brief Launch a command using the caller's Linux privileges and session.
* @details The common platform signature's elevation and interactivity flags do not apply on Linux.
*/
bp::child run_command(bool, bool, const std::string &cmd, boost::filesystem::path &working_dir, const bp::environment &env, FILE *file, std::error_code &ec, bp::group *group) {
// clang-format off
if (!group) {
if (!file) {
+2 -2
View File
@@ -37,11 +37,11 @@ extern "C" {
#if !VA_CHECK_VERSION(1, 21, 0)
/**
* @brief Stub vaMapBuffer2 when building against libva before 2.21.0.
* @details The older vaMapBuffer API does not accept the mapping flags supplied by the new ABI.
*
* @param dpy VA display.
* @param buf_id VA buffer ID.
* @param pbuf Output mapped buffer pointer.
* @param flags Mapping flags.
* @return VA status code.
*/
VAStatus
@@ -49,7 +49,7 @@ extern "C" {
VADisplay dpy,
VABufferID buf_id,
void **pbuf,
uint32_t flags
uint32_t
) {
return vaMapBuffer(dpy, buf_id, pbuf);
}
+14 -20
View File
@@ -494,19 +494,12 @@ namespace wl {
void xdg_done(zxdg_output_v1 *) {}
/**
* @brief Receive physical wl-output geometry metadata.
*
* @param wl_output Wayland output.
* @param x Horizontal coordinate reported by Wayland.
* @param y Vertical coordinate reported by Wayland.
* @param physical_width Physical width in millimeters.
* @param physical_height Physical height in millimeters.
* @param subpixel Wayland subpixel layout enum.
* @param make Monitor manufacturer string reported by Wayland.
* @param model Monitor model string reported by Wayland.
* @param transform Wayland output transform value.
* @brief Ignore physical wl-output geometry; monitor placement comes from xdg-output metadata.
* @details Retain the wl-output listener signature while ignoring all event arguments.
*/
void wl_geometry(wl_output *wl_output, std::int32_t x, std::int32_t y, std::int32_t physical_width, std::int32_t physical_height, std::int32_t subpixel, const char *make, const char *model, std::int32_t transform) {}
void wl_geometry(wl_output *, std::int32_t, std::int32_t, std::int32_t, std::int32_t, std::int32_t, const char *, const char *, std::int32_t) {
// Logical placement comes from xdg-output, so physical geometry has nothing to update.
}
/**
* @brief Store the active pixel mode reported by wl-output.
@@ -521,18 +514,19 @@ namespace wl {
/**
* @brief Acknowledge completion of a wl-output metadata update.
*
* @param wl_output Wayland output.
* @details Retain the wl-output listener signature while ignoring the output argument.
*/
void wl_done(wl_output *wl_output) {}
void wl_done(wl_output *) {
// Metadata is stored as each event arrives; completion requires no additional work.
}
/**
* @brief Receive the wl-output scale factor.
*
* @param wl_output Wayland output.
* @param factor Wayland output scale factor.
* @brief Ignore wl-output scale; capture uses the active pixel mode and xdg-output logical size.
* @details Retain the wl-output listener signature while ignoring both event arguments.
*/
void wl_scale(wl_output *wl_output, std::int32_t factor) {}
void wl_scale(wl_output *, std::int32_t) {
// Pixel mode and xdg-output logical size already describe the capture and input coordinate spaces.
}
wl_output *output; ///< Wayland output associated with this monitor.
std::string name; ///< xdg-output name used for display selection.
+6 -6
View File
@@ -890,13 +890,13 @@ namespace rtsp_stream {
/**
* @brief Handle RTSP OPTIONS requests.
* @details The common command signature supplies a server pointer that this handler does not need.
*
* @param rtsp_server RTSP server instance handling the request.
* @param sock Socket used to read or write the protocol message.
* @param session Active streaming or pairing session for the request.
* @param req Parsed RTSP request being handled.
*/
void cmd_option(rtsp_server_t *rtsp_server, tcp::socket &sock, launch_session_t &session, msg_t &&req) {
void cmd_option(rtsp_server_t *, tcp::socket &sock, launch_session_t &session, msg_t &&req) {
OPTION_ITEM option {};
// I know these string literals will not be modified
@@ -910,13 +910,13 @@ namespace rtsp_stream {
/**
* @brief Handle RTSP DESCRIBE requests.
* @details The common command signature supplies a server pointer that this handler does not need.
*
* @param rtsp_server RTSP server instance.
* @param sock Client socket.
* @param session Launch session.
* @param req RTSP request message.
*/
void cmd_describe(rtsp_server_t *rtsp_server, tcp::socket &sock, launch_session_t &session, msg_t &&req) {
void cmd_describe(rtsp_server_t *, tcp::socket &sock, launch_session_t &session, msg_t &&req) {
OPTION_ITEM option {};
// I know these string literals will not be modified
@@ -1318,13 +1318,13 @@ namespace rtsp_stream {
/**
* @brief Handle RTSP PLAY requests and start streaming.
* @details The common command signature supplies a server pointer that this handler does not need.
*
* @param rtsp_server RTSP server instance handling the request.
* @param sock Socket used to read or write the protocol message.
* @param session Active streaming or pairing session for the request.
* @param req Parsed RTSP request being handled.
*/
void cmd_play(rtsp_server_t *rtsp_server, tcp::socket &sock, launch_session_t &session, msg_t &&req) {
void cmd_play(rtsp_server_t *, tcp::socket &sock, launch_session_t &session, msg_t &&req) {
OPTION_ITEM option {};
// I know these string literals will not be modified
+8 -6
View File
@@ -681,14 +681,14 @@ namespace video {
/**
* @brief Start the synchronous multi-client capture thread.
*
* @param ctx Native context object used by the operation or callback.
* @param ctx Unused state required by the shared capture lifetime callback signature.
* @return 0 when the capture thread is started.
*/
int start_capture_sync(capture_thread_sync_ctx_t &ctx);
/**
* @brief Stop capture sync processing.
* @brief Leave synchronous capture cleanup to the capture thread.
*
* @param ctx Native context object used by the operation or callback.
* @param ctx Unused state required by the shared capture lifetime callback signature.
*/
void end_capture_sync(capture_thread_sync_ctx_t &ctx);
/**
@@ -3668,16 +3668,18 @@ namespace video {
/**
* @brief Start capture sync.
* @details The shared lifetime callback supplies unused state; the thread accesses the shared capture queue directly.
*/
int start_capture_sync(capture_thread_sync_ctx_t &ctx) {
int start_capture_sync(capture_thread_sync_ctx_t &) {
std::jthread {&captureThreadSync}.detach();
return 0;
}
/**
* @brief Stop capture sync processing.
* @brief Leave synchronous capture cleanup to the capture thread.
*/
void end_capture_sync(capture_thread_sync_ctx_t &ctx) {
void end_capture_sync(capture_thread_sync_ctx_t &) {
// The capture thread stops its queue and signals its sessions when it exits.
}
/**
+44
View File
@@ -6,6 +6,7 @@
#ifdef __linux__
// standard includes
#include <chrono>
#include <vector>
// lib includes
@@ -14,9 +15,26 @@
// local includes
#include "src/platform/common.h"
#include "src/thread_safe.h"
namespace platf {
bool fill_canonical_channel_map(int channels, pa_channel_map &pa_map);
namespace pa {
/**
* @brief Forward a PulseAudio integer result to the caller's alarm.
* @param value Result supplied by PulseAudio.
* @param userdata Alarm receiving the result.
*/
void cb_i(pa_context *, std::uint32_t value, void *userdata);
/**
* @brief Forward PulseAudio operation completion to the caller's alarm.
* @param status Nonzero on success, zero on failure.
* @param userdata Alarm receiving the translated result.
*/
void success_cb(pa_context *, int status, void *userdata);
} // namespace pa
} // namespace platf
namespace {
@@ -109,4 +127,30 @@ TEST(LinuxAudioChannelMap, RejectsUnsupportedChannelCounts) {
EXPECT_FALSE(platf::fill_canonical_channel_map(platf::speaker::MAX_SPEAKERS + 1, pa_map));
}
/**
* @brief PulseAudio integer callbacks ring the supplied alarm without requiring a context.
*/
TEST(LinuxAudioCallbackTest, ForwardsIntegerResultWithoutContext) {
safe::alarm_raw_t<int> alarm;
platf::pa::cb_i(nullptr, 42, &alarm);
ASSERT_TRUE(alarm.wait_for(std::chrono::milliseconds {0}));
ASSERT_TRUE(alarm.status().has_value());
EXPECT_EQ(*alarm.status(), 42);
}
/**
* @brief PulseAudio success callbacks preserve the zero-success and one-failure alarm convention.
*/
TEST(LinuxAudioCallbackTest, TranslatesSuccessAndFailureWithoutContext) {
for (const int status : {0, 1, -1}) {
safe::alarm_raw_t<int> alarm;
platf::pa::success_cb(nullptr, status, &alarm);
ASSERT_TRUE(alarm.wait_for(std::chrono::milliseconds {0}));
ASSERT_TRUE(alarm.status().has_value());
EXPECT_EQ(*alarm.status(), status == 0 ? 1 : 0);
}
}
#endif // __linux__
@@ -102,6 +102,32 @@ TEST(WaylandMonitorTest, RecordsRefreshOfCurrentMode) {
EXPECT_EQ(monitor.refresh_mhz, 120000);
}
/**
* @brief Ignored wl-output events must not overwrite xdg-output placement or the active pixel mode.
*/
TEST(WaylandMonitorTest, ListenerIgnoresPhysicalGeometryScaleAndDone) {
wl::monitor_t monitor {nullptr};
monitor.xdg_position(nullptr, 100, 200);
monitor.xdg_size(nullptr, 1280, 720);
monitor.wl_mode(nullptr, WL_OUTPUT_MODE_CURRENT, 2560, 1440, 120000);
monitor.name = "DP-1";
monitor.description = "Logical monitor";
monitor.wl_listener.geometry(&monitor, nullptr, -500, -600, 600, 340, WL_OUTPUT_SUBPIXEL_UNKNOWN, "Other", "Physical monitor", WL_OUTPUT_TRANSFORM_90);
monitor.wl_listener.scale(&monitor, nullptr, 3);
monitor.wl_listener.done(&monitor, nullptr);
EXPECT_EQ(monitor.viewport.offset_x, 100);
EXPECT_EQ(monitor.viewport.offset_y, 200);
EXPECT_EQ(monitor.viewport.logical_width, 1280);
EXPECT_EQ(monitor.viewport.logical_height, 720);
EXPECT_EQ(monitor.viewport.width, 2560);
EXPECT_EQ(monitor.viewport.height, 1440);
EXPECT_EQ(monitor.refresh_mhz, 120000);
EXPECT_EQ(monitor.name, "DP-1");
EXPECT_EQ(monitor.description, "Logical monitor");
}
TEST(WaylandCaptureTest, UsesDamageCaptureOnlyOnAFasterOutput) {
// An output at twice the stream's rate, and one just at the threshold.
EXPECT_TRUE(wl::use_damage_capture(3, 60.0, 120000));
+4 -4
View File
@@ -215,19 +215,19 @@ protected:
// Add a route to test not_found
server->resource["^/notfound-test$"]["GET"] = [](
const std::shared_ptr<SimpleWeb::ServerBase<SimpleWeb::HTTPS>::Response> &response,
const std::shared_ptr<SimpleWeb::ServerBase<SimpleWeb::HTTPS>::Request> &request
const std::shared_ptr<SimpleWeb::ServerBase<SimpleWeb::HTTPS>::Request> &
) {
// Call the actual confighttp::not_found function
confighttp::not_found(response, request, "Test not found");
confighttp::not_found(response, "Test not found");
};
// Add a route to test bad_request
server->resource["^/badrequest-test$"]["GET"] = [](
const std::shared_ptr<SimpleWeb::ServerBase<SimpleWeb::HTTPS>::Response> &response,
const std::shared_ptr<SimpleWeb::ServerBase<SimpleWeb::HTTPS>::Request> &request
const std::shared_ptr<SimpleWeb::ServerBase<SimpleWeb::HTTPS>::Request> &
) {
// Call the actual confighttp::bad_request function
confighttp::bad_request(response, request, "Test bad request");
confighttp::bad_request(response, "Test bad request");
};
// Add a route to test send_response with JSON
+101
View File
@@ -0,0 +1,101 @@
/**
* @file tests/unit/test_rtsp.cpp
* @brief Verify RTSP response handlers through their common dispatcher signature.
*/
// standard includes
#include <array>
#include <string>
#include <utility>
// lib includes
#include <boost/asio.hpp>
extern "C" {
#include <moonlight-common-c/src/Rtsp.h>
}
// test includes
#include "../tests_common.h"
// local includes
#include <src/rtsp.h>
namespace rtsp_stream {
class rtsp_server_t;
/**
* @brief Release a parsed RTSP message and its allocation.
* @param message Message to release.
*/
void free_msg(PRTSP_MESSAGE message);
/**
* @brief Owning RTSP message type used by the dispatcher.
*/
using msg_t = util::safe_ptr<RTSP_MESSAGE, &free_msg>;
/**
* @brief Respond to OPTIONS through the common dispatcher signature.
* @param sock Connected client socket.
* @param session Launch session.
* @param req Request to handle.
*/
void cmd_option(rtsp_server_t *, boost::asio::ip::tcp::socket &sock, launch_session_t &session, msg_t &&req);
/**
* @brief Respond to DESCRIBE through the common dispatcher signature.
* @param sock Connected client socket.
* @param session Launch session.
* @param req Request to handle.
*/
void cmd_describe(rtsp_server_t *, boost::asio::ip::tcp::socket &sock, launch_session_t &session, msg_t &&req);
/**
* @brief Respond to PLAY through the common dispatcher signature.
* @param sock Connected client socket.
* @param session Launch session.
* @param req Request to handle.
*/
void cmd_play(rtsp_server_t *, boost::asio::ip::tcp::socket &sock, launch_session_t &session, msg_t &&req);
} // namespace rtsp_stream
/**
* @brief All three handlers respond with the request's sequence even without a server instance.
*/
TEST(RtspResponseTest, CommonDispatcherPreservesResponsesWithoutServerContext) {
using boost::asio::ip::tcp;
const std::array handlers {
&rtsp_stream::cmd_option,
&rtsp_stream::cmd_describe,
&rtsp_stream::cmd_play,
};
for (const auto handler : handlers) {
boost::asio::io_context io;
tcp::acceptor acceptor {io, {boost::asio::ip::address_v4::loopback(), 0}};
tcp::socket client {io};
client.connect(acceptor.local_endpoint());
auto server_socket = acceptor.accept();
rtsp_stream::launch_session_t session {};
rtsp_stream::msg_t request {new RTSP_MESSAGE {}};
request->sequenceNumber = 42;
handler(nullptr, server_socket, session, std::move(request));
server_socket.shutdown(tcp::socket::shutdown_send);
std::string response;
boost::system::error_code read_error;
boost::asio::read(client, boost::asio::dynamic_buffer(response), read_error);
ASSERT_EQ(read_error, boost::asio::error::eof);
EXPECT_TRUE(response.starts_with("RTSP/1.0 200 OK\r\n"));
EXPECT_NE(response.find("CSeq: 42\r\n"), std::string::npos);
if (handler == &rtsp_stream::cmd_describe) {
EXPECT_NE(response.find("a=x-ss-general.featureFlags:"), std::string::npos);
EXPECT_NE(response.find("a=x-ss-general.encryptionSupported:"), std::string::npos);
EXPECT_NE(response.find("a=fmtp:97 surround-params="), std::string::npos);
} else {
EXPECT_TRUE(response.ends_with("\r\n\r\n"));
}
}
}
+14 -2
View File
@@ -24,7 +24,15 @@ HANDLE session_change_event;
constexpr auto SERVICE_NAME = "SunshineService";
DWORD WINAPI HandlerEx(DWORD dwControl, DWORD dwEventType, LPVOID lpEventData, LPVOID lpContext) {
/**
* @brief Handle service control and console-session events from the Windows service manager.
* @details Event data and user context are unused but retained in the native callback signature.
*
* @param dwControl Service control code.
* @param dwEventType Session change event type when handling SERVICE_CONTROL_SESSIONCHANGE.
* @return NO_ERROR for handled controls or ERROR_CALL_NOT_IMPLEMENTED otherwise.
*/
DWORD WINAPI HandlerEx(DWORD dwControl, DWORD dwEventType, LPVOID, LPVOID) {
switch (dwControl) {
case SERVICE_CONTROL_INTERROGATE:
return NO_ERROR;
@@ -185,7 +193,11 @@ void TerminateSunshine(HANDLE console_token, const PROCESS_INFORMATION &process_
}
}
VOID WINAPI ServiceMain(DWORD dwArgc, LPTSTR *lpszArgv) {
/**
* @brief Run Sunshine in the active console session until the Windows service is stopped.
* @details The service manager's argument count and argument vector are unused; the native signature is retained.
*/
VOID WINAPI ServiceMain(DWORD, LPTSTR *) {
service_status_handle = RegisterServiceCtrlHandlerEx(SERVICE_NAME, HandlerEx, nullptr);
if (service_status_handle == nullptr) {
// Nothing we can really do here but terminate ourselves