209 Commits
Author SHA1 Message Date
Henrik Rydgård ef6029cc2e STR_VIEW 2026-09-02 18:15:17 +02:00
Henrik Rydgård 597e2bc6c0 Add a comment about the shared_ptr<atomic> 2026-09-02 17:53:12 +02:00
Henrik Rydgård a0873e5b0e Enable HTTPS on Linux, through naett's libcurl backend
naett has had a complete libcurl backend all along; we just never built it,
so Linux ran with HTTPS_NOT_AVAILABLE. That means no homebrew store over
HTTPS, and RetroAchievements talking to plain http://retroachievements.org.

libcurl is loaded with dlopen rather than linked, the same way we handle the
Vulkan loader, so it stays a soft dependency: we need the curl headers at
build time, but a build made here still starts on a machine without libcurl
installed - it just reports HTTPS as unavailable, exactly like today. Distro
packagers get the behavior they'd expect either way, and certificate
validation comes free from the system CA store.

New net::HTTPSAvailable() answers "did that work", and SDLMain folds it into
SYSPROP_SUPPORTS_HTTPS, which everything downstream already degrades on.

Four fixes to the backend itself, all noted in ext/naett/README-ppsspp.md:

- panic() called exit(1) on a pipe or curl_multi_perform failure. Taking the
  emulator down because a download failed isn't acceptable - the backend now
  disables itself and requests complete with naettGenericError.
- CURLINFO_RESPONSE_CODE writes a long into res->code, which is an int. Eight
  bytes into four, getting away with it only because the next field absorbs
  the zeroes.
- curl_easy_setopt is varargs and wants a long for these options; int literals
  and int variables are UB on LP64.
- naettPlatformCloseResponse called through a null function pointer when
  libcurl was missing. Found by testing that path, which segfaulted.

CI needs libcurl4-openssl-dev (curl-dev on Alpine) or it would quietly keep
building without HTTPS.
2026-09-02 17:42:04 +02:00
Henrik Rydgård 2096df52f5 Vendor naett in-tree, de-amalgamated
naett has been a submodule pinned at v0.3.3; upstream has had no commits since
April 2024, and we want to carry local changes (next up: a libcurl-backed HTTPS
path for Linux). It's ~1500 lines of MIT C, smaller than several things we
already vendor, so bring it in-tree and drop the submodule.

