#include "prx/libc/include/general/VabiMacros.hpp" #include "prx/libSceHttpCache/libSceHttpCache.hpp" #include "prx/libSceRtc/libSceRtc.hpp" #include "prx/libSceUserService/libSceUserService.hpp" #include #include #include #include #include #include #include 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(0x817a0011); constexpr int InvalidValue = static_cast(0x817a0012); constexpr int InvalidUser = static_cast(0x817a0013); constexpr int TooManyCaches = static_cast(0x817a0002); constexpr int UserServiceAlreadyInitialized = static_cast(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 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, "a) == InvalidValue); QuotaEntry entries[]{{"", 1}}; quota.entries = entries; Require(sceHttpCacheSetQuota(cache, "a) == 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 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(sizeof(buffer))); if (read == 0) return body; body.append(buffer, static_cast(read)); } } struct Exchange { int cache; std::int32_t user = InitialUser; std::uint32_t method = 0; std::vector
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
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
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
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
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; }