9
0
Fork 0

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>
This commit is contained in:
Jan Luebbe 2015-06-05 09:18:56 +02:00 committed by Sascha Hauer
parent 06fc3557c9
commit be6c6e6536
9 changed files with 1535 additions and 0 deletions

3
scripts/bbremote Executable file
View File

@ -0,0 +1,3 @@
#!/usr/bin/env python2
import remote.main

View File

View File

@ -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()

168
scripts/remote/main.py Normal file
View File

@ -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)

154
scripts/remote/messages.py Normal file
View File

@ -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

28
scripts/remote/missing.py Normal file
View File

@ -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()

773
scripts/remote/ratp.py Normal file
View File

@ -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

189
scripts/remote/ratpfs.py Normal file
View File

@ -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

View File

@ -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())