<feed xmlns='http://www.w3.org/2005/Atom'>
<title>gbos.git/usr/httpd.c, branch master</title>
<subtitle>gbos - a tiny Unix-flavored OS for the Game Boy Color (kernel, shell, coreutils in C, RAM filesystem)</subtitle>
<id>https://git.sl0p.foo/gbos.git/atom/usr/httpd.c?h=master</id>
<link rel='self' href='https://git.sl0p.foo/gbos.git/atom/usr/httpd.c?h=master'/>
<link rel='alternate' type='text/html' href='https://git.sl0p.foo/gbos.git/'/>
<updated>2026-09-22T19:54:03Z</updated>
<entry>
<title>net: one socket per client - four-tuple demux, spawning accept, bounded close</title>
<updated>2026-09-22T19:54:03Z</updated>
<author>
<name>user</name>
<email>user@clank</email>
</author>
<published>2026-09-22T19:54:03Z</published>
<link rel='alternate' type='text/html' href='https://git.sl0p.foo/gbos.git/commit/?id=dcd1f1320c102f9cc4cff7eee387ec538304f7d1'/>
<id>urn:sha1:dcd1f1320c102f9cc4cff7eee387ec538304f7d1</id>
<content type='text'>
A server could only ever hold ONE connection, which makes an ircd pointless:
listen() turned the listener into the connection, and net_find_tcp demuxed on
local port alone, so two clients on 6667 were indistinguishable.

- net_find_tcp now matches an established socket on the full four-tuple
  (local port + peer IP + peer port) and only falls back to a LISTENing
  socket when none matches.
- A SYN at a listener no longer consumes it: tcp_spawn_conn clones a new
  socket (local port + owning pid, so exit/kill still reclaims it) and the
  listener keeps listening. accept() returns that socket; closing it leaves
  the listener alone. A full table drops the SYN, and the client's retransmit
  is taken once a slot frees.
- tcp_peer_is_current preserves BC: net_find_tcp scans with the socket index
  there, and the compare needs BC for the rx buffer base. Without this the
  scan died at the first mismatching socket, so a second client's handshake
  ACK never reached its socket and it hung in SYNRCVD - tcpdump showed our
  SYN+ACK going out and the client's NICK retransmitted five times into
  silence.
- listen() only conflicts with another LISTENER on the port; connections
  share it by design now.
- MAX_SOCKS 4 -&gt; 6 (listener + 3 clients + 2 spare; 537 bytes of WRAM0 still
  free), and SK_ACC marks a spawned connection as not yet accepted.

tcp_close's settle is also time-boxed to ~250 ms of real time. It counted
PUMPS (8 x 8000), which is ~30s of wall clock when the peer never answers -
invisible with well-behaved peers, but a daemon refusing a client that stayed
connected froze itself, and every other client with it, for half a minute.

httpd keeps its listener open across requests now, so the next client's SYN
is accepted immediately instead of being dropped between close and re-listen:
three parallel clients go 0.1/1.1/2.1s -&gt; 0.4/1.0/0.1s.
</content>
</entry>
<entry>
<title>usr: httpd - the Game Boy serves its filesystem over HTTP</title>
<updated>2026-09-22T15:12:52Z</updated>
<author>
<name>user</name>
<email>user@clank</email>
</author>
<published>2026-09-22T15:12:52Z</published>
<link rel='alternate' type='text/html' href='https://git.sl0p.foo/gbos.git/commit/?id=30e67fe0f4e66e2161ca61f870f8947d50287995'/>
<id>urn:sha1:30e67fe0f4e66e2161ca61f870f8947d50287995</id>
<content type='text'>
    /# httpd &amp;
    host$ curl http://10.0.0.2/        -&gt; a listing of the cart RAM
    host$ curl http://10.0.0.2/readme  -&gt; the file, from battery-backed SRAM

GET only. "/" serves /index.html if present, else a generated listing; any
directory lists with every entry linked, so you can click around a 1997
handheld's disk from a browser. Content type from the extension. No
Content-Length: HTTP/1.0 + Connection: close delimits the body by closing,
which suits a stack with one in-flight segment.

All output goes through one 200-byte buffer, so each net_send is a full
segment instead of one tiny segment per string.

It drains the whole request, not just the request line. That is not
politeness: leaving the rest unread means those segments are never
acknowledged, so the client retransmits headers for a minute, never sends
its FIN, and its dead connection keeps arriving at port 80 long after we
re-listen. One curl with 1.4 KB of headers stalled every following request
for 30-120s.

It deliberately does NOT read the keyboard either. Backgrounded - the useful
way to run it - every key it polled would be a key stolen from the shell's
prompt. It is a daemon: kill(1) stops it, and the kernel reclaims its socket.

Verified with curl: index, byte-exact file bodies, nested paths, directory
listings, 404, 501, a 1.4 KB request spanning segments, browser user-agents,
three parallel clients (0.1/1.1/2.1s), the shell staying usable alongside it,
and six kill/restart cycles - more than there are sockets.
</content>
</entry>
</feed>
