///|
/// Additional errors for building and validating UDS exchanges.
pub suberror UdsError {
InvalidIdentifier
InvalidAddressFormat
EmptyPayload
InvalidBlockCounter
InvalidLength
} derive(Debug)
///|
/// A parsed positive or negative response with its service identifier.
pub struct UdsResponseInfo {
positive : Bool
response_service : Byte
payload : Array[Byte]
negative_code : Byte?
}
///|
/// Return the numeric service identifier.
pub fn uds_service_id(service : UdsService) -> Byte {
match service {
DiagnosticSessionControl => 0x10
EcuReset => 0x11
ReadDataByIdentifier => 0x22
WriteDataByIdentifier => 0x2E
ReadMemoryByAddress => 0x23
WriteMemoryByAddress => 0x3D
RoutineControl => 0x31
SecurityAccess => 0x27
CommunicationControl => 0x28
ClearDiagnosticInformation => 0x14
ReadDtcInformation => 0x19
RequestDownload => 0x34
TransferData => 0x36
RequestTransferExit => 0x37
TesterPresent => 0x3E
}
}
///|
/// Return the service's stable diagnostic name.
pub fn uds_service_name(service : UdsService) -> String {
match service {
DiagnosticSessionControl => "DiagnosticSessionControl"
EcuReset => "EcuReset"
ReadDataByIdentifier => "ReadDataByIdentifier"
WriteDataByIdentifier => "WriteDataByIdentifier"
ReadMemoryByAddress => "ReadMemoryByAddress"
WriteMemoryByAddress => "WriteMemoryByAddress"
RoutineControl => "RoutineControl"
SecurityAccess => "SecurityAccess"
CommunicationControl => "CommunicationControl"
ClearDiagnosticInformation => "ClearDiagnosticInformation"
ReadDtcInformation => "ReadDtcInformation"
RequestDownload => "RequestDownload"
TransferData => "TransferData"
RequestTransferExit => "RequestTransferExit"
TesterPresent => "TesterPresent"
}
}
///|
/// Return the request's numeric service identifier.
pub fn DiagnosticRequest::service_id(self : DiagnosticRequest) -> Byte {
uds_service_id(self.service)
}
///|
/// Return the service's name.
pub fn DiagnosticRequest::service_name(self : DiagnosticRequest) -> String {
uds_service_name(self.service)
}
///|
/// Return whether a request contains a suppress-positive-response bit.
pub fn DiagnosticRequest::suppress_response(self : DiagnosticRequest) -> Bool {
match self.payload.last() {
Some(value) =>
(value.to_int() & 0x80) != 0 &&
(
self.service is TesterPresent ||
self.service is DiagnosticSessionControl
)
None => false
}
}
///|
/// Build a write-data-by-identifier request.
pub fn write_data(
identifier : UInt,
data : Array[Byte],
) -> DiagnosticRequest raise UdsError {
if identifier > 0xFFFF {
raise InvalidIdentifier
}
{
service: WriteDataByIdentifier,
payload: [0x2E, (identifier >> 8).to_byte(), identifier.to_byte()] + data,
}
}
///|
/// Build a routine-control request with an optional input record.
pub fn routine_control(
routine_type : Byte,
identifier : UInt,
data? : Array[Byte] = [],
) -> DiagnosticRequest raise UdsError {
if identifier > 0xFFFF {
raise InvalidIdentifier
}
{
service: RoutineControl,
payload: [
0x31,
routine_type,
(identifier >> 8).to_byte(),
identifier.to_byte(),
] +
data,
}
}
///|
/// Build a security-access seed/key request.
pub fn security_access(subfunction : Byte) -> DiagnosticRequest {
{ service: SecurityAccess, payload: [0x27, subfunction] }
}
///|
/// Build a communication-control request.
pub fn communication_control(
control_type : Byte,
communication_type : Byte,
) -> DiagnosticRequest {
{
service: CommunicationControl,
payload: [0x28, control_type, communication_type],
}
}
///|
/// Build a request to clear one DTC group or all groups.
pub fn clear_diagnostic_information(
group : UInt,
) -> DiagnosticRequest raise UdsError {
if group > 0xFFFFFF {
raise InvalidIdentifier
}
{
service: ClearDiagnosticInformation,
payload: [
0x14,
(group >> 16).to_byte(),
(group >> 8).to_byte(),
group.to_byte(),
],
}
}
///|
/// Build a DTC information request.
pub fn read_dtc_information(
subfunction : Byte,
mask : Byte,
) -> DiagnosticRequest {
{ service: ReadDtcInformation, payload: [0x19, subfunction, mask] }
}
///|
/// Build a memory read request using a compact address/length format.
pub fn read_memory(
address : UInt,
length : UInt,
address_bytes? : Int = 2,
length_bytes? : Int = 2,
) -> DiagnosticRequest raise UdsError {
let format = check_address_format(address_bytes, length_bytes)
{
service: ReadMemoryByAddress,
payload: [0x23, format] +
encode_uint(address, address_bytes) +
encode_uint(length, length_bytes),
}
}
///|
/// Build a memory write request.
pub fn write_memory(
address : UInt,
data : Array[Byte],
address_bytes? : Int = 2,
) -> DiagnosticRequest raise UdsError {
let format = check_address_format(address_bytes, 1)
{
service: WriteMemoryByAddress,
payload: [0x3D, format] +
encode_uint(address, address_bytes) +
encode_uint(data.length().reinterpret_as_uint(), 1) +
data,
}
}
///|
/// Build a request-download command.
pub fn request_download(
data_format : Byte,
address : UInt,
length : UInt,
address_bytes? : Int = 2,
length_bytes? : Int = 2,
) -> DiagnosticRequest raise UdsError {
let format = check_address_format(address_bytes, length_bytes)
{
service: RequestDownload,
payload: [0x34, data_format, format] +
encode_uint(address, address_bytes) +
encode_uint(length, length_bytes),
}
}
///|
/// Build a transfer-data block.
pub fn transfer_data(
block_counter : Byte,
data : Array[Byte],
) -> DiagnosticRequest raise UdsError {
if block_counter == 0 {
raise InvalidBlockCounter
}
{ service: TransferData, payload: [0x36, block_counter] + data }
}
///|
pub fn request_transfer_exit(data? : Array[Byte] = []) -> DiagnosticRequest {
{ service: RequestTransferExit, payload: [0x37] + data }
}
///|
/// Parse a response and preserve the original payload for callers.
pub fn parse_uds_response(
request : DiagnosticRequest,
payload : Array[Byte],
) -> UdsResponseInfo raise UdsError {
if payload.is_empty() {
raise EmptyPayload
}
if payload[0] == 0x7F {
if payload.length() < 3 {
raise InvalidLength
}
{
positive: false,
response_service: payload[1],
payload: payload.copy(),
negative_code: Some(payload[2]),
}
} else {
let expected = uds_service_id(request.service) + 0x40
if payload[0] != expected {
raise InvalidLength
}
{
positive: true,
response_service: payload[0],
payload: payload.copy(),
negative_code: None,
}
}
}
///|
pub fn UdsResponseInfo::is_positive(self : UdsResponseInfo) -> Bool {
self.positive
}
///|
pub fn UdsResponseInfo::response_service(self : UdsResponseInfo) -> Byte {
self.response_service
}
///|
pub fn UdsResponseInfo::payload(self : UdsResponseInfo) -> Array[Byte] {
self.payload.copy()
}
///|
pub fn UdsResponseInfo::negative_code(self : UdsResponseInfo) -> Byte? {
self.negative_code
}
///|
/// Return a stable description for common UDS negative response codes.
pub fn uds_negative_code_name(code : Byte) -> String {
match code {
0x10 => "general-reject"
0x11 => "service-not-supported"
0x12 => "subfunction-not-supported"
0x13 => "incorrect-message-length-or-format"
0x22 => "conditions-not-correct"
0x24 => "request-sequence-error"
0x31 => "request-out-of-range"
0x33 => "security-access-denied"
0x35 => "invalid-key"
0x36 => "exceed-number-of-attempts"
0x37 => "required-time-delay-not-expired"
0x78 => "response-pending"
_ => "unknown-negative-response"
}
}
///|
/// Return a service request encoded as a CAN data frame.
pub fn diagnostic_frame(
request : DiagnosticRequest,
id : UInt,
extended? : Bool = false,
) -> Frame raise FrameError {
data_frame(id, request.payload, extended~)
}
///|
fn check_address_format(
address_bytes : Int,
length_bytes : Int,
) -> Byte raise UdsError {
if address_bytes < 1 ||
address_bytes > 4 ||
length_bytes < 1 ||
length_bytes > 4 {
raise InvalidAddressFormat
}
((address_bytes << 4) | length_bytes).to_byte()
}
///|
fn encode_uint(value : UInt, width : Int) -> Array[Byte] {
let result : Array[Byte] = []
for index in 0..> (8 * (width - index - 1))).to_byte())
}
result
}