diff --git a/.gitignore b/.gitignore index 46fb7fb..affae6e 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,6 @@ *.pyc /dist /xmodem.egg-info +/.coverage +/.tox +*.py.swp diff --git a/.travis.yml b/.travis.yml index d0a2c03..7520ce9 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,10 +1,23 @@ language: python -python: - - "2.6" - - "2.7" -# install required packages + +env: + - TOXENV=py26 + - TOXENV=py27 + - TOXENV=py33 + - TOXENV=pypy + - TOXENV=py34 + install: - - sudo apt-get install lrzsz -# command to run tests + - pip install -q tox coveralls + - sudo apt-get install -qq -y lrzsz + script: - - make tests + - tox -e $TOXENV + +after_success: + - coveralls + +notifications: + email: + - contact@jeffquast.com + - maze@pyth0n.org diff --git a/Makefile b/Makefile index fa25e10..84700ea 100644 --- a/Makefile +++ b/Makefile @@ -1,7 +1,5 @@ tests: - PYTHONPATH=. python test/test.py test/test.py - PYTHONPATH=. python test/test-recv.py - PYTHONPATH=. python test/test-send.py + tox upload: python setup.py sdist upload diff --git a/requirements-testing.txt b/requirements-testing.txt new file mode 100644 index 0000000..93cf5f7 --- /dev/null +++ b/requirements-testing.txt @@ -0,0 +1,3 @@ +pytest-cov +pytest +tox diff --git a/test/functional/__init__.py b/test/functional/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/test/functional/accessories.py b/test/functional/accessories.py new file mode 100644 index 0000000..f271c52 --- /dev/null +++ b/test/functional/accessories.py @@ -0,0 +1,29 @@ +import subprocess + + +def _multi_which(prog_names): + for prog_name in prog_names: + proc = subprocess.Popen(('which', prog_name), stdout=subprocess.PIPE) + stdout, stderr = proc.communicate() + if proc.returncode == 0: + return stdout.strip() + return None + + +def _get_recv_program(): + bin_path = _multi_which(('rb', 'lrb')) + assert bin_path is not None, ( + "program required: {0!r}. " + "Try installing lrzsz package.".format(bin_path)) + return bin_path + + +def _get_send_program(): + bin_path = _multi_which(('sb', 'lsb')) + assert bin_path is not None, ( + "program required: {0!r}. " + "Try installing lrzsz package.".format(bin_path)) + return bin_path + +recv_prog = _get_recv_program() +send_prog = _get_send_program() diff --git a/test/functional/test-recv.py b/test/functional/test-recv.py deleted file mode 100644 index 1313156..0000000 --- a/test/functional/test-recv.py +++ /dev/null @@ -1,42 +0,0 @@ -import select -import subprocess -import sys -import StringIO -from xmodem import XMODEM - -if __name__ == '__main__': - pipe = subprocess.Popen(['sx', '--xmodem', __file__], - stdin=subprocess.PIPE, stdout=subprocess.PIPE) - si, so = (pipe.stdin, pipe.stdout) - - def getc(size, timeout=3): - w,t,f = select.select([so], [], [], timeout) - if w: - data = so.read(size) - else: - data = None - - print 'getc(', repr(data), ')' - return data - - def putc(data, timeout=3): - w,t,f = select.select([], [si], [], timeout) - if t: - si.write(data) - si.flush() - size = len(data) - else: - size = None - - print 'putc(', repr(data), repr(size), ')' - return size - - stream = StringIO.StringIO() - xmodem = XMODEM(getc, putc) - nbytes = xmodem.recv(stream, retry=8) - - print >> sys.stderr, 'received', nbytes, 'bytes' - print >> sys.stderr, stream.getvalue() - - sys.exit(int(nbytes == 0)) - diff --git a/test/functional/test-send.py b/test/functional/test-send.py deleted file mode 100644 index cd42f80..0000000 --- a/test/functional/test-send.py +++ /dev/null @@ -1,48 +0,0 @@ -import os -import select -import subprocess -import sys -import StringIO -import tempfile -from xmodem import XMODEM - -if __name__ == '__main__': - fd, fn = tempfile.mkstemp() - pipe = subprocess.Popen(['rx', '--xmodem', fn], - stdin=subprocess.PIPE, stdout=subprocess.PIPE) - si, so = (pipe.stdin, pipe.stdout) - - def getc(size, timeout=3): - w,t,f = select.select([so], [], [], timeout) - if w: - data = so.read(size) - else: - data = None - - print 'getc(', repr(data), ')' - return data - - def putc(data, timeout=3): - w,t,f = select.select([], [si], [], timeout) - if t: - si.write(data) - si.flush() - size = len(data) - else: - size = None - - print 'putc(', repr(data), repr(size), ')' - return size - - stream = open(__file__, 'rb') - xmodem = XMODEM(getc, putc) - status = xmodem.send(stream, retry=8) - stream.close() - - print >> sys.stderr, 'sent', status - print >> sys.stderr, file(fn).read() - - os.unlink(fn) - - sys.exit(int(not status)) - diff --git a/test/functional/test_xmodem.py b/test/functional/test_xmodem.py new file mode 100644 index 0000000..45a446d --- /dev/null +++ b/test/functional/test_xmodem.py @@ -0,0 +1,274 @@ +# std imports +from __future__ import print_function +import os +import sys +import errno +import select +import logging +import tempfile +import functools +import subprocess +try: + # python 3 + from io import BytesIO +except ImportError: + # python 2 + import StringIO.StringIO as BytesIO + +# local +from xmodem import XMODEM, XMODEM1k +from .accessories import recv_prog, send_prog + +logging.basicConfig(format='%(levelname)-5s %(message)s', + level=logging.DEBUG) + + +CHUNKSIZE = 521 + + +def _fill_binary_data(stream): + for byte in range(0x00, 0xff + 1): + stream.write(bytearray([byte] * CHUNKSIZE)) + stream.seek(0) + return stream + + +def _verify_binary_data(stream, padding=b'\xff'): + stream.seek(0) + for byte in range(0x00, 0xff + 1): + assert stream.read(CHUNKSIZE) == bytearray([byte] * CHUNKSIZE) + while True: + try: + # BSD-style EOF + data = stream.read(1) + assert data in (b'', padding) + if data == b'': + # BSD-style EOF + break + except OSError as err: + # Linux-style EOF + assert err.errno == errno.EIO + + +def _proc_getc(size, timeout=1, proc=None): + # our getc function simply pipes to the standard out of the `rb' + # or `lrb' program -- any data written by such program is returned + # by our getc() callback. + assert proc.returncode is None, ("{0} has exited: (returncode={1})" + .format(proc, proc.returncode)) + logging.debug(('get', size)) + ready_read, _, _ = select.select([proc.stdout], [], [], timeout) + if not ready_read: + assert False, ("Timeout on stdout of {0}.".format(proc)) + data = proc.stdout.read(size) + logging.debug(('got', len(data), data)) + return data + + +def _proc_putc(data, timeout=1, proc=None): + # similarly, our putc function simply writes to the standard of + # our `rb' or `lrb' program -- any data written by our XMODEM + # protocol via putc() callback is written to the stdin of such + # program. + assert proc.returncode is None, ("{0} has exited: (returncode={1})" + .format(proc, proc.returncode)) + _, ready_write, _ = select.select([], [proc.stdin], [], timeout) + if not ready_write: + assert False, ("Timeout on stdin of {0}.".format(proc)) + logging.debug(('put', len(data), data)) + proc.stdin.write(data) + proc.stdin.flush() + return len(data) + + +def _send_callback(total_packets, success_count, error_count): + # this simple callback simply asserts that no errors have occurred, and + # prints the given status to stderr. This is captured but displayed in + # py.test output only on error. + assert error_count == 0 + assert success_count == total_packets + print('{0}'.format(total_packets), file=sys.stderr) + + +def test_xmodem_send(): + """ Using external program for receive, verify XMODEM.send(). """ + # Given, + _, recv_filename = tempfile.mkstemp() + try: + proc = subprocess.Popen( + (recv_prog, '--xmodem', '--verbose', recv_filename), + stdin=subprocess.PIPE, stdout=subprocess.PIPE, bufsize=0) + + getc = functools.partial(_proc_getc, proc=proc) + putc = functools.partial(_proc_putc, proc=proc) + + xmodem = XMODEM(getc, putc, pad=b'\xbb') + stream = _fill_binary_data(BytesIO()) + + # Exercise, + status = xmodem.send(stream, timeout=5, callback=_send_callback) + + # Verify, + assert status is True + _verify_binary_data(stream) + _verify_binary_data(open(recv_filename, 'rb'), padding=b'\xbb') + proc.wait() + assert proc.returncode == 0 + + finally: + if os.path.isfile(recv_filename): + os.unlink(recv_filename) + + +def test_xmodem_recv(): + """ Using external program for send, verify XMODEM.recv(). """ + # Given, + _, send_filename = tempfile.mkstemp() + try: + with open(send_filename, 'wb') as stream: + _fill_binary_data(stream) + proc = subprocess.Popen( + (send_prog, '--xmodem', '--verbose', send_filename), + stdin=subprocess.PIPE, stdout=subprocess.PIPE, bufsize=0) + + getc = functools.partial(_proc_getc, proc=proc) + putc = functools.partial(_proc_putc, proc=proc) + + xmodem = XMODEM(getc, putc, pad=b'\xbb') + recv_stream = BytesIO() + + # Exercise, + status = xmodem.recv(recv_stream, timeout=5) + + # Verify, + assert status == recv_stream.tell() + _verify_binary_data(recv_stream, padding=b'\xbb') + proc.wait() + assert proc.returncode == 0 + + finally: + os.unlink(send_filename) + + +def test_xmodem1k_send(): + """ Using external program for receive, verify XMODEM1k.send(). """ + # Given, + _, recv_filename = tempfile.mkstemp() + try: + proc = subprocess.Popen( + (recv_prog, '--xmodem', '--verbose', recv_filename), + stdin=subprocess.PIPE, stdout=subprocess.PIPE, bufsize=0) + + getc = functools.partial(_proc_getc, proc=proc) + putc = functools.partial(_proc_putc, proc=proc) + + xmodem = XMODEM1k(getc, putc, pad=b'\xbb') + stream = _fill_binary_data(BytesIO()) + + # Exercise, + status = xmodem.send(stream, timeout=5, callback=_send_callback) + + # Verify, + assert status is True + _verify_binary_data(stream) + _verify_binary_data(open(recv_filename, 'rb'), padding=b'\xbb') + proc.wait() + assert proc.returncode == 0 + + finally: + os.unlink(recv_filename) + + +def test_xmodem1k_recv(): + """ Using external program for send, verify XMODEM1k.recv(). """ + # Given, + _, send_filename = tempfile.mkstemp() + try: + with open(send_filename, 'wb') as stream: + _fill_binary_data(stream) + proc = subprocess.Popen( + (send_prog, '--xmodem', '--verbose', '--1k', send_filename), + stdin=subprocess.PIPE, stdout=subprocess.PIPE, bufsize=0) + + getc = functools.partial(_proc_getc, proc=proc) + putc = functools.partial(_proc_putc, proc=proc) + + xmodem = XMODEM1k(getc, putc, pad=b'\xbb') + recv_stream = BytesIO() + + # Exercise, + status = xmodem.recv(recv_stream, timeout=5) + + # Verify, + assert status == recv_stream.tell() + _verify_binary_data(recv_stream, padding=b'\xbb') + proc.wait() + assert proc.returncode == 0 + + finally: + if os.path.isfile(send_filename): + os.unlink(send_filename) + + +def test_xmodem_send_16bit_crc(): + """ + Using external program for receive, verify XMODEM.send() with 16-bit CRC. + """ + # Given, + _, recv_filename = tempfile.mkstemp() + try: + proc = subprocess.Popen( + (recv_prog, '--xmodem', '--verbose', '--with-crc', recv_filename), + stdin=subprocess.PIPE, stdout=subprocess.PIPE, bufsize=0) + + getc = functools.partial(_proc_getc, proc=proc) + putc = functools.partial(_proc_putc, proc=proc) + + xmodem = XMODEM(getc, putc, pad=b'\xbb') + stream = _fill_binary_data(BytesIO()) + + # Exercise, + status = xmodem.send(stream, timeout=5, callback=_send_callback) + + # Verify, + assert status is True + _verify_binary_data(stream) + _verify_binary_data(open(recv_filename, 'rb'), padding=b'\xbb') + proc.wait() + assert proc.returncode == 0 + + finally: + if os.path.isfile(recv_filename): + os.unlink(recv_filename) + + +def test_xmodem_recv_oldstyle_checksum(): + """ + Using external program for send, verify XMODEM.recv() with crc_mode 0. + """ + # Given, + _, send_filename = tempfile.mkstemp() + try: + with open(send_filename, 'wb') as stream: + _fill_binary_data(stream) + proc = subprocess.Popen( + (send_prog, '--xmodem', '--verbose', send_filename), + stdin=subprocess.PIPE, stdout=subprocess.PIPE, bufsize=0) + + getc = functools.partial(_proc_getc, proc=proc) + putc = functools.partial(_proc_putc, proc=proc) + + xmodem = XMODEM(getc, putc, pad=b'\xbb') + recv_stream = BytesIO() + + # Exercise, + status = xmodem.recv(recv_stream, timeout=5, crc_mode=0) + + # Verify, + assert status == recv_stream.tell() + _verify_binary_data(recv_stream, padding=b'\xbb') + proc.wait() + assert proc.returncode == 0 + + finally: + os.unlink(send_filename) diff --git a/test/unit/test_xmodem.py b/test/unit/test_xmodem.py index cce6111..3b724d5 100644 --- a/test/unit/test_xmodem.py +++ b/test/unit/test_xmodem.py @@ -1,8 +1,13 @@ """ Unit tests for XMODEM protocol. """ -# std -from StringIO import StringIO +# std imports +try: + # python 3 + from io import BytesIO +except ImportError: + # python 2 + import StringIO.StringIO as BytesIO # local import xmodem @@ -26,7 +31,7 @@ def test_xmodem_dummy_fails_send(mode): putc=dummy_putc, mode=mode) # exercise - status = modem.send(StringIO(b'dummy-stream')) + status = modem.send(BytesIO(b'dummy-stream')) # verify assert not status, ("Expected value of status `False'") @@ -39,4 +44,4 @@ def test_xmodem_bad_mode(): mode=mode) # exercise with pytest.raises(ValueError): - status = modem.send(StringIO(b'dummy-stream')) + status = modem.send(BytesIO(b'dummy-stream')) diff --git a/tox.ini b/tox.ini new file mode 100644 index 0000000..37ca48f --- /dev/null +++ b/tox.ini @@ -0,0 +1,13 @@ +[tox] +envlist = py26, + py27, + py33, + py34, + pypy + +skip_missing_interpreters = true + +[testenv] +usedevelop=true +deps=-rrequirements-testing.txt +commands = py.test --verbose --verbose --cov xmodem {posargs} diff --git a/xmodem/__init__.py b/xmodem/__init__.py index 1ec04ac..591289a 100644 --- a/xmodem/__init__.py +++ b/xmodem/__init__.py @@ -106,6 +106,7 @@ ''' +from __future__ import division, print_function __author__ = 'Wijnand Modderman ' __copyright__ = ['Copyright (c) 2010 Wijnand Modderman', @@ -113,20 +114,21 @@ __license__ = 'MIT' __version__ = '0.3.3' +import platform import logging import time import sys from functools import partial # Protocol bytes -SOH = chr(0x01) -STX = chr(0x02) -EOT = chr(0x04) -ACK = chr(0x06) -DLE = chr(0x10) -NAK = chr(0x15) -CAN = chr(0x18) -CRC = chr(0x43) # C +SOH = b'\x01' +STX = b'\x02' +EOT = b'\x04' +ACK = b'\x06' +DLE = b'\x10' +NAK = b'\x15' +CAN = b'\x18' +CRC = b'C' class XMODEM(object): @@ -190,7 +192,7 @@ class XMODEM(object): 0x6e17, 0x7e36, 0x4e55, 0x5e74, 0x2e93, 0x3eb2, 0x0ed1, 0x1ef0, ] - def __init__(self, getc, putc, mode='xmodem', pad='\x1a'): + def __init__(self, getc, putc, mode='xmodem', pad=b'\x1a'): self.getc = getc self.putc = putc self.mode = mode @@ -201,7 +203,7 @@ def abort(self, count=2, timeout=60): ''' Send an abort sequence using CAN bytes. ''' - for counter in xrange(0, count): + for _ in range(count): self.putc(CAN, timeout) def send(self, stream, retry=16, timeout=60, quiet=False, callback=None): @@ -209,7 +211,7 @@ def send(self, stream, retry=16, timeout=60, quiet=False, callback=None): Send a stream via the XMODEM protocol. >>> stream = file('/etc/issue', 'rb') - >>> print modem.send(stream) + >>> print(modem.send(stream)) True Returns ``True`` upon successful transmission or ``False`` in case of @@ -244,6 +246,7 @@ def callback(total_packets, success_count, error_count) raise ValueError("Invalid mode specified: {self.mode!r}" .format(self=self)) + self.log.debug('Begin start sequence, packet_size=%d', packet_size) error_count = 0 crc_mode = 0 cancel = 0 @@ -251,27 +254,31 @@ def callback(total_packets, success_count, error_count) char = self.getc(1) if char: if char == NAK: + self.log.debug('standard checksum requested (NAK).') crc_mode = 0 break elif char == CRC: + self.log.debug('16-bit CRC requested (CRC).') crc_mode = 1 break elif char == CAN: if not quiet: - print >> sys.stderr, 'received CAN' + print('received CAN', file=sys.stderr) if cancel: - self.log.info('Transmission canceled: ' - 'Received CAN twice.') + self.log.info('Transmission canceled: received 2xCAN ' + 'at start-sequence') return False else: + self.log.debug('cancellation at start sequence.') cancel = 1 else: - self.log.error('send ERROR expected NAK/CRC, ' - 'got %s', ord(char)) + self.log.error('send error: expected NAK, CRC, or CAN; ' + 'got %r', char) error_count += 1 if error_count >= retry: - self.log.info('error_count reached %s, aborting.', retry) + self.log.info('send error: error_count reached %d, ' + 'aborting.', retry) self.abort(timeout=timeout) return False @@ -283,31 +290,21 @@ def callback(total_packets, success_count, error_count) while True: data = stream.read(packet_size) if not data: - self.log.info('sending EOT') # end of stream + self.log.debug('send: at EOF') break total_packets += 1 + + header = self._make_send_header(packet_size, sequence) data = data.ljust(packet_size, self.pad) - if crc_mode: - crc = self.calc_crc(data) - else: - crc = self.calc_checksum(data) + checksum = self._make_send_checksum(crc_mode, data) # emit packet while True: - if packet_size == 128: - self.putc(SOH) - else: # packet_size == 1024 - self.putc(STX) - self.putc(chr(sequence)) - self.putc(chr(0xff - sequence)) + self.log.debug('send: block %d', sequence) + self.putc(header) self.putc(data) - if crc_mode: - self.putc(chr(crc >> 8)) - self.putc(chr(crc & 0xff)) - else: - self.putc(chr(crc)) - + self.putc(checksum) char = self.getc(1, timeout) if char == ACK: success_count += 1 @@ -315,28 +312,33 @@ def callback(total_packets, success_count, error_count) callback(total_packets, success_count, error_count) break if char == NAK: + self.log.warn('send error: NAK received ' + 'for block %d', sequence) error_count += 1 if callable(callback): callback(total_packets, success_count, error_count) if error_count >= retry: # excessive amounts of retransmissions requested, # abort transfer + self.log.error('send error: NAK received %d times, ' + 'aborting.', error_count) self.abort(timeout=timeout) - self.log.warning('excessive NAKs, transfer aborted') return False # return to loop and resend continue # protocol error + self.log.error('send error: expected ACK, NAK; got %r, ' + 'aborting.', char) self.abort(timeout=timeout) - self.log.error('protocol error (getc returned %r)', char) return False # keep track of sequence sequence = (sequence + 1) % 0x100 while True: + self.log.debug('sending EOT, awaiting ACK') # end of transmission self.putc(EOT) @@ -345,21 +347,42 @@ def callback(total_packets, success_count, error_count) if char == ACK: break else: + self.log.error('send error: expected ACK; got %r', char) error_count += 1 if error_count >= retry: + self.log.warn('EOT was not ACKd, aborting transfer') self.abort(timeout=timeout) - self.log.warning('EOT was not ACKd, transfer aborted') return False - self.log.info('Transmission successful.') + self.log.info('Transmission successful (ACK received).') return True + def _make_send_header(self, packet_size, sequence): + assert packet_size in (128, 1024), packet_size + _bytes = [] + if packet_size == 128: + _bytes.append(ord(SOH)) + elif packet_size == 1024: + _bytes.append(ord(STX)) + _bytes.extend([sequence, 0xff - sequence]) + return bytearray(_bytes) + + def _make_send_checksum(self, crc_mode, data): + _bytes = [] + if crc_mode: + crc = self.calc_crc(data) + _bytes.extend([crc >> 8, crc & 0xff]) + else: + crc = self.calc_checksum(data) + _bytes.append(crc) + return bytearray(_bytes) + def recv(self, stream, crc_mode=1, retry=16, timeout=60, delay=1, quiet=0): ''' Receive a stream via the XMODEM protocol. >>> stream = file('/etc/issue', 'wb') - >>> print modem.recv(stream) + >>> print(modem.recv(stream)) 2342 Returns the number of bytes received on success or ``None`` in case of @@ -377,30 +400,38 @@ def recv(self, stream, crc_mode=1, retry=16, timeout=60, delay=1, quiet=0): self.log.info('error_count reached %d, aborting.', retry) self.abort(timeout=timeout) return None - elif crc_mode and error_count < (retry / 2): + elif crc_mode and error_count < (retry // 2): if not self.putc(CRC): + self.log.debug('recv error: putc failed, ' + 'sleeping for %d', delay) time.sleep(delay) error_count += 1 else: crc_mode = 0 if not self.putc(NAK): + self.log.debug('recv error: putc failed, ' + 'sleeping for %d', delay) time.sleep(delay) error_count += 1 char = self.getc(1, timeout) - if not char: + if char is None: + self.log.warn('recv error: getc timeout in start sequence') error_count += 1 continue elif char == SOH: - #crc_mode = 0 + self.log.debug('recv: SOH') break elif char == STX: + self.log.debug('recv: STX') break elif char == CAN: if cancel: - self.log.info('Transmission canceled: Received CAN twice.') + self.log.info('Transmission canceled: received 2xCAN ' + 'at start-sequence') return None else: + self.log.debug('cancellation at start sequence.') cancel = 1 else: error_count += 1 @@ -414,10 +445,14 @@ def recv(self, stream, crc_mode=1, retry=16, timeout=60, delay=1, quiet=0): while True: while True: if char == SOH: - packet_size = 128 + if packet_size != 128: + self.log.debug('recv: SOH, using 128b packet_size') + packet_size = 128 break elif char == STX: - packet_size = 1024 + if packet_size != 1024: + self.log.debug('recv: SOH, using 1k packet_size') + packet_size = 1024 break elif char == EOT: # We received an EOT, so send an ACK and return the @@ -429,15 +464,18 @@ def recv(self, stream, crc_mode=1, retry=16, timeout=60, delay=1, quiet=0): elif char == CAN: # cancel at two consecutive cancels if cancel: - self.log.info('Transmission canceled: ' - 'Received CAN twice.') + self.log.info('Transmission canceled: received 2xCAN ' + 'at block %d', sequence) return None else: + self.log.debug('cancellation at block %d', sequence) cancel = 1 else: + err_msg = ('recv error: expected SOH, EOT; ' + 'got {0!r}'.format(char)) if not quiet: - print >> sys.stderr, \ - 'recv ERROR expected SOH/EOT, got', ord(char) + print(err_msg, file=sys.stderr) + self.log.warn(err_msg) error_count += 1 if error_count >= retry: self.log.info('error_count reached %d, aborting.', @@ -448,34 +486,33 @@ def recv(self, stream, crc_mode=1, retry=16, timeout=60, delay=1, quiet=0): # read sequence error_count = 0 cancel = 0 + self.log.debug('recv: block sequence') seq1 = self.getc(1, timeout) if seq1 is None: - self.log.warning('getc failed to get first sequence byte') + self.log.warn('getc failed to get first sequence byte') seq2 = None else: seq1 = ord(seq1) seq2 = self.getc(1, timeout) if seq2 is None: - self.log.warning('getc failed to get second sequence byte') + self.log.warn('getc failed to get second sequence byte') else: seq2 = 0xff - ord(seq2) - if seq1 == sequence and seq2 == sequence: + if not (seq1 == seq2 == sequence): + # consume data anyway ... even though we will discard it, + # it is not the sequence we expected! + self.log.error('expected sequence %d, ' + 'got (seq1=%r, seq2=%r), ' + 'receiving next block, will NAK.', + sequence, seq1, seq2) + self.getc(packet_size + 1 + crc_mode) + else: # sequence is ok, read packet # packet_size + checksum + self.log.debug('reading data block %d', sequence) data = self.getc(packet_size + 1 + crc_mode, timeout) - if crc_mode: - csum = (ord(data[-2]) << 8) + ord(data[-1]) - data = data[:-2] - self.log.debug('CRC (%04x <> %04x)', - csum, self.calc_crc(data)) - valid = csum == self.calc_crc(data) - else: - csum = data[-1] - data = data[:-1] - self.log.debug('checksum (checksum(%02x <> %02x)', - ord(csum), self.calc_checksum(data)) - valid = ord(csum) == self.calc_checksum(data) + valid, data = self._verify_recv_checksum(crc_mode, data) # valid data, append chunk if valid: @@ -483,17 +520,39 @@ def recv(self, stream, crc_mode=1, retry=16, timeout=60, delay=1, quiet=0): stream.write(data) self.putc(ACK) sequence = (sequence + 1) % 0x100 + # get next byte char = self.getc(1, timeout) continue - else: - # consume data - self.getc(packet_size + 1 + crc_mode) - self.log.debug('expecting sequence %d, got %s/%s', - sequence, seq1, seq2) # something went wrong, request retransmission - self.log.warning('error in recv(), requesting retransmission') + self.log.warn('recv error: requesting retransmission (NAK)') self.putc(NAK) + continue + + def _verify_recv_checksum(self, crc_mode, data): + if crc_mode: + _checksum = bytearray(data[-2:]) + their_sum = (_checksum[0] << 8) + _checksum[1] + data = data[:-2] + + our_sum = self.calc_crc(data) + valid = bool(their_sum == our_sum) + if not valid: + self.log.warn('recv error: checksum fail ' + '(theirs=%04x, ours=%04x), ', + their_sum, our_sum) + else: + _checksum = bytearray([data[-1]]) + their_sum = _checksum[0] + data = data[:-1] + + our_sum = self.calc_checksum(data) + valid = their_sum == our_sum + if not valid: + self.log.warn('recv error: checksum fail ' + '(theirs=%02x, ours=%02x)', + their_sum, our_sum) + return valid, data def calc_checksum(self, data, checksum=0): ''' @@ -506,7 +565,10 @@ def calc_checksum(self, data, checksum=0): '0x3c' ''' - return (sum(map(ord, data)) + checksum) % 256 + if platform.python_version_tuple() >= ('3', '0', '0'): + return (sum(data) + checksum) % 256 + else: + return (sum(map(ord, data)) + checksum) % 256 def calc_crc(self, data, crc=0): ''' @@ -519,8 +581,9 @@ def calc_crc(self, data, crc=0): '0xd5e3' ''' - for char in data: - crc = ((crc << 8) ^ self.crctable[((crc >> 8) ^ ord(char)) & 0xff]) & 0xffff + for char in bytearray(data): + crctbl_idx = ((crc >> 8) ^ char) & 0xff + crc = ((crc << 8) ^ self.crctable[crctbl_idx]) & 0xffff return crc & 0xffff @@ -548,8 +611,8 @@ def run(): def _func(so, si): import select - print 'si', si - print 'so', so + print(('si', si)) + print(('so', so)) def getc(size, timeout=3): read_ready, _, _ = select.select([so], [], [], timeout) @@ -558,7 +621,7 @@ def getc(size, timeout=3): else: data = None - print 'getc(', repr(data), ')' + print(('getc(', repr(data), ')')) return data def putc(data, timeout=3): @@ -570,7 +633,7 @@ def putc(data, timeout=3): else: size = None - print 'putc(', repr(data), repr(size), ')' + print(('putc(', repr(data), repr(size), ')')) return size return getc, putc