odxtools 10.1.1__py3-none-any.whl → 10.2.1__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 (112) hide show
  1. odxtools/addrdeffilter.py +33 -0
  2. odxtools/addrdefphyssegment.py +33 -0
  3. odxtools/checksum.py +67 -0
  4. odxtools/checksumresult.py +7 -0
  5. odxtools/database.py +24 -4
  6. odxtools/datablock.py +153 -0
  7. odxtools/datafile.py +23 -0
  8. odxtools/dataformat.py +39 -0
  9. odxtools/dataformatselection.py +9 -0
  10. odxtools/description.py +2 -5
  11. odxtools/diagdatadictionaryspec.py +1 -3
  12. odxtools/diaglayers/diaglayer.py +31 -11
  13. odxtools/diaglayers/ecuvariant.py +12 -19
  14. odxtools/diaglayers/hierarchyelement.py +5 -5
  15. odxtools/diaglayers/protocol.py +14 -0
  16. odxtools/direction.py +7 -0
  17. odxtools/ecumem.py +71 -0
  18. odxtools/ecumemconnector.py +136 -0
  19. odxtools/encryptcompressmethod.py +39 -0
  20. odxtools/encryptcompressmethodtype.py +13 -0
  21. odxtools/expectedident.py +40 -0
  22. odxtools/externflashdata.py +34 -0
  23. odxtools/filter.py +32 -0
  24. odxtools/flash.py +88 -0
  25. odxtools/flashclass.py +32 -0
  26. odxtools/flashdata.py +70 -0
  27. odxtools/fwchecksum.py +7 -0
  28. odxtools/fwsignature.py +7 -0
  29. odxtools/identdesc.py +54 -0
  30. odxtools/identvalue.py +32 -0
  31. odxtools/identvaluetype.py +14 -0
  32. odxtools/internflashdata.py +33 -0
  33. odxtools/loadfile.py +1 -1
  34. odxtools/mem.py +80 -0
  35. odxtools/modification.py +3 -2
  36. odxtools/negoffset.py +21 -0
  37. odxtools/odxlink.py +4 -2
  38. odxtools/ownident.py +38 -0
  39. odxtools/physicaltype.py +12 -10
  40. odxtools/physmem.py +52 -0
  41. odxtools/physsegment.py +42 -0
  42. odxtools/posoffset.py +21 -0
  43. odxtools/security.py +42 -0
  44. odxtools/securitymethod.py +7 -0
  45. odxtools/segment.py +63 -0
  46. odxtools/session.py +88 -0
  47. odxtools/sessiondesc.py +101 -0
  48. odxtools/sessionsubelemtype.py +14 -0
  49. odxtools/sizedeffilter.py +33 -0
  50. odxtools/sizedefphyssegment.py +33 -0
  51. odxtools/specialdata.py +2 -1
  52. odxtools/subcomponentparamconnector.py +1 -1
  53. odxtools/targetaddroffset.py +13 -0
  54. odxtools/templates/comparam-spec.odx-c.xml.jinja2 +1 -0
  55. odxtools/templates/comparam-subset.odx-cs.xml.jinja2 +1 -0
  56. odxtools/templates/diag_layer_container.odx-d.xml.jinja2 +2 -1
  57. odxtools/templates/flash.odx-f.xml.jinja2 +42 -0
  58. odxtools/templates/macros/printAdminData.xml.jinja2 +4 -4
  59. odxtools/templates/macros/printAudience.xml.jinja2 +3 -3
  60. odxtools/templates/macros/printChecksum.xml.jinja2 +36 -0
  61. odxtools/templates/macros/printComparam.xml.jinja2 +1 -1
  62. odxtools/templates/macros/printComparamRef.xml.jinja2 +1 -3
  63. odxtools/templates/macros/printCompuMethod.xml.jinja2 +1 -1
  64. odxtools/templates/macros/printDOP.xml.jinja2 +3 -3
  65. odxtools/templates/macros/printDatablock.xml.jinja2 +78 -0
  66. odxtools/templates/macros/printDiagComm.xml.jinja2 +2 -2
  67. odxtools/templates/macros/printDiagLayer.xml.jinja2 +2 -1
  68. odxtools/templates/macros/printDiagVariable.xml.jinja2 +4 -4
  69. odxtools/templates/macros/printDynDefinedSpec.xml.jinja2 +3 -3
  70. odxtools/templates/macros/printDynamicEndmarkerField.xml.jinja2 +2 -2
  71. odxtools/templates/macros/printDynamicLengthField.xml.jinja2 +2 -2
  72. odxtools/templates/macros/printEcuMem.xml.jinja2 +24 -0
  73. odxtools/templates/macros/printEcuMemConnector.xml.jinja2 +58 -0
  74. odxtools/templates/macros/printEndOfPdu.xml.jinja2 +1 -1
  75. odxtools/templates/macros/printEnvDataDesc.xml.jinja2 +1 -1
  76. odxtools/templates/macros/printExpectedIdent.xml.jinja2 +21 -0
  77. odxtools/templates/macros/printFlashdata.xml.jinja2 +43 -0
  78. odxtools/templates/macros/printIdentDesc.xml.jinja2 +17 -0
  79. odxtools/templates/macros/printMem.xml.jinja2 +35 -0
  80. odxtools/templates/macros/printMux.xml.jinja2 +3 -3
  81. odxtools/templates/macros/printOdxCategory.xml.jinja2 +4 -4
  82. odxtools/templates/macros/printOwnIdent.xml.jinja2 +17 -0
  83. odxtools/templates/macros/printParam.xml.jinja2 +4 -4
  84. odxtools/templates/macros/printParentRef.xml.jinja2 +1 -5
  85. odxtools/templates/macros/printPhysMem.xml.jinja2 +20 -0
  86. odxtools/templates/macros/printPhysSegment.xml.jinja2 +33 -0
  87. odxtools/templates/macros/printPreConditionStateRef.xml.jinja2 +1 -1
  88. odxtools/templates/macros/printProtStack.xml.jinja2 +1 -1
  89. odxtools/templates/macros/printProtocol.xml.jinja2 +1 -1
  90. odxtools/templates/macros/printSecurity.xml.jinja2 +37 -0
  91. odxtools/templates/macros/printSegment.xml.jinja2 +31 -0
  92. odxtools/templates/macros/printService.xml.jinja2 +3 -3
  93. odxtools/templates/macros/printSession.xml.jinja2 +45 -0
  94. odxtools/templates/macros/printSessionDesc.xml.jinja2 +40 -0
  95. odxtools/templates/macros/printSingleEcuJob.xml.jinja2 +3 -3
  96. odxtools/templates/macros/printSpecialData.xml.jinja2 +2 -2
  97. odxtools/templates/macros/printStateTransitionRef.xml.jinja2 +1 -1
  98. odxtools/templates/macros/printStaticField.xml.jinja2 +1 -1
  99. odxtools/templates/macros/printSubComponent.xml.jinja2 +3 -3
  100. odxtools/templates/macros/printTable.xml.jinja2 +6 -6
  101. odxtools/templates/macros/printUnitSpec.xml.jinja2 +2 -2
  102. odxtools/text.py +2 -6
  103. odxtools/utils.py +22 -1
  104. odxtools/validityfor.py +30 -0
  105. odxtools/version.py +2 -2
  106. odxtools/writepdxfile.py +70 -21
  107. {odxtools-10.1.1.dist-info → odxtools-10.2.1.dist-info}/METADATA +1 -1
  108. {odxtools-10.1.1.dist-info → odxtools-10.2.1.dist-info}/RECORD +112 -55
  109. {odxtools-10.1.1.dist-info → odxtools-10.2.1.dist-info}/WHEEL +1 -1
  110. {odxtools-10.1.1.dist-info → odxtools-10.2.1.dist-info}/entry_points.txt +0 -0
  111. {odxtools-10.1.1.dist-info → odxtools-10.2.1.dist-info}/licenses/LICENSE +0 -0
  112. {odxtools-10.1.1.dist-info → odxtools-10.2.1.dist-info}/top_level.txt +0 -0
