diff --git a/docs/source/pages/knowledge_base.rst b/docs/source/pages/knowledge_base.rst index 91bb49e4..bcd18042 100644 --- a/docs/source/pages/knowledge_base.rst +++ b/docs/source/pages/knowledge_base.rst @@ -10,6 +10,7 @@ understanding of the UDS protocol itself. knowledge_base/osi_model.rst knowledge_base/client_server_model.rst knowledge_base/addressing.rst + knowledge_base/states.rst knowledge_base/service.rst knowledge_base/diagnostic_message.rst knowledge_base/packet.rst diff --git a/docs/source/pages/knowledge_base/states.rst b/docs/source/pages/knowledge_base/states.rst new file mode 100644 index 00000000..35eaf902 --- /dev/null +++ b/docs/source/pages/knowledge_base/states.rst @@ -0,0 +1,70 @@ +.. _knowledge-base-states: + +States +====== +In UDS, the behavior of a server (ECU) depends on its current operating state. Many diagnostic services are +only available when specific conditions are satisfied. For example, a service may only be supported in +Extended Diagnostic Session, require an unlocked security level (to protect from unwanted access), +or be available only when the engine is stopped from safety reasons. + +The current state of the ECU therefore determines whether a diagnostic request is accepted or rejected. + +Typical diagnostic states include: + +* `Diagnostic Session`_ +* `Security Access`_ +* `Authentication`_ + +In addition to these diagnostic states, many ECUs also consider vehicle-specific conditions, such as: + +* whether the ignition (KL15) is ON +* whether the engine is running +* vehicle speed +* battery voltage +* gear selector position +* parking brake status + +Manufacturers may define additional ECU-specific states depending on the functionality being protected. + + +.. _knowledge-base-state-session: + +Diagnostic Session +------------------ +The Diagnostic Session defines the current operating mode of the ECU. It is controlled through the +:ref:`DiagnosticSessionControl ` service. + +Only one diagnostic session can be active at any given time. +After an ECU reset or power-up, the server enters the **Default Session**. +A client may request a different session, such as Programming Session or Extended Diagnostic Session, +to gain access to additional diagnostic features. + + +.. _knowledge-base-state-security-access: + +Security Access +--------------- +Security Access protects diagnostic functionality that should not be available to every client. +It is controlled through the :ref:`SecurityAccess ` service. + +The server starts in a **locked** state. A client may unlock one or more security levels by +successfully completing the Security Access sequence (`requestSeed` → `sendKey`). + +The unlocked security level shall be cleared whenever the diagnostic session changes or the ECU is reset. + + +.. _knowledge-base-state-authentication: + +Authentication +-------------- +Authentication provides an identity-based mechanism for authorizing diagnostic communication. +It is controlled through the :ref:`Authentication ` service. + +Unlike `Security Access`_, which is based on a challenge-response algorithm using a seed and key, +Authentication establishes the identity of the communicating parties, typically using cryptographic credentials. + +Authentication may be performed in either direction. +Depending on the ECU implementation and requested SubFunction, the client may authenticate the server, +the server may authenticate the client, or both parties may authenticate each other (bi-directional authentication). + +Some diagnostic services may only be available after successful authentication. diff --git a/docs/source/pages/user_guide.rst b/docs/source/pages/user_guide.rst index 7e650395..a8be1254 100644 --- a/docs/source/pages/user_guide.rst +++ b/docs/source/pages/user_guide.rst @@ -11,6 +11,7 @@ for getting started. user_guide/addressing.rst user_guide/client.rst user_guide/message_translation.rst + user_guide/diagnostic_configuration.rst user_guide/logging.rst user_guide/can.rst user_guide/custom.rst diff --git a/docs/source/pages/user_guide/diagnostic_configuration.rst b/docs/source/pages/user_guide/diagnostic_configuration.rst new file mode 100644 index 00000000..98189958 --- /dev/null +++ b/docs/source/pages/user_guide/diagnostic_configuration.rst @@ -0,0 +1,141 @@ +.. _implementation-ecu-diagnostic-configuration: + +Diagnostic Configuration +======================== +The Diagnostic Configuration module provides a mechanism for describing the conditions under which an ECU supports +diagnostic messages. It allows applications to model ECU operating states and define the availability of +diagnostic functions depending on the current state. + +The implementation is located in the :mod:`uds.diagnostic_configuration` package and consists of +the following components: + +- `State`_ +- `States Definitions`_ +- `ECU Diagnostic Configuration`_ + + +State +----- +The :class:`~uds.diagnostic_configuration.state.State` class represents a single +:ref:`ECU state ` that may affect the availability of diagnostic functions. +A state has a name, a predefined set of allowed values, and a current value representing the ECU's current operating +condition. + +Attributes: + +- :attr:`~uds.diagnostic_configuration.state.State.name` - name of a state +- :attr:`~uds.diagnostic_configuration.state.State.possible_values` - collection of all values the state can assume +- :attr:`~uds.diagnostic_configuration.state.State.current_value` - current value of the state + +**Example code:** + + .. code-block:: python + + import uds + + # create an example state describing the active diagnostic session + session = uds.diagnostic_configuration.State(name="Diagnostic Session", + possible_values={"Default", "Programming", "Extended"}) + + # change the current session + session.current_value = "Default" + + # mark the current session as undefined + session.current_value = None + + +States Definitions +------------------ +The :mod:`uds.diagnostic_configuration.state_definitions` module provides predefined +:class:`~uds.diagnostic_configuration.state.State` objects representing the most common ECU states used in +UDS diagnostic communication. These definitions can be used directly or serve as a starting point for creating custom +diagnostic configurations. + +The following state definitions are provided: + +- :obj:`~uds.diagnostic_configuration.state_definitions.DEFAULT_DIAGNOSTIC_SESSION_STATE` + - current diagnostic session +- :obj:`~uds.diagnostic_configuration.state_definitions.DEFAULT_SECURITY_ACCESS_STATE` + - currently unlocked security access level +- :obj:`~uds.diagnostic_configuration.state_definitions.DEFAULT_AUTHENTICATION_STATE` + - current authentication state +- :obj:`~uds.diagnostic_configuration.state_definitions.DEFAULT_IGNITION_STATE` + - current ignition status +- :obj:`~uds.diagnostic_configuration.state_definitions.DEFAULT_ENGINE_STATE` + - current engine state +- :obj:`~uds.diagnostic_configuration.state_definitions.DEFAULT_SECURED_TRANSMISSION_STATE` + – indicates whether secured data transmission is active +- :obj:`~uds.diagnostic_configuration.state_definitions.DEFAULT_ADDRESSING_TYPE_STATE` + – current addressing type (physical or functional) + + +ECU Diagnostic Configuration +---------------------------- +The :class:`~uds.diagnostic_configuration.ecu_configuration.EcuDiagnosticConfiguration` class is the central +component of the Diagnostic Configuration module. It stores the ECU states relevant for diagnostic communication +together with the restrictions that determine when diagnostic services, sub-functions, DID and RID are available. + +Restrictions can be defined at multiple levels of specificity: + +- Service Identifier (SID) +- Sub-function (for a given SID) +- Data Identifier (DID, for a given SID) +- Routine Identifier (RID, for a given SID) + +More specific restrictions complement or override the restrictions defined at higher levels. + +Attributes: + +- :attr:`~uds.diagnostic_configuration.ecu_configuration.EcuDiagnosticConfiguration.states` + - configured ECU states +- :attr:`~uds.diagnostic_configuration.ecu_configuration.EcuDiagnosticConfiguration.states_names` + - names of all configured states +- :attr:`~uds.diagnostic_configuration.ecu_configuration.EcuDiagnosticConfiguration.states_mapping` + - mapping from state name to :class:`~uds.diagnostic_configuration.state.State` object +- :attr:`~uds.diagnostic_configuration.ecu_configuration.EcuDiagnosticConfiguration.sid_restrictions` + - restrictions defined for diagnostic services (SIDs) +- :attr:`~uds.diagnostic_configuration.ecu_configuration.EcuDiagnosticConfiguration.subfunction_restrictions` + - restrictions defined for service sub-functions +- :attr:`~uds.diagnostic_configuration.ecu_configuration.EcuDiagnosticConfiguration.did_restrictions` + - restrictions defined for Data Identifiers (DIDs) +- :attr:`~uds.diagnostic_configuration.ecu_configuration.EcuDiagnosticConfiguration.rid_restrictions` + - restrictions defined for Routine Identifiers (RIDs) + +Methods: + +- :meth:`~uds.diagnostic_configuration.ecu_configuration.EcuDiagnosticConfiguration.combine_restrictions` + - combines multiple restriction definitions into a single effective restriction +- :meth:`~uds.diagnostic_configuration.ecu_configuration.EcuDiagnosticConfiguration.get_restrictions` + - returns the effective restrictions for a diagnostic message + + +**Example code:** + + .. code-block:: python + + import uds + + # create an example (simplified) ECU Configuration + ecu_config = uds.diagnostic_configuration.EcuDiagnosticConfiguration( + states=(uds.diagnostic_configuration.DEFAULT_DIAGNOSTIC_SESSION_STATE, + uds.diagnostic_configuration.DEFAULT_SECURITY_ACCESS_STATE, + uds.diagnostic_configuration.DEFAULT_ADDRESSING_TYPE_STATE), + sid_restrictions={ + 0x22: { + "Session": {0x03}, + }}, + did_restrictions={ + 0x22: { + 0xF190: { + "Session": {0x03}, + "SecurityAccess": {0x01}, + "AddressingType": {uds.addressing.AddressingType.PHYSICAL}} + }}, + rid_restrictions={}, + subfunction_restrictions={}) + + # Restrictions for ReadDataByIdentifier (0x22) + ecu_config.sid_restrictions[0x22] + + # Restrictions for 22 (ReadDataByIdentifier) F1 90 (DID 0xF190) message + ecu_config.get_restrictions([0x22, 0xF1, 0x90]) diff --git a/uds/__init__.py b/uds/__init__.py index 8ad692dd..d7ca60dc 100644 --- a/uds/__init__.py +++ b/uds/__init__.py @@ -22,6 +22,7 @@ "can", "client", "translator", + "diagnostic_configuration", "message", "packet", "segmentation", diff --git a/uds/diagnostic_configuration/__init__.py b/uds/diagnostic_configuration/__init__.py index a714ce0c..375fe1bd 100644 --- a/uds/diagnostic_configuration/__init__.py +++ b/uds/diagnostic_configuration/__init__.py @@ -11,6 +11,7 @@ DEFAULT_AUTHENTICATION_STATE, DEFAULT_DIAGNOSTIC_SESSION_STATE, DEFAULT_ENGINE_STATE, + DEFAULT_IGNITION_STATE, DEFAULT_SECURED_TRANSMISSION_STATE, DEFAULT_SECURITY_ACCESS_STATE, ) diff --git a/uds/diagnostic_configuration/ecu_configuration.py b/uds/diagnostic_configuration/ecu_configuration.py index 15233d45..f95b8dee 100644 --- a/uds/diagnostic_configuration/ecu_configuration.py +++ b/uds/diagnostic_configuration/ecu_configuration.py @@ -66,13 +66,13 @@ def __getitem__(self, item: str) -> State: @property def states(self) -> Set[State]: - """Get ECU states that are relevant for diagnostic communication.""" + """Get :ref:`ECU states ` relevant for diagnostic communication.""" return self.__states @states.setter def states(self, states: Collection[State]) -> None: """ - Set ECU states relevant for diagnostic communication. + Set :ref:`ECU states ` relevant for diagnostic communication. :param states: ECU states relevant for diagnostic communication. """ diff --git a/uds/diagnostic_configuration/state.py b/uds/diagnostic_configuration/state.py index 01388518..9fb5d042 100644 --- a/uds/diagnostic_configuration/state.py +++ b/uds/diagnostic_configuration/state.py @@ -1,4 +1,4 @@ -"""Implementation for diagnostic communication state.""" +"""Implementation for :ref:`diagnostic communication state `.""" __all__ = ["State"] diff --git a/uds/diagnostic_configuration/state_definitions.py b/uds/diagnostic_configuration/state_definitions.py index bce2a7ef..001636e1 100644 --- a/uds/diagnostic_configuration/state_definitions.py +++ b/uds/diagnostic_configuration/state_definitions.py @@ -1,28 +1,45 @@ -"""Definitions of typical diagnostic communication states.""" +"""Predefined State objects representing common :ref:`diagnostic communication states `.""" __all__ = ["DEFAULT_DIAGNOSTIC_SESSION_STATE", "DEFAULT_SECURITY_ACCESS_STATE", "DEFAULT_AUTHENTICATION_STATE", - "DEFAULT_SECURED_TRANSMISSION_STATE", + "DEFAULT_IGNITION_STATE", "DEFAULT_ENGINE_STATE", + "DEFAULT_SECURED_TRANSMISSION_STATE", "DEFAULT_ADDRESSING_TYPE_STATE"] from uds.addressing import AddressingType +from uds.utilities import OFF_ON_MAPPING from .state import State DEFAULT_DIAGNOSTIC_SESSION_STATE = State(name="Session", possible_values=range(0x80)) -DEFAULT_SECURITY_ACCESS_STATE = State(name="Unlocked SecurityAccess level", +"""State representing the current :ref:`Diagnostic Session `.""" + +DEFAULT_SECURITY_ACCESS_STATE = State(name="SecurityAccess", possible_values={"Locked", *range(1, 0x80, 2)}) -DEFAULT_AUTHENTICATION_STATE = State(name="Authentication state", +"""State representing the currently unlocked :ref:`Security Access ` level.""" + +DEFAULT_AUTHENTICATION_STATE = State(name="Authentication", possible_values={"noone authenticated", "Client authenticated", "Server authenticated", "Client and server authenticated"}) -DEFAULT_SECURED_TRANSMISSION_STATE = State(name="Secured Transmission", +"""State representing the current :ref:`Authentication ` status.""" + +DEFAULT_IGNITION_STATE = State(name="Ignition", + possible_values=OFF_ON_MAPPING.values()) +"""State indicating whether the vehicle ignition is ON or OFF.""" + +DEFAULT_ENGINE_STATE = State(name="Engine", + possible_values=OFF_ON_MAPPING.values()) +"""State indicating whether the engine is running.""" + +DEFAULT_SECURED_TRANSMISSION_STATE = State(name="SecuredTransmission", possible_values={"yes", "no"}) -DEFAULT_ENGINE_STATE = State(name="Engine state", - possible_values={"ON", "OFF"}) +"""State indicating whether secured data transmission is active.""" + DEFAULT_ADDRESSING_TYPE_STATE = State(name="AddressingType", possible_values=set(AddressingType)) +"""State representing the current :class:`~uds.addressing.AddressingType`.""" diff --git a/uds/utilities/__init__.py b/uds/utilities/__init__.py index 81d34ab5..1b382b6f 100644 --- a/uds/utilities/__init__.py +++ b/uds/utilities/__init__.py @@ -52,6 +52,7 @@ NETWORKS_MAPPING, NO_YES_MAPPING, NODE_IDENTIFICATION_NUMBER_MAPPING, + OFF_ON_MAPPING, PERIODIC_DID_BIT_LENGTH, PERIODIC_DID_OFFSET, POWER_DOWN_TIME_MAPPING, diff --git a/uds/utilities/constants/__init__.py b/uds/utilities/constants/__init__.py index c2429d94..a7801cc5 100644 --- a/uds/utilities/constants/__init__.py +++ b/uds/utilities/constants/__init__.py @@ -32,6 +32,7 @@ NETWORKS_MAPPING, NO_YES_MAPPING, NODE_IDENTIFICATION_NUMBER_MAPPING, + OFF_ON_MAPPING, POWER_DOWN_TIME_MAPPING, REPEATED_DATA_RECORDS_NUMBER, SCALING_BYTE_TYPE_MAPPING, diff --git a/uds/utilities/constants/other.py b/uds/utilities/constants/other.py index 17465895..e15fdc15 100644 --- a/uds/utilities/constants/other.py +++ b/uds/utilities/constants/other.py @@ -3,7 +3,7 @@ __all__ = [ # Shared "REPEATED_DATA_RECORDS_NUMBER", - "NO_YES_MAPPING", + "NO_YES_MAPPING", "OFF_ON_MAPPING", "COMPRESSION_METHOD_MAPPING", "ENCRYPTION_METHOD_MAPPING", # SID 0x11 "POWER_DOWN_TIME_MAPPING", @@ -44,6 +44,9 @@ NO_YES_MAPPING: Dict[int, str] = {0: "no", 1: "yes"} """Generic `no` and `yes` values mapping.""" +OFF_ON_MAPPING: Dict[int, str] = {0: "OFF", 1: "ON"} +"""Generic `OFF` and `ON` values mapping.""" + COMPRESSION_METHOD_MAPPING: Dict[int, str] = ({0: "no compression"} | {value: f"compression #{value}" for value in range(1, 0x10)}) """Values mapping for compressionMethod Data Record that is part of messages for multiple services."""