aboutsummaryrefslogtreecommitdiffstats
path: root/tools/gateway.py
blob: 81a70af0b9c737bcfa6092a306932c26c860b1e6 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
#!/usr/bin/env python3
"""gateway.py - a 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.

usage: tools/gateway.py [rom]
"""
import os, sys, subprocess, threading, time

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

def slip_encode(payload):
    out = bytearray([END])
    for c in payload:
        if c == END:   out += bytes([ESC, ESC_END])
        elif c == ESC: out += bytes([ESC, ESC_ESC])
        else:          out.append(c)
    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 reader(out, inp):
    buf, in_frame, esc = bytearray(), False, False
    while True:
        b = out.read(1)
        if not b:
            return
        c = b[0]
        if c == END:
            if in_frame and buf:
                reply = handle_frame(buf)
                inp.write(slip_encode(reply)); inp.flush()
                buf, in_frame, esc = bytearray(), False, False
            else:
                buf, in_frame, esc = bytearray(), True, False
        elif in_frame:
            if esc:
                c = {ESC_END: END, ESC_ESC: ESC}.get(c, c); esc = False; buf.append(c)
            elif c == ESC:
                esc = True
            else:
                buf.append(c)
        else:
            sys.stderr.write(chr(c) if 32 <= c < 127 else ("\n" if c == 10 else "."))
            sys.stderr.flush()

def main():
    p = subprocess.Popen([EMU, "--headless", "--uncapped", 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
    p.stdin.write(b"exit\n"); p.stdin.flush()
    try: p.wait(timeout=3)
    except Exception: p.kill()

if __name__ == "__main__":
    main()