async def listen(self): ''' Listen for open/close requests on configured interface. After open request, session is started and a new task is scheduled to handle it. After close request, the handling task is closed and session terminated. Both requests receive responses confirming the operation. ''' read = loop.select(self.iface.iface_num() | loop.READ) write = loop.select(self.iface.iface_num() | loop.WRITE) while True: report = await read repmarker, repsid = ustruct.unpack(_REP, report) # because tasks paused on I/O have a priority over time-scheduled # tasks, we need to `yield` explicitly before sending a response to # open/close request. Otherwise the handler would have no chance to # run and schedule communication. if repmarker == _REP_MARKER_OPEN: newsid = self.newsid() self.open(newsid) yield await write self.writeopen(newsid) elif repmarker == _REP_MARKER_CLOSE: self.close(repsid) yield await write self.writeclose(repsid)
async def send_cmd(cmd: Cmd, iface: io.HID) -> None: init_desc = frame_init() cont_desc = frame_cont() offset = 0 seq = 0 datalen = len(cmd.data) buf, frm = make_struct(init_desc) frm.cid = cmd.cid frm.cmd = cmd.cmd frm.bcnt = datalen offset += utils.memcpy(frm.data, 0, cmd.data, offset, datalen) iface.write(buf) if offset < datalen: frm = overlay_struct(buf, cont_desc) write = loop.select(iface.iface_num() | io.POLL_WRITE) while offset < datalen: frm.seq = seq offset += utils.memcpy(frm.data, 0, cmd.data, offset, datalen) while True: await write if iface.write(buf) > 0: break seq += 1
async def areadinto(self, buf): ''' Read exactly `len(buf)` bytes into `buf`, waiting for additional reports, if needed. Raises `EOFError` if end-of-message is encountered before the full read can be completed. ''' if self.size < len(buf): raise EOFError read = loop.select(self.iface.iface_num() | io.POLL_READ) nread = 0 while nread < len(buf): if self.ofs == len(self.data): # we are at the end of received data # wait for continuation report while True: report = await read marker = report[0] if marker == _REP_MARKER: break self.data = report[_REP_CONT_DATA:_REP_CONT_DATA + self.size] self.ofs = 0 # copy as much as possible to target buffer nbytes = utils.memcpy(buf, nread, self.data, self.ofs, len(buf)) nread += nbytes self.ofs += nbytes self.size -= nbytes return nread
async def awrite(self, buf): ''' Encode and write every byte from `buf`. Does not need to be called in case message has zero length. Raises `EOFError` if the length of `buf` exceeds the remaining message length. ''' if self.size < len(buf): raise EOFError write = loop.select(self.iface.iface_num() | io.POLL_WRITE) nwritten = 0 while nwritten < len(buf): # copy as much as possible to report buffer nbytes = utils.memcpy(self.data, self.ofs, buf, nwritten, len(buf)) nwritten += nbytes self.ofs += nbytes self.size -= nbytes if self.ofs == _REP_LEN: # we are at the end of the report, flush it await write self.iface.write(self.data) self.ofs = _REP_CONT_DATA return nwritten
async def __iter__(self): timeout = loop.sleep(1000 * 1000 * 1) touch = loop.select(io.TOUCH) wait_timeout = loop.wait(touch, timeout) wait_touch = loop.wait(touch) content = None self.back.taint() self.input.taint() while content is None: self.render() if self.pbutton is not None: wait = wait_timeout else: wait = wait_touch result = await wait if touch in wait.finished: event, *pos = result content = self.touch(event, pos) else: if self.input.word: # just reset the pending state self.edit(self.input.content) else: # invalid character, backspace it self.edit(self.input.content[:-1]) return content
def __iter__(self): touch = loop.select(io.TOUCH) result = None while result is None: self.render() event, *pos = yield touch result = self.touch(event, pos) return result
def _dispatch_reports(): read = loop.select(_interface) while True: report = yield read # if __debug__: # log.debug(__name__, 'read report %s', ubinascii.hexlify(report)) sessions.dispatch(memoryview(report), _session_open, _session_close, _session_unknown)
async def read_cmd(iface: io.HID) -> Cmd: desc_init = frame_init() desc_cont = frame_cont() read = loop.select(iface.iface_num() | io.POLL_READ) buf = await read ifrm = overlay_struct(buf, desc_init) bcnt = ifrm.bcnt data = ifrm.data datalen = len(data) seq = 0 if ifrm.cmd & _TYPE_MASK == _TYPE_CONT: # unexpected cont packet, abort current msg if __debug__: log.warning(__name__, '_TYPE_CONT') return None if datalen < bcnt: databuf = bytearray(bcnt) utils.memcpy(databuf, 0, data, 0, bcnt) data = databuf else: data = data[:bcnt] while datalen < bcnt: buf = await read cfrm = overlay_struct(buf, desc_cont) if cfrm.seq == _CMD_INIT: # _CMD_INIT frame, cancels current channel ifrm = overlay_struct(buf, desc_init) data = ifrm.data[:ifrm.bcnt] break if cfrm.cid != ifrm.cid: # cont frame for a different channel, reply with BUSY and skip if __debug__: log.warning(__name__, '_ERR_CHANNEL_BUSY') await send_cmd(cmd_error(cfrm.cid, _ERR_CHANNEL_BUSY), iface) continue if cfrm.seq != seq: # cont frame for this channel, but incorrect seq number, abort # current msg if __debug__: log.warning(__name__, '_ERR_INVALID_SEQ') await send_cmd(cmd_error(cfrm.cid, _ERR_INVALID_SEQ), iface) return None datalen += utils.memcpy(data, datalen, cfrm.data, 0, bcnt - datalen) seq += 1 return Cmd(ifrm.cid, ifrm.cmd, data)
async def click() -> tuple: touch = loop.select(io.TOUCH) while True: ev, *pos = yield touch if ev == io.TOUCH_START: break while True: ev, *pos = yield touch if ev == io.TOUCH_END: break return pos
def __iter__(self): timeout = loop.sleep(1000 * 1000 * 1) touch = loop.select(io.TOUCH) wait = loop.wait(touch, timeout) while True: self.render() result = yield wait if touch in wait.finished: event, *pos = result self.touch(event, pos) elif self.zoom_buttons: self.zoom_buttons = None for btn in self.key_buttons: btn.taint()
async def aclose(self): '''Flush and close the message transmission.''' if self.ofs != _REP_CONT_DATA: # we didn't write anything or last write() wasn't report-aligned, # pad the final report and flush it while self.ofs < _REP_LEN: self.data[self.ofs] = 0x00 self.ofs += 1 write = loop.select(self.iface.iface_num() | io.POLL_WRITE) while True: await write n = self.iface.write(self.data) if n == len(self.data): break
def __iter__(self): timeout = loop.sleep(1000 * 1000 * 1) touch = loop.select(io.TOUCH) wait = loop.wait(touch, timeout) while True: self.render() result = yield wait if touch in wait.finished: event, *pos = result self.touch(event, pos) else: self.pending_button = None self.pending_index = 0 self._update_suggestion() self._update_buttons()
async def aopen(self): ''' Begin the message transmission by waiting for initial V2 message report on this session. `self.type` and `self.size` are initialized and available after `aopen()` returns. ''' read = loop.select(self.iface.iface_num() | loop.READ) while True: # wait for initial report report = await read marker, sid, mtype, msize = ustruct.unpack(_REP_INIT, report) if sid == self.sid and marker == _REP_MARKER_INIT: break # load received message header self.type = mtype self.size = msize self.data = report[_REP_INIT_DATA:_REP_INIT_DATA + msize] self.ofs = 0 self.seq = 0
async def enter_text(self): timeout = loop.sleep(1000 * 1000 * 1) touch = loop.select(io.TOUCH) wait_timeout = loop.wait(touch, timeout) wait_touch = loop.wait(touch) content = None while content is None: self.render() if self.pbutton is not None: wait = wait_timeout else: wait = wait_touch result = await wait if touch in wait.finished: event, *pos = result content = self.touch(event, pos) else: # disable the pending buttons self.edit(self.input.content) return content
async def aopen(self): ''' Begin the message transmission by waiting for initial V2 message report on this session. `self.type` and `self.size` are initialized and available after `aopen()` returns. ''' read = loop.select(self.iface.iface_num() | io.POLL_READ) while True: # wait for initial report report = await read marker = report[0] if marker == _REP_MARKER: _, m1, m2, mtype, msize = ustruct.unpack(_REP_INIT, report) if m1 != _REP_MAGIC or m2 != _REP_MAGIC: raise ValueError break # load received message header self.type = mtype self.size = msize self.data = report[_REP_INIT_DATA:_REP_INIT_DATA + msize] self.ofs = 0
def test_reader(): rep_len = 64 interface_num = 0xdeadbeef message_type = 0x4321 message_len = 250 interface = MockHID(interface_num) reader = codec_v1.Reader(interface) message = bytearray(range(message_len)) report_header = bytearray(unhexlify('3f23234321000000fa')) # open, expected one read first_report = report_header + message[:rep_len - len(report_header)] assert_async(reader.aopen(), [ (None, select(io.POLL_READ | interface_num)), (first_report, StopIteration()), ]) assert_eq(reader.type, message_type) assert_eq(reader.size, message_len) # empty read empty_buffer = bytearray() assert_async(reader.areadinto(empty_buffer), [ (None, StopIteration()), ]) assert_eq(len(empty_buffer), 0) assert_eq(reader.size, message_len) # short read, expected no read short_buffer = bytearray(32) assert_async(reader.areadinto(short_buffer), [ (None, StopIteration()), ]) assert_eq(len(short_buffer), 32) assert_eq(short_buffer, message[:len(short_buffer)]) assert_eq(reader.size, message_len - len(short_buffer)) # aligned read, expected no read aligned_buffer = bytearray(rep_len - len(report_header) - len(short_buffer)) assert_async(reader.areadinto(aligned_buffer), [ (None, StopIteration()), ]) assert_eq(aligned_buffer, message[len(short_buffer):][:len(aligned_buffer)]) assert_eq(reader.size, message_len - len(short_buffer) - len(aligned_buffer)) # one byte read, expected one read next_report_header = bytearray(unhexlify('3f')) next_report = next_report_header + message[ rep_len - len(report_header):][:rep_len - len(next_report_header)] onebyte_buffer = bytearray(1) assert_async(reader.areadinto(onebyte_buffer), [ (None, select(io.POLL_READ | interface_num)), (next_report, StopIteration()), ]) assert_eq( onebyte_buffer, message[len(short_buffer):][len(aligned_buffer):] [:len(onebyte_buffer)]) assert_eq( reader.size, message_len - len(short_buffer) - len(aligned_buffer) - len(onebyte_buffer)) # too long read, raises eof assert_async(reader.areadinto(bytearray(reader.size + 1)), [ (None, EOFError()), ]) # long read, expect multiple reads start_size = reader.size long_buffer = bytearray(start_size) report_payload = message[rep_len - len(report_header) + rep_len - len(next_report_header):] report_payload_head = report_payload[:rep_len - len(next_report_header) - len(onebyte_buffer)] report_payload_rest = report_payload[len(report_payload_head):] report_payload_rest = list( chunks(report_payload_rest, rep_len - len(next_report_header))) report_payloads = [report_payload_head] + report_payload_rest next_reports = [next_report_header + r for r in report_payloads] expected_syscalls = [] for i, _ in enumerate(next_reports): prev_report = next_reports[i - 1] if i > 0 else None expected_syscalls.append( (prev_report, select(io.POLL_READ | interface_num))) expected_syscalls.append((next_reports[-1], StopIteration())) assert_async(reader.areadinto(long_buffer), expected_syscalls) assert_eq(long_buffer, message[-start_size:]) assert_eq(reader.size, 0) # one byte read, raises eof assert_async(reader.areadinto(onebyte_buffer), [ (None, EOFError()), ])
def test_writer(): rep_len = 64 interface_num = 0xdeadbeef message_type = 0x87654321 message_len = 1024 interface = MockHID(interface_num) writer = codec_v1.Writer(interface) writer.setheader(message_type, message_len) # init header corresponding to the data above report_header = bytearray(unhexlify('3f2323432100000400')) assert_eq(writer.data, report_header + bytearray(rep_len - len(report_header))) # empty write start_size = writer.size assert_async(writer.awrite(bytearray()), [ (None, StopIteration()), ]) assert_eq(writer.data, report_header + bytearray(rep_len - len(report_header))) assert_eq(writer.size, start_size) # short write, expected no report start_size = writer.size short_payload = bytearray(range(4)) assert_async(writer.awrite(short_payload), [ (None, StopIteration()), ]) assert_eq(writer.size, start_size - len(short_payload)) assert_eq( writer.data, report_header + short_payload + bytearray(rep_len - len(report_header) - len(short_payload))) # aligned write, expected one report start_size = writer.size aligned_payload = bytearray( range(rep_len - len(report_header) - len(short_payload))) assert_async(writer.awrite(aligned_payload), [ (None, select(io.POLL_WRITE | interface_num)), (None, StopIteration()), ]) assert_eq(interface.data, [ report_header + short_payload + aligned_payload + bytearray(rep_len - len(report_header) - len(short_payload) - len(aligned_payload)), ]) assert_eq(writer.size, start_size - len(aligned_payload)) interface.data.clear() # short write, expected no report, but data starts with correct seq and cont marker report_header = bytearray(unhexlify('3f')) start_size = writer.size assert_async(writer.awrite(short_payload), [ (None, StopIteration()), ]) assert_eq(writer.size, start_size - len(short_payload)) assert_eq(writer.data[:len(report_header) + len(short_payload)], report_header + short_payload) # long write, expected multiple reports start_size = writer.size long_payload_head = bytearray( range(rep_len - len(report_header) - len(short_payload))) long_payload_rest = bytearray(range(start_size - len(long_payload_head))) long_payload = long_payload_head + long_payload_rest expected_payloads = [short_payload + long_payload_head] + list( chunks(long_payload_rest, rep_len - len(report_header))) expected_reports = [report_header + r for r in expected_payloads] expected_reports[-1] += bytearray( bytes(1) * (rep_len - len(expected_reports[-1]))) # test write expected_write_reports = expected_reports[:-1] assert_async( writer.awrite(long_payload), len(expected_write_reports) * [(None, select(io.POLL_WRITE | interface_num))] + [(None, StopIteration())]) assert_eq(interface.data, expected_write_reports) assert_eq(writer.size, start_size - len(long_payload)) interface.data.clear() # test write raises eof assert_async(writer.awrite(bytearray(1)), [(None, EOFError())]) assert_eq(interface.data, []) # test close expected_close_reports = expected_reports[-1:] assert_async( writer.aclose(), len(expected_close_reports) * [(None, select(io.POLL_WRITE | interface_num))] + [(None, StopIteration())]) assert_eq(interface.data, expected_close_reports) assert_eq(writer.size, 0)