From 456e1fba4471face764888a9bb40dfd3d30ae080 Mon Sep 17 00:00:00 2001 From: slush0 Date: Mon, 4 Apr 2016 23:40:05 +0200 Subject: [PATCH] Pipe support & async pipe transport --- src/lib/uasyncio/core.py | 42 +++------ src/lib/uasyncio/epoll.py | 73 ---------------- src/lib/uasyncio/queues.py | 94 -------------------- src/lib/uasyncio/stream.py | 139 ----------------------------- src/lib_linux/errno.py | 38 ++++++++ src/lib_linux/ffilib.py | 46 ++++++++++ src/lib_linux/os.py | 22 ++++- src/lib_linux/select.py | 90 +++++++++++++++++++ src/lib_linux/stat.py | 149 ++++++++++++++++++++++++++++++++ src/lib_linux/transport_pipe.py | 141 ++++++++++++++++++++++++++++++ src/playground/__init__.py | 19 +++- src/trezor/msg2.py | 18 ++++ 12 files changed, 530 insertions(+), 341 deletions(-) delete mode 100644 src/lib/uasyncio/epoll.py delete mode 100644 src/lib/uasyncio/queues.py delete mode 100644 src/lib/uasyncio/stream.py create mode 100644 src/lib_linux/errno.py create mode 100644 src/lib_linux/ffilib.py create mode 100644 src/lib_linux/select.py create mode 100644 src/lib_linux/stat.py create mode 100644 src/lib_linux/transport_pipe.py create mode 100644 src/trezor/msg2.py diff --git a/src/lib/uasyncio/core.py b/src/lib/uasyncio/core.py index cf73ad44fa..d643e06c00 100644 --- a/src/lib/uasyncio/core.py +++ b/src/lib/uasyncio/core.py @@ -13,14 +13,6 @@ class EventLoop: self.q = [] self.cnt = 0 self.last_sleep = 0 # For performance stats - #self.button_cb = None - - ''' - def create_task(self, coro): - # CPython 3.4.2 - self.call_at(0, coro) - # CPython asyncio incompatibility: we don't return Task object - ''' def call_soon(self, callback, *args): self.call_at(0, callback, *args) @@ -78,27 +70,21 @@ class EventLoop: delay = arg elif isinstance(ret, StopLoop): return arg - ''' - elif isinstance(ret, IORead): - self.add_reader(ret.obj.fileno(), lambda self, c, f: self.call_soon(c, f), self, cb, ret.obj) - self.add_reader(ret.obj.fileno(), lambda c, f: self.call_soon(c, f), cb, ret.obj) - self.add_reader(arg.fileno(), lambda cb: self.call_soon(cb), cb) - self.add_reader(arg.fileno(), cb) - continue - elif isinstance(ret, IOWrite): - self.add_writer(arg.fileno(), lambda cb: self.call_soon(cb), cb) - self.add_writer(arg.fileno(), cb) - continue - elif isinstance(ret, IOReadDone): - self.remove_reader(arg.fileno()) - elif isinstance(ret, IOWriteDone): - self.remove_writer(arg.fileno()) - ''' + # elif isinstance(ret, IORead): + # self.add_reader(arg.fileno(), lambda self, c, f: self.call_soon(c, f), self, cb, arg) + # self.add_reader(arg.fileno(), lambda c, f: self.call_soon(c, f), cb, arg) + # self.add_reader(arg.fileno(), lambda cb: self.call_soon(cb), cb) + # self.add_reader(arg.fileno(), cb) + # continue + # elif isinstance(ret, IOWrite): + # self.add_writer(arg.fileno(), lambda cb: self.call_soon(cb), cb) + # self.add_writer(arg.fileno(), cb) + # continue + # elif isinstance(ret, IOReadDone): + # self.remove_reader(arg.fileno()) + # elif isinstance(ret, IOWriteDone): + # self.remove_writer(arg.fileno()) - #elif isinstance(ret, IOButton): - # print("TADY") - # self.button_cb = cb - # continue elif isinstance(ret, type_gen): self.call_soon(ret) elif ret is None: diff --git a/src/lib/uasyncio/epoll.py b/src/lib/uasyncio/epoll.py deleted file mode 100644 index dd2d328c57..0000000000 --- a/src/lib/uasyncio/epoll.py +++ /dev/null @@ -1,73 +0,0 @@ -import uselect -import errno - -from .core import EventLoop - -if __debug__: - import logging - log = logging.getLogger("asyncio") - -class EpollEventLoop(EventLoop): - - def __init__(self): - EventLoop.__init__(self) - self.poller = uselect.poll() - self.objmap = {} - - def add_reader(self, fd, cb, *args): - if __debug__: - log.debug("add_reader%s", (fd, cb, args)) - if args: - self.poller.register(fd, uselect.POLLIN) - self.objmap[fd] = (cb, args) - else: - self.poller.register(fd, uselect.POLLIN) - self.objmap[fd] = cb - - def remove_reader(self, fd): - if __debug__: - log.debug("remove_reader(%s)", fd) - self.poller.unregister(fd) - del self.objmap[fd] - - def add_writer(self, fd, cb, *args): - if __debug__: - log.debug("add_writer%s", (fd, cb, args)) - if args: - self.poller.register(fd, uselect.POLLOUT) - self.objmap[fd] = (cb, args) - else: - self.poller.register(fd, uselect.POLLOUT) - self.objmap[fd] = cb - - def remove_writer(self, fd): - if __debug__: - log.debug("remove_writer(%s)", fd) - try: - self.poller.unregister(fd) - self.objmap.pop(fd, None) - except OSError as e: - # StreamWriter.awrite() first tries to write to an fd, - # and if that succeeds, yield IOWrite may never be called - # for that fd, and it will never be added to poller. So, - # ignore such error. - if e.args[0] != errno.ENOENT: - raise - - def wait(self, delay): - if __debug__: - log.debug("epoll.wait(%d)", delay) - # We need one-shot behavior (second arg of 1 to .poll()) - if delay == -1: - res = self.poller.poll(-1, 1) - else: - res = self.poller.poll(int(delay * 1000), 1) - # log.debug("epoll result: %s", res) - for fd, ev in res: - cb = self.objmap[fd] - if __debug__: - log.debug("Calling IO callback: %r", cb) - if isinstance(cb, tuple): - cb[0](*cb[1]) - else: - self.call_soon(cb) diff --git a/src/lib/uasyncio/queues.py b/src/lib/uasyncio/queues.py deleted file mode 100644 index 4a8ae5fe53..0000000000 --- a/src/lib/uasyncio/queues.py +++ /dev/null @@ -1,94 +0,0 @@ -from collections.deque import deque -from uasyncio.core import sleep - - -class QueueEmpty(Exception): - """Exception raised by get_nowait().""" - - -class QueueFull(Exception): - """Exception raised by put_nowait().""" - - -class Queue: - """A queue, useful for coordinating producer and consumer coroutines. - - If maxsize is less than or equal to zero, the queue size is infinite. If it - is an integer greater than 0, then "yield from put()" will block when the - queue reaches maxsize, until an item is removed by get(). - - Unlike the standard library Queue, you can reliably know this Queue's size - with qsize(), since your single-threaded uasyncio application won't be - interrupted between calling qsize() and doing an operation on the Queue. - """ - _attempt_delay = 0.1 - - def __init__(self, maxsize=0): - self.maxsize = maxsize - self._queue = deque() - - def _get(self): - return self._queue.popleft() - - def get(self): - """Returns generator, which can be used for getting (and removing) - an item from a queue. - - Usage:: - - item = yield from queue.get() - """ - while not self._queue: - yield from sleep(self._attempt_delay) - return self._get() - - def get_nowait(self): - """Remove and return an item from the queue. - - Return an item if one is immediately available, else raise QueueEmpty. - """ - if not self._queue: - raise QueueEmpty() - return self._get() - - def _put(self, val): - self._queue.append(val) - - def put(self, val): - """Returns generator which can be used for putting item in a queue. - - Usage:: - - yield from queue.put(item) - """ - while self.qsize() > self.maxsize and self.maxsize: - yield from sleep(self._attempt_delay) - self._put(val) - - def put_nowait(self, val): - """Put an item into the queue without blocking. - - If no free slot is immediately available, raise QueueFull. - """ - if self.qsize() >= self.maxsize and self.maxsize: - raise QueueFull() - self._put(val) - - def qsize(self): - """Number of items in the queue.""" - return len(self._queue) - - def empty(self): - """Return True if the queue is empty, False otherwise.""" - return not self._queue - - def full(self): - """Return True if there are maxsize items in the queue. - - Note: if the Queue was initialized with maxsize=0 (the default), - then full() is never True. - """ - if self.maxsize <= 0: - return False - else: - return self.qsize() >= self.maxsize diff --git a/src/lib/uasyncio/stream.py b/src/lib/uasyncio/stream.py deleted file mode 100644 index 1edf849b85..0000000000 --- a/src/lib/uasyncio/stream.py +++ /dev/null @@ -1,139 +0,0 @@ -import usocket -import errno -from .core import IOReadDone, IOWriteDone, IORead, IOWrite - -if __debug__: - import logging - log = logging.getLogger("asyncio") - -class StreamReader: - - def __init__(self, s): - self.s = s - - def read(self, n=-1): - yield IORead(self.s) - while True: - res = self.s.read(n) - if res is not None: - break - log.warn("Empty read") - if not res: - yield IOReadDone(self.s) - return res - - def readline(self): - if __debug__: - log.debug("StreamReader.readline()") - yield IORead(self.s) -# if __debug__: -# log.debug("StreamReader.readline(): after IORead: %s", s) - while True: - res = self.s.readline() - if res is not None: - break - log.warn("Empty read") - if not res: - yield IOReadDone(self.s) - if __debug__: - log.debug("StreamReader.readline(): res: %s", res) - return res - - def aclose(self): - yield IOReadDone(self.s) - self.s.close() - - def __repr__(self): - return "" % self.s - - -class StreamWriter: - - def __init__(self, s, extra): - self.s = s - self.extra = extra - - def awrite(self, buf): - # This method is called awrite (async write) to not proliferate - # incompatibility with original asyncio. Unlike original asyncio - # whose .write() method is both not a coroutine and guaranteed - # to return immediately (which means it has to buffer all the - # data), this method is a coroutine. - sz = len(buf) - if __debug__: - log.debug("StreamWriter.awrite(): spooling %d bytes", sz) - while True: - res = self.s.write(buf) - # If we spooled everything, return immediately - if res == sz: - if __debug__: - log.debug("StreamWriter.awrite(): completed spooling %d bytes", res) - return - if res is None: - res = 0 - if __debug__: - log.debug("StreamWriter.awrite(): spooled partial %d bytes", res) - assert res < sz - buf = buf[res:] - sz -= res - yield IOWrite(self.s) - # assert s2.fileno() == self.s.fileno() - if __debug__: - log.debug("StreamWriter.awrite(): can write more") - - def aclose(self): - yield IOWriteDone(self.s) - self.s.close() - - def get_extra_info(self, name, default=None): - return self.extra.get(name, default) - - def __repr__(self): - return "" % self.s - - -def open_connection(host, port): - if __debug__: - log.debug("open_connection(%s, %s)", host, port) - s = usocket.socket() - s.setblocking(False) - ai = usocket.getaddrinfo(host, port) - addr = ai[0][4] - try: - s.connect(addr) - except OSError as e: - if e.args[0] != errno.EINPROGRESS: - raise - if __debug__: - log.debug("open_connection: After connect") - yield IOWrite(s) -# if __debug__: -# assert s2.fileno() == s.fileno() - if __debug__: - log.debug("open_connection: After iowait: %s", s) - return StreamReader(s), StreamWriter(s, {}) - - -def start_server(client_coro, host, port, backlog=10): - if __debug__: - log.debug("start_server(%s, %s)", host, port) - s = usocket.socket() - s.setblocking(False) - - ai = usocket.getaddrinfo(host, port) - addr = ai[0][4] - s.setsockopt(usocket.SOL_SOCKET, usocket.SO_REUSEADDR, 1) - s.bind(addr) - s.listen(backlog) - while True: - if __debug__: - log.debug("start_server: Before accept") - yield IORead(s) - if __debug__: - log.debug("start_server: After iowait") - s2, client_addr = s.accept() - s2.setblocking(False) - if __debug__: - log.debug("start_server: After accept: %s", s2) - extra = {"peername": client_addr} - yield client_coro(StreamReader(s2), StreamWriter(s2, extra)) diff --git a/src/lib_linux/errno.py b/src/lib_linux/errno.py new file mode 100644 index 0000000000..7b7935ef8b --- /dev/null +++ b/src/lib_linux/errno.py @@ -0,0 +1,38 @@ +EPERM = 1 # Operation not permitted +ENOENT = 2 # No such file or directory +ESRCH = 3 # No such process +EINTR = 4 # Interrupted system call +EIO = 5 # I/O error +ENXIO = 6 # No such device or address +E2BIG = 7 # Argument list too long +ENOEXEC = 8 # Exec format error +EBADF = 9 # Bad file number +ECHILD = 10 # No child processes +EAGAIN = 11 # Try again +ENOMEM = 12 # Out of memory +EACCES = 13 # Permission denied +EFAULT = 14 # Bad address +ENOTBLK = 15 # Block device required +EBUSY = 16 # Device or resource busy +EEXIST = 17 # File exists +EXDEV = 18 # Cross-device link +ENODEV = 19 # No such device +ENOTDIR = 20 # Not a directory +EISDIR = 21 # Is a directory +EINVAL = 22 # Invalid argument +ENFILE = 23 # File table overflow +EMFILE = 24 # Too many open files +ENOTTY = 25 # Not a typewriter +ETXTBSY = 26 # Text file busy +EFBIG = 27 # File too large +ENOSPC = 28 # No space left on device +ESPIPE = 29 # Illegal seek +EROFS = 30 # Read-only file system +EMLINK = 31 # Too many links +EPIPE = 32 # Broken pipe +EDOM = 33 # Math argument out of domain of func +ERANGE = 34 # Math result not representable +EAFNOSUPPORT = 97 # Address family not supported by protocol +ECONNRESET = 104 # Connection timed out +ETIMEDOUT = 110 # Connection timed out +EINPROGRESS = 115 # Operation now in progress diff --git a/src/lib_linux/ffilib.py b/src/lib_linux/ffilib.py new file mode 100644 index 0000000000..dc4d672a28 --- /dev/null +++ b/src/lib_linux/ffilib.py @@ -0,0 +1,46 @@ +import sys +try: + import ffi +except ImportError: + ffi = None + +_cache = {} + +def open(name, maxver=10, extra=()): + if not ffi: + return None + try: + return _cache[name] + except KeyError: + pass + def libs(): + if sys.platform == "linux": + yield '%s.so' % name + for i in range(maxver, -1, -1): + yield '%s.so.%u' % (name, i) + else: + for ext in ('dylib', 'dll'): + yield '%s.%s' % (name, ext) + for n in extra: + yield n + err = None + for n in libs(): + try: + l = ffi.open(n) + _cache[name] = l + return l + except OSError as e: + err = e + raise err + +def libc(): + return open("libc", 6) + +# Find out bitness of the platform, even if long ints are not supported +# TODO: All bitness differences should be removed from micropython-lib, and +# this snippet too. +bitness = 1 +v = sys.maxsize +while v: + bitness += 1 + v >>= 1 diff --git a/src/lib_linux/os.py b/src/lib_linux/os.py index 1645a2cfcb..557e71c734 100644 --- a/src/lib_linux/os.py +++ b/src/lib_linux/os.py @@ -41,6 +41,9 @@ if libc: opendir_ = libc.func("P", "opendir", "s") readdir_ = libc.func("P", "readdir", "P") open_ = libc.func("i", "open", "sii") + fdopen_ = libc.func("i", "fdopen", "sii") + fsync_ = libc.func("i", "fsync", "i") + #lseek = libc.func("i", "lseek", "iii") read_ = libc.func("i", "read", "ipi") write_ = libc.func("i", "write", "iPi") close_ = libc.func("i", "close", "i") @@ -48,6 +51,7 @@ if libc: access_ = libc.func("i", "access", "si") fork_ = libc.func("i", "fork", "") pipe_ = libc.func("i", "pipe", "p") + mkfifo_ = libc.func("i", "mkfifo", "si") _exit_ = libc.func("v", "_exit", "i") getpid_ = libc.func("i", "getpid", "") waitpid_ = libc.func("i", "waitpid", "ipi") @@ -56,9 +60,6 @@ if libc: kill_ = libc.func("i", "kill", "ii") getenv_ = libc.func("s", "getenv", "P") - - - def check_error(ret): # Return True is error was EINTR (which usually means that OS call # should be restarted). @@ -163,6 +164,11 @@ def open(n, flags, mode=0o777): check_error(r) return r +def fdopen(n, flags, mode=0o777): + r = fdopen_(n, flags, mode) + check_error(r) + return r + def read(fd, n): buf = bytearray(n) r = read_(fd, buf, n) @@ -174,6 +180,11 @@ def write(fd, buf): check_error(r) return r +def fsync(fd): + r = fsync_(fd) + check_error(r) + return r + def close(fd): r = close_(fd) check_error(r) @@ -202,6 +213,11 @@ def pipe(): check_error(r) return a[0], a[1] +def mkfifo(n, mode=0o777): + r = mkfifo_(n, mode) + check_error(r) + return r + def _exit(n): _exit_(n) diff --git a/src/lib_linux/select.py b/src/lib_linux/select.py new file mode 100644 index 0000000000..e7de109df3 --- /dev/null +++ b/src/lib_linux/select.py @@ -0,0 +1,90 @@ +import ffi +import ustruct as struct +import os +import errno +import ffilib + + +libc = ffilib.libc() + +#int epoll_create(int size); +epoll_create = libc.func("i", "epoll_create", "i") +#int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event); +epoll_ctl = libc.func("i", "epoll_ctl", "iiiP") +#int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout); +epoll_wait = libc.func("i", "epoll_wait", "ipii") + +EPOLLIN = 0x001 +EPOLLPRI = 0x002 +EPOLLOUT = 0x004 +EPOLLERR = 0x008 +EPOLLHUP = 0x010 +EPOLLRDHUP = 0x2000 +EPOLLONESHOT = 1 << 30 +EPOLLET = 1 << 31 + +EPOLL_CTL_ADD = 1 +EPOLL_CTL_DEL = 2 +EPOLL_CTL_MOD = 3 + +# TODO: struct epoll_event's 2nd member is union of uint64_t, etc. +# On x86, uint64_t is 4-byte aligned, on many other platforms - 8-byte. +# Until uctypes module can assign native struct offset, use dirty hack +# below. +# TODO: Get rid of all this dirtiness, move it on C side +if ffilib.bitness > 32: + # On x86_64, epoll_event is packed struct + epoll_event = " 0: + vals = struct.unpack(epoll_event, s) + res.append((vals[1], vals[0])) + return res + + def close(self): + os.close(self.epfd) + + +def epoll(sizehint=4): + fd = epoll_create(sizehint) + os.check_error(fd) + return Epoll(fd) diff --git a/src/lib_linux/stat.py b/src/lib_linux/stat.py new file mode 100644 index 0000000000..704adfe2e1 --- /dev/null +++ b/src/lib_linux/stat.py @@ -0,0 +1,149 @@ +"""Constants/functions for interpreting results of os.stat() and os.lstat(). + +Suggested usage: from stat import * +""" + +# Indices for stat struct members in the tuple returned by os.stat() + +ST_MODE = 0 +ST_INO = 1 +ST_DEV = 2 +ST_NLINK = 3 +ST_UID = 4 +ST_GID = 5 +ST_SIZE = 6 +ST_ATIME = 7 +ST_MTIME = 8 +ST_CTIME = 9 + +# Extract bits from the mode + +def S_IMODE(mode): + """Return the portion of the file's mode that can be set by + os.chmod(). + """ + return mode & 0o7777 + +def S_IFMT(mode): + """Return the portion of the file's mode that describes the + file type. + """ + return mode & 0o170000 + +# Constants used as S_IFMT() for various file types +# (not all are implemented on all systems) + +S_IFDIR = 0o040000 # directory +S_IFCHR = 0o020000 # character device +S_IFBLK = 0o060000 # block device +S_IFREG = 0o100000 # regular file +S_IFIFO = 0o010000 # fifo (named pipe) +S_IFLNK = 0o120000 # symbolic link +S_IFSOCK = 0o140000 # socket file + +# Functions to test for each file type + +def S_ISDIR(mode): + """Return True if mode is from a directory.""" + return S_IFMT(mode) == S_IFDIR + +def S_ISCHR(mode): + """Return True if mode is from a character special device file.""" + return S_IFMT(mode) == S_IFCHR + +def S_ISBLK(mode): + """Return True if mode is from a block special device file.""" + return S_IFMT(mode) == S_IFBLK + +def S_ISREG(mode): + """Return True if mode is from a regular file.""" + return S_IFMT(mode) == S_IFREG + +def S_ISFIFO(mode): + """Return True if mode is from a FIFO (named pipe).""" + return S_IFMT(mode) == S_IFIFO + +def S_ISLNK(mode): + """Return True if mode is from a symbolic link.""" + return S_IFMT(mode) == S_IFLNK + +def S_ISSOCK(mode): + """Return True if mode is from a socket.""" + return S_IFMT(mode) == S_IFSOCK + +# Names for permission bits + +S_ISUID = 0o4000 # set UID bit +S_ISGID = 0o2000 # set GID bit +S_ENFMT = S_ISGID # file locking enforcement +S_ISVTX = 0o1000 # sticky bit +S_IREAD = 0o0400 # Unix V7 synonym for S_IRUSR +S_IWRITE = 0o0200 # Unix V7 synonym for S_IWUSR +S_IEXEC = 0o0100 # Unix V7 synonym for S_IXUSR +S_IRWXU = 0o0700 # mask for owner permissions +S_IRUSR = 0o0400 # read by owner +S_IWUSR = 0o0200 # write by owner +S_IXUSR = 0o0100 # execute by owner +S_IRWXG = 0o0070 # mask for group permissions +S_IRGRP = 0o0040 # read by group +S_IWGRP = 0o0020 # write by group +S_IXGRP = 0o0010 # execute by group +S_IRWXO = 0o0007 # mask for others (not in group) permissions +S_IROTH = 0o0004 # read by others +S_IWOTH = 0o0002 # write by others +S_IXOTH = 0o0001 # execute by others + +# Names for file flags + +UF_NODUMP = 0x00000001 # do not dump file +UF_IMMUTABLE = 0x00000002 # file may not be changed +UF_APPEND = 0x00000004 # file may only be appended to +UF_OPAQUE = 0x00000008 # directory is opaque when viewed through a union stack +UF_NOUNLINK = 0x00000010 # file may not be renamed or deleted +UF_COMPRESSED = 0x00000020 # OS X: file is hfs-compressed +UF_HIDDEN = 0x00008000 # OS X: file should not be displayed +SF_ARCHIVED = 0x00010000 # file may be archived +SF_IMMUTABLE = 0x00020000 # file may not be changed +SF_APPEND = 0x00040000 # file may only be appended to +SF_NOUNLINK = 0x00100000 # file may not be renamed or deleted +SF_SNAPSHOT = 0x00200000 # file is a snapshot file + + +_filemode_table = ( + ((S_IFLNK, "l"), + (S_IFREG, "-"), + (S_IFBLK, "b"), + (S_IFDIR, "d"), + (S_IFCHR, "c"), + (S_IFIFO, "p")), + + ((S_IRUSR, "r"),), + ((S_IWUSR, "w"),), + ((S_IXUSR|S_ISUID, "s"), + (S_ISUID, "S"), + (S_IXUSR, "x")), + + ((S_IRGRP, "r"),), + ((S_IWGRP, "w"),), + ((S_IXGRP|S_ISGID, "s"), + (S_ISGID, "S"), + (S_IXGRP, "x")), + + ((S_IROTH, "r"),), + ((S_IWOTH, "w"),), + ((S_IXOTH|S_ISVTX, "t"), + (S_ISVTX, "T"), + (S_IXOTH, "x")) +) + +def filemode(mode): + """Convert a file's mode to a string of the form '-rwxrwxrwx'.""" + perm = [] + for table in _filemode_table: + for bit, char in table: + if mode & bit == bit: + perm.append(char) + break + else: + perm.append("-") + return "".join(perm) diff --git a/src/lib_linux/transport_pipe.py b/src/lib_linux/transport_pipe.py new file mode 100644 index 0000000000..eb1ace2939 --- /dev/null +++ b/src/lib_linux/transport_pipe.py @@ -0,0 +1,141 @@ +'''PipeTransport implements fake wire transport over local named pipe. +Use this transport for talking with trezor simulator.''' + +import os +import ustruct +import uselect + +from uasyncio import core + +read_fd = None +write_fd = None + +poll = None +on_read = None + +def init(filename): + global read_fd, write_fd, poll + + filename_read = filename + '.to' + filename_write = filename + '.from' + + os.mkfifo(filename_read, 0o600) + os.mkfifo(filename_write, 0o600) + + write_fd = os.open(filename_write, os.O_RDWR, 0o600) + read_fd = os.open(filename_read, os.O_RDWR, 0o600) + + poll = uselect.poll() + poll.register(read_fd, uselect.POLLIN) + + # Setup polling + loop = core.get_event_loop() + loop.call_soon(watch_read()) + +def set_notify(_on_read): + global on_read + on_read = _on_read + +def close(): + global read_fd, write_fd + + os.close(read_fd) + os.close(write_fd) + +def watch_read(): + global on_read + sleep = core.Sleep(0.01) + while True: + if ready_to_read() and on_read: + on_read() + + yield sleep + +def ready_to_read(): + global poll + return len(poll.poll(0)) > 0 + +def read(): + """ + If there is data available to be read from the transport, reads the data and tries to parse it as a protobuf message. If the parsing succeeds, return a protobuf object. + Otherwise, returns None. + """ + + if not ready_to_read(): + return None + + data = _read() + if data == None: + return None + + return _parse_message(data) + +def write(msg): + """ + Write mesage to tansport. msg should be a member of a valid `protobuf class `_ with a SerializeToString() method. + """ + ser = msg.SerializeToString() + header = ustruct.pack(">HL", mapping.get_type(msg), len(ser)) + + _write(b"##%s%s" % (header, ser)) + +def _parse_message(data): + (msg_type, _data) = data + if msg_type == 'protobuf': + return _data + else: + # inst = mapping.get_class(msg_type)() + # inst.ParseFromString(_data) + inst = _data + return inst + +def _read_headers(): + global read_fd + + # Try to read headers until some sane value are detected + is_ok = False + while not is_ok: + + # Align cursor to the beginning of the header ("##") + c = os.read(read_fd, 1) + i = 0 + while c != b'#': + i += 1 + if i >= 64: + # timeout + raise Exception("Timed out while waiting for the magic character") + # print "Aligning to magic characters" + c = os.read(read_fd, 1) + print(c) + + if os.read(read_fd, 1) != b'#': + # Second character must be # to be valid header + raise Exception("Second magic character is broken") + + # Now we're most likely on the beginning of the header + try: + headerlen = ustruct.calcsize(">HL") + (msg_type, datalen) = ustruct.unpack(">HL", os.read(read_fd, headerlen)) + break + except: + raise Exception("Cannot parse header length") + + return (msg_type, datalen) + +def _write(msg): + global write_fd + try: + os.write(write_fd, msg) + # os.fsync(write_fd) + except OSError: + print("Error while writing to socket") + raise + +def _read(): + global read_fd + try: + (msg_type, datalen) = _read_headers() + return (msg_type, os.read(read_fd, datalen)) + except: + print("Failed to read from device") + raise diff --git a/src/playground/__init__.py b/src/playground/__init__.py index bfa1fdf504..fbcbbc1fdc 100644 --- a/src/playground/__init__.py +++ b/src/playground/__init__.py @@ -2,19 +2,26 @@ import sys sys.path.append('lib') +if sys.platform == 'linux': + # Packages used only on linux platform (named pipes, ...) + sys.path.append('lib_linux') + import utime import math import gc from uasyncio import core -from trezor import ui +# import transport_pipe as pipe + +from trezor import ui, io +from trezor import msg2 as msg logging.basicConfig(level=logging.INFO) loop = core.get_event_loop() def perf_info(): mem_free = gc.mem_free() - # gc.collect() + gc.collect() print("free_mem: %s/%s, last_sleep: %.06f" % \ (mem_free, gc.mem_free(), loop.last_sleep)) loop.call_later(1, perf_info) @@ -87,9 +94,13 @@ def tap_to_confirm(): yield core.Sleep(DELAY) +def on_read(): + print("READY TO READ") + print(msg.read()) + def run(): - # sekunda(3) - # loop.call_soon(wait_for()) + # pipe.init('../pipe', on_read) + msg.set_notify(on_read) loop.call_soon(perf_info) loop.call_soon(tap_to_confirm()) diff --git a/src/trezor/msg2.py b/src/trezor/msg2.py new file mode 100644 index 0000000000..b4cbbaa9b1 --- /dev/null +++ b/src/trezor/msg2.py @@ -0,0 +1,18 @@ +import sys + +if sys.platform == 'linux': + import transport_pipe as pipe + + def write(msg): + return pipe.write(msg) + + def read(): + return pipe.read() + + def set_notify(_on_read): + return pipe.set_notify(_on_read) + + pipe.init('../pipe') + +else: + NotImplemented("HID transport")