Also drop the generated single-file amalgam (naett.c) that every build system
was compiling, and build src/*.c directly instead - otherwise the file you edit
isn't the file that gets compiled, which is a trap for anyone patching this.
example/ and testrig/ (a whole Android Studio project) are gone with it.

Two changes were needed to make the sources build on their own, both noted in
ext/naett/README-ppsspp.md along with the upstream commit:

- naett_internal.h now includes naett.h, which the amalgam pulled in first.
- naett_linux.c now includes stdio.h/stdlib.h. It calls exit/calloc/realloc/
  free/fprintf without ever including either, and only got away with it because
  naett_core.c sat above it in the concatenation.

No functional change - Linux still has HTTPS_NOT_AVAILABLE set, so it doesn't
build naett at all yet.

Rename naett to naett-lib
2026-09-02 17:35:14 +02:00
Henrik Rydgård 57d22d01d1 Net: reap finished connection threads, and don't echo unsendable close codes
NewThreadExecutor::Run pushed a std::thread per connection and only ever joined them
in the destructor, so a server leaked a joinable thread object for every connection
it had ever served. Measured with 60 connect/disconnect cycles against the debugger:
handle count +60 before, +1 after. Each worker now flags itself done as its last act,
and Run() reaps the finished ones first. Only the accept thread calls Run(), so the
flag is the only thing that needs to be atomic.

Note this doesn't bound how many connections can be in flight at once - it just stops
the finished ones from piling up.

Separately, a received close code was echoed straight back. RFC 6455 7.4.1 reserves
1004, 1005, 1006 and 1015 for describing how a connection ended locally, so they must
never go on the wire - echoing one back would be our protocol violation rather than
the client's. Send PROTOCOL_ERROR when they give us something we can't repeat.
2026-08-31 11:45:53 +02:00
Henrik Rydgård fb38b3eb66 Net: three smaller fixes from reviewing the server code
RequestHeader::GetParamValue indexed parts[1] without checking the size. A query
parameter with no '=' at all ("?foo") makes SplitString return a single element, so
both the DEBUG_LOG and the assignment read off the end of the vector. Nothing calls
GetParamValue today, so this is latent rather than live, but it's driven straight
off the request line.

The 64-bit frame length was assembled with header[n] << 24 on uint8_t values, which
promote to int - a byte >= 0x80 in the top position shifts into the sign bit and then
sign-extends when widened to uint64_t. The resulting size was always rejected, just
by the wrong check and via signed overflow to get there. Cast first.

OutputSink::Block() had the same shape as the InputSink one this branch already
fixed: a broken socket is reported ready immediately and forever, so waiting on it
is a spin. Bail if the sink already knows it's broken.
2026-08-31 01:24:56 +02:00
Henrik RydgårdandClaude Opus 5 abd91da5eb WebSocket: teach the input sink about EOF, and stop losing why a read failed
Follow-up to the previous commit, from Nemoumbra's questions - which found a worse
spin than the one that fix addressed.

InputSink couldn't tell "nothing right now" from "peer is gone": Fill() treats
recv() == 0 as no data and only sets hasError_ on a real error. Block() then waits
with WaitUntilReady(), which reports a closed socket as ready immediately and
forever, so TakeExact() looped on it without ever returning. A client that
disconnects with half a frame buffered - easy to do while blasting messages - put
the server in an infinite loop inside TakeExact, never even returning to Process().
Measured 7.95 CPU-seconds over 8 seconds; 0.08 after.

So: track EOF explicitly (sticky atEnd_, exposed as AtEnd()), and have Block() give
up when nothing more can arrive.

That information was being thrown away in three more places:

* Process() only tried to fill when the sink was already empty, so a disconnect went
  unnoticed for as long as there were leftovers - and if those leftovers were a
  partial frame, the read above never completed. Always fill, and close once the
  peer is gone and we've consumed what it sent.
* ReadPending() uses TakeAtMost(), which returns 0 both for "nothing right now" and
  "nothing ever again", and then reported success having consumed nothing. Ask the
  sink which it was.
* Both TakeExact() call sites answered a failed read with POLICY_VIOLATION, blaming
  the client for a protocol error when it had simply disconnected. Check the sink
  and report ABNORMAL when that's what happened.

Also stop queueing data once our own close frame is queued. RFC 6455 5.5.1 forbids
data frames after a close, and beyond the protocol, anything appended afterwards
keeps the buffers non-empty and starves the "everything is flushed" check that ends
the connection. Observed the server pumping 167MB of log broadcasts after being
asked to close.

The repeated close-and-discard is now one helper.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DCPmm7FoQUoqrbMdhfqhQ2
2026-08-31 00:57:17 +02:00
Henrik RydgårdandClaude Opus 5 03b313e22b WebSocket: don't spin at 100% CPU when the output can't drain
Reported by Nemoumbra: the debugger server could get stuck in a tight select()
loop after a lot of traffic, burning a core.

Once OutputSink hits a real send() error it latches hasError_, after which Flush()
returns immediately without consuming anything, so out_->Empty() is false forever.
Process() waited for that to empty before finishing the close, kept the fd in the
write set, and select() reports an errored socket as ready every time - so it
returned true on every lap without ever making progress, and WebSocketDebuggerLoop
span. This needs sentClose_ to be set for it to be unrecoverable, since otherwise
the read side notices the disconnect and closes; a client that sends CLOSE (or
trips a protocol error) while output is backed up gets exactly that. Reproduced
with a client that queues ~120MB of responses, sends CLOSE, then resets the
connection without reading: 6.02 CPU-seconds over 6 seconds before, 0.06 after.

Treat an output error as fatal to the connection instead.

Also, select() returning -1 always returned true, so any error that doesn't fix
itself (a bad fd rather than EINTR) was a second busy-loop with no wait at all.
EINTR retries, everything else closes.

Finally, SendFlush() erased the drained bytes off the front of outBuf_ every lap.
With a backlog that's a memmove of the whole buffer per lap, i.e. quadratic in the
backlog, which burns CPU on its own while draining a slow client. Track a consumed
offset and only compact once the dead prefix is worth reclaiming.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DCPmm7FoQUoqrbMdhfqhQ2
2026-08-31 00:42:43 +02:00
Henrik Rydgård 9b9083d3e5 Fix for headless port problem by Claude 2026-08-16 23:09:12 +02:00
Henrik Rydgård 4d8a5d74e7 Merge pull request #22086 from hrydgard/read-u32-more
Some fixes to Claude's paranoia, more memory access function cleanup
2026-08-11 23:52:17 +02:00
Henrik Rydgård 04ef18a9d9 Send the crash dump output to the debug output stream in headless. This makes it so that crash-failed test output will contain crash details.
Also fix some warnings and stuff.
2026-08-11 22:36:47 +02:00
Henrik Rydgård cd052ea640 Adjust the level of Claude-based paranoia here and there 2026-08-11 20:08:01 +02:00
Nemoumbra 2c7d4c5613 Small HTTPClient cleanup 2026-08-11 04:12:46 +03:00
Nemoumbra c797ac200c Fixed IPv6 formatting 2026-08-11 04:02:16 +03:00
Nemoumbra c4fab7d1dc Removed unnecessary waiting 2026-08-11 03:50:48 +03:00
Henrik Rydgård feefbfcba0 Apply Nemo's feedback 2026-08-10 11:29:02 +02:00
Henrik RydgårdandClaude Sonnet 5 df751d5571 URL/VFS: fix latent pointer/index UB on empty or tiny input
UriDecode() formed SRC_END - 2 unconditionally, a pointer before the
start of the buffer (UB) for a 0- or 1-byte input. IsLocalAbsolutePath()
indexed path[0]/path[1] on a std::string_view with no bounds check,
UB for an empty path (path[0]) or a 1-byte path on Windows (path[1]).
Neither was known to crash in practice, but both are real UB flagged
by hardened/UBSan builds and easy to trigger (e.g. an empty query
string, or listing the VFS root).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01L4QAoxV2KY7ek4PcZw3WvY
2026-08-10 11:26:39 +02:00
Henrik RydgårdandClaude Sonnet 5 37cea65e0e WebsocketServer: cap message size, avoid UB on empty-vector payloads
ReadFrame() accepted a 64-bit client-supplied payload length with only
a top-bit check, and ReadPending() immediately resized a buffer by it
before any data had arrived - a single frame claiming a huge length
(reachable via the WebSocket debugger endpoint) could trigger a
multi-exabyte allocation attempt. Now rejected up front (both the
single frame and the fragmented-message total) against a 64MB cap.

Also replaced &payload[0]/&vector[0] with .data() in the send/receive
paths - operator[] on a possibly-empty vector (e.g. an empty PING) is
UB even when the result is never dereferenced.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01L4QAoxV2KY7ek4PcZw3WvY
2026-08-10 11:26:24 +02:00
Henrik RydgårdandClaude Sonnet 5 99ab8b81ca HTTPHeaders: fix OOB alloc from a request line with no trailing space
ParseHttpHeader() used strchr(buffer, ' ') unconditionally as endptr,
even though the parser explicitly supports HTTP/0.9-style requests
with no trailing space/version (type = SIMPLE). A request line like
"GET /" with no space made strchr return null, and nullptr - buffer
truncated to a garbage length driving new[]/memcpy. Falls back to the
end of the line when no space is found, and clamps param_length to
avoid a similar issue when '?' appears after the (missing) space.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01L4QAoxV2KY7ek4PcZw3WvY
2026-08-10 11:26:24 +02:00
Henrik Rydgård a7b96ce7e0 Add the error flag to OutputSink as well. 2026-08-10 10:08:13 +02:00
Henrik Rydgård 0a1821f5d8 InputSink: Add an error flag. OutputSink: Unify the error handling between unix and Windows 2026-08-10 10:04:21 +02:00
Henrik Rydgård 933751e2a9 Windows: Avoid POSIX error codes leaking into our WSA socket error codes 2026-08-10 10:04:21 +02:00
Henrik Rydgård 82a3ef31b1 InputSink: Inline AccountFill into Fill 2026-08-10 10:04:21 +02:00
Henrik Rydgård abb57c620f Adjust some log levels 2026-08-09 22:10:54 +02:00
Henrik Rydgård d31b475bb2 Minor fixes in Common/Net from Nemo's writeup 2026-08-09 19:03:28 +02:00
Henrik Rydgård 3504be26b0 More DNS fixes by copilot, additionally fixed up by claude 2026-08-07 08:49:29 +02:00
Henrik Rydgård 798ba7f79d Fix some bugs in Resolve.cpp 2026-08-07 08:49:29 +02:00
Henrik Rydgård 96ae36b7dd Fix some comments, remove redundant fields etc 2026-06-13 17:44:26 +02:00
Henrik Rydgård 2604e169e1 Make progress optional in some function to avoid flouting convention 2026-06-13 17:18:35 +02:00
Henrik Rydgård 00126e4c6f Correct some logging categories 2026-06-13 17:12:16 +02:00
Henrik Rydgård 433defee9a Address some more freedback in HTTPRequest 2026-06-13 15:21:05 +02:00
Henrik Rydgård 95ce95e489 Add a new "Net" log category, do assorted cleanup 2026-06-13 14:43:59 +02:00
Henrik Rydgård e8a1638c8d Cleanup 2026-06-09 23:49:54 +02:00
Nemoumbra 1aac66a1e4 More helpful error logs for the input sink 2026-05-12 03:31:49 +03:00
Henrik Rydgård d4bd7c604c Move a utility function 2026-05-05 15:36:30 +02:00
Henrik Rydgård e0a5a3e2bf iOS: Add basic deep link support.
Fixes #21577
2026-04-28 22:17:24 +02:00
Henrik Rydgård 8c8dd0f0d0 Add utility function to snoop the file cache 2026-03-09 10:40:51 +01:00
Henrik Rydgård 71d884379d Fix a potential race condition 2026-03-09 00:51:39 +01:00
Henrik Rydgård 53f1f57bbc Crashfix in Resolve.cpp, fix some bad return values 2026-03-09 00:21:56 +01:00
Henrik Rydgård 1a95e9e4e9 Fetch the public adhoc server list from metadata.ppsspp.org/adhoc-servers.json 2026-03-06 13:25:07 +01:00
Henrik Rydgård cc49e5079d Refactor a bit, preparing for async download 2026-03-05 14:30:32 +01:00
Henrik Rydgård 0d8358827b Move the pre-loaded adhoc server address list logic to sceNetAdhoc.cpp
Yes, that file is growing too big...
2026-03-05 10:05:08 +01:00
Henrik Rydgård 3a7911407b Assorted minor cleanups 2026-01-19 15:47:40 +01:00
MMaZaHaKa aa350b3082 upd proAdhoc 2026-01-16 06:17:03 +02:00
Henrik Rydgård d2c9104eff Polish and fixes to the upload form. 2025-10-27 12:20:24 +01:00
Henrik Rydgård 9abd8c21e1 Finish implementing the multipart protocol, now multi file uploads work without corruption. 2025-10-27 12:20:24 +01:00
Henrik Rydgård dc682fb2c8 Basic upload via web browser support, only single file almost works 2025-10-27 12:20:24 +01:00
Henrik Rydgård c686d48268 Add prototype Upload screen and a couple new icons. Some refactoring. 2025-10-27 12:20:20 +01:00
Henrik Rydgård 28b9bc2fb3 Make more dev screens usable in portrait by converting them to TabbedDialogScreen 2025-10-23 15:33:09 +02:00
Henrik Rydgård d43b1d4618 String function cleanup 2025-08-31 14:45:41 +02:00