Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
3548030
fix!: make the optional-integration features compile standalone
zheylmun Jul 28, 2026
05730f0
feat: expose the data format identifier on RequestDownloadRequest
zheylmun Jul 28, 2026
e78e24b
feat: map decode errors to the NRC a server should return
zheylmun Jul 28, 2026
b7ea9ae
fix!: stop TesterPresentRequest rewriting reserved sub-function bytes
zheylmun Jul 28, 2026
b0ea8f9
feat: dispatch allowed_nack_codes from the Request enum
zheylmun Jul 28, 2026
a96acb6
feat: model RequestUpload (0x35 / 0x75)
zheylmun Jul 28, 2026
6d91023
fix: stop the DTC iterators looping forever on a partial record
zheylmun Jul 28, 2026
bb7e744
refactor!: rename DtcSeverityAndStatusIter to WwhObdDtcSeverityIter
zheylmun Jul 28, 2026
ebf08e0
refactor!: make encapsulation follow one predictable rule
zheylmun Jul 28, 2026
5dcb292
refactor!: wire-order DataFormatIdentifier::new, document decode rema…
zheylmun Jul 28, 2026
7d2e879
docs+test: self-review fixes — consolidate CHANGELOG, widen iterator …
zheylmun Jul 28, 2026
5834495
fix!: reject misaligned DTC record lists at decode
zheylmun Jul 28, 2026
274c3ab
fix!: model ClearDiagnosticInformation's memorySelection as optional
zheylmun Jul 29, 2026
7786500
refactor: finish the const-fn, accessor and rustdoc consistency pass
zheylmun Jul 29, 2026
9052056
fix: accept every addressAndLengthFormatIdentifier ISO permits
zheylmun Jul 29, 2026
a249bd0
fix!: model ECUReset's powerDownTime as conditional
zheylmun Jul 29, 2026
324da00
fix!: handle SPRMIB and MemorySelection in ReadDTCInformation
zheylmun Jul 29, 2026
c671fed
fix!: add the mandatory DTCFormatIdentifier to the DTC count response
zheylmun Jul 29, 2026
66fbc2c
test: round-trip the ISO message-flow example frames
zheylmun Jul 29, 2026
4362ae9
fix: give DtcFaultDetectionCounterRecord a constructor
zheylmun Jul 29, 2026
7f3442c
fix!: align three services' error and length handling with the spec
zheylmun Jul 29, 2026
98f5edd
fix!: complete ControlDTCSetting's request model
zheylmun Jul 29, 2026
f8bde8c
fix!: decode the subnet nibble of CommunicationControl's communicatio…
zheylmun Jul 29, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
259 changes: 215 additions & 44 deletions CHANGELOG.md

Large diffs are not rendered by default.

11 changes: 0 additions & 11 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

