mirror of
https://github.com/aydencharles/onionHEN.git
synced 2026-10-22 11:53:07 -05:00
234 lines
8.0 KiB
C++
234 lines
8.0 KiB
C++
/* Host unit tests for the SPRX loader policy layer (no PS5 SDK). */
|
|
#include "test_harness.h"
|
|
#include "test_support.h"
|
|
|
|
#include <onion/sprx_loader.hpp>
|
|
|
|
#include <cstdio>
|
|
#include <limits.h>
|
|
#include <string>
|
|
#include <vector>
|
|
#include <unistd.h>
|
|
|
|
namespace {
|
|
|
|
struct FakeRuntime final : onion::sprx::IRemoteSprxRuntime {
|
|
std::vector<onion::sprx::LoadStatus> responses;
|
|
std::string received_path;
|
|
int find_calls = 0;
|
|
int load_calls = 0;
|
|
size_t response_index = 0;
|
|
bool loaded = false;
|
|
bool cleanup_ok = true;
|
|
|
|
bool find_loaded(pid_t, std::string_view,
|
|
onion::sprx::ModuleInfo *out) const noexcept override {
|
|
auto *self = const_cast<FakeRuntime *>(this);
|
|
++self->find_calls;
|
|
if (!loaded)
|
|
return false;
|
|
if (out)
|
|
out->handle = 42;
|
|
return true;
|
|
}
|
|
|
|
onion::sprx::LoadResult load(pid_t pid, std::string_view path,
|
|
std::string_view,
|
|
const onion::sprx::LoadOptions &) noexcept override {
|
|
++load_calls;
|
|
received_path.assign(path.data(), path.size());
|
|
const auto status = response_index < responses.size()
|
|
? responses[response_index++]
|
|
: onion::sprx::LoadStatus::Loaded;
|
|
onion::sprx::LoadResult result;
|
|
result.status = status;
|
|
result.pid = pid;
|
|
result.module_handle = status == onion::sprx::LoadStatus::Loaded ? 42 : 0;
|
|
result.remote_result = status == onion::sprx::LoadStatus::Loaded ? 0 : -1;
|
|
result.cleanup_ok = cleanup_ok;
|
|
if (status == onion::sprx::LoadStatus::Loaded)
|
|
loaded = true;
|
|
return result;
|
|
}
|
|
};
|
|
|
|
struct FakeAccess final : onion::sprx::ITargetAccessPolicy {
|
|
onion::sprx::AccessStatus prepare_status = onion::sprx::AccessStatus::Allowed;
|
|
onion::sprx::AccessStatus restore_status = onion::sprx::AccessStatus::Allowed;
|
|
int prepares = 0;
|
|
int restores = 0;
|
|
|
|
onion::sprx::AccessResult prepare(pid_t, std::string_view) noexcept override {
|
|
++prepares;
|
|
return {prepare_status};
|
|
}
|
|
|
|
onion::sprx::AccessResult restore(pid_t, std::string_view) noexcept override {
|
|
++restores;
|
|
return {restore_status};
|
|
}
|
|
};
|
|
|
|
bool make_request_file(std::string *path) {
|
|
char raw[256] = {};
|
|
if (onion_test_write_temp_text_file(".sprx", "test", raw, sizeof(raw)) != 0)
|
|
return false;
|
|
*path = raw;
|
|
return true;
|
|
}
|
|
|
|
onion::sprx::LoadRequest request_for(pid_t pid, const std::string &path) {
|
|
onion::sprx::LoadRequest request;
|
|
request.pid = pid;
|
|
request.title_id = "CUSA12345";
|
|
request.path = path;
|
|
request.options.retry_delay_ms = 0;
|
|
return request;
|
|
}
|
|
|
|
int test_allowlist_and_canonicalize() {
|
|
std::string path;
|
|
TEST_ASSERT_TRUE(make_request_file(&path));
|
|
const std::string link = path + ".link";
|
|
unlink(link.c_str());
|
|
TEST_ASSERT_EQ_INT(0, symlink(path.c_str(), link.c_str()));
|
|
|
|
onion::sprx::TitleIdAllowlist allowlist;
|
|
TEST_ASSERT_TRUE(allowlist.add_exact("CUSA12345"));
|
|
FakeRuntime runtime;
|
|
FakeAccess access;
|
|
onion::sprx::SprxLoader loader(runtime, access, allowlist);
|
|
auto result = loader.load(request_for(123, link));
|
|
|
|
TEST_ASSERT_TRUE(result.succeeded());
|
|
TEST_ASSERT_EQ_INT(1, result.attempts);
|
|
char resolved[PATH_MAX] = {};
|
|
TEST_ASSERT_TRUE(realpath(path.c_str(), resolved) != nullptr);
|
|
TEST_ASSERT_STREQ(resolved, runtime.received_path.c_str());
|
|
TEST_ASSERT_EQ_INT(1, access.prepares);
|
|
TEST_ASSERT_EQ_INT(1, access.restores);
|
|
unlink(link.c_str());
|
|
unlink(path.c_str());
|
|
return 0;
|
|
}
|
|
|
|
int test_title_allowlist_denies_before_runtime() {
|
|
std::string path;
|
|
TEST_ASSERT_TRUE(make_request_file(&path));
|
|
onion::sprx::TitleIdAllowlist allowlist;
|
|
TEST_ASSERT_TRUE(allowlist.add_exact("CUSA99999"));
|
|
FakeRuntime runtime;
|
|
FakeAccess access;
|
|
onion::sprx::SprxLoader loader(runtime, access, allowlist);
|
|
const auto result = loader.load(request_for(123, path));
|
|
TEST_ASSERT_EQ_INT(static_cast<int>(onion::sprx::LoadStatus::PolicyDenied),
|
|
static_cast<int>(result.status));
|
|
TEST_ASSERT_EQ_INT(0, runtime.load_calls);
|
|
TEST_ASSERT_EQ_INT(0, access.prepares);
|
|
unlink(path.c_str());
|
|
return 0;
|
|
}
|
|
|
|
int test_retry_and_failure_state() {
|
|
std::string path;
|
|
TEST_ASSERT_TRUE(make_request_file(&path));
|
|
onion::sprx::TitleIdAllowlist allowlist;
|
|
TEST_ASSERT_TRUE(allowlist.add_prefix("CUSA"));
|
|
FakeRuntime runtime;
|
|
runtime.responses = {onion::sprx::LoadStatus::Timeout,
|
|
onion::sprx::LoadStatus::AttachFailed,
|
|
onion::sprx::LoadStatus::Loaded};
|
|
FakeAccess access;
|
|
onion::sprx::SprxLoader loader(runtime, access, allowlist);
|
|
auto request = request_for(123, path);
|
|
request.options.max_attempts = 3;
|
|
const auto result = loader.load(request);
|
|
TEST_ASSERT_TRUE(result.succeeded());
|
|
TEST_ASSERT_EQ_INT(3, result.attempts);
|
|
TEST_ASSERT_EQ_INT(3, runtime.load_calls);
|
|
TEST_ASSERT_EQ_INT(3, access.prepares);
|
|
TEST_ASSERT_EQ_INT(3, access.restores);
|
|
unlink(path.c_str());
|
|
return 0;
|
|
}
|
|
|
|
int test_retry_exhausted_preserves_cause() {
|
|
std::string path;
|
|
TEST_ASSERT_TRUE(make_request_file(&path));
|
|
onion::sprx::TitleIdAllowlist allowlist;
|
|
TEST_ASSERT_TRUE(allowlist.add_exact("CUSA12345"));
|
|
FakeRuntime runtime;
|
|
runtime.responses = {onion::sprx::LoadStatus::Timeout,
|
|
onion::sprx::LoadStatus::Timeout};
|
|
FakeAccess access;
|
|
onion::sprx::SprxLoader loader(runtime, access, allowlist);
|
|
auto request = request_for(123, path);
|
|
request.options.max_attempts = 2;
|
|
const auto result = loader.load(request);
|
|
TEST_ASSERT_EQ_INT(static_cast<int>(onion::sprx::LoadStatus::RetryExhausted),
|
|
static_cast<int>(result.status));
|
|
TEST_ASSERT_EQ_INT(static_cast<int>(onion::sprx::LoadStatus::Timeout),
|
|
static_cast<int>(result.underlying_status));
|
|
TEST_ASSERT_EQ_INT(2, result.attempts);
|
|
unlink(path.c_str());
|
|
return 0;
|
|
}
|
|
|
|
int test_unresolved_cleanup_stops_retry() {
|
|
std::string path;
|
|
TEST_ASSERT_TRUE(make_request_file(&path));
|
|
onion::sprx::TitleIdAllowlist allowlist;
|
|
TEST_ASSERT_TRUE(allowlist.add_exact("CUSA12345"));
|
|
FakeRuntime runtime;
|
|
runtime.responses = {onion::sprx::LoadStatus::Timeout,
|
|
onion::sprx::LoadStatus::Loaded};
|
|
runtime.cleanup_ok = false;
|
|
FakeAccess access;
|
|
onion::sprx::SprxLoader loader(runtime, access, allowlist);
|
|
auto request = request_for(123, path);
|
|
request.options.max_attempts = 2;
|
|
const auto result = loader.load(request);
|
|
TEST_ASSERT_EQ_INT(static_cast<int>(onion::sprx::LoadStatus::Timeout),
|
|
static_cast<int>(result.status));
|
|
TEST_ASSERT_EQ_INT(1, result.attempts);
|
|
TEST_ASSERT_EQ_INT(1, runtime.load_calls);
|
|
TEST_ASSERT_TRUE(!result.cleanup_ok);
|
|
unlink(path.c_str());
|
|
return 0;
|
|
}
|
|
|
|
int test_access_prepare_failure() {
|
|
std::string path;
|
|
TEST_ASSERT_TRUE(make_request_file(&path));
|
|
onion::sprx::TitleIdAllowlist allowlist;
|
|
TEST_ASSERT_TRUE(allowlist.add_exact("CUSA12345"));
|
|
FakeRuntime runtime;
|
|
FakeAccess access;
|
|
access.prepare_status = onion::sprx::AccessStatus::Denied;
|
|
onion::sprx::SprxLoader loader(runtime, access, allowlist);
|
|
const auto result = loader.load(request_for(123, path));
|
|
TEST_ASSERT_EQ_INT(static_cast<int>(onion::sprx::LoadStatus::PolicyDenied),
|
|
static_cast<int>(result.status));
|
|
TEST_ASSERT_EQ_INT(0, runtime.load_calls);
|
|
TEST_ASSERT_EQ_INT(1, access.prepares);
|
|
TEST_ASSERT_EQ_INT(0, access.restores);
|
|
unlink(path.c_str());
|
|
return 0;
|
|
}
|
|
|
|
} // namespace
|
|
|
|
extern "C" int test_sprx_loader_suite(void) {
|
|
int failures = 0;
|
|
failures += onion_test_run("sprx_allowlist_canonicalize",
|
|
test_allowlist_and_canonicalize);
|
|
failures += onion_test_run("sprx_title_allowlist_denies",
|
|
test_title_allowlist_denies_before_runtime);
|
|
failures += onion_test_run("sprx_retry_state", test_retry_and_failure_state);
|
|
failures += onion_test_run("sprx_retry_exhausted", test_retry_exhausted_preserves_cause);
|
|
failures += onion_test_run("sprx_unresolved_cleanup", test_unresolved_cleanup_stops_retry);
|
|
failures += onion_test_run("sprx_access_prepare_failure",
|
|
test_access_prepare_failure);
|
|
return failures;
|
|
}
|