Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 15 additions & 15 deletions app/src/gfn/cloud_session.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -73,9 +73,9 @@ SessionInfo GfnClient::StartSession(AuthSession& session, const std::string& lau
"Content-Type: application/json",
"nv-client-id: " + client_id_,
"nv-browser-type: CHROME",
"nv-client-streamer: NVIDIA-CLASSIC",
"nv-client-type: NATIVE",
"nv-client-version: 2.0.80.173",
"nv-client-streamer: WEBRTC",
"nv-client-type: BROWSER",
"nv-client-version: " + std::string(kCloudMatchClientVersion),
"nv-device-make: UNKNOWN",
"nv-device-model: UNKNOWN",
"nv-device-os: WINDOWS",
Expand Down Expand Up @@ -129,14 +129,14 @@ SessionInfo GfnClient::StartSession(AuthSession& session, const std::string& lau
AppendSessionTraceLog("START headers:\n" + HeadersForTrace(headers));
AppendSessionTraceLog("START request body:\n" + JsonForTrace(body));

HttpResponse response = http_client_.Post(url, kUserAgent, headers, body);
HttpResponse response = http_client_.Post(url, kCloudMatchUserAgent, headers, body);
if (response.status_code == 401)
{
session = ForceRefreshSavedSession(session);
jwt_token = ResolveSessionJwt(session);
headers[0] = "Authorization: GFNJWT " + jwt_token;
AppendSessionTraceLog("START authorization rejected; refreshed token and retrying once");
response = http_client_.Post(url, kUserAgent, headers, body);
response = http_client_.Post(url, kCloudMatchUserAgent, headers, body);
}
AppendSessionTraceLog("START response HTTP " + std::to_string(response.status_code));
AppendSessionTraceLog("START response body:\n" + JsonForTrace(response.body));
Expand Down Expand Up @@ -230,9 +230,9 @@ SessionInfo GfnClient::PollSession(AuthSession& session, const std::string& sess
"Authorization: GFNJWT " + jwt_token,
"nv-client-id: " + client_id_,
"nv-browser-type: CHROME",
"nv-client-streamer: NVIDIA-CLASSIC",
"nv-client-type: NATIVE",
"nv-client-version: 2.0.80.173",
"nv-client-streamer: WEBRTC",
"nv-client-type: BROWSER",
"nv-client-version: " + std::string(kCloudMatchClientVersion),
"nv-device-make: UNKNOWN",
"nv-device-model: UNKNOWN",
"nv-device-os: WINDOWS",
Expand All @@ -243,14 +243,14 @@ SessionInfo GfnClient::PollSession(AuthSession& session, const std::string& sess

AppendSessionTraceLog("POLL url=" + url);
AppendSessionTraceLog("POLL headers:\n" + HeadersForTrace(headers));
HttpResponse response = http_client_.Get(url, kUserAgent, headers);
HttpResponse response = http_client_.Get(url, kCloudMatchUserAgent, headers);
if (response.status_code == 401)
{
session = ForceRefreshSavedSession(session);
jwt_token = ResolveSessionJwt(session);
headers[0] = "Authorization: GFNJWT " + jwt_token;
AppendSessionTraceLog("POLL authorization rejected; refreshed token and retrying once");
response = http_client_.Get(url, kUserAgent, headers);
response = http_client_.Get(url, kCloudMatchUserAgent, headers);
}
AppendSessionTraceLog("POLL response HTTP " + std::to_string(response.status_code));
AppendSessionTraceLog("POLL response body:\n" + JsonForTrace(response.body));
Expand Down Expand Up @@ -342,9 +342,9 @@ void GfnClient::StopSession(AuthSession& session, const std::string& session_id)
"Authorization: GFNJWT " + jwt_token,
"nv-client-id: " + client_id_,
"nv-browser-type: CHROME",
"nv-client-streamer: NVIDIA-CLASSIC",
"nv-client-type: NATIVE",
"nv-client-version: 2.0.80.173",
"nv-client-streamer: WEBRTC",
"nv-client-type: BROWSER",
"nv-client-version: " + std::string(kCloudMatchClientVersion),
"nv-device-make: UNKNOWN",
"nv-device-model: UNKNOWN",
"nv-device-os: WINDOWS",
Expand All @@ -356,15 +356,15 @@ void GfnClient::StopSession(AuthSession& session, const std::string& session_id)
AppendSessionTraceLog("STOP url=" + url);
AppendSessionTraceLog("STOP headers:\n" + HeadersForTrace(headers));
HttpResponse response = http_client_.Request(
"DELETE", url, kUserAgent, headers);
"DELETE", url, kCloudMatchUserAgent, headers);
if (response.status_code == 401)
{
session = ForceRefreshSavedSession(session);
jwt_token = ResolveSessionJwt(session);
headers[0] = "Authorization: GFNJWT " + jwt_token;
AppendSessionTraceLog("STOP authorization rejected; refreshed token and retrying once");
response = http_client_.Request(
"DELETE", url, kUserAgent, headers);
"DELETE", url, kCloudMatchUserAgent, headers);
}
AppendSessionTraceLog("STOP response HTTP " + std::to_string(response.status_code));
AppendSessionTraceLog("STOP response body:\n" + JsonForTrace(response.body));
Expand Down
5 changes: 5 additions & 0 deletions app/src/gfn/cloud_session_internal.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,11 @@
namespace opennow::gfn::cloud_session
{

inline constexpr const char* kCloudMatchClientVersion = "2.0.89.154";
inline constexpr const char* kCloudMatchUserAgent =
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 "
"(KHTML, like Gecko) Chrome/152.0.0.0 Safari/537.36";

void ApplySessionNetworkInfo(json_t* session, SessionInfo& info);
void CollectIceServersFromObject(json_t* object, SessionInfo& info);

Expand Down
6 changes: 3 additions & 3 deletions app/src/gfn/cloud_session_protocol.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -327,7 +327,7 @@ std::string BuildSessionBody(
json_object_set_new(req, "clientIdentification", json_string("GFN-PC"));
json_object_set_new(req, "deviceHashId", json_string(device_id.c_str()));
json_object_set_new(req, "clientVersion", json_string("30.0"));
json_object_set_new(req, "clientPlatformName", json_string("windows"));
json_object_set_new(req, "clientPlatformName", json_string("browser"));

json_object_set_new(req, "availableSupportedControllers", json_array());
json_object_set_new(req, "sdkVersion", json_string("1.0"));
Expand Down Expand Up @@ -434,13 +434,13 @@ std::string CreateNetworkTestSession(
json_object_set_new(profile, "heightInPixels", json_integer(stream_settings.height));
json_object_set_new(profile, "framesPerSecond", json_integer(stream_settings.fps));
json_object_set_new(
request, "clientPlatformName", json_string("windows"));
request, "clientPlatformName", json_string("browser"));
json_object_set_new(request, "netTestProfile", profile);
json_object_set_new(root.get(), "netTestRequestData", request);

const HttpResponse response = http_client.Post(
streaming_base_url + "v2/nettestsession",
GfnClient::kUserAgent,
kCloudMatchUserAgent,
headers,
DumpJson(root.get()));
if (response.status_code < 200 || response.status_code >= 300)
Expand Down
2 changes: 2 additions & 0 deletions scripts/test-streaming-host.sh
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,8 @@ bash tests/run_gl_renderer_reconfiguration_test.sh
printf 'PASS gl_renderer_reconfiguration\n'
run_cpp nvst_sdp app/src/webrtc/nvst_sdp.cpp
run_cpp cloud_session_protocol "${sections[@]}" app/src/gfn/cloud_session_protocol.cpp app/src/gfn/shared.cpp -ljansson
run_cpp cloud_session_identity "${sections[@]}" app/src/gfn/cloud_session.cpp \
app/src/gfn/cloud_session_protocol.cpp app/src/gfn/cloud_session_state.cpp app/src/gfn/shared.cpp -ljansson
run_cpp stream_bitrate_pipeline "${sections[@]}" app/src/stream_settings.cpp app/src/localization.cpp \
app/src/gfn/cloud_session_protocol.cpp app/src/gfn/shared.cpp app/src/webrtc/nvst_sdp.cpp -ljansson
run_c rtp_h264_assembly extern/libpeer/src/rtp.c
Expand Down
140 changes: 140 additions & 0 deletions tests/cloud_session_identity_test.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
#include "gfn/cloud_session_internal.hpp"

#include <algorithm>
#include <cassert>
#include <cstdlib>
#include <filesystem>
#include <string>

namespace
{
bool reject_next_request = false;
int request_count = 0;
int refresh_count = 0;
opennow::StreamSettings settings;
}

namespace opennow
{
bool StreamDiagnosticsEnabled() { return false; }

StreamSettings LoadStreamSettings() { return settings; }

AuthSession GfnClient::EnsureFreshSavedSession(const AuthSession& session) const
{
return session;
}

AuthSession GfnClient::ForceRefreshSavedSession(const AuthSession& session) const
{
++refresh_count;
auto refreshed = session;
refreshed.tokens.id_token = "synthetic-refreshed-" + std::to_string(refresh_count);
return refreshed;
}

std::vector<StreamRegion> GfnClient::FetchStreamRegions(AuthSession&) const
{
return {};
}

std::vector<StreamRegion> GfnClient::MeasureStreamRegionLatencies(
std::vector<StreamRegion> regions) const
{
return regions;
}

HttpResponse HttpClient::Request(
const std::string& method, const std::string& url,
const std::string& user_agent, const std::vector<std::string>& headers,
const std::string& body, const std::string&, HttpTransferControl) const
{
++request_count;
assert(std::find(headers.begin(), headers.end(), "nv-client-type: BROWSER") != headers.end());
assert(std::find(headers.begin(), headers.end(), "nv-client-streamer: WEBRTC") != headers.end());
assert(std::find(headers.begin(), headers.end(), "nv-client-version: 2.0.89.154") != headers.end());
assert(user_agent.find("Chrome/") != std::string::npos);
assert(user_agent.find("NVIDIACEFClient") == std::string::npos);
assert(user_agent.find("GFN-PC/") == std::string::npos);
const std::string token = refresh_count == 0
? "synthetic-initial"
: "synthetic-refreshed-" + std::to_string(refresh_count);
assert(std::find(headers.begin(), headers.end(), "Authorization: GFNJWT " + token) != headers.end());

const bool network_test = url.ends_with("v2/nettestsession");
if (method == "POST")
{
const auto root = gfn::detail::LoadJson(body);
json_t* request = json_object_get(
root.get(), network_test ? "netTestRequestData" : "sessionRequestData");
assert(gfn::detail::GetString(request, "clientPlatformName") == "browser");
if (!network_test)
{
assert(gfn::detail::GetInteger(request, "appLaunchMode") == 2);
assert(gfn::detail::GetInteger(request, "remoteControllersBitmap") == 1);
assert(gfn::detail::GetString(request, "networkTestSessionId") == "synthetic-nettest");
}
}
if (network_test)
return {200, R"({"requestStatus":{"statusCode":1},"netTestSession":{"sessionId":"synthetic-nettest"}})"};
if (reject_next_request)
{
reject_next_request = false;
return {401, "{}"};
}
return {200, R"({"requestStatus":{"statusCode":1},"session":{"sessionId":"synthetic-session","status":1}})"};
}

HttpResponse HttpClient::Get(
const std::string& url, const std::string& user_agent,
const std::vector<std::string>& headers, const std::string& proxy,
HttpTransferControl control) const
{
return Request("GET", url, user_agent, headers, {}, proxy, control);
}

HttpResponse HttpClient::Post(
const std::string& url, const std::string& user_agent,
const std::vector<std::string>& headers, const std::string& body,
const std::string& proxy, HttpTransferControl control) const
{
return Request("POST", url, user_agent, headers, body, proxy, control);
}
}

int main()
{
const auto original_directory = std::filesystem::current_path();
std::string pattern = (std::filesystem::temp_directory_path() / "opennow-cloudmatch-XXXXXX").string();
const char* directory = mkdtemp(pattern.data());
assert(directory);
std::filesystem::current_path(directory);
std::filesystem::create_directories(opennow::gfn::detail::GetAppHome());
settings.region = "https://cloudmatch.example/";

for (const bool retry : {false, true})
{
request_count = 0;
refresh_count = 0;
opennow::AuthSession session;
session.user.user_id = "synthetic-user";
session.tokens.id_token = "synthetic-initial";
session.provider.streaming_service_url = settings.region;
opennow::GfnClient client;

reject_next_request = retry;
const auto started = client.StartSession(session, "123", settings);
assert(started.session_id == "synthetic-session");
reject_next_request = retry;
const auto polled = client.PollSession(session, started.session_id);
assert(polled.session_id == started.session_id);
reject_next_request = retry;
client.StopSession(session, started.session_id);
assert(request_count == (retry ? 7 : 4));
assert(refresh_count == (retry ? 3 : 0));
assert(opennow::gfn::cloud_session::LoadActiveCloudSessionId(session).empty());
}

std::filesystem::current_path(original_directory);
std::filesystem::remove_all(directory);
}
Loading