Chris Liechti | 3e02f70 | 2015-12-16 23:06:04 +0100 | [diff] [blame] | 1 | #! python |
| 2 | # |
| 3 | # This is a codec to create and decode hexdumps with spaces between characters. used by miniterm. |
| 4 | # |
| 5 | # This file is part of pySerial. https://github.com/pyserial/pyserial |
Chris Liechti | 4e34c4c | 2016-02-19 23:54:14 +0100 | [diff] [blame] | 6 | # (C) 2015-2016 Chris Liechti <cliechti@gmx.net> |
Chris Liechti | 3e02f70 | 2015-12-16 23:06:04 +0100 | [diff] [blame] | 7 | # |
| 8 | # SPDX-License-Identifier: BSD-3-Clause |
Chris Liechti | c0c660a | 2015-08-25 00:55:51 +0200 | [diff] [blame] | 9 | """\ |
| 10 | Python 'hex' Codec - 2-digit hex with spaces content transfer encoding. |
Chris Liechti | 4e34c4c | 2016-02-19 23:54:14 +0100 | [diff] [blame] | 11 | |
| 12 | Encode and decode may be a bit missleading at first sight... |
| 13 | |
| 14 | The textual representation is a hex dump: e.g. "40 41" |
| 15 | The "encoded" data of this is the binary form, e.g. b"@A" |
| 16 | |
| 17 | Therefore decoding is binary to text and thus converting binary data to hex dump. |
| 18 | |
Chris Liechti | c0c660a | 2015-08-25 00:55:51 +0200 | [diff] [blame] | 19 | """ |
| 20 | |
Kurt McKee | 057387c | 2018-02-07 22:10:38 -0600 | [diff] [blame] | 21 | from __future__ import absolute_import |
| 22 | |
Chris Liechti | c0c660a | 2015-08-25 00:55:51 +0200 | [diff] [blame] | 23 | import codecs |
| 24 | import serial |
| 25 | |
Chris Liechti | 7bb26e4 | 2016-03-08 22:59:48 +0100 | [diff] [blame] | 26 | |
| 27 | try: |
| 28 | unicode |
| 29 | except (NameError, AttributeError): |
| 30 | unicode = str # for Python 3, pylint: disable=redefined-builtin,invalid-name |
| 31 | |
| 32 | |
Chris Liechti | c0c660a | 2015-08-25 00:55:51 +0200 | [diff] [blame] | 33 | HEXDIGITS = '0123456789ABCDEF' |
| 34 | |
Chris Liechti | c0c660a | 2015-08-25 00:55:51 +0200 | [diff] [blame] | 35 | |
Chris Liechti | 92df95a | 2016-02-09 23:30:37 +0100 | [diff] [blame] | 36 | # Codec APIs |
Chris Liechti | 033f17c | 2015-08-30 21:28:04 +0200 | [diff] [blame] | 37 | |
Chris Liechti | 4e34c4c | 2016-02-19 23:54:14 +0100 | [diff] [blame] | 38 | def hex_encode(data, errors='strict'): |
Chris Liechti | 7bb26e4 | 2016-03-08 22:59:48 +0100 | [diff] [blame] | 39 | """'40 41 42' -> b'@ab'""" |
Chris Liechti | 4e34c4c | 2016-02-19 23:54:14 +0100 | [diff] [blame] | 40 | return (serial.to_bytes([int(h, 16) for h in data.split()]), len(data)) |
Chris Liechti | c0c660a | 2015-08-25 00:55:51 +0200 | [diff] [blame] | 41 | |
Chris Liechti | 033f17c | 2015-08-30 21:28:04 +0200 | [diff] [blame] | 42 | |
Chris Liechti | 4e34c4c | 2016-02-19 23:54:14 +0100 | [diff] [blame] | 43 | def hex_decode(data, errors='strict'): |
Chris Liechti | 7bb26e4 | 2016-03-08 22:59:48 +0100 | [diff] [blame] | 44 | """b'@ab' -> '40 41 42'""" |
| 45 | return (unicode(''.join('{:02X} '.format(ord(b)) for b in serial.iterbytes(data))), len(data)) |
Chris Liechti | c0c660a | 2015-08-25 00:55:51 +0200 | [diff] [blame] | 46 | |
Chris Liechti | 033f17c | 2015-08-30 21:28:04 +0200 | [diff] [blame] | 47 | |
Chris Liechti | c0c660a | 2015-08-25 00:55:51 +0200 | [diff] [blame] | 48 | class Codec(codecs.Codec): |
Chris Liechti | 4e34c4c | 2016-02-19 23:54:14 +0100 | [diff] [blame] | 49 | def encode(self, data, errors='strict'): |
Chris Liechti | 7bb26e4 | 2016-03-08 22:59:48 +0100 | [diff] [blame] | 50 | """'40 41 42' -> b'@ab'""" |
Chris Liechti | 4e34c4c | 2016-02-19 23:54:14 +0100 | [diff] [blame] | 51 | return serial.to_bytes([int(h, 16) for h in data.split()]) |
Chris Liechti | 033f17c | 2015-08-30 21:28:04 +0200 | [diff] [blame] | 52 | |
Chris Liechti | 4e34c4c | 2016-02-19 23:54:14 +0100 | [diff] [blame] | 53 | def decode(self, data, errors='strict'): |
Chris Liechti | 7bb26e4 | 2016-03-08 22:59:48 +0100 | [diff] [blame] | 54 | """b'@ab' -> '40 41 42'""" |
| 55 | return unicode(''.join('{:02X} '.format(ord(b)) for b in serial.iterbytes(data))) |
Chris Liechti | c0c660a | 2015-08-25 00:55:51 +0200 | [diff] [blame] | 56 | |
Chris Liechti | 033f17c | 2015-08-30 21:28:04 +0200 | [diff] [blame] | 57 | |
Chris Liechti | c0c660a | 2015-08-25 00:55:51 +0200 | [diff] [blame] | 58 | class IncrementalEncoder(codecs.IncrementalEncoder): |
Chris Liechti | 4e34c4c | 2016-02-19 23:54:14 +0100 | [diff] [blame] | 59 | """Incremental hex encoder""" |
Chris Liechti | c0c660a | 2015-08-25 00:55:51 +0200 | [diff] [blame] | 60 | |
| 61 | def __init__(self, errors='strict'): |
| 62 | self.errors = errors |
| 63 | self.state = 0 |
| 64 | |
| 65 | def reset(self): |
| 66 | self.state = 0 |
| 67 | |
| 68 | def getstate(self): |
| 69 | return self.state |
| 70 | |
| 71 | def setstate(self, state): |
| 72 | self.state = state |
| 73 | |
Chris Liechti | 4e34c4c | 2016-02-19 23:54:14 +0100 | [diff] [blame] | 74 | def encode(self, data, final=False): |
| 75 | """\ |
| 76 | Incremental encode, keep track of digits and emit a byte when a pair |
| 77 | of hex digits is found. The space is optional unless the error |
| 78 | handling is defined to be 'strict'. |
| 79 | """ |
Chris Liechti | c0c660a | 2015-08-25 00:55:51 +0200 | [diff] [blame] | 80 | state = self.state |
| 81 | encoded = [] |
Chris Liechti | 4e34c4c | 2016-02-19 23:54:14 +0100 | [diff] [blame] | 82 | for c in data.upper(): |
Chris Liechti | c0c660a | 2015-08-25 00:55:51 +0200 | [diff] [blame] | 83 | if c in HEXDIGITS: |
| 84 | z = HEXDIGITS.index(c) |
| 85 | if state: |
| 86 | encoded.append(z + (state & 0xf0)) |
| 87 | state = 0 |
| 88 | else: |
| 89 | state = 0x100 + (z << 4) |
| 90 | elif c == ' ': # allow spaces to separate values |
| 91 | if state and self.errors == 'strict': |
| 92 | raise UnicodeError('odd number of hex digits') |
| 93 | state = 0 |
| 94 | else: |
| 95 | if self.errors == 'strict': |
Chris Liechti | c8f3f82 | 2016-06-08 03:35:28 +0200 | [diff] [blame] | 96 | raise UnicodeError('non-hex digit found: {!r}'.format(c)) |
Chris Liechti | c0c660a | 2015-08-25 00:55:51 +0200 | [diff] [blame] | 97 | self.state = state |
| 98 | return serial.to_bytes(encoded) |
| 99 | |
Chris Liechti | 033f17c | 2015-08-30 21:28:04 +0200 | [diff] [blame] | 100 | |
Chris Liechti | c0c660a | 2015-08-25 00:55:51 +0200 | [diff] [blame] | 101 | class IncrementalDecoder(codecs.IncrementalDecoder): |
Chris Liechti | 4e34c4c | 2016-02-19 23:54:14 +0100 | [diff] [blame] | 102 | """Incremental decoder""" |
| 103 | def decode(self, data, final=False): |
Chris Liechti | 7bb26e4 | 2016-03-08 22:59:48 +0100 | [diff] [blame] | 104 | return unicode(''.join('{:02X} '.format(ord(b)) for b in serial.iterbytes(data))) |
Chris Liechti | c0c660a | 2015-08-25 00:55:51 +0200 | [diff] [blame] | 105 | |
Chris Liechti | 033f17c | 2015-08-30 21:28:04 +0200 | [diff] [blame] | 106 | |
Chris Liechti | c0c660a | 2015-08-25 00:55:51 +0200 | [diff] [blame] | 107 | class StreamWriter(Codec, codecs.StreamWriter): |
Chris Liechti | 4e34c4c | 2016-02-19 23:54:14 +0100 | [diff] [blame] | 108 | """Combination of hexlify codec and StreamWriter""" |
Chris Liechti | c0c660a | 2015-08-25 00:55:51 +0200 | [diff] [blame] | 109 | |
Chris Liechti | 033f17c | 2015-08-30 21:28:04 +0200 | [diff] [blame] | 110 | |
Chris Liechti | c0c660a | 2015-08-25 00:55:51 +0200 | [diff] [blame] | 111 | class StreamReader(Codec, codecs.StreamReader): |
Chris Liechti | 4e34c4c | 2016-02-19 23:54:14 +0100 | [diff] [blame] | 112 | """Combination of hexlify codec and StreamReader""" |
Chris Liechti | c0c660a | 2015-08-25 00:55:51 +0200 | [diff] [blame] | 113 | |
Chris Liechti | c0c660a | 2015-08-25 00:55:51 +0200 | [diff] [blame] | 114 | |
Chris Liechti | c0c660a | 2015-08-25 00:55:51 +0200 | [diff] [blame] | 115 | def getregentry(): |
Chris Liechti | 4e34c4c | 2016-02-19 23:54:14 +0100 | [diff] [blame] | 116 | """encodings module API""" |
Chris Liechti | c0c660a | 2015-08-25 00:55:51 +0200 | [diff] [blame] | 117 | return codecs.CodecInfo( |
| 118 | name='hexlify', |
| 119 | encode=hex_encode, |
| 120 | decode=hex_decode, |
| 121 | incrementalencoder=IncrementalEncoder, |
| 122 | incrementaldecoder=IncrementalDecoder, |
| 123 | streamwriter=StreamWriter, |
| 124 | streamreader=StreamReader, |
Chris Liechti | 7bb26e4 | 2016-03-08 22:59:48 +0100 | [diff] [blame] | 125 | #~ _is_text_encoding=True, |
Chris Liechti | c0c660a | 2015-08-25 00:55:51 +0200 | [diff] [blame] | 126 | ) |