summaryrefslogtreecommitdiffstats
path: root/scripts/remote
diff options
context:
space:
mode:
authorJan Luebbe <jlu@pengutronix.de>2015-06-05 09:18:56 +0200
committerSascha Hauer <s.hauer@pengutronix.de>2016-01-18 09:25:50 +0100
commitbe6c6e653683e86d4c7aeb2b67c62ec0895befa3 (patch)
tree16de4d407994470101aed97834a4b6249f4952cd /scripts/remote
parent06fc3557c94c2b9d43bcfab72f4a024c7860be64 (diff)
downloadbarebox-be6c6e653683e86d4c7aeb2b67c62ec0895befa3.tar.gz
barebox-be6c6e653683e86d4c7aeb2b67c62ec0895befa3.tar.xz
host side for barebox remote control
This contains the host tool for barebox remote control. It is written in Phython with its own implementation of the RATP protocol. Currently this is a very simple tool which needs more work, but the code can also be used as a library. Example output: console: '. ' console: '.. ' console: 'dev ' console: 'env ' console: 'mnt ' console: '\n' Result: BBPacketCommandReturn(exit_code=0) Signed-off-by: Jan Lübbe <j.luebbe@pengutronix.de> Signed-off-by: Sascha Hauer <s.hauer@pengutronix.de> Signed-off-by: Andrey Smirnov <andrew.smirnov@gmail.com> Tested-by: Andrey Smirnov <andrew.smirnov@gmail.com>
Diffstat (limited to 'scripts/remote')
-rw-r--r--scripts/remote/__init__.py0
-rw-r--r--scripts/remote/controller.py173
-rw-r--r--scripts/remote/main.py168
-rw-r--r--scripts/remote/messages.py154
-rw-r--r--scripts/remote/missing.py28
-rw-r--r--scripts/remote/ratp.py773
-rw-r--r--scripts/remote/ratpfs.py189
-rw-r--r--scripts/remote/threadstdio.py47
8 files changed, 1532 insertions, 0 deletions
diff --git a/scripts/remote/__init__.py b/scripts/remote/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/scripts/remote/__init__.py
diff --git a/scripts/remote/controller.py b/scripts/remote/controller.py
new file mode 100644
index 0000000000..a7257ecc97
--- /dev/null
+++ b/scripts/remote/controller.py
@@ -0,0 +1,173 @@
+#!/usr/bin/env python2
+# -*- coding: utf-8 -*-
+
+from __future__ import absolute_import, division, print_function
+
+import struct
+import logging
+import sys
+import os
+from threading import Thread
+from Queue import Queue, Empty
+from .ratpfs import RatpFSServer
+from .messages import *
+from .ratp import RatpError
+
+try:
+ from time import monotonic
+except:
+ from .missing import monotonic
+
+
+def unpack(data):
+ p_type, = struct.unpack("!H", data[:2])
+ logging.debug("unpack: %r data=%r", p_type, repr(data))
+ if p_type == BBType.command:
+ logging.debug("received: command")
+ return BBPacketCommand(raw=data)
+ elif p_type == BBType.command_return:
+ logging.debug("received: command_return")
+ return BBPacketCommandReturn(raw=data)
+ elif p_type == BBType.consolemsg:
+ logging.debug("received: consolemsg")
+ return BBPacketConsoleMsg(raw=data)
+ elif p_type == BBType.ping:
+ logging.debug("received: ping")
+ return BBPacketPing(raw=data)
+ elif p_type == BBType.pong:
+ logging.debug("received: pong")
+ return BBPacketPong(raw=data)
+ elif p_type == BBType.getenv_return:
+ logging.debug("received: getenv_return")
+ return BBPacketGetenvReturn(raw=data)
+ elif p_type == BBType.fs:
+ logging.debug("received: fs")
+ return BBPacketFS(raw=data)
+ elif p_type == BBType.fs_return:
+ logging.debug("received: fs_return")
+ return BBPacketFSReturn(raw=data)
+ else:
+ logging.debug("received: UNKNOWN")
+ return BBPacket(raw=data)
+
+
+class Controller(Thread):
+ def __init__(self, conn):
+ Thread.__init__(self)
+ self.daemon = True
+ self.conn = conn
+ self.fsserver = None
+ self.rxq = None
+ self.conn.connect(timeout=5.0)
+ self._txq = Queue()
+ self._stop = False
+ self.fsserver = RatpFSServer()
+
+ def _send(self, bbpkt):
+ self.conn.send(bbpkt.pack())
+
+ def _handle(self, bbpkt):
+ if isinstance(bbpkt, BBPacketConsoleMsg):
+ os.write(sys.stdout.fileno(), bbpkt.text)
+ elif isinstance(bbpkt, BBPacketPong):
+ print("pong",)
+ elif isinstance(bbpkt, BBPacketFS):
+ if self.fsserver != None:
+ self._send(self.fsserver.handle(bbpkt))
+
+ def _expect(self, bbtype, timeout=1.0):
+ if timeout is not None:
+ limit = monotonic()+timeout
+ while timeout is None or limit > monotonic():
+ pkt = self.conn.recv(0.1)
+ if not pkt:
+ continue
+ bbpkt = unpack(pkt)
+ if isinstance(bbpkt, bbtype):
+ return bbpkt
+ else:
+ self._handle(bbpkt)
+
+ def export(self, path):
+ self.fsserver = RatpFSServer(path)
+
+ def ping(self):
+ self._send(BBPacketPing())
+ r = self._expect(BBPacketPong)
+ logging.info("Ping: %r", r)
+ if not r:
+ return 1
+ else:
+ print("pong")
+ return 0
+
+ def command(self, cmd):
+ self._send(BBPacketCommand(cmd=cmd))
+ r = self._expect(BBPacketCommandReturn, timeout=None)
+ logging.info("Command: %r", r)
+ return r.exit_code
+
+ def getenv(self, varname):
+ self._send(BBPacketGetenv(varname=varname))
+ r = self._expect(BBPacketGetenvReturn)
+ return r.text
+
+ def close(self):
+ self.conn.close()
+
+ def run(self):
+ assert self.rxq is not None
+ try:
+ while not self._stop:
+ # receive
+ pkt = self.conn.recv()
+ if pkt:
+ bbpkt = unpack(pkt)
+ if isinstance(bbpkt, BBPacketConsoleMsg):
+ self.rxq.put((self, bbpkt.text))
+ else:
+ self._handle(bbpkt)
+ # send
+ try:
+ pkt = self._txq.get(block=False)
+ except Empty:
+ pkt = None
+ if pkt:
+ self._send(pkt)
+ except RatpError as detail:
+ print("Ratp error:", detail, file=sys.stderr);
+ self.rxq.put((self, None))
+ return
+
+ def start(self, queue):
+ assert self.rxq is None
+ self.rxq = queue
+ Thread.start(self)
+
+ def stop(self):
+ self._stop = True
+ self.join()
+ self._stop = False
+ self.rxq = None
+
+ def send_async(self, pkt):
+ self._txq.put(pkt)
+
+ def send_async_console(self, text):
+ self._txq.put(BBPacketConsoleMsg(text=text))
+
+ def send_async_ping(self):
+ self._txq.put(BBPacketPing())
+
+
+def main():
+ import serial
+ from .ratp import SerialRatpConnection
+ url = "rfc2217://192.168.23.176:3002"
+ port = serial.serial_for_url(url, 115200)
+ conn = SerialRatpConnection(port)
+ ctrl = Controller(conn)
+ return ctrl
+
+if __name__ == "__main__":
+ C = main()
diff --git a/scripts/remote/main.py b/scripts/remote/main.py
new file mode 100644
index 0000000000..bd304723bb
--- /dev/null
+++ b/scripts/remote/main.py
@@ -0,0 +1,168 @@
+#!/usr/bin/env python2
+
+from __future__ import absolute_import, division, print_function
+
+import sys
+import os
+import argparse
+import logging
+from Queue import Queue
+from .ratp import RatpError
+
+try:
+ import serial
+except:
+ print("error: No python-serial package found", file=sys.stderr)
+ exit(2)
+
+
+def versiontuple(v):
+ return tuple(map(int, (v.split("."))))
+
+if versiontuple(serial.VERSION) < (2, 7):
+ print("warning: python-serial package is buggy in RFC2217 mode,",
+ "consider updating to at least 2.7", file=sys.stderr)
+
+from .ratp import SerialRatpConnection
+from .controller import Controller
+from .threadstdio import ConsoleInput
+
+
+def get_controller(args):
+ port = serial.serial_for_url(args.port, args.baudrate)
+ conn = SerialRatpConnection(port)
+
+ while True:
+ try:
+ ctrl = Controller(conn)
+ break
+ except (RatpError):
+ if args.wait == True:
+ pass
+ else:
+ raise
+
+ return ctrl
+
+
+def handle_run(args):
+ ctrl = get_controller(args)
+ ctrl.export(args.export)
+ res = ctrl.command(' '.join(args.arg))
+ if res:
+ res = 1
+ ctrl.close()
+ return res
+
+
+def handle_ping(args):
+ ctrl = get_controller(args)
+ res = ctrl.ping()
+ if res:
+ res = 1
+ ctrl.close()
+ return res
+
+
+def handle_getenv(args):
+ ctrl = get_controller(args)
+ value = ctrl.getenv(' '.join(args.arg))
+ if not value:
+ res = 1
+ else:
+ print(value)
+ res = 0
+ ctrl.close()
+ return res
+
+
+def handle_listen(args):
+ port = serial.serial_for_url(args.port, args.baudrate)
+ conn = SerialRatpConnection(port)
+ conn.listen()
+ while True:
+ conn.wait(None)
+ conn.close()
+
+
+def handle_console(args):
+ queue = Queue()
+ ctrl = get_controller(args)
+ ctrl.export(args.export)
+ ctrl.start(queue)
+ ctrl.send_async_console('\r')
+ cons = ConsoleInput(queue, exit='\x14') # CTRL-T
+ cons.start()
+ try:
+ while True:
+ src, data = queue.get(block=True)
+ if src == cons:
+ if data is None: # shutdown
+ cons.join()
+ break
+ elif data == '\x10': # CTRL-P
+ ctrl.send_async_ping()
+ else:
+ ctrl.send_async_console(data)
+ elif src == ctrl:
+ if data is None: # shutdown
+ sys.exit(1)
+ break
+ else:
+ os.write(sys.stdout.fileno(), data)
+ ctrl.stop()
+ ctrl.close()
+ finally:
+ print()
+ print("total retransmits=%i crc-errors=%i" % (
+ ctrl.conn.total_retransmits,
+ ctrl.conn.total_crc_errors))
+
+VERBOSITY = {
+ 0: logging.WARN,
+ 1: logging.INFO,
+ 2: logging.DEBUG,
+ }
+
+parser = argparse.ArgumentParser(prog='bbremote')
+parser.add_argument('-v', '--verbose', action='count', default=0)
+parser.add_argument('--port', type=str, default=os.environ.get('BBREMOTE_PORT', None))
+parser.add_argument('--baudrate', type=int, default=os.environ.get('BBREMOTE_BAUDRATE', 115200))
+parser.add_argument('--export', type=str, default=os.environ.get('BBREMOTE_EXPORT', None))
+parser.add_argument('-w', '--wait', action='count', default=0)
+subparsers = parser.add_subparsers(help='sub-command help')
+
+parser_run = subparsers.add_parser('run', help="run a barebox command")
+parser_run.add_argument('arg', nargs='+', help="barebox command to run")
+parser_run.set_defaults(func=handle_run)
+
+parser_ping = subparsers.add_parser('ping', help="test connection")
+parser_ping.set_defaults(func=handle_ping)
+
+parser_ping = subparsers.add_parser('getenv', help="get a barebox environment variable")
+parser_ping.add_argument('arg', nargs='+', help="variable name")
+parser_ping.set_defaults(func=handle_getenv)
+
+parser_run = subparsers.add_parser('listen', help="listen for an incoming connection")
+parser_run.set_defaults(func=handle_listen)
+
+parser_run = subparsers.add_parser('console', help="connect to the console")
+parser_run.set_defaults(func=handle_console)
+
+args = parser.parse_args()
+logging.basicConfig(level=VERBOSITY[args.verbose],
+ format='%(levelname)-8s %(module)-8s %(funcName)-16s %(message)s')
+try:
+ res = args.func(args)
+ exit(res)
+except RatpError as detail:
+ print("Ratp error:", detail, file=sys.stderr);
+ exit(127)
+except KeyboardInterrupt:
+ print("\nInterrupted", file=sys.stderr);
+ exit(1)
+#try:
+# res = args.func(args)
+#except Exception as e:
+# print("error: failed to establish connection: %s" % e, file=sys.stderr)
+# exit(2)
diff --git a/scripts/remote/messages.py b/scripts/remote/messages.py
new file mode 100644
index 0000000000..8e8495b12e
--- /dev/null
+++ b/scripts/remote/messages.py
@@ -0,0 +1,154 @@
+#!/usr/bin/env python2
+# -*- coding: utf-8 -*-
+
+from __future__ import absolute_import, division, print_function
+
+import struct
+
+
+class BBType(object):
+ command = 1
+ command_return = 2
+ consolemsg = 3
+ ping = 4
+ pong = 5
+ getenv = 6
+ getenv_return = 7
+ fs = 8
+ fs_return = 9
+
+
+class BBPacket(object):
+ def __init__(self, p_type=0, p_flags=0, payload="", raw=None):
+ self.p_type = p_type
+ self.p_flags = p_flags
+ if raw is not None:
+ self.unpack(raw)
+ else:
+ self.payload = payload
+
+ def __repr__(self):
+ return "BBPacket(%i, %i)" % (self.p_type, self.p_flags)
+
+ def _unpack_payload(self, data):
+ self.payload = data
+
+ def _pack_payload(self):
+ return self.payload
+
+ def unpack(self, data):
+ self.p_type, self.p_flags = struct.unpack("!HH", data[:4])
+ self._unpack_payload(data[4:])
+
+ def pack(self):
+ return struct.pack("!HH", self.p_type, self.p_flags) + \
+ self._pack_payload()
+
+
+class BBPacketCommand(BBPacket):
+ def __init__(self, raw=None, cmd=None):
+ self.cmd = cmd
+ super(BBPacketCommand, self).__init__(BBType.command, raw=raw)
+
+ def __repr__(self):
+ return "BBPacketCommand(cmd=%r)" % self.cmd
+
+ def _unpack_payload(self, payload):
+ self.cmd = payload
+
+ def _pack_payload(self):
+ return self.cmd
+
+
+class BBPacketCommandReturn(BBPacket):
+ def __init__(self, raw=None, exit_code=None):
+ self.exit_code = exit_code
+ super(BBPacketCommandReturn, self).__init__(BBType.command_return,
+ raw=raw)
+
+ def __repr__(self):
+ return "BBPacketCommandReturn(exit_code=%i)" % self.exit_code
+
+ def _unpack_payload(self, data):
+ self.exit_code, = struct.unpack("!L", data[:4])
+
+ def _pack_payload(self):
+ return struct.pack("!L", self.exit_code)
+
+
+class BBPacketConsoleMsg(BBPacket):
+ def __init__(self, raw=None, text=None):
+ self.text = text
+ super(BBPacketConsoleMsg, self).__init__(BBType.consolemsg, raw=raw)
+
+ def __repr__(self):
+ return "BBPacketConsoleMsg(text=%r)" % self.text
+
+ def _unpack_payload(self, payload):
+ self.text = payload
+
+ def _pack_payload(self):
+ return self.text
+
+
+class BBPacketPing(BBPacket):
+ def __init__(self, raw=None):
+ super(BBPacketPing, self).__init__(BBType.ping, raw=raw)
+
+ def __repr__(self):
+ return "BBPacketPing()"
+
+
+class BBPacketPong(BBPacket):
+ def __init__(self, raw=None):
+ super(BBPacketPong, self).__init__(BBType.pong, raw=raw)
+
+ def __repr__(self):
+ return "BBPacketPong()"
+
+
+class BBPacketGetenv(BBPacket):
+ def __init__(self, raw=None, varname=None):
+ self.varname = varname
+ super(BBPacketGetenv, self).__init__(BBType.getenv, raw=raw)
+
+ def __repr__(self):
+ return "BBPacketGetenv(varname=%r)" % self.varname
+
+ def _unpack_payload(self, payload):
+ self.varname = payload
+
+ def _pack_payload(self):
+ return self.varname
+
+
+class BBPacketGetenvReturn(BBPacket):
+ def __init__(self, raw=None, text=None):
+ self.text = text
+ super(BBPacketGetenvReturn, self).__init__(BBType.getenv_return,
+ raw=raw)
+
+ def __repr__(self):
+ return "BBPacketGetenvReturn(varvalue=%s)" % self.text
+
+ def _unpack_payload(self, payload):
+ self.text = payload
+
+ def _pack_payload(self):
+ return self.text
+
+
+class BBPacketFS(BBPacket):
+ def __init__(self, raw=None, payload=None):
+ super(BBPacketFS, self).__init__(BBType.fs, payload=payload, raw=raw)
+
+ def __repr__(self):
+ return "BBPacketFS(payload=%r)" % self.payload
+
+
+class BBPacketFSReturn(BBPacket):
+ def __init__(self, raw=None, payload=None):
+ super(BBPacketFSReturn, self).__init__(BBType.fs_return, payload=payload, raw=raw)
+
+ def __repr__(self):
+ return "BBPacketFSReturn(payload=%r)" % self.payload
diff --git a/scripts/remote/missing.py b/scripts/remote/missing.py
new file mode 100644
index 0000000000..67c2dfa8c0
--- /dev/null
+++ b/scripts/remote/missing.py
@@ -0,0 +1,28 @@
+#!/usr/bin/env python
+
+import ctypes
+import os
+
+CLOCK_MONOTONIC_RAW = 4 # from <linux/time.h>
+
+
+class timespec(ctypes.Structure):
+ _fields_ = [
+ ('tv_sec', ctypes.c_long),
+ ('tv_nsec', ctypes.c_long)
+ ]
+
+librt = ctypes.CDLL('librt.so.1', use_errno=True)
+clock_gettime = librt.clock_gettime
+clock_gettime.argtypes = [ctypes.c_int, ctypes.POINTER(timespec)]
+
+
+def monotonic():
+ t = timespec()
+ if clock_gettime(CLOCK_MONOTONIC_RAW, ctypes.pointer(t)) != 0:
+ errno_ = ctypes.get_errno()
+ raise OSError(errno_, os.strerror(errno_))
+ return t.tv_sec + t.tv_nsec * 1e-9
+
+if __name__ == "__main__":
+ print monotonic()
diff --git a/scripts/remote/ratp.py b/scripts/remote/ratp.py
new file mode 100644
index 0000000000..079fb871a3
--- /dev/null
+++ b/scripts/remote/ratp.py
@@ -0,0 +1,773 @@
+#!/usr/bin/env python2
+# -*- coding: utf-8 -*-
+
+from __future__ import absolute_import, division, print_function
+
+import crcmod
+import logging
+import struct
+from enum import Enum
+from time import sleep
+
+try:
+ from time import monotonic
+except:
+ from .missing import monotonic
+
+csum_func = crcmod.predefined.mkCrcFun('xmodem')
+
+
+class RatpState(Enum):
+ listen = "listen" # 1
+ syn_sent = "syn-sent" # 2
+ syn_received = "syn-received" # 3
+ established = "established" # 4
+ fin_wait = "fin-wait" # 5
+ last_ack = "last-ack" # 6
+ closing = "closing" # 7
+ time_wait = "time-wait" # 8
+ closed = "closed" # 9
+
+
+class RatpInvalidHeader(ValueError):
+ pass
+
+
+class RatpInvalidPayload(ValueError):
+ pass
+
+
+class RatpError(ValueError):
+ pass
+
+
+class RatpPacket(object):
+
+ def __init__(self, data=None, flags=''):
+ self.payload = None
+ self.synch = 0x01
+ self._control = 0
+ self.length = 0
+ self.csum = 0
+ self.c_syn = False
+ self.c_ack = False
+ self.c_fin = False
+ self.c_rst = False
+ self.c_sn = 0
+ self.c_an = 0
+ self.c_eor = False
+ self.c_so = False
+ if data:
+ (self.synch, self._control, self.length, self.csum) = \
+ struct.unpack('!BBBB', data)
+ if self.synch != 0x01:
+ raise RatpInvalidHeader("invalid synch octet (%x != %x)" %
+ (self.synch, 0x01))
+ csum = (self._control + self.length + self.csum) & 0xff
+ if csum != 0xff:
+ raise RatpInvalidHeader("invalid csum octet (%x != %x)" %
+ (csum, 0xff))
+ self._unpack_control()
+ elif flags:
+ if 'S' in flags:
+ self.c_syn = True
+ if 'A' in flags:
+ self.c_ack = True
+ if 'F' in flags:
+ self.c_fin = True
+ if 'R' in flags:
+ self.c_rst = True
+ if 'E' in flags:
+ self.c_eor = True
+
+ def __repr__(self):
+ s = "RatpPacket("
+ if self.c_syn:
+ s += "SYN,"
+ if self.c_ack:
+ s += "ACK,"
+ if self.c_fin:
+ s += "FIN,"
+ if self.c_rst:
+ s += "RST,"
+ s += "SN=%i,AN=%i," % (self.c_sn, self.c_an)
+ if self.c_eor:
+ s += "EOR,"
+ if self.c_so:
+ s += "SO,DATA=%i)" % self.length
+ else:
+ s += "DATA=%i)" % self.length
+ return s
+
+ def _pack_control(self):
+ self._control = 0 | \
+ self.c_syn << 7 | \
+ self.c_ack << 6 | \
+ self.c_fin << 5 | \
+ self.c_rst << 4 | \
+ self.c_sn << 3 | \
+ self.c_an << 2 | \
+ self.c_eor << 1 | \
+ self.c_so << 0
+
+ def _unpack_control(self):
+ self.c_syn = bool(self._control & 1 << 7)
+ self.c_ack = bool(self._control & 1 << 6)
+ self.c_fin = bool(self._control & 1 << 5)
+ self.c_rst = bool(self._control & 1 << 4)
+ self.c_sn = bool(self._control & 1 << 3)
+ self.c_an = bool(self._control & 1 << 2)
+ self.c_eor = bool(self._control & 1 << 1)
+ self.c_so = bool(self._control & 1 << 0)
+
+ def pack(self):
+ self._pack_control()
+ self.csum = 0
+ self.csum = (self._control + self.length + self.csum)
+ self.csum = (self.csum & 0xff) ^ 0xff
+ return struct.pack('!BBBB', self.synch, self._control, self.length,
+ self.csum)
+
+ def unpack_payload(self, payload):
+ (c_recv,) = struct.unpack('!H', payload[-2:])
+ c_calc = csum_func(payload[:-2])
+ if c_recv != c_calc:
+ raise RatpInvalidPayload("bad checksum (%04x != %04x)" %
+ (c_recv, c_calc))
+ self.payload = payload[:-2]
+
+ def pack_payload(self):
+ c_calc = csum_func(self.payload)
+ return self.payload+struct.pack('!H', c_calc)
+
+
+class RatpConnection(object):
+ def __init__(self):
+ self._state = RatpState.closed
+ self._passive = True
+ self._input = b''
+ self._s_sn = 0
+ self._r_sn = 0
+ self._retrans = None
+ self._retrans_counter = None
+ self._retrans_deadline = None
+ self._r_mdl = None
+ self._s_mdl = 0xff
+ self._rx_buf = [] # reassembly buffer
+ self._rx_queue = []
+ self._tx_queue = []
+ self._rtt_alpha = 0.8
+ self._rtt_beta = 2.0
+ self._srtt = 0.2
+ self._rto_min, self._rto_max = 0.2, 1
+ self._tx_timestamp = None
+ self.total_retransmits = 0
+ self.total_crc_errors = 0
+
+ def _update_srtt(self, rtt):
+ self._srtt = (self._rtt_alpha * self._srtt) + \
+ ((1.0 - self._rtt_alpha) * rtt)
+ logging.info("SRTT: %r", self._srtt)
+
+ def _get_rto(self):
+ return min(self._rto_max,
+ max(self._rto_min, self._rtt_beta * self._srtt))
+
+ def _write(self, pkt):
+
+ if pkt.payload or pkt.c_so or pkt.c_syn or pkt.c_rst or pkt.c_fin:
+ self._s_sn = pkt.c_sn
+ if not self._retrans:
+ self._retrans = pkt
+ self._retrans_counter = 0
+ else:
+ self.total_retransmits += 1
+ self._retrans_counter += 1
+ if self._retrans_counter > 10:
+ raise RatpError("Maximum retransmit count exceeded")
+ self._retrans_deadline = monotonic()+self._get_rto()
+
+ logging.info("Write: %r", pkt)
+
+ self._write_raw(pkt.pack())
+ if pkt.payload:
+ self._write_raw(pkt.pack_payload())
+ self._tx_timestamp = monotonic()
+
+ def _check_rto(self):
+ if self._retrans is None:
+ return
+
+ if self._retrans_deadline < monotonic():
+ logging.debug("Retransmit...")
+ self._write(self._retrans)
+
+ def _check_time_wait(self):
+ if not self._state == RatpState.time_wait:
+ return
+
+ remaining = self._time_wait_deadline - monotonic()
+ if remaining < 0:
+ self._state = RatpState.closed
+ else:
+ logging.debug("Time-Wait: %.2f remaining" % remaining)
+ sleep(min(remaining, 0.1))
+
+ def _read(self):
+ if len(self._input) < 4:
+ self._input += self._read_raw(4-len(self._input))
+ if len(self._input) < 4:
+ return
+
+ try:
+ pkt = RatpPacket(data=self._input[:4])
+ except RatpInvalidHeader as e:
+ logging.info("%r", e)
+ self._input = self._input[1:]
+ return
+
+ self._input = self._input[4:]
+
+ logging.info("Read: %r", pkt)
+
+ if pkt.c_syn or pkt.c_rst or pkt.c_so or pkt.c_fin:
+ return pkt
+
+ if pkt.length == 0:
+ return pkt
+
+ while len(self._input) < pkt.length+2:
+ self._input += self._read_raw()
+
+ try:
+ pkt.unpack_payload(self._input[:pkt.length+2])
+ except RatpInvalidPayload as e:
+ self.total_crc_errors += 1
+ return
+ finally:
+ self._input = self._input[pkt.length+2:]
+
+ return pkt
+
+ def _close(self):
+ pass
+
+ def _a(self, r):
+ logging.info("A")
+
+ if r.c_rst:
+ return True
+
+ if r.c_ack:
+ s = RatpPacket(flags='R')
+ s.c_sn = r.c_an
+ self._write(s)
+ return False
+
+ if r.c_syn:
+ self._r_mdl = r.length
+
+ s = RatpPacket(flags='SA')
+ s.c_sn = 0
+ s.c_an = (r.c_sn + 1) % 2
+ s.length = self._s_mdl
+ self._write(s)
+ self._state = RatpState.syn_received
+ return False
+
+ return False
+
+ def _b(self, r):
+ logging.info("B")
+
+ if r.c_ack and r.c_an != (self._s_sn + 1) % 2:
+ if r.c_rst:
+ return False
+ else:
+ s = RatpPacket(flags='R')
+ s.c_sn = r.c_an
+ self._write(s)
+ return False
+
+ if r.c_rst:
+ if r.c_ack:
+ self._retrans = None
+ # FIXME: delete the TCB
+ self._state = RatpState.closed
+ return False
+ else:
+ return False
+
+ if r.c_syn:
+ if r.c_ack:
+ self._r_mdl = r.length
+ self._retrans = None
+ self._r_sn = r.c_sn
+ s = RatpPacket(flags='A')
+ s.c_sn = r.c_an
+ s.c_an = (r.c_sn + 1) % 2
+ self._write(s)
+ self._state = RatpState.established
+ return False
+ else:
+ self._retrans = None
+ s = RatpPacket(flags='SA')
+ s.c_sn = 0
+ s.c_an = (r.c_sn + 1) % 2
+ s.length = self._s_mdl
+ self._write(s)
+ self._state = RatpState.syn_received
+ return False
+
+ return False
+
+ def _c1(self, r):
+ logging.info("C1")
+
+ if r.c_sn != self._r_sn:
+ return True
+
+ if r.c_rst or r.c_fin:
+ return False
+
+ s = RatpPacket(flags='A')
+ s.c_sn = r.c_an
+ s.c_an = (r.c_sn + 1) % 2
+ self._write(s)
+ return False
+
+ def _c2(self, r):
+ logging.info("C2")
+
+ if r.length == 0 and r.c_so == 0:
+ return True
+
+ if r.c_sn != self._r_sn:
+ return True
+
+ if r.c_rst or r.c_fin:
+ return False
+
+ if r.c_syn:
+ s = RatpPacket(flags='RA')
+ s.c_sn = r.c_an
+ s.c_an = (r.c_sn + 1) % 2
+ self._write(s)
+ self._retrans = None
+ # FIXME: inform the user "Error: Connection reset"
+ self._state = RatpState.closed
+ return False
+
+ # FIXME: only ack duplicate data packages?
+ # This is not documented in RFC 916
+ if r.length or r.c_so:
+ logging.info("C2: duplicate data packet, dropping")
+ s = RatpPacket(flags='A')
+ s.c_sn = r.c_an
+ s.c_an = (r.c_sn + 1) % 2
+ self._write(s)
+
+ return False
+
+ def _d1(self, r):
+ logging.info("D1")
+
+ if not r.c_rst:
+ return True
+
+ if self._passive:
+ self._retrans = None
+ self._state = RatpState.listen
+ return False
+ else:
+ self._retrans = None
+
+ self._state = RatpState.closed
+ raise RatpError("Connection refused")
+
+ def _d2(self, r):
+ logging.info("D2")
+
+ if not r.c_rst:
+ return True
+
+ self._retrans = None
+
+ self._state = RatpState.closed
+
+ raise RatpError("Connection reset")
+
+ def _d3(self, r):
+ logging.info("C3")
+
+ if not r.c_rst:
+ return True
+
+ self._state = RatpState.closed
+ return False
+
+ def _e(self, r):
+ logging.info("E")
+
+ if not r.c_syn:
+ return True
+
+ self._retrans = None
+ s = RatpPacket(flags='R')
+ if r.c_ack:
+ s.c_sn = r.c_an
+ else:
+ s.c_sn = 0
+ self._write(s)
+ self._state = RatpState.closed
+ raise RatpError("Connection reset")
+
+ def _f1(self, r):
+ logging.info("F1")
+
+ if not r.c_ack:
+ return False
+
+ if r.c_an == (self._s_sn + 1) % 2:
+ return True
+
+ if self._passive:
+ self._retrans = None
+ s = RatpPacket(flags='R')
+ s.c_sn = r.c_an
+ self._write(s)
+ self._state = RatpState.listen
+ return False
+ else:
+ self._retrans = None
+ s = RatpPacket(flags='R')
+ s.c_sn = r.c_an
+ self._write(s)
+ self._state = RatpState.closed
+ raise RatpError("Connection refused")
+
+ def _f2(self, r):
+ logging.info("F2")
+
+ if not r.c_ack:
+ return False
+
+ if r.c_an == (self._s_sn + 1) % 2:
+ if self._retrans:
+ self._retrans = None
+ self._update_srtt(monotonic()-self._tx_timestamp)
+ # FIXME: inform the user with an "Ok" if a buffer has been
+ # entirely acknowledged. Another packet containing data may
+ # now be sent.
+ return True
+
+ return True
+
+ def _f3(self, r):
+ logging.info("F3")
+
+ if not r.c_ack:
+ return False
+
+ if r.c_an == (self._s_sn + 1) % 2:
+ return True
+
+ return True
+
+ def _g(self, r):
+ logging.info("G")
+
+ if not r.c_rst:
+ return False
+
+ self._retrans = None
+ if r.c_ack:
+ s = RatpPacket(flags='R')
+ s.c_sn = r.c_an
+ self._write(s)
+ else:
+ s = RatpPacket(flags='RA')
+ s.c_sn = r.c_an
+ s.c_an = (r.c_sn + 1) % 2
+ self._write(s)
+
+ return False
+
+ def _h1(self, r):
+ logging.info("H1")
+
+ # FIXME: initial data?
+ self._state = RatpState.established
+ self._r_sn = r.c_sn
+
+ return False
+
+ def _h2(self, r):
+ logging.info("H2")
+
+ if not r.c_fin:
+ return True
+
+ if self._retrans is not None:
+ # FIXME: inform the user "Warning: Data left unsent.", "Connection closing."
+ self._retrans = None
+ s = RatpPacket(flags='FA')
+ s.c_sn = r.c_an
+ s.c_an = (r.c_sn + 1) % 2
+ self._write(s)
+ self._state = RatpState.last_ack
+ raise RatpError("Connection closed by remote")
+
+ def _h3(self, r):
+ logging.info("H3")
+
+ if not r.c_fin:
+ # Our fin was lost, rely on retransmission
+ return False
+
+ if r.length or r.c_so:
+ self._retrans = None
+ s = RatpPacket(flags='RA')
+ s.c_sn = r.c_an
+ s.c_an = (r.c_sn + 1) % 2
+ self._write(s)
+ self._state = RatpState.closed
+ raise RatpError("Connection reset")
+
+ if r.c_an == (self._s_sn + 1) % 2:
+ self._retrans = None
+ s = RatpPacket(flags='A')
+ s.c_sn = r.c_an
+ s.c_an = (r.c_sn + 1) % 2
+ self._write(s)
+ self._time_wait_deadline = monotonic() + self._get_rto()
+ self._state = RatpState.time_wait
+ return False
+ else:
+ self._retrans = None
+ s = RatpPacket(flags='A')
+ s.c_sn = r.c_an
+ s.c_an = (r.c_sn + 1) % 2
+ self._write(s)
+ self._state = RatpState.closing
+ return False
+
+ def _h4(self, r):
+ logging.info("H4")
+
+ if r.c_an == (self._s_sn + 1) % 2:
+ self._retrans = None
+ self._time_wait_deadline = monotonic() + self._get_rto()
+ self._state = RatpState.time_wait
+ return False
+
+ return False
+
+ def _h5(self, r):
+ logging.info("H5")
+
+ if r.c_an == (self._s_sn + 1) % 2:
+ self._time_wait_deadline = monotonic() + self._get_rto()
+ self._state = RatpState.time_wait
+ return False
+
+ return False
+
+ def _h6(self, r):
+ logging.info("H6")
+
+ if not r.c_ack:
+ return False
+
+ if not r.c_fin:
+ return False
+
+ self._retrans = None
+ s = RatpPacket(flags='A')
+ s.c_sn = r.c_an
+ s.c_an = (r.c_sn + 1) % 2
+ self._write(s)
+ self._time_wait_deadline = monotonic() + self._get_rto()
+ return False
+
+ def _i1(self, r):
+ logging.info("I1")
+
+ if r.c_so:
+ self._r_sn = r.c_sn
+ self._rx_buf.append(chr(r.length))
+ elif r.length:
+ self._r_sn = r.c_sn
+ self._rx_buf.append(r.payload)
+ else:
+ return False
+
+ # reassemble
+ if r.c_eor:
+ logging.info("Reassembling %i frames", len(self._rx_buf))
+ self._rx_queue.append(''.join(self._rx_buf))
+ self._rx_buf = []
+
+ s = RatpPacket(flags='A')
+ s.c_sn = r.c_an
+ s.c_an = (r.c_sn + 1) % 2
+ self._write(s)
+ return False
+
+ def _machine(self, pkt):
+ logging.info("State: %r", self._state)
+ if self._state == RatpState.listen:
+ self._a(pkt)
+ elif self._state == RatpState.syn_sent:
+ self._b(pkt)
+ elif self._state == RatpState.syn_received:
+ self._c1(pkt) and \
+ self._d1(pkt) and \
+ self._e(pkt) and \
+ self._f1(pkt) and \
+ self._h1(pkt)
+ elif self._state == RatpState.established:
+ self._c2(pkt) and \
+ self._d2(pkt) and \
+ self._e(pkt) and \
+ self._f2(pkt) and \
+ self._h2(pkt) and \
+ self._i1(pkt)
+ elif self._state == RatpState.fin_wait:
+ self._c2(pkt) and \
+ self._d2(pkt) and \
+ self._e(pkt) and \
+ self._f3(pkt) and \
+ self._h3(pkt)
+ elif self._state == RatpState.last_ack:
+ self._c2(pkt) and \
+ self._d3(pkt) and \
+ self._e(pkt) and \
+ self._f3(pkt) and \
+ self._h4(pkt)
+ elif self._state == RatpState.closing:
+ self._c2(pkt) and \
+ self._d3(pkt) and \
+ self._e(pkt) and \
+ self._f3(pkt) and \
+ self._h5(pkt)
+ elif self._state == RatpState.time_wait:
+ self._d3(pkt) and \
+ self._e(pkt) and \
+ self._f3(pkt) and \
+ self._h6(pkt)
+ elif self._state == RatpState.closed:
+ self._g(pkt)
+
+ def wait(self, deadline):
+ while deadline is None or deadline > monotonic():
+ pkt = self._read()
+ if pkt:
+ self._machine(pkt)
+ else:
+ self._check_rto()
+ self._check_time_wait()
+ if not self._retrans or self._rx_queue:
+ return
+
+ def wait1(self, deadline):
+ while deadline is None or deadline > monotonic():
+ pkt = self._read()
+ if pkt:
+ self._machine(pkt)
+ else:
+ self._check_rto()
+ self._check_time_wait()
+ if not self._retrans:
+ return
+
+ def listen(self):
+ logging.info("LISTEN")
+ self._state = RatpState.listen
+
+ def connect(self, timeout=5.0):
+ deadline = monotonic() + timeout
+ logging.info("CONNECT")
+ self._retrans = None
+ syn = RatpPacket(flags='S')
+ syn.length = self._s_mdl
+ self._write(syn)
+ self._state = RatpState.syn_sent
+ self.wait(deadline)
+
+ def send_one(self, data, eor=True, timeout=1.0):
+ deadline = monotonic() + timeout
+ logging.info("SEND_ONE (len=%i, eor=%r)", len(data), eor)
+ assert self._state == RatpState.established
+ assert self._retrans is None
+ snd = RatpPacket(flags='A')
+ snd.c_eor = eor
+ snd.c_sn = (self._s_sn + 1) % 2
+ snd.c_an = (self._r_sn + 1) % 2
+ snd.length = len(data)
+ snd.payload = data
+ self._write(snd)
+ self.wait1(deadline=None)
+
+ def send(self, data, timeout=1.0):
+ logging.info("SEND (len=%i)", len(data))
+ while len(data) > 255:
+ self.send_one(data[:255], eor=False, timeout=timeout)
+ data = data[255:]
+ self.send_one(data, eor=True, timeout=timeout)
+
+ def recv(self, timeout=1.0):
+ deadline = monotonic() + timeout
+
+ assert self._state == RatpState.established
+ if self._rx_queue:
+ return self._rx_queue.pop(0)
+ self.wait(deadline)
+ if self._rx_queue:
+ return self._rx_queue.pop(0)
+
+ def close(self, timeout=1.0):
+ deadline = monotonic() + timeout
+ logging.info("CLOSE")
+ if self._state == RatpState.established:
+ fin = RatpPacket(flags='FA') # FIXME: only F?
+ fin.c_sn = (self._s_sn + 1) % 2
+ fin.c_an = (self._r_sn + 1) % 2
+ self._write(fin)
+ self._state = RatpState.fin_wait
+ while deadline > monotonic() and not self._state == RatpState.time_wait:
+ self.wait(deadline)
+ while self._state == RatpState.time_wait:
+ self.wait(None)
+ if self._state == RatpState.closed:
+ logging.info("CLOSE: success")
+ else:
+ logging.info("CLOSE: failure")
+
+
+ def abort(self):
+ logging.info("ABORT")
+
+ def status(self):
+ logging.info("STATUS")
+ return self._state
+
+
+class SerialRatpConnection(RatpConnection):
+ def __init__(self, port):
+ super(SerialRatpConnection, self).__init__()
+ self.__port = port
+ self.__port.timeout = 0.01
+ self.__port.writeTimeout = None
+ self.__port.flushInput()
+
+ def _write_raw(self, data):
+ if data:
+ logging.debug("-> %r", bytearray(data))
+ return self.__port.write(data)
+
+ def _read_raw(self, size=1):
+ data = self.__port.read(size)
+ if data:
+ logging.debug("<- %r", bytearray(data))
+ return data
diff --git a/scripts/remote/ratpfs.py b/scripts/remote/ratpfs.py
new file mode 100644
index 0000000000..91ca044540
--- /dev/null
+++ b/scripts/remote/ratpfs.py
@@ -0,0 +1,189 @@
+#!/usr/bin/env python2
+# -*- coding: utf-8 -*-
+
+from __future__ import absolute_import, division, print_function
+
+import logging
+import os
+import stat
+import struct
+from enum import IntEnum
+
+from .messages import BBPacketFS, BBPacketFSReturn
+
+class RatpFSType(IntEnum):
+ invalid = 0
+ mount_call = 1
+ mount_return = 2
+ readdir_call = 3
+ readdir_return = 4
+ stat_call = 5
+ stat_return = 6
+ open_call = 7
+ open_return = 8
+ read_call = 9
+ read_return = 10
+ write_call = 11
+ write_return = 12
+ close_call = 13
+ close_return = 14
+ truncate_call = 15
+ truncate_return = 16
+
+
+class RatpFSError(ValueError):
+ pass
+
+
+class RatpFSPacket(object):
+ def __init__(self, type=RatpFSType.invalid, payload="", raw=None):
+ if raw is not None:
+ type, = struct.unpack('!B', raw[:1])
+ self.type = RatpFSType(type)
+ self.payload = raw[1:]
+ else:
+ self.type = type
+ self.payload = payload
+
+ def __repr__(self):
+ s = "%s(" % self.__class__.__name__
+ s += "TYPE=%i," % self.type
+ s += "PAYLOAD=%s)" % repr(self.payload)
+ return s
+
+ def pack(self):
+ return struct.pack('!B', int(self.type))+self.payload
+
+
+class RatpFSServer(object):
+ def __init__(self, path=None):
+ self.path = path
+ if path:
+ self.path = os.path.abspath(os.path.expanduser(path))
+ self.next_handle = 1 # 0 is invalid
+ self.files = {}
+ self.mounted = False
+ logging.info("exporting: %s", self.path)
+
+ def _alloc_handle(self):
+ handle = self.next_handle
+ self.next_handle += 1
+ return handle
+
+ def _resolve(self, path):
+ components = path.split('/')
+ components = [x for x in components if x and x != '..']
+ return os.path.join(self.path, *components)
+
+ def handle_stat(self, path):
+
+ try:
+ logging.info("path: %r", path)
+ path = self._resolve(path)
+ logging.info("path1: %r", path)
+ s = os.stat(path)
+ except OSError as e:
+ return struct.pack('!BI', 0, e.errno)
+ if stat.S_ISREG(s.st_mode):
+ return struct.pack('!BI', 1, s.st_size)
+ elif stat.S_ISDIR(s.st_mode):
+ return struct.pack('!BI', 2, s.st_size)
+ else:
+ return struct.pack('!BI', 0, 0)
+
+ def handle_open(self, params):
+ flags, = struct.unpack('!I', params[:4])
+ flags = flags & (os.O_RDONLY | os.O_WRONLY | os.O_RDWR | os.O_CREAT |
+ os.O_TRUNC)
+ path = params[4:]
+ try:
+ f = os.open(self._resolve(path), flags, 0666)
+ except OSError as e:
+ return struct.pack('!II', 0, e.errno)
+ h = self._alloc_handle()
+ self.files[h] = f
+ size = os.lseek(f, 0, os.SEEK_END)
+ return struct.pack('!II', h, size)
+
+ def handle_read(self, params):
+ h, pos, size = struct.unpack('!III', params)
+ f = self.files[h]
+ os.lseek(f, pos, os.SEEK_SET)
+ size = min(size, 4096)
+ return os.read(f, size)
+
+ def handle_write(self, params):
+ h, pos = struct.unpack('!II', params[:8])
+ payload = params[8:]
+ f = self.files[h]
+ pos = os.lseek(f, pos, os.SEEK_SET)
+ assert os.write(f, payload) == len(payload)
+ return ""
+
+ def handle_readdir(self, path):
+ res = ""
+ for x in os.listdir(self._resolve(path)):
+ res += x+'\0'
+ return res
+
+ def handle_close(self, params):
+ h, = struct.unpack('!I', params[:4])
+ os.close(self.files.pop(h))
+ return ""
+
+ def handle_truncate(self, params):
+ h, size = struct.unpack('!II', params)
+ f = self.files[h]
+ os.ftruncate(f, size)
+ return ""
+
+ def handle(self, bbcall):
+ assert isinstance(bbcall, BBPacketFS)
+ logging.debug("bb-call: %s", bbcall)
+ fscall = RatpFSPacket(raw=bbcall.payload)
+ logging.info("fs-call: %s", fscall)
+
+ if not self.path:
+ logging.warning("no filesystem exported")
+ fsreturn = RatpFSPacket(type=RatpFSType.invalid)
+ elif fscall.type == RatpFSType.mount_call:
+ self.mounted = True
+ fsreturn = RatpFSPacket(type=RatpFSType.mount_return)
+ elif not self.mounted:
+ logging.warning("filesystem not mounted")
+ fsreturn = RatpFSPacket(type=RatpFSType.invalid)
+ elif fscall.type == RatpFSType.readdir_call:
+ payload = self.handle_readdir(fscall.payload)
+ fsreturn = RatpFSPacket(type=RatpFSType.readdir_return,
+ payload=payload)
+ elif fscall.type == RatpFSType.stat_call:
+ payload = self.handle_stat(fscall.payload)
+ fsreturn = RatpFSPacket(type=RatpFSType.stat_return,
+ payload=payload)
+ elif fscall.type == RatpFSType.open_call:
+ payload = self.handle_open(fscall.payload)
+ fsreturn = RatpFSPacket(type=RatpFSType.open_return,
+ payload=payload)
+ elif fscall.type == RatpFSType.read_call:
+ payload = self.handle_read(fscall.payload)
+ fsreturn = RatpFSPacket(type=RatpFSType.read_return,
+ payload=payload)
+ elif fscall.type == RatpFSType.write_call:
+ payload = self.handle_write(fscall.payload)
+ fsreturn = RatpFSPacket(type=RatpFSType.write_return,
+ payload=payload)
+ elif fscall.type == RatpFSType.close_call:
+ payload = self.handle_close(fscall.payload)
+ fsreturn = RatpFSPacket(type=RatpFSType.close_return,
+ payload=payload)
+ elif fscall.type == RatpFSType.truncate_call:
+ payload = self.handle_truncate(fscall.payload)
+ fsreturn = RatpFSPacket(type=RatpFSType.truncate_return,
+ payload=payload)
+ else:
+ raise RatpFSError()
+
+ logging.info("fs-return: %s", fsreturn)
+ bbreturn = BBPacketFSReturn(payload=fsreturn.pack())
+ logging.debug("bb-return: %s", bbreturn)
+ return bbreturn
diff --git a/scripts/remote/threadstdio.py b/scripts/remote/threadstdio.py
new file mode 100644
index 0000000000..db249892ac
--- /dev/null
+++ b/scripts/remote/threadstdio.py
@@ -0,0 +1,47 @@
+#!/usr/bin/python2
+
+import os
+import sys
+import termios
+import atexit
+from threading import Thread
+from Queue import Queue, Empty
+
+class ConsoleInput(Thread):
+ def __init__(self, queue, exit='\x14'):
+ Thread.__init__(self)
+ self.daemon = True
+ self.q = queue
+ self._exit = exit
+ self.fd = sys.stdin.fileno()
+ old = termios.tcgetattr(self.fd)
+ new = termios.tcgetattr(self.fd)
+ new[3] = new[3] & ~termios.ICANON & ~termios.ECHO & ~termios.ISIG
+ new[6][termios.VMIN] = 1
+ new[6][termios.VTIME] = 0
+ termios.tcsetattr(self.fd, termios.TCSANOW, new)
+
+ def cleanup():
+ termios.tcsetattr(self.fd, termios.TCSAFLUSH, old)
+ atexit.register(cleanup)
+
+ def run(self):
+ while True:
+ c = os.read(self.fd, 1)
+ if c == self._exit:
+ self.q.put((self, None))
+ return
+ else:
+ self.q.put((self, c))
+
+if __name__ == "__main__":
+ q = Queue()
+ i = ConsoleInput(q)
+ i.start()
+ while True:
+ event = q.get(block=True)
+ src, c = event
+ if c == '\x04':
+ break
+ os.write(sys.stdout.fileno(), c.upper())
+