18 changes: 11 additions & 7 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -20,20 +20,24 @@ authors = [

[features]
default = ["std"]
std = ["alloc", "embedded-io/std", "thiserror/std"]
alloc = ["embedded-io/alloc"]
serde = ["dep:serde", "dep:serde_bytes"]
utoipa = ["dep:utoipa"]
clap = ["dep:clap"]
std = ["alloc", "embedded-io/std", "thiserror/std", "serde?/std"]
alloc = ["embedded-io/alloc", "serde?/alloc"]
# `serde` is the only optional integration usable on a bare-metal target. It is wired as a
# core-only dependency (`default-features = false`) and picks up serde's `alloc`/`std` layers
# only when this crate's own `alloc`/`std` features are on, via weak `serde?/..` features.
serde = ["dep:serde"]
# `utoipa` and `clap` both imply `std`. Their derive macros expand to `std::`, `String` and
# `Vec` paths inside this crate, which cannot compile under `#![no_std]`.
utoipa = ["std", "dep:utoipa"]
clap = ["std", "dep:clap"]

[dependencies]
automotive-wire-codec = { version = "0.3", default-features = false }
bitmask-enum = "2"
embedded-io = { version = "0.7", default-features = false }
thiserror = { version = "2", default-features = false }
# Optional dependencies
serde = { version = "1", optional = true, features = ["derive"] }
serde_bytes = { version = "0.11", optional = true }
serde = { version = "1", optional = true, default-features = false, features = ["derive"] }
utoipa = { version = "5", optional = true }
clap = { version = "4", optional = true, features = ["derive"] }

Expand Down
62 changes: 32 additions & 30 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,33 +13,35 @@ It is not in a complete state yet with the 0.1.0 release, please check back soon
This library provides serialization and deserialization of UDS messages.
It is based on the ISO 14229-1:2020 standard.

| Service Name | Request SID | Response SID | Support |
| -------------------------------- | ----------- | ------------ | ------- |
| `DiagnosticSessionControl` | 0x10 | 0x50 | ✓ |
| `ECUReset` | 0x11 | 0x51 | ✓ |
| `ClearDiagnosticInformation` | 0x14 | 0x54 | ✓ |
| `ReadDTCInformation` | 0x19 | 0x59 | Partial |
| `ReadDataByIdentifier` | 0x22 | 0x62 | ✓ |
| `ReadMemoryByAddress` | 0x23 | 0x63 | |
| `ReadScalingDataByIdentifier` | 0x24 | 0x64 | |
| `SecurityAccess` | 0x27 | 0x67 | ✓ |
| `CommunicationControl` | 0x28 | 0x68 | ✓ |
| `Authentication` | 0x29 | 0x69 | |
| `ReadDataByPeriodicIdentifier` | 0x2A | 0x6A | |
| `WriteDataByIdentifier` | 0x2E | 0x6E | ✓ |
| `InputOutputControlByIdentifier` | 0x2F | 0x6F | |
| `RoutineControl` | 0x31 | 0x71 | ✓ |
| `RequestDownload` | 0x34 | 0x74 | ✓ |
| `RequestUpload` | 0x35 | 0x75 | |
| `TransferData` | 0x36 | 0x76 | ✓ |
| `RequestTransferExit` | 0x37 | 0x77 | ✓ |
| `RequestFileTransfer` | 0x38 | 0x78 | ✓ |
| `WriteMemoryByAddress` | 0x3D | 0x7D | |
| `TesterPresent` | 0x3E | 0x7E | ✓ |
| `SecuredDataTransmission` | 0x84 | 0xC4 | |
| `ControlDTCSetting` | 0x85 | 0xC5 | ✓ |
| `ResponseOnEvent` | 0x86 | 0xC6 | |
| `LinkControl` | 0x87 | 0xC7 | |
| Service Name | Request SID | Response SID | Support |
| ---------------------------------- | ----------- | ------------ | ------- |
| `DiagnosticSessionControl` | 0x10 | 0x50 | ✓ |
| `EcuReset` | 0x11 | 0x51 | ✓ |
| `ClearDiagnosticInformation` | 0x14 | 0x54 | ✓ |
| `ReadDTCInformation` | 0x19 | 0x59 | Partial |
| `ReadDataByIdentifier` | 0x22 | 0x62 | ✓ |
| `ReadMemoryByAddress` | 0x23 | 0x63 | |
| `ReadScalingDataByIdentifier` | 0x24 | 0x64 | |
| `SecurityAccess` | 0x27 | 0x67 | ✓ |
| `CommunicationControl` | 0x28 | 0x68 | ✓ |
| `Authentication` | 0x29 | 0x69 | |
| `ReadDataByPeriodicIdentifier` | 0x2A | 0x6A | |
| `DynamicallyDefinedDataIdentifier` | 0x2C | 0x6C | |
| `WriteDataByIdentifier` | 0x2E | 0x6E | ✓ |
| `InputOutputControlByIdentifier` | 0x2F | 0x6F | |
| `RoutineControl` | 0x31 | 0x71 | ✓ |
| `RequestDownload` | 0x34 | 0x74 | ✓ |
| `RequestUpload` | 0x35 | 0x75 | ✓ |
| `TransferData` | 0x36 | 0x76 | ✓ |
| `RequestTransferExit` | 0x37 | 0x77 | ✓ |
| `RequestFileTransfer` | 0x38 | 0x78 | ✓ |
| `WriteMemoryByAddress` | 0x3D | 0x7D | |
| `TesterPresent` | 0x3E | 0x7E | ✓ |
| `AccessTimingParameter` | 0x83 | 0xC3 | |
| `SecuredDataTransmission` | 0x84 | 0xC4 | |
| `ControlDtcSetting` | 0x85 | 0xC5 | ✓ |
| `ResponseOnEvent` | 0x86 | 0xC6 | |
| `LinkControl` | 0x87 | 0xC7 | |

## Integration

Expand Down Expand Up @@ -83,11 +85,11 @@ you need to keep before the buffer is reused.
These services decode into typed \[`Request`\]/\[`Response`\] variants: `DiagnosticSessionControl`,
`EcuReset`, `SecurityAccess`, `CommunicationControl`, `TesterPresent`, `ControlDtcSetting`,
`ReadDataByIdentifier`, `WriteDataByIdentifier`, `ClearDiagnosticInfo`, `ReadDtcInfo`,
`RoutineControl`, `RequestDownload`, `TransferData`, `RequestTransferExit`, `RequestFileTransfer`,
and `NegativeResponse`.
`RoutineControl`, `RequestDownload`, `RequestUpload`, `TransferData`, `RequestTransferExit`,
`RequestFileTransfer`, and `NegativeResponse`.

All other services enumerated in \[`UdsServiceType`\] (e.g. `Authentication`, `ReadMemoryByAddress`,
`RequestUpload`, `ResponseOnEvent`) are not individually modeled. Frames for them decode into
`ResponseOnEvent`) are not individually modeled. Frames for them decode into
\[`Request::Other`\] / \[`Response::Other`\], carrying the service type and raw payload bytes for
pass-through.

Expand Down
46 changes: 44 additions & 2 deletions fuzz/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion src/dtc/ext_data.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ pub enum DtcExtDataRecordNumber {
impl DtcExtDataRecordNumber {
/// Create a new `DtcExtDataRecordNumber` from a raw byte, mapping it to the correct variant.
#[must_use]
pub fn new(value: u8) -> Self {
pub const fn new(value: u8) -> Self {
match value {
0x00 | 0xF0..=0xFD => Self::IsoSaeReserved(value),
0x01..=0x8F => Self::VehicleManufacturer(value),
Expand Down
2 changes: 1 addition & 1 deletion src/dtc/snapshot.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ impl DtcSnapshotRecordNumber {
/// (`0x00`/`0xF0`), `All` (`0xFF`), or `Number`. Every byte is accepted (decoding is
/// deliberately liberal); no value is rejected.
#[must_use]
pub fn new(record_number: u8) -> Self {
pub const fn new(record_number: u8) -> Self {
match record_number {
0x00 | 0xF0 => Self::Reserved(record_number),
0xFF => Self::All,
Expand Down
49 changes: 43 additions & 6 deletions src/dtc/status.rs
Original file line number Diff line number Diff line change
Expand Up @@ -204,13 +204,32 @@ pub struct DtcRecord {
impl DtcRecord {
/// Create a `DtcRecord` from its three component bytes.
#[must_use]
pub fn new(high_byte: u8, middle_byte: u8, low_byte: u8) -> Self {
pub const fn new(high_byte: u8, middle_byte: u8, low_byte: u8) -> Self {
Self {
high_byte,
middle_byte,
low_byte,
}
}

/// The high byte, which ISO 14229-1 Annex D.1 uses to identify the system group
/// (powertrain, body, chassis, network).
#[must_use]
pub const fn high_byte(&self) -> u8 {
self.high_byte
}

/// The middle byte of the DTC number.
#[must_use]
pub const fn middle_byte(&self) -> u8 {
self.middle_byte
}

/// The low byte of the DTC number, which carries the failure type.
#[must_use]
pub const fn low_byte(&self) -> u8 {
self.low_byte
}
}

impl From<u32> for DtcRecord {
Expand Down Expand Up @@ -431,19 +450,21 @@ impl<'a> Decode<'a> for DtcSeverityMask {
}
}

/// Indicates the number of the specific `DTCSnapshot` data record requested
/// Setting to 0xFF will return all `DTCStoredDataRecords` at once
/// Identifies which `DTCStoredDataRecord` is being requested.
///
/// Setting to `0xFF` will return all `DTCStoredDataRecords` at once.
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct DtcStoredDataRecordNumber(u8);

// create a constructor for DtcStoredDataRecordNumber
impl DtcStoredDataRecordNumber {
/// Create a `DtcStoredDataRecordNumber` from a raw byte, rejecting the values ISO 14229-1
/// reserves.
///
/// # Errors
/// Will return `Err(Error::ReservedForLegislativeUse()` if the record number == 0x00 or 0xF0
pub fn new(record_number: u8) -> Result<Self, Error> {
/// Returns [`Error::ReservedForLegislativeUse`] if the record number is `0x00` or `0xF0`.
pub const fn new(record_number: u8) -> Result<Self, Error> {
if record_number == 0 || record_number == 0xF0 {
return Err(Error::ReservedForLegislativeUse(record_number));
}
Expand Down Expand Up @@ -551,6 +572,22 @@ mod encode_param_tests {
mod dtc_status_tests {
use super::*;

#[test]
fn dtc_record_exposes_its_three_wire_bytes() {
// A decoded DtcRecord has to be inspectable byte-wise: D.1 assigns meaning to the
// high byte (system group) separately from the middle and low bytes.
let record = DtcRecord::from(0x12_3456);
assert_eq!(record.high_byte(), 0x12);
assert_eq!(record.middle_byte(), 0x34);
assert_eq!(record.low_byte(), 0x56);
}

#[test]
fn dtc_record_byte_accessors_are_usable_in_const_context() {
const HIGH: u8 = CLEAR_ALL_DTCS.high_byte();
assert_eq!(HIGH, 0xFF);
}

#[test]
fn status_mask() {
let status_mask = DtcStatusMask::TestFailed | DtcStatusMask::PendingDtc;
Expand Down
Loading
Loading