ref:main
/**
* @file tests/e2e/moonlight_client/gamestream.h
* @brief Minimal GameStream HTTP client (serverinfo, pairing, applist, launch) for end-to-end tests.
*/
#pragma once
// standard includes
#include <cstdint>
#include <functional>
#include <map>
#include <optional>
#include <string>
#include <vector>
namespace e2e {
/**
* @brief Client identity used for pairing and HTTPS requests.
*/
struct identity_t {
std::string cert_pem; ///< Self-signed client certificate.
std::string key_pem; ///< Private key of the certificate.
std::string unique_id; ///< Client unique id sent with every request.
};
/**
* @brief Load an identity from a directory, creating it on first use.
*
* @param dir Directory holding `client.pem`, `client.key` and `uniqueid`.
* @return Identity.
*/
identity_t load_or_create_identity(const std::string &dir);
/**
* @brief Parsed `/serverinfo` fields needed to start a stream.
*/
struct server_info_t {
std::string app_version; ///< `appversion`.
std::string gfe_version; ///< `GfeVersion`.
int https_port {0}; ///< `HttpsPort`.
int codec_mode_support {0}; ///< `ServerCodecModeSupport`.
bool paired {false}; ///< `PairStatus`.
std::string state; ///< `state`.
};
/**
* @brief One application from `/applist`.
*/
struct app_t {
std::string title; ///< App title.
int id {0}; ///< App id.
};
/**
* @brief GameStream client bound to one host.
*/
class client_t {
public:
/**
* @brief Create a client.
*
* @param host Host name or address.
* @param http_port Plain HTTP port (Sunshine `port`).
* @param identity Client identity.
*/
client_t(std::string host, int http_port, identity_t identity);
/**
* @brief Fetch `/serverinfo` over HTTP, or HTTPS when paired.
*
* @param https Whether to use the HTTPS port with the client certificate.
* @return Server information, or nothing on failure.
*/
std::optional<server_info_t> server_info(bool https);
/**
* @brief Run the four-phase pairing handshake.
*
* @param pin Four-digit PIN.
* @param device_name Name shown in Sunshine's pending pairing list.
* @param approve Called on another thread once `getservercert` is pending; must submit the PIN.
* @return True when the host reports the client as paired.
*/
bool pair(const std::string &pin, const std::string &device_name, const std::function<bool()> &approve);
/**
* @brief List applications over HTTPS.
*
* @return Applications, empty on failure.
*/
std::vector<app_t> app_list();
/**
* @brief Launch an application over HTTPS.
*
* @param app_id App id.
* @param width Stream width.
* @param height Stream height.
* @param fps Stream frame rate.
* @param ri_key Remote input AES key (16 bytes).
* @param ri_key_id Remote input key id.
* @param extra_query Extra query parameters, starting with `&`.
* @return RTSP session URL, or nothing on failure.
*/
std::optional<std::string> launch(int app_id, int width, int height, int fps, const std::vector<std::uint8_t> &ri_key, int ri_key_id, const std::string &extra_query);
/**
* @brief Quit the running application over HTTPS.
*
* @return True on success.
*/
bool cancel();
/**
* @brief HTTPS port learned from serverinfo.
*
* @return Port number.
*/
[[nodiscard]] int https_port() const {
return https;
}
private:
/**
* @brief Perform a GET request.
*
* @param https Whether to use HTTPS with the client certificate.
* @param path Path starting with `/`.
* @param query Query string without the leading `?`; uniqueid and uuid are appended.
* @param timeout_s Request timeout in seconds.
* @return Response body, or nothing on transport failure.
*/
std::optional<std::string> get(bool https, const std::string &path, const std::string &query, long timeout_s = 10);
std::string host; ///< Host name or address.
int http; ///< HTTP port.
int https {0}; ///< HTTPS port.
identity_t id; ///< Client identity.
};
/**
* @brief Extract the text of the first `<tag>` element.
*
* @param xml XML document.
* @param tag Element name.
* @return Element text, or nothing.
*/
std::optional<std::string> xml_tag(const std::string &xml, const std::string &tag);
/**
* @brief Extract the `status_code` attribute of the root element.
*
* @param xml XML document.
* @return Status code, or -1.
*/
int xml_status(const std::string &xml);
/**
* @brief Encode bytes as uppercase hex.
*
* @param data Bytes.
* @return Hex string.
*/
std::string to_hex(const std::vector<std::uint8_t> &data);
/**
* @brief Generate random bytes.
*
* @param n Number of bytes.
* @return Random bytes.
*/
std::vector<std::uint8_t> random_bytes(std::size_t n);
/**
* @brief Perform an authenticated JSON request against Sunshine's web API.
*
* @param method HTTP method.
* @param url Full URL.
* @param user Web UI user name.
* @param password Web UI password.
* @param body JSON body, empty for none.
* @return Response body, or nothing on transport failure.
*/
std::optional<std::string> api_request(const std::string &method, const std::string &url, const std::string &user, const std::string &password, const std::string &body);
} // namespace e2e