odxtools/identvalue.py ADDED
@@ -0,0 +1,32 @@
1
+ # SPDX-License-Identifier: MIT
2
+ from dataclasses import dataclass
3
+ from xml.etree import ElementTree
4
+
5
+ from .exceptions import odxraise
6
+ from .identvaluetype import IdentValueType
7
+ from .odxdoccontext import OdxDocContext
8
+
9
+
10
+ @dataclass(kw_only=True)
11
+ class IdentValue:
12
+ """
13
+ Corresponds to IDENT-VALUE.
14
+ """
15
+
16
+ value: str
17
+
18
+ # note that the spec says this attribute is named "TYPE", but in
19
+ # python, "type" is a build-in function...
20
+ value_type: IdentValueType
21
+
22
+ @staticmethod
23
+ def from_et(et_element: ElementTree.Element, context: OdxDocContext) -> "IdentValue":
24
+ value = et_element.text or ""
25
+
26
+ try:
27
+ value_type = IdentValueType(et_element.attrib["TYPE"])
28
+ except Exception as e:
29
+ odxraise(f"Cannot parse IDENT-VALUE-TYPE: {e}")
30
+ value_type = None
31
+
32
+ return IdentValue(value=value, value_type=value_type)
@@ -0,0 +1,14 @@
1
+ # SPDX-License-Identifier: MIT
2
+ from enum import Enum
3
+
4
+ from .odxtypes import DataType
5
+
6
+
7
+ class IdentValueType(Enum):
8
+ A_UINT32 = "A_UINT32"
9
+ A_BYTEFIELD = "A_BYTEFIELD"
10
+ A_ASCIISTRING = "A_ASCIISTRING"
11
+
12
+ @property
13
+ def data_type(self) -> DataType:
14
+ return DataType(self.value)
@@ -0,0 +1,33 @@
1
+ # SPDX-License-Identifier: MIT
2
+ from dataclasses import dataclass
3
+ from typing import Any
4
+ from xml.etree import ElementTree
5
+
6
+ from .exceptions import odxrequire
7
+ from .flashdata import Flashdata
8
+ from .odxdoccontext import OdxDocContext
9
+ from .odxlink import OdxLinkDatabase, OdxLinkId
10
+ from .snrefcontext import SnRefContext
11
+ from .utils import dataclass_fields_asdict
12
+
13
+
14
+ @dataclass(kw_only=True)
15
+ class InternFlashdata(Flashdata):
16
+ data: str
17
+
18
+ @staticmethod
19
+ def from_et(et_element: ElementTree.Element, context: OdxDocContext) -> "InternFlashdata":
20
+ kwargs = dataclass_fields_asdict(Flashdata.from_et(et_element, context))
21
+
22
+ data = odxrequire(et_element.findtext("DATA"))
23
+
24
+ return InternFlashdata(data=data, **kwargs)
25
+
26
+ def _build_odxlinks(self) -> dict[OdxLinkId, Any]:
27
+ return super()._build_odxlinks()
28
+
29
+ def _resolve_odxlinks(self, odxlinks: OdxLinkDatabase) -> None:
30
+ super()._resolve_odxlinks(odxlinks)
31
+
32
+ def _resolve_snrefs(self, context: SnRefContext) -> None:
33
+ super()._resolve_snrefs(context)
odxtools/loadfile.py CHANGED
@@ -23,7 +23,7 @@ def load_odx_d_file(odx_d_file_name: str | Path) -> Database:
23
23
  def load_file(file_name: str | Path) -> Database:
