diff --git a/core/libs/prx/libkernel/Socket/src/AddressConversion.cpp b/core/libs/prx/libkernel/Socket/src/AddressConversion.cpp index b10fb9633..1ed1a2e20 100644 --- a/core/libs/prx/libkernel/Socket/src/AddressConversion.cpp +++ b/core/libs/prx/libkernel/Socket/src/AddressConversion.cpp @@ -1,47 +1,217 @@ -#ifdef _WIN32 -#define WIN32_LEAN_AND_MEAN -#include -#include -#else -#include -#endif #include "prx/libc/include/general/VabiMacros.hpp" +#include #include #include extern "C" int* APS5_VABI __error_nid_postfix(); +namespace { +constexpr int kGuestInet = 2; +constexpr int kGuestInet6 = 28; +constexpr std::size_t kMaxText = 46; + +bool ParseDecimalPart(const char* begin, const char* end, std::uint8_t& value) { + const std::ptrdiff_t length = end - begin; + if (length < 1 || length > 3) return false; + if (length > 1 && *begin == '0') return false; + unsigned total = 0; + for (const char* cursor = begin; cursor != end; ++cursor) { + if (*cursor < '0' || *cursor > '9') return false; + total = total * 10 + static_cast(*cursor - '0'); + } + if (total > 255) return false; + value = static_cast(total); + return true; +} + +bool ParseQuad(const char* begin, const char* end, std::uint8_t (&bytes)[4]) { + const char* partBegin = begin; + int parts = 0; + for (const char* cursor = begin;; ++cursor) { + if (cursor != end && *cursor != '.') continue; + if (parts == 4 || !ParseDecimalPart(partBegin, cursor, bytes[parts])) return false; + ++parts; + if (cursor == end) break; + partBegin = cursor + 1; + } + return parts == 4; +} + +int HexValue(char symbol) { + if (symbol >= '0' && symbol <= '9') return symbol - '0'; + if (symbol >= 'a' && symbol <= 'f') return symbol - 'a' + 10; + if (symbol >= 'A' && symbol <= 'F') return symbol - 'A' + 10; + return -1; +} + +bool ParseHexGroup(const char* begin, const char* end, std::uint16_t& value) { + const std::ptrdiff_t length = end - begin; + if (length < 1 || length > 4) return false; + unsigned total = 0; + for (const char* cursor = begin; cursor != end; ++cursor) { + const int digit = HexValue(*cursor); + if (digit < 0) return false; + total = total * 16 + static_cast(digit); + } + value = static_cast(total); + return true; +} + +bool ParseGroupList(const char* begin, const char* end, bool mayEndWithQuad, + std::uint16_t (&groups)[8], int& count) { + count = 0; + if (begin == end) return true; + const char* groupBegin = begin; + for (const char* cursor = begin;; ++cursor) { + if (cursor != end && *cursor != ':') continue; + const bool last = cursor == end; + bool hasDot = false; + for (const char* probe = groupBegin; probe != cursor; ++probe) hasDot = hasDot || *probe == '.'; + if (hasDot) { + std::uint8_t quad[4]; + if (!last || !mayEndWithQuad || count > 6 || !ParseQuad(groupBegin, cursor, quad)) return false; + groups[count++] = static_cast(quad[0] << 8 | quad[1]); + groups[count++] = static_cast(quad[2] << 8 | quad[3]); + return true; + } + if (count == 8 || !ParseHexGroup(groupBegin, cursor, groups[count])) return false; + ++count; + if (last) return true; + groupBegin = cursor + 1; + } +} + +bool TextToV6(const char* text, std::uint8_t (&bytes)[16]) { + const char* const end = text + std::strlen(text); + const char* gap = std::strstr(text, "::"); + std::uint16_t head[8]; + std::uint16_t tail[8]; + int headCount = 0; + int tailCount = 0; + if (!gap) { + if (!ParseGroupList(text, end, true, head, headCount) || headCount != 8) return false; + } else { + if (std::strstr(gap + 1, "::")) return false; + if (!ParseGroupList(text, gap, false, head, headCount)) return false; + if (!ParseGroupList(gap + 2, end, true, tail, tailCount)) return false; + if (headCount + tailCount > 7) return false; + } + std::uint16_t words[8]{}; + for (int index = 0; index < headCount; ++index) words[index] = head[index]; + for (int index = 0; index < tailCount; ++index) words[8 - tailCount + index] = tail[index]; + for (int index = 0; index < 8; ++index) { + bytes[index * 2] = static_cast(words[index] >> 8); + bytes[index * 2 + 1] = static_cast(words[index]); + } + return true; +} + +std::size_t AppendDecimal(char* out, unsigned value) { + char digits[3]; + std::size_t count = 0; + do { + digits[count++] = static_cast('0' + value % 10); + value /= 10; + } while (value); + for (std::size_t index = 0; index < count; ++index) out[index] = digits[count - 1 - index]; + return count; +} + +std::size_t FormatQuad(const std::uint8_t* bytes, char* out) { + std::size_t length = 0; + for (int index = 0; index < 4; ++index) { + if (index) out[length++] = '.'; + length += AppendDecimal(out + length, bytes[index]); + } + return length; +} + +std::size_t AppendHex(char* out, unsigned value) { + static const char symbols[] = "0123456789abcdef"; + char digits[4]; + std::size_t count = 0; + do { + digits[count++] = symbols[value & 15]; + value >>= 4; + } while (value); + for (std::size_t index = 0; index < count; ++index) out[index] = digits[count - 1 - index]; + return count; +} + +void FindLongestZeroRun(const std::uint16_t (&words)[8], int& start, int& length) { + start = -1; + length = 0; + for (int from = 0; from < 8; ++from) { + if (words[from] != 0) continue; + int run = 0; + while (from + run < 8 && words[from + run] == 0) ++run; + if (run > length) { + start = from; + length = run; + } + from += run; + } + if (length < 2) { + start = -1; + length = 0; + } +} + +std::size_t V6ToText(const std::uint8_t* bytes, char* out) { + std::uint16_t words[8]; + for (int index = 0; index < 8; ++index) + words[index] = static_cast(bytes[index * 2] << 8 | bytes[index * 2 + 1]); + int runStart; + int runLength; + FindLongestZeroRun(words, runStart, runLength); + std::size_t length = 0; + if (runStart == 0 && (runLength == 6 || (runLength == 5 && words[5] == 0xffff))) { + const char* prefix = runLength == 6 ? "::" : "::ffff:"; + length = std::strlen(prefix); + std::memcpy(out, prefix, length); + return length + FormatQuad(bytes + 12, out + length); + } + for (int index = 0; index < 8;) { + if (index == runStart) { + out[length++] = ':'; + if (index + runLength == 8) out[length++] = ':'; + index += runLength; + continue; + } + if (index) out[length++] = ':'; + length += AppendHex(out + length, words[index]); + ++index; + } + return length; +} +} + extern "C" { int APS5_VABI __inet_pton_nid_postfix(int family, const char* text, void* destination) { - if (family != 2 && family != 28) { *__error_nid_postfix() = 47; return -1; } + if (family != kGuestInet && family != kGuestInet6) { *__error_nid_postfix() = 47; return -1; } if (!text || !destination) { *__error_nid_postfix() = 14; return -1; } - unsigned char address[16]{}; - const int nativeFamily = family == 2 ? AF_INET : AF_INET6; -#ifdef _WIN32 - const int result = InetPtonA(nativeFamily, text, address); -#else - const int result = ::inet_pton(nativeFamily, text, address); -#endif - if (result == 1) std::memcpy(destination, address, family == 2 ? 4 : 16); - else if (result == -1) *__error_nid_postfix() = 22; - return result; + if (family == kGuestInet) { + std::uint8_t quad[4]; + if (!ParseQuad(text, text + std::strlen(text), quad)) return 0; + std::memcpy(destination, quad, sizeof(quad)); + return 1; + } + std::uint8_t address[16]; + if (!TextToV6(text, address)) return 0; + std::memcpy(destination, address, sizeof(address)); + return 1; } const char* APS5_VABI __inet_ntop_nid_postfix(int family, const void* source, char* destination, std::uint32_t capacity) { - if (family != 2 && family != 28) { *__error_nid_postfix() = 47; return nullptr; } + if (family != kGuestInet && family != kGuestInet6) { *__error_nid_postfix() = 47; return nullptr; } if (!source || !destination) { *__error_nid_postfix() = 14; return nullptr; } - char text[INET6_ADDRSTRLEN]{}; - const int nativeFamily = family == 2 ? AF_INET : AF_INET6; -#ifdef _WIN32 - const auto* result = InetNtopA(nativeFamily, const_cast(source), text, sizeof(text)); -#else - const auto* result = ::inet_ntop(nativeFamily, source, text, sizeof(text)); -#endif - if (!result) { *__error_nid_postfix() = 22; return nullptr; } - const auto size = std::strlen(text) + 1; - if (size > capacity) { *__error_nid_postfix() = 28; return nullptr; } - std::memcpy(destination, text, size); + char text[kMaxText + 1]; + const auto* bytes = static_cast(source); + const std::size_t length = family == kGuestInet ? FormatQuad(bytes, text) : V6ToText(bytes, text); + text[length] = '\0'; + if (length + 1 > capacity) { *__error_nid_postfix() = 28; return nullptr; } + std::memcpy(destination, text, length + 1); return destination; } } diff --git a/core/libs/tests/GuestAddressConversion.cpp b/core/libs/tests/GuestAddressConversion.cpp index 458ec1b13..675da4ed6 100644 --- a/core/libs/tests/GuestAddressConversion.cpp +++ b/core/libs/tests/GuestAddressConversion.cpp @@ -31,4 +31,49 @@ int main() { Require(__inet_pton_nid_postfix(2, "256.1.2.3", address.data()) == 0); Require(__inet_pton_nid_postfix(28, "::ffff:192.0.2.17", address.data()) == 1); Require(address[10] == 255 && address[11] == 255 && address[12] == 192 && address[15] == 17); + + for (const char* rejected : {"01.2.3.4", "1.2.3", "1.2.3.4.5", "1.2.3.4.", " 1.2.3.4", "0x1.2.3.4", "1..3.4", "1.2.3.-4"}) { + address.fill(0xA5); + Require(__inet_pton_nid_postfix(2, rejected, address.data()) == 0); + Require(address[0] == 0xA5); + } + for (const char* rejected : {"::ffff:01.2.3.4", "::ffff:1.2.3.04", "1:2:3:4:5:6:7:8::", "::1:2:3:4:5:6:7:8", "1::2::3", ":::", ":1::", "1::2:", ":1:2:3:4:5:6:7", + "1:2:3:4:5:6:7", "1:2:3:4:5:6:7:8:9", "12345::", "fe80::1%eth0", "1.2.3.4::", "::1.2.3.4:1", "::g", "1:2:3:4:5:6:1.2.3.4:7", + "1:2:3:4:5:6:7:1.2.3.4", ""}) { + address.fill(0xA5); + Require(__inet_pton_nid_postfix(28, rejected, address.data()) == 0); + Require(address[0] == 0xA5 && address[15] == 0xA5); + } + Require(__inet_pton_nid_postfix(28, "1:2:3:4:5:6:7::", address.data()) == 1); + Require(address[13] == 7 && address[14] == 0 && address[1] == 1); + Require(__inet_pton_nid_postfix(28, "ABCD:ef01::1", address.data()) == 1); + Require(address[0] == 0xAB && address[1] == 0xCD && address[2] == 0xEF && address[3] == 0x01 && address[15] == 1); + + struct Text { const char* bytesAsText; const char* expected; }; + const Text samples[] = { + {"::ffff:0.0.0.1", "::ffff:0.0.0.1"}, {"::ffff:0:1:0", "::ffff:0:1:0"}, {"::1", "::1"}, {"::", "::"}, {"1::", "1::"}, + {"::2", "::2"}, {"::1.2.3.4", "::1.2.3.4"}, {"1:0:0:2:0:0:0:3", "1:0:0:2::3"}, {"1:0:2:0:3:0:4:0", "1:0:2:0:3:0:4:0"}, + {"0:0:1:0:0:2:0:0", "::1:0:0:2:0:0"}, {"::ffff:1.2.3.4", "::ffff:1.2.3.4"}, {"2001:DB8::17", "2001:db8::17"}}; + for (const Text& sample : samples) { + Require(__inet_pton_nid_postfix(28, sample.bytesAsText, address.data()) == 1); + char output[64]{}; + Require(__inet_ntop_nid_postfix(28, address.data(), output, sizeof(output)) == output); + Require(std::strcmp(output, sample.expected) == 0); + const auto length = std::strlen(sample.expected); + std::memset(output, 'x', sizeof(output)); + Require(__inet_ntop_nid_postfix(28, address.data(), output, static_cast(length + 1)) == output); + Require(std::strcmp(output, sample.expected) == 0); + std::memset(output, 'x', sizeof(output)); + *__error_nid_postfix() = 0; + Require(__inet_ntop_nid_postfix(28, address.data(), output, static_cast(length)) == nullptr); + Require(*__error_nid_postfix() == 28 && output[0] == 'x'); + } + address.fill(0xFF); + char widest[64]{}; + Require(__inet_ntop_nid_postfix(28, address.data(), widest, sizeof(widest)) == widest); + Require(std::strcmp(widest, "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff") == 0); + const unsigned char fourth[4] = {255, 0, 7, 100}; + Require(__inet_ntop_nid_postfix(2, fourth, widest, 12) == widest && std::strcmp(widest, "255.0.7.100") == 0); + Require(__inet_ntop_nid_postfix(2, fourth, widest, 11) == nullptr && *__error_nid_postfix() == 28); + Require(__inet_ntop_nid_postfix(10, fourth, widest, sizeof(widest)) == nullptr && *__error_nid_postfix() == 47); } diff --git a/docs/dev/TechnicalDebt.md b/docs/dev/TechnicalDebt.md index 7eee00de2..615ace22e 100644 --- a/docs/dev/TechnicalDebt.md +++ b/docs/dev/TechnicalDebt.md @@ -341,6 +341,7 @@ Throughout the project, every function at every stage either **does exactly what - [sceVideoOutAddPreVblankStartEvent](../../core/libs/prx/libSceVideoOut/src/Event.cpp) (libSceVideoOut) - how long before the vblank the console signals pre-vblank start is unknown. The event is triggered on each emulated vblank tick immediately before the vblank event, with its own count as the event data - [sched_get_priority_max, sched_get_priority_min](../../core/libs/prx/libkernel/System/src/Process.cpp) (libkernel) - `SCHED_OTHER` (2) reports the same 256 to 767 range as `SCHED_FIFO` and `SCHED_RR`; the console's answer is unverified. FreeBSD accepts `SCHED_OTHER` with its own range, and shadPS4 rejects it with `EINVAL`. `pthread_setschedparam` ignores the policy, so a priority taken from this range applies +- [inet_pton, inet_ntop](../../core/libs/prx/libkernel/Socket/src/AddressConversion.cpp) (libkernel) - the text forms are assumed to be those of FreeBSD 11.4 and are unverified on a console: `inet_pton` accepts only four decimal parts without leading zeros for `AF_INET` and the RFC 4291 forms for `AF_INET6` (one `::` standing for at least one group, a dotted-quad tail only as the last 32 bits, no zone id), and `inet_ntop` prints lowercase hex with the first longest run of two or more zero groups compressed, and a dotted tail only for `::a.b.c.d` and `::ffff:a.b.c.d`. They no longer depend on the host resolver library - [gethostbyname](../../core/libs/prx/libScePosixForWebKit/Resolver.cpp) (libScePosixForWebKit) - numeric IPv4 forms, trailing whitespace and 64-bit accumulation follow FreeBSD 11.4 [inet_aton](https://github.com/freebsd/freebsd-src/blob/releng/11.4/lib/libc/inet/inet_addr.c); clearing the resolver error on numeric success follows [fakeaddr](https://github.com/freebsd/freebsd-src/blob/releng/11.4/lib/libc/net/gethostnamadr.c). Exact PS5 behavior is unverified; hostname lookup still uses the host resolver - [sceAppContentGetAddcontDownloadProgress](../../core/libs/prx/libSceAppContent/Export.cpp) (libSceAppContent) - imported by PPSA08755, which fails to load without it; added without an implementation because its behavior is unverified. [SharpProspero](https://github.com/SvenGDK/SharpProspero) lists `(uint32_t serviceLabel, entitlementLabel, SceAppContentAddcontDownloadProgress*)`, but no PS5 reference gives the progress layout or the error codes