odxtools 6.7.1__py3-none-any.whl → 7.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (60) hide show
  1. odxtools/basicstructure.py +102 -112
  2. odxtools/dataobjectproperty.py +3 -7
  3. odxtools/decodestate.py +1 -13
  4. odxtools/diagcodedtype.py +9 -96
  5. odxtools/diagcomm.py +11 -3
  6. odxtools/diagdatadictionaryspec.py +14 -14
  7. odxtools/diaglayer.py +52 -1
  8. odxtools/diaglayerraw.py +13 -7
  9. odxtools/diagservice.py +12 -16
  10. odxtools/dopbase.py +5 -8
  11. odxtools/dtcdop.py +21 -19
  12. odxtools/dynamicendmarkerfield.py +119 -0
  13. odxtools/dynamiclengthfield.py +39 -29
  14. odxtools/dynenddopref.py +38 -0
  15. odxtools/encodestate.py +188 -23
  16. odxtools/endofpdufield.py +33 -18
  17. odxtools/environmentdata.py +8 -1
  18. odxtools/environmentdatadescription.py +21 -15
  19. odxtools/field.py +4 -3
  20. odxtools/leadinglengthinfotype.py +25 -12
  21. odxtools/matchingparameter.py +2 -2
  22. odxtools/minmaxlengthtype.py +36 -26
  23. odxtools/multiplexer.py +42 -23
  24. odxtools/multiplexercase.py +3 -3
  25. odxtools/multiplexerdefaultcase.py +7 -3
  26. odxtools/nameditemlist.py +14 -0
  27. odxtools/odxlink.py +38 -4
  28. odxtools/odxtypes.py +20 -2
  29. odxtools/parameterinfo.py +126 -40
  30. odxtools/parameters/codedconstparameter.py +17 -13
  31. odxtools/parameters/dynamicparameter.py +5 -4
  32. odxtools/parameters/lengthkeyparameter.py +66 -17
  33. odxtools/parameters/matchingrequestparameter.py +23 -11
  34. odxtools/parameters/nrcconstparameter.py +42 -22
  35. odxtools/parameters/parameter.py +35 -42
  36. odxtools/parameters/parameterwithdop.py +15 -22
  37. odxtools/parameters/physicalconstantparameter.py +16 -16
  38. odxtools/parameters/reservedparameter.py +5 -2
  39. odxtools/parameters/systemparameter.py +3 -2
  40. odxtools/parameters/tableentryparameter.py +3 -2
  41. odxtools/parameters/tablekeyparameter.py +88 -39
  42. odxtools/parameters/tablestructparameter.py +45 -44
  43. odxtools/parameters/valueparameter.py +16 -17
  44. odxtools/paramlengthinfotype.py +30 -22
  45. odxtools/request.py +9 -0
  46. odxtools/response.py +5 -13
  47. odxtools/standardlengthtype.py +51 -13
  48. odxtools/statechart.py +5 -9
  49. odxtools/statetransition.py +3 -8
  50. odxtools/staticfield.py +30 -20
  51. odxtools/tablerow.py +5 -3
  52. odxtools/templates/macros/printDynamicEndmarkerField.xml.jinja2 +16 -0
  53. odxtools/templates/macros/printVariant.xml.jinja2 +8 -0
  54. odxtools/version.py +2 -2
  55. {odxtools-6.7.1.dist-info → odxtools-7.1.0.dist-info}/METADATA +1 -1
  56. {odxtools-6.7.1.dist-info → odxtools-7.1.0.dist-info}/RECORD +60 -57
  57. {odxtools-6.7.1.dist-info → odxtools-7.1.0.dist-info}/LICENSE +0 -0
  58. {odxtools-6.7.1.dist-info → odxtools-7.1.0.dist-info}/WHEEL +0 -0
  59. {odxtools-6.7.1.dist-info → odxtools-7.1.0.dist-info}/entry_points.txt +0 -0
  60. {odxtools-6.7.1.dist-info → odxtools-7.1.0.dist-info}/top_level.txt +0 -0
odxtools/encodestate.py CHANGED
@@ -1,12 +1,15 @@
1
1
  # SPDX-License-Identifier: MIT
2
2
  import warnings
3
3
  from dataclasses import dataclass, field
4
- from typing import TYPE_CHECKING, Any, Dict, Optional
4
+ from typing import Dict, Optional, SupportsBytes
5
5
 
6
- from .exceptions import OdxWarning
6
+ from .exceptions import EncodeError, OdxWarning, odxassert, odxraise
7
+ from .odxtypes import AtomicOdxType, DataType
7
8
 
8
- if TYPE_CHECKING:
9
- from .tablerow import TableRow
9
+ try:
10
+ import bitstruct.c as bitstruct
11
+ except ImportError:
12
+ import bitstruct
10
13
 
11
14
 
12
15
  @dataclass
@@ -15,10 +18,23 @@ class EncodeState:
15
18
  """
16
19
 
17
20
  #: payload that has been constructed so far
18
- coded_message: bytearray
21
+ coded_message: bytearray = field(default_factory=bytearray)
19
22
 
20
- #: a mapping from short name to value for each parameter
21
- parameter_values: Dict[str, Any]
23
+ #: the bits of the payload that are used
24
+ used_mask: bytearray = field(default_factory=bytearray)
25
+
26
+ #: The absolute position in bytes from the beginning of the PDU to
27
+ #: which relative positions refer to, e.g., the beginning of the
28
+ #: structure.
29
+ origin_byte_position: int = 0
30
+
31
+ #: The absolute position in bytes from the beginning of the PDU
32
+ #: where the next object ought to be placed into the PDU
33
+ cursor_byte_position: int = 0
34
+
35
+ #: The bit position [0-7] where the next object ought to be
36
+ #: placed into the PDU
37
+ cursor_bit_position: int = 0
22
38
 
23
39
  #: If encoding a response: request that triggered the response
24
40
  triggering_request: Optional[bytes] = None
@@ -28,31 +44,180 @@ class EncodeState:
28
44
  length_keys: Dict[str, int] = field(default_factory=dict)
29
45
 
30
46
  #: Mapping from the short name of a table-key parameter to the
31
- #: corresponding row of the table (specified by TableKeyParameter)
32
- table_keys: Dict[str, "TableRow"] = field(default_factory=dict)
47
+ #: short name of the corresponding row of the table (specified by
48
+ #: TableKeyParameter)
49
+ table_keys: Dict[str, str] = field(default_factory=dict)
50
+
51
+ #: The cursor position where a given length- or table key is located
52
+ #: in the PDU
53
+ key_pos: Dict[str, int] = field(default_factory=dict)
33
54
 
34
55
  #: Flag whether we are currently the last parameter of the PDU
35
- #: (needed for MinMaxLengthType)
36
- is_end_of_pdu: bool = False
56
+ #: (needed for MinMaxLengthType, EndOfPduField, etc.)
57
+ is_end_of_pdu: bool = True
58
+
59
+ def __post_init__(self) -> None:
60
+ # if a coded message has been specified, but no used_mask, we
61
+ # assume that all of the bits of the coded message are
62
+ # currently used.
63
+ if len(self.coded_message) > len(self.used_mask):
64
+ self.used_mask += b'\xff' * (len(self.coded_message) - len(self.used_mask))
65
+ if len(self.coded_message) < len(self.used_mask):
66
+ odxraise(f"The specified bit mask 0x{self.used_mask.hex()} for used bits "
67
+ f"is not suitable for representing the coded_message "
68
+ f"0x{self.coded_message.hex()}")
69
+ self.used_mask = self.used_mask[:len(self.coded_message)]
70
+
71
+ def emplace_atomic_value(
72
+ self,
73
+ *,
74
+ internal_value: AtomicOdxType,
75
+ bit_length: int,
76
+ base_data_type: DataType,
77
+ is_highlow_byte_order: bool,
78
+ used_mask: Optional[bytes],
79
+ ) -> None:
80
+ """Convert the internal_value to bytes and emplace this into the PDU"""
81
+
82
+ raw_value: AtomicOdxType
83
+
84
+ # Check that bytes and strings actually fit into the bit length
85
+ if base_data_type == DataType.A_BYTEFIELD:
86
+ if not isinstance(internal_value, (bytes, bytearray, SupportsBytes)):
87
+ odxraise()
88
+ if 8 * len(internal_value) > bit_length:
89
+ raise EncodeError(f"The bytefield {internal_value.hex()} is too large "
90
+ f"({len(internal_value)} bytes)."
91
+ f" The maximum length is {bit_length//8}.")
92
+ raw_value = bytes(internal_value)
93
+ elif base_data_type == DataType.A_ASCIISTRING:
94
+ if not isinstance(internal_value, str):
95
+ odxraise()
96
+
97
+ # The spec says ASCII, meaning only byte values 0-127.
98
+ # But in practice, vendors use iso-8859-1, aka latin-1
99
+ # reason being iso-8859-1 never fails since it has a valid
100
+ # character mapping for every possible byte sequence.
101
+ raw_value = internal_value.encode("iso-8859-1")
102
+
103
+ if 8 * len(raw_value) > bit_length:
104
+ raise EncodeError(f"The string {repr(internal_value)} is too large."
105
+ f" The maximum number of characters is {bit_length//8}.")
106
+ elif base_data_type == DataType.A_UTF8STRING:
107
+ if not isinstance(internal_value, str):
108
+ odxraise()
109
+
110
+ raw_value = internal_value.encode("utf-8")
111
+
112
+ if 8 * len(raw_value) > bit_length:
113
+ raise EncodeError(f"The string {repr(internal_value)} is too large."
114
+ f" The maximum number of bytes is {bit_length//8}.")
115
+
116
+ elif base_data_type == DataType.A_UNICODE2STRING:
117
+ if not isinstance(internal_value, str):
118
+ odxraise()
37
119
 
38
- def emplace_atomic_value(self,
39
- new_data: bytes,
40
- param_name: str,
41
- pos: Optional[int] = None) -> None:
42
- if pos is None:
43
- pos = len(self.coded_message)
120
+ text_encoding = "utf-16-be" if is_highlow_byte_order else "utf-16-le"
121
+ raw_value = internal_value.encode(text_encoding)
122
+
123
+ if 8 * len(raw_value) > bit_length:
124
+ raise EncodeError(f"The string {repr(internal_value)} is too large."
125
+ f" The maximum number of characters is {bit_length//16}.")
126
+ else:
127
+ raw_value = internal_value
128
+
129
+ # If the bit length is zero, return empty bytes
130
+ if bit_length == 0:
131
+ if (base_data_type.value in [
132
+ DataType.A_INT32, DataType.A_UINT32, DataType.A_FLOAT32, DataType.A_FLOAT64
133
+ ] and base_data_type.value != 0):
134
+ odxraise(
135
+ f"The number {repr(internal_value)} cannot be encoded into {bit_length} bits.",
136
+ EncodeError)
137
+ self.emplace_bytes(b'')
138
+ return
139
+
140
+ char = base_data_type.bitstruct_format_letter
141
+ padding = (8 - ((bit_length + self.cursor_bit_position) % 8)) % 8
142
+ odxassert((0 <= padding and padding < 8 and
143
+ (padding + bit_length + self.cursor_bit_position) % 8 == 0),
144
+ f"Incorrect padding {padding}")
145
+ left_pad = f"p{padding}" if padding > 0 else ""
146
+
147
+ # actually encode the value
148
+ coded = bitstruct.pack(f"{left_pad}{char}{bit_length}", raw_value)
149
+
150
+ # create the raw mask of used bits for numeric objects
151
+ used_mask_raw = used_mask
152
+ if base_data_type in [DataType.A_INT32, DataType.A_UINT32
153
+ ] and (self.cursor_bit_position != 0 or
154
+ (self.cursor_bit_position + bit_length) % 8 != 0):
155
+ if used_mask is None:
156
+ tmp = (1 << bit_length) - 1
157
+ else:
158
+ tmp = int.from_bytes(used_mask, "big")
159
+ tmp <<= self.cursor_bit_position
160
+
161
+ used_mask_raw = tmp.to_bytes((self.cursor_bit_position + bit_length + 7) // 8, "big")
162
+
163
+ # apply byte order to numeric objects
164
+ if not is_highlow_byte_order and base_data_type in [
165
+ DataType.A_INT32, DataType.A_UINT32, DataType.A_FLOAT32, DataType.A_FLOAT64
166
+ ]:
167
+ coded = coded[::-1]
168
+
169
+ if used_mask_raw is not None:
170
+ used_mask_raw = used_mask_raw[::-1]
171
+
172
+ self.cursor_bit_position = 0
173
+ self.emplace_bytes(coded, obj_used_mask=used_mask_raw)
174
+
175
+ def emplace_bytes(self,
176
+ new_data: bytes,
177
+ obj_name: Optional[str] = None,
178
+ obj_used_mask: Optional[bytes] = None) -> None:
179
+ if self.cursor_bit_position != 0:
180
+ odxraise("EncodeState.emplace_bytes can only be called "
181
+ "for a bit position of 0!", RuntimeError)
182
+
183
+ pos = self.cursor_byte_position
44
184
 
45
185
  # Make blob longer if necessary
46
186
  min_length = pos + len(new_data)
47
187
  if len(self.coded_message) < min_length:
48
- self.coded_message.extend([0] * (min_length - len(self.coded_message)))
188
+ pad = b'\x00' * (min_length - len(self.coded_message))
189
+ self.coded_message += pad
190
+ self.used_mask += pad
191
+
192
+ if obj_used_mask is None:
193
+ # Happy path for when no obj_used_mask has been
194
+ # specified. In this case we assume that all bits of the
195
+ # new data to be emplaced are used.
196
+ n = len(new_data)
49
197
 
50
- for byte_idx_val, byte_idx_rpc in enumerate(range(pos, pos + len(new_data))):
51
- # insert byte value
52
- if self.coded_message[byte_idx_rpc] & new_data[byte_idx_val] != 0:
198
+ if self.used_mask[pos:pos + n] != b'\x00' * n:
53
199
  warnings.warn(
54
- f"Object '{param_name}' overlaps with another parameter (bytes are already set)",
200
+ f"Overlapping objects detected in between bytes {pos} and "
201
+ f"{pos+n}",
55
202
  OdxWarning,
56
203
  stacklevel=1,
57
204
  )
58
- self.coded_message[byte_idx_rpc] |= new_data[byte_idx_val]
205
+ self.coded_message[pos:pos + n] = new_data
206
+ self.used_mask[pos:pos + n] = b'\xff' * n
207
+ else:
208
+ # insert data the hard way, i.e. we have to look at each
209
+ # individual byte to determine if it has already been used
210
+ # somewhere else (it would be nice if bytearrays supported
211
+ # bitwise operations!)
212
+ for i in range(len(new_data)):
213
+ if self.used_mask[pos + i] & obj_used_mask[i] != 0:
214
+ warnings.warn(
215
+ f"Overlapping objects detected at position {pos + i}",
216
+ OdxWarning,
217
+ stacklevel=1,
218
+ )
219
+ self.coded_message[pos + i] &= ~obj_used_mask[i]
220
+ self.coded_message[pos + i] |= new_data[i] & obj_used_mask[i]
221
+ self.used_mask[pos + i] |= obj_used_mask[i]
222
+
223
+ self.cursor_byte_position += len(new_data)
odxtools/endofpdufield.py CHANGED
@@ -1,8 +1,10 @@
1
1
  # SPDX-License-Identifier: MIT
2
2
  from dataclasses import dataclass
3
- from typing import List, Optional
3
+ from typing import List, Optional, Sequence
4
4
  from xml.etree import ElementTree
5
5
 
6
+ from typing_extensions import override
7
+
6
8
  from .decodestate import DecodeState
7
9
  from .encodestate import EncodeState
8
10
  from .exceptions import EncodeError, odxassert, odxraise
@@ -39,30 +41,40 @@ class EndOfPduField(Field):
39
41
 
40
42
  return eopf
41
43
 
42
- def convert_physical_to_bytes(
43
- self,
44
- physical_value: ParameterValue,
45
- encode_state: EncodeState,
46
- bit_position: int = 0,
47
- ) -> bytes:
48
-
49
- odxassert(
50
- bit_position == 0, "End of PDU field must be byte aligned. "
51
- "Is there an error in reading the .odx?", EncodeError)
52
- if not isinstance(physical_value, list):
44
+ @override
45
+ def encode_into_pdu(self, physical_value: Optional[ParameterValue],
46
+ encode_state: EncodeState) -> None:
47
+ odxassert(not encode_state.cursor_bit_position,
48
+ "No bit position can be specified for end-of-pdu fields!")
49
+ odxassert(encode_state.is_end_of_pdu,
50
+ "End-of-pdu fields can only be located at the end of PDUs!")
51
+
52
+ if not isinstance(physical_value, Sequence):
53
53
  odxraise(
54
- f"Expected a list of values for end-of-pdu field {self.short_name}, "
55
- f"got {type(physical_value)}", EncodeError)
54
+ f"Invalid type {type(physical_value).__name__} of physical "
55
+ f"value for end-of-pdu field, expected a list", EncodeError)
56
+ return
57
+
58
+ orig_is_end_of_pdu = encode_state.is_end_of_pdu
59
+ encode_state.is_end_of_pdu = False
56
60
 
57
- coded_message = b''
58
- for value in physical_value:
59
- coded_message += self.structure.convert_physical_to_bytes(value, encode_state)
60
- return coded_message
61
+ for i, value in enumerate(physical_value):
62
+ if i == len(physical_value) - 1:
63
+ encode_state.is_end_of_pdu = orig_is_end_of_pdu
61
64
 
65
+ self.structure.encode_into_pdu(value, encode_state)
66
+
67
+ encode_state.is_end_of_pdu = orig_is_end_of_pdu
68
+
69
+ @override
62
70
  def decode_from_pdu(self, decode_state: DecodeState) -> ParameterValue:
63
71
  odxassert(not decode_state.cursor_bit_position,
64
72
  "No bit position can be specified for end-of-pdu fields!")
65
73
 
74
+ orig_origin = decode_state.origin_byte_position
75
+ orig_cursor = decode_state.cursor_byte_position
76
+ decode_state.origin_byte_position = decode_state.cursor_byte_position
77
+
66
78
  result: List[ParameterValue] = []
67
79
  while decode_state.cursor_byte_position < len(decode_state.coded_message):
68
80
  # ATTENTION: the ODX specification is very misleading
@@ -71,4 +83,7 @@ class EndOfPduField(Field):
71
83
  # repeated are identical, not their values
72
84
  result.append(self.structure.decode_from_pdu(decode_state))
73
85
 
86
+ decode_state.origin_byte_position = orig_origin
87
+ decode_state.cursor_byte_position = max(orig_cursor, decode_state.cursor_byte_position)
88
+
74
89
  return result
@@ -11,7 +11,14 @@ from .utils import dataclass_fields_asdict
11
11
 
12
12
  @dataclass
13
13
  class EnvironmentData(BasicStructure):
14
- """This class represents Environment Data that describes the circumstances in which the error occurred."""
14
+ """This class represents Environment Data that describes the
15
+ circumstances in which the error occurred.
16
+
17
+ This is one of the many multiplexer mechanisms specified by the
18
+ ODX standard, because an environment data parameter must only be
19
+ used if a DTC parameter has a certain set of values. (In this
20
+ sense, it is quite similar to NRC-CONST parameters.)
21
+ """
15
22
 
16
23
  all_value: Optional[bool]
17
24
  dtc_values: List[int]
@@ -1,13 +1,15 @@
1
1
  # SPDX-License-Identifier: MIT
2
2
  from dataclasses import dataclass
3
- from typing import TYPE_CHECKING, Any, Dict, List, Optional, Tuple
3
+ from typing import TYPE_CHECKING, Any, Dict, List, Optional
4
4
  from xml.etree import ElementTree
5
5
 
6
+ from typing_extensions import override
7
+
6
8
  from .complexdop import ComplexDop
7
9
  from .decodestate import DecodeState
8
10
  from .encodestate import EncodeState
9
11
  from .environmentdata import EnvironmentData
10
- from .exceptions import DecodeError, EncodeError, odxrequire
12
+ from .exceptions import odxraise, odxrequire
11
13
  from .odxlink import OdxDocFragment, OdxLinkDatabase, OdxLinkId, OdxLinkRef
12
14
  from .odxtypes import ParameterValue
13
15
  from .utils import dataclass_fields_asdict
@@ -18,8 +20,14 @@ if TYPE_CHECKING:
18
20
 
19
21
  @dataclass
20
22
  class EnvironmentDataDescription(ComplexDop):
21
- """This class represents Environment Data Description, which is a complex DOP
22
- that is used to define the interpretation of environment data."""
23
+ """This class represents environment data descriptions
24
+
25
+ An environment data description provides a list of all environment
26
+ data objects that are potentially applicable to decode a given
27
+ response. (If a given environment data object is applicable
28
+ depends on the value of the DtcDOP that is associated with it.)
29
+
30
+ """
23
31
 
24
32
  # in ODX 2.0.0, ENV-DATAS seems to be a mandatory
25
33
  # sub-element of ENV-DATA-DESC, in ODX 2.2 it is not
@@ -86,23 +94,21 @@ class EnvironmentDataDescription(ComplexDop):
86
94
  for ed in self.env_datas:
87
95
  ed._resolve_snrefs(diag_layer)
88
96
 
89
- def convert_physical_to_bytes(self,
90
- physical_value: ParameterValue,
91
- encode_state: EncodeState,
92
- bit_position: int = 0) -> bytes:
93
- """Convert the physical value into bytes.
97
+ @override
98
+ def encode_into_pdu(self, physical_value: Optional[ParameterValue],
99
+ encode_state: EncodeState) -> None:
100
+ """Convert a physical value into bytes and emplace them into a PDU.
94
101
 
95
102
  Since environmental data is supposed to never appear on the
96
103
  wire, this method just raises an EncodeError exception.
97
104
  """
98
- raise EncodeError("EnvironmentDataDescription DOPs cannot be encoded or decoded")
105
+ odxraise("EnvironmentDataDescription DOPs cannot be encoded or decoded")
99
106
 
100
- def convert_bytes_to_physical(self,
101
- decode_state: DecodeState,
102
- bit_position: int = 0) -> Tuple[ParameterValue, int]:
103
- """Extract the bytes from the PDU and convert them to the physical value.
107
+ @override
108
+ def decode_from_pdu(self, decode_state: DecodeState) -> ParameterValue:
109
+ """Extract the bytes from a PDU and convert them to a physical value.
104
110
 
105
111
  Since environmental data is supposed to never appear on the
106
112
  wire, this method just raises an DecodeError exception.
107
113
  """
108
- raise DecodeError("EnvironmentDataDescription DOPs cannot be encoded or decoded")
114
+ odxraise("EnvironmentDataDescription DOPs cannot be encoded or decoded")
odxtools/field.py CHANGED
@@ -6,7 +6,7 @@ from .basicstructure import BasicStructure
6
6
  from .complexdop import ComplexDop
7
7
  from .environmentdatadescription import EnvironmentDataDescription
8
8
  from .exceptions import odxassert, odxrequire
9
- from .odxlink import OdxDocFragment, OdxLinkDatabase, OdxLinkRef
9
+ from .odxlink import OdxDocFragment, OdxLinkDatabase, OdxLinkRef, resolve_snref
10
10
  from .odxtypes import odxstr_to_bool
11
11
  from .utils import dataclass_fields_asdict
12
12
 
@@ -84,8 +84,9 @@ class Field(ComplexDop):
84
84
  """Recursively resolve any short-name references"""
85
85
  if self.structure_snref is not None:
86
86
  structures = diag_layer.diag_data_dictionary_spec.structures
87
- self._structure = odxrequire(structures.get(self.structure_snref))
87
+ self._structure = resolve_snref(self.structure_snref, structures, BasicStructure)
88
88
 
89
89
  if self.env_data_desc_snref is not None:
90
90
  env_data_descs = diag_layer.diag_data_dictionary_spec.env_data_descs
91
- self._env_data_desc = odxrequire(env_data_descs.get(self.env_data_desc_snref))
91
+ self._env_data_desc = resolve_snref(self.env_data_desc_snref, env_data_descs,
92
+ EnvironmentDataDescription)
@@ -1,11 +1,13 @@
1
1
  # SPDX-License-Identifier: MIT
2
2
  from dataclasses import dataclass
3
- from typing import Any, Optional
3
+ from typing import Optional
4
+
5
+ from typing_extensions import override
4
6
 
5
7
  from .decodestate import DecodeState
6
8
  from .diagcodedtype import DctType, DiagCodedType
7
9
  from .encodestate import EncodeState
8
- from .exceptions import odxassert, odxraise
10
+ from .exceptions import EncodeError, odxassert, odxraise
9
11
  from .odxtypes import AtomicOdxType, DataType
10
12
 
11
13
 
@@ -42,29 +44,40 @@ class LeadingLengthInfoType(DiagCodedType):
42
44
  # DCT is dynamic!
43
45
  return None
44
46
 
45
- def convert_internal_to_bytes(self, internal_value: Any, encode_state: EncodeState,
46
- bit_position: int) -> bytes:
47
+ @override
48
+ def encode_into_pdu(self, internal_value: AtomicOdxType, encode_state: EncodeState) -> None:
49
+
50
+ if not isinstance(internal_value, (str, bytes)):
51
+ odxraise(
52
+ f"LEADING-LENGTH-INFO types can only be used for strings and byte fields, "
53
+ f"not {type(internal_value).__name__}", EncodeError)
54
+ return
47
55
 
48
56
  byte_length = self._minimal_byte_length_of(internal_value)
49
57
 
50
- length_bytes = self._encode_internal_value(
51
- byte_length,
52
- bit_position=bit_position,
58
+ used_mask = None
59
+ bit_pos = encode_state.cursor_bit_position
60
+ if encode_state.cursor_bit_position != 0 or (bit_pos + self.bit_length) % 8 != 0:
61
+ used_mask = (1 << self.bit_length) - 1
62
+ used_mask <<= bit_pos
63
+
64
+ encode_state.emplace_atomic_value(
65
+ internal_value=byte_length,
66
+ used_mask=None,
53
67
  bit_length=self.bit_length,
54
68
  base_data_type=DataType.A_UINT32,
55
69
  is_highlow_byte_order=self.is_highlow_byte_order,
56
70
  )
57
71
 
58
- value_bytes = self._encode_internal_value(
59
- internal_value,
60
- bit_position=0,
72
+ encode_state.emplace_atomic_value(
73
+ internal_value=internal_value,
74
+ used_mask=None,
61
75
  bit_length=8 * byte_length,
62
76
  base_data_type=self.base_data_type,
63
77
  is_highlow_byte_order=self.is_highlow_byte_order,
64
78
  )
65
79
 
66
- return length_bytes + value_bytes
67
-
80
+ @override
68
81
  def decode_from_pdu(self, decode_state: DecodeState) -> AtomicOdxType:
69
82
 
70
83
  # Extract length of the parameter value
@@ -32,8 +32,8 @@ class MatchingParameter:
32
32
  doc_frags: List[OdxDocFragment]) -> "MatchingParameter":
33
33
 
34
34
  expected_value = odxrequire(et_element.findtext("EXPECTED-VALUE"))
35
- diag_com_snref_el = odxrequire(et_element.find("DIAG-COMM-SNREF"))
36
- diag_comm_snref = odxrequire(diag_com_snref_el.get("SHORT-NAME"))
35
+ diag_comm_snref_el = odxrequire(et_element.find("DIAG-COMM-SNREF"))
36
+ diag_comm_snref = odxrequire(diag_comm_snref_el.get("SHORT-NAME"))
37
37
  out_param_snref_el = et_element.find("OUT-PARAM-IF-SNREF")
38
38
  out_param_snpathref_el = et_element.find("OUT-PARAM-IF-SNPATHREF")
39
39
  out_param_if = None
@@ -2,6 +2,8 @@
2
2
  from dataclasses import dataclass
3
3
  from typing import Optional
4
4
 
5
+ from typing_extensions import override
6
+
5
7
  from .decodestate import DecodeState
6
8
  from .diagcodedtype import DctType, DiagCodedType
7
9
  from .encodestate import EncodeState
@@ -51,8 +53,9 @@ class MinMaxLengthType(DiagCodedType):
51
53
  termination_sequence = bytes([0xFF, 0xFF])
52
54
  return termination_sequence
53
55
 
54
- def convert_internal_to_bytes(self, internal_value: AtomicOdxType, encode_state: EncodeState,
55
- bit_position: int) -> bytes:
56
+ @override
57
+ def encode_into_pdu(self, internal_value: AtomicOdxType, encode_state: EncodeState) -> None:
58
+
56
59
  if not isinstance(internal_value, (bytes, str)):
57
60
  odxraise("MinMaxLengthType is currently only implemented for strings and byte arrays",
58
61
  EncodeError)
@@ -62,20 +65,24 @@ class MinMaxLengthType(DiagCodedType):
62
65
  else:
63
66
  data_length = len(internal_value)
64
67
 
65
- value_bytes = bytearray(
66
- self._encode_internal_value(
67
- internal_value,
68
- bit_position=0,
69
- bit_length=8 * data_length,
70
- base_data_type=self.base_data_type,
71
- is_highlow_byte_order=self.is_highlow_byte_order,
72
- ))
68
+ orig_cursor = encode_state.cursor_byte_position
69
+ encode_state.emplace_atomic_value(
70
+ internal_value=internal_value,
71
+ used_mask=None,
72
+ bit_length=8 * data_length,
73
+ base_data_type=self.base_data_type,
74
+ is_highlow_byte_order=self.is_highlow_byte_order,
75
+ )
76
+ value_len = encode_state.cursor_byte_position - orig_cursor
73
77
 
74
78
  # TODO: ensure that the termination delimiter is not
75
79
  # encountered within the encoded value.
76
80
 
77
- odxassert(self.termination != "END-OF-PDU" or encode_state.is_end_of_pdu)
78
- if encode_state.is_end_of_pdu or len(value_bytes) == self.max_length:
81
+ odxassert(
82
+ self.termination != "END-OF-PDU" or encode_state.is_end_of_pdu,
83
+ "Encountered a MIN-MAX-LENGTH type with END-OF-PDU termination "
84
+ "which is not located at the end of the PDU")
85
+ if encode_state.is_end_of_pdu or value_len == self.max_length:
79
86
  # All termination types may be ended by the end of the PDU
80
87
  # or once reaching the maximum length. In this case, we
81
88
  # must not add the termination sequence
@@ -85,20 +92,23 @@ class MinMaxLengthType(DiagCodedType):
85
92
 
86
93
  # ensure that we don't try to encode an odd-length
87
94
  # value when using a two-byte terminator
88
- odxassert(len(value_bytes) % len(termination_sequence) == 0)
89
-
90
- value_bytes.extend(termination_sequence)
91
-
92
- if len(value_bytes) < self.min_length:
93
- raise EncodeError(f"Encoded value for MinMaxLengthType "
94
- f"must be at least {self.min_length} bytes long. "
95
- f"(Is: {len(value_bytes)} bytes.)")
96
- elif self.max_length is not None and len(value_bytes) > self.max_length:
97
- raise EncodeError(f"Encoded value for MinMaxLengthType "
98
- f"must not be longer than {self.max_length} bytes. "
99
- f"(Is: {len(value_bytes)} bytes.)")
100
-
101
- return value_bytes
95
+ odxassert(value_len % len(termination_sequence) == 0)
96
+
97
+ value_len += len(termination_sequence)
98
+ encode_state.emplace_bytes(termination_sequence)
99
+
100
+ if value_len < self.min_length:
101
+ odxraise(
102
+ f"Encoded value for MinMaxLengthType "
103
+ f"must be at least {self.min_length} bytes long. "
104
+ f"(Is: {value_len} bytes.)", EncodeError)
105
+ return
106
+ elif self.max_length is not None and value_len > self.max_length:
107
+ odxraise(
108
+ f"Encoded value for MinMaxLengthType "
109
+ f"must not be longer than {self.max_length} bytes. "
110
+ f"(Is: {value_len} bytes.)", EncodeError)
111
+ return
102
112
 
103
113
  def decode_from_pdu(self, decode_state: DecodeState) -> AtomicOdxType:
104
114
  odxassert(decode_state.cursor_bit_position == 0,