aboutsummaryrefslogtreecommitdiffstats
path: root/docs/internals.md
diff options
context:
space:
mode:
authoruser <user@clank>2026-09-22 17:12:52 +0200
committeruser <user@clank>2026-09-22 17:12:52 +0200
commit30e67fe0f4e66e2161ca61f870f8947d50287995 (patch)
treea04189f34687a4d600903100feca18aedc5d1604 /docs/internals.md
parentnet: passive open (listen/accept) + own sockets + close promptly (diff)
downloadgbos-30e67fe0f4e66e2161ca61f870f8947d50287995.tar.gz
gbos-30e67fe0f4e66e2161ca61f870f8947d50287995.tar.xz
gbos-30e67fe0f4e66e2161ca61f870f8947d50287995.zip
usr: httpd - the Game Boy serves its filesystem over HTTP
/# httpd & host$ curl http://10.0.0.2/ -> a listing of the cart RAM host$ curl http://10.0.0.2/readme -> 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.
Diffstat (limited to 'docs/internals.md')
-rw-r--r--docs/internals.md40
1 files changed, 37 insertions, 3 deletions
diff --git a/docs/internals.md b/docs/internals.md
index 8ee7816..62167f4 100644
--- a/docs/internals.md
+++ b/docs/internals.md
@@ -149,6 +149,17 @@ Userland networking is plain C on top of that: `dhcp` (DISCOVER→ACK, then
pump stdin and print the reply), `irc` (a full client: channels, /join /msg
/me, nick colors, flood-safe rendering) and `chat`.
+**Listening** (`net_op_listen` / `net_op_accept`): sockets demux on the local
+port *alone*, so a listener and its connection are the same socket - the SYN
+fills in the peer and the socket becomes the connection. That means one
+connection at a time, re-armed by listening again after close; a second client
+knocking is dropped in silence and its SYN retransmit is taken when we are
+free again (~1s later), which is why `httpd` serves three parallel clients in
+0.1/1.1/2.1s. `accept` never blocks: one RX pump per call, like `recv_nb`.
+Since SYNs reach the same socket as the live connection, `tcp_peer_is_current`
+is what tells a peer's retransmitted SYN apart from a newcomer's - without it
+a second client silently *hijacks* the connection mid-request.
+
**Receive-path invariants** (all three were bugs once, and each one is
invisible until a peer sends more than a toy reply):
@@ -165,6 +176,22 @@ invisible until a peer sends more than a toy reply):
that sits past a gap marks the socket `TCP_DONE`, and `recv` then reports
EOF at the hole — truncating the transfer just before the last segments.
+`tcp_close` FINs and then pumps for the peer's FIN/ACK, but **stops as soon as
+it arrives** instead of burning its whole budget: the socket otherwise sits in
+`TCP_FINWAIT` for about a second, and a server that closes and re-listens drops
+the next client's SYN in that window, so every request after the first paid a
+SYN retransmit (1.1s → 0.1s per request).
+
+A server must also **drain the request it was sent**. Reading only the request
+line leaves the rest unacknowledged: the client retransmits headers for a
+minute, never sends its FIN, and its dead connection keeps arriving at the port
+long after the server re-listens. One 1.4 KB request stalled every following
+connection for 30-120s.
+
+Sockets are owned by the pid that created them (`SK_OWNER`) and released by
+`SockReleaseOwner` on exit *and* on kill — the table has only 4 entries, and a
+killed `irc`/`nc`/`httpd` used to hold its slot until reboot.
+
Bytes that arrive **outside** SLIP frames land in a console ring instead —
that's how the host can type into the shell remotely (`tools/gbtype`).
`sys_pollcon` pumps the link itself before draining that ring, so a program
@@ -185,13 +212,20 @@ A small **block-based, persistent filesystem** on battery-backed cart RAM
```
block 0 superblock (magic + version)
block 1 bitmaps (free blocks + free inodes)
-blocks 2-3 inode table (32 inodes x 16 B: type, size, 8 direct block ptrs)
+blocks 2-3 inode table (32 inodes x 16 B: type, nlink, size, 12 block ptrs)
blocks 4.. data blocks
```
- **Inodes + nested directories.** A directory is a file of 16-byte entries
- (inode# + name), each carrying `.`/`..`. Files are up to **2 KiB** (8 direct
- blocks); **32** inodes, 16 entries per directory.
+ (inode# + name), each carrying `.`/`..`. Files are up to **3 KiB**
+ (`NDIRECT` = 12 direct blocks, the whole tail of the inode); **32** inodes,
+ 16 entries per directory. `sys_getb`/`sys_putb` **bounds-check the block
+ index** - they did not, so a file that grew past the pointers simply kept
+ walking into the *next inode*, reading its fields as block numbers. A file
+ could round-trip fine (reads and writes aliased identically) and only show
+ the damage once something else touched the neighbouring inode.
+- **One-block data cache.** Two files open at once thrash it: `cp` of a 3 KiB
+ file alternates read/write blocks per byte and takes ~25s. Correct, slow.
- **Paths + per-process cwd.** `open`/`mkdir`/`chdir`/`remove`/`ls` take paths
(absolute `/a/b` or relative `a/b`); each process has a cwd inode (`PROC_CWD`,
inherited on fork). Shell builtins: `cd`, and a cwd-aware prompt.