Skip to content

Commit 1e5f030

Browse files
authored
Merge pull request #391 from andlaus/improve_encoding_fallbacks
improve the fallback code for some undefined encodings in non-strict mode
2 parents a2bff12 + bf32e80 commit 1e5f030

File tree

2 files changed

+98
-42
lines changed

2 files changed

+98
-42
lines changed

odxtools/decodestate.py

+35-18
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,9 @@ def extract_atomic_value(
103103

104104
# Deal with raw byte fields, ...
105105
if base_data_type == DataType.A_BYTEFIELD:
106-
odxassert(base_type_encoding in (None, Encoding.NONE, Encoding.BCD_P, Encoding.BCD_UP))
106+
odxassert(
107+
base_type_encoding in (None, Encoding.NONE, Encoding.BCD_P, Encoding.BCD_UP),
108+
f"Illegal encoding '{base_type_encoding}' for A_BYTEFIELD")
107109

108110
# note that we do not ensure that BCD-encoded byte fields
109111
# only represent "legal" values
@@ -149,28 +151,19 @@ def extract_atomic_value(
149151
odxraise(f"Illegal encoding ({base_type_encoding}) specified for "
150152
f"{base_data_type.value}")
151153

152-
internal_value = raw_value
154+
if base_type_encoding == Encoding.BCD_P:
155+
internal_value = self.__decode_bcd_p(raw_value)
156+
elif base_type_encoding == Encoding.BCD_UP:
157+
internal_value = self.__decode_bcd_up(raw_value)
158+
else:
159+
internal_value = raw_value
153160

154161
# ... unsigned integers, ...
155162
elif base_data_type == DataType.A_UINT32:
156163
if base_type_encoding == Encoding.BCD_P:
157-
# packed BCD
158-
tmp2 = raw_value
159-
internal_value = 0
160-
factor = 1
161-
while tmp2 > 0:
162-
internal_value += (tmp2 & 0xf) * factor
163-
factor *= 10
164-
tmp2 >>= 4
164+
internal_value = self.__decode_bcd_p(raw_value)
165165
elif base_type_encoding == Encoding.BCD_UP:
166-
# unpacked BCD
167-
tmp2 = raw_value
168-
internal_value = 0
169-
factor = 1
170-
while tmp2 > 0:
171-
internal_value += (tmp2 & 0xf) * factor
172-
factor *= 10
173-
tmp2 >>= 8
166+
internal_value = self.__decode_bcd_up(raw_value)
174167
elif base_type_encoding in (None, Encoding.NONE):
175168
# no encoding
176169
internal_value = raw_value
@@ -193,3 +186,27 @@ def extract_atomic_value(
193186
self.cursor_bit_position = 0
194187

195188
return internal_value
189+
190+
@staticmethod
191+
def __decode_bcd_p(value: int) -> int:
192+
# packed BCD
193+
result = 0
194+
factor = 1
195+
while value > 0:
196+
result += (value & 0xf) * factor
197+
factor *= 10
198+
value >>= 4
199+
200+
return result
201+
202+
@staticmethod
203+
def __decode_bcd_up(value: int) -> int:
204+
# unpacked BCD
205+
result = 0
206+
factor = 1
207+
while value > 0:
208+
result += (value & 0xf) * factor
209+
factor *= 10
210+
value >>= 8
211+
212+
return result

odxtools/encodestate.py

+63-24
Original file line numberDiff line numberDiff line change
@@ -101,17 +101,26 @@ def emplace_atomic_value(
101101
odxraise(f"{internal_value!r} is not a bytefield", EncodeError)
102102
return
103103

104-
odxassert(base_type_encoding in (None, Encoding.NONE, Encoding.BCD_P, Encoding.BCD_UP))
104+
odxassert(
105+
base_type_encoding in (None, Encoding.NONE, Encoding.BCD_P, Encoding.BCD_UP),
106+
f"Illegal encoding '{base_type_encoding}' for A_BYTEFIELD")
105107

106108
# note that we do not ensure that BCD-encoded byte fields
107109
# only represent "legal" values
108110
raw_value = bytes(internal_value)
109111

112+
if 8 * len(raw_value) > bit_length:
113+
odxraise(
114+
f"The value '{internal_value!r}' cannot be encoded using "
115+
f"{bit_length} bits.", EncodeError)
116+
raw_value = raw_value[0:bit_length // 8]
117+
110118
# ... string types, ...
111119
elif base_data_type in (DataType.A_UTF8STRING, DataType.A_ASCIISTRING,
112120
DataType.A_UNICODE2STRING):
113121
if not isinstance(internal_value, str):
114-
odxraise(f"The internal value {internal_value!r} is not a string", EncodeError)
122+
odxraise(f"The internal value '{internal_value!r}' is not a string", EncodeError)
123+
internal_value = str(internal_value)
115124

116125
str_encoding = get_string_encoding(base_data_type, base_type_encoding,
117126
is_highlow_byte_order)
@@ -122,16 +131,17 @@ def emplace_atomic_value(
122131

123132
if 8 * len(raw_value) > bit_length:
124133
odxraise(
125-
f"The internal representation {raw_value!r} is too large "
126-
f"to be encoded. The maximum number of bytes is {(bit_length + 7)//8}.",
127-
EncodeError)
134+
f"The value '{internal_value!r}' cannot be encoded using "
135+
f"{bit_length} bits.", EncodeError)
136+
raw_value = raw_value[0:bit_length // 8]
128137

129138
# ... signed integers, ...
130139
elif base_data_type == DataType.A_INT32:
131140
if not isinstance(internal_value, int):
132141
odxraise(
133142
f"Internal value must be of integer type, not {type(internal_value).__name__}",
134143
EncodeError)
144+
internal_value = int(internal_value)
135145

136146
if base_type_encoding == Encoding.ONEC:
137147
# one-complement
@@ -154,34 +164,35 @@ def emplace_atomic_value(
154164
else:
155165
raw_value = (1 << (bit_length - 1)) + abs(internal_value)
156166
else:
157-
odxraise(f"Illegal encoding ({base_type_encoding}) specified for "
158-
f"{base_data_type.value}")
167+
odxraise(
168+
f"Illegal encoding ({base_type_encoding and base_type_encoding.value}) specified for "
169+
f"{base_data_type.value}")
159170

160-
raw_value = internal_value
171+
if base_type_encoding == Encoding.BCD_P:
172+
raw_value = self.__encode_bcd_p(abs(internal_value))
173+
elif base_type_encoding == Encoding.BCD_UP:
174+
raw_value = self.__encode_bcd_up(abs(internal_value))
175+
else:
176+
raw_value = internal_value
177+
178+
if raw_value.bit_length() > bit_length:
179+
odxraise(
180+
f"The value '{internal_value!r}' cannot be encoded using "
181+
f"{bit_length} bits.", EncodeError)
182+
raw_value &= (1 << bit_length) - 1
161183

162184
# ... unsigned integers, ...
163185
elif base_data_type == DataType.A_UINT32:
164186
if not isinstance(internal_value, int) or internal_value < 0:
165187
odxraise(f"Internal value must be a positive integer, not {internal_value!r}")
188+
internal_value = abs(int(internal_value))
166189

167190
if base_type_encoding == Encoding.BCD_P:
168191
# packed BCD
169-
tmp2 = internal_value
170-
raw_value = 0
171-
shift = 0
172-
while tmp2 > 0:
173-
raw_value |= (tmp2 % 10) << shift
174-
shift += 4
175-
tmp2 //= 10
192+
raw_value = self.__encode_bcd_p(internal_value)
176193
elif base_type_encoding == Encoding.BCD_UP:
177194
# unpacked BCD
178-
tmp2 = internal_value
179-
raw_value = 0
180-
shift = 0
181-
while tmp2 > 0:
182-
raw_value |= (tmp2 % 10) << shift
183-
shift += 8
184-
tmp2 //= 10
195+
raw_value = self.__encode_bcd_up(internal_value)
185196
elif base_type_encoding in (None, Encoding.NONE):
186197
# no encoding
187198
raw_value = internal_value
@@ -191,6 +202,12 @@ def emplace_atomic_value(
191202

192203
raw_value = internal_value
193204

205+
if raw_value.bit_length() > bit_length:
206+
odxraise(
207+
f"The value '{internal_value!r}' cannot be encoded using "
208+
f"{bit_length} bits.", EncodeError)
209+
raw_value &= (1 << bit_length) - 1
210+
194211
# ... and others (floating point values)
195212
else:
196213
odxassert(base_data_type in (DataType.A_FLOAT32, DataType.A_FLOAT64))
@@ -210,15 +227,15 @@ def emplace_atomic_value(
210227
self.emplace_bytes(b'')
211228
return
212229

213-
char = base_data_type.bitstruct_format_letter
230+
format_char = base_data_type.bitstruct_format_letter
214231
padding = (8 - ((bit_length + self.cursor_bit_position) % 8)) % 8
215232
odxassert((0 <= padding and padding < 8 and
216233
(padding + bit_length + self.cursor_bit_position) % 8 == 0),
217234
f"Incorrect padding {padding}")
218235
left_pad = f"p{padding}" if padding > 0 else ""
219236

220237
# actually encode the value
221-
coded = bitstruct.pack(f"{left_pad}{char}{bit_length}", raw_value)
238+
coded = bitstruct.pack(f"{left_pad}{format_char}{bit_length}", raw_value)
222239

223240
# create the raw mask of used bits for numeric objects
224241
used_mask_raw = used_mask
@@ -290,3 +307,25 @@ def emplace_bytes(self,
290307
self.used_mask[pos + i] |= obj_used_mask[i]
291308

292309
self.cursor_byte_position += len(new_data)
310+
311+
@staticmethod
312+
def __encode_bcd_p(value: int) -> int:
313+
result = 0
314+
shift = 0
315+
while value > 0:
316+
result |= (value % 10) << shift
317+
shift += 4
318+
value //= 10
319+
320+
return result
321+
322+
@staticmethod
323+
def __encode_bcd_up(value: int) -> int:
324+
result = 0
325+
shift = 0
326+
while value > 0:
327+
result |= (value % 10) << shift
328+
shift += 8
329+
value //= 10
330+
331+
return result

0 commit comments

Comments
 (0)