[PATCH 6/8] host side for barebox remote control
Sascha Hauer
s.hauer at pengutronix.de
Fri Jan 8 03:13:53 PST 2016
From: Jan Luebbe <jlu at pengutronix.de>
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 at pengutronix.de>
Signed-off-by: Sascha Hauer <s.hauer at pengutronix.de>
---
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
More information about the barebox
mailing list