Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
1 change: 1 addition & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,7 @@ jobs:
--strict crates/uds_on_ip
--strict crates/simple_doip
--strict crates/uds_session
--strict crates/uds_protocol

# ===================================================================================
# Fuzz Tests (hand-rolled: the reusable workflow's fuzz-tests job cannot reach these)
Expand Down
6 changes: 2 additions & 4 deletions crates/uds_protocol/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -32,10 +32,8 @@ serde = ["dep:serde"]
#
# `utoipa` additionally implies `serde`, because a `ToSchema` describes this crate's *serde*
# representation: several types serialize as a single protocol byte rather than as their Rust
# shape, and their schemas are written to match. A `utoipa`-without-`serde` build would therefore
# publish a schema for a wire format that build cannot produce. It also cost real complexity —
# every type reachable only through a serde repr needed a second cfg predicate and a
# `allow(dead_code)` to stay compilable in a configuration nobody wants.
# shape, and their schemas are written to match. A `utoipa`-without-`serde` build would
# publish a schema for a wire format that build cannot produce.
utoipa = ["std", "serde", "dep:utoipa"]
clap = ["std", "dep:clap"]

Expand Down
27 changes: 15 additions & 12 deletions crates/uds_protocol/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,19 +25,22 @@ other crate in the stack.
(ISO 14229-2) sits beside it in the stack but does not depend on it — the
session layer never reads a message's content.
- [`uds_services`](https://github.com/luminartech/uds_stack/tree/main/crates/uds_services)
(ISO 14229-1's behaviour, to this crate's format) and
(ISO 14229-1's behavior, to this crate's format) and
[`uds_on_ip`](https://github.com/luminartech/uds_stack/tree/main/crates/uds_on_ip)
(ISO 14229-5) both depend on this crate directly: they dispatch and transport
the message types this crate encodes and decodes.

## What this crate does not do
## Scope

It is a synchronous, allocation-free codec, and nothing more. It owns no
sockets, buffers, or async runtime; it does not dispatch a request to a
handler, choose a negative response code, or hold any session state. Driving
the I/O loop, over `DoIP`, `UDSonIP`, ISO-TP, or anything else, is a caller's
job — see [`uds_services`](https://github.com/luminartech/uds_stack/tree/main/crates/uds_services)
for the layer that owns dispatch.
It is a synchronous, allocation-free codec. The sockets, buffers and async
runtime are the caller's, as is driving the I/O loop over `DoIP`, `UDSonIP`,
ISO-TP or anything else. Dispatching a request to a handler and choosing a
negative response code are
[`uds_services`](https://github.com/luminartech/uds_stack/tree/main/crates/uds_services)'s,
and session state is
[`uds_session`](https://github.com/luminartech/uds_stack/tree/main/crates/uds_session)'s.

The services it encodes and decodes:

| Service Name | Request SID | Response SID | Support |
| ----------------------------------- | ----------- | ------------ | ------- |
Expand Down Expand Up @@ -149,14 +152,14 @@ A checkmark in the table above means the service decodes into a typed
[`Request`]/[`Response`] variant; [`NegativeResponse`] is typed as well. Every
other service [`UdsServiceType`] enumerates decodes into [`Request::Other`] /
[`Response::Other`], carrying the service type and the raw payload bytes for
pass-through — an unmodelled service is handed to you intact rather than
pass-through — an unmodeled service is handed to you intact rather than
rejected.

## Wire codec dependency

`uds_protocol` builds its byte-level decoding on top of the [`automotive-wire-codec`](https://crates.io/crates/automotive-wire-codec)
crate, and re-exports its `Incomplete`, `TrailingBytes` and `InvalidWidth` types and its codec
traits (`Encode`, `Decode`, `DecodeIter`) at the crate root. These types are intentionally part of `uds_protocol`'s public API:
traits (`Encode`, `Decode`, `DecodeIter`) at the crate root. These types are part of `uds_protocol`'s public API:
they are shared across the Luminar automotive protocol crates so that callers handling multiple
protocols see one consistent short-read/trailing-data error shape. Because of this, a semver-major
release of `automotive-wire-codec` is a breaking change for `uds_protocol` as well.
Expand All @@ -177,12 +180,12 @@ Pull requests, bug reports and questions are welcome — see
Security reports go through GitHub's private vulnerability reporting; see
[`SECURITY.md`](https://github.com/luminartech/uds_stack/blob/main/SECURITY.md).

## Licence
## License

Licensed under either of [MIT](LICENSE-MIT) or [Apache-2.0](LICENSE-APACHE)
at your option. Both files are the workspace's, shared by every crate here.

Neither licence grants any right in ISO 14229-1 itself, which remains ISO's —
Neither license grants any right in ISO 14229-1 itself, which remains ISO's —
see
[Relationship to the standards](https://github.com/luminartech/uds_stack#relationship-to-the-standards)
in the workspace README.
9 changes: 4 additions & 5 deletions crates/uds_protocol/fuzz/fuzz_targets/fuzz_request_decode.rs
Original file line number Diff line number Diff line change
@@ -1,15 +1,14 @@
// Fuzz the Request decode path with arbitrary bytes.
// Any panic here indicates a bug — decode should return Err, not crash.
// Any panic here is a bug: decode returns Err rather than crashing.
#![no_main]
use libfuzzer_sys::fuzz_target;
use uds_protocol::{Decode, Request};

fuzz_target!(|data: &[u8]| {
// Attempt to decode arbitrary bytes as a UDS request.
// We don't care about the result — only that it doesn't panic.
// Attempt to decode arbitrary bytes as a UDS request. The result is irrelevant; the
// target checks only that decoding does not panic.
// Reporting SPRMIB indexes into the payload of an unmodeled service, so run it on every
// frame that decodes. It is panic-free by construction today -- `data.first()`, not
// `data[0]` -- which is exactly why it is worth pinning before an edit changes that.
// frame that decodes.
if let Ok((request, _)) = Request::decode(data) {
let _ = request.is_positive_response_suppressed();
}
Expand Down
6 changes: 3 additions & 3 deletions crates/uds_protocol/fuzz/fuzz_targets/fuzz_response_decode.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
// Fuzz the Response decode path with arbitrary bytes.
// Any panic here indicates a bug — decode should return Err, not crash.
// Any panic here is a bug: decode returns Err rather than crashing.
#![no_main]
use libfuzzer_sys::fuzz_target;
use uds_protocol::{Decode, Response};

fuzz_target!(|data: &[u8]| {
// Attempt to decode arbitrary bytes as a UDS response.
// We don't care about the result — only that it doesn't panic.
// Attempt to decode arbitrary bytes as a UDS response. The result is irrelevant; the
// target checks only that decoding does not panic.
let _ = Response::decode(data);
});
12 changes: 6 additions & 6 deletions crates/uds_protocol/fuzz/fuzz_targets/fuzz_roundtrip.rs
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
// Fuzz encode→decode roundtrip: decode arbitrary bytes into a Request,
// re-encode it, and verify the encoding is idempotent at the byte level.
// Fuzz the encode/decode round trip: decode arbitrary bytes into a Request,
// re-encode it, and check that the encoding is idempotent at the byte level.
//
// `Request` does not implement `PartialEq`, so we compare the canonical wire
// bytes rather than the decoded values: encoding a decoded message, decoding
// those bytes again, and re-encoding must yield identical bytes.
// The comparison is on the canonical wire bytes rather than the decoded values:
// encoding a decoded message, decoding those bytes again, and re-encoding must
// yield identical bytes.
#![no_main]
use automotive_wire_codec::SliceSink;
use libfuzzer_sys::fuzz_target;
use uds_protocol::{Decode, Encode, Request};

fuzz_target!(|data: &[u8]| {
// Only proceed if we can decode the input.
// Only a decodable input is checked.
let Ok((request, _rest)) = Request::decode(data) else {
return;
};
Expand Down
15 changes: 5 additions & 10 deletions crates/uds_protocol/src/dtc/ext_data.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
use crate::{Decode, Encode, Error, Incomplete};
use automotive_wire_codec::write_u8;

/// The `DtcExtDataRecordNumber` is used in the request message to get a stored
/// `DTCExtDataRecord` It's used to specify the type of `DTCExtDataRecord` to be reported.
/// Which stored `DTCExtDataRecord` a `ReadDTCInformation` request asks for.
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
#[cfg_attr(feature = "serde", serde(from = "u8", into = "u8"))]
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
Expand All @@ -17,25 +16,22 @@ pub enum DtcExtDataRecordNumber {
#[non_exhaustive]
VehicleManufacturerSpecific(u8),

/// Requests the server to report regulated emissions OBD stored
/// `DTCExtendedDataRecords`. The values are specified in SAE J1979-DA.
/// A regulated-emissions OBD extended data record, numbered as SAE J1979-DA defines.
///
/// 0x90-0x9F
#[non_exhaustive]
RegulatedEmissionsObdDtcExtDataRecords(u8),

/// The `DtcExtDataRecordNumber` parameter is used to specify the DTC number of the
/// `DTCExtendedData` record to be reported.
/// Regulated stored `DTCExtendedDataRecord`s.
///
/// 0xA0-0xEF
#[non_exhaustive]
RegulatedDtcExtDataRecords(u8),

/// Requests the server to report all regulated emissions OBD stored
/// `DTCExtendedDataRecords`.
/// Every regulated-emissions OBD extended data record the server stores.
AllRegulatedEmissionsObdDtcExtDataRecords,

/// Requests the server to report all stored `DTCExtendedDataRecords`
/// Every extended data record the server stores.
AllDtcExtDataRecords,
}

Expand Down Expand Up @@ -112,7 +108,6 @@ impl<'a> Decode<'a> for DtcExtDataRecordNumber {
}
}

// tests
#[cfg(test)]
mod tests {
use super::*;
Expand Down
4 changes: 2 additions & 2 deletions crates/uds_protocol/src/dtc/snapshot.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,8 @@ pub enum DtcSnapshotRecordNumber {

impl DtcSnapshotRecordNumber {
/// Create a `DtcSnapshotRecordNumber`, classifying the raw byte into `Reserved`
/// (`0x00`/`0xF0`), `All` (`0xFF`), or `Number`. Every byte is accepted (decoding is
/// deliberately liberal); no value is rejected.
/// (`0x00`/`0xF0`), `All` (`0xFF`), or `Number`. Every byte is accepted, as it is on
/// decode.
#[must_use]
pub const fn new(record_number: u8) -> Self {
match record_number {
Expand Down
Loading
Loading