24
24
  if str(file_name).lower().endswith(".pdx"):
25
25
  return load_pdx_file(str(file_name))
26
- elif str(file_name).lower().endswith(".odx-d"):
26
+ elif Path(file_name).suffix.lower().startswith(".odx"):
27
27
  return load_odx_d_file(str(file_name))
28
28
  else:
29
29
  raise RuntimeError(f"Could not guess the file format of file '{file_name}'!")
odxtools/mem.py ADDED
@@ -0,0 +1,80 @@
1
+ # SPDX-License-Identifier: MIT
2
+ from dataclasses import dataclass
3
+ from typing import Any
4
+ from xml.etree import ElementTree
5
+
6
+ from .datablock import Datablock
7
+ from .exceptions import odxraise
8
+ from .externflashdata import ExternFlashdata
9
+ from .flashdata import Flashdata
10
+ from .globals import xsi
11
+ from .internflashdata import InternFlashdata
12
+ from .nameditemlist import NamedItemList
13
+ from .odxdoccontext import OdxDocContext
14
+ from .odxlink import OdxLinkDatabase, OdxLinkId
15
+ from .session import Session
16
+ from .snrefcontext import SnRefContext
17
+
18
+
19
+ @dataclass(kw_only=True)
20
+ class Mem:
21
+ sessions: NamedItemList[Session]
22
+ datablocks: NamedItemList[Datablock]
23
+ flashdatas: NamedItemList[Flashdata]
24
+
25
+ @staticmethod
26
+ def from_et(et_element: ElementTree.Element, context: OdxDocContext) -> "Mem":
27
+ sessions = NamedItemList([
28
+ Session.from_et(sess_elem, context)
29
+ for sess_elem in et_element.iterfind("SESSIONS/SESSION")
30
+ ])
31
+
32
+ datablocks = NamedItemList([
33
+ Datablock.from_et(db_elem, context)
34
+ for db_elem in et_element.iterfind("DATABLOCKS/DATABLOCK")
35
+ ])
36
+
37
+ flashdatas: NamedItemList[Flashdata] = NamedItemList()
38
+ for flashdata_elem in et_element.iterfind("FLASHDATAS/FLASHDATA"):
39
+ flashdata_type = flashdata_elem.attrib.get(f"{xsi}type")
40
+ if flashdata_type == "INTERN-FLASHDATA":
41
+ flashdatas.append(InternFlashdata.from_et(flashdata_elem, context))
42
+ elif flashdata_type == "EXTERN-FLASHDATA":
43
+ flashdatas.append(ExternFlashdata.from_et(flashdata_elem, context))
44
+ else:
45
+ odxraise(f"Encountered unknown flashdata type {flashdata_type}")
46
+ flashdatas.append(Flashdata.from_et(flashdata_elem, context))
47
+
48
+ return Mem(
49
+ sessions=sessions,
50
+ datablocks=datablocks,
51
+ flashdatas=flashdatas,
52
+ )
53
+
54
+ def _build_odxlinks(self) -> dict[OdxLinkId, Any]:
55
+ odxlinks = {}
56
+
57
+ for session in self.sessions:
58
+ odxlinks.update(session._build_odxlinks())
59
+ for datablock in self.datablocks:
60
+ odxlinks.update(datablock._build_odxlinks())
61
+ for flashdata in self.flashdatas:
62
+ odxlinks.update(flashdata._build_odxlinks())
63
+
64
+ return odxlinks
65
+
66
+ def _resolve_odxlinks(self, odxlinks: OdxLinkDatabase) -> None:
67
+ for session in self.sessions:
68
+ session._resolve_odxlinks(odxlinks)
69
+ for datablock in self.datablocks:
70
+ datablock._resolve_odxlinks(odxlinks)
71
+ for flashdata in self.flashdatas:
72
+ flashdata._resolve_odxlinks(odxlinks)
73
+
74
+ def _resolve_snrefs(self, context: SnRefContext) -> None:
75
+ for session in self.sessions:
76
+ session._resolve_snrefs(context)
77
+ for datablock in self.datablocks:
78
+ datablock._resolve_snrefs(context)
79
+ for flashdata in self.flashdatas:
80
+ flashdata._resolve_snrefs(context)
odxtools/modification.py CHANGED
@@ -7,6 +7,7 @@ from .exceptions import odxrequire
7
7
  from .odxdoccontext import OdxDocContext
8
8
  from .odxlink import OdxLinkDatabase, OdxLinkId
9
9
  from .snrefcontext import SnRefContext
10
+ from .utils import strip_indent
10
11
 
11
12
 
12
13
  @dataclass(kw_only=True)
@@ -16,8 +17,8 @@ class Modification:
16
17
 
17
18
  @staticmethod
18
19
  def from_et(et_element: ElementTree.Element, context: OdxDocContext) -> "Modification":
19
- change = odxrequire(et_element.findtext("CHANGE"))
20
- reason = et_element.findtext("REASON")
20
+ change = odxrequire(strip_indent(et_element.findtext("CHANGE")))
21
+ reason = strip_indent(et_element.findtext("REASON"))
21
22
 
22
23
  return Modification(change=change, reason=reason)
23
24
 
odxtools/negoffset.py ADDED
@@ -0,0 +1,21 @@
1
+ # SPDX-License-Identifier: MIT
2
+ from dataclasses import dataclass
3
+ from xml.etree import ElementTree
4
+
5
+ from .exceptions import odxrequire
6
+ from .odxdoccontext import OdxDocContext
7
+ from .targetaddroffset import TargetAddrOffset
8
+ from .utils import dataclass_fields_asdict, read_hex_binary
9
+
10
+
11
+ @dataclass(kw_only=True)
12
+ class NegOffset(TargetAddrOffset):
13
+ negative_offset: int
14
+
15
+ @staticmethod
16
+ def from_et(et_element: ElementTree.Element, context: OdxDocContext) -> "NegOffset":
17
+ kwargs = dataclass_fields_asdict(TargetAddrOffset.from_et(et_element, context))
18
+
19
+ negative_offset = odxrequire(read_hex_binary(et_element.find("NEGATIVE-OFFSET")))
20
+
21
+ return NegOffset(negative_offset=negative_offset, **kwargs)
odxtools/odxlink.py CHANGED
@@ -204,8 +204,10 @@ class OdxLinkDatabase:
204
204
  # locate an object exhibiting with the referenced local ID
205
205
  # in the ID database for the document fragment
206
206
  if (obj := doc_frag_db.get(ref.ref_id)) is not None:
207
- if expected_type is not None:
208
- odxassert(isinstance(obj, expected_type))
207
+ if expected_type is not None and not isinstance(obj, expected_type):
208
+ odxraise(f"Referenced object for link {ref.ref_id} is of type "
209
+ f"{type(obj).__name__} which is not a subclass of expected "
210
+ f"type {expected_type.__name__}")
209
211
 
210
212
  return obj
211
213
 
odxtools/ownident.py ADDED
@@ -0,0 +1,38 @@
1
+ # SPDX-License-Identifier: MIT
2
+ from dataclasses import dataclass
3
+ from typing import Any
4
+ from xml.etree import ElementTree
5
+
6
+ from .element import IdentifiableElement
7
+ from .exceptions import odxrequire
8
+ from .identvalue import IdentValue
9
+ from .odxdoccontext import OdxDocContext
10
+ from .odxlink import OdxLinkDatabase, OdxLinkId
11
+ from .snrefcontext import SnRefContext
12
+ from .utils import dataclass_fields_asdict
13
+
14
+
15
+ @dataclass(kw_only=True)
16
+ class OwnIdent(IdentifiableElement):
17
+ """
18
+ Corresponds to OWN-IDENT.
19
+ """
20
+
21
+ ident_value: IdentValue
22
+
23
+ @staticmethod
24
+ def from_et(et_element: ElementTree.Element, context: OdxDocContext) -> "OwnIdent":
25
+ kwargs = dataclass_fields_asdict(IdentifiableElement.from_et(et_element, context))
26
+
27
+ ident_value = IdentValue.from_et(odxrequire(et_element.find("IDENT-VALUE")), context)
28
+
29
+ return OwnIdent(ident_value=ident_value, **kwargs)
30
+
31
+ def _build_odxlinks(self) -> dict[OdxLinkId, Any]:
32
+ return {self.odx_id: self}
33
+
34
+ def _resolve_odxlinks(self, odxlinks: OdxLinkDatabase) -> None:
35
+ pass
36
+
37
+ def _resolve_snrefs(self, context: SnRefContext) -> None:
38
+ pass
odxtools/physicaltype.py CHANGED
@@ -2,7 +2,7 @@
2
2
  from dataclasses import dataclass
3
3
  from xml.etree import ElementTree
4
4
 
5
- from .exceptions import odxraise
5
+ from .exceptions import odxraise, odxrequire
6
6
  from .odxdoccontext import OdxDocContext
7
7
  from .odxtypes import DataType
8
8
  from .radix import Radix
@@ -31,27 +31,29 @@ class PhysicalType:
31
31
  PhysicalType(DataType.A_FLOAT64, precision=2)
32
32
  """
33
33
 
34
+ #: Number of digits after the decimal point to display to the user
35
+ #: The precision is only applicable if the base data type is
36
+ #: A_FLOAT32 or A_FLOAT64.
34
37
  precision: int | None = None
35
- """Number of digits after the decimal point to display to the user
36
- The precision is only applicable if the base data type is A_FLOAT32 or A_FLOAT64.
37
- """
38
38
 
39
39
  base_data_type: DataType
40
40
 
41
+ #: The display radix defines how integers are displayed to the
42
+ #: user. The display radix is only applicable if the base data type
43
+ #: is A_UINT32.
41
44
  display_radix: Radix | None = None
42
- """The display radix defines how integers are displayed to the user.
43
- The display radix is only applicable if the base data type is A_UINT32.
44
- """
45
45
 
46
46
  @staticmethod
47
47
  def from_et(et_element: ElementTree.Element, context: OdxDocContext) -> "PhysicalType":
48
48
  precision_str = et_element.findtext("PRECISION")
49
49
  precision = int(precision_str) if precision_str is not None else None
50
50
 
51
- base_data_type_str = et_element.get("BASE-DATA-TYPE")
52
- if base_data_type_str not in DataType.__members__:
51
+ base_data_type_str = odxrequire(et_element.attrib.get("BASE-DATA-TYPE"))
52
+ try:
53
+ base_data_type = DataType(base_data_type_str)
54
+ except ValueError:
53
55
  odxraise(f"Encountered unknown base data type '{base_data_type_str}'")
54
- base_data_type = DataType(base_data_type_str)
56
+ base_data_type = None
55
57
 
56
58
  display_radix_str = et_element.get("DISPLAY-RADIX")
57
59
  if display_radix_str is not None:
odxtools/physmem.py ADDED
@@ -0,0 +1,52 @@
1
+ # SPDX-License-Identifier: MIT
2
+ from dataclasses import dataclass
3
+ from typing import Any
4
+ from xml.etree import ElementTree
5
+
6
+ from .addrdefphyssegment import AddrdefPhysSegment
7
+ from .element import IdentifiableElement
8
+ from .globals import xsi
9
+ from .nameditemlist import NamedItemList
10
+ from .odxdoccontext import OdxDocContext
11
+ from .odxlink import OdxLinkDatabase, OdxLinkId
12
+ from .physsegment import PhysSegment
13
+ from .sizedefphyssegment import SizedefPhysSegment
14
+ from .snrefcontext import SnRefContext
15
+ from .utils import dataclass_fields_asdict
16
+
17
+
18
+ @dataclass(kw_only=True)
19
+ class PhysMem(IdentifiableElement):
20
+ phys_segments: NamedItemList[PhysSegment]
21
+
22
+ @staticmethod
23
+ def from_et(et_element: ElementTree.Element, context: OdxDocContext) -> "PhysMem":
24
+ kwargs = dataclass_fields_asdict(IdentifiableElement.from_et(et_element, context))
25
+
26
+ phys_segments: NamedItemList[PhysSegment] = NamedItemList()
27
+ for phys_segment_elem in et_element.iterfind("PHYS-SEGMENTS/PHYS-SEGMENT"):
28
+ phys_segment_type = phys_segment_elem.attrib.get(f"{xsi}type")
29
+ if phys_segment_type == "ADDRDEF-PHYS-SEGMENT":
30
+ phys_segments.append(AddrdefPhysSegment.from_et(phys_segment_elem, context))
31
+ elif phys_segment_type == "SIZEDEF-PHYS-SEGMENT":
32
+ phys_segments.append(SizedefPhysSegment.from_et(phys_segment_elem, context))
33
+ else:
34
+ phys_segments.append(PhysSegment.from_et(phys_segment_elem, context))
35
+
36
+ return PhysMem(phys_segments=phys_segments, **kwargs)
37
+
38
+ def _build_odxlinks(self) -> dict[OdxLinkId, Any]:
39
+ odxlinks = {self.odx_id: self}
40
+
41
+ for phys_segment in self.phys_segments:
42
+ odxlinks.update(phys_segment._build_odxlinks())
43
+
44
+ return odxlinks
45
+
46
+ def _resolve_odxlinks(self, odxlinks: OdxLinkDatabase) -> None:
47
+ for phys_segment in self.phys_segments:
48
+ phys_segment._resolve_odxlinks(odxlinks)
49
+
50
+ def _resolve_snrefs(self, context: SnRefContext) -> None:
51
+ for phys_segment in self.phys_segments:
52
+ phys_segment._resolve_snrefs(context)
@@ -0,0 +1,42 @@
1
+ # SPDX-License-Identifier: MIT
2
+ from dataclasses import dataclass
3
+ from typing import Any
4
+ from xml.etree import ElementTree
5
+
6
+ from .element import IdentifiableElement
7
+ from .exceptions import odxrequire
8
+ from .odxdoccontext import OdxDocContext
9
+ from .odxlink import OdxLinkDatabase, OdxLinkId
10
+ from .snrefcontext import SnRefContext
11
+ from .utils import dataclass_fields_asdict, read_hex_binary
12
+
13
+
14
+ @dataclass(kw_only=True)
15
+ class PhysSegment(IdentifiableElement):
16
+ fillbyte: int | None = None
17
+ block_size: int | None = None
18
+ start_address: int
19
+
20
+ @staticmethod
21
+ def from_et(et_element: ElementTree.Element, context: OdxDocContext) -> "PhysSegment":
22
+ kwargs = dataclass_fields_asdict(IdentifiableElement.from_et(et_element, context))
23
+
24
+ fillbyte = read_hex_binary(et_element.find("FILLBYTE"))
25
+ block_size = 0
26
+ if (bs_elem := et_element.find("BLOCK-SIZE")) is not None:
27
+ block_size = int(odxrequire(bs_elem.text) or "0")
28
+ start_address = odxrequire(read_hex_binary(et_element.find("START-ADDRESS")))
29
+
30
+ return PhysSegment(
31
+ fillbyte=fillbyte, block_size=block_size, start_address=start_address, **kwargs)
32
+
33
+ def _build_odxlinks(self) -> dict[OdxLinkId, Any]:
34
+ odxlinks = {self.odx_id: self}
35
+
36
+ return odxlinks
37
+
38
+ def _resolve_odxlinks(self, odxlinks: OdxLinkDatabase) -> None:
39
+ pass
40
+
41
+ def _resolve_snrefs(self, context: SnRefContext) -> None:
42
+ pass
odxtools/posoffset.py ADDED
@@ -0,0 +1,21 @@
1
+ # SPDX-License-Identifier: MIT
2
+ from dataclasses import dataclass
3
+ from xml.etree import ElementTree
4
+
5
+ from .exceptions import odxrequire
6
+ from .odxdoccontext import OdxDocContext
7
+ from .targetaddroffset import TargetAddrOffset
8
+ from .utils import dataclass_fields_asdict, read_hex_binary
9
+
10
+
11
+ @dataclass(kw_only=True)
12
+ class PosOffset(TargetAddrOffset):
13
+ positive_offset: int
14
+
15
+ @staticmethod
16
+ def from_et(et_element: ElementTree.Element, context: OdxDocContext) -> "PosOffset":
17
+ kwargs = dataclass_fields_asdict(TargetAddrOffset.from_et(et_element, context))
18
+
19
+ positive_offset = odxrequire(read_hex_binary(et_element.find("POSITIVE-OFFSET")))
20
+
21
+ return PosOffset(positive_offset=positive_offset, **kwargs)
odxtools/security.py ADDED
@@ -0,0 +1,42 @@
1
+ # SPDX-License-Identifier: MIT
2
+ from dataclasses import dataclass
3
+ from xml.etree import ElementTree
4
+
5
+ from .fwchecksum import FwChecksum
6
+ from .fwsignature import FwSignature
7
+ from .odxdoccontext import OdxDocContext
8
+ from .securitymethod import SecurityMethod
9
+ from .validityfor import ValidityFor
10
+
11
+
12
+ @dataclass(kw_only=True)
13
+ class Security:
14
+ security_method: SecurityMethod | None = None
15
+ fw_signature: FwSignature | None = None
16
+ fw_checksum: FwChecksum | None = None
17
+ validity_for: ValidityFor | None = None
18
+
19
+ @staticmethod
20
+ def from_et(et_element: ElementTree.Element, context: OdxDocContext) -> "Security":
21
+ security_method = None
22
+ if (sm_elem := et_element.find("SECURITY-METHOD")) is not None:
23
+ security_method = SecurityMethod.from_et(sm_elem, context)
24
+
25
+ fw_signature = None
26
+ if (fws_elem := et_element.find("FW-SIGNATURE")) is not None:
27
+ fw_signature = FwSignature.from_et(fws_elem, context)
28
+
29
+ fw_checksum = None
30
+ if (fwcs_elem := et_element.find("FW-CHECKSUM")) is not None:
31
+ fw_checksum = FwChecksum.from_et(fwcs_elem, context)
32
+
33
+ validity_for = None
34
+ if (val_elem := et_element.find("VALIDITY-FOR")) is not None:
35
+ validity_for = ValidityFor.from_et(val_elem, context)
36
+
37
+ return Security(
38
+ security_method=security_method,
39
+ fw_signature=fw_signature,
40
+ fw_checksum=fw_checksum,
41
+ validity_for=validity_for,
42
+ )
@@ -0,0 +1,7 @@
1
+ # SPDX-License-Identifier: MIT
2
+ from .validityfor import ValidityFor
3
+
4
+ # Note that the ODX specification specifies a separate tag for this,
5
+ # but this tag is identical to VALIDITY-FOR, so let's use a type alias
6
+ # to reduce the amount of copy-and-pasted code
7
+ SecurityMethod = ValidityFor
odxtools/segment.py ADDED
@@ -0,0 +1,63 @@
1
+ # SPDX-License-Identifier: MIT
2
+ from dataclasses import dataclass
3
+ from typing import Any
4
+ from xml.etree import ElementTree
5
+
6
+ from .element import IdentifiableElement
7
+ from .encryptcompressmethod import EncryptCompressMethod
8
+ from .exceptions import odxrequire
9
+ from .odxdoccontext import OdxDocContext
10
+ from .odxlink import OdxLinkDatabase, OdxLinkId
11
+ from .snrefcontext import SnRefContext
12
+ from .utils import dataclass_fields_asdict, read_hex_binary
13
+
14
+
15
+ @dataclass(kw_only=True)
16
+ class Segment(IdentifiableElement):
17
+ source_start_address: int
18
+ compressed_size: int | None = None
19
+
20
+ # exactly one of the two next fields must be not None
21
+ uncompressed_size: int | None = None
22
+ source_end_address: int | None = None
23
+
24
+ encrypt_compress_method: EncryptCompressMethod | None = None
25
+
26
+ @staticmethod
27
+ def from_et(et_element: ElementTree.Element, context: OdxDocContext) -> "Segment":
28
+ kwargs = dataclass_fields_asdict(IdentifiableElement.from_et(et_element, context))
29
+
30
+ source_start_address = odxrequire(read_hex_binary(et_element.find("SOURCE-START-ADDRESS")))
31
+ compressed_size = None
32
+ if (cs_elem := et_element.find("COMPRESSED-SIZE")) is not None:
33
+ compressed_size = int(odxrequire(cs_elem.text) or "0")
34
+
35
+ # exactly one of the two next fields must be not None
36
+ uncompressed_size = None
37
+ if (ucs_elem := et_element.find("UNCOMPRESSED-SIZE")) is not None:
38
+ uncompressed_size = int(odxrequire(ucs_elem.text) or "0")
39
+ source_end_address = read_hex_binary(et_element.find("SOURCE-END-ADDRESS"))
40
+
41
+ encrypt_compress_method = None
42
+ if (encrypt_compress_method_elem := et_element.find("ENCRYPT-COMPRESS-METHOD")) is not None:
43
+ encrypt_compress_method = EncryptCompressMethod.from_et(encrypt_compress_method_elem,
44
+ context)
45
+
46
+ return Segment(
47
+ source_start_address=source_start_address,
48
+ compressed_size=compressed_size,
49
+ uncompressed_size=uncompressed_size,
50
+ source_end_address=source_end_address,
51
+ encrypt_compress_method=encrypt_compress_method,
52
+ **kwargs)
53
+
54
+ def _build_odxlinks(self) -> dict[OdxLinkId, Any]:
55
+ odxlinks = {self.odx_id: self}
56
+
57
+ return odxlinks
58
+
59
+ def _resolve_odxlinks(self, odxlinks: OdxLinkDatabase) -> None:
60
+ pass
61
+
62
+ def _resolve_snrefs(self, context: SnRefContext) -> None:
63
+ pass
odxtools/session.py ADDED
@@ -0,0 +1,88 @@
1
+ # SPDX-License-Identifier: MIT
2
+ from dataclasses import dataclass, field
3
+ from typing import Any
4
+ from xml.etree import ElementTree
5
+
6
+ from .checksum import Checksum
7
+ from .datablock import Datablock
8
+ from .element import IdentifiableElement
9
+ from .expectedident import ExpectedIdent
10
+ from .nameditemlist import NamedItemList
11
+ from .odxdoccontext import OdxDocContext
12
+ from .odxlink import OdxLinkDatabase, OdxLinkId, OdxLinkRef
13
+ from .security import Security
14
+ from .snrefcontext import SnRefContext
15
+ from .specialdatagroup import SpecialDataGroup
16
+ from .utils import dataclass_fields_asdict
17
+
18
+
19
+ @dataclass(kw_only=True)
20
+ class Session(IdentifiableElement):
21
+ expected_idents: NamedItemList[ExpectedIdent] = field(default_factory=NamedItemList)
22
+ checksums: NamedItemList[Checksum] = field(default_factory=NamedItemList)
23
+ securities: list[Security] = field(default_factory=list)
24
+ datablock_refs: list[OdxLinkRef] = field(default_factory=list)
25
+ sdgs: list[SpecialDataGroup] = field(default_factory=list)
26
+
27
+ @property
28
+ def datablocks(self) -> NamedItemList[Datablock]:
29
+ return self._datablocks
30
+
31
+ @staticmethod
32
+ def from_et(et_element: ElementTree.Element, context: OdxDocContext) -> "Session":
33
+
34
+ kwargs = dataclass_fields_asdict(IdentifiableElement.from_et(et_element, context))
35
+
36
+ expected_idents = NamedItemList([
37
+ ExpectedIdent.from_et(el, context)
38
+ for el in et_element.iterfind("EXPECTED-IDENTS/EXPECTED-IDENT")
39
+ ])
40
+ checksums = NamedItemList(
41
+ [Checksum.from_et(el, context) for el in et_element.iterfind("CHECKSUMS/CHECKSUM")])
42
+ securities = [
43
+ Security.from_et(el, context) for el in et_element.iterfind("SECURITYS/SECURITY")
44
+ ]
45
+ datablock_refs = [
46
+ OdxLinkRef.from_et(el, context)
47
+ for el in et_element.iterfind("DATABLOCK-REFS/DATABLOCK-REF")
48
+ ]
49
+ sdgs = [SpecialDataGroup.from_et(sdge, context) for sdge in et_element.iterfind("SDGS/SDG")]
50
+
51
+ return Session(
52
+ expected_idents=expected_idents,
53
+ checksums=checksums,
54
+ securities=securities,
55
+ datablock_refs=datablock_refs,
56
+ sdgs=sdgs,
57
+ **kwargs)
58
+
59
+ def _build_odxlinks(self) -> dict[OdxLinkId, Any]:
60
+ odxlinks = {self.odx_id: self}
61
+
62
+ for ei in self.expected_idents:
63
+ odxlinks.update(ei._build_odxlinks())
64
+ for cs in self.checksums:
65
+ odxlinks.update(cs._build_odxlinks())
66
+ for sdg in self.sdgs:
67
+ odxlinks.update(sdg._build_odxlinks())
68
+
69
+ return odxlinks
70
+
71
+ def _resolve_odxlinks(self, odxlinks: OdxLinkDatabase) -> None:
72
+ self._datablocks = NamedItemList(
73
+ [odxlinks.resolve(ref, Datablock) for ref in self.datablock_refs])
74
+
75
+ for ei in self.expected_idents:
76
+ ei._resolve_odxlinks(odxlinks)
77
+ for cs in self.checksums:
78
+ cs._resolve_odxlinks(odxlinks)
79
+ for sdg in self.sdgs:
80
+ sdg._resolve_odxlinks(odxlinks)
81
+
82
+ def _resolve_snrefs(self, context: SnRefContext) -> None:
83
+ for ei in self.expected_idents:
84
+ ei._resolve_snrefs(context)
85
+ for cs in self.checksums:
86
+ cs._resolve_snrefs(context)
87
+ for sdg in self.sdgs:
88
+ sdg._resolve_snrefs(context)