diff options
Diffstat (limited to '')
| -rw-r--r-- | Makefile | 2 | ||||
| -rw-r--r-- | c/necho.c | 33 | ||||
| -rw-r--r-- | c/netlib.h | 39 | ||||
| -rw-r--r-- | c/wget.c | 18 | ||||
| -rw-r--r-- | include/gbos.inc | 1 | ||||
| -rw-r--r-- | src/programs.asm | 7 | ||||
| -rw-r--r-- | tools/gateway.py | 66 |
7 files changed, 112 insertions, 54 deletions
@@ -20,7 +20,7 @@ $(BUILD)/%.o: src/%.asm | $(BUILD) # C programs: compiled with SDCC into ROM-bank blobs and INCBIN'd by programs.asm CBLOBS := c/chello.bin c/echo.bin c/true.bin c/false.bin c/uname.bin \ c/pid.bin c/cat.bin c/wc.bin c/head.bin c/args.bin \ - c/ls.bin c/save.bin c/rm.bin c/sh.bin c/ps.bin c/kill.bin c/spin.bin c/blk.bin c/mkdir.bin c/count.bin c/ptest.bin c/necho.bin + c/ls.bin c/save.bin c/rm.bin c/sh.bin c/ps.bin c/kill.bin c/spin.bin c/blk.bin c/mkdir.bin c/count.bin c/ptest.bin c/necho.bin c/wget.bin $(BUILD)/programs.o: $(CBLOBS) # C programs also depend on the shared libc sources @@ -1,38 +1,7 @@ -#include "gbos.h" +#include "netlib.h" /* necho: send a SLIP-framed packet over the link port and print the reply. Talks to a host gateway (tools/gateway.py) that echoes frames back. */ -#define SLIP_END 0xC0 -#define SLIP_ESC 0xDB -#define SLIP_ESC_END 0xDC -#define SLIP_ESC_ESC 0xDD - -static void slip_send(const char *buf, unsigned char len) { - unsigned char i, c; - ssend(SLIP_END); - for (i = 0; i < len; i++) { - c = (unsigned char)buf[i]; - if (c == SLIP_END) { ssend(SLIP_ESC); ssend(SLIP_ESC_END); } - else if (c == SLIP_ESC) { ssend(SLIP_ESC); ssend(SLIP_ESC_ESC); } - else ssend(c); - } - ssend(SLIP_END); -} - -static unsigned char slip_recv(char *buf, unsigned char max) { - unsigned char len = 0, c; - for (;;) { - c = srecv(); - if (c == SLIP_END) { if (len) return len; continue; } - if (c == SLIP_ESC) { - c = srecv(); - if (c == SLIP_ESC_END) c = SLIP_END; - else if (c == SLIP_ESC_ESC) c = SLIP_ESC; - } - if (len < max) buf[len++] = (char)c; - } -} - void main(void) { char buf[64]; unsigned char len; diff --git a/c/netlib.h b/c/netlib.h new file mode 100644 index 0000000..ec98eac --- /dev/null +++ b/c/netlib.h @@ -0,0 +1,39 @@ +#ifndef NETLIB_H +#define NETLIB_H +/* SLIP (RFC 1055) framing over the link port. Header-only so each program gets + its own copy (the C build links one translation unit per program). */ +#include "gbos.h" + +#define SLIP_END 0xC0 +#define SLIP_ESC 0xDB +#define SLIP_ESC_END 0xDC +#define SLIP_ESC_ESC 0xDD + +/* send a framed packet (len up to 255) */ +static void slip_send(const char *buf, unsigned char len) { + unsigned char i, c; + ssend(SLIP_END); + for (i = 0; i < len; i++) { + c = (unsigned char)buf[i]; + if (c == SLIP_END) { ssend(SLIP_ESC); ssend(SLIP_ESC_END); } + else if (c == SLIP_ESC) { ssend(SLIP_ESC); ssend(SLIP_ESC_ESC); } + else ssend(c); + } + ssend(SLIP_END); +} + +/* receive one framed packet into buf (max), returns its length */ +static unsigned char slip_recv(char *buf, unsigned char max) { + unsigned char len = 0, c; + for (;;) { + c = srecv(); + if (c == SLIP_END) { if (len) return len; continue; } + if (c == SLIP_ESC) { + c = srecv(); + if (c == SLIP_ESC_END) c = SLIP_END; + else if (c == SLIP_ESC_ESC) c = SLIP_ESC; + } + if (len < max) buf[len++] = (char)c; + } +} +#endif diff --git a/c/wget.c b/c/wget.c new file mode 100644 index 0000000..013b326 --- /dev/null +++ b/c/wget.c @@ -0,0 +1,18 @@ +#include "netlib.h" +/* wget URL : send the URL to the host gateway over SLIP, print the reply body. + The gateway does the real DNS/TCP/HTTP and streams the body back as frames: + each reply frame is 'D'<chunk>, ending with a single 'E' frame. */ +void main(void) { + char *argv[4]; + unsigned char argc = argv_parse(argv, 4); + char buf[220]; + unsigned char len, i; + if (argc < 1) { puts("usage: wget URL"); nl(); return; } + slip_send(argv[0], strlen(argv[0])); + for (;;) { + len = slip_recv(buf, 219); + if (len >= 1 && buf[0] == 'E') break; /* end of stream */ + for (i = 1; i < len; i++) putc(buf[i]); /* skip the 'D' type byte */ + } + nl(); +} diff --git a/include/gbos.inc b/include/gbos.inc index 71d6fa0..7103f48 100644 --- a/include/gbos.inc +++ b/include/gbos.inc @@ -214,5 +214,6 @@ DEF PROG_MKDIR EQU 20 DEF PROG_COUNT EQU 21 DEF PROG_PTEST EQU 22 DEF PROG_NECHO EQU 23 +DEF PROG_WGET EQU 24 ENDC diff --git a/src/programs.asm b/src/programs.asm index a0bef96..fbe6067 100644 --- a/src/programs.asm +++ b/src/programs.asm @@ -112,6 +112,9 @@ ProgPtest: SECTION "prog_necho", ROMX[$4000], BANK[25] ProgNecho: INCBIN "c/necho.bin" +SECTION "prog_wget", ROMX[$4000], BANK[26] +ProgWget: + INCBIN "c/wget.bin" ; ----------------------------------------------------------------------------- ; PROG_SH (bank 3) - the shell, now written in C (c/sh.c): parses >/</| and @@ -202,6 +205,8 @@ ProgramTable:: dw ProgPtest db LOW(BANK(ProgNecho)), HIGH(BANK(ProgNecho)) dw ProgNecho + db LOW(BANK(ProgWget)), HIGH(BANK(ProgWget)) + dw ProgWget ; ----------------------------------------------------------------------------- ; NameTable (ROM0) - command name -> program id, searched by sys_lookup. @@ -256,4 +261,6 @@ NameTable:: db PROG_PTEST db "necho", 0 db PROG_NECHO + db "wget", 0 + db PROG_WGET db 0 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() |
