This repository has been archived by the owner on Jan 13, 2023. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 125
/
codecs.py
215 lines (173 loc) · 6.26 KB
/
codecs.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
from codecs import Codec, CodecInfo, register as lookup_function
from typing import Union, Tuple
from warnings import warn
from iota.exceptions import with_context
__all__ = [
'AsciiTrytesCodec',
'TrytesDecodeError',
]
class TrytesDecodeError(ValueError):
"""
Indicates that a tryte string could not be decoded to bytes.
"""
pass
class AsciiTrytesCodec(Codec):
"""
Legacy codec for converting byte strings into trytes, and vice
versa.
This method encodes each pair of trytes as an ASCII code point (and
vice versa when decoding).
The end result requires more space than if the trytes were converted
mathematically, but because the result is ASCII, it's easier to work
with.
Think of this kind of like Base 64 for balanced ternary (:
"""
name = 'trytes_ascii'
compat_name = 'trytes'
"""
Old name for this codec.
Note: Will be removed in PyOTA v2.1!
"""
# :bc: Without the bytearray cast, Python 2 will populate the dict
# with characters instead of integers.
alphabet = dict(enumerate(bytearray(b'9ABCDEFGHIJKLMNOPQRSTUVWXYZ')))
"""
Used to encode bytes into trytes.
"""
index = dict(zip(alphabet.values(), alphabet.keys()))
"""
Used to decode trytes into bytes.
"""
@classmethod
def get_codec_info(cls) -> CodecInfo:
"""
Returns information used by the codecs library to configure the
codec for use.
"""
codec = cls()
codec_info = {
'encode': codec.encode,
'decode': codec.decode,
# In Python 2, all codecs are made equal.
# In Python 3, some codecs are more equal than others.
'_is_text_encoding': False
}
return CodecInfo(**codec_info)
def encode(self,
input: Union[memoryview, bytes, bytearray],
errors: str = 'strict') -> Tuple[bytes, int]:
"""
Encodes a byte string into trytes.
"""
if isinstance(input, memoryview):
input = input.tobytes()
if not isinstance(input, (bytes, bytearray)):
raise with_context(
exc=TypeError(
"Can't encode {type}; byte string expected.".format(
type=type(input).__name__,
)),
context={
'input': input,
},
)
# :bc: In Python 2, iterating over a byte string yields
# characters instead of integers.
if not isinstance(input, bytearray):
input = bytearray(input)
trytes = bytearray()
for c in input:
second, first = divmod(c, len(self.alphabet))
trytes.append(self.alphabet[first])
trytes.append(self.alphabet[second])
return bytes(trytes), len(input)
def decode(self,
input: Union[memoryview, bytes, bytearray],
errors: str = 'strict') -> Tuple[bytes, int]:
"""
Decodes a tryte string into bytes.
"""
if isinstance(input, memoryview):
input = input.tobytes()
if not isinstance(input, (bytes, bytearray)):
raise with_context(
exc=TypeError(
"Can't decode {type}; byte string expected.".format(
type=type(input).__name__,
)),
context={
'input': input,
},
)
# :bc: In Python 2, iterating over a byte string yields
# characters instead of integers.
if not isinstance(input, bytearray):
input = bytearray(input)
bytes_ = bytearray()
for i in range(0, len(input), 2):
try:
first, second = input[i:i + 2]
except ValueError:
if errors == 'strict':
raise with_context(
exc=TrytesDecodeError(
"'{name}' codec can't decode value; "
"tryte sequence has odd length.".format(
name=self.name,
),
),
context={
'input': input,
},
)
elif errors == 'replace':
bytes_ += b'?'
continue
try:
bytes_.append(
self.index[first]
+ (self.index[second] * len(self.index))
)
except ValueError:
# This combination of trytes yields a value > 255 when
# decoded.
# Naturally, we can't represent this using ASCII.
if errors == 'strict':
raise with_context(
exc=TrytesDecodeError(
"'{name}' codec can't decode trytes {pair} "
"at position {i}-{j}: "
"ordinal not in range(255)".format(
name=self.name,
pair=chr(first) + chr(second),
i=i,
j=i + 1,
),
),
context={
'input': input,
}
)
elif errors == 'replace':
bytes_ += b'?'
return bytes(bytes_), len(input)
@lookup_function
def check_trytes_codec(encoding):
"""
Determines which codec to use for the specified encoding.
References:
- https://docs.python.org/3/library/codecs.html#codecs.register
"""
if encoding == AsciiTrytesCodec.name:
return AsciiTrytesCodec.get_codec_info()
elif encoding == AsciiTrytesCodec.compat_name:
warn(
'"{old_codec}" codec will be removed in PyOTA v2.1. '
'Use "{new_codec}" instead.'.format(
new_codec=AsciiTrytesCodec.name,
old_codec=AsciiTrytesCodec.compat_name,
),
DeprecationWarning,
)
return AsciiTrytesCodec.get_codec_info()
return None