From 6d6ec4178defdffb3437512b02c1422950c15317 Mon Sep 17 00:00:00 2001 From: Kim Date: Tue, 28 Oct 2025 13:05:18 -0400 Subject: [PATCH 1/8] refactor: Normalize suppress positive bit argument order --- src/lib.rs | 11 +++---- src/request.rs | 18 ++++++----- src/services/control_dtc_settings.rs | 45 ++++++++++++++-------------- 3 files changed, 40 insertions(+), 34 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 53adc36..72d75d2 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -133,13 +133,14 @@ impl From for u8 { } } } +impl TryFrom for DtcSettings { + type Error = Error; -impl From for DtcSettings { - fn from(value: u8) -> Self { + fn try_from(value: u8) -> Result { match value { - 0x01 => Self::On, - 0x02 => Self::Off, - _ => panic!("Invalid DTC setting: {value}"), + 0x01 => Ok(Self::On), + 0x02 => Ok(Self::Off), + _ => Err(Error::InvalidDtcSubfunctionType(value)), } } } diff --git a/src/request.rs b/src/request.rs index 1e632a4..b046d63 100644 --- a/src/request.rs +++ b/src/request.rs @@ -65,10 +65,10 @@ impl Request { pub fn communication_control( communication_enable: CommunicationControlType, communication_type: CommunicationType, - suppress_response: bool, + suppress_positive_response: bool, ) -> Self { Request::CommunicationControl(CommunicationControlRequest::new( - suppress_response, + suppress_positive_response, communication_enable, communication_type, )) @@ -86,10 +86,10 @@ impl Request { communication_enable: CommunicationControlType, communication_type: CommunicationType, node_id: u16, - suppress_response: bool, + suppress_positive_response: bool, ) -> Self { Request::CommunicationControl(CommunicationControlRequest::new_with_node_id( - suppress_response, + suppress_positive_response, communication_enable, communication_type, node_id, @@ -98,8 +98,11 @@ impl Request { /// Create a new `ControlDTCSettings` request #[must_use] - pub fn control_dtc_settings(setting: DtcSettings, suppress_response: bool) -> Self { - Request::ControlDTCSettings(ControlDTCSettingsRequest::new(setting, suppress_response)) + pub fn control_dtc_settings(suppress_positive_response: bool, setting: DtcSettings) -> Self { + Request::ControlDTCSettings(ControlDTCSettingsRequest::new( + suppress_positive_response, + setting, + )) } /// Create a new `DiagnosticSessionControl` request @@ -271,6 +274,7 @@ impl WireFormat for Request { /// or the deserialization could read unexpected data fn decode(reader: &mut R) -> Result, Error> { let service = UdsServiceType::service_from_request_byte(reader.read_u8()?); + tracing::debug!("Deserializing UDS Request for service: {:?}", service); Ok(Some(match service { UdsServiceType::CommunicationControl => Self::CommunicationControl( CommunicationControlRequest::decode_single_value(reader)?, @@ -404,7 +408,7 @@ mod tests { assert!(communication_control_request.is_positive_response_suppressed()); let control_dtc_settings_request = - ProtocolRequest::control_dtc_settings(DtcSettings::On, true); + ProtocolRequest::control_dtc_settings(true, DtcSettings::On); assert!(control_dtc_settings_request.is_positive_response_suppressed()); let diagnostic_session_control_request = ProtocolRequest::diagnostic_session_control( diff --git a/src/services/control_dtc_settings.rs b/src/services/control_dtc_settings.rs index 1d6db54..c7801e9 100644 --- a/src/services/control_dtc_settings.rs +++ b/src/services/control_dtc_settings.rs @@ -1,4 +1,4 @@ -use crate::{DtcSettings, Error, SUCCESS, SingleValueWireFormat, WireFormat}; +use crate::{DtcSettings, Error, SingleValueWireFormat, SuppressablePositiveResponse, WireFormat}; use byteorder::{ReadBytesExt, WriteBytesExt}; /// The `ControlDTCSettings` service is used to control the DTC settings of the ECU. @@ -8,29 +8,32 @@ use byteorder::{ReadBytesExt, WriteBytesExt}; #[non_exhaustive] pub struct ControlDTCSettingsRequest { /// The requested DTC logging setting - pub setting: DtcSettings, - /// Whether the ECU should suppress a response - pub suppress_response: bool, + setting: SuppressablePositiveResponse, } impl ControlDTCSettingsRequest { - pub(crate) fn new(setting: DtcSettings, suppress_response: bool) -> Self { + pub(crate) fn new(suppress_positive_response: bool, setting: DtcSettings) -> Self { Self { - setting, - suppress_response, + setting: SuppressablePositiveResponse::new(suppress_positive_response, setting), } } + /// Getter for whether a positive response should be suppressed + #[must_use] + pub fn suppress_positive_response(&self) -> bool { + self.setting.suppress_positive_response() + } + + /// Getter for the setting + #[must_use] + pub fn setting(&self) -> DtcSettings { + self.setting.value() + } } impl WireFormat for ControlDTCSettingsRequest { fn decode(reader: &mut T) -> Result, Error> { - let request_byte = reader.read_u8()?; - let setting = DtcSettings::from(request_byte & !SUCCESS); - let suppress_response = request_byte & SUCCESS != 0; - Ok(Some(Self { - setting, - suppress_response, - })) + let setting = SuppressablePositiveResponse::try_from(reader.read_u8()?)?; + Ok(Some(Self { setting })) } fn required_size(&self) -> usize { @@ -38,14 +41,12 @@ impl WireFormat for ControlDTCSettingsRequest { } fn encode(&self, writer: &mut T) -> Result { - let request_byte = - u8::from(self.setting) | if self.suppress_response { SUCCESS } else { 0 }; - writer.write_u8(request_byte)?; + writer.write_u8(u8::from(self.setting))?; Ok(1) } fn is_positive_response_suppressed(&self) -> bool { - self.suppress_response + self.suppress_positive_response() } } @@ -71,7 +72,7 @@ impl ControlDTCSettingsResponse { impl WireFormat for ControlDTCSettingsResponse { fn decode(reader: &mut T) -> Result, Error> { - let setting = DtcSettings::from(reader.read_u8()?); + let setting = DtcSettings::try_from(reader.read_u8()?)?; Ok(Some(Self { setting })) } @@ -94,7 +95,7 @@ mod request { #[test] fn simple_request() { - let req = ControlDTCSettingsRequest::new(DtcSettings::On, true); + let req = ControlDTCSettingsRequest::new(true, DtcSettings::On); let mut buffer = Vec::new(); let written = req.encode(&mut buffer).unwrap(); assert_eq!(buffer, vec![0x81]); @@ -103,8 +104,8 @@ mod request { let parsed = ControlDTCSettingsRequest::decode_single_value(&mut buffer.as_slice()).unwrap(); - assert_eq!(parsed.setting, DtcSettings::On); - assert!(parsed.suppress_response); + assert_eq!(parsed.setting(), DtcSettings::On); + assert!(parsed.suppress_positive_response()); } } From a15702d58964bfe3933f4e234408417906fa9230 Mon Sep 17 00:00:00 2001 From: Kim Date: Tue, 28 Oct 2025 13:08:42 -0400 Subject: [PATCH 2/8] fix: ECU Reset only responds with power down time on 1 reset type --- src/error.rs | 2 ++ src/response.rs | 2 +- src/services/ecu_reset.rs | 52 +++++++++++++++++++++++++++++++++------ 3 files changed, 47 insertions(+), 9 deletions(-) diff --git a/src/error.rs b/src/error.rs index fe69c1f..1655316 100644 --- a/src/error.rs +++ b/src/error.rs @@ -40,4 +40,6 @@ pub enum Error { InvalidDtcFormatIdentifier(u8), #[error("Reserved for legislative use: {0} ({1})")] ReservedForLegislativeUse(String, u8), + #[error("Serialization error: {0}")] + SerializationError(String), } diff --git a/src/response.rs b/src/response.rs index 1c7402f..449669b 100644 --- a/src/response.rs +++ b/src/response.rs @@ -82,7 +82,7 @@ impl Response { } #[must_use] - pub fn ecu_reset(reset_type: ResetType, power_down_time: u8) -> Self { + pub fn ecu_reset(reset_type: ResetType, power_down_time: Option) -> Self { Response::EcuReset(EcuResetResponse::new(reset_type, power_down_time)) } diff --git a/src/services/ecu_reset.rs b/src/services/ecu_reset.rs index 7b0f66c..b8ed7cf 100644 --- a/src/services/ecu_reset.rs +++ b/src/services/ecu_reset.rs @@ -77,12 +77,20 @@ impl SingleValueWireFormat for EcuResetRequest {} #[non_exhaustive] pub struct EcuResetResponse { pub reset_type: ResetType, - pub power_down_time: u8, + /// Optional power down time in seconds for [ResetType::EnableRapidPowerShutDown] + pub power_down_time: Option, } impl EcuResetResponse { /// Create a new '`EcuResetResponse`' - pub(crate) fn new(reset_type: ResetType, power_down_time: u8) -> Self { + /// + /// `power_down_time` is only valid for [ResetType::EnableRapidPowerShutDown], will be set to None otherwise + pub(crate) fn new(reset_type: ResetType, power_down_time: Option) -> Self { + let power_down_time = if reset_type == ResetType::EnableRapidPowerShutDown { + power_down_time + } else { + None + }; Self { reset_type, power_down_time, @@ -94,7 +102,10 @@ impl WireFormat for EcuResetResponse { /// Deserialization function to read a [`EcuResetResponse`] from a `Reader` fn decode(reader: &mut T) -> Result, Error> { let reset_type = ResetType::try_from(reader.read_u8()?)?; - let power_down_time = reader.read_u8()?; + let mut power_down_time = None; + if reset_type == ResetType::EnableRapidPowerShutDown { + power_down_time = Some(reader.read_u8()?); + } Ok(Some(Self { reset_type, power_down_time, @@ -102,14 +113,27 @@ impl WireFormat for EcuResetResponse { } fn required_size(&self) -> usize { - 2 + if self.reset_type == ResetType::EnableRapidPowerShutDown { + 2 + } else { + 1 + } } /// Serialization function to write a [`EcuResetResponse`] to a `Writer` fn encode(&self, buffer: &mut T) -> Result { buffer.write_u8(u8::from(self.reset_type))?; - buffer.write_u8(self.power_down_time)?; - Ok(2) + if self.reset_type == ResetType::EnableRapidPowerShutDown { + if let Some(power_down_time) = self.power_down_time { + buffer.write_u8(power_down_time)?; + } else { + return Err(Error::SerializationError( + "ECUReset: Power down time must be set for EnableRapidPowerShutDown" + .to_string(), + )); + } + } + Ok(self.required_size()) } } @@ -139,8 +163,8 @@ mod response { #[test] fn ecu_reset_response() { - let bytes: [u8; 2] = [0x01, 0x20]; - let resp = EcuResetResponse::new(ResetType::HardReset, 0x20); + let bytes: [u8; 2] = [0x04, 0x20]; + let resp = EcuResetResponse::new(ResetType::EnableRapidPowerShutDown, Some(0x20)); let mut buffer = Vec::new(); let written = resp.encode(&mut buffer).unwrap(); let result = EcuResetResponse::decode_single_value(&mut bytes.as_slice()).unwrap(); @@ -149,4 +173,16 @@ mod response { assert_eq!(written, 2); assert_eq!(written, resp.required_size()); } + + #[test] + // Test that power down time is ignored for other reset types + fn ecu_reset_response_no_power_down_time() { + let bytes: [u8; 1] = [0x01]; + let resp = EcuResetResponse::new(ResetType::HardReset, Some(0x20)); + let mut buffer = Vec::new(); + let written = resp.encode(&mut buffer).unwrap(); + assert_eq!(written, 1); + let result = EcuResetResponse::decode_single_value(&mut bytes.as_slice()).unwrap(); + assert_eq!(result, EcuResetResponse::new(ResetType::HardReset, None)); + } } From a3b4ed70ac6635936d89ecba3c7c4386d6f60392 Mon Sep 17 00:00:00 2001 From: Kim Date: Tue, 28 Oct 2025 14:03:01 -0400 Subject: [PATCH 3/8] feat: Add values to NRCs and fix all values --- src/common/negative_response_code.rs | 147 +++++++++++++++++++-------- 1 file changed, 105 insertions(+), 42 deletions(-) diff --git a/src/common/negative_response_code.rs b/src/common/negative_response_code.rs index b7978a0..ffb3b1b 100644 --- a/src/common/negative_response_code.rs +++ b/src/common/negative_response_code.rs @@ -3,45 +3,66 @@ #[cfg_attr(feature = "clap", derive(clap::ValueEnum))] #[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))] #[derive(Clone, Copy, Debug, Eq, PartialEq)] +#[repr(u8)] pub enum NegativeResponseCode { /// This response code shall not be used in a negative response message. /// This positiveResponse parameter value is reserved for server internal implementation - PositiveResponse, + PositiveResponse = 0x00, /// This range of values is reserved for future definition. #[cfg_attr(feature = "clap", clap(skip))] ISOSAEReserved(u8), /// This response code indicates that the requested action has been rejected by the server. /// The `GeneralReject` response code shall only be implemented in the server if none of the negative response codes meet the needs of the implementation. /// This response code shall not be used as a general replacement for other response codes defined. - GeneralReject, + GeneralReject = 0x10, /// This response code indicates that the requested action will not be taken because the server does not support the requested service. /// The server shall send this response code in the case where the client has sent a request message with a service identifier which is either unknown or not supported. /// This negative response code is not shown in the list of negative response codes to be supported for a diagnostic service, /// because this negative response code is not applicable for supported services. - ServiceNotSupported, + ServiceNotSupported = 0x11, /// This response code indicates that the requested action will not be taken because the server does not support the service specific parameters of the request message. /// The server shall send this response code in case the client has sent a request message with a known and supported service identifier but with "sub function“ which is either unknown or not supported. - SubFunctionNotSupported, + SubFunctionNotSupported = 0x12, /// This response code indicates that the requested action will not be taken because the length of the received request message does not match the prescribed length for the specified service, /// or that the format of the parameters do not match the prescribed format for the specified service. - IncorrectMessageLengthOrInvalidFormat, + IncorrectMessageLengthOrInvalidFormat = 0x13, /// This response code shall be reported by the server if the response to be generated exceeds the maximum number of bytes available by the underlying network layer. - ResponseTooLong, + ResponseTooLong = 0x14, + // -- ISO SAE Reserved: 0x15 to 0x20 -- /// This response code indicates that the server is temporarily too busy to perform the requested operation. /// In this circumstance the client shall perform repetition of the request message or "try again later". /// The repetition of the request shall be delayed by a time specified in the respective implementation documents. - BusyRepeatRequest, + BusyRepeatRequest = 0x21, /// This response code indicates that the requested action will not be taken because the server prerequisite conditions are not met. - ConditionsNotCorrect, + ConditionsNotCorrect = 0x22, + // -- ISO SAE Reserved: 0x23 -- /// This response code indicates that the requested action will not be taken because the server expects a different sequence of request messages or message as sent by the client. /// This may occur when sequence sensitive requests are issued in the wrong order. - RequestSequenceError, + RequestSequenceError = 0x24, + /// This NRC indicates that the server has received the request but the requested action could not be performed by the server as a subnet component which is + /// necessary to supply the requested information did not respond within the specified time. + /// + /// The noResponseFromSubnetComponent negative response shall be implemented by gateways in electronic systems which contain electronic + /// subnet components and which do not directly respond to the client's request. + /// + /// The gateway may receive the request for the subnet component and then request the necessary information from the subnet component. If the subnet + /// component fails to respond, the server shall use this negative response to inform the client about the failure of the subnet component. + /// + /// This NRC is in general supported by each diagnostic service, as not otherwise stated in the data link specific implementation document, therefore it is not + /// listed FailurePreventsExecutionOfRequestedAction in the list of applicable response codes of the diagnostic services. `NegativeResponseCode` is a shared error mechanism + NoResponseFromSubnetComponent = 0x25, + /// This NRC indicates that the requested action will not be taken because a failure condition, identified by a DTC (with at least one DTC status bit for + /// TestFailed, Pending, Confirmed or TestFailedSinceLastClear set to 1), has occurred and that this failure condition prevents the server from performing the requested action. + /// + /// This NRC can, for example, direct the technician to read DTCs in order to identify and fix the problem. + FailurePreventsExecutionOfRequestedAction = 0x26, /// This response code indicates that the requested action will not be taken because the server has detected that the request message contains a parameter which attempts to substitute a value beyond its range of authority /// (e.g. attempting to substitute a data byte of 111 when the data is only defined to 100), /// or which attempts to access a dataIdentifier/routineIdentifer that is not supported or not supported in active session. + /// /// This response code shall be implemented for all services which allow the client to read data, /// write data, or adjust functions by data in the server. - RequestOutOfRange, + RequestOutOfRange = 0x31, /// This response code indicates that the requested action will not be taken because the server's security strategy has not been satisfied by the client. The server shall send this response code if one of the following cases occur: /// - the test conditions of the server are not met, /// - the required message sequence e.g. `DiagnosticSessionControl`, securityAccess is not met, @@ -49,32 +70,33 @@ pub enum NegativeResponseCode { /// /// Beside the mandatory use of this negative response code as specified in the applicable services within this standard, /// this negative response code can also be used for any case where security is required and is not yet granted to perform the required service. - SecurityAccessDenied, + SecurityAccessDenied = 0x33, /// This response code indicates that the requested action will not be taken because the client has insufficient rights based on its Authentication state. - AuthenticationRequired, + AuthenticationRequired = 0x34, /// This response code indicates that the server has not given security access because the key sent by the client did not match with the key in the server's memory. /// This counts as an attempt to gain security. /// The server shall remain locked and increment its internal securityAccessFailed counter. - InvalidKey, + InvalidKey = 0x35, /// This response code indicates that the requested action will not be taken because the client has unsuccessfully attempted to gain security access more times than the server's security strategy will allow. - ExceedNumberOfAttempts, + ExceedNumberOfAttempts = 0x36, /// This response code indicates that the requested action will not be taken because the client's latest attempt to gain security access was initiated before the server's required timeout period had elapsed. - RequiredTimeDelayNotExpired, + RequiredTimeDelayNotExpired = 0x37, /// Reserved by ISO 15764 #[cfg_attr(feature = "clap", clap(skip))] ExtendedDataLinkSecurityReserved(u8), /// This response code indicates that an attempt to upload/download to a server's memory cannot be accomplished due to some fault conditions. - UploadDownloadNotAccepted, + UploadDownloadNotAccepted = 0x70, /// This response code indicates that a data transfer operation was halted due to some fault. /// The active transferData sequence shall be aborted. - TransferDataSuspended, + TransferDataSuspended = 0x71, /// This response code indicates that the server detected an error when erasing or programming a memory location in the permanent memory device (e.g. Flash Memory). - GeneralProgrammingFailure, + GeneralProgrammingFailure = 0x72, /// This response code indicates that the server detected an error in the sequence of `BlockSequenceCounter` values. /// Note that the repetition of a `TransferDataRequest` message with a `BlockSequenceCounter` equal to the one included in the previous `TransferDataRequest` message shall be accepted by the server. - WrongBlockSequenceCounter, + WrongBlockSequenceCounter = 0x73, /// This response code indicates that the server detected an error in the sequence of `BlockSequenceCounter` values. - RequestCorrectlyReceivedResponsePending, + RequestCorrectlyReceivedResponsePending = 0x78, + // -- ISO SAE Reserved: 0x79 to 0x7D -- /// This response code indicates that the requested action will not be taken because the server does not support the requested sub-function in the session currently active. /// Within the programmingSession negative response code 0x12 (subFunctionNotSupported) may optionally be reported instead of negative response code 0x7F (subFunctionNotSupportedInActiveSession). /// This response code shall only be used when the requested sub-function is known to be supported in another session, @@ -82,67 +104,74 @@ pub enum NegativeResponseCode { /// This response code shall be supported by each diagnostic service with a sub-function parameter, /// if not otherwise stated in the data link specific implementation document, /// therefore it is not listed in the list of applicable response codes of the diagnostic services. - SubFunctionNotSupportedInActiveSession, + SubFunctionNotSupportedInActiveSession = 0x7E, /// This response code indicates that the requested action will not be taken because the server does not support the requested service in the session currently active. /// This response code shall only be used when the requested service is known to be supported in another session, otherwise response code 0x11 (serviceNotSupported) shall be used. /// This response code is in general supported by each diagnostic service, /// as not otherwise stated in the data link specific implementation document, /// therefore it is not listed in the list of applicable response codes of the diagnostic services. - ServiceNotSupportedInActiveSession, + ServiceNotSupportedInActiveSession = 0x7F, + // -- ISO SAE Reserved: 0x80 -- /// This response code indicates that the requested action will not be taken because the server prerequisite condition for RPM is not met (current RPM is above a pre-programmed maximum threshold). - RPMTooHigh, + RPMTooHigh = 0x81, /// This response code indicates that the requested action will not be taken because the server prerequisite condition for RPM is not met (current RPM is below a pre-programmed minimum threshold). - RPMTooLow, + RPMTooLow = 0x82, /// This is required for those actuator tests which cannot be actuated while the Engine is running. /// This is different from RPM too high negative response and needs to be allowed. - EngineIsRunning, + EngineIsRunning = 0x83, /// This is required for those actuator tests which cannot be actuated unless the Engine is running. /// This is different from RPM too low negative response, and needs to be allowed. - EngineIsNotRunning, + EngineIsNotRunning = 0x84, /// This response code indicates that the requested action will not be taken because the server prerequisite condition for engine run time is not met /// (current engine run time is below a preprogrammed limit). - EngineRunTimeTooLow, + EngineRunTimeTooLow = 0x85, /// This response code indicates that the requested action will not be taken because the server prerequisite condition for temperature is not met /// (current temperature is above a preprogrammed maximum threshold). - TemperatureTooHigh, + TemperatureTooHigh = 0x86, /// This response code indicates that the requested action will not be taken because the server prerequisite condition for temperature is not met /// (current temperature is below a preprogrammed minimum threshold). - TemperatureTooLow, + TemperatureTooLow = 0x87, /// This response code indicates that the requested action will not be taken because the server prerequisite condition for vehicle speed is not met /// (current VS is above a pre-programmed maximum threshold). - VehicleSpeedTooHigh, + VehicleSpeedTooHigh = 0x88, /// This response code indicates that the requested action will not be taken because the server prerequisite condition for vehicle speed is not met /// (current VS is below a pre-programmed minimum threshold). - VehicleSpeedTooLow, + VehicleSpeedTooLow = 0x89, /// This response code indicates that the requested action will not be taken because the server prerequisite condition for throttle/pedal position is not met /// (current TP/APP is above a preprogrammed maximum threshold). - ThrottleOrPedalTooHigh, + ThrottleOrPedalTooHigh = 0x8A, /// This response code indicates that the requested action will not be taken because the server prerequisite condition for throttle/pedal position is not met /// (current TP/APP is below a preprogrammed minimum threshold). - ThrottleOrPedalTooLow, + ThrottleOrPedalTooLow = 0x8B, /// This response code indicates that the requested action will not be taken because the server prerequisite condition for being in neutral is not met /// (current transmission range is not in neutral). - TransmissionRangeNotInNeutral, + TransmissionRangeNotInNeutral = 0x8C, /// This response code indicates that the requested action will not be taken because the server prerequisite condition for being in gear is not met /// (current transmission range is not in gear). - TransmissionRangeNotInGear, + TransmissionRangeNotInGear = 0x8D, + // -- ISO SAE Reserved: 0x8E -- /// For safety reasons, this is required for certain tests before it begins, /// and must be maintained for the entire duration of the test. - BrakeSwitchNotClosed, + BrakeSwitchNotClosed = 0x8F, /// For safety reasons, this is required for certain tests before it begins, /// and must be maintained for the entire duration of the test. - ShifterLeverNotInPark, + ShifterLeverNotInPark = 0x90, /// This response code indicates that the requested action will not be taken because the server prerequisite condition for torque converter clutch is not met /// (current TCC status above a preprogrammed limit or locked). - TorqueConverterClutchLocked, + TorqueConverterClutchLocked = 0x91, /// This response code indicates that the requested action will not be taken because the server prerequisite condition for voltage at the primary pin of the server /// (ECU) is not met /// (current voltage is above a pre-programmed maximum threshold). - VoltageTooHigh, + VoltageTooHigh = 0x92, /// This response code indicates that the requested action will not be taken because the server prerequisite condition for voltage at the primary pin of the server /// (ECU) is not met /// (current voltage is below a pre-programmed maximum threshold). - VoltageTooLow, + VoltageTooLow = 0x93, + /// This response code indicates that the server has received the request but the requested action could not be performed by the server because an application which is + /// necessary to supply the requested information is temporality not available. This NRC is in general supported by each diagnostic service, as not otherwise + /// stated in the data link specific implementation document, therefore it is not listed reservedForSpecificConditionsNotCorrect + /// in the list of applicable response codes of the diagnostic services. + ResourceTemporarilyNotAvailable = 0x94, /// This range of values is reserved for future definition. #[cfg_attr(feature = "clap", clap(skip))] ReservedForSpecificConditionsNotMet(u8), @@ -162,6 +191,8 @@ impl From for u8 { NegativeResponseCode::BusyRepeatRequest => 0x21, NegativeResponseCode::ConditionsNotCorrect => 0x22, NegativeResponseCode::RequestSequenceError => 0x24, + NegativeResponseCode::NoResponseFromSubnetComponent => 0x25, + NegativeResponseCode::FailurePreventsExecutionOfRequestedAction => 0x26, NegativeResponseCode::RequestOutOfRange => 0x31, NegativeResponseCode::SecurityAccessDenied => 0x33, NegativeResponseCode::AuthenticationRequired => 0x34, @@ -194,6 +225,7 @@ impl From for u8 { NegativeResponseCode::TorqueConverterClutchLocked => 0x91, NegativeResponseCode::VoltageTooHigh => 0x92, NegativeResponseCode::VoltageTooLow => 0x93, + NegativeResponseCode::ResourceTemporarilyNotAvailable => 0x94, NegativeResponseCode::ReservedForSpecificConditionsNotMet(value) => value, } } @@ -215,7 +247,9 @@ impl From for NegativeResponseCode { 0x22 => Self::ConditionsNotCorrect, 0x23 => Self::ISOSAEReserved(value), 0x24 => Self::RequestSequenceError, - 0x25..=0x30 => Self::ISOSAEReserved(value), + 0x25 => Self::NoResponseFromSubnetComponent, + 0x26 => Self::FailurePreventsExecutionOfRequestedAction, + 0x27..=0x30 => Self::ISOSAEReserved(value), 0x31 => Self::RequestOutOfRange, 0x32 => Self::ISOSAEReserved(value), 0x33 => Self::SecurityAccessDenied, @@ -254,8 +288,37 @@ impl From for NegativeResponseCode { 0x91 => Self::TorqueConverterClutchLocked, 0x92 => Self::VoltageTooHigh, 0x93 => Self::VoltageTooLow, - 0x94..=0xFE => Self::ReservedForSpecificConditionsNotMet(value), + 0x94 => Self::ResourceTemporarilyNotAvailable, + 0x95..=0xFE => Self::ReservedForSpecificConditionsNotMet(value), 0xFF => Self::ISOSAEReserved(value), } } } + +// tests +#[cfg(test)] +mod tests { + use super::*; + #[test] + fn test_nrc_from_u8() { + let nrc = NegativeResponseCode::from(0x13); + assert_eq!( + nrc, + NegativeResponseCode::IncorrectMessageLengthOrInvalidFormat + ); + } + #[test] + fn test_nrc_to_u8() { + let nrc = NegativeResponseCode::RequestOutOfRange; + let nrc_byte: u8 = nrc.into(); + assert_eq!(nrc_byte, 0x31); + } + #[test] + fn test_iso_reserved_nrc_direct() { + let nrc = NegativeResponseCode::ISOSAEReserved(0x94); + let nrc_byte: u8 = nrc.into(); + assert_eq!(nrc_byte, 0x94); + let check_same = NegativeResponseCode::from(0x94); + assert_ne!(nrc, check_same); + } +} From 21883849b3973341e01ade979474ccd376b70c1a Mon Sep 17 00:00:00 2001 From: Kim Date: Thu, 30 Oct 2025 14:12:44 -0400 Subject: [PATCH 4/8] refactor(clippy): Clippy + Remove unused import --- src/common/negative_response_code.rs | 4 ++-- src/services/ecu_reset.rs | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/common/negative_response_code.rs b/src/common/negative_response_code.rs index ffb3b1b..18c331a 100644 --- a/src/common/negative_response_code.rs +++ b/src/common/negative_response_code.rs @@ -49,10 +49,10 @@ pub enum NegativeResponseCode { /// component fails to respond, the server shall use this negative response to inform the client about the failure of the subnet component. /// /// This NRC is in general supported by each diagnostic service, as not otherwise stated in the data link specific implementation document, therefore it is not - /// listed FailurePreventsExecutionOfRequestedAction in the list of applicable response codes of the diagnostic services. `NegativeResponseCode` is a shared error mechanism + /// listed `FailurePreventsExecutionOfRequestedAction` in the list of applicable response codes of the diagnostic services. `NegativeResponseCode` is a shared error mechanism NoResponseFromSubnetComponent = 0x25, /// This NRC indicates that the requested action will not be taken because a failure condition, identified by a DTC (with at least one DTC status bit for - /// TestFailed, Pending, Confirmed or TestFailedSinceLastClear set to 1), has occurred and that this failure condition prevents the server from performing the requested action. + /// `TestFailed`, `Pending`, `Confirmed` or `TestFailedSinceLastClear` set to 1), has occurred and that this failure condition prevents the server from performing the requested action. /// /// This NRC can, for example, direct the technician to read DTCs in order to identify and fix the problem. FailurePreventsExecutionOfRequestedAction = 0x26, diff --git a/src/services/ecu_reset.rs b/src/services/ecu_reset.rs index b8ed7cf..83a383e 100644 --- a/src/services/ecu_reset.rs +++ b/src/services/ecu_reset.rs @@ -77,14 +77,14 @@ impl SingleValueWireFormat for EcuResetRequest {} #[non_exhaustive] pub struct EcuResetResponse { pub reset_type: ResetType, - /// Optional power down time in seconds for [ResetType::EnableRapidPowerShutDown] + /// Optional power down time in seconds for [`ResetType::EnableRapidPowerShutDown`] pub power_down_time: Option, } impl EcuResetResponse { /// Create a new '`EcuResetResponse`' /// - /// `power_down_time` is only valid for [ResetType::EnableRapidPowerShutDown], will be set to None otherwise + /// `power_down_time` is only valid for [`ResetType::EnableRapidPowerShutDown`], will be set to None otherwise pub(crate) fn new(reset_type: ResetType, power_down_time: Option) -> Self { let power_down_time = if reset_type == ResetType::EnableRapidPowerShutDown { power_down_time From d559aa8f649a3fab9c9ae2c2ac222b60e382daa8 Mon Sep 17 00:00:00 2001 From: Kim Date: Tue, 4 Nov 2025 13:51:43 -0500 Subject: [PATCH 5/8] feat: Add missing blocks for UDS DIDs Mostly Vehicle Manufacturer and System supplier. Still missing some blocks (EDR/OBD/etc) --- src/common/diagnostic_identifier.rs | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/src/common/diagnostic_identifier.rs b/src/common/diagnostic_identifier.rs index 278043b..1a4b539 100644 --- a/src/common/diagnostic_identifier.rs +++ b/src/common/diagnostic_identifier.rs @@ -71,6 +71,14 @@ impl TryFrom for UDSIdentifier { fn try_from(value: u16) -> Result { Ok(match value { 0x0000..=0x00FF => Self::ISOSAEReserved(value), + 0x0100..=0xA5FF => Self::VehicleManufacturerSpecific(value), + //0xA600..=0xA7FF => Self::ISOSAEReserved(value), + 0xA800..=0xACFF => Self::VehicleManufacturerSpecific(value), + 0xB000..=0xB1FF => Self::VehicleManufacturerSpecific(value), + 0xC000..=0xC2FF => Self::VehicleManufacturerSpecific(value), + 0xCF00..=0xEFFF => Self::VehicleManufacturerSpecific(value), + // 0xF000..=0xF00F => Self::TractorTrailer(value), + 0xF010..=0xF0FF => Self::VehicleManufacturerSpecific(value), 0xF100..=0xF17F => Self::VehicleManufacturerSpecific(value), // 0x0100..0xA5FF => Manufacturer Specific, 0xF180 => Self::BootSoftwareIdentification, @@ -105,6 +113,16 @@ impl TryFrom for UDSIdentifier { 0xF19D => Self::ECUInstallationDate, 0xF19E => Self::ODXFile, 0xF19F => Self::Entity, + 0xF1A0..=0xF1EF => Self::VehicleManufacturerSpecific(value), + 0xF1F0..=0xF1FF => Self::SystemSupplierSpecific(value), + // 0xF200..=0xFDFF => Self::PeriodicDataIdentifier(value), + // 0xF300..=0xF3FF => Self::DynamicallyDefined(value), + // 0xF400..=0xF5FF => Self::OBD(value), + // 0xF600..=0xF6FF => Self::OBDMonitor(value), + // 0xF700..=0xF7FF => Self::OBD(value), + // 0xF800..=0xF8FF => Self::OBDInfoType(value), + // 0xF900..=0xF9FF => Self::Tachograph(value), + // 0xFA00..=0xFA0F => Self::AirbagDeployment(value), 0xFD00..=0xFEFF => Self::SystemSupplierSpecific(value), 0xFF02..=0xFFFF => Self::ISOSAEReserved(value), From 98ad5f79b827870f68e4cccaa1535b5757340831 Mon Sep 17 00:00:00 2001 From: Kim Date: Tue, 4 Nov 2025 13:52:12 -0500 Subject: [PATCH 6/8] chore: Make u16 print as hex for DIDs --- src/error.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/error.rs b/src/error.rs index 1655316..1396e51 100644 --- a/src/error.rs +++ b/src/error.rs @@ -6,9 +6,9 @@ pub enum Error { IoError(#[from] std::io::Error), #[error("Insufficient data. Expected {0} bytes.")] InsufficientData(usize), - #[error("Invalid Diagnostic Identifier: {0:X}")] + #[error("Invalid Diagnostic Identifier: {0:#X}")] InvalidDiagnosticIdentifier(u16), - #[error("Invalid Diagnostic Identifier: {0:X} with payload {1:?}")] + #[error("Invalid Diagnostic Identifier: {0:#X} with payload {1:?}")] InvalidDiagnosticIdentifierPayload(u16, Vec), #[error("Invalid diagnostic session type: {0}")] InvalidDiagnosticSessionType(u8), From 14a18f8a2f93f86e315f6bc7e4c5ef1969af92e5 Mon Sep 17 00:00:00 2001 From: Kim Date: Tue, 4 Nov 2025 13:52:48 -0500 Subject: [PATCH 7/8] chore: Add an error log for unknown DIDs --- src/traits.rs | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/src/traits.rs b/src/traits.rs index c68f1fc..101aa57 100644 --- a/src/traits.rs +++ b/src/traits.rs @@ -171,9 +171,15 @@ where match Self::try_from(u16::from_be_bytes(identifier_data)) { Ok(identifier) => Ok(Some(identifier)), - Err(_) => Err(Error::InvalidDiagnosticIdentifier(u16::from_be_bytes( - identifier_data, - ))), + Err(_) => { + tracing::error!( + "Invalid identifier value: {:#X}", + u16::from_be_bytes(identifier_data) + ); + Err(Error::InvalidDiagnosticIdentifier(u16::from_be_bytes( + identifier_data, + ))) + } } } From d50ec78b73e017f5c5b4ec19b9b5318a4b2254ac Mon Sep 17 00:00:00 2001 From: Kim Date: Mon, 10 Nov 2025 11:48:04 -0500 Subject: [PATCH 8/8] style: Clippy fix --- src/traits.rs | 21 ++++++++++----------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/src/traits.rs b/src/traits.rs index 101aa57..ca93898 100644 --- a/src/traits.rs +++ b/src/traits.rs @@ -169,17 +169,16 @@ where _ => unreachable!("Impossible to read more than 2 bytes into 2 byte array"), } - match Self::try_from(u16::from_be_bytes(identifier_data)) { - Ok(identifier) => Ok(Some(identifier)), - Err(_) => { - tracing::error!( - "Invalid identifier value: {:#X}", - u16::from_be_bytes(identifier_data) - ); - Err(Error::InvalidDiagnosticIdentifier(u16::from_be_bytes( - identifier_data, - ))) - } + if let Ok(identifier) = Self::try_from(u16::from_be_bytes(identifier_data)) { + Ok(Some(identifier)) + } else { + tracing::error!( + "Invalid identifier value: {:#X}", + u16::from_be_bytes(identifier_data) + ); + Err(Error::InvalidDiagnosticIdentifier(u16::from_be_bytes( + identifier_data, + ))) } }