diff options
| author | user <user@clank> | 2026-07-17 01:31:38 +0200 |
|---|---|---|
| committer | user <user@clank> | 2026-07-17 01:31:38 +0200 |
| commit | 62171c761c72f7bc515b20361d043bfd239f71c9 (patch) | |
| tree | 4a3f1046403cd58177c4d9fa64d4af8ca066cac2 /tools | |
| parent | net: SLIP framing over the link port + a host gateway (echo round-trip) (diff) | |
| download | gbos-62171c761c72f7bc515b20361d043bfd239f71c9.tar.gz gbos-62171c761c72f7bc515b20361d043bfd239f71c9.tar.xz gbos-62171c761c72f7bc515b20361d043bfd239f71c9.zip | |
net: wget - real HTTP over the link port via the gateway
Grow the link-port demo from echo to actual network access. The Game Boy
still only does SLIP framing + display; the host gateway does DNS/TCP/HTTP.
- c/netlib.h: SLIP framing factored out (header-only, per-program copy).
necho.c now uses it too.
- c/wget.c: `wget URL` frames the URL, then prints the reply body. The
gateway streams the body back as typed frames: 'D'<chunk> ... 'E'.
- tools/gateway.py: add --mode http (urlopen the frame as a URL, cap the
body, chunk it) alongside --mode echo; --cmd runs any gbos command.
Verified: `wget example.com` streams back the full Example Domain HTML onto
the terminal; `wget sl0p.foo` fetches the real page. A Game Boy on the web,
over the link cable.
Diffstat (limited to 'tools')
| -rw-r--r-- | tools/gateway.py | 66 |
1 files changed, 45 insertions, 21 deletions
diff --git a/tools/gateway.py b/tools/gateway.py index 81a70af..3129773 100644 --- a/tools/gateway.py +++ b/tools/gateway.py @@ -1,18 +1,22 @@ #!/usr/bin/env python3 -"""gateway.py - a host-side "link cable adapter" for gbos networking. +"""gateway.py - host-side "link cable adapter" for gbos networking. -Wraps the emulator, owns its link-port serial (stdio), and speaks SLIP. This -milestone just ECHOES every frame back (loopback), so `necho` on gbos can prove -a framed round-trip. Console (ASCII) bytes on the same channel are printed. +Wraps the emulator, owns its link-port serial (stdio), speaks SLIP, and does +the real network work the Game Boy can't (DNS/TCP/HTTP). Modes: -usage: tools/gateway.py [rom] + echo - bounce every frame back (loopback test) + http - treat each frame as a URL, HTTP GET it, stream the body back + +Reply frames are typed: b'D'+chunk for data, b'E' to end the stream. + +usage: tools/gateway.py [--mode echo|http] [--cmd "wget example.com"] [--rom PATH] """ -import os, sys, subprocess, threading, time +import os, sys, subprocess, threading, time, argparse, urllib.request EMU = os.path.expanduser("~/dev/gbc/build/sl0pboy") -ROM = sys.argv[1] if len(sys.argv) > 1 else os.path.join(os.path.dirname(__file__), "..", "gbos.gb") - END, ESC, ESC_END, ESC_ESC = 0xC0, 0xDB, 0xDC, 0xDD +CHUNK = 200 +LIMIT = 800 # cap the fetched body (link is ~1-32 KB/s) def slip_encode(payload): out = bytearray([END]) @@ -23,13 +27,28 @@ def slip_encode(payload): out.append(END) return bytes(out) -def handle_frame(payload): - """The 'network'. For now: echo. (Later: DNS/TCP/HTTP/IRC.)""" - sys.stderr.write("\n[gw] frame in : %r\n" % bytes(payload)) - sys.stderr.write("[gw] frame out: %r (echo)\n" % bytes(payload)) - return bytes(payload) +def http_fetch(payload): + url = bytes(payload).decode("latin1", "replace").strip() + if not url.startswith("http"): + url = "http://" + url + sys.stderr.write("\n[gw] GET %s\n" % url) + try: + req = urllib.request.Request(url, headers={"User-Agent": "gbos/0.1"}) + with urllib.request.urlopen(req, timeout=8) as r: + body = r.read(LIMIT) + except Exception as e: + body = ("ERR: " + str(e)).encode() + sys.stderr.write("[gw] %d bytes\n" % len(body)) + frames = [b"D" + body[i:i+CHUNK] for i in range(0, len(body), CHUNK)] + frames.append(b"E") + return frames + +def handle(payload, mode): + if mode == "http": + return http_fetch(payload) + return [bytes(payload)] # echo -def reader(out, inp): +def reader(out, inp, mode): buf, in_frame, esc = bytearray(), False, False while True: b = out.read(1) @@ -38,8 +57,8 @@ def reader(out, inp): c = b[0] if c == END: if in_frame and buf: - reply = handle_frame(buf) - inp.write(slip_encode(reply)); inp.flush() + for f in handle(buf, mode): + inp.write(slip_encode(f)); inp.flush() buf, in_frame, esc = bytearray(), False, False else: buf, in_frame, esc = bytearray(), True, False @@ -55,12 +74,17 @@ def reader(out, inp): sys.stderr.flush() def main(): - p = subprocess.Popen([EMU, "--headless", "--uncapped", ROM], + ap = argparse.ArgumentParser() + ap.add_argument("--mode", default="echo", choices=["echo", "http"]) + ap.add_argument("--cmd", default="necho") + ap.add_argument("--rom", default=os.path.join(os.path.dirname(__file__), "..", "gbos.gb")) + a = ap.parse_args() + p = subprocess.Popen([EMU, "--headless", "--uncapped", a.rom], stdin=subprocess.PIPE, stdout=subprocess.PIPE, bufsize=0) - threading.Thread(target=reader, args=(p.stdout, p.stdin), daemon=True).start() - time.sleep(1.0) # boot to the shell - p.stdin.write(b"necho\n"); p.stdin.flush() - time.sleep(2.0) # let the framed round-trip happen + threading.Thread(target=reader, args=(p.stdout, p.stdin, a.mode), daemon=True).start() + time.sleep(1.0) + p.stdin.write((a.cmd + "\n").encode()); p.stdin.flush() + time.sleep(4.0) p.stdin.write(b"exit\n"); p.stdin.flush() try: p.wait(timeout=3) except Exception: p.kill() |
