aboutsummaryrefslogtreecommitdiff
path: root/extmod/webrepl
diff options
context:
space:
mode:
authorJim Mussared <jim.mussared@gmail.com>2019-10-21 15:55:18 +1100
committerDamien George <damien.p.george@gmail.com>2019-12-20 12:59:13 +1100
commit7ce1e0b1dc466e48606164aad223c81c93a9cea2 (patch)
tree7ee755742cb06393c13b420610525b85b77f10aa /extmod/webrepl
parent7f235cbee924305e2d8a8aa86876770af66d7d82 (diff)
extmod/webrepl: Move webrepl scripts to common place and use manifest.
Move webrepl support code from ports/esp8266/modules into extmod/webrepl (to be alongside extmod/modwebrepl.c), and use frozen manifests to include it in the build on esp8266 and esp32. A small modification is made to webrepl.py to make it work on non-ESP ports, i.e. don't call dupterm_notify if not available.
Diffstat (limited to 'extmod/webrepl')
-rw-r--r--extmod/webrepl/manifest.py1
-rw-r--r--extmod/webrepl/webrepl.py80
-rw-r--r--extmod/webrepl/webrepl_setup.py102
-rw-r--r--extmod/webrepl/websocket_helper.py74
4 files changed, 257 insertions, 0 deletions
diff --git a/extmod/webrepl/manifest.py b/extmod/webrepl/manifest.py
new file mode 100644
index 000000000..0f2b44005
--- /dev/null
+++ b/extmod/webrepl/manifest.py
@@ -0,0 +1 @@
+freeze('.', ('webrepl.py', 'webrepl_setup.py', 'websocket_helper.py',))
diff --git a/extmod/webrepl/webrepl.py b/extmod/webrepl/webrepl.py
new file mode 100644
index 000000000..24c63299d
--- /dev/null
+++ b/extmod/webrepl/webrepl.py
@@ -0,0 +1,80 @@
+# This module should be imported from REPL, not run from command line.
+import socket
+import uos
+import network
+import uwebsocket
+import websocket_helper
+import _webrepl
+
+listen_s = None
+client_s = None
+
+def setup_conn(port, accept_handler):
+ global listen_s
+ listen_s = socket.socket()
+ listen_s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
+
+ ai = socket.getaddrinfo("0.0.0.0", port)
+ addr = ai[0][4]
+
+ listen_s.bind(addr)
+ listen_s.listen(1)
+ if accept_handler:
+ listen_s.setsockopt(socket.SOL_SOCKET, 20, accept_handler)
+ for i in (network.AP_IF, network.STA_IF):
+ iface = network.WLAN(i)
+ if iface.active():
+ print("WebREPL daemon started on ws://%s:%d" % (iface.ifconfig()[0], port))
+ return listen_s
+
+
+def accept_conn(listen_sock):
+ global client_s
+ cl, remote_addr = listen_sock.accept()
+ prev = uos.dupterm(None)
+ uos.dupterm(prev)
+ if prev:
+ print("\nConcurrent WebREPL connection from", remote_addr, "rejected")
+ cl.close()
+ return
+ print("\nWebREPL connection from:", remote_addr)
+ client_s = cl
+ websocket_helper.server_handshake(cl)
+ ws = uwebsocket.websocket(cl, True)
+ ws = _webrepl._webrepl(ws)
+ cl.setblocking(False)
+ # notify REPL on socket incoming data (ESP32/ESP8266-only)
+ if hasattr(uos, 'dupterm_notify'):
+ cl.setsockopt(socket.SOL_SOCKET, 20, uos.dupterm_notify)
+ uos.dupterm(ws)
+
+
+def stop():
+ global listen_s, client_s
+ uos.dupterm(None)
+ if client_s:
+ client_s.close()
+ if listen_s:
+ listen_s.close()
+
+
+def start(port=8266, password=None):
+ stop()
+ if password is None:
+ try:
+ import webrepl_cfg
+ _webrepl.password(webrepl_cfg.PASS)
+ setup_conn(port, accept_conn)
+ print("Started webrepl in normal mode")
+ except:
+ print("WebREPL is not configured, run 'import webrepl_setup'")
+ else:
+ _webrepl.password(password)
+ setup_conn(port, accept_conn)
+ print("Started webrepl in manual override mode")
+
+
+def start_foreground(port=8266):
+ stop()
+ s = setup_conn(port, None)
+ accept_conn(s)
diff --git a/extmod/webrepl/webrepl_setup.py b/extmod/webrepl/webrepl_setup.py
new file mode 100644
index 000000000..129313a21
--- /dev/null
+++ b/extmod/webrepl/webrepl_setup.py
@@ -0,0 +1,102 @@
+import sys
+#import uos as os
+import os
+import machine
+
+RC = "./boot.py"
+CONFIG = "./webrepl_cfg.py"
+
+def input_choice(prompt, choices):
+ while 1:
+ resp = input(prompt)
+ if resp in choices:
+ return resp
+
+def getpass(prompt):
+ return input(prompt)
+
+def input_pass():
+ while 1:
+ passwd1 = getpass("New password (4-9 chars): ")
+ if len(passwd1) < 4 or len(passwd1) > 9:
+ print("Invalid password length")
+ continue
+ passwd2 = getpass("Confirm password: ")
+ if passwd1 == passwd2:
+ return passwd1
+ print("Passwords do not match")
+
+
+def exists(fname):
+ try:
+ with open(fname):
+ pass
+ return True
+ except OSError:
+ return False
+
+
+def get_daemon_status():
+ with open(RC) as f:
+ for l in f:
+ if "webrepl" in l:
+ if l.startswith("#"):
+ return False
+ return True
+ return None
+
+
+def change_daemon(action):
+ LINES = ("import webrepl", "webrepl.start()")
+ with open(RC) as old_f, open(RC + ".tmp", "w") as new_f:
+ found = False
+ for l in old_f:
+ for patt in LINES:
+ if patt in l:
+ found = True
+ if action and l.startswith("#"):
+ l = l[1:]
+ elif not action and not l.startswith("#"):
+ l = "#" + l
+ new_f.write(l)
+ if not found:
+ new_f.write("import webrepl\nwebrepl.start()\n")
+ # FatFs rename() is not POSIX compliant, will raise OSError if
+ # dest file exists.
+ os.remove(RC)
+ os.rename(RC + ".tmp", RC)
+
+
+def main():
+ status = get_daemon_status()
+
+ print("WebREPL daemon auto-start status:", "enabled" if status else "disabled")
+ print("\nWould you like to (E)nable or (D)isable it running on boot?")
+ print("(Empty line to quit)")
+ resp = input("> ").upper()
+
+ if resp == "E":
+ if exists(CONFIG):
+ resp2 = input_choice("Would you like to change WebREPL password? (y/n) ", ("y", "n", ""))
+ else:
+ print("To enable WebREPL, you must set password for it")
+ resp2 = "y"
+
+ if resp2 == "y":
+ passwd = input_pass()
+ with open(CONFIG, "w") as f:
+ f.write("PASS = %r\n" % passwd)
+
+
+ if resp not in ("D", "E") or (resp == "D" and not status) or (resp == "E" and status):
+ print("No further action required")
+ sys.exit()
+
+ change_daemon(resp == "E")
+
+ print("Changes will be activated after reboot")
+ resp = input_choice("Would you like to reboot now? (y/n) ", ("y", "n", ""))
+ if resp == "y":
+ machine.reset()
+
+main()
diff --git a/extmod/webrepl/websocket_helper.py b/extmod/webrepl/websocket_helper.py
new file mode 100644
index 000000000..9c06db502
--- /dev/null
+++ b/extmod/webrepl/websocket_helper.py
@@ -0,0 +1,74 @@
+import sys
+try:
+ import ubinascii as binascii
+except:
+ import binascii
+try:
+ import uhashlib as hashlib
+except:
+ import hashlib
+
+DEBUG = 0
+
+def server_handshake(sock):
+ clr = sock.makefile("rwb", 0)
+ l = clr.readline()
+ #sys.stdout.write(repr(l))
+
+ webkey = None
+
+ while 1:
+ l = clr.readline()
+ if not l:
+ raise OSError("EOF in headers")
+ if l == b"\r\n":
+ break
+ # sys.stdout.write(l)
+ h, v = [x.strip() for x in l.split(b":", 1)]
+ if DEBUG:
+ print((h, v))
+ if h == b'Sec-WebSocket-Key':
+ webkey = v
+
+ if not webkey:
+ raise OSError("Not a websocket request")
+
+ if DEBUG:
+ print("Sec-WebSocket-Key:", webkey, len(webkey))
+
+ d = hashlib.sha1(webkey)
+ d.update(b"258EAFA5-E914-47DA-95CA-C5AB0DC85B11")
+ respkey = d.digest()
+ respkey = binascii.b2a_base64(respkey)[:-1]
+ if DEBUG:
+ print("respkey:", respkey)
+
+ sock.send(b"""\
+HTTP/1.1 101 Switching Protocols\r
+Upgrade: websocket\r
+Connection: Upgrade\r
+Sec-WebSocket-Accept: """)
+ sock.send(respkey)
+ sock.send("\r\n\r\n")
+
+
+# Very simplified client handshake, works for MicroPython's
+# websocket server implementation, but probably not for other
+# servers.
+def client_handshake(sock):
+ cl = sock.makefile("rwb", 0)
+ cl.write(b"""\
+GET / HTTP/1.1\r
+Host: echo.websocket.org\r
+Connection: Upgrade\r
+Upgrade: websocket\r
+Sec-WebSocket-Key: foo\r
+\r
+""")
+ l = cl.readline()
+# print(l)
+ while 1:
+ l = cl.readline()
+ if l == b"\r\n":
+ break
+# sys.stdout.write(l)