Merge pull request #3530 from MarcZX100/codex/libkernel-inet-text-rules-3126

fix(libkernel): follow FreeBSD inet text rules
This commit is contained in:
Adrià 2026-10-11 23:32:18 +02:00 committed by GitHub
commit b2477d9804
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 246 additions and 30 deletions

View File

@ -1,47 +1,217 @@
#ifdef _WIN32
#define WIN32_LEAN_AND_MEAN
#include <winsock2.h>
#include <ws2tcpip.h>
#else
#include <arpa/inet.h>
#endif
#include "prx/libc/include/general/VabiMacros.hpp"
#include <cstddef>
#include <cstdint>
#include <cstring>
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<unsigned>(*cursor - '0');
}
if (total > 255) return false;
value = static_cast<std::uint8_t>(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<unsigned>(digit);
}
value = static_cast<std::uint16_t>(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<std::uint16_t>(quad[0] << 8 | quad[1]);
groups[count++] = static_cast<std::uint16_t>(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<std::uint8_t>(words[index] >> 8);
bytes[index * 2 + 1] = static_cast<std::uint8_t>(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<char>('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<std::uint16_t>(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<void*>(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<const std::uint8_t*>(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;
}
}

View File

@ -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<std::uint32_t>(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<std::uint32_t>(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);
}

View File

@ -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