Files
AnyPS5/core/libs/tests/GuestHttpCache.cpp
T

445 lines
21 KiB
C++

#include "prx/libc/include/general/VabiMacros.hpp"
#include "prx/libSceHttpCache/libSceHttpCache.hpp"
#include "prx/libSceRtc/libSceRtc.hpp"
#include "prx/libSceUserService/libSceUserService.hpp"
#include <array>
#include <cstddef>
#include <cstdint>
#include <cstdlib>
#include <string>
#include <string_view>
#include <vector>
namespace {
using Header = HttpCacheHeader;
using Request = HttpCacheRequest;
using ResponseHeader = HttpCacheResponseHeader;
using Response = HttpCacheResponse;
using QuotaEntry = HttpCacheQuotaEntry;
using Quota = HttpCacheQuota;
}
namespace {
constexpr int NotFound = static_cast<int>(0x817a0011);
constexpr int InvalidValue = static_cast<int>(0x817a0012);
constexpr int InvalidUser = static_cast<int>(0x817a0013);
constexpr int TooManyCaches = static_cast<int>(0x817a0002);
constexpr int UserServiceAlreadyInitialized = static_cast<int>(0x80960003);
constexpr std::int32_t InitialUser = 0x10000000;
void Require(bool value) { if (!value) std::abort(); }
void TestWithoutCache() {
Require(sceHttpCacheTerm(1) == NotFound);
Require(sceHttpCacheCreateRequest(1, -1) == NotFound);
Require(sceHttpCacheClearAll(1, -1) == NotFound);
Require(sceHttpCacheCompleteRequest(0x01000001) == NotFound);
Require(sceHttpCacheDeleteRequest(0x01000001) == NotFound);
Require(sceHttpCacheSetCacheSharing(1) == 0);
Require(sceHttpCacheSystemSendStatistics() == 0);
Require(sceHttpCacheSystemTerm() == 0);
Require(sceHttpCacheDebugSetUrl() == 0);
Require(sceHttpCacheRegisterCacheInfoUrl(nullptr) == InvalidValue);
}
void TestRequests() {
const int cache = sceHttpCacheInit(0);
const int second = sceHttpCacheInit(0x10000);
Require(cache == 1 && second == 2);
Require(sceUserServiceInitialize(nullptr) == UserServiceAlreadyInitialized);
Require(sceHttpCacheCreateRequest(9, -1) == NotFound);
Require(sceHttpCacheCreateRequest(cache, 5) == InvalidUser);
Require(sceHttpCacheCreateRequest(cache, 0x10000001) == InvalidUser);
const int request = sceHttpCacheCreateRequest(cache, -1);
Require(request == 0x01000001);
Require(sceHttpCacheCreateRequest(cache, 255) == 0x01000002);
Require(sceHttpCacheCreateRequest(cache, InitialUser) == 0x01000003);
Require(sceHttpCacheCreateRequest(cache, 0xfe) == 0x01000004);
Require(sceHttpCacheCreateRequest(second, -1) == 0x02000001);
Require(sceHttpCacheCompleteRequest(request) == 0);
Require(sceHttpCacheCompleteRequest(0x01000099) == NotFound);
Require(sceHttpCacheCompleteRequest(0x09000001) == NotFound);
std::array<std::uint8_t, 4> buffer{1, 2, 3, 4};
Require(sceHttpCacheReadData(request, 4, nullptr) == InvalidValue);
Require(sceHttpCacheReadData(request, 0, nullptr) == 0);
Require(sceHttpCacheReadData(request, buffer.size(), buffer.data()) == 0);
Require(buffer[0] == 1 && buffer[3] == 4);
Require(sceHttpCacheReadData(0x01000099, buffer.size(), buffer.data()) == NotFound);
Require(sceHttpCacheSetData(request, nullptr, 4) == InvalidValue);
Require(sceHttpCacheSetData(0x01000099, buffer.data(), buffer.size()) == NotFound);
Require(sceHttpCacheSetData(request, buffer.data(), buffer.size()) == 0);
Request query{};
Response response{};
Request next{};
ResponseHeader header{};
std::int32_t result = 0;
Require(sceHttpCacheRetrieve(request, nullptr, &response, &next, &result) == InvalidValue);
Require(sceHttpCacheRetrieve(request, &query, &response, &next, nullptr) == InvalidValue);
Require(sceHttpCacheRetrieve(0x01000099, &query, &response, &next, &result) == NotFound);
const Header nullName[]{{nullptr, "a"}};
query.url = "http://a/";
query.headerCount = 1;
Require(sceHttpCacheRetrieve(request, &query, &response, &next, &result) == InvalidValue);
query.headers = nullName;
Require(sceHttpCacheRetrieve(request, &query, &response, &next, &result) == InvalidValue);
query.headerCount = 0;
result = 7;
Require(sceHttpCacheRetrieve(request, &query, &response, &next, &result) == 0 && result == 0);
Require(sceHttpCacheRevalidate(request, nullptr, &response, &result) == InvalidValue);
Require(sceHttpCacheRevalidate(request, &header, &response, nullptr) == InvalidValue);
Require(sceHttpCacheRevalidate(0x01000099, &header, &response, &result) == NotFound);
result = 7;
Require(sceHttpCacheRevalidate(request, &header, &response, &result) == 0 && result == 0);
Require(sceHttpCacheSetResponseHeader(request, nullptr) == InvalidValue);
Require(sceHttpCacheSetResponseHeader(0x01000099, &header) == NotFound);
Require(sceHttpCacheSetResponseHeader(request, &header) == 0);
header.headerCount = 1;
Require(sceHttpCacheSetResponseHeader(request, &header) == InvalidValue);
Require(sceHttpCacheRevalidate(request, &header, &response, &result) == InvalidValue);
header.headerCount = 0;
Require(sceHttpCacheDeleteRequest(0x01000099) == 0);
Require(sceHttpCacheDeleteRequest(0x09000001) == NotFound);
Require(sceHttpCacheDeleteRequest(request) == 0);
Require(sceHttpCacheCompleteRequest(request) == NotFound);
Require(sceHttpCacheReadData(request, 0, nullptr) == NotFound);
Require(sceHttpCacheCreateRequest(cache, -1) == 0x01000005);
Require(sceHttpCacheClearAll(cache, -1) == 0);
Require(sceHttpCacheTerm(second) == 0);
Require(sceHttpCacheCompleteRequest(0x02000001) == NotFound);
Require(sceHttpCacheTerm(cache) == 0);
Require(sceHttpCacheTerm(cache) == NotFound);
Require(sceUserServiceInitialize(nullptr) == 0);
Require(sceUserServiceTerminate() == 0);
}
void TestCacheArguments() {
const int cache = sceHttpCacheInit(0);
Require(cache == 3);
Require(sceHttpCacheCreateRequest(cache, -1) == 0x03000001);
Require(sceHttpCacheSetQuota(9, nullptr) == NotFound);
Require(sceHttpCacheSetQuota(cache, nullptr) == InvalidValue);
Quota quota{1, nullptr};
Require(sceHttpCacheSetQuota(cache, &quota) == InvalidValue);
QuotaEntry entries[]{{"", 1}};
quota.entries = entries;
Require(sceHttpCacheSetQuota(cache, &quota) == InvalidValue);
entries[0].name = "a";
const std::int32_t users[]{InitialUser};
Require(sceHttpCacheClearAllUserTags(9, users, 1) == NotFound);
Require(sceHttpCacheClearAllUserTags(cache, nullptr, 1) == InvalidValue);
Require(sceHttpCacheClearAllUserTags(cache, users, 21) == InvalidValue);
Require(sceHttpCacheTerm(cache) == 0);
}
void TestCacheLimit() {
std::vector<int> caches;
for (int i = 0; i < 127; ++i) {
const int cache = sceHttpCacheInit(0);
Require(cache >= 1 && cache <= 127);
caches.push_back(cache);
}
Require(caches.front() == 4 && caches[123] == 127 && caches[124] == 1 && caches.back() == 3);
Require(sceHttpCacheInit(0) == TooManyCaches);
Require(sceHttpCacheTerm(caches[10]) == 0);
Require(sceHttpCacheInit(0) == caches[10]);
for (const int cache : caches) Require(sceHttpCacheTerm(cache) == 0);
}
constexpr std::uint64_t TicksPerSecond = 1000000;
void Advance(std::uint64_t seconds) {
RtcTick tick{};
Require(sceRtcGetCurrentTick(&tick) == 0);
tick.tick += seconds * TicksPerSecond;
Require(sceRtcSetCurrentTick(&tick) == 0);
}
std::string_view Value(const Response& response, std::string_view name) {
for (std::size_t i = 0; i < response.headerCount; ++i) {
if (name == response.headers[i].name) return response.headers[i].value;
}
return {};
}
std::string_view Value(const Request& request, std::string_view name) {
for (std::size_t i = 0; i < request.headerCount; ++i) {
if (name == request.headers[i].name) return request.headers[i].value;
}
return {};
}
std::string ReadAll(int request) {
std::string body;
char buffer[4];
for (;;) {
const int read = sceHttpCacheReadData(request, sizeof(buffer), buffer);
Require(read >= 0 && read <= static_cast<int>(sizeof(buffer)));
if (read == 0) return body;
body.append(buffer, static_cast<std::size_t>(read));
}
}
struct Exchange {
int cache;
std::int32_t user = InitialUser;
std::uint32_t method = 0;
std::vector<Header> requestHeaders;
Request next{};
Response response{};
std::int32_t result = -1;
int request = 0;
int Retrieve(const char* url) {
request = sceHttpCacheCreateRequest(cache, user);
Require(request > 0);
Request query{method, 0, url, requestHeaders.size(), requestHeaders.data()};
next = Request{};
response = Response{};
result = -1;
return sceHttpCacheRetrieve(request, &query, &response, &next, &result);
}
void Store(std::uint32_t status, std::vector<Header> headers, std::string_view body) {
ResponseHeader header{status, 0, headers.size(), headers.data()};
Require(sceHttpCacheSetResponseHeader(request, &header) == 0);
Require(sceHttpCacheSetData(request, body.data(), body.size()) == 0);
Require(sceHttpCacheReadData(request, 0, nullptr) == 0);
Require(sceHttpCacheCompleteRequest(request) == 0);
}
int Revalidate(std::uint32_t status, std::vector<Header> headers) {
ResponseHeader header{status, 0, headers.size(), headers.data()};
response = Response{};
result = -1;
return sceHttpCacheRevalidate(request, &header, &response, &result);
}
void Done() {
Require(sceHttpCacheDeleteRequest(request) == 0);
}
};
void Cached(Exchange& exchange, const char* url, std::vector<Header> headers, std::string_view body) {
Require(exchange.Retrieve(url) == 0 && exchange.result == 0);
exchange.Store(200, std::move(headers), body);
exchange.Done();
}
void RequireMiss(Exchange& exchange, const char* url) {
Require(exchange.Retrieve(url) == 0 && exchange.result == 0);
exchange.Done();
}
void TestStoreAndHit() {
Exchange exchange{sceHttpCacheInit(0)};
exchange.requestHeaders = {{"User-Agent", "agent"}};
Require(exchange.Retrieve("http://store/a") == 0 && exchange.result == 0);
exchange.Store(200, {{"Cache-Control", "max-age=3600"}, {"ETag", "\"v1\""}}, "hello world");
Require(sceHttpCacheSetData(exchange.request, "!", 1) == 0);
exchange.Done();
Require(exchange.Retrieve("http://store/a") == 0 && exchange.result == 1);
Require(exchange.response.statusCode == 200 && exchange.response.headerCount == 2 && exchange.response.contentLength == 11);
Require(Value(exchange.response, "Cache-Control") == "max-age=3600" && Value(exchange.response, "ETag") == "\"v1\"");
Require(ReadAll(exchange.request) == "hello world");
Request again{0, 0, "http://store/a", 0, nullptr};
std::int32_t result = -1;
Require(sceHttpCacheRetrieve(exchange.request, &again, nullptr, &exchange.next, &result) == InvalidValue && result == 1);
exchange.Done();
exchange.requestHeaders = {{"User-Agent", "other"}};
RequireMiss(exchange, "http://store/a");
exchange.requestHeaders = {{"user-agent", "agent"}, {"If-None-Match", "\"v1\""}};
RequireMiss(exchange, "http://store/a");
exchange.requestHeaders = {{"User-Agent", "agent"}};
exchange.method = 1;
RequireMiss(exchange, "http://store/a");
exchange.method = 0;
exchange.user = -1;
RequireMiss(exchange, "http://store/a");
Cached(exchange, "http://store/system", {{"Cache-Control", "max-age=3600"}}, "x");
RequireMiss(exchange, "http://store/system");
exchange.user = InitialUser;
Require(exchange.Retrieve("http://store/a") == 0 && exchange.result == 1);
exchange.Done();
Require(exchange.Retrieve("http://store/a") == 0 && exchange.result == 1);
exchange.Store(200, {{"Content-Type", "text/plain"}}, "uncacheable");
exchange.Done();
RequireMiss(exchange, "http://store/a");
Cached(exchange, "http://store/b", {{"Cache-Control", "no-store, max-age=60"}}, "x");
RequireMiss(exchange, "http://store/b");
Require(exchange.Retrieve("http://store/b") == 0 && exchange.result == 0);
exchange.Store(404, {{"Cache-Control", "max-age=60"}}, "x");
exchange.Done();
RequireMiss(exchange, "http://store/b");
Require(exchange.Retrieve("http://store/c") == 0 && exchange.result == 0);
ResponseHeader pending{200, 0, 1, nullptr};
Header maxAge[]{{"Cache-Control", "max-age=60"}};
pending.headers = maxAge;
Require(sceHttpCacheSetResponseHeader(exchange.request, &pending) == 0);
Require(sceHttpCacheSetData(exchange.request, "lost", 4) == 0);
exchange.Done();
RequireMiss(exchange, "http://store/c");
Cached(exchange, "http://store/d", {{"Expires", "x"}}, "expires");
Require(exchange.Retrieve("http://store/d") == 0 && exchange.result == 0);
exchange.Done();
Cached(exchange, "http://store/e", {{"Cache-Control", "max-age=60"}, {"Vary", "Accept-Language"}}, "en");
exchange.requestHeaders = {{"User-Agent", "agent"}, {"Accept-Language", "fr"}};
RequireMiss(exchange, "http://store/e");
exchange.requestHeaders = {{"User-Agent", "agent"}};
Require(exchange.Retrieve("http://store/e") == 0 && exchange.result == 1);
exchange.Done();
Require(sceHttpCacheClearAll(exchange.cache, -1) == 0);
Require(sceHttpCacheClearAll(exchange.cache, 5) == 0);
Require(exchange.Retrieve("http://store/e") == 0 && exchange.result == 1);
exchange.Done();
Require(sceHttpCacheTerm(exchange.cache) == 0);
exchange.cache = sceHttpCacheInit(0);
Require(exchange.Retrieve("http://store/e") == 0 && exchange.result == 1);
exchange.Done();
Require(sceHttpCacheClearAll(exchange.cache, InitialUser) == 0);
RequireMiss(exchange, "http://store/e");
Require(sceHttpCacheTerm(exchange.cache) == 0);
}
void TestLifetimeOrder() {
Exchange exchange{sceHttpCacheInit(0)};
Cached(exchange, "http://order/expires-later", {{"Date", "Sat, 01 Jan 2000 00:00:00 GMT"}, {"Expires", "Sat, 01 Jan 2000 01:00:00 GMT"}}, "x");
RequireMiss(exchange, "http://order/expires-later");
Cached(exchange, "http://order/expires-earlier", {{"Date", "Sat, 01 Jan 2000 01:00:00 GMT"}, {"Expires", "Sat, 01 Jan 2000 00:00:00 GMT"}}, "expired");
Require(exchange.Retrieve("http://order/expires-earlier") == 0 && exchange.result == 1);
Require(ReadAll(exchange.request) == "expired" && Value(exchange.response, "Warning").empty());
exchange.Done();
Cached(exchange, "http://order/modified-earlier", {{"Date", "Mon, 10 Jan 2000 00:00:00 GMT"}, {"Last-Modified", "Sat, 01 Jan 2000 00:00:00 GMT"}}, "x");
Require(exchange.Retrieve("http://order/modified-earlier") == 0 && exchange.result == 2);
Require(Value(exchange.next, "If-Modified-Since") == "Sat, 01 Jan 2000 00:00:00 GMT");
exchange.Done();
Cached(exchange, "http://order/modified-later", {{"Date", "Sat, 01 Jan 2000 00:00:00 GMT"}, {"Last-Modified", "Sun, 02 Jan 2000 00:00:00 GMT"}}, "modified");
Advance(400 * 24 * 3600);
Require(exchange.Retrieve("http://order/modified-later") == 0 && exchange.result == 1);
Require(ReadAll(exchange.request) == "modified" && !Value(exchange.response, "Warning").empty());
exchange.Done();
Require(sceHttpCacheTerm(exchange.cache) == 0);
}
void TestRevalidation() {
Exchange exchange{sceHttpCacheInit(0)};
Cached(exchange, "http://stale/a", {{"Cache-Control", "max-age=60"}, {"ETag", "e1"}, {"Last-Modified", "lm"}}, "old body");
Advance(120);
exchange.requestHeaders = {{"Accept", "*/*"}};
Require(exchange.Retrieve("http://stale/a") == 0 && exchange.result == 2);
Require(std::string_view(exchange.next.url) == "http://stale/a" && exchange.next.method == 0 && exchange.next.headerCount == 3);
Require(Value(exchange.next, "Accept") == "*/*" && Value(exchange.next, "If-Modified-Since") == "lm" && Value(exchange.next, "If-None-Match") == "e1");
Require(exchange.response.headers == nullptr);
Require(sceHttpCacheReadData(exchange.request, 0, nullptr) == 0);
Request query{0, 0, "http://stale/a", 0, nullptr};
std::int32_t result = -1;
Require(sceHttpCacheRetrieve(exchange.request, &query, nullptr, nullptr, &result) == InvalidValue && result == 2);
Require(exchange.Revalidate(304, {{"Cache-Control", "max-age=600"}, {"ETag", "e2"}, {"X-New", "1"}}) == 0 && exchange.result == 1);
Require(exchange.response.statusCode == 200 && exchange.response.contentLength == 8);
Require(Value(exchange.response, "Cache-Control") == "max-age=600" && Value(exchange.response, "ETag") == "e1" && Value(exchange.response, "X-New").empty());
Require(ReadAll(exchange.request) == "old body");
exchange.Done();
Advance(120);
Require(exchange.Retrieve("http://stale/a") == 0 && exchange.result == 1);
Require(ReadAll(exchange.request) == "old body");
exchange.Done();
exchange.requestHeaders = {{"Cache-Control", "no-cache"}};
Require(exchange.Retrieve("http://stale/a") == 0 && exchange.result == 2);
Require(exchange.Revalidate(200, {{"Cache-Control", "max-age=600"}, {"ETag", "e3"}}) == 0 && exchange.result == 0);
Require(exchange.response.headers == nullptr);
Require(sceHttpCacheSetData(exchange.request, "new body", 8) == 0);
Require(sceHttpCacheCompleteRequest(exchange.request) == 0);
exchange.Done();
exchange.requestHeaders = {{"Pragma", "no-cache"}};
Require(exchange.Retrieve("http://stale/a") == 0 && exchange.result == 2 && Value(exchange.next, "If-None-Match") == "e3");
exchange.Done();
exchange.requestHeaders = {};
Require(exchange.Retrieve("http://stale/a") == 0 && exchange.result == 1);
Require(ReadAll(exchange.request) == "new body");
exchange.Done();
Require(exchange.Retrieve("http://stale/a") == 0 && exchange.result == 1);
Require(exchange.Revalidate(304, {{"Cache-Control", "max-age=60"}}) == 0 && exchange.result == 0);
exchange.Done();
exchange.requestHeaders = {{"Cache-Control", "no-cache"}};
Require(exchange.Retrieve("http://stale/a") == 0 && exchange.result == 2);
Require(exchange.Revalidate(304, {{"ETag", "e3"}}) == 0 && exchange.result == 0);
exchange.Done();
exchange.requestHeaders = {};
RequireMiss(exchange, "http://stale/a");
Cached(exchange, "http://stale/b", {{"Cache-Control", "max-age=60"}, {"ETag", "b"}}, "b");
exchange.requestHeaders = {{"Cache-Control", "no-cache"}};
Require(exchange.Retrieve("http://stale/b") == 0 && exchange.result == 2);
Require(exchange.Revalidate(404, {{"Cache-Control", "max-age=60"}}) == 0 && exchange.result == 0);
exchange.Done();
exchange.requestHeaders = {};
RequireMiss(exchange, "http://stale/b");
Cached(exchange, "http://stale/c", {{"Cache-Control", "max-age=60"}}, "c");
Advance(120);
RequireMiss(exchange, "http://stale/c");
exchange.requestHeaders = {{"Cache-Control", "max-stale"}};
RequireMiss(exchange, "http://stale/c");
exchange.requestHeaders = {};
Cached(exchange, "http://stale/d", {{"Cache-Control", "max-age=60"}}, "d");
Advance(120);
exchange.requestHeaders = {{"Cache-Control", "max-stale"}};
Require(exchange.Retrieve("http://stale/d") == 0 && exchange.result == 1);
Require(Value(exchange.response, "Warning") == "110 - \"Response is Stale\"" && ReadAll(exchange.request) == "d");
exchange.Done();
exchange.requestHeaders = {{"Cache-Control", "max-stale=30"}};
RequireMiss(exchange, "http://stale/d");
exchange.requestHeaders = {};
Cached(exchange, "http://stale/e", {{"Cache-Control", "max-age=60, stale-while-revalidate=600"}, {"ETag", "e"}}, "swr");
Advance(120);
Require(exchange.Retrieve("http://stale/e") == 0 && exchange.result == 2);
Require(exchange.response.statusCode == 200 && Value(exchange.response, "Warning") == "110 - \"Response is Stale\"");
Require(Value(exchange.response, "Age").substr(0, 2) == "12" && ReadAll(exchange.request) == "swr");
exchange.Done();
Cached(exchange, "http://stale/f", {{"Cache-Control", "max-age=60, stale-if-error=600"}, {"ETag", "f"}}, "sie");
Advance(120);
Require(exchange.Retrieve("http://stale/f") == 0 && exchange.result == 2 && exchange.response.headers == nullptr);
Require(exchange.Revalidate(503, {}) == 0 && exchange.result == 1);
Require(exchange.response.statusCode == 200 && Value(exchange.response, "Warning") == "110 - \"Response is Stale\"" && ReadAll(exchange.request) == "sie");
exchange.Done();
Require(sceHttpCacheTerm(exchange.cache) == 0);
}
void TestPublicResponses() {
Exchange exchange{sceHttpCacheInit(0)};
Require(exchange.Retrieve("http://public/a") == 0 && exchange.result == 0);
std::vector<Header> headers{{"Cache-Control", "public, max-age=60"}};
ResponseHeader header{200, 0, headers.size(), headers.data()};
Require(sceHttpCacheSetCacheSharing(0) == 0);
Require(sceHttpCacheSetResponseHeader(exchange.request, &header) == 0);
Require(sceHttpCacheCompleteRequest(exchange.request) == 0);
exchange.Done();
Require(exchange.Retrieve("http://public/a") == 0 && exchange.result == 1);
exchange.Done();
Require(sceHttpCacheSetCacheSharing(1) == 0);
exchange.user = 0xfe;
Cached(exchange, "http://public/b", headers, "shared");
Require(exchange.Retrieve("http://public/b") == 0 && exchange.result == 1 && ReadAll(exchange.request) == "shared");
exchange.Done();
exchange.user = InitialUser;
Require(exchange.Retrieve("http://public/a") == 0);
exchange.Done();
Require(sceHttpCacheClearAll(exchange.cache, 0xfe) == 0);
RequireMiss(exchange, "http://public/b");
Require(sceHttpCacheTerm(exchange.cache) == 0);
}
}
int main() {
TestWithoutCache();
TestRequests();
TestCacheArguments();
TestCacheLimit();
TestStoreAndHit();
TestLifetimeOrder();
TestRevalidation();
TestPublicResponses();
return 0;
}