aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Makefile2
-rw-r--r--c/necho.c33
-rw-r--r--c/netlib.h39
-rw-r--r--c/wget.c18
-rw-r--r--include/gbos.inc1
-rw-r--r--src/programs.asm7
-rw-r--r--tools/gateway.py66
7 files changed, 112 insertions, 54 deletions
diff --git a/Makefile b/Makefile
index 4da1b2b..2dc3db7 100644
--- a/Makefile
+++ b/Makefile
@@ -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
diff --git a/c/necho.c b/c/necho.c
index b9dc9d5..99551d4 100644
--- a/c/necho.c
+++ b/c/necho.c
@@ -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()