From 9e4ca3780ac73d2d7f97695e963be9bda137dc03 Mon Sep 17 00:00:00 2001 From: Andreas Lauser Date: Tue, 6 Feb 2024 12:15:34 +0100 Subject: [PATCH 1/6] specify that the size of LeadingLengthInfoType object is dynamic the `.bit_length` attribute does *not* specify the size of the while object, but only the size of the length indicator at the beginning of the object. This is then followed by an array of the size of the value given in this length indicator field. Signed-off-by: Andreas Lauser Signed-off-by: Gerrit Ecke --- odxtools/leadinglengthinfotype.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/odxtools/leadinglengthinfotype.py b/odxtools/leadinglengthinfotype.py index 1105c348..9fb5cb8b 100644 --- a/odxtools/leadinglengthinfotype.py +++ b/odxtools/leadinglengthinfotype.py @@ -11,6 +11,11 @@ @dataclass class LeadingLengthInfoType(DiagCodedType): + #: bit length of the length specifier field + #: + #: this is then followed by the number of bytes specified by that + #: field, i.e., this is NOT the length of the LeadingLengthInfoType + #: object. bit_length: int def __post_init__(self) -> None: @@ -31,7 +36,11 @@ def dct_type(self) -> DctType: return "LEADING-LENGTH-INFO-TYPE" def get_static_bit_length(self) -> Optional[int]: - return self.bit_length + # note that self.bit_length is just the length of the length + # specifier field. This is then followed by the same number of + # bytes as the value of this field, i.e., the length of this + # DCT is dynamic! + return None def convert_internal_to_bytes(self, internal_value: Any, encode_state: EncodeState, bit_position: int) -> bytes: From 94939909e36a876b526d5597c97ba875bcb03358 Mon Sep 17 00:00:00 2001 From: Andreas Lauser Date: Tue, 6 Feb 2024 12:15:49 +0100 Subject: [PATCH 2/6] rename loop variable for parameters from `p` or `parameter` to `param` this simplifies the debugging experience a bit. For now, list comprehensions stay as they are because they are usually very simple... Signed-off-by: Andreas Lauser Signed-off-by: Gerrit Ecke --- odxtools/basicstructure.py | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/odxtools/basicstructure.py b/odxtools/basicstructure.py index 3acfc503..72af8685 100644 --- a/odxtools/basicstructure.py +++ b/odxtools/basicstructure.py @@ -74,10 +74,10 @@ def get_static_bit_length(self) -> Optional[int]: def coded_const_prefix(self, request_prefix: bytes = b'') -> bytes: prefix = b'' encode_state = EncodeState(prefix, parameter_values={}, triggering_request=request_prefix) - for p in self.parameters: - if isinstance(p, (CodedConstParameter, NrcConstParameter, MatchingRequestParameter, - PhysicalConstantParameter)): - encode_state.coded_message = p.encode_into_pdu(encode_state) + for param in self.parameters: + if isinstance(param, (CodedConstParameter, NrcConstParameter, MatchingRequestParameter, + PhysicalConstantParameter)): + encode_state.coded_message = param.encode_into_pdu(encode_state) else: break return encode_state.coded_message @@ -213,10 +213,10 @@ def convert_bytes_to_physical(self, inner_decode_state = DecodeState( coded_message=byte_code, parameter_values={}, cursor_position=0) - for parameter in self.parameters: - value, cursor_position = parameter.decode_from_pdu(inner_decode_state) + for param in self.parameters: + value, cursor_position = param.decode_from_pdu(inner_decode_state) - inner_decode_state.parameter_values[parameter.short_name] = value + inner_decode_state.parameter_values[param.short_name] = value inner_decode_state = DecodeState( coded_message=byte_code, parameter_values=inner_decode_state.parameter_values, @@ -282,8 +282,8 @@ def parameter_dict(self) -> ParameterDict: def _build_odxlinks(self) -> Dict[OdxLinkId, Any]: result = super()._build_odxlinks() - for p in self.parameters: - result.update(p._build_odxlinks()) + for param in self.parameters: + result.update(param._build_odxlinks()) return result @@ -291,12 +291,12 @@ def _resolve_odxlinks(self, odxlinks: OdxLinkDatabase) -> None: """Recursively resolve any references (odxlinks or sn-refs)""" super()._resolve_odxlinks(odxlinks) - for p in self.parameters: - p._resolve_odxlinks(odxlinks) + for param in self.parameters: + param._resolve_odxlinks(odxlinks) def _resolve_snrefs(self, diag_layer: "DiagLayer") -> None: """Recursively resolve any references (odxlinks or sn-refs)""" super()._resolve_snrefs(diag_layer) - for p in self.parameters: - p._resolve_snrefs(diag_layer) + for param in self.parameters: + param._resolve_snrefs(diag_layer) From 37e619717d1d16614ed0f78d4adb737af8f5603e Mon Sep 17 00:00:00 2001 From: Andreas Lauser Date: Tue, 6 Feb 2024 12:16:04 +0100 Subject: [PATCH 3/6] length/table key parameters: prevent warning about overlapping parameter if these parameters where specified explicitly, their value was encoded into the blob twice: once whilst going over all parameters and once for the post processing step which is required due to the possibility to specify these parameters implicitly. This avoids the warning. Signed-off-by: Andreas Lauser Signed-off-by: Gerrit Ecke --- odxtools/basicstructure.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/odxtools/basicstructure.py b/odxtools/basicstructure.py index 72af8685..6f459075 100644 --- a/odxtools/basicstructure.py +++ b/odxtools/basicstructure.py @@ -139,6 +139,21 @@ def convert_physical_to_internal(self, # the ODX is located last in the PDU... encode_state.is_end_of_pdu = is_end_of_pdu + if isinstance( + param, + (LengthKeyParameter, TableKeyParameter)) and param.short_name in param_value: + # This is a hack to always encode a dummy value for + # length- and table keys. since these can be specified + # implicitly (i.e., by means of parameters that use + # these keys), to avoid getting an "overlapping + # parameter" warning, we must encode a value of zero + # into the PDU here and add the real value of the + # parameter in a post processing step. + tmp = encode_state.parameter_values.pop(param.short_name) + encode_state.coded_message = param.encode_into_pdu(encode_state) + encode_state.parameter_values[param.short_name] = tmp + continue + encode_state.coded_message = param.encode_into_pdu(encode_state) if self.byte_size is not None and len(encode_state.coded_message) < self.byte_size: From 7196eae3d3964a909962be7f2c484b32a4b82098 Mon Sep 17 00:00:00 2001 From: Andreas Lauser Date: Tue, 6 Feb 2024 12:16:19 +0100 Subject: [PATCH 4/6] fix deprecation warning in test_singleecujob Signed-off-by: Andreas Lauser Signed-off-by: Gerrit Ecke --- tests/test_singleecujob.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/test_singleecujob.py b/tests/test_singleecujob.py index 79d49ceb..9fb6ea97 100644 --- a/tests/test_singleecujob.py +++ b/tests/test_singleecujob.py @@ -424,8 +424,8 @@ def test_resolve_odxlinks(self) -> None: self.assertEqual(self.context.specialAudience, odxrequire(self.singleecujob_object.audience).enabled_audiences[0]) - self.assertEqual(self.context.inputDOP, self.singleecujob_object.input_params[0].dop) - self.assertEqual(self.context.outputDOP, self.singleecujob_object.output_params[0].dop) + self.assertEqual(self.context.inputDOP, self.singleecujob_object.input_params[0].dop_base) + self.assertEqual(self.context.outputDOP, self.singleecujob_object.output_params[0].dop_base) self.assertEqual(self.context.negOutputDOP, self.singleecujob_object.neg_output_params[0].dop) From bec5e92231a922353c9b4c9de1eab298026dfcff Mon Sep 17 00:00:00 2001 From: Andreas Lauser Date: Tue, 6 Feb 2024 12:16:49 +0100 Subject: [PATCH 5/6] snoop: correctly detect response types and streamline terminal output Once upon a time, the `response_type` property of `Response` objects used to be a string. In modern times it has become an enum in order to make it play nicely with type checking. Besides this, the output of the snoop tool is slightly decluttered, i.e., it does not prefix everything which is send by the tester by `tester:` and everything send by the ECU by `ECU:`. (this is IMO quite clear from the context.) Signed-off-by: Andreas Lauser Signed-off-by: Gerrit Ecke --- odxtools/cli/snoop.py | 31 ++++++++++++++++++++----------- 1 file changed, 20 insertions(+), 11 deletions(-) diff --git a/odxtools/cli/snoop.py b/odxtools/cli/snoop.py index e725740c..19875593 100644 --- a/odxtools/cli/snoop.py +++ b/odxtools/cli/snoop.py @@ -12,6 +12,7 @@ import odxtools.uds as uds from odxtools.exceptions import DecodeError from odxtools.isotp_state_machine import IsoTpStateMachine +from odxtools.response import Response, ResponseType from . import _parser_utils @@ -31,7 +32,7 @@ def handle_telegram(telegram_id: int, payload: bytes) -> None: if telegram_id == ecu_tx_id: if uds.is_response_pending(payload): - print(f" -> ECU: ... (response pending)") + print(f" ... (response pending)") return decoded_message = None @@ -48,21 +49,29 @@ def handle_telegram(telegram_id: int, payload: bytes) -> None: if len(decoded_message) > 1: dec_str = f" (decoding {i+1})" - response_type = getattr(resp.coding_object, "response_type", None) rt_str = "unknown" - if response_type == "POS-RESPONSE": - rt_str = "positive" - elif response_type == "NEG-RESPONSE": - rt_str = "negative" - - print(f" -> {rt_str} ECU response{dec_str}: \"{resp.coding_object.short_name}\"") + if isinstance(resp.coding_object, Response): + if resp.coding_object.response_type == ResponseType.POSITIVE: + rt_str = "positive" + elif resp.coding_object.response_type in (ResponseType.NEGATIVE, + ResponseType.GLOBAL_NEGATIVE): + rt_str = "negative" + + settable_params = [] for param_name, param_val in resp.param_dict.items(): param = [x for x in params if x.short_name == param_name][0] if not param.is_settable: continue - print(f" {param_name} = {repr(param_val)}") + settable_params.append((param_name, param_val)) + + if settable_params: + print(f" {rt_str} response{dec_str} {resp.coding_object.short_name}:") + for param_name, param_val in settable_params: + print(f" {param_name} = {repr(param_val)}") + else: + print(f" {rt_str} response{dec_str} {resp.coding_object.short_name}") else: - print(f" -> ECU response: unrecognized response of {len(payload)} bytes length: " + print(f" unrecognized response of {len(payload)} bytes length: " f"0x{payload.hex()}") return @@ -76,7 +85,7 @@ def handle_telegram(telegram_id: int, payload: bytes) -> None: last_request = None if decoded_message is not None: - print(f"tester request: \"{decoded_message.coding_object.short_name}\"") + print(f"request {decoded_message.coding_object.short_name}:") params = decoded_message.coding_object.parameters for param_name, param_val in decoded_message.param_dict.items(): param = [x for x in params if x.short_name == param_name][0] From a34004178aaa948b1d20212996879eae0f170283 Mon Sep 17 00:00:00 2001 From: Andreas Lauser Date: Tue, 6 Feb 2024 13:32:26 +0100 Subject: [PATCH 6/6] structures: ensure that no unknown parameters are specified for `encode()` Signed-off-by: Andreas Lauser Signed-off-by: Gerrit Ecke --- examples/somersault.pdx | Bin 24464 -> 25021 bytes examples/somersaultecu.py | 12 +++++++++++ odxtools/basicstructure.py | 9 +++++++- tests/test_somersault.py | 42 ++++++++++++++++++++++++++----------- 4 files changed, 50 insertions(+), 13 deletions(-) diff --git a/examples/somersault.pdx b/examples/somersault.pdx index a17710aef9616435fc726ea9f55bac0fa7a12253..874e58c8a09d1c26709221a13eaecaaa7e9658fc 100644 GIT binary patch literal 25021 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 literal 24464 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 diff --git a/examples/somersaultecu.py b/examples/somersaultecu.py index 831398b6..a94721b1 100755 --- a/examples/somersaultecu.py +++ b/examples/somersaultecu.py @@ -713,6 +713,18 @@ class SomersaultSID(IntEnum): bit_position=None, sdgs=[], ), + ValueParameter( + short_name="sault_time", + long_name=None, + semantic=None, + description=None, + physical_default_value_raw="255", + byte_position=2, + dop_ref=OdxLinkRef("somersault.DOP.duration", doc_frags), + dop_snref=None, + bit_position=None, + sdgs=[], + ), ]), byte_size=None, ), diff --git a/odxtools/basicstructure.py b/odxtools/basicstructure.py index 6f459075..02e9b102 100644 --- a/odxtools/basicstructure.py +++ b/odxtools/basicstructure.py @@ -8,7 +8,7 @@ from .dataobjectproperty import DataObjectProperty from .decodestate import DecodeState from .encodestate import EncodeState -from .exceptions import DecodeError, EncodeError, OdxWarning, odxassert, odxraise +from .exceptions import DecodeError, EncodeError, OdxWarning, odxassert, odxraise, strict_mode from .nameditemlist import NamedItemList from .odxlink import OdxDocFragment, OdxLinkDatabase, OdxLinkId from .odxtypes import ParameterDict, ParameterValue, ParameterValueDict @@ -124,6 +124,13 @@ def convert_physical_to_internal(self, f"Expected a dictionary for the values of structure {self.short_name}, " f"got {type(param_value)}") + # in strict mode, ensure that no values for unknown parameters are specified. + if strict_mode: + param_names = [param.short_name for param in self.parameters] + for param_key in param_value: + if param_key not in param_names: + odxraise(f"Value for unknown parameter '{param_key}' specified") + encode_state = EncodeState( b'', dict(param_value), diff --git a/tests/test_somersault.py b/tests/test_somersault.py index 516d53cf..5397dfd7 100644 --- a/tests/test_somersault.py +++ b/tests/test_somersault.py @@ -1,7 +1,7 @@ # SPDX-License-Identifier: MIT import unittest -from odxtools.exceptions import odxrequire +from odxtools.exceptions import OdxError, odxrequire from odxtools.load_pdx_file import load_pdx_file from odxtools.parameters.nrcconstparameter import NrcConstParameter @@ -145,9 +145,11 @@ def test_somersault_lazy(self) -> None: self.assertEqual([x.short_name for x in service.negative_responses], ["flips_not_done"]) pr = service.positive_responses.grudging_forward - self.assertEqual([x.short_name for x in pr.parameters], ["sid", "num_flips_done"]) + self.assertEqual([x.short_name for x in pr.parameters], + ["sid", "num_flips_done", "sault_time"]) self.assertEqual([x.short_name for x in pr.required_parameters], []) - self.assertEqual(pr.get_static_bit_length(), 16) + self.assertEqual([x.short_name for x in pr.free_parameters], ["sault_time"]) + self.assertEqual(pr.get_static_bit_length(), 24) nr = service.negative_responses.flips_not_done self.assertEqual( @@ -162,7 +164,16 @@ def test_somersault_lazy(self) -> None: self.assertEqual(nrc_const.coded_values, [0, 1, 2]) -class TestDecode(unittest.TestCase): +class TestEnDecode(unittest.TestCase): + + def test_encode_specify_unknown_param(self) -> None: + ecu = odxdb.ecus.somersault_lazy + service = ecu.services.do_forward_flips + request = odxrequire(service.request) + with self.assertRaises(OdxError) as eo: + request.encode(forward_soberness_check=0x12, num_flips=5, grass_level="what grass?") + + self.assertEqual(str(eo.exception), "Value for unknown parameter 'grass_level' specified") def test_decode_request(self) -> None: messages = odxdb.ecus.somersault_assiduous.decode(bytes([0x03, 0x45])) @@ -226,9 +237,9 @@ def test_code_table_params(self) -> None: dizzyness_level=42, happiness_level=92, last_pos_response=("forward_grudging", { - "dizzyness_level": 42 + "sault_time": 249 })) - self.assertEqual(resp_data.hex(), "622a5c03fa7b") + self.assertEqual(resp_data.hex(), "622a5c03fa7bf9") decoded_resp_data = pr.decode(resp_data) assert isinstance(decoded_resp_data, dict) @@ -241,7 +252,7 @@ def test_code_table_params(self) -> None: self.assertEqual( set(decoded_resp_data["last_pos_response"] [1].keys()), # type: ignore[index, union-attr] - {"sid", "num_flips_done"}) + {"sid", "num_flips_done", "sault_time"}) # the num_flips_done parameter is a matching request parameter # for this response, so it produces a binary blob. possibly, # it should be changed to a ValueParameter... @@ -249,13 +260,16 @@ def test_code_table_params(self) -> None: decoded_resp_data["last_pos_response"][1] # type: ignore[index, call-overload] ["num_flips_done"], # type: ignore[index, call-overload] bytes([123])) + self.assertEqual( + decoded_resp_data["last_pos_response"][1] # type: ignore[index, call-overload] + ["sault_time"], # type: ignore[index, call-overload] + 249) # test the "backward flips grudgingly done" response resp_data = pr.encode( dizzyness_level=75, happiness_level=3, last_pos_response=("backward_grudging", { - 'dizzyness_level': 75, 'num_flips_done': 5, 'grumpiness_level': 150 })) @@ -310,14 +324,14 @@ def test_free_param_info(self) -> None: with patch("sys.stdout", stdout): pos_response.print_free_parameters_info() - expected_output = "forward_soberness_check: uint8\nnum_flips: uint8\n" + expected_output = "forward_soberness_check: uint8\nnum_flips: uint8\nsault_time: uint8\n" actual_output = stdout.getvalue() self.assertEqual(actual_output, expected_output) with patch("sys.stdout", stdout): neg_response.print_free_parameters_info() expected_output = ( - "forward_soberness_check: uint8\nnum_flips: uint8\nflips_successfully_done: uint8\n" + "forward_soberness_check: uint8\nnum_flips: uint8\nsault_time: uint8\nflips_successfully_done: uint8\n" ) actual_output = stdout.getvalue() self.assertEqual(actual_output, expected_output) @@ -336,9 +350,13 @@ def test_decode_response(self) -> None: f"There should be only one service for 0x0145 but there are: {messages}", ) m = messages[0] - self.assertEqual(m.coded_message, bytes([0xFA, 0x03])) + self.assertEqual(m.coded_message.hex(), "fa03ff") self.assertEqual(m.coding_object, pos_response) - self.assertEqual(m.param_dict, {"sid": 0xFA, "num_flips_done": bytearray([0x03])}) + self.assertEqual(m.param_dict, { + "sid": 0xFA, + "num_flips_done": bytearray([0x03]), + "sault_time": 255 + }) class TestNavigation(unittest.TestCase):