mirror of
https://github.com/boykopovar/AnyPS5.git
synced 2026-10-28 00:35:47 +00:00
433 lines
25 KiB
C++
433 lines
25 KiB
C++
#include "prx/libSceNpCommon/include/Api.hpp"
|
|
#include "SceTypes.hpp"
|
|
|
|
#include <atomic>
|
|
#include <chrono>
|
|
#include <array>
|
|
#include <cstdint>
|
|
#include <cstdio>
|
|
#include <cstdlib>
|
|
#include <cstring>
|
|
#include <thread>
|
|
|
|
namespace {
|
|
|
|
constexpr int NpIpcAlreadyInitialized = static_cast<int>(0x80559D00);
|
|
constexpr int NpIpcNotInitialized = static_cast<int>(0x80559D01);
|
|
constexpr int NpOutOfMemory = static_cast<int>(0x80550005);
|
|
constexpr int NpNullObject = static_cast<int>(0x80558005);
|
|
constexpr int NpNotInitialized = static_cast<int>(0x80558001);
|
|
constexpr int NpAlreadyInitialized = static_cast<int>(0x80558002);
|
|
constexpr int NpAlreadyQueued = static_cast<int>(0x80558006);
|
|
constexpr int NpUtilInvalid = static_cast<int>(0x80550601);
|
|
constexpr int NpUtilBufferTooSmall = static_cast<int>(0x80550602);
|
|
constexpr int NpUtilOutOfRange = static_cast<int>(0x8055060A);
|
|
constexpr int NpUtilInvalidCharacter = static_cast<int>(0x80550608);
|
|
constexpr int NpInvalidFormat = static_cast<int>(0x80558003);
|
|
constexpr int NpInvalidCharacter = static_cast<int>(0x80558004);
|
|
constexpr int NpBufferTooSmall = static_cast<int>(0x80558008);
|
|
constexpr int NpOutOfRange = static_cast<int>(0x8055800E);
|
|
constexpr int NpInvalidArgument = static_cast<int>(0x80558009);
|
|
constexpr int NpEventFlagBusy = static_cast<int>(0x8055800C);
|
|
constexpr int NpSemaBusy = static_cast<int>(0x8055800D);
|
|
constexpr int KernelNotPermitted = static_cast<int>(0x80020001);
|
|
constexpr int KernelNoSuchObject = static_cast<int>(0x80020003);
|
|
constexpr std::uint32_t EventFlagWaitAnd = 1;
|
|
constexpr std::uint32_t EventFlagShared = 0x100;
|
|
|
|
void Require(bool value, const char* message) {
|
|
if (value) return;
|
|
std::fprintf(stderr, "%s\n", message);
|
|
std::abort();
|
|
}
|
|
|
|
void* APS5_VABI ReturnArgument(void* arg) {
|
|
Pthread self {};
|
|
Require(sceNpThreadGetId(&self) == 0 && self != nullptr, "a created thread can read its id");
|
|
return arg;
|
|
}
|
|
|
|
void TestAtomics() {
|
|
std::int32_t value = 5;
|
|
Require(sceNpAtomicInc32(&value) == 5 && value == 6, "increment returns the previous value");
|
|
Require(sceNpAtomicDec32(&value) == 6 && value == 5, "decrement returns the previous value");
|
|
Require(sceNpAtomicCas32(&value, 4, 9) == 5 && value == 5, "a failed compare-and-swap returns the current value");
|
|
Require(sceNpAtomicCas32(&value, 5, 9) == 5 && value == 9, "a successful compare-and-swap returns the previous value");
|
|
}
|
|
|
|
void TestSemaphoresAndFlags() {
|
|
KernelSema sema {};
|
|
Require(sceNpCreateSema(&sema, 0, 0, 2, "NpSema") == 0, "a semaphore is created");
|
|
Require(sceNpPollSema(sema) == NpSemaBusy, "an empty semaphore reports the busy error");
|
|
Require(sceNpSignalSema(sema) == 0 && sceNpPollSema(sema) == 0, "a signalled semaphore can be taken once");
|
|
Require(sceNpDeleteSema(sema) == 0, "the semaphore is deleted");
|
|
|
|
KernelEventFlag flag {};
|
|
Require(sceNpCreateEventFlag(&flag, "NpFlag", 0, 0) == 0, "an event flag is created");
|
|
std::uint64_t result = 0;
|
|
Require(sceNpPollEventFlag(flag, 1, EventFlagWaitAnd, &result) == NpEventFlagBusy, "a clear event flag reports the busy error");
|
|
Require(sceNpSetEventFlag(flag, 1) == 0 && sceNpPollEventFlag(flag, 1, EventFlagWaitAnd, &result) == 0 && result == 1, "a set bit is polled");
|
|
Require(sceNpDeleteEventFlag(flag) == 0, "the event flag is deleted");
|
|
}
|
|
|
|
void TestSharedEventFlags() {
|
|
KernelEventFlag creator {};
|
|
Require(sceNpCreateEventFlag(&creator, "NpSharedFlag", EventFlagShared, 0) == 0, "a shared event flag is created");
|
|
KernelEventFlag opened {};
|
|
Require(sceNpOpenEventFlag(&opened, "NpSharedFlag") == 0 && opened != creator, "the shared event flag is opened by name");
|
|
std::uint64_t result = 0;
|
|
Require(sceNpSetEventFlag(opened, 2) == 0 && sceNpPollEventFlag(creator, 2, EventFlagWaitAnd, &result) == 0 && result == 2,
|
|
"a bit set through the opened handle is polled through the creator");
|
|
Require(sceNpCloseEventFlag(creator) == KernelNotPermitted, "the creator handle is not closed");
|
|
Require(sceNpCloseEventFlag(opened) == 0, "the opened handle is closed");
|
|
Require(sceNpCloseEventFlag(opened) == KernelNoSuchObject, "a closed handle is gone");
|
|
Require(sceNpDeleteEventFlag(creator) == 0, "the creator deletes the shared event flag");
|
|
}
|
|
|
|
void TestThreads() {
|
|
Require(sceNpThreadGetId(nullptr) == NpInvalidArgument, "a null thread id output is refused");
|
|
Pthread thread {};
|
|
int marker = 0;
|
|
Require(sceNpCreateThread(&thread, ReturnArgument, &marker, 0, 0x10000, 0, "NpThread") == 0, "a thread is created");
|
|
void* value = nullptr;
|
|
Require(sceNpJoinThread(thread, &value) == 0 && value == &marker, "the thread is joined with its return value");
|
|
}
|
|
|
|
void UseAllocator(const NpAllocator& allocator) {
|
|
auto* block = static_cast<std::uint8_t*>(allocator.allocate(64, allocator.context));
|
|
Require(block != nullptr, "the allocator hands out memory");
|
|
block[63] = 0x5A;
|
|
block = static_cast<std::uint8_t*>(allocator.reallocate(block, 256, allocator.context));
|
|
Require(block != nullptr && block[63] == 0x5A, "reallocation keeps the contents");
|
|
allocator.release(block, allocator.context);
|
|
allocator.release(nullptr, allocator.context);
|
|
}
|
|
|
|
void TestHeaps() {
|
|
NpMemoryHeap memoryHeap {};
|
|
Require(sceNpMemoryHeapInit(nullptr, 0x1000, "np-test") == NpNullObject, "a null memory heap is refused");
|
|
Require(sceNpMemoryHeapInit(&memoryHeap, 0x1001, "np-test") == 0, "a memory heap is created");
|
|
Require(memoryHeap.memory.size == 0x4000 && memoryHeap.memory.address != nullptr && memoryHeap.heap.mspace != nullptr,
|
|
"the memory heap size is rounded up to 16 KiB");
|
|
NpAllocatorEx allocatorEx {};
|
|
sceNpMemoryHeapGetAllocatorEx(&memoryHeap, 0x1234, &allocatorEx);
|
|
|
|
Require(allocatorEx.allocator.context == &memoryHeap && allocatorEx.outOfMemoryError == 0x1234,
|
|
"the extended allocator carries the caller's error code");
|
|
UseAllocator(allocatorEx.allocator);
|
|
sceNpMemoryHeapDestroy(&memoryHeap);
|
|
|
|
Require(memoryHeap.memory.address == nullptr && memoryHeap.heap.mspace == nullptr, "destroying clears the memory heap");
|
|
|
|
NpKernelMemory memory {};
|
|
void* address = nullptr;
|
|
Require(sceNpAllocateKernelMemoryWithAlignment(0x10, "np-test", &address, nullptr) == NpNullObject,
|
|
"kernel memory needs an object");
|
|
Require(sceNpAllocateKernelMemoryWithAlignment(0x10, "np-test", &address, &memory) == 0
|
|
&& address == memory.address && memory.size == 0x4000,
|
|
"kernel memory is mapped in 16 KiB units");
|
|
NpHeap heap {};
|
|
Require(sceNpHeapInit(&heap, memory.address, memory.size, "np-test") == 0 && heap.mspace != nullptr, "a heap is created");
|
|
NpAllocator allocator {};
|
|
sceNpHeapGetAllocator(&heap, &allocator);
|
|
|
|
Require(allocator.context == &heap, "the heap allocator uses the heap as its context");
|
|
UseAllocator(allocator);
|
|
NpHeapStat stat {};
|
|
Require(sceNpHeapGetStat(&heap, &stat) == 0 && stat.maxInuseSize >= stat.currentInuseSize, "heap statistics are reported");
|
|
sceNpHeapDestroy(&heap);
|
|
|
|
Require(heap.mspace == nullptr, "destroying clears the heap");
|
|
Require(sceNpFreeKernelMemory(&memory) == 0 && memory.address == nullptr, "kernel memory is released");
|
|
Require(sceNpFreeKernelMemory(nullptr) == NpNullObject, "freeing needs an object");
|
|
|
|
Require(sceNpArchInit() == 0 && sceNpArchInit() == 0, "the library initialises more than once");
|
|
NpAllocatorEx* global = sceNpGlobalHeapGetAllocatorExPtr();
|
|
Require(global->outOfMemoryError == NpOutOfMemory, "the global allocator reports the library's out-of-memory error");
|
|
UseAllocator(global->allocator);
|
|
sceNpArchTerm();
|
|
|
|
UseAllocator(sceNpGlobalHeapGetAllocatorExPtr()->allocator);
|
|
sceNpArchTerm();
|
|
|
|
Require(sceNpIpc2ClientTerm() == NpIpcNotInitialized, "the ipc client must be started before it stops");
|
|
Require(sceNpIpc2ClientInit() == 0 && sceNpIpc2ClientInit() == NpIpcAlreadyInitialized, "the ipc client starts once");
|
|
Require(sceNpIpc2ClientTerm() == 0 && sceNpIpc2ClientInit() == 0 && sceNpIpc2ClientTerm() == 0, "the ipc client restarts");
|
|
}
|
|
|
|
struct CalloutRecord {
|
|
std::atomic<int> order {0};
|
|
std::atomic<int> first {0};
|
|
std::atomic<int> second {0};
|
|
};
|
|
|
|
CalloutRecord g_callouts;
|
|
|
|
void APS5_VABI RecordFirst(void*) {
|
|
g_callouts.first = ++g_callouts.order;
|
|
}
|
|
|
|
void APS5_VABI RecordSecond(void*) {
|
|
g_callouts.second = ++g_callouts.order;
|
|
}
|
|
|
|
void APS5_VABI RecordUnexpected(void*) {
|
|
Require(false, "a stopped callout does not run");
|
|
}
|
|
|
|
void TestCallouts() {
|
|
NpCalloutContext context {};
|
|
NpCallout callout {};
|
|
Require(sceNpCalloutStartOnCtx(&context, &callout, 0, RecordFirst, nullptr) == NpNotInitialized, "a context must be initialised first");
|
|
Require(sceNpCalloutInitCtx(&context, "np-callout", 0x4000, 0, 0) == 0, "a callout context starts");
|
|
Require(sceNpCalloutInitCtx(&context, "np-callout", 0x4000, 0, 0) == NpAlreadyInitialized, "a context starts once");
|
|
|
|
NpCallout stopped {};
|
|
int wasPending = 0;
|
|
Require(sceNpCalloutStartOnCtx(&context, &stopped, 60000000, RecordUnexpected, nullptr) == 0, "a distant callout is queued");
|
|
Require(sceNpCalloutStartOnCtx(&context, &stopped, 60000000, RecordUnexpected, nullptr) == NpAlreadyQueued,
|
|
"a queued callout cannot be queued again");
|
|
Require(sceNpCalloutStopOnCtx(&context, &stopped, &wasPending) == 0 && wasPending == 1, "a queued callout is removed");
|
|
Require(sceNpCalloutStopOnCtx(&context, &stopped, &wasPending) == 0 && wasPending == 0, "removing twice reports nothing pending");
|
|
|
|
NpCallout queuedFirst {};
|
|
NpCallout queuedSecond {};
|
|
Require(sceNpCalloutStartOnCtx(&context, &queuedFirst, 30000000, RecordUnexpected, nullptr) == 0, "an earlier distant callout is queued");
|
|
Require(sceNpCalloutStartOnCtx(&context, &queuedSecond, 60000000, RecordUnexpected, nullptr) == 0, "a later distant callout is queued");
|
|
const std::uint64_t queuedDeadline = queuedFirst.deadline;
|
|
Require(sceNpCalloutStartOnCtx(&context, &queuedFirst, 0, RecordFirst, &context) == NpAlreadyQueued,
|
|
"the earlier callout cannot be queued again");
|
|
Require(queuedFirst.next == &queuedSecond && queuedFirst.deadline == queuedDeadline && queuedFirst.handler == RecordUnexpected &&
|
|
queuedFirst.arg == nullptr, "a rejected start leaves the queued callout unchanged");
|
|
Require(sceNpCalloutStopOnCtx(&context, &queuedSecond, &wasPending) == 0 && wasPending == 1,
|
|
"the later callout is still pending after the rejected start");
|
|
Require(sceNpCalloutStopOnCtx(&context, &queuedFirst, &wasPending) == 0 && wasPending == 1, "the earlier callout is still pending");
|
|
|
|
NpCallout later {};
|
|
Require(sceNpCalloutStartOnCtx(&context, &later, 20000, RecordSecond, nullptr) == 0, "a later callout is queued");
|
|
Require(sceNpCalloutStartOnCtx(&context, &callout, 1000, RecordFirst, nullptr) == 0, "an earlier callout is queued");
|
|
for (int i = 0; i < 2000 && g_callouts.second == 0; ++i) std::this_thread::sleep_for(std::chrono::milliseconds(1));
|
|
Require(g_callouts.first == 1 && g_callouts.second == 2, "callouts run in deadline order");
|
|
sceNpCalloutTermCtx(&context);
|
|
|
|
Require(context.initialized == 0, "terminating clears the context");
|
|
sceNpCalloutTermCtx(&context);
|
|
|
|
}
|
|
|
|
bool Equals(const char* actual, const char* expected) {
|
|
return std::strcmp(actual, expected) == 0;
|
|
}
|
|
|
|
void TestIds() {
|
|
Require(sceNpUtilCmpAccountId(7, 7) == 0 && sceNpUtilCmpAccountId(7, 8) == 1, "account ids compare for equality");
|
|
|
|
constexpr int NullArgument = static_cast<int>(0x80550003);
|
|
constexpr int InvalidNpId = static_cast<int>(0x80550605);
|
|
constexpr int NotMatch = static_cast<int>(0x80550609);
|
|
auto makeNpId = [](const char* name, std::uint32_t opt, std::uint32_t platform) {
|
|
std::array<std::uint8_t, 0x24> id{};
|
|
std::memcpy(id.data(), name, std::strlen(name));
|
|
std::memcpy(id.data() + 0x14, &opt, sizeof(opt));
|
|
std::memcpy(id.data() + 0x18, &platform, sizeof(platform));
|
|
id[0x1C] = 1;
|
|
return id;
|
|
};
|
|
const auto alice = makeNpId("Alice_01", 5, 0x11223344);
|
|
auto other = alice;
|
|
Require(sceNpCmpNpId(nullptr, alice.data()) == NullArgument && sceNpCmpNpId(alice.data(), nullptr) == NullArgument, "null NpIds are refused");
|
|
Require(sceNpCmpNpId(alice.data(), other.data()) == 0, "identical NpIds match");
|
|
other[0x1C] = 0;
|
|
Require(sceNpCmpNpId(alice.data(), other.data()) == InvalidNpId && sceNpCmpNpId(other.data(), alice.data()) == InvalidNpId, "the flag byte at 0x1C must be 1");
|
|
Require(sceNpCmpNpId(makeNpId("Bob-2", 5, 0x11223344).data(), alice.data()) == NotMatch, "different online ids do not match");
|
|
Require(sceNpCmpNpId(makeNpId("Alice_01", 6, 0x11223344).data(), alice.data()) == NotMatch, "different words at 0x14 do not match");
|
|
Require(sceNpCmpNpId(makeNpId("Alice_01", 5, 0).data(), alice.data()) == 0, "an empty first byte at 0x18 on the first id matches");
|
|
Require(sceNpCmpNpId(alice.data(), makeNpId("Alice_01", 5, 0).data()) == 0, "an empty first byte at 0x18 on the second id matches");
|
|
Require(sceNpCmpNpId(alice.data(), makeNpId("Alice_01", 5, 0x11223355).data()) == NotMatch, "different words at 0x18 do not match");
|
|
|
|
Require(sceNpIntIsOnlineIdString("Alice_01") && sceNpIntIsOnlineIdString("Bob-2"), "letters, digits, '_' and '-' are allowed");
|
|
Require(!sceNpIntIsOnlineIdString("ab") && !sceNpIntIsOnlineIdString("abcdefghijklmnopq"), "online ids are 3 to 16 characters");
|
|
Require(!sceNpIntIsOnlineIdString("1abc") && !sceNpIntIsOnlineIdString("ab.c") && !sceNpIntIsOnlineIdString(nullptr),
|
|
"online ids start with a letter and use no other symbols");
|
|
}
|
|
|
|
void TestJsonText() {
|
|
char escaped[64] = {};
|
|
Require(sceNpUtilJsonEscape(escaped, sizeof(escaped), "a\"b/\n\x01", 7) == 0 && Equals(escaped, "a\\\"b\\/\\n\\u0001"),
|
|
"json escaping covers quotes, solidus, newlines and controls");
|
|
std::strcpy(escaped, "x");
|
|
Require(sceNpUtilJsonEscape(escaped, sizeof(escaped), "yz", 2) == 0 && Equals(escaped, "xyz"), "escaping appends to the buffer");
|
|
Require(sceNpUtilJsonEscape(escaped, 6, "a", 1) == NpUtilBufferTooSmall, "escaping needs room for one escape");
|
|
Require(sceNpUtilJsonEscape(escaped, sizeof(escaped), nullptr, 1) == NpUtilInvalid, "escaping needs a source");
|
|
|
|
char text[32] = {};
|
|
std::size_t length = 0;
|
|
Require(sceNpUtilJsonUnescape(text, sizeof(text), "a\\nb\\u0041", 10, &length) == 0 && Equals(text, "a\nbA") && length == 5,
|
|
"json unescaping decodes escapes and counts the terminator");
|
|
Require(sceNpUtilJsonUnescape(text, sizeof(text), "\\uD83D\\uDE00", 12, &length) == 0 && Equals(text, "\xF0\x9F\x98\x80")
|
|
&& length == 5,
|
|
"surrogate pairs become one UTF-8 character");
|
|
Require(sceNpUtilJsonUnescape(text, sizeof(text), "\\u00G1", 6, &length) == NpUtilBufferTooSmall && Equals(text, "\\u00G1"),
|
|
"a bad unicode escape is copied and reported");
|
|
Require(sceNpUtilJsonUnescape(nullptr, sizeof(text), "abc", 3, &length) == 0 && length == 4, "a null buffer reports the size");
|
|
Require(sceNpUtilJsonUnescape(text, 3, "abc", 3, &length) == NpUtilBufferTooSmall, "unescaping needs room for the terminator");
|
|
|
|
char one[4] = {};
|
|
std::size_t consumed = 0;
|
|
Require(sceNpUtilJsonGetOneChar(one, sizeof(one), "\\u00e9x", 7, &length, &consumed) == 0 && length == 2 && consumed == 6
|
|
&& std::memcmp(one, "\xC3\xA9", 2) == 0,
|
|
"one escaped character is decoded");
|
|
Require(sceNpUtilJsonGetOneChar(one, sizeof(one), "\xE6\x97\xA5", 3, &length, &consumed) == 0 && length == 3 && consumed == 3,
|
|
"one UTF-8 character is copied");
|
|
Require(sceNpUtilJsonGetOneChar(one, sizeof(one), "\xE6\x97", 2, &length, &consumed) == NpUtilBufferTooSmall && length == 0
|
|
&& consumed == 3,
|
|
"a cut UTF-8 character reports its full length");
|
|
Require(sceNpUtilJsonGetOneChar(one, 1, "\xC3\xA9", 2, &length, &consumed) == NpUtilBufferTooSmall && length == 2,
|
|
"a short buffer still reports the decoded size");
|
|
|
|
const char* character = nullptr;
|
|
const char* japanese = "\xE6\x97\xA5" "a";
|
|
Require(sceNpUtilGetNthChar(1, japanese, &character) == 0 && character == japanese + 3, "characters are counted, not bytes");
|
|
Require(sceNpUtilGetNthChar(10, "abc", &character) == 0 && *character == '\0', "counting stops at the end");
|
|
Require(sceNpUtilGetNthChar(2, "a\x01b", &character) == NpUtilInvalidCharacter, "control characters are refused");
|
|
|
|
char url[32] = {};
|
|
Require(sceNpUtilHttpUrlEncode(url, sizeof(url), "a b=c*", 6, &length) == 0 && Equals(url, "a+b%3Dc*") && length == 9,
|
|
"form encoding keeps the safe set and escapes the rest");
|
|
Require(sceNpUtilHttpUrlEncode(nullptr, 0, "@", 1, &length) == 0 && length == 4, "a null buffer reports the encoded size");
|
|
Require(sceNpUtilHttpUrlEncode(url, 3, "@", 1, &length) == NpUtilBufferTooSmall, "encoding needs room for the terminator");
|
|
}
|
|
|
|
void TestTitleIds() {
|
|
NpTitleIdParts parts {{'A', 'B', 'C', 'D', '1', '2', '3', '4', '5'}, 0, 7};
|
|
char titleId[13] = {};
|
|
Require(sceNpUtilBuildTitleId(&parts, titleId, sizeof(titleId)) == 0 && Equals(titleId, "ABCD12345_07"), "the index is two digits");
|
|
Require(sceNpUtilBuildTitleId(&parts, titleId, 12) == NpUtilBufferTooSmall, "a title id needs 13 bytes");
|
|
parts.index = 100;
|
|
Require(sceNpUtilBuildTitleId(&parts, titleId, sizeof(titleId)) == NpUtilOutOfRange, "the index is at most 99");
|
|
}
|
|
|
|
void TestLanguagesAndDebugIds() {
|
|
char code[6] = {};
|
|
char tag[36] = {};
|
|
Require(sceNpUtilGetNpLanguageCode(1, code) == 0 && Equals(code, "en"), "language 1 is English");
|
|
Require(sceNpUtilGetNpLanguageCode2(20, tag) == 0 && Equals(tag, "es-419"), "language 20 is Latin American Spanish");
|
|
Require(Equals(sceNpUtilGetNpLanguageCodeStr(11), "zh-CN") && Equals(sceNpUtilGetNpLanguageCode2Str(30), "uk-UA"),
|
|
"language strings are returned directly");
|
|
Require(sceNpUtilGetNpLanguageCode(31, code) == NpUtilInvalid && sceNpUtilGetNpLanguageCodeStr(31) == nullptr,
|
|
"unknown languages are refused");
|
|
Require(sceNpUtilGetNpLanguageCode2(0, nullptr) == NpUtilInvalid, "a language code needs a buffer");
|
|
const std::uint32_t first = sceNpDbgAssignDebugId();
|
|
Require(first != 0 && sceNpDbgAssignDebugId() == first + 1, "debug ids count up");
|
|
}
|
|
|
|
void TestBase64() {
|
|
const std::uint8_t payload[] = {1, 2, 3, 4, 5};
|
|
char text[16] = {};
|
|
Require(sceNpBase64Encoder(payload, text, 5) == 8 && Equals(text, "AQIDBAU="), "base64 pads the last group");
|
|
Require(sceNpBase64Encoder(payload, text, 0) == 0, "an empty payload encodes to nothing");
|
|
Require(sceNpBase64GetDecodeSize("AQIDBAU=", 8) == 5 && sceNpBase64GetDecodeSize("AQ==", 4) == 1,
|
|
"the decoded size follows the padding");
|
|
Require(sceNpBase64GetDecodeSize("", 0) == NpInvalidFormat && sceNpBase64GetDecodeSize("AQI", 3) == NpInvalidFormat,
|
|
"base64 needs whole groups");
|
|
Require(sceNpBase64GetDecodeSize("AQ=A", 4) == NpInvalidCharacter && sceNpBase64GetDecodeSize("A=AAAQID", 8) == NpInvalidCharacter,
|
|
"padding is only allowed at the end");
|
|
std::uint8_t bytes[8] = {};
|
|
Require(sceNpBase64Decoder("AQIDBAU=", 8, bytes, sizeof(bytes)) == 5 && std::memcmp(bytes, payload, 5) == 0,
|
|
"base64 decodes back to the payload");
|
|
Require(sceNpBase64Decoder("AQID", 4, bytes, 2) == NpInvalidFormat, "decoding refuses a short buffer");
|
|
Require(sceNpBase64Decoder("AQ?D", 4, bytes, 3) == NpInvalidCharacter, "decoding refuses characters outside the alphabet");
|
|
|
|
const std::uint8_t url[] = {0xFB, 0x00};
|
|
std::memset(text, 0, sizeof(text));
|
|
Require(sceNpBase64UrlEncoder(url, text, 1) == 2 && Equals(text, "-w"), "base64url leaves out the padding");
|
|
Require(sceNpBase64UrlEncoder(url, text, 2) == 3 && Equals(text, "-wA"), "base64url writes three characters for two bytes");
|
|
Require(sceNpBase64UrlGetDecodeSize("-wA", 3) == 2 && sceNpBase64UrlGetDecodeSize("-w==", 4) == 1,
|
|
"base64url accepts padded and unpadded tails");
|
|
Require(sceNpBase64UrlGetDecodeSize("", 0) == NpInvalidCharacter && sceNpBase64UrlGetDecodeSize("AAAAA", 5) == NpInvalidCharacter,
|
|
"a single trailing character cannot be decoded");
|
|
Require(sceNpBase64UrlGetDecodeSize("+w==", 4) == NpInvalidCharacter, "base64url refuses the standard alphabet");
|
|
Require(sceNpBase64UrlDecoder("-wA", 3, bytes, 2) == 2 && bytes[0] == 0xFB && bytes[1] == 0, "base64url decodes a short tail");
|
|
Require(sceNpBase64UrlDecoder("-w", 2, bytes, 0) == NpInvalidFormat, "base64url decoding refuses a short buffer");
|
|
}
|
|
|
|
void TestNumbers() {
|
|
Require(sceNpHexToInt('a') == 10 && sceNpHexToInt('F') == 15 && sceNpHexToInt('g') == -1, "hex digits are read");
|
|
Require(sceNpIntToHex(10, false) == 'a' && sceNpIntToHex(15, true) == 'F' && sceNpIntToHex(9, true) == '9', "hex digits are written");
|
|
|
|
char text[32] = {};
|
|
std::size_t length = 0;
|
|
Require(sceNpUInt32ToStr(text, sizeof(text), 255, 16, 3, &length) == 0 && Equals(text, "0XFF") && length == 4,
|
|
"the hex prefix follows the case flag");
|
|
Require(sceNpUInt32ToStr(text, sizeof(text), 8, 8, 2, &length) == 0 && Equals(text, "010") && length == 3, "octal gets a zero prefix");
|
|
Require(sceNpUInt32ToStr(text, sizeof(text), 0, 10, 4, &length) == 0 && Equals(text, "0") && length == 1, "zero has no sign");
|
|
Require(sceNpUInt32ToStr(text, 4, 1234, 10, 0, &length) == NpBufferTooSmall, "the terminator must fit");
|
|
Require(sceNpUInt32ToStr(text, sizeof(text), 1, 37, 0, &length) == NpInvalidArgument, "the radix is limited to 36");
|
|
Require(sceNpUInt32ToStr(nullptr, 0, 42, 10, 4, &length) == 0 && length == 3, "a null buffer reports the length");
|
|
Require(sceNpInt32ToStr(text, sizeof(text), -2147483647 - 1, 10, 0, &length) == 0 && Equals(text, "-2147483648"),
|
|
"a negative 32-bit value always has a sign");
|
|
Require(sceNpInt32ToStr(text, 3, -42, 10, 0, &length) == NpBufferTooSmall, "the sign takes buffer space");
|
|
Require(sceNpInt64ToStr(text, sizeof(text), -42, 10, 0, &length) == 0 && Equals(text, "42") && length == 2,
|
|
"a 64-bit value only gets a sign with the sign flag");
|
|
Require(sceNpInt64ToStr(text, sizeof(text), -42, 10, 4, &length) == 0 && Equals(text, "-42"), "the sign flag writes the minus");
|
|
Require(sceNpUInt64ToStr(text, sizeof(text), UINT64_MAX, 16, 3, &length) == 0 && Equals(text, "0XFFFFFFFFFFFFFFFF") && length == 18,
|
|
"the full 64-bit range is formatted");
|
|
|
|
std::int32_t value32 = 0;
|
|
std::size_t parsed = 0;
|
|
Require(sceNpStrToInt32("-567abc", &value32, 10, &parsed) == 0 && value32 == -567 && parsed == 4, "parsing stops at the first non-digit");
|
|
Require(sceNpStrToInt32("0x1A", &value32, 0, &parsed) == 0 && value32 == 26, "base zero follows the prefix");
|
|
Require(sceNpStrToInt32("invalid", &value32, 10, &parsed) == NpOutOfRange, "no digits is an error");
|
|
parsed = 0;
|
|
Require(sceNpStrToInt32("3000000000", &value32, 10, &parsed) == NpOutOfRange && parsed == 10,
|
|
"the length is reported before the 32-bit range check");
|
|
std::int64_t value64 = 0;
|
|
Require(sceNpStrToInt64("-9223372036854775808", &value64, 10, nullptr) == 0 && value64 == INT64_MIN, "the 64-bit minimum parses");
|
|
Require(sceNpStrToInt64("99999999999999999999", &value64, 10, nullptr) == NpOutOfRange, "64-bit overflow is an error");
|
|
std::uint32_t unsigned32 = 0;
|
|
Require(sceNpStrToUInt32("4294967295", &unsigned32, 10, nullptr) == 0 && unsigned32 == UINT32_MAX, "the 32-bit maximum parses");
|
|
Require(sceNpStrToUInt32("4294967296", &unsigned32, 10, nullptr) == NpOutOfRange, "32-bit unsigned overflow is an error");
|
|
std::uint64_t unsigned64 = 0;
|
|
Require(sceNpStrToUInt64("0xFFFFFFFFFFFFFFFF", &unsigned64, 0, nullptr) == 0 && unsigned64 == UINT64_MAX, "the 64-bit maximum parses");
|
|
Require(sceNpStrToUInt64("18446744073709551616", &unsigned64, 10, nullptr) == NpOutOfRange, "64-bit unsigned overflow is an error");
|
|
}
|
|
|
|
void TestHexAndCopies() {
|
|
const std::uint8_t data[] = {0x0A, 0xFE};
|
|
char text[8] = {};
|
|
Require(sceNpStrBuildHex(text, 5, data, 2, false) == 0 && Equals(text, "0afe"), "bytes are written as lowercase hex");
|
|
Require(sceNpStrBuildHex(text, 5, data, 2, true) == 0 && Equals(text, "0AFE"), "bytes are written as uppercase hex");
|
|
Require(sceNpStrBuildHex(text, 4, data, 2, false) == NpBufferTooSmall, "hex output needs room for the terminator");
|
|
std::uint8_t bytes[4] = {};
|
|
Require(sceNpStrParseHex("0aFE", bytes, 2) == 0 && bytes[0] == 0x0A && bytes[1] == 0xFE, "hex is read in either case");
|
|
Require(sceNpStrParseHex("0AF", bytes, 2) == NpInvalidArgument, "odd hex is refused");
|
|
Require(sceNpStrParseHex("0AFE", bytes, 1) == NpBufferTooSmall, "hex parsing checks the output size");
|
|
Require(sceNpStrParseHex("0AG1", bytes, 2) == NpInvalidArgument, "non-hex characters are refused");
|
|
Require(sceNpStrnParseHex("DEADBEEF1234", 8, bytes, 4) == 0 && bytes[0] == 0xDE && bytes[3] == 0xEF, "a hex prefix is read");
|
|
|
|
char copy[8] = {};
|
|
Require(sceNpStrcpyToBuf(copy, 6, "Hello") == 0 && Equals(copy, "Hello"), "a string that fits is copied");
|
|
Require(sceNpStrcpyToBuf(copy, 5, "Hello") == NpBufferTooSmall, "a string without room for its terminator is refused");
|
|
Require(sceNpStrncpyToBuf(copy, 6, "Hello World", 5) == 0 && Equals(copy, "Hello"), "the length limit sizes the copy");
|
|
Require(sceNpStrncpyToBuf(copy, 5, "Hello World", 5) == NpBufferTooSmall, "the limited string still needs a terminator");
|
|
}
|
|
|
|
}
|
|
|
|
int main() {
|
|
TestAtomics();
|
|
TestSemaphoresAndFlags();
|
|
TestSharedEventFlags();
|
|
TestThreads();
|
|
TestHeaps();
|
|
TestBase64();
|
|
TestNumbers();
|
|
TestHexAndCopies();
|
|
TestCallouts();
|
|
TestIds();
|
|
TestLanguagesAndDebugIds();
|
|
TestJsonText();
|
|
TestTitleIds();
|
|
std::puts("guest np common runtime: PASS");
|
|
}
|