+[docs]
+classScript(BaseCommand,ABC):
+"""Script is a base class for a synchronous gallia command.
+ To implement a script, create a subclass and implement the
+ .main() method."""
+
+ GROUP="script"
+
+ defsetup(self)->None:...
+
+ @abstractmethod
+ defmain(self)->None:...
+
+ defteardown(self)->None:...
+
+ defrun(self)->int:
+ self.setup()
+ try:
+ self.main()
+ finally:
+ self.teardown()
+
+ returnexitcodes.OK
+[docs]
+classAsyncScript(BaseCommand,ABC):
+"""AsyncScript is a base class for a asynchronous gallia command.
+ To implement an async script, create a subclass and implement
+ the .main() method."""
+
+ GROUP="script"
+
+ asyncdefsetup(self)->None:...
+
+ @abstractmethod
+ asyncdefmain(self)->None:...
+
+ asyncdefteardown(self)->None:...
+
+ asyncdef_run(self)->None:
+ awaitself.setup()
+ try:
+ awaitself.main()
+ finally:
+ awaitself.teardown()
+
+ defrun(self)->int:
+ asyncio.run(self._run())
+ returnexitcodes.OK
+
+
+
+classScannerConfig(AsyncScriptConfig,cli_group="scanner",config_section="gallia.scanner"):
+ dumpcap:bool=Field(
+ sys.platform.startswith("linux"),description="Enable/Disable creating a pcap file"
+ )
+ target:Idempotent[TargetURI]=Field(
+ description="URI that describes the target",metavar="TARGET"
+ )
+ power_supply:Idempotent[PowerSupplyURI]|None=Field(
+ None,
+ description="URI specifying the location of the relevant opennetzteil server",
+ metavar="URI",
+ )
+ power_cycle:bool=Field(
+ False,
+ description="use the configured power supply to power-cycle the ECU when needed (e.g. before starting the scan, or to recover bad state during scanning)",
+ )
+ power_cycle_sleep:float=Field(
+ 5.0,description="time to sleep after the power-cycle",metavar="SECs"
+ )
+
+ @field_serializer("target","power_supply")
+ defserialize_target_uri(self,target_uri:TargetURI|None)->Any:
+ iftarget_uriisNone:
+ returnNone
+
+ returntarget_uri.raw
+
+ @model_validator(mode="after")
+ defcheck_power_supply_required(self)->Self:
+ ifself.power_cycleandself.power_supplyisNone:
+ raiseValueError("power-cycle needs power-supply")
+
+ returnself
+
+
+
+[docs]
+classScanner(AsyncScript,ABC):
+"""Scanner is a base class for all scanning related commands.
+ A scanner has the following properties:
+
+ - It is async.
+ - It loads transports via TargetURIs; available via `self.transport`.
+ - Controlling PowerSupplies via the opennetzteil API is supported.
+ - `setup()` can be overwritten (do not forget to call `super().setup()`)
+ for preparation tasks, such as establishing a network connection or
+ starting background tasks.
+ - pcap logfiles can be recorded via a Dumpcap background task.
+ - `teardown()` can be overwritten (do not forget to call `super().teardown()`)
+ for cleanup tasks, such as terminating a network connection or background
+ tasks.
+ - `main()` is the relevant entry_point for the scanner and must be implemented.
+ """
+
+ HAS_ARTIFACTS_DIR=True
+ CATCHED_EXCEPTIONS:list[type[Exception]]=[ConnectionError,UDSException]
+
+ def__init__(self,config:ScannerConfig):
+ super().__init__(config)
+ self.config:ScannerConfig=config
+ self.power_supply:PowerSupply|None=None
+ self.transport:BaseTransport
+ self.dumpcap:Dumpcap|None=None
+
+ @abstractmethod
+ asyncdefmain(self)->None:...
+
+ asyncdefsetup(self)->None:
+ fromgallia.plugins.pluginimportload_transport
+
+ ifself.config.power_supplyisnotNone:
+ self.power_supply=awaitPowerSupply.connect(self.config.power_supply)
+ ifself.config.power_cycleisTrue:
+ awaitself.power_supply.power_cycle(
+ self.config.power_cycle_sleep,lambda:asyncio.sleep(2)
+ )
+
+ # Start dumpcap as the first subprocess; otherwise network
+ # traffic might be missing.
+ ifself.config.dumpcap:
+ ifshutil.which("dumpcap")isNone:
+ raiseRuntimeError("--dumpcap specified but `dumpcap` is not available")
+ self.dumpcap=awaitDumpcap.start(self.config.target,self.artifacts_dir)
+ ifself.dumpcapisNone:
+ logger.error("`dumpcap` could not be started!")
+ else:
+ awaitself.dumpcap.sync()
+
+ self.transport=awaitload_transport(self.config.target).connect(self.config.target)
+
+ asyncdefteardown(self)->None:
+ awaitself.transport.close()
+
+ ifself.dumpcap:
+ awaitself.dumpcap.stop()
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/_modules/gallia/command/uds.html b/_modules/gallia/command/uds.html
new file mode 100644
index 000000000..9d99b8273
--- /dev/null
+++ b/_modules/gallia/command/uds.html
@@ -0,0 +1,316 @@
+
+
+
+
+
+
+
+ gallia.command.uds — gallia documentation
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+# SPDX-FileCopyrightText: AISEC Pentesting Team
+#
+# SPDX-License-Identifier: Apache-2.0
+
+importjson
+fromabcimportABC
+
+frompydanticimportfield_validator
+
+fromgallia.command.baseimportFileNames,Scanner,ScannerConfig
+fromgallia.command.configimportField
+fromgallia.logimportget_logger
+fromgallia.plugins.pluginimportload_ecu,load_ecus
+fromgallia.services.uds.core.serviceimportNegativeResponse,UDSResponse
+fromgallia.services.uds.ecuimportECU
+fromgallia.services.uds.helpersimportraise_for_error
+
+logger=get_logger(__name__)
+
+
+classUDSScannerConfig(ScannerConfig,cli_group="uds",config_section="gallia.protocols.uds"):
+ ecu_reset:int|None=Field(
+ None,
+ description="Trigger an initial ecu_reset via UDS; reset level is optional",
+ const=0x01,
+ )
+ oem:str=Field(
+ ECU.OEM,
+ description="The OEM of the ECU, used to choose a OEM specific ECU implementation",
+ metavar="OEM",
+ )
+ timeout:float=Field(
+ 2,description="Timeout value to wait for a response from the ECU",metavar="SECONDS"
+ )
+ max_retries:int=Field(
+ 3,
+ description="Number of maximum retries while sending UDS requests. If supported by the transport, this will trigger reconnects if required.",
+ metavar="INT",
+ )
+ ping:bool=Field(True,description="Enable/Disable initial TesterPresent request")
+ tester_present_interval:float=Field(
+ 0.5,
+ description="Modify the interval of the cyclic tester present packets",
+ metavar="SECONDS",
+ )
+ tester_present:bool=Field(
+ True,description="Enable/Disable tester present background worker"
+ )
+ properties:bool=Field(
+ True,description="Read and store the ECU proporties prior and after scan"
+ )
+ compare_properties:bool=Field(
+ True,description="Compare properties before and after the scan"
+ )
+
+ @field_validator("oem")
+ @classmethod
+ defcheck_oem(cls,v:str)->str:
+ ecu_names=[ecu.OEMforecuinload_ecus()]
+
+ ifvnotinecu_names:
+ raiseValueError(f"Not a valid OEM. Use any of {ecu_names}.")
+
+ returnv
+
+
+
+[docs]
+classUDSScanner(Scanner,ABC):
+"""UDSScanner is a baseclass, particularly for scanning tasks
+ related to the UDS protocol. The differences to Scanner are:
+
+ - `self.ecu` contains a OEM specific UDS client object.
+ - A background tasks sends TesterPresent regularly to avoid timeouts.
+ """
+
+ SUBGROUP:str|None="uds"
+
+ def__init__(self,config:UDSScannerConfig):
+ super().__init__(config)
+ self.config:UDSScannerConfig=config
+ self.ecu:ECU
+ self._implicit_logging=True
+
+ @property
+ defimplicit_logging(self)->bool:
+ returnself._implicit_logging
+
+ @implicit_logging.setter
+ defimplicit_logging(self,value:bool)->None:
+ self._implicit_logging=value
+
+ ifself.db_handlerisnotNone:
+ self._apply_implicit_logging_setting()
+
+ def_apply_implicit_logging_setting(self)->None:
+ self.ecu.implicit_logging=self._implicit_logging
+
+ asyncdefsetup(self)->None:
+ awaitsuper().setup()
+
+ self.ecu=load_ecu(self.config.oem)(
+ self.transport,
+ timeout=self.config.timeout,
+ max_retry=self.config.max_retries,
+ power_supply=self.power_supply,
+ )
+
+ self.ecu.db_handler=self.db_handler
+
+ ifself.db_handlerisnotNone:
+ try:
+ # No idea, but str(args.target) fails with a strange traceback.
+ # Lets use the attribute directly…
+ awaitself.db_handler.insert_scan_run(self.config.target.raw)
+ self._apply_implicit_logging_setting()
+ exceptExceptionase:
+ logger.warning(f"Could not write the scan run to the database: {e:!r}")
+
+ ifself.config.ecu_resetisnotNone:
+ resp:UDSResponse=awaitself.ecu.ecu_reset(self.config.ecu_reset)
+ ifisinstance(resp,NegativeResponse):
+ logger.warning(f"ECUReset failed: {resp}")
+ logger.warning("Switching to default session")
+ raise_for_error(awaitself.ecu.set_session(0x01))
+ resp=awaitself.ecu.ecu_reset(self.config.ecu_reset)
+ ifisinstance(resp,NegativeResponse):
+ logger.warning(f"ECUReset in session 0x01 failed: {resp}")
+
+ # Handles connecting to the target and waits
+ # until it is ready.
+ ifself.config.ping:
+ awaitself.ecu.wait_for_ecu()
+
+ awaitself.ecu.connect()
+
+ ifself.config.tester_present:
+ awaitself.ecu.start_cyclic_tester_present(self.config.tester_present_interval)
+
+ ifself.config.propertiesisTrue:
+ path=self.artifacts_dir.joinpath(FileNames.PROPERTIES_PRE.value)
+ path.write_text(json.dumps(awaitself.ecu.properties(True),indent=4)+"\n")
+
+ ifself.db_handlerisnotNone:
+ self._apply_implicit_logging_setting()
+
+ ifself.config.propertiesisTrue:
+ try:
+ awaitself.db_handler.insert_scan_run_properties_pre(
+ awaitself.ecu.properties()
+ )
+ exceptExceptionase:
+ logger.warning(f"Could not write the properties_pre to the database: {e!r}")
+
+ asyncdefteardown(self)->None:
+ ifself.config.propertiesisTrueand(notself.ecu.transport.is_closed):
+ path=self.artifacts_dir.joinpath(FileNames.PROPERTIES_POST.value)
+ path.write_text(json.dumps(awaitself.ecu.properties(True),indent=4)+"\n")
+
+ path_pre=self.artifacts_dir.joinpath(FileNames.PROPERTIES_PRE.value)
+ prop_pre=json.loads(path_pre.read_text())
+
+ ifself.config.compare_propertiesandawaitself.ecu.properties(False)!=prop_pre:
+ logger.warning("ecu properties differ, please investigate!")
+
+ ifself.db_handlerisnotNoneandself.config.propertiesisTrue:
+ try:
+ awaitself.db_handler.complete_scan_run(awaitself.ecu.properties(False))
+ exceptExceptionase:
+ logger.warning(f"Could not write the scan run to the database: {e!r}")
+
+ ifself.config.tester_present:
+ awaitself.ecu.stop_cyclic_tester_present()
+
+ # This must be the last one.
+ awaitsuper().teardown()
+
+
+
+classUDSDiscoveryScannerConfig(ScannerConfig):
+ timeout:float=Field(0.5,description="timeout value for request")
+
+
+
+[docs]
+classUDSDiscoveryScanner(Scanner,ABC):
+ def__init__(self,config:UDSDiscoveryScannerConfig):
+ super().__init__(config)
+ self.config:UDSDiscoveryScannerConfig=config
+
+ asyncdefsetup(self)->None:
+ awaitsuper().setup()
+
+ ifself.db_handlerisnotNone:
+ try:
+ awaitself.db_handler.insert_discovery_run(self.config.target.url.scheme)
+ exceptExceptionase:
+ logger.warning(f"Could not write the discovery run to the database: {e!r}")
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/_modules/gallia/log.html b/_modules/gallia/log.html
new file mode 100644
index 000000000..4c12ca885
--- /dev/null
+++ b/_modules/gallia/log.html
@@ -0,0 +1,979 @@
+
+
+
+
+
+
+
+ gallia.log — gallia documentation
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+[docs]
+@unique
+classColorMode(Enum):
+"""ColorMode is used as an argument to :func:`set_color_mode`."""
+
+ #: Colors are always turned on.
+ ALWAYS="always"
+ #: Colors are turned off if the target
+ #: stream (e.g. stderr) is not a tty.
+ AUTO="auto"
+ #: No colors are used. In other words,
+ #: no ANSI escape codes are included.
+ NEVER="never"
+
+
+
+
+[docs]
+defresolve_color_mode(mode:ColorMode,stream:TextIO=sys.stderr)->bool:
+"""Sets the color mode of the console log handler.
+
+ :param mode: The available options are described in :class:`ColorMode`.
+ :param stream: Used as a reference for :attr:`ColorMode.AUTO`.
+ """
+ ifsys.platform=="win32":
+ returnFalse
+
+ matchmode:
+ caseColorMode.ALWAYS:
+ returnTrue
+ caseColorMode.AUTO:
+ ifos.getenv("NO_COLOR")isnotNone:
+ returnFalse
+ else:
+ returnstream.isatty()
+ caseColorMode.NEVER:
+ returnFalse
+
+
+
+# https://stackoverflow.com/a/35804945
+def_add_logging_level(level_name:str,level_num:int)->None:
+ method_name=level_name.lower()
+
+ ifhasattr(logging,level_name):
+ raiseAttributeError(f"{level_name} already defined in logging module")
+ ifhasattr(logging,method_name):
+ raiseAttributeError(f"{method_name} already defined in logging module")
+ ifhasattr(logging.getLoggerClass(),method_name):
+ raiseAttributeError(f"{method_name} already defined in logger class")
+
+ # This method was inspired by the answers to Stack Overflow post
+ # http://stackoverflow.com/q/2183233/2988730, especially
+ # http://stackoverflow.com/a/13638084/2988730
+ deffor_level(self,message,*args,**kwargs):# type: ignore
+ ifself.isEnabledFor(level_num):
+ self._log(
+ level_num,
+ message,
+ args,
+ **kwargs,
+ )
+
+ defto_root(message,*args,**kwargs):# type: ignore
+ logging.log(level_num,message,*args,**kwargs)
+
+ logging.addLevelName(level_num,level_name)
+ setattr(logging,level_name,level_num)
+ setattr(logging.getLoggerClass(),method_name,for_level)
+ setattr(logging,method_name,to_root)
+
+
+_add_logging_level("TRACE",5)
+_add_logging_level("NOTICE",25)
+
+
+
+[docs]
+@unique
+classLoglevel(IntEnum):
+"""A wrapper around the constants exposed by python's
+ ``logging`` module. Since gallia adds two additional
+ loglevel's (``NOTICE`` and ``TRACE``), this class
+ provides a type safe way to access the loglevels.
+ The level ``NOTICE`` was added to conform better to
+ RFC3164. Subsequently, ``TRACE`` was added to have
+ a facility for optional debug messages.
+ Loglevel describes python specific values for loglevels
+ which are required to integrate with the python ecosystem.
+ For generic priority values, see :class:`PenlogPriority`.
+ """
+
+ CRITICAL=logging.CRITICAL
+ ERROR=logging.ERROR
+ WARNING=logging.WARNING
+ NOTICE=logging.NOTICE# type: ignore
+ INFO=logging.INFO
+ DEBUG=logging.DEBUG
+ TRACE=logging.TRACE# type: ignore
+
+
+
+
+[docs]
+@unique
+classPenlogPriority(IntEnum):
+"""PenlogPriority holds the values which are written
+ to json log records. These values conform to RFC3164
+ with the addition of ``TRACE``. Since Python uses different
+ int values for the loglevels, there are two enums in
+ gallia describing loglevels. PenlogPriority describes
+ generic priority values which are included in json
+ log records.
+ """
+
+ EMERGENCY=0
+ ALERT=1
+ CRITICAL=2
+ ERROR=3
+ WARNING=4
+ NOTICE=5
+ INFO=6
+ DEBUG=7
+ TRACE=8
+
+
+[docs]
+ @classmethod
+ deffrom_str(cls,string:str)->PenlogPriority:
+"""Converts a string to an instance of PenlogPriority.
+ ``string`` can be a numeric value (0 to 8 inclusive)
+ or a string with a case insensitive name of the level
+ (e.g. ``debug``).
+ """
+ ifstring.isnumeric():
+ returncls(int(string,0))
+
+ matchstring.lower():
+ case"emergency":
+ returncls.EMERGENCY
+ case"alert":
+ returncls.ALERT
+ case"critical":
+ returncls.CRITICAL
+ case"error":
+ returncls.ERROR
+ case"warning":
+ returncls.WARNING
+ case"notice":
+ returncls.NOTICE
+ case"info":
+ returncls.INFO
+ case"debug":
+ returncls.DEBUG
+ case"trace":
+ returncls.TRACE
+ case_:
+ raiseValueError(f"{string} not a valid priority")
+
+
+
+[docs]
+ @classmethod
+ deffrom_level(cls,value:int)->PenlogPriority:
+"""Converts an int value (e.g. from python's logging module)
+ to an instance of this class.
+ """
+ matchvalue:
+ caseLoglevel.TRACE:
+ returncls.TRACE
+ caseLoglevel.DEBUG:
+ returncls.DEBUG
+ caseLoglevel.INFO:
+ returncls.INFO
+ caseLoglevel.NOTICE:
+ returncls.NOTICE
+ caseLoglevel.WARNING:
+ returncls.WARNING
+ caseLoglevel.ERROR:
+ returncls.ERROR
+ caseLoglevel.CRITICAL:
+ returncls.CRITICAL
+ case_:
+ raiseValueError("invalid value")
+[docs]
+defsetup_logging(
+ level:Loglevel|None=None,
+ color_mode:ColorMode=ColorMode.AUTO,
+ no_volatile_info:bool=False,
+ logger_name:str="gallia",
+)->None:
+"""Enable and configure gallia's logging system.
+ If this fuction is not called as early as possible,
+ the logging system is in an undefined state und might
+ not behave as expected. Always use this function to
+ initialize gallia's logging. For instance, ``setup_logging()``
+ initializes a QueueHandler to avoid blocking calls during
+ logging.
+
+ :param level: The loglevel to enable for the console handler.
+ If this argument is None, the env variable
+ ``GALLIA_LOGLEVEL`` (see :doc:`../env`) is read.
+ :param file_level: The loglevel to enable for the file handler.
+ :param path: The path to the logfile containing json records.
+ :param color_mode: The color mode to use for the console.
+ """
+ iflevelisNone:
+ # FIXME: why is this here and not in config?
+ if(raw:=os.getenv("GALLIA_LOGLEVEL"))isnotNone:
+ level=PenlogPriority.from_str(raw).to_level()
+ else:
+ level=Loglevel.DEBUG
+
+ # These are slow and not used by gallia.
+ logging.logMultiprocessing=False
+ logging.logThreads=False
+ logging.logProcesses=False
+
+ logger=logging.getLogger(logger_name)
+ # LogLevel cannot be 0 (NOTSET), because only the root logger sends it to its handlers then
+ logger.setLevel(1)
+
+ # Clean up potentially existing handlers and create a new async QueueHandler for stderr output
+ whilelen(logger.handlers)>0:
+ logger.handlers[0].close()
+ logger.removeHandler(logger.handlers[0])
+ colored=resolve_color_mode(color_mode)
+ add_stderr_log_handler(logger_name,level,no_volatile_info,colored)
+[docs]
+@dataclass
+classUDSRequestConfig:
+ # timeout for this request in sec
+ timeout:float|None=None
+ # maximum number of attempts in case of network errors
+ max_retry:int|None=None
+ # Skip the hooks which apply to session changes.
+ skip_hooks:bool=False
+ # tags to be applied to the logged output
+ tags:list[str]|None=None
+
+
+
+logger=get_logger(__name__)
+
+
+classUDSClient:
+ def__init__(
+ self,
+ transport:BaseTransport,
+ timeout:float,
+ max_retry:int=0,
+ ):
+ self.transport=transport
+ self.timeout=timeout
+ self.max_retry=max_retry
+ self.retry_wait=0.2
+ self.pending_timeout=5
+ self.mutex=asyncio.Lock()
+
+ asyncdefconnect(self)->None:...
+
+ asyncdefreconnect(self,timeout:int|None=None)->None:
+"""Calls the underlying transport to trigger a reconnect"""
+ asyncwithself.mutex:
+ awaitself.reconnect_unsafe(timeout=timeout)
+
+ asyncdefreconnect_unsafe(self,timeout:int|None=None)->None:
+"""Calls the underlying transport to trigger a reconnect without locking"""
+ self.transport=awaitself.transport.reconnect(timeout)
+ logger.debug("Reconnected transport successfully")
+ awaitself.connect()
+
+ asyncdef_read(self,timeout:float|None=None,tags:list[str]|None=None)->bytes:
+ iftimeoutisNoneandself.timeout:
+ timeout=self.timeout
+ returnawaitself.transport.read(timeout,tags)
+
+ asyncdefrequest_unsafe(
+ self,request:service.UDSRequest,config:UDSRequestConfig|None=None
+ )->service.UDSResponse:
+"""This method is the same as request() with the difference
+ that it does not hold the mutex in the underlying transport.
+ """
+ config=configifconfigisnotNoneelseUDSRequestConfig()
+ tags:list[str]=[]ifconfig.tagsisNoneelseconfig.tags
+
+ last_exception:Exception=MissingResponse(request)
+ max_retry=config.max_retryifconfig.max_retryisnotNoneelseself.max_retry
+ timeout=config.timeoutifconfig.timeoutisnotNoneelseself.timeout
+ foriinrange(max_retry+1):
+ # Exponential backoff
+ wait_time=self.retry_wait*2**i
+
+ # Avoid pasting this very line in every error branch.
+ ifi>0:
+ logger.info(f"Requesting UDS PDU failed; retrying: {i} / {max_retry}…")
+ try:
+ logger.debug(request.pdu.hex(),extra={"tags":["write","uds"]+tags})
+ raw_resp=awaitself.transport.request_unsafe(request.pdu,timeout,config.tags)
+ ifraw_resp==b"":
+ raiseBrokenPipeError("connection to target lost")
+ exceptTimeoutErrorase:
+ logger.debug(f"{request} failed with: {repr(e)}")
+ last_exception=MissingResponse(request,str(e))
+ ifi<max_retry:
+ logger.debug(f"Sleeping for {wait_time}s")
+ awaitasyncio.sleep(wait_time)
+ continue
+ exceptConnectionErrorase:
+ logger.warning(f"{request} failed with: {e!r}")
+ last_exception=MissingResponse(request,str(e))
+ ifi<max_retry:
+ logger.info(f"Sleeping for {wait_time}s before attempting to reconnect")
+ awaitasyncio.sleep(wait_time)
+ awaitself.reconnect_unsafe()
+ continue
+
+ logger.debug(raw_resp.hex(),extra={"tags":["read","uds"]+tags})
+ resp=parse_pdu(raw_resp,request)
+
+ ifisinstance(resp,service.NegativeResponse):
+ ifresp.response_code==UDSErrorCodes.busyRepeatRequest:
+ ifi>=max_retry:
+ returnresp
+ awaitasyncio.sleep(wait_time)
+ continue
+ # We already had ECUs which thought an infinite
+ # response_pending loop is a good idea…
+ # Let's limit this.
+ n_pending=1
+ MAX_N_PENDING=120
+ n_timeout=0
+ waiting_time=0.5
+ max_n_timeout=max(timeoutiftimeoutelse0,20)/waiting_time
+ while(
+ isinstance(resp,service.NegativeResponse)
+ andresp.response_code==UDSErrorCodes.requestCorrectlyReceivedResponsePending
+ ):
+ logger.info(
+ f"Received ResponsePending: {n_pending}/{MAX_N_PENDING}; "
+ +f"waiting for next message: {n_timeout}/{int(max_n_timeout)}"
+ )
+ try:
+ raw_resp=awaitself._read(timeout=waiting_time,tags=config.tags)
+ ifraw_resp==b"":
+ raiseBrokenPipeError("connection to target lost")
+ logger.debug(raw_resp.hex(),extra={"tags":["read","uds"]+tags})
+ exceptTimeoutErrorase:
+ # Send a tester present to indicate that
+ # we are still there.
+ # TODO: Is this really necessary?
+ awaitself._tester_present(suppress_resp=True)
+ n_timeout+=1
+ ifn_timeout>=max_n_timeout:
+ last_exception=MissingResponse(request,str(e))
+ break
+ continue
+ resp=parse_pdu(raw_resp,request)
+ n_timeout=0# Only raise errors for consecutive timeouts
+ n_pending+=1
+ ifn_pending>=MAX_N_PENDING:
+ raiseRuntimeError("ECU appears to be stuck in ResponsePending loop")
+ else:
+ # We reach this code here once all response pending
+ # and similar busy stuff is resolved.
+ returnresp
+
+ logger.debug(f"{request} failed after retry loop")
+ raiselast_exception
+
+ asyncdef_tester_present(
+ self,suppress_resp:bool=False,config:UDSRequestConfig|None=None
+ )->service.UDSResponse|None:
+ config=configifconfigisnotNoneelseUDSRequestConfig()
+ timeout=config.timeoutifconfig.timeoutelseself.timeout
+ ifsuppress_resp:
+ pdu=service.TesterPresentRequest(suppress_response=True).pdu
+ tags=config.tagsifconfig.tagsisnotNoneelse[]
+ logger.debug(pdu.hex(),extra={"tags":["write","uds"]+tags})
+ awaitself.transport.write(pdu,timeout,config.tags)
+ # TODO: This is not fail safe: What if there is an answer???
+ returnNone
+ returnawaitself.tester_present(False,config)
+
+ asyncdefsend_raw(
+ self,pdu:bytes,config:UDSRequestConfig|None=None
+ )->service.NegativeResponse|service.PositiveResponse:
+"""Raw request, which does not need to be compliant with the standard.
+ It can be used to send arbitrary data packets.
+
+ :param pdu: The data.
+ :param config: Passed on to request_pdu().
+ :return: The response of the server.
+ """
+ returnawaitself.request(service.RawRequest(pdu),config)
+
+ asyncdefdiagnostic_session_control(
+ self,
+ diagnostic_session_type:int,
+ suppress_response:bool=False,
+ config:UDSRequestConfig|None=None,
+ )->service.NegativeResponse|service.DiagnosticSessionControlResponse:
+"""Sets the diagnostic session which is specified by a specific diagnosticSessionType
+ sub-function.
+ This is an implementation of the UDS request for service DiagnosticSessionControl (0x10).
+
+ :param diagnostic_session_type: The session sub-function.
+ :param suppress_response: If set to True, the server is advised to not send back a positive
+ response.
+ :param config: Passed on to request_pdu().
+ :return: The response of the server.
+ """
+ returnawaitself.request(
+ service.DiagnosticSessionControlRequest(diagnostic_session_type,suppress_response),
+ config,
+ )
+
+ asyncdefecu_reset(
+ self,
+ reset_type:int,
+ suppress_response:bool=False,
+ config:UDSRequestConfig|None=None,
+ )->service.NegativeResponse|service.ECUResetResponse:
+"""Resets the ECU using the specified reset type sub-function.
+ This is an implementation of the UDS request for service ECUReset (0x11).
+
+ :param reset_type: The reset type sub-function.
+ :param suppress_response: If set to True, the server is advised to not send back a positive
+ response.
+ :param config: Passed on to request_pdu().
+ :return: The response of the server.
+ """
+ returnawaitself.request(service.ECUResetRequest(reset_type,suppress_response),config)
+
+ asyncdefsecurity_access_request_seed(
+ self,
+ security_access_type:int,
+ security_access_data_record:bytes=b"",
+ suppress_response:bool=False,
+ config:UDSRequestConfig|None=None,
+ )->service.NegativeResponse|service.SecurityAccessResponse:
+"""Requests a seed for a security access level.
+ This is an implementation of the UDS request for the requestSeed sub-function group
+ of the service SecurityAccess (0x27).
+
+ :param security_access_type: The securityAccess type sub-function.
+ :param security_access_data_record: Optional data.
+ :param suppress_response: If set to True, the server is advised to not send back a positive
+ response.
+ :param config: Passed on to request_pdu().
+ :return: The response of the server.
+ """
+ returnawaitself.request(
+ service.RequestSeedRequest(
+ security_access_type,security_access_data_record,suppress_response
+ ),
+ config,
+ )
+
+ asyncdefsecurity_access_send_key(
+ self,
+ security_access_type:int,
+ security_key:bytes,
+ suppress_response:bool=False,
+ config:UDSRequestConfig|None=None,
+ )->service.NegativeResponse|service.SecurityAccessResponse:
+"""Sends the key for a security access level.
+ This is an implementation of the UDS request for the sendKey sub-function group
+ of the service SecurityAccess (0x27).
+
+ :param security_access_type: The securityAccess type sub-function.
+ :param security_key: The response to the seed challenge.
+ :param suppress_response: If set to True, the server is advised to not send back a positive
+ response.
+ :param config: Passed on to request_pdu().
+ :return: The response of the server.
+ """
+ returnawaitself.request(
+ service.SendKeyRequest(security_access_type,security_key,suppress_response),
+ config,
+ )
+
+ asyncdefcommunication_control(
+ self,
+ control_type:int,
+ communication_type:int,
+ suppress_response:bool=False,
+ config:UDSRequestConfig|None=None,
+ )->service.NegativeResponse|service.CommunicationControlResponse:
+"""Controls communication of the ECU.
+ This is an implementation of the UDS request for service CommunicationControl (0x28).
+
+ :param control_type: The control type sub-function.
+ :param communication_type: The communication type.
+ :param suppress_response: If set to True, the server is advised to not send back a positive
+ response.
+ :param config: Passed on to request_pdu().
+ :return: The response of the server.
+ """
+ returnawaitself.request(
+ service.CommunicationControlRequest(
+ control_type,communication_type,suppress_response
+ ),
+ config,
+ )
+
+ asyncdeftester_present(
+ self,suppress_response:bool=False,config:UDSRequestConfig|None=None
+ )->service.NegativeResponse|service.TesterPresentResponse:
+"""Signals to the ECU, that the tester is still present.
+ This is an implementation of the UDS request for service TesterPresent (0x3E).
+
+ :param suppress_response: If set to True, the server is advised to not send back a positive
+ response.
+ :param config: Passed on to request_pdu().
+ :return: The response of the server.
+ """
+ returnawaitself.request(service.TesterPresentRequest(suppress_response),config)
+
+ asyncdefcontrol_dtc_setting(
+ self,
+ dtc_setting_type:int,
+ dtc_setting_control_option_record:bytes=b"",
+ suppress_response:bool=False,
+ config:UDSRequestConfig|None=None,
+ )->service.NegativeResponse|service.ControlDTCSettingResponse:
+"""Control the setting of DTCs.
+ This is an implementation of the UDS request for service ControlDTCSetting (0x85).
+
+
+ :param dtc_setting_type: The setting type.
+ :param dtc_setting_control_option_record: Optional data.
+ :param suppress_response: If set to True, the server is advised to not send back a positive
+ response.
+ :param config: Passed on to request_pdu().
+ :return: The response of the server.
+ """
+ returnawaitself.request(
+ service.ControlDTCSettingRequest(
+ dtc_setting_type,dtc_setting_control_option_record,suppress_response
+ ),
+ config,
+ )
+
+ asyncdefread_data_by_identifier(
+ self,
+ data_identifiers:int|Sequence[int],
+ config:UDSRequestConfig|None=None,
+ )->service.NegativeResponse|service.ReadDataByIdentifierResponse:
+"""Reads data which is identified by a specific dataIdentifier.
+ This is an implementation of the UDS request for service ReadDataByIdentifier (0x22).
+ While this implementation supports requesting multiple dataIdentifiers at once, as is
+ permitted in the standard, it is recommended to request them separately,
+ because the support is optional on the server side.
+ Additionally, it is not possible to reliably determine each single dataRecord from a
+ corresponding response.
+
+ :param data_identifiers: One or multiple dataIdentifiers. A dataIdentifier is a max two
+ bytes integer.
+ :param config: Passed on to request_pdu().
+ :return: The response of the server.
+ """
+ returnawaitself.request(service.ReadDataByIdentifierRequest(data_identifiers),config)
+
+ asyncdefread_memory_by_address(
+ self,
+ memory_address:int,
+ memory_size:int,
+ address_and_length_format_identifier:int|None=None,
+ config:UDSRequestConfig|None=None,
+ )->service.NegativeResponse|service.ReadMemoryByAddressResponse:
+"""Reads data from a specific memory address on the UDS server.
+ This is an implementation of the UDS request for service ReadMemoryByAddress (0x3d).
+ While it exposes each parameter of the corresponding specification,
+ some parameters can be computed from the remaining ones and can therefore be omitted.
+
+ :param memory_address: The start address.
+ :param memory_size: The number of bytes to read.
+ :param address_and_length_format_identifier: The byte lengths of the memory address and
+ size. If omitted, this parameter is computed
+ based on the memory_address and memory_size
+ parameters.
+ :param config: Passed on to request_pdu().
+ :return: The response of the server.
+ """
+ returnawaitself.request(
+ service.ReadMemoryByAddressRequest(
+ memory_address,memory_size,address_and_length_format_identifier
+ ),
+ config,
+ )
+
+ asyncdefwrite_data_by_identifier(
+ self,
+ data_identifier:int,
+ data_record:bytes,
+ config:UDSRequestConfig|None=None,
+ )->service.NegativeResponse|service.WriteDataByIdentifierResponse:
+"""Writes data which is identified by a specific dataIdentifier.
+ This is an implementation of the UDS request for service WriteDataByIdentifier (0x2E).
+
+ :param data_identifier: The identifier. A dataIdentifier is a max two bytes integer.
+ :param data_record: The data to be written.
+ :param config: Passed on to request_pdu().
+ :return: The response of the server.
+ """
+ returnawaitself.request(
+ service.WriteDataByIdentifierRequest(data_identifier,data_record),config
+ )
+
+ asyncdefwrite_memory_by_address(# noqa: PLR0913
+ self,
+ memory_address:int,
+ data_record:bytes,
+ memory_size:int|None=None,
+ address_and_length_format_identifier:int|None=None,
+ config:UDSRequestConfig|None=None,
+ )->service.NegativeResponse|service.WriteMemoryByAddressResponse:
+"""Writes data to a specific memory on the UDS server.
+ This is an implementation of the UDS request for service writeMemoryByAddress (0x3d).
+ While it exposes each parameter of the corresponding specification,
+ some parameters can be computed from the remaining ones and can therefore be omitted.
+
+ :param memory_address: The start address.
+ :param data_record: The data to be written.
+ :param memory_size: The number of bytes to write.
+ If omitted, the byte length of the data is used.
+ :param address_and_length_format_identifier: The byte lengths of the memory address and
+ size. If omitted, this parameter is computed
+ based on the memory_address and memory_size
+ or data_record parameters.
+ :param config: Passed on to request_pdu().
+ :return: The response of the server.
+ """
+ returnawaitself.request(
+ service.WriteMemoryByAddressRequest(
+ memory_address,
+ data_record,
+ memory_size,
+ address_and_length_format_identifier,
+ ),
+ config,
+ )
+
+ asyncdefclear_diagnostic_information(
+ self,group_of_dtc:int,config:UDSRequestConfig|None=None
+ )->service.NegativeResponse|service.ClearDiagnosticInformationResponse:
+"""Clears diagnostic trouble codes according to a given mask.
+ This is an implementation of the UDS request for service clearDiagnosticInformation (0x14).
+
+ :param group_of_dtc: The three byte mask, which determines the DTCs to be cleared.
+ :param config: Passed on to request_pdu().
+ :return: The response of the server.
+ """
+ returnawaitself.request(service.ClearDiagnosticInformationRequest(group_of_dtc),config)
+
+ asyncdefread_dtc_information_report_number_of_dtc_by_status_mask(
+ self,
+ dtc_status_mask:int,
+ suppress_response:bool=False,
+ config:UDSRequestConfig|None=None,
+ )->service.NegativeResponse|service.ReportNumberOfDTCByStatusMaskResponse:
+"""Read the number of DTCs with the specified state from the UDS server.
+ This is an implementation of the UDS request for the reportNumberOfDTCByStatusMask
+ sub-function of the service ReadDTCInformation (0x19).
+
+ :param dtc_status_mask: Used to select a portion of the DTCs based on their state.
+ :param suppress_response: If set to True, the server is advised to not send back a positive
+ response.
+ :param config: Passed on to request_pdu().
+ :return: The response of the server.
+ """
+ returnawaitself.request(
+ service.ReportNumberOfDTCByStatusMaskRequest(dtc_status_mask,suppress_response),
+ config,
+ )
+
+ asyncdefread_dtc_information_report_dtc_by_status_mask(
+ self,
+ dtc_status_mask:int,
+ suppress_response:bool=False,
+ config:UDSRequestConfig|None=None,
+ )->service.NegativeResponse|service.ReportDTCByStatusMaskResponse:
+"""Read DTCs and their state from the UDS server.
+ This is an implementation of the UDS request for the reportDTCByStatusMask sub-function of
+ the service ReadDTCInformation (0x19).
+
+ :param dtc_status_mask: Used to select a portion of the DTCs based on their state.
+ :param suppress_response: If set to True, the server is advised to not send back a positive
+ response.
+ :param config: Passed on to request_pdu().
+ :return: The response of the server.
+ """
+ returnawaitself.request(
+ service.ReportDTCByStatusMaskRequest(dtc_status_mask,suppress_response),
+ config,
+ )
+
+ asyncdefread_dtc_information_report_mirror_memory_dtc_by_status_mask(
+ self,
+ dtc_status_mask:int,
+ suppress_response:bool=False,
+ config:UDSRequestConfig|None=None,
+ )->service.NegativeResponse|service.ReportMirrorMemoryDTCByStatusMaskResponse:
+"""Read DTCs and their state from the UDS server's mirror memory.
+ This is an implementation of the UDS request for the reportMirrorMemoryDTCByStatusMask
+ sub-function of the
+ service ReadDTCInformation (0x19).
+
+ :param dtc_status_mask: Used to select a portion of the DTCs based on their state.
+ :param suppress_response: If set to True, the server is advised to not send back a positive
+ response.
+ :param config: Passed on to request_pdu().
+ :return: The response of the server.
+ """
+ returnawaitself.request(
+ service.ReportMirrorMemoryDTCByStatusMaskRequest(dtc_status_mask,suppress_response),
+ config,
+ )
+
+ asyncdefread_dtc_information_report_number_of_mirror_memory_dtc_by_status_mask(
+ self,
+ dtc_status_mask:int,
+ suppress_response:bool=False,
+ config:UDSRequestConfig|None=None,
+ )->service.NegativeResponse|service.ReportNumberOfMirrorMemoryDTCByStatusMaskResponse:
+"""Read the number of DTCs with the specified state from the UDS server's mirror memory.
+ This is an implementation of the UDS request for the
+ reportNumberOfMirrorMemoryDTCByStatusMask sub-function of
+ the service ReadDTCInformation (0x19).
+
+ :param dtc_status_mask: Used to select a portion of the DTCs based on their state.
+ :param suppress_response: If set to True, the server is advised to not send back a positive
+ response.
+ :param config: Passed on to request_pdu().
+ :return: The response of the server.
+ """
+ returnawaitself.request(
+ service.ReportNumberOfMirrorMemoryDTCByStatusMaskRequest(
+ dtc_status_mask,suppress_response
+ ),
+ config,
+ )
+
+ asyncdefread_dtc_information_report_number_of_emissions_related_obd_dtc_by_status_mask(
+ self,
+ dtc_status_mask:int,
+ suppress_response:bool=False,
+ config:UDSRequestConfig|None=None,
+ )->(
+ service.NegativeResponse|service.ReportNumberOfEmissionsRelatedOBDDTCByStatusMaskResponse
+ ):
+"""Read the number of emission related DTCs with the specified state from the UDS server.
+ This is an implementation of the UDS request for the
+ reportNumberOfEmissionsRelatedOBDDTCByStatusMask sub-function of the service
+ ReadDTCInformation (0x19).
+
+ :param dtc_status_mask: Used to select a portion of the DTCs based on their state.
+ :param suppress_response: If set to True, the server is advised to not send back a positive
+ response.
+ :param config: Passed on to request_pdu().
+ :return: The response of the server.
+ """
+ returnawaitself.request(
+ service.ReportNumberOfEmissionsRelatedOBDDTCByStatusMaskRequest(
+ dtc_status_mask,suppress_response
+ ),
+ config,
+ )
+
+ asyncdefread_dtc_information_report_emissions_related_obd_dtc_by_status_mask(
+ self,
+ dtc_status_mask:int,
+ suppress_response:bool=False,
+ config:UDSRequestConfig|None=None,
+ )->service.NegativeResponse|service.ReportEmissionsRelatedOBDDTCByStatusMaskResponse:
+"""Read the number of emission related DTCs with the specified state from the UDS server.
+ This is an implementation of the UDS request for the
+ reportNumberOfEmissionsRelatedOBDDTCByStatusMask
+ sub-function of the service ReadDTCInformation (0x19).
+
+ :param dtc_status_mask: Used to select a portion of the DTCs based on their state.
+ :param suppress_response: If set to True, the server is advised to not send back a positive
+ response.
+ :param config: Passed on to request_pdu().
+ :return: The response of the server.
+ """
+ returnawaitself.request(
+ service.ReportEmissionsRelatedOBDDTCByStatusMaskRequest(
+ dtc_status_mask,suppress_response
+ ),
+ config,
+ )
+
+ asyncdefinput_output_control_by_identifier(
+ self,
+ data_identifier:int,
+ control_option_record:bytes,
+ control_enable_mask_record:bytes=b"",
+ config:UDSRequestConfig|None=None,
+ )->service.NegativeResponse|service.InputOutputControlByIdentifierResponse:
+"""Controls input or output values on the server.
+ This is an implementation of the UDS request for the service InputOutputControlByIdentifier
+ (0x2F).
+ This function exposes the parameters as in the corresponding specification,
+ hence is suitable for all variants of this service.
+ For the variants which use an inputOutputControlParameter as the first byte of the
+ controlOptionRecord, using the corresponding wrappers is recommended.
+
+ :param data_identifier: The data identifier of the value(s) to be controlled.
+ :param control_option_record: The controlStates, which specify the intended values of the
+ input / output parameters, optionally prefixed with an
+ inputOutputControlParameter or only an
+ inputOutputControlParameter.
+ :param control_enable_mask_record: In cases where the dataIdentifier corresponds to multiple
+ input / output parameters, this mask specifies which ones
+ should be affected by this request.
+ :param config: Passed on to request_pdu().
+ :return: The response of the server.
+ """
+ pdu=struct.pack("!BH",UDSIsoServices.InputOutputControlByIdentifier,data_identifier)
+ pdu+=control_option_record+control_enable_mask_record
+ returnawaitself.request(
+ service.InputOutputControlByIdentifierRequest(
+ data_identifier,control_option_record,control_enable_mask_record
+ ),
+ config,
+ )
+
+ asyncdefinput_output_control_by_identifier_return_control_to_ecu(
+ self,
+ data_identifier:int,
+ control_enable_mask_record:bytes=b"",
+ config:UDSRequestConfig|None=None,
+ )->service.NegativeResponse|service.InputOutputControlByIdentifierResponse:
+"""Gives the control over input / output parameters back to the ECU.
+ This is a convenience wrapper for the generic input_output_control_by_id() for the case
+ where an inputOutputControlParameter is used and is set to returnControlToECU.
+ In that case no further controlState parameters can be submitted.
+
+ :param data_identifier: The data identifier of the value(s) for which control should be
+ returned to the ECU.
+ :param control_enable_mask_record: In cases where the dataIdentifier corresponds to multiple
+ input / output parameters, this mask specifies which ones
+ should be affected by this request.
+ :param config: Passed on to request_pdu().
+ :return: The response of the server.
+ """
+ returnawaitself.request(
+ service.ReturnControlToECURequest(data_identifier,control_enable_mask_record),
+ config,
+ )
+
+ asyncdefinput_output_control_by_identifier_reset_to_default(
+ self,
+ data_identifier:int,
+ control_enable_mask_record:bytes=b"",
+ config:UDSRequestConfig|None=None,
+ )->service.NegativeResponse|service.InputOutputControlByIdentifierResponse:
+"""Sets the input / output parameters to the default value(s).
+ This is a convenience wrapper of the generic request for the case where an
+ inputOutputControlParameter is used and is set to resetToDefault.
+ In that case no further controlState parameters can be submitted.
+
+ :param data_identifier: The data identifier of the value(s) for which the values should be
+ reset.
+ :param control_enable_mask_record: In cases where the dataIdentifier corresponds to multiple
+ input / output parameters, this mask specifies which ones
+ should be affected by this request.
+ :param config: Passed on to request_pdu().
+ :return: The response of the server.
+ """
+ returnawaitself.request(
+ service.ResetToDefaultRequest(data_identifier,control_enable_mask_record),
+ config,
+ )
+
+ asyncdefinput_output_control_by_identifier_freeze_current_state(
+ self,
+ data_identifier:int,
+ control_enable_mask_record:bytes=b"",
+ config:UDSRequestConfig|None=None,
+ )->service.NegativeResponse|service.InputOutputControlByIdentifierResponse:
+"""Freezes the input / output parameters at their current state.
+ This is a convenience wrapper of the generic request for the case where an
+ inputOutputControlParameter is used and is set to freezeCurrentState.
+ In that case no further controlState parameters can be submitted.
+
+ :param data_identifier: The data identifier of the value(s) to be frozen.
+ :param control_enable_mask_record: In cases where the dataIdentifier corresponds to multiple
+ input / output parameters, this mask specifies which ones
+ should be affected by this request.
+ :param config: Passed on to request_pdu().
+ :return: The response of the server.
+ """
+ returnawaitself.request(
+ service.FreezeCurrentStateRequest(data_identifier,control_enable_mask_record),
+ config,
+ )
+
+ asyncdefinput_output_control_by_identifier_short_term_adjustment(
+ self,
+ data_identifier:int,
+ control_states:bytes,
+ control_enable_mask_record:bytes=b"",
+ config:UDSRequestConfig|None=None,
+ )->service.NegativeResponse|service.InputOutputControlByIdentifierResponse:
+"""Sets the input / output parameters as specified in the controlOptionRecord.
+ This is a convenience wrapper of the generic request for the case
+ where an inputOutputControlParameter is used and is set to freezeCurrentState.
+ In that case controlState parameters are required.
+
+ :param data_identifier: The data identifier of the value(s) to be adjusted.
+ :param control_states: The controlStates, which specify the intended values of the input /
+ output parameters.
+ :param control_enable_mask_record: In cases where the dataIdentifier corresponds to multiple
+ input / output parameters, this mask specifies which ones
+ should be affected by this request.
+ :param config: Passed on to request_pdu().
+ :return: The response of the server.
+ """
+ returnawaitself.request(
+ service.ShortTermAdjustmentRequest(data_identifier,control_enable_mask_record),
+ config,
+ )
+
+ asyncdefroutine_control_start_routine(
+ self,
+ routine_identifier:int,
+ routine_control_option_record:bytes=b"",
+ suppress_response:bool=False,
+ config:UDSRequestConfig|None=None,
+ )->service.NegativeResponse|service.StartRoutineResponse:
+"""Starts a specific routine on the server.
+ This is an implementation of the UDS request for the startRoutine sub-function of the
+ service routineControl (0x31).
+
+ :param routine_identifier: The identifier of the routine.
+ :param routine_control_option_record: Optional data.
+ :param suppress_response: If set to True, the server is advised to not send back a positive
+ response.
+ :param config: Passed on to request_pdu().
+ :return: The response of the server.
+ """
+ returnawaitself.request(
+ service.StartRoutineRequest(
+ routine_identifier,routine_control_option_record,suppress_response
+ ),
+ config,
+ )
+
+ asyncdefroutine_control_stop_routine(
+ self,
+ routine_identifier:int,
+ routine_control_option_record:bytes=b"",
+ suppress_response:bool=False,
+ config:UDSRequestConfig|None=None,
+ )->service.NegativeResponse|service.StopRoutineResponse:
+"""Stops a specific routine on the server.
+ This is an implementation of the UDS request for the stopRoutine sub-function of the service
+ routineControl (0x31).
+
+ :param routine_identifier: The identifier of the routine.
+ :param routine_control_option_record: Optional data.
+ :param suppress_response: If set to True, the server is advised to not send back a positive
+ response.
+ :param config: Passed on to request_pdu().
+ :return: The response of the server.
+ """
+ returnawaitself.request(
+ service.StopRoutineRequest(
+ routine_identifier,routine_control_option_record,suppress_response
+ ),
+ config,
+ )
+
+ asyncdefroutine_control_request_routine_results(
+ self,
+ routine_identifier:int,
+ routine_control_option_record:bytes=b"",
+ suppress_response:bool=False,
+ config:UDSRequestConfig|None=None,
+ )->service.NegativeResponse|service.RequestRoutineResultsResponse:
+"""Requests the results of a specific routine on the server.
+ This is an implementation of the UDS request for the requestRoutineResults sub-function of
+ the service routineControl (0x31).
+
+ :param routine_identifier: The identifier of the routine.
+ :param routine_control_option_record: Optional data.
+ :param suppress_response: If set to True, the server is advised to not send back a positive
+ response.
+ :param config: Passed on to request_pdu().
+ :return: The response of the server.
+ """
+ returnawaitself.request(
+ service.RequestRoutineResultsRequest(
+ routine_identifier,routine_control_option_record,suppress_response
+ ),
+ config,
+ )
+
+ asyncdefrequest_download(# noqa: PLR0913
+ self,
+ memory_address:int,
+ memory_size:int,
+ compression_method:int=0x0,
+ encryption_method:int=0x0,
+ address_and_length_format_identifier:int|None=None,
+ config:UDSRequestConfig|None=None,
+ )->service.NegativeResponse|service.RequestDownloadResponse:
+"""Requests the download of data, i.e. the possibility to send data from the client to the
+ server.
+ This is an implementation of the UDS request for requestDownload (0x34).
+
+ :param memory_address: The address at which data should be downloaded.
+ :param memory_size: The number of bytes to be downloaded.
+ :param compression_method: Encodes the utilized compressionFormat (0x0 for none)
+ :param encryption_method: Encodes the utilized encryptionFormat (0x0 for none)
+ :param address_and_length_format_identifier: The byte lengths of the memory address and
+ size. If omitted, this parameter is computed
+ based on the memory_address and memory_size
+ parameters.
+ :param config: Passed on to request_pdu().
+ :return: The response of the server.
+ """
+ returnawaitself.request(
+ service.RequestDownloadRequest(
+ memory_address,
+ memory_size,
+ compression_method,
+ encryption_method,
+ address_and_length_format_identifier,
+ ),
+ config,
+ )
+
+ asyncdefrequest_upload(# noqa: PLR0913
+ self,
+ memory_address:int,
+ memory_size:int,
+ compression_method:int=0x0,
+ encryption_method:int=0x0,
+ address_and_length_format_identifier:int|None=None,
+ config:UDSRequestConfig|None=None,
+ )->service.NegativeResponse|service.RequestUploadResponse:
+"""Requests the upload of data, i.e. the possibility to receive data from the server.
+ This is an implementation of the UDS request for requestUpload (0x35).
+
+ :param memory_address: The address at which data should be uploaded.
+ :param memory_size: The number of bytes to be uploaded.
+ :param compression_method: Encodes the utilized compressionFormat (0x0 for none)
+ :param encryption_method: Encodes the utilized encryptionFormat (0x0 for none)
+ :param address_and_length_format_identifier: The byte lengths of the memory address and
+ size. If omitted, this parameter is computed
+ based on the memory_address and memory_size
+ parameters.
+ :param config: Passed on to request_pdu().
+ :return: The response of the server.
+ """
+ returnawaitself.request(
+ service.RequestUploadRequest(
+ memory_address,
+ memory_size,
+ compression_method,
+ encryption_method,
+ address_and_length_format_identifier,
+ ),
+ config,
+ )
+
+ asyncdeftransfer_data(
+ self,
+ block_sequence_counter:int,
+ transfer_request_parameter_record:bytes=b"",
+ config:UDSRequestConfig|None=None,
+ )->service.NegativeResponse|service.TransferDataResponse:
+"""Transfers data to the server or requests the next data from the server.
+ This is an implementation of the UDS request for transferData (0x36).
+
+ :param block_sequence_counter: The current block sequence counter.
+ Initialized with one and incremented for each new data.
+ After 0xff, the counter is resumed at 0
+ :param transfer_request_parameter_record: Contains the data to be transferred if downloading
+ to the server.
+ :param config: Passed on to request_pdu().
+ :return: The response of the server.
+ """
+ returnawaitself.request(
+ service.TransferDataRequest(block_sequence_counter,transfer_request_parameter_record),
+ config,
+ )
+
+ asyncdefrequest_transfer_exit(
+ self,
+ transfer_request_parameter_record:bytes=b"",
+ config:UDSRequestConfig|None=None,
+ )->service.NegativeResponse|service.RequestTransferExitResponse:
+"""Ends the transfer of data.
+ This is an implementation of the UDS request for requestTransferExit (0x77).
+
+ :param transfer_request_parameter_record: Optional data.
+ :param config: Passed on to request_pdu().
+ :return: The response of the server.
+ """
+ returnawaitself.request(
+ service.RequestTransferExitRequest(transfer_request_parameter_record),
+ config,
+ )
+
+ asyncdefdefine_by_identifier(
+ self,
+ dynamically_defined_data_identifier:int,
+ source_data_identifiers:int|Sequence[int],
+ positions_in_source_data_record:int|Sequence[int],
+ memory_sizes:int|Sequence[int],
+ suppress_response:bool=False,
+ )->service.NegativeResponse|service.DefineByIdentifierResponse:
+"""Defines a data identifier which combines data from multiple existing data identifiers on the UDS server.
+ This is an implementation of the UDS request for the defineByIdentifier sub-function of the
+ service DynamicallyDefineDataIdentifier (0x2C).
+
+ :param dynamically_defined_data_identifier: The new data identifier.
+ :param source_data_identifiers: The source data identifiers which refer to the data to be included in the new data identifier.
+ :param positions_in_source_data_record: The start positions for each source data identifier. Note, that the position is 1-indexed.
+ :param memory_sizes: The number of bytes for each source data identifier, starting from the starting position.
+ :param suppress_response: If set to True, the server is advised to not send back a positive
+ response.
+ """
+ returnawaitself.request(
+ service.DefineByIdentifierRequest(
+ dynamically_defined_data_identifier,
+ source_data_identifiers,
+ positions_in_source_data_record,
+ memory_sizes,
+ suppress_response,
+ )
+ )
+
+ asyncdefdefine_by_memory_address(
+ self,
+ dynamically_defined_data_identifier:int,
+ memory_addresses:int|Sequence[int],
+ memory_sizes:int|Sequence[int],
+ address_and_length_format_identifier:int|None=None,
+ suppress_response:bool=False,
+ )->service.NegativeResponse|service.DefineByMemoryAddressResponse:
+"""Defines a data identifier which combines data from multiple existing memory regions on the UDS server.
+ This is an implementation of the UDS request for the defineByMemoryAddress sub-function of the
+ service DynamicallyDefineDataIdentifier (0x2C).
+ While it exposes each parameter of the corresponding specification,
+ some parameters can be computed from the remaining ones and can therefore be omitted.
+
+ :param dynamically_defined_data_identifier: The new data identifier.
+ :param memory_addresses: The memory addresses for each source data.
+ :param memory_sizes: The number of bytes for each source data, starting from the memory address.
+ :param address_and_length_format_identifier: The byte lengths of the memory address and
+ size. If omitted, this parameter is computed
+ based on the memory_address and memory_size
+ or data_record parameters.
+ :param suppress_response: If set to True, the server is advised to not send back a positive
+ response.
+ """
+ returnawaitself.request(
+ service.DefineByMemoryAddressRequest(
+ dynamically_defined_data_identifier,
+ memory_addresses,
+ memory_sizes,
+ address_and_length_format_identifier,
+ suppress_response,
+ )
+ )
+
+ asyncdefclear_dynamically_defined_data_identifier(
+ self,dynamically_defined_data_identifier:int|None,suppress_response:bool=False
+ )->service.ClearDynamicallyDefinedDataIdentifierResponse:
+"""Clears either a specific dynamically defined data identifier or all if no data identifier is given.
+ This is an implementation of the UDS request for the clearDynamicallyDefinedDataIdentifier sub-function of the
+ service DynamicallyDefineDataIdentifier (0x2C).
+
+ :param dynamically_defined_data_identifier: The dynamically defined data identifier to be cleared, or None if all are to be cleared.
+ :param suppress_response: If set to True, the server is advised to not send back a positive
+ response.
+ """
+ returnawaitself.request(
+ service.ClearDynamicallyDefinedDataIdentifierRequest(
+ dynamically_defined_data_identifier,suppress_response
+ )
+ )
+
+ @overload
+ asyncdefrequest(
+ self,request:service.RawRequest,config:UDSRequestConfig|None=None
+ )->service.NegativeResponse|service.PositiveResponse:...
+
+ @overload
+ asyncdefrequest(
+ self,
+ request:service.DiagnosticSessionControlRequest,
+ config:UDSRequestConfig|None=None,
+ )->service.NegativeResponse|service.DiagnosticSessionControlResponse:...
+
+ @overload
+ asyncdefrequest(
+ self,
+ request:service.ECUResetRequest,
+ config:UDSRequestConfig|None=None,
+ )->service.NegativeResponse|service.ECUResetResponse:...
+
+ @overload
+ asyncdefrequest(
+ self,
+ request:service.RequestSeedRequest,
+ config:UDSRequestConfig|None=None,
+ )->service.NegativeResponse|service.SecurityAccessResponse:...
+
+ @overload
+ asyncdefrequest(
+ self,request:service.SendKeyRequest,config:UDSRequestConfig|None=None
+ )->service.NegativeResponse|service.SecurityAccessResponse:...
+
+ @overload
+ asyncdefrequest(
+ self,
+ request:service.CommunicationControlRequest,
+ config:UDSRequestConfig|None=None,
+ )->service.NegativeResponse|service.CommunicationControlResponse:...
+
+ @overload
+ asyncdefrequest(
+ self,
+ request:service.TesterPresentRequest,
+ config:UDSRequestConfig|None=None,
+ )->service.NegativeResponse|service.TesterPresentResponse:...
+
+ @overload
+ asyncdefrequest(
+ self,
+ request:service.ControlDTCSettingRequest,
+ config:UDSRequestConfig|None=None,
+ )->service.NegativeResponse|service.ControlDTCSettingResponse:...
+
+ @overload
+ asyncdefrequest(
+ self,
+ request:service.ReadDataByIdentifierRequest,
+ config:UDSRequestConfig|None=None,
+ )->service.NegativeResponse|service.ReadDataByIdentifierResponse:...
+
+ @overload
+ asyncdefrequest(
+ self,
+ request:service.ReadMemoryByAddressRequest,
+ config:UDSRequestConfig|None=None,
+ )->service.NegativeResponse|service.ReadMemoryByAddressResponse:...
+
+ @overload
+ asyncdefrequest(
+ self,
+ request:service.WriteDataByIdentifierRequest,
+ config:UDSRequestConfig|None=None,
+ )->service.NegativeResponse|service.WriteDataByIdentifierResponse:...
+
+ @overload
+ asyncdefrequest(
+ self,
+ request:service.WriteMemoryByAddressRequest,
+ config:UDSRequestConfig|None=None,
+ )->service.NegativeResponse|service.WriteMemoryByAddressResponse:...
+
+ @overload
+ asyncdefrequest(
+ self,
+ request:service.ClearDiagnosticInformationRequest,
+ config:UDSRequestConfig|None=None,
+ )->service.NegativeResponse|service.ClearDiagnosticInformationResponse:...
+
+ @overload
+ asyncdefrequest(
+ self,
+ request:service.ReportNumberOfDTCByStatusMaskRequest,
+ config:UDSRequestConfig|None=None,
+ )->service.NegativeResponse|service.ReportNumberOfDTCByStatusMaskResponse:...
+
+ @overload
+ asyncdefrequest(
+ self,
+ request:service.ReportDTCByStatusMaskRequest,
+ config:UDSRequestConfig|None=None,
+ )->service.NegativeResponse|service.ReportDTCByStatusMaskResponse:...
+
+ @overload
+ asyncdefrequest(
+ self,
+ request:service.ReportMirrorMemoryDTCByStatusMaskRequest,
+ config:UDSRequestConfig|None=None,
+ )->service.NegativeResponse|service.ReportMirrorMemoryDTCByStatusMaskResponse:...
+
+ @overload
+ asyncdefrequest(
+ self,
+ request:service.ReportNumberOfMirrorMemoryDTCByStatusMaskRequest,
+ config:UDSRequestConfig|None=None,
+ )->service.NegativeResponse|service.ReportNumberOfMirrorMemoryDTCByStatusMaskResponse:...
+
+ @overload
+ asyncdefrequest(
+ self,
+ request:service.ReportNumberOfEmissionsRelatedOBDDTCByStatusMaskRequest,
+ config:UDSRequestConfig|None=None,
+ )->(
+ service.NegativeResponse|service.ReportNumberOfEmissionsRelatedOBDDTCByStatusMaskResponse
+ ):...
+
+ @overload
+ asyncdefrequest(
+ self,
+ request:service.ReportEmissionsRelatedOBDDTCByStatusMaskRequest,
+ config:UDSRequestConfig|None=None,
+ )->service.NegativeResponse|service.ReportEmissionsRelatedOBDDTCByStatusMaskResponse:...
+
+ @overload
+ asyncdefrequest(
+ self,
+ request:service.InputOutputControlByIdentifierRequest,
+ config:UDSRequestConfig|None=None,
+ )->service.NegativeResponse|service.InputOutputControlByIdentifierResponse:...
+
+ @overload
+ asyncdefrequest(
+ self,
+ request:service.StartRoutineRequest,
+ config:UDSRequestConfig|None=None,
+ )->service.NegativeResponse|service.StartRoutineResponse:...
+
+ @overload
+ asyncdefrequest(
+ self,
+ request:service.StopRoutineRequest,
+ config:UDSRequestConfig|None=None,
+ )->service.NegativeResponse|service.StopRoutineResponse:...
+
+ @overload
+ asyncdefrequest(
+ self,
+ request:service.RequestRoutineResultsRequest,
+ config:UDSRequestConfig|None=None,
+ )->service.NegativeResponse|service.RequestRoutineResultsResponse:...
+
+ @overload
+ asyncdefrequest(
+ self,
+ request:service.RequestDownloadRequest,
+ config:UDSRequestConfig|None=None,
+ )->service.NegativeResponse|service.RequestDownloadResponse:...
+
+ @overload
+ asyncdefrequest(
+ self,
+ request:service.RequestUploadRequest,
+ config:UDSRequestConfig|None=None,
+ )->service.NegativeResponse|service.RequestUploadResponse:...
+
+ @overload
+ asyncdefrequest(
+ self,
+ request:service.TransferDataRequest,
+ config:UDSRequestConfig|None=None,
+ )->service.NegativeResponse|service.TransferDataResponse:...
+
+ @overload
+ asyncdefrequest(
+ self,
+ request:service.RequestTransferExitRequest,
+ config:UDSRequestConfig|None=None,
+ )->service.NegativeResponse|service.RequestTransferExitResponse:...
+
+ @overload
+ asyncdefrequest(
+ self,request:service.DefineByIdentifierRequest,config:UDSRequestConfig|None=None
+ )->service.DefineByIdentifierResponse:...
+
+ @overload
+ asyncdefrequest(
+ self,request:service.DefineByMemoryAddressRequest,config:UDSRequestConfig|None=None
+ )->service.DefineByMemoryAddressResponse:...
+
+ @overload
+ asyncdefrequest(
+ self,
+ request:service.ClearDynamicallyDefinedDataIdentifierRequest,
+ config:UDSRequestConfig|None=None,
+ )->service.ClearDynamicallyDefinedDataIdentifierResponse:...
+
+ asyncdefrequest(
+ self,request:service.UDSRequest,config:UDSRequestConfig|None=None
+ )->service.UDSResponse:
+"""Sends a raw UDS request and returns the response.
+ Network errors are handled via exponential backoff.
+ Pending errors, triggered by the ECU are resolved as well.
+
+ :param request: request to send
+ :param config: The request config parameters
+ :return: The response.
+ """
+ returnawaitself._request(request,config)
+
+ asyncdef_request(
+ self,request:service.UDSRequest,config:UDSRequestConfig|None=None
+ )->service.UDSResponse:
+ asyncwithself.mutex:
+ returnawaitself.request_unsafe(request,config)
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/_modules/gallia/services/uds/core/constants.html b/_modules/gallia/services/uds/core/constants.html
new file mode 100644
index 000000000..eb718d74d
--- /dev/null
+++ b/_modules/gallia/services/uds/core/constants.html
@@ -0,0 +1,368 @@
+
+
+
+
+
+
+
+ gallia.services.uds.core.constants — gallia documentation
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+@unique
+classDiagnosticSessionControlSubFuncs(IntEnum):
+ defaultSession=0x01
+ programmingSession=0x02
+ extendedDiagnosticSession=0x03
+ safetySystemDiagnosticSession=0x04
+
+
+@unique
+classRoutineControlSubFuncs(IntEnum):
+ startRoutine=0x01
+ stopRoutine=0x02
+ requestRoutineResults=0x03
+
+
+@unique
+classCCSubFuncs(IntEnum):
+ enableRxAndTx=0x00
+ enableRxAndDisableTx=0x01
+ disableRxAndEnableTx=0x02
+ disableRxAndTx=0x03
+ # Plus vendor specific stuff...
+
+
+@unique
+classCDTCSSubFuncs(IntEnum):
+ ON=0x01
+ OFF=0x02
+ # Plus vendor specific stuff...
+
+
+@unique
+classReadDTCInformationSubFuncs(IntEnum):
+ reportNumberOfDTCByStatusMask=0x01
+ reportDTCByStatusMask=0x02
+ reportSupportedDTC=0x0A
+ reportFirstTestFailedDTC=0x0B
+ reportFirstConfirmedDTC=0x0C
+ reportMostRecentTestFailedDTC=0x0D
+ reportMostRecentConfirmedDTC=0x0E
+ reportMirrorMemoryDTCByStatusMask=0x0F
+ reportNumberOfMirrorMemoryDTCByStatusMask=0x11
+ reportNumberOfEmissionsRelatedOBDDTCByStatusMask=0x12
+ reportEmissionsRelatedOBDDTCByStatusMask=0x13
+ reportDTCFaultDetectionCounter=0x14
+ reportDTCWithPermanentStatus=0x15
+
+
+@unique
+classEcuResetSubFuncs(IntEnum):
+ hardReset=0x01
+ keyOffOnReset=0x02
+ softReset=0x03
+ enableRapidPowerShutDown=0x04
+ disableRapidPowerShutDown=0x05
+
+
+@unique
+classInputOutputControlParameter(IntEnum):
+ returnControlToECU=0x00
+ resetToDefault=0x01
+ freezeCurrentState=0x02
+ shortTermAdjustment=0x03
+
+
+@unique
+classDTCFormatIdentifier(IntEnum):
+ # ISO15031-6DTCFormat
+ ISO_15031_6=0x00
+ # ISO14229-1DTCFormat
+ ISO_14229_1=0x01
+ # SAEJ1939-73DTCFormat
+ SAE_J1939_73=0x02
+ # ISO11992-4DTCFormat
+ ISO_11992_4=0x03
+
+
+# This dictionary maps UDS services to the echo length of their responses.
+# Echos in that context are values which are identical to the corresponding entry in a request.
+# Therefore they can be used to match responses to requests but are not adding any new information.
+# Some services (e.g. rdbi) can accept more data records in a request and response accordingly.
+# In that case there can be several echos. However, as of the time of writing, multiple data
+# records are not considered in the rest of the code.
+# For a complete handling one might need to transfer this to a function.
+UDSIsoServicesEchoLength={
+ UDSIsoServices.DiagnosticSessionControl:1,
+ UDSIsoServices.EcuReset:1,
+ UDSIsoServices.SecurityAccess:1,
+ UDSIsoServices.CommunicationControl:1,
+ UDSIsoServices.TesterPresent:1,
+ UDSIsoServices.AccessTimingParameter:1,
+ UDSIsoServices.ControlDTCSetting:1,
+ UDSIsoServices.ResponseOnEvent:1,# There are a number of echos but only one byte is a prefix
+ UDSIsoServices.LinkControl:1,
+ UDSIsoServices.ReadDataByIdentifier:2,
+ UDSIsoServices.ReadScalingDataByIdentifier:2,
+ UDSIsoServices.ReadDataByPeriodicIdentifier:1,# This one is a little weird
+ UDSIsoServices.DynamicallyDefineDataIdentifier:3,
+ UDSIsoServices.WriteDataByIdentifier:2,
+ # This one would require to parse the addressAndLengthFormatIdentifier field
+ # UDSIsoServices.WriteMemoryByAddress: None,
+ UDSIsoServices.ReadDTCInformation:1,
+ UDSIsoServices.InputOutputControlByIdentifier:2,
+ UDSIsoServices.RoutineControl:3,
+ UDSIsoServices.TransferData:1,
+}
+
+
+@unique
+classDataIdentifier(IntEnum):
+ ActiveDiagnosticSessionDataIdentifier=0xF186
+
+
+@unique
+classDynamicallyDefineDataIdentifierSubFuncs(IntEnum):
+ defineByIdentifier=0x01
+ defineByMemoryAddress=0x02
+ clearDynamicallyDefinedDataIdentifier=0x03
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/_modules/gallia/services/uds/core/service.html b/_modules/gallia/services/uds/core/service.html
new file mode 100644
index 000000000..5be882ce6
--- /dev/null
+++ b/_modules/gallia/services/uds/core/service.html
@@ -0,0 +1,3869 @@
+
+
+
+
+
+
+
+ gallia.services.uds.core.service — gallia documentation
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+[docs]
+ asyncdefread_session(self,config:UDSRequestConfig|None=None)->int:
+"""Read out current session.
+
+ Returns:
+ The current session as int.
+ """
+ resp=awaitself.read_data_by_identifier(
+ DataIdentifier.ActiveDiagnosticSessionDataIdentifier,config=config
+ )
+ ifisinstance(resp,service.NegativeResponse):
+ raiseas_exception(resp)
+ returnfrom_bytes(resp.data_record)
+
+
+
+[docs]
+ asyncdefset_session_pre(self,level:int,config:UDSRequestConfig|None=None)->bool:
+"""set_session_pre() is called before the diagnostic session control
+ pdu is written on the wire. Implement this if there are special
+ preconditions for a particular session, such as disabling error
+ logging.
+
+ Args:
+ uds: The UDSClient class where this hook is embedded. The caller typically
+ calls this function with `self` as the first argument.
+ session: The desired session identifier.
+ Returns:
+ True on success, False on error.
+ """
+ returnTrue
+
+
+
+[docs]
+ asyncdefset_session_post(self,level:int,config:UDSRequestConfig|None=None)->bool:
+"""set_session_post() is called after the diagnostic session control
+ pdu was written on the wire. Implement this if there are special
+ cleanup routines or sleeping until a certain moment is required.
+
+ Args:
+ uds: The UDSClient class where this hook is embedded. The caller typically
+ calls this function with `self` as the first argument.
+ session: The desired session identifier.
+ Returns:
+ True on success, False on error.
+ """
+ returnTrue
+
+
+
+[docs]
+ asyncdefcheck_and_set_session(
+ self,
+ expected_session:int,
+ retries:int=3,
+ )->bool:
+"""check_and_set_session() reads the current session and (re)tries to set
+ the session to the expected session if they do not match.
+
+ Returns True if the current session matches the expected session,
+ or if read_session is not supported by the ECU or in the current session."""
+
+ logger.debug(f"Checking current session, expecting {g_repr(expected_session)}")
+
+ try:
+ current_session=awaitself.read_session(config=UDSRequestConfig(max_retry=retries))
+ exceptUnexpectedNegativeResponsease:
+ ifsuggests_identifier_not_supported(e.RESPONSE_CODE):
+ logger.info(
+ f"Read current session not supported: {e.RESPONSE_CODE.name}, skipping check_session"
+ )
+ returnTrue
+ raisee
+ exceptTimeoutError:
+ logger.warning("Reading current session timed out, skipping check_session")
+ returnTrue
+
+ logger.debug(f"Current session is {g_repr(current_session)}")
+ ifcurrent_session==expected_session:
+ returnTrue
+
+ foriinrange(retries):
+ logger.warning(
+ f"Not in session {g_repr(expected_session)}, ECU replied with {g_repr(current_session)}"
+ )
+
+ logger.info(
+ f"Switching to session {g_repr(expected_session)}; attempt {i+1} of {retries}"
+ )
+ resp=awaitself.set_session(expected_session)
+
+ ifisinstance(resp,service.NegativeResponse):
+ logger.warning(f"Switching to session {g_repr(expected_session)} failed: {resp}")
+
+ try:
+ current_session=awaitself.read_session(
+ config=UDSRequestConfig(max_retry=retries)
+ )
+ logger.debug(f"Current session is {g_repr(current_session)}")
+ ifcurrent_session==expected_session:
+ returnTrue
+ exceptUnexpectedNegativeResponsease:
+ ifsuggests_identifier_not_supported(e.RESPONSE_CODE):
+ logger.info(
+ f"Read current session not supported: {e.RESPONSE_CODE.name}, skipping check_session"
+ )
+ returnTrue
+ raisee
+ exceptTimeoutError:
+ logger.warning("Reading current session timed out, skipping check_session")
+ returnTrue
+
+ logger.warning(
+ f"Failed to switch to session {g_repr(expected_session)} after {retries} attempts"
+ )
+ returnFalse
+[docs]
+ asyncdefleave_session(
+ self,
+ level:int,
+ config:UDSRequestConfig|None=None,
+ sleep:float|None=None,
+ )->bool:
+"""leave_session() is a hook which can be called explicitly by a
+ scanner when a session is to be disabled. Use this hook if resetting
+ the ECU is required, e.g. when disabling the programming session.
+ """
+ resp:service.UDSResponse=awaitself.ecu_reset(0x01)
+ ifisinstance(resp,service.NegativeResponse):
+ ifsleepisnotNone:
+ awaitself.power_cycle(sleep=sleep)
+ else:
+ awaitself.power_cycle()
+ awaitself.reconnect()
+ awaitself.wait_for_ecu()
+
+ resp=awaitself.set_session(0x01,config=config)
+ ifisinstance(resp,service.NegativeResponse):
+ ifsleepisnotNone:
+ awaitself.power_cycle(sleep=sleep)
+ else:
+ awaitself.power_cycle()
+ awaitself.reconnect()
+ returnTrue
+[docs]
+ asyncdefread_dtc(
+ self,config:UDSRequestConfig|None=None
+ )->service.NegativeResponse|service.ReportDTCByStatusMaskResponse:
+"""Read all dtc records from the ecu."""
+ returnawaitself.read_dtc_information_report_dtc_by_status_mask(0xFF,config=config)
+
+
+
+[docs]
+ asyncdefclear_dtc(
+ self,config:UDSRequestConfig|None=None
+ )->service.NegativeResponse|service.ClearDiagnosticInformationResponse:
+"""Clear all dtc records on the ecu."""
+ returnawaitself.clear_diagnostic_information(0xFFFFFF,config=config)
+
+
+
+[docs]
+ asyncdefread_vin(
+ self,config:UDSRequestConfig|None=None
+ )->service.NegativeResponse|service.ReadDataByIdentifierResponse:
+"""Read the VIN of the vehicle"""
+ returnawaitself.read_data_by_identifier(0xF190,config=config)
+
+
+
+[docs]
+ asyncdeftransmit_data(
+ self,
+ data:bytes,
+ block_length:int,
+ max_block_length:int=0xFFF,
+ config:UDSRequestConfig|None=None,
+ )->None:
+"""transmit_data splits the data to be sent in several blocks of size block_length,
+ transfers all of them and concludes the transmission with RequestTransferExit"""
+ ifblock_length>max_block_length:
+ logger.warning(f"Limiting block size to {g_repr(max_block_length)}")
+ block_length=max_block_length
+ # block_length includes the service identifier and block counter; payload must be smaller
+ payload_size=block_length-2
+ counter=0
+ foriinrange(0,len(data),payload_size):
+ counter+=1
+ payload=data[i:i+payload_size]
+ logger.debug(
+ f"Transferring block {g_repr(counter)} with payload size {g_repr(len(payload))}"
+ )
+ resp:service.UDSResponse=awaitself.transfer_data(
+ counter&0xFF,payload,config=config
+ )
+ raise_for_error(resp,f"Transmitting data failed at index {g_repr(i)}")
+ resp=awaitself.request_transfer_exit(config=config)
+ raise_for_error(resp)
+
+
+ asyncdef_wait_for_ecu_endless_loop(self,sleep_time:float)->None:
+"""Internal method with endless loop in case of no answer from ECU"""
+ config=UDSRequestConfig(timeout=0.5,max_retry=0,skip_hooks=True)
+ i=-1
+ whileTrue:
+ i=(i+1)%4
+ logger.info(f"Waiting for ECU{'.'*i}")
+ try:
+ awaitasyncio.sleep(sleep_time)
+ awaitself.ping(config=config)
+ break
+ exceptConnectionErrorase:
+ logger.debug(f"ECU not ready: {e!r}, reconnecting…")
+ awaitself.reconnect()
+ exceptUDSExceptionase:
+ logger.debug(f"ECU not ready: {e!r}")
+ logger.info("ECU ready")
+
+
+[docs]
+ asyncdefwait_for_ecu(
+ self,
+ timeout:float|None=10,
+ )->bool:
+"""Wait for ecu to be alive again (e.g. after reset).
+ Sends a ping every 0.5s and waits at most timeout.
+ If timeout is None, wait endlessly"""
+ logger.info(f"Waiting for {timeout}s for ECU to respond")
+ ifself.tester_present_taskandself.tester_present_interval:
+ awaitself.stop_cyclic_tester_present()
+
+ try:
+ awaitasyncio.wait_for(self._wait_for_ecu_endless_loop(0.5),timeout=timeout)
+ returnTrue
+ exceptTimeoutError:
+ logger.critical("Timeout while waiting for ECU!")
+ returnFalse
+ finally:
+ ifself.tester_present_taskandself.tester_present_interval:
+ awaitself.start_cyclic_tester_present(self.tester_present_interval)
+
+
+ asyncdef_tester_present_worker(self,interval:float)->None:
+ assertself.transport
+ logger.debug("tester present worker started")
+ task=asyncio.current_task()
+ whiletaskisnotNoneandtask.cancelling()==0:
+ try:
+ awaitasyncio.sleep(interval)
+ # TODO: Only ping if there was no other UDS traffic for `interval` amount of time
+ awaitself.ping(UDSRequestConfig(max_retry=0))
+ exceptasyncio.CancelledError:
+ logger.debug("tester present worker terminated")
+ raise
+ exceptConnectionError:
+ logger.info("connection lost; tester present waiting…")
+ exceptExceptionase:
+ logger.warning(f"Tester present worker got {e!r}")
+ logger.debug("Tester present worker was cancelled but received no asyncio.CancelledError")
+
+ asyncdefstart_cyclic_tester_present(self,interval:float)->None:
+ logger.debug("Starting tester present worker")
+ self.tester_present_interval=interval
+ coroutine=self._tester_present_worker(interval)
+ self.tester_present_task=asyncio.create_task(coroutine)
+ self.tester_present_task.add_done_callback(
+ handle_task_error,
+ context=set_task_handler_ctx_variable(__name__,"TesterPresent"),
+ )
+
+ # enforce context switch
+ # this ensures, that the task is executed at least once
+ # if the task is not executed, task.cancel will fail with CancelledError
+ awaitasyncio.sleep(0)
+
+ asyncdefstop_cyclic_tester_present(self)->None:
+ logger.debug("Stopping tester present worker")
+ ifself.tester_present_taskisNone:
+ logger.warning("BUG: stop_cyclic_tester_present() called but no task running")
+ return
+
+ self.tester_present_task.cancel()
+ try:
+ awaitself.tester_present_task
+ exceptasyncio.CancelledError:
+ pass
+
+ asyncdefupdate_state(
+ self,request:service.UDSRequest,response:service.UDSResponse
+ )->None:
+ ifisinstance(response,service.DiagnosticSessionControlResponse):
+ self.state.reset()
+ self.state.session=response.diagnostic_session_type
+
+ if(
+ isinstance(response,service.ReadDataByIdentifierResponse)
+ andresponse.data_identifier==DataIdentifier.ActiveDiagnosticSessionDataIdentifier
+ ):
+ new_session=int.from_bytes(response.data_record,"big")
+
+ ifself.state.session!=new_session:
+ self.state.reset()
+ self.state.session=new_session
+
+ if(
+ isinstance(response,service.SecurityAccessResponse)
+ andresponse.security_access_type%2==0
+ ):
+ self.state.security_access_level=response.security_access_type-1
+
+ ifisinstance(response,service.ECUResetResponse):
+ self.state.reset()
+
+
+[docs]
+ asyncdefrefresh_state(self,reset_state:bool=False)->None:
+"""
+ Refresh the attributes of the ECU states, if possible.
+ By, default, old values are only overwritten in case the corresponding
+ information can be requested from the ECU and could be retrieved from a
+ positive response from the ECU.
+
+ :param reset_state: If True, the ECU state is reset before updating it.
+ """
+ ifreset_state:
+ self.state.reset()
+
+ awaitself.read_session()
+
+
+ asyncdef_request(
+ self,request:service.UDSRequest,config:UDSRequestConfig|None=None
+ )->service.UDSResponse:
+"""Sends a raw UDS request and returns the response.
+ Network errors are handled via exponential backoff.
+ Pending errors, triggered by the ECU are resolved as well.
+
+ :param request: request to send
+ :param config: The request config parameters
+ :return: The response.
+ """
+ response=None
+ exception:Exception|None=None
+ send_time=datetime.now(UTC).astimezone()
+ receive_time=None
+
+ try:
+ response=awaitsuper()._request(request,config)
+ receive_time=datetime.now(UTC).astimezone()
+ returnresponse
+ exceptResponseExceptionase:
+ exception=e
+ response=e.response
+ raise
+ exceptExceptionase:
+ exception=e
+ raise
+ finally:
+ try:
+ ifself.implicit_loggingandself.db_handlerisnotNone:
+ mode=LogMode.implicit
+
+ ifconfigisnotNoneandconfig.tagsisnotNoneand"ANALYZE"inconfig.tags:
+ mode=LogMode.emphasized
+
+ awaitself.db_handler.insert_scan_result(
+ self.state.__dict__,
+ service.UDSRequest.parse_dynamic(request.pdu),
+ response,
+ exception,
+ send_time,
+ receive_time,
+ mode,
+ )
+ exceptExceptionase:
+ logger.warning(f"Could not log messages to database: {g_repr(e)}")
+
+ ifresponseisnotNone:
+ awaitself.update_state(request,response)
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/_modules/gallia/transports/base.html b/_modules/gallia/transports/base.html
new file mode 100644
index 000000000..3d3160359
--- /dev/null
+++ b/_modules/gallia/transports/base.html
@@ -0,0 +1,421 @@
+
+
+
+
+
+
+
+ gallia.transports.base — gallia documentation
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+[docs]
+classTargetURI:
+"""TargetURI represents a target to which gallia can connect.
+ The target string must conform to a URI is specified by RFC3986.
+
+ Basically, this is a wrapper around Python's ``urlparse()`` and
+ ``parse_qs()`` methods. TargetURI provides frequently used properties
+ for a more userfriendly usage. Instances are meant to be passed to
+ :meth:`BaseTransport.connect()` of transport implementations.
+ """
+
+ def__init__(self,raw:str)->None:
+ self.raw=raw
+ self.url=urlparse(raw)
+ self.qs=parse_qs(self.url.query)
+
+
+[docs]
+ @classmethod
+ deffrom_parts(
+ cls,
+ scheme:str,
+ host:str,
+ port:int|None,
+ args:dict[str,Any],
+ )->Self:
+"""Constructs a instance of TargetURI with the given arguments.
+ The ``args`` dict is used for the query string.
+ """
+ netloc=hostifportisNoneelsejoin_host_port(host,port)
+ returncls(urlunparse((scheme,netloc,"","",urlencode(args),"")))
+
+
+ @property
+ defscheme(self)->TransportScheme:
+"""The URI scheme"""
+ returnTransportScheme(self.url.scheme)
+
+ @property
+ defhostname(self)->str|None:
+"""The hostname (without port)"""
+ returnself.url.hostname
+
+ @property
+ defport(self)->int|None:
+"""The port number"""
+ returnself.url.port
+
+ @property
+ defnetloc(self)->str:
+"""The hostname and the portnumber, separated by a colon."""
+ returnself.url.netloc
+
+ @property
+ defpath(self)->str:
+"""The path property of the url."""
+ returnself.url.path
+
+ @property
+ deflocation(self)->str:
+"""A URI string which only consists of the relevant scheme,
+ the host and the port.
+ """
+ returnf"{self.scheme}://{self.url.netloc}"
+
+ @property
+ defqs_flat(self)->dict[str,str]:
+"""A dict which contains the query string's key/value pairs.
+ In case a key appears multiple times, this variant only
+ contains the first found key/value pair. In contrast to
+ :attr:`qs`, this variant avoids lists and might be easier
+ to use for some cases.
+ """
+ d={}
+ fork,vinself.qs.items():
+ d[k]=v[0]
+ returnd
+
+ def__str__(self)->str:
+ returnself.raw
+[docs]
+classBaseTransport(ABC):
+"""BaseTransport is the base class providing the required
+ interface for all transports used by gallia.
+
+ A transport usually is some kind of network protocol which
+ carries an application level protocol. A good example is
+ DoIP carrying UDS requests which acts as a minimal middleware
+ on top of TCP.
+
+ This class is to be used as a subclass with all abstractmethods
+ implemented and the SCHEME property filled.
+
+ A few methods provide a ``tags`` argument. The debug logs of these
+ calls include these tags in the ``tags`` property of the relevant
+ :class:`gallia.log.PenlogRecord`.
+ """
+
+ #: The scheme for the implemented protocol, e.g. "doip".
+ SCHEME:str=""
+ #: The buffersize of the transport. Might be used in read() calls.
+ #: Defaults to :const:`io.DEFAULT_BUFFER_SIZE`.
+ BUFSIZE:int=io.DEFAULT_BUFFER_SIZE
+
+ def__init__(self,target:TargetURI)->None:
+ self.mutex=asyncio.Lock()
+ self.target=target
+ self.is_closed=False
+
+ def__init_subclass__(
+ cls,
+ /,
+ scheme:str,
+ bufsize:int=io.DEFAULT_BUFFER_SIZE,
+ **kwargs:Any,
+ )->None:
+ super().__init_subclass__(**kwargs)
+ cls.SCHEME=scheme
+ cls.BUFSIZE=bufsize
+
+
+[docs]
+ @classmethod
+ defcheck_scheme(cls,target:TargetURI)->None:
+"""Checks if the provided URI has the correct scheme."""
+ iftarget.scheme!=cls.SCHEME:
+ raiseValueError(f"invalid scheme: {target.scheme}; expected: {cls.SCHEME}")
+
+
+
+[docs]
+ @classmethod
+ @abstractmethod
+ asyncdefconnect(
+ cls,
+ target:str|TargetURI,
+ timeout:float|None=None,
+ )->Self:
+"""Classmethod to connect the transport to a relevant target.
+ The target argument is a URI, such as `doip://192.0.2.2:13400?src_addr=0xf4&dst_addr=0x1d"`
+ An instance of the relevant transport class is returned.
+ """
+
+
+
+[docs]
+ @abstractmethod
+ asyncdefclose(self)->None:
+"""Terminates the connection and clean up all allocated ressources."""
+
+
+
+[docs]
+ asyncdefreconnect(self,timeout:float|None=None)->Self:
+"""Closes the connection to the target and attempts to reconnect every
+ 100 ms until at max timeout. If timeout is None, only attempt to connect
+ once.
+ A new instance of this class is returned rendering the old one obsolete.
+ This method is safe for concurrent use.
+ """
+ asyncwithself.mutex:
+ try:
+ awaitself.close()
+ exceptConnectionErrorase:
+ logger.warning(f"close() failed during reconnect ({e!r}); ignoring")
+
+ asyncwithasyncio.timeout(timeout):
+ logger.debug(
+ f"Attempting to establish a new connection with a timeout of {timeout}"
+ )
+ whileTrue:
+ try:
+ returnawaitself.connect(self.target)
+ exceptConnectionErrorase:
+ logger.info(f"Connection attempt failed while reconnecting: {e!r}")
+ iftimeoutisNone:
+ logger.debug("Breaking out of the reconnect-loop since timeout is None")
+ raisee
+ awaitasyncio.sleep(0.1)
+
+
+
+[docs]
+ @abstractmethod
+ asyncdefread(
+ self,
+ timeout:float|None=None,
+ tags:list[str]|None=None,
+ )->bytes:
+"""Reads one message and returns its raw byte representation.
+ An example for one message is 'one line, terminated by newline'
+ for a TCP transport yielding lines.
+ """
+
+
+
+[docs]
+ @abstractmethod
+ asyncdefwrite(
+ self,
+ data:bytes,
+ timeout:float|None=None,
+ tags:list[str]|None=None,
+ )->int:
+"""Writes one message and return the number of written bytes."""
+
+
+
+[docs]
+ asyncdefrequest(
+ self,
+ data:bytes,
+ timeout:float|None=None,
+ tags:list[str]|None=None,
+ )->bytes:
+"""Chains a :meth:`write()` call with a :meth:`read()` call.
+ The call is protected by a mutex and is thus safe for concurrent
+ use.
+ """
+ asyncwithself.mutex:
+ returnawaitself.request_unsafe(data,timeout,tags)
+
+
+
+[docs]
+ asyncdefrequest_unsafe(
+ self,
+ data:bytes,
+ timeout:float|None=None,
+ tags:list[str]|None=None,
+ )->bytes:
+"""Chains a :meth:`write()` call with a :meth:`read()` call.
+ The call is **not** protected by a mutex. Only use this method
+ when you know what you are doing.
+ """
+ awaitself.write(data,timeout,tags)
+ returnawaitself.read(timeout,tags)
+[docs]
+ asyncdefget_idle_traffic(self,sniff_time:float)->list[int]:
+"""Listen to traffic on the bus and return list of IDs
+ which are seen in the specified period of time.
+ The output of this function can be used as input to set_filter.
+ """
+ addr_idle:list[int]=[]
+ t1=time.time()
+ whiletime.time()-t1<sniff_time:
+ try:
+ addr,_=awaitself.recvfrom(timeout=1)
+ ifaddrnotinaddr_idle:
+ logger.info(f"Received a message from {addr:03x}")
+ addr_idle.append(addr)
+ exceptTimeoutError:
+ continue
+ addr_idle.sort()
+ returnaddr_idle
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/_modules/gallia/transports/doip.html b/_modules/gallia/transports/doip.html
new file mode 100644
index 000000000..8585743be
--- /dev/null
+++ b/_modules/gallia/transports/doip.html
@@ -0,0 +1,1010 @@
+
+
+
+
+
+
+
+ gallia.transports.doip — gallia documentation
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+[docs]
+ asyncdefreconnect(self,timeout:float|None=None)->Self:
+ # It might be that the DoIP endpoint is not immediately ready for another
+ # connection, so set the timeout to 10s by default.
+ returnawaitsuper().reconnect(10iftimeoutisNoneelsetimeout)
+[docs]
+ asyncdefwrite(
+ self,
+ data:bytes,
+ timeout:float|None=None,
+ tags:list[str]|None=None,
+ )->int:
+ t=tags+["write"]iftagsisnotNoneelse["write"]
+ logger.trace(data.hex(),extra={"tags":t})
+
+ try:
+ awaitasyncio.wait_for(self._conn.write_diag_request(data),timeout)
+ exceptDoIPNegativeAckErrorase:
+ ife.nack_code!=DiagnosticMessageNegativeAckCodes.TargetUnreachable:
+ raisee
+ # TargetUnreachable can be just a temporary issue. Thus, we do not raise
+ # BrokenPipeError but instead ignore it here and let upper layers handle
+ # missing responses
+ logger.debug("DoIP message was ACKed with TargetUnreachable")
+
+ returnlen(data)
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/_modules/gallia/transports/hsfz.html b/_modules/gallia/transports/hsfz.html
new file mode 100644
index 000000000..d37f59736
--- /dev/null
+++ b/_modules/gallia/transports/hsfz.html
@@ -0,0 +1,505 @@
+
+
+
+
+
+
+
+ gallia.transports.hsfz — gallia documentation
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
BaseCommand is the baseclass for all gallia commands.
+This class can be used in standalone scripts via the
+gallia command line interface facility.
+
This class needs to be subclassed and all the abstract
+methods need to be implemented. The artifacts_dir is
+generated based on the COMMAND, GROUP, SUBGROUP
+properties (falls back to the class name if all three
+are not set).
Scanner is a base class for all scanning related commands.
+A scanner has the following properties:
+
+
It is async.
+
It loads transports via TargetURIs; available via self.transport.
+
Controlling PowerSupplies via the opennetzteil API is supported.
+
setup() can be overwritten (do not forget to call super().setup())
+for preparation tasks, such as establishing a network connection or
+starting background tasks.
+
pcap logfiles can be recorded via a Dumpcap background task.
+
teardown() can be overwritten (do not forget to call super().teardown())
+for cleanup tasks, such as terminating a network connection or background
+tasks.
+
main() is the relevant entry_point for the scanner and must be implemented.
A wrapper around the constants exposed by python’s
+logging module. Since gallia adds two additional
+loglevel’s (NOTICE and TRACE), this class
+provides a type safe way to access the loglevels.
+The level NOTICE was added to conform better to
+RFC3164. Subsequently, TRACE was added to have
+a facility for optional debug messages.
+Loglevel describes python specific values for loglevels
+which are required to integrate with the python ecosystem.
+For generic priority values, see PenlogPriority.
PenlogPriority holds the values which are written
+to json log records. These values conform to RFC3164
+with the addition of TRACE. Since Python uses different
+int values for the loglevels, there are two enums in
+gallia describing loglevels. PenlogPriority describes
+generic priority values which are included in json
+log records.
Converts a string to an instance of PenlogPriority.
+string can be a numeric value (0 to 8 inclusive)
+or a string with a case insensitive name of the level
+(e.g. debug).
Enable and configure gallia’s logging system.
+If this fuction is not called as early as possible,
+the logging system is in an undefined state und might
+not behave as expected. Always use this function to
+initialize gallia’s logging. For instance, setup_logging()
+initializes a QueueHandler to avoid blocking calls during
+logging.
+
+
Parameters:
+
+
level – The loglevel to enable for the console handler.
+If this argument is None, the env variable
+GALLIA_LOGLEVEL (see Environment Variables) is read.
+
file_level – The loglevel to enable for the file handler.
+
path – The path to the logfile containing json records.
+
color_mode – The color mode to use for the console.
ECU is a high level interface wrapping a UDSClient class. It provides
+semantically correct higher level interfaces such as read_session()
+or ping(). Vendor specific implementations can be derived from this
+class. For the arguments of the constructor, please check uds.uds.UDS.
leave_session() is a hook which can be called explicitly by a
+scanner when a session is to be disabled. Use this hook if resetting
+the ECU is required, e.g. when disabling the programming session.
Refresh the attributes of the ECU states, if possible.
+By, default, old values are only overwritten in case the corresponding
+information can be requested from the ECU and could be retrieved from a
+positive response from the ECU.
+
+
Parameters:
+
reset_state – If True, the ECU state is reset before updating it.
set_session_post() is called after the diagnostic session control
+pdu was written on the wire. Implement this if there are special
+cleanup routines or sleeping until a certain moment is required.
+
+
Parameters:
+
+
uds – The UDSClient class where this hook is embedded. The caller typically
+calls this function with self as the first argument.
set_session_pre() is called before the diagnostic session control
+pdu is written on the wire. Implement this if there are special
+preconditions for a particular session, such as disabling error
+logging.
+
+
Parameters:
+
+
uds – The UDSClient class where this hook is embedded. The caller typically
+calls this function with self as the first argument.
transmit_data splits the data to be sent in several blocks of size block_length,
+transfers all of them and concludes the transmission with RequestTransferExit
BaseTransport is the base class providing the required
+interface for all transports used by gallia.
+
A transport usually is some kind of network protocol which
+carries an application level protocol. A good example is
+DoIP carrying UDS requests which acts as a minimal middleware
+on top of TCP.
+
This class is to be used as a subclass with all abstractmethods
+implemented and the SCHEME property filled.
+
A few methods provide a tags argument. The debug logs of these
+calls include these tags in the tags property of the relevant
+gallia.log.PenlogRecord.
Classmethod to connect the transport to a relevant target.
+The target argument is a URI, such as doip://192.0.2.2:13400?src_addr=0xf4&dst_addr=0x1d”
+An instance of the relevant transport class is returned.
Reads one message and returns its raw byte representation.
+An example for one message is ‘one line, terminated by newline’
+for a TCP transport yielding lines.
Closes the connection to the target and attempts to reconnect every
+100 ms until at max timeout. If timeout is None, only attempt to connect
+once.
+A new instance of this class is returned rendering the old one obsolete.
+This method is safe for concurrent use.
Classmethod to connect the transport to a relevant target.
+The target argument is a URI, such as doip://192.0.2.2:13400?src_addr=0xf4&dst_addr=0x1d”
+An instance of the relevant transport class is returned.
Reads one message and returns its raw byte representation.
+An example for one message is ‘one line, terminated by newline’
+for a TCP transport yielding lines.
Closes the connection to the target and attempts to reconnect every
+100 ms until at max timeout. If timeout is None, only attempt to connect
+once.
+A new instance of this class is returned rendering the old one obsolete.
+This method is safe for concurrent use.
Classmethod to connect the transport to a relevant target.
+The target argument is a URI, such as doip://192.0.2.2:13400?src_addr=0xf4&dst_addr=0x1d”
+An instance of the relevant transport class is returned.
Reads one message and returns its raw byte representation.
+An example for one message is ‘one line, terminated by newline’
+for a TCP transport yielding lines.
Classmethod to connect the transport to a relevant target.
+The target argument is a URI, such as doip://192.0.2.2:13400?src_addr=0xf4&dst_addr=0x1d”
+An instance of the relevant transport class is returned.
Reads one message and returns its raw byte representation.
+An example for one message is ‘one line, terminated by newline’
+for a TCP transport yielding lines.
Classmethod to connect the transport to a relevant target.
+The target argument is a URI, such as doip://192.0.2.2:13400?src_addr=0xf4&dst_addr=0x1d”
+An instance of the relevant transport class is returned.
Listen to traffic on the bus and return list of IDs
+which are seen in the specified period of time.
+The output of this function can be used as input to set_filter.
Reads one message and returns its raw byte representation.
+An example for one message is ‘one line, terminated by newline’
+for a TCP transport yielding lines.
Classmethod to connect the transport to a relevant target.
+The target argument is a URI, such as doip://192.0.2.2:13400?src_addr=0xf4&dst_addr=0x1d”
+An instance of the relevant transport class is returned.
Reads one message and returns its raw byte representation.
+An example for one message is ‘one line, terminated by newline’
+for a TCP transport yielding lines.
TargetURI represents a target to which gallia can connect.
+The target string must conform to a URI is specified by RFC3986.
+
Basically, this is a wrapper around Python’s urlparse() and
+parse_qs() methods. TargetURI provides frequently used properties
+for a more userfriendly usage. Instances are meant to be passed to
+BaseTransport.connect() of transport implementations.
A dict which contains the query string’s key/value pairs.
+In case a key appears multiple times, this variant only
+contains the first found key/value pair. In contrast to
+qs, this variant avoids lists and might be easier
+to use for some cases.
Classmethod to connect the transport to a relevant target.
+The target argument is a URI, such as doip://192.0.2.2:13400?src_addr=0xf4&dst_addr=0x1d”
+An instance of the relevant transport class is returned.
Reads one message and returns its raw byte representation.
+An example for one message is ‘one line, terminated by newline’
+for a TCP transport yielding lines.
gallia has support for controlling power supplies either directly via builtin drivers.
+Power supplies are mostly used for power cycling the current device under test.
+There is no limit in accessing power supplies, e.g. voltage or current settings can be controlled as well.
+
Own drivers can be included by implementing the gallia.power_supply.BasePowerSupply interface.
+On the commandline there is the --power-supply argument to specify a relevant power supply.
+Further, there is --power-cycle to automatically power-cycle the device under test.
+There is an experimental cli tool netzteil included in gallia.
+This cli tool can be used to control all supported power supplies via the cli.
+
The argument for --power-supply is a URI of the following form:
All gallia settings stem from the commandline interface.
+The documentation for all available settings per subcommand is available via -h/--help.
+Frequently used settings can be put in a configfile which is called gallia.toml.
+Settings from the config file set the default of the respective commandline option.
+The config can always be overwritten by manually setting the relevant cli option.
+
The configuration file gallia.toml is written in TOML.
+Inheritence is not supported; the first file is loaded.
+The gallia.toml file is loaded from these locations (in this particular order):
current Git root (if the current directory is a Git repository)
+
$XDG_CONFIG_HOME/gallia/gallia.toml
+
~/.config/gallia/gallia.toml
+
+
Only some cli options are exposed to the config file.
+The available config settings can be obtained from gallia--template.
+The output of --template is maintained to be up to date and is intended as a starting point.
gallia supports hooks for preparation or cleanup/postprocessing tasks.
+Alternatively, they can be useful for e.g. sending notifications about the exit_code via e.g. matrix or ntfy.sh.
+Hooks are shell scripts which are executed before (= pre-hook) or after (= post-hook) the main() method.
+These scripts can be specified via --pre-hook or --post-hook or via gallia.toml as well.
+
The hook scripts have these environment variables set; some are optional and hook scripts are encouraged to check their presence before accessing them:
+
+
GALLIA_HOOK
Either pre or post.
+
+
GALLIA_ARTIFACTS_DIR
Path to the artifactsdir for the current testrun.
+
+
GALLIA_EXIT_CODE (post)
Is set to the exit_code which gallia will use after the hook terminates.
+For instance GALLIA_EXIT_CODE different from zero means that the current testrun failed.
+
+
GALLIA_META (post)
Contains the JSON encoded content of META.json.
+
+
GALLIA_INVOCATION
The content os sys.argv, in other words the raw invocation of gallia.
+
+
GALLIA_GROUP (optional)
Usually the first part of the command on the cli. For instance, for galliascanudsidentifiers
+GALLIA_GROUP is scan.
+
+
GALLIA_SUBGROUP (optional)
Usually the second part of the command on the cli. For instance, for galliascanudsidentifiers
+GALLIA_GROUP is uds.
+
+
GALLIA_COMMAND (optional)
Usually the last part of the command on the cli. For instance, for galliascanudsidentifiers
+GALLIA_COMMAND is identifiers.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/env.html b/env.html
new file mode 100644
index 000000000..0191a4de9
--- /dev/null
+++ b/env.html
@@ -0,0 +1,145 @@
+
+
+
+
+
+
+
+
+ Environment Variables — gallia documentation
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
For some cases gallia can be configured with environment variables.
+gallia-specific variables begin with GALLIA_.
+
+
GALLIA_CONFIG
The path to the config file usually called gallia.toml.
+Disables autodiscovery of the config.
+
+
GALLIA_LOGLEVEL
When gallia.log.setup_logging() is called without an argument this environment variable is read to set the loglevel.
+Supported value are: trace, debug, info, notice, warning, error, critical.
+As an alternative, the int values from 0 to 7 can be used.
+Mostly useful in own scripts or tests.
+This variable is not read when using the gallia cli.
+
+
NO_COLOR
If this variable is set, gallia by default does not use color codes, see: https://no-color.org/
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/genindex.html b/genindex.html
new file mode 100644
index 000000000..1f6405dff
--- /dev/null
+++ b/genindex.html
@@ -0,0 +1,590 @@
+
+
+
+
+
+
+
+ Index — gallia documentation
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Index
+
+
+
+
+
+
+
+
+
+
Index
+
+
+ A
+ | B
+ | C
+ | D
+ | E
+ | F
+ | G
+ | H
+ | I
+ | L
+ | M
+ | N
+ | P
+ | Q
+ | R
+ | S
+ | T
+ | U
+ | W
+
+
This project is intended for research and development usage only!
+Inappropriate usage might cause irreversible damage to the device under test.
+We do not take any responsibility for damage caused by the usage of this tool.
+
+
Gallia is an extendable pentesting framework with the focus on the automotive domain.
+The scope of the toolchain is conducting penetration tests from a single ECU up to whole cars.
+Currently, the main focus lies on the UDS interface.
+Acting as a generic interface, the logging functionality implements reproducible tests and enables post-processing tasks.
gallia is designed as a pentesting framework where each test produces a lot of data.
+It is possible to design own standalone tools or plugins utilizing the gallia Python modules.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/logging.html b/logging.html
new file mode 100644
index 000000000..3ebbaf838
--- /dev/null
+++ b/logging.html
@@ -0,0 +1,168 @@
+
+
+
+
+
+
+
+
+ Logging — gallia documentation
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
gallia uses structured structured logging implemented as line separated JSON records.
+Each scanner creates a artifacts_dir under artifacts_base, which contains a zstd compressed logfile log.json.zst.
+The logfile is created with loglevel DEBUG; for debugging purposes loglevel TRACE can be enabled with the setting trace_log.
+Logfiles can be displayed with the hr tool which is included in gallia.
+
The generic interface which represents a logrecord is gallia.log.PenlogRecord.
+The generic interface which is used to read a logfile gallia.log.PenlogReader.
fromgallia.logimportget_logger,setup_logging,Loglevel
+
+# The logfile's loglevel is Loglevel.DEBUG.
+# It can be set with the keyword argument file_level.
+setup_logging(level=Loglevel.INFO)
+logger=get_logger(__name__)
+logger.info("hello world")
+logger.debug("hello debug")
+
+
+
If processing of a logfile is needed, here is a minimal example; for custom functionality see gallia.log.PenlogReader and gallia.log.PenlogReader.records().
Below is an example that adds a new command to the CLI (using gallia.command.Script).
+Let’s assume the following code snippet lives in the python module hello.py within the hello_gallia package.
uv manages the project environment, including the python version.
+All uv commands must be invoked within the gallia repository.
+
$ pipxinstalluv
+$ uvsync
+
+
+
If you want to use a different Python version from the one defined in .python-version, the flags --python-preferenceonly-system or --python for uvsync might be helpful; e.g. to use your system provided Python 3.11:
Just use LSP.
+Most editors (e.g. neovim) support the Language Server Protocol.
+The required tools are listed as development dependencies in pyproject.toml and are automatically managed by uv.
+Please refer to the documentation of your text editor of choice for configuring LSP support.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/transports.html b/transports.html
new file mode 100644
index 000000000..262adeb03
--- /dev/null
+++ b/transports.html
@@ -0,0 +1,262 @@
+
+
+
+
+
+
+
+
+ Transports — gallia documentation
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
The argument --target specifies all parameters which are required to establish a connection to the device under test.
+The argument to --target is specified as a URI.
+An URI consists of these components:
The parameters support: string, int (use 0x prefix for hex values) and bool (true/false) values.
+The relevant transport protocol is specified in the scheme.
Discovery scans are specific for the underlying transport, such as DoIP or ISO-TP.
+The idea is crafting a valid UDS payload which is valid and at least some answer is expected.
+A well working payload is 1001 which is a request to the DiagnosticSessionControl service.
+This request instructs an ECU to change to the so called DefaultSession.
+The DiagnosticSessionControl service and the DefaultSession should be always available, thus this payload is a good candidate for a discovery scan.
+Payloads different from 1001 can be used as well; for instance 1003 to enable the ExtendedDiagnosticSession (session id 0x03).
+Another well working example is 3E00, the TesterPresent service.
+
The addressing of the ECU is provided by the underlying transport protocol.
+Most of the time there are two addresses: the tester address and the ECU address.
+The basic idea of a discovery scan is sending a valid UDS payload to all valid ECU addresses with a fixed tester address.
+When a valid answer is received an ECU has been found.
The Diagnostics Over Internet Protocol (DoIP) is a application level protocol on top of TCP enabling tunneling UDS messages.
+As an advantage all features of modern operating systems can be used.
+Furthermore, no custom hardware, such as expensive CAN bus adapters are required.
+
+
Note
+
The user needs to provide a DHCP server enabling the communication stack on the DoIP gateway’s side.
+
+
DoIP has three parameters which are required to establish a communication channel to an ECU:
+
+
SourceAddress: In the automotive chargon also called tester address. It is often static and set to 0x0e00.
+
TargetAddress: This is the address of a present ECU. This parameter needs to be discovered.
+
RoutingActivationType: DoIP provides further optional layers of authentication which can e.g. be vendor specific. Since gallia needs UDS endpoints this is most likely WWH_OB, which is a symbolic identifier for the constant 0x01. Optionally, the RoutingActivationType can be scanned in order to find vendor specific routing methods.
+
+
Assuming DHCP and the networking setup is running properly, the DoIP connection establishment looks like the following:
+
+
TCP connect to the DoIP gateway. The IP address of the gateway is set by the mentioned user controlled DHCP server.
+
Sending a RoutingActivationRequest; the gateway must acknowledge this.
+
UDS requests can be tunneled to arbitrary ECUs using appropriate DoIP DiagnosticMessage packets.
+
+
The mentioned DiagnosticMessage packets contain a SourceAddress, a TargetAddress, and a UserData field.
+For the discovery scan on DoIP first step 1. and 2. from the described connection establishment process are performed.
+Subsequently, a valid UDS message (c.f. previous section) is the put in the UserData field and the TargetAddress field is iterated.
+When a valid answer is received an ECU has been found; when the gateway sends a NACK or just timeouts, no ECU is available on the tried TargetAddress.
ISO-TP is a standard for a transport protocol on top of the CAN bus system.
+The CAN bus is a field bus which acts as a broadcast medium; any connected participant can read all messages.
+On the CAN bus there is no concept of a connection.
+Typically, there are cyclic messages on the CAN bus which are important for selected participants.
+However, in order to implement a connection channel for the UDS protocol (which is required by law to be present in vecicles) the ISO-TP standard comes into play.
+In contrast to DoIP special CAN hardware is required.
+The ISO-TP protocol and the interaction with CAN interfaces is handled by the networking stack of the Linux kernel.
+
For a discovery scan it is important to distinguish whether the tester is connected to a filtered interface (e.g. the OBD connector) or to an unfiltered interface (e.g. an internal CAN bus).
+In order to not confuse the discovery scanner, the so called idle traffic needs to be observed.
+The idle traffic consists of the mentioned cyclic messages of the can bus.
+Since there is no concept of a connection on the CAN bus itself and the parameters for the ISO-TP connection are unknown at the very first stage, an educated guess for a deny list is required.
+Typically, gallia waits for a few seconds and observes the CAN bus traffic.
+Subsequently, a deny filter is configured which filters out all CAN IDs seen in the idle traffic.
+
ISO-TP provides multiple different addressing methods:
+
+
normal addressing with normal CAN IDs,
+
normal addressing with extended CAN IDs,
+
extended addressing with normal CAN IDs,
+
extended addressing with extended CAN IDs,
+
the mentioned schemes but with CAN-FD below,
+
…
+
+
+
Note
+
For the detailed explanation of all these addressing modes we refer to the relevant ISO standard documents or further documents or presentations which are available online.
+
+
The tester needs to make assuptions about what addressing scheme is used; otherwise the scan does not yield any results.
+ISO-TP provides the following parameters:
+
+
source can_id:
+
+
Without extended addressing: Often set to an address with a static offset to the destination can_id.
+
Extended addressing: Often set to a static value, e.g. 0x6f1; a.k.a. tester address.
+
+
+
destination can_id:
+
+
Without extended addressing: Address of the ECU
+
Extended addressing: Somehow part of the ECU address, e.g. 0x600|ext_address
+
+
+
extended source address: When extended addressing is in use, often set to a static value, e.g. 0xf1.
+
extended destination address: When extended addressing is in use, it is the address of the ECU.
+
+
The discovery procedure is dependend on the used addressing scheme.
+From a high level perspective, the destination id is iterated and a valid payload is sent.
+If a valid answer is received, an ECU has been found.
UDS has the concept of sessions.
+Different sessions can for example offer different services.
+A session is identified by a 1 byte session ID.
+The UDS standard defines a set of well known session IDs, but vendors are free to add their own sessions.
+Some sessions might only be available from a specific ECU state (e.g. current session, enabled/configured ECU features, coding, …).
+Most of those preconditions cannot be detected automatically and might require vendor specific knowledge.
+
The session scan tries to find all available session transitions.
+Starting from the default session (0x01), all session IDs are iterated and enabling the relevant session is tried.
+If the ECU replies with a positive response, the session is available.
+In case of a negative response, the session is considered not available from the current state.
+To detect sessions, which are only reachable from a session different to the default session, a recursive approach is used.
+The scan for new sessions starts at each previously identified session.
+The maximum depth is limited to avoid endless scans in case of transition cycles, such as 0x01->0x03->0x05->0x03.
+The scan is finished, if no new session transition is found.
The service scan operates at the UDS protocol level.
+UDS provides several endpoints called services.
+Each service has an identifier and a specific list of arguments or sub-functions.
+
In order to identify available services, a reverse matching is applied.
+According to the UDS standard, ECUs reply with the error codes serviceNotSupported or serviceNotSupportedInActiveSession when an unimplemented service is requested.
+Therefore, each service which responds with a different error code is considered available.
+To address the different services and their varying length of arguments and sub-functions the scanner automatically appends \x00 bytes if the received response was incorrectMessageLengthOrInvalidFormat.
The identifier scan operates at the UDS protocol level; to be more specific it operates at the level of a specific UDS service.
+Most UDS services need identifiers is input arguments.
+For instance, the ReadDataByIdentifier service requires a data identifier input for the requested ressource.
+In order to find out the available identifiers for a specific service the Identifier Scan is employed.
+
In order to identify available data identifiers, a reverse matching is applied.
+According to the UDS standard, ECUs reply with the error codes serviceNotSupported or serviceNotSupportedInActiveSession when an unimplemented service is requested.
+If the ECU responds with any of serviceNotSupported, serviceNotSupportedInActiveSession,subFunctionNotSupported, subFunctionNotSupportedInActiveSession, or requestOutOfRange the identifier is considered not available.
+
A few services such as RoutineControl offer a subFunction as well.
+SubFunction arguments can be discovered with the same technique but the error codes for the reverse matching are different.
+For discovering available subFunctions the following error codes indicate the subFunction is not available: serviceNotSupported, serviceNotSupportedInActiveSession, subFunctionNotSupported, or subFunctionNotSupportedInActiveSession.
+
Each identifier or subFunction which responds with a different error code is considered available.
For testing purposes, there exists the possibility to spawn virtual ECUs, against which the scanners can be run.
+The virtual ECUs can however also be used independently of the remaining Gallia tools.
+
The generic command to create a virtual ECU is as follows:
The virtual ECU model is separated from the transport layer that is used for communication.
+Currently, two different transport types are supported.
+For each of them, a corresponding transport scheme exists on the scanner side,
+which has to be used to enable communication between the scanner and the virtual ECU.
The transport scheme which is the easiest to use is the tcp-lines scheme.
+It requires no additional setup and can be used immediately.
+When using this transport scheme, the virtual ECU can handle requests from multiple UDS clients,
+but conversely a scanner can only talk to a single virtual ECU.
+For most scanners this is the intended behavior.
+For discovery scanners instead, the tcp-lines transport is not suitable.
+
For example, a random virtual ECU, which uses the tcp-lines protocol for communication
+and listens for IPv4 connections on port 20162 can be started with the following command:
The iso-tp scheme operates on an existing can interface, which can be a physical interface or a virtual interface.
+The following commands can be used to set up a virtual CAN interface with the name vcan0:
+
# iplinkadddevvcan0typevcan
+# iplinksetupvcan0
+
+
+
In contrast to the tcp-lines approach,
+using the iso-tp transport scheme allows to simulate a whole bus of several virtual ECUs,
+and is therefore also suitable for testing discovery scanners.
+
For example, two random virtual ECUs, which uses the iso-tp protocol for communication
+and use the vcan0 interface can be started with the following commands:
This type of model, creates an ECU, with a random set of supported sessions, services, sub-functions and identifiers,
+where applicable.
+By default, this model makes use of all available default behaviors,
+thereby offering a very standard conformant behavior out of the box.
+As with any model, these default mechanisms can be disabled via the vecu-options.
+It can be further customized by specifying mandatory as well as optional sessions and services.
+Additionally, the probabilities which are used to compute the previously mentioned set of available
+functionality can be altered.
+
For example, a random virtual ECU with no customization can be created with the following command:
After the startup, the output shows an overview of the supported sessions, services and sub-functions.
+In this case only one session with ten services is offered.
+
To enable reproducibility, at the startup, the output shows the seed, which has been used to initialize the random
+number generator.
+Using the same seed in combination with the same arguments, one can recreate the same virtual ECU:
+
gallia vecu "tcp-lines://127.0.0.1:20162" rng --seed <seed>
+
+
+
The following command shows how to control the selection of services, by altering the mandatory and optional services,
+as well as the probability that determines, how likely one of the optional services is included.
+The same is possible for services.
+For other categories, only the probabilities can be changed.
This type of model creates a virtual ECU that mimics the behavior of an already scanned ECU.
+This functionality is based on the database logging of the scanners.
+For each request that the virtual ECU receives, it looks up if there is a corresponding request and response pair in
+the database, which also satisfies other conditions, such as a matching ECU state.
+By default, it will return either the corresponding response, or no response at all, if no such pair exists.
+As with any model, default ECU mechanisms, such as a default request or correct handling of the
+suppressPosRespMsgIndicationBit are supported, but are disabled by default.
+They might be particularly useful to compensate for behavior, that is not intended to be handled explicitly,
+while being able to include the corresponding ECU in the testing procedure.
+
When using the db model, the virtual ECU requires the path of the database to which scan results have been
+logged for the corresponding ECU.
+For example, a virtual ECU that mimics the behavior of an ECU,
+that was logged in /path/to/db can be created with the following command:
If the database contains logs of multiple ECUs, one particular ECU can be chosen by its name.
+Note, that the name is not filled in automatically
+and may have to be manually added and referenced in one or more addresses.
+Additionally, it is possible to include only those scan runs, for which the ECU satisfied a set of properties.
+For example, the following command creates a virtual ECU that mimics the behavior of the ECU XYZ based on the logs
+in /path/to/db where the software_version was 1.2.3: