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 | |
| 21 | import codecs |
| 22 | import serial |
| 23 | |
| 24 | HEXDIGITS = '0123456789ABCDEF' |
| 25 | |
Chris Liechti | c0c660a | 2015-08-25 00:55:51 +0200 | [diff] [blame] | 26 | |
Chris Liechti | 92df95a | 2016-02-09 23:30:37 +0100 | [diff] [blame] | 27 | # Codec APIs |
Chris Liechti | 033f17c | 2015-08-30 21:28:04 +0200 | [diff] [blame] | 28 | |
Chris Liechti | 4e34c4c | 2016-02-19 23:54:14 +0100 | [diff] [blame^] | 29 | def hex_encode(data, errors='strict'): |
| 30 | """'40 41 42' -> '@ab'""" |
| 31 | 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] | 32 | |
Chris Liechti | 033f17c | 2015-08-30 21:28:04 +0200 | [diff] [blame] | 33 | |
Chris Liechti | 4e34c4c | 2016-02-19 23:54:14 +0100 | [diff] [blame^] | 34 | def hex_decode(data, errors='strict'): |
| 35 | """'@ab' -> '40 41 42'""" |
| 36 | return (''.join('{:02X} '.format(b) for b in data), len(data)) |
Chris Liechti | c0c660a | 2015-08-25 00:55:51 +0200 | [diff] [blame] | 37 | |
Chris Liechti | 033f17c | 2015-08-30 21:28:04 +0200 | [diff] [blame] | 38 | |
Chris Liechti | c0c660a | 2015-08-25 00:55:51 +0200 | [diff] [blame] | 39 | class Codec(codecs.Codec): |
Chris Liechti | 4e34c4c | 2016-02-19 23:54:14 +0100 | [diff] [blame^] | 40 | def encode(self, data, errors='strict'): |
| 41 | """'40 41 42' -> '@ab'""" |
| 42 | return serial.to_bytes([int(h, 16) for h in data.split()]) |
Chris Liechti | 033f17c | 2015-08-30 21:28:04 +0200 | [diff] [blame] | 43 | |
Chris Liechti | 4e34c4c | 2016-02-19 23:54:14 +0100 | [diff] [blame^] | 44 | def decode(self, data, errors='strict'): |
| 45 | """'@ab' -> '40 41 42'""" |
| 46 | return ''.join('{:02X} '.format(b) for b in data) |
Chris Liechti | c0c660a | 2015-08-25 00:55:51 +0200 | [diff] [blame] | 47 | |
Chris Liechti | 033f17c | 2015-08-30 21:28:04 +0200 | [diff] [blame] | 48 | |
Chris Liechti | c0c660a | 2015-08-25 00:55:51 +0200 | [diff] [blame] | 49 | class IncrementalEncoder(codecs.IncrementalEncoder): |
Chris Liechti | 4e34c4c | 2016-02-19 23:54:14 +0100 | [diff] [blame^] | 50 | """Incremental hex encoder""" |
Chris Liechti | c0c660a | 2015-08-25 00:55:51 +0200 | [diff] [blame] | 51 | |
| 52 | def __init__(self, errors='strict'): |
| 53 | self.errors = errors |
| 54 | self.state = 0 |
| 55 | |
| 56 | def reset(self): |
| 57 | self.state = 0 |
| 58 | |
| 59 | def getstate(self): |
| 60 | return self.state |
| 61 | |
| 62 | def setstate(self, state): |
| 63 | self.state = state |
| 64 | |
Chris Liechti | 4e34c4c | 2016-02-19 23:54:14 +0100 | [diff] [blame^] | 65 | def encode(self, data, final=False): |
| 66 | """\ |
| 67 | Incremental encode, keep track of digits and emit a byte when a pair |
| 68 | of hex digits is found. The space is optional unless the error |
| 69 | handling is defined to be 'strict'. |
| 70 | """ |
Chris Liechti | c0c660a | 2015-08-25 00:55:51 +0200 | [diff] [blame] | 71 | state = self.state |
| 72 | encoded = [] |
Chris Liechti | 4e34c4c | 2016-02-19 23:54:14 +0100 | [diff] [blame^] | 73 | for c in data.upper(): |
Chris Liechti | c0c660a | 2015-08-25 00:55:51 +0200 | [diff] [blame] | 74 | if c in HEXDIGITS: |
| 75 | z = HEXDIGITS.index(c) |
| 76 | if state: |
| 77 | encoded.append(z + (state & 0xf0)) |
| 78 | state = 0 |
| 79 | else: |
| 80 | state = 0x100 + (z << 4) |
| 81 | elif c == ' ': # allow spaces to separate values |
| 82 | if state and self.errors == 'strict': |
| 83 | raise UnicodeError('odd number of hex digits') |
| 84 | state = 0 |
| 85 | else: |
| 86 | if self.errors == 'strict': |
| 87 | raise UnicodeError('non-hex digit found: %r' % c) |
| 88 | self.state = state |
| 89 | return serial.to_bytes(encoded) |
| 90 | |
Chris Liechti | 033f17c | 2015-08-30 21:28:04 +0200 | [diff] [blame] | 91 | |
Chris Liechti | c0c660a | 2015-08-25 00:55:51 +0200 | [diff] [blame] | 92 | class IncrementalDecoder(codecs.IncrementalDecoder): |
Chris Liechti | 4e34c4c | 2016-02-19 23:54:14 +0100 | [diff] [blame^] | 93 | """Incremental decoder""" |
| 94 | def decode(self, data, final=False): |
| 95 | return ''.join('{:02X} '.format(b) for b in data) |
Chris Liechti | c0c660a | 2015-08-25 00:55:51 +0200 | [diff] [blame] | 96 | |
Chris Liechti | 033f17c | 2015-08-30 21:28:04 +0200 | [diff] [blame] | 97 | |
Chris Liechti | c0c660a | 2015-08-25 00:55:51 +0200 | [diff] [blame] | 98 | class StreamWriter(Codec, codecs.StreamWriter): |
Chris Liechti | 4e34c4c | 2016-02-19 23:54:14 +0100 | [diff] [blame^] | 99 | """Combination of hexlify codec and StreamWriter""" |
Chris Liechti | c0c660a | 2015-08-25 00:55:51 +0200 | [diff] [blame] | 100 | |
Chris Liechti | 033f17c | 2015-08-30 21:28:04 +0200 | [diff] [blame] | 101 | |
Chris Liechti | c0c660a | 2015-08-25 00:55:51 +0200 | [diff] [blame] | 102 | class StreamReader(Codec, codecs.StreamReader): |
Chris Liechti | 4e34c4c | 2016-02-19 23:54:14 +0100 | [diff] [blame^] | 103 | """Combination of hexlify codec and StreamReader""" |
Chris Liechti | c0c660a | 2015-08-25 00:55:51 +0200 | [diff] [blame] | 104 | |
Chris Liechti | c0c660a | 2015-08-25 00:55:51 +0200 | [diff] [blame] | 105 | |
Chris Liechti | c0c660a | 2015-08-25 00:55:51 +0200 | [diff] [blame] | 106 | def getregentry(): |
Chris Liechti | 4e34c4c | 2016-02-19 23:54:14 +0100 | [diff] [blame^] | 107 | """encodings module API""" |
Chris Liechti | c0c660a | 2015-08-25 00:55:51 +0200 | [diff] [blame] | 108 | return codecs.CodecInfo( |
| 109 | name='hexlify', |
| 110 | encode=hex_encode, |
| 111 | decode=hex_decode, |
| 112 | incrementalencoder=IncrementalEncoder, |
| 113 | incrementaldecoder=IncrementalDecoder, |
| 114 | streamwriter=StreamWriter, |
| 115 | streamreader=StreamReader, |
| 116 | _is_text_encoding=True, |
| 117 | ) |