From: Jan Luebbe <jlu@xxxxxxxxxxxxxx> 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@xxxxxxxxxxxxxx> Signed-off-by: Sascha Hauer <s.hauer@xxxxxxxxxxxxxx> --- scripts/bbremote | 3 + scripts/remote/controller.py | 173 ++++++++++ scripts/remote/main.py | 169 +++++++++ scripts/remote/messages.py | 154 +++++++++ scripts/remote/missing.py | 28 ++ scripts/remote/ratp.py | 773 ++++++++++++++++++++++++++++++++++++++++++ scripts/remote/ratpfs.py | 189 +++++++++++ scripts/remote/threadstdio.py | 47 +++ 8 files changed, 1536 insertions(+) create mode 100755 scripts/bbremote create mode 100644 scripts/remote/controller.py create mode 100644 scripts/remote/main.py create mode 100644 scripts/remote/messages.py create mode 100644 scripts/remote/missing.py create mode 100644 scripts/remote/ratp.py create mode 100644 scripts/remote/ratpfs.py create mode 100644 scripts/remote/threadstdio.py diff --git a/scripts/bbremote b/scripts/bbremote new file mode 100755 index 0000000..bc5351d --- /dev/null +++ b/scripts/bbremote @@ -0,0 +1,3 @@ +#!/usr/bin/env python2 + +import remote.main diff --git a/scripts/remote/controller.py b/scripts/remote/controller.py new file mode 100644 index 0000000..a7257ec --- /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 0000000..9350151 --- /dev/null +++ b/scripts/remote/main.py @@ -0,0 +1,169 @@ +#!/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: + event = queue.get(block=True) + src, data = event + 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 0000000..8e8495b --- /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 0000000..67c2dfa --- /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 0000000..079fb87 --- /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 0000000..0333bf7 --- /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(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 0000000..db24989 --- /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()) + -- 2.6.4 _______________________________________________ barebox mailing list barebox@xxxxxxxxxxxxxxxxxxx http://lists.infradead.org/mailman/listinfo/barebox