diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d47c930..a60a34e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -49,6 +49,21 @@ jobs: files: lcov.info fail_ci_if_error: true + no_std: + name: Build no_std (bare-metal) + runs-on: ubuntu-latest + timeout-minutes: 5 + steps: + - uses: actions/checkout@v5 + - uses: dtolnay/rust-toolchain@stable + with: + targets: thumbv6m-none-eabi + - uses: Swatinem/rust-cache@v2 + - name: Build no_std + no_alloc + run: cargo build --no-default-features --target thumbv6m-none-eabi + - name: Build no_std + alloc + run: cargo build --no-default-features --features alloc --target thumbv6m-none-eabi + lint: name: Run Clippy runs-on: ubuntu-latest @@ -59,6 +74,8 @@ jobs: - uses: Swatinem/rust-cache@v2 - run: rustup component add clippy - run: cargo clippy --all-features + - run: cargo clippy --no-default-features + - run: cargo clippy --no-default-features --features alloc format: name: Run Cargo Format diff --git a/Cargo.lock b/Cargo.lock index 546a136..67b2757 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4,9 +4,9 @@ version = 4 [[package]] name = "anstream" -version = "0.6.20" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ae563653d1938f79b1ab1b5e668c87c76a9930414574a6583a7b7e11a8e6192" +checksum = "824a212faf96e9acacdbd09febd34438f8f711fb84e09a8916013cd7815ca28d" dependencies = [ "anstyle", "anstyle-parse", @@ -19,33 +19,33 @@ dependencies = [ [[package]] name = "anstyle" -version = "1.0.11" +version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "862ed96ca487e809f1c8e5a8447f6ee2cf102f846893800b20cebdf541fc6bbd" +checksum = "940b3a0ca603d1eade50a4846a2afffd5ef57a9feac2c0e2ec2e14f9ead76000" [[package]] name = "anstyle-parse" -version = "0.2.7" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e7644824f0aa2c7b9384579234ef10eb7efb6a0deb83f9630a49594dd9c15c2" +checksum = "52ce7f38b242319f7cabaa6813055467063ecdc9d355bbb4ce0c68908cd8130e" dependencies = [ "utf8parse", ] [[package]] name = "anstyle-query" -version = "1.1.4" +version = "1.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e231f6134f61b71076a3eab506c379d4f36122f2af15a9ff04415ea4c3339e2" +checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc" dependencies = [ "windows-sys", ] [[package]] name = "anstyle-wincon" -version = "3.0.10" +version = "3.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3e0633414522a32ffaac8ac6cc8f748e090c5717661fddeea04219e2344f5f2a" +checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d" dependencies = [ "anstyle", "once_cell_polyfill", @@ -68,11 +68,21 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" +[[package]] +name = "byteorder-embedded-io" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed6bb9472871706c9b1f648ca527031e33d647a95706d6ab5659f22ca28d419" +dependencies = [ + "byteorder", + "embedded-io", +] + [[package]] name = "clap" -version = "4.5.47" +version = "4.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7eac00902d9d136acd712710d71823fb8ac8004ca445a89e73a41d45aa712931" +checksum = "b193af5b67834b676abd72466a96c1024e6a6ad978a1f484bd90b85c94041351" dependencies = [ "clap_builder", "clap_derive", @@ -80,9 +90,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.47" +version = "4.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ad9bbf750e73b5884fb8a211a9424a1906c1e156724260fdae972f31d70e1d6" +checksum = "714a53001bf66416adb0e2ef5ac857140e7dc3a0c48fb28b2f10762fc4b5069f" dependencies = [ "anstream", "anstyle", @@ -92,9 +102,9 @@ dependencies = [ [[package]] name = "clap_derive" -version = "4.5.47" +version = "4.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbfd7eae0b0f1a6e63d4b13c9c478de77c2eb546fba158ad50b4203dc24b9f9c" +checksum = "1110bd8a634a1ab8cb04345d8d878267d57c3cf1b38d91b71af6686408bbca6a" dependencies = [ "heck", "proc-macro2", @@ -104,15 +114,21 @@ dependencies = [ [[package]] name = "clap_lex" -version = "0.7.5" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b94f61472cee1439c0b966b47e3aca9ae07e45d070759512cd390ea2bebc6675" +checksum = "c8d4a3bb8b1e0c1050499d1815f5ab16d04f0959b233085fb31653fbfc9d98f9" [[package]] name = "colorchoice" -version = "1.0.4" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570" + +[[package]] +name = "embedded-io" +version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75" +checksum = "9eb1aa714776b75c7e67e1da744b81a129b3ff919c8712b5e1b32252c1f07cc7" [[package]] name = "equivalent" @@ -122,9 +138,9 @@ checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" [[package]] name = "hashbrown" -version = "0.15.5" +version = "0.16.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" +checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" [[package]] name = "heck" @@ -134,75 +150,58 @@ checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" [[package]] name = "indexmap" -version = "2.11.0" +version = "2.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2481980430f9f78649238835720ddccc57e52df14ffce1c6f37391d61b563e9" +checksum = "7714e70437a7dc3ac8eb7e6f8df75fd8eb422675fc7678aff7364301092b1017" dependencies = [ "equivalent", "hashbrown", "serde", + "serde_core", ] [[package]] name = "is_terminal_polyfill" -version = "1.70.1" +version = "1.70.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" +checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695" [[package]] name = "itoa" -version = "1.0.15" +version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" +checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" [[package]] name = "memchr" -version = "2.7.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0" - -[[package]] -name = "once_cell" -version = "1.21.3" +version = "2.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" +checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79" [[package]] name = "once_cell_polyfill" -version = "1.70.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4895175b425cb1f87721b59f0f286c2092bd4af812243672510e1ac53e2e0ad" - -[[package]] -name = "pin-project-lite" -version = "0.2.16" +version = "1.70.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" +checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" [[package]] name = "proc-macro2" -version = "1.0.101" +version = "1.0.106" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89ae43fd86e4158d6db51ad8e2b80f313af9cc74f5c0e03ccb87de09998732de" +checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" dependencies = [ "unicode-ident", ] [[package]] name = "quote" -version = "1.0.40" +version = "1.0.45" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" +checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924" dependencies = [ "proc-macro2", ] -[[package]] -name = "ryu" -version = "1.0.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" - [[package]] name = "serde" version = "1.0.228" @@ -245,14 +244,15 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.143" +version = "1.0.149" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d401abef1d108fbd9cbaebc3e46611f4b1021f714a0597a71f41ee463f5f4a5a" +checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86" dependencies = [ "itoa", "memchr", - "ryu", "serde", + "serde_core", + "zmij", ] [[package]] @@ -263,9 +263,9 @@ checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" [[package]] name = "syn" -version = "2.0.106" +version = "2.0.117" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ede7c438028d4436d71104916910f5bb611972c5cfd7f89b8300a8186e6fada6" +checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99" dependencies = [ "proc-macro2", "quote", @@ -274,74 +274,43 @@ dependencies = [ [[package]] name = "thiserror" -version = "2.0.16" +version = "2.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3467d614147380f2e4e374161426ff399c91084acd2363eaf549172b3d5e60c0" +checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "2.0.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c5e1be1c48b9172ee610da68fd9cd2770e7a4056cb3fc98710ee6906f0c7960" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "tracing" -version = "0.1.41" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" -dependencies = [ - "pin-project-lite", - "tracing-attributes", - "tracing-core", -] - -[[package]] -name = "tracing-attributes" -version = "0.1.30" +version = "2.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81383ab64e72a7a8b8e13130c49e3dab29def6d0c7d76a03087b3cf71c5c6903" +checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" dependencies = [ "proc-macro2", "quote", "syn", ] -[[package]] -name = "tracing-core" -version = "0.1.34" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9d12581f227e93f094d3af2ae690a574abb8a2b9b7a96e7cfe9647b2b617678" -dependencies = [ - "once_cell", -] - [[package]] name = "uds_protocol" version = "0.1.0" dependencies = [ "bitmask-enum", - "byteorder", + "byteorder-embedded-io", "clap", + "embedded-io", "serde", "serde_bytes", "thiserror", - "tracing", "utoipa", ] [[package]] name = "unicode-ident" -version = "1.0.18" +version = "1.0.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" +checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" [[package]] name = "utf8parse" @@ -374,80 +343,21 @@ dependencies = [ [[package]] name = "windows-link" -version = "0.1.3" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" [[package]] name = "windows-sys" -version = "0.60.2" +version = "0.61.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" -dependencies = [ - "windows-targets", -] - -[[package]] -name = "windows-targets" -version = "0.53.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d5fe6031c4041849d7c496a8ded650796e7b6ecc19df1a431c1a363342e5dc91" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" dependencies = [ "windows-link", - "windows_aarch64_gnullvm", - "windows_aarch64_msvc", - "windows_i686_gnu", - "windows_i686_gnullvm", - "windows_i686_msvc", - "windows_x86_64_gnu", - "windows_x86_64_gnullvm", - "windows_x86_64_msvc", ] [[package]] -name = "windows_aarch64_gnullvm" -version = "0.53.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86b8d5f90ddd19cb4a147a5fa63ca848db3df085e25fee3cc10b39b6eebae764" - -[[package]] -name = "windows_aarch64_msvc" -version = "0.53.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7651a1f62a11b8cbd5e0d42526e55f2c99886c77e007179efff86c2b137e66c" - -[[package]] -name = "windows_i686_gnu" -version = "0.53.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1dc67659d35f387f5f6c479dc4e28f1d4bb90ddd1a5d3da2e5d97b42d6272c3" - -[[package]] -name = "windows_i686_gnullvm" -version = "0.53.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ce6ccbdedbf6d6354471319e781c0dfef054c81fbc7cf83f338a4296c0cae11" - -[[package]] -name = "windows_i686_msvc" -version = "0.53.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "581fee95406bb13382d2f65cd4a908ca7b1e4c2f1917f143ba16efe98a589b5d" - -[[package]] -name = "windows_x86_64_gnu" -version = "0.53.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e55b5ac9ea33f2fc1716d1742db15574fd6fc8dadc51caab1c16a3d3b4190ba" - -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.53.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0a6e035dd0599267ce1ee132e51c27dd29437f63325753051e71dd9e42406c57" - -[[package]] -name = "windows_x86_64_msvc" -version = "0.53.0" +name = "zmij" +version = "1.0.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486" +checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa" diff --git a/Cargo.toml b/Cargo.toml index 9628e1a..eb4f9e9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,15 +19,18 @@ authors = [ ] [features] +default = ["std"] +std = ["alloc", "byteorder-embedded-io/std", "embedded-io/std", "thiserror/std"] +alloc = [] serde = ["dep:serde", "dep:serde_bytes"] utoipa = ["dep:utoipa"] clap = ["dep:clap"] [dependencies] bitmask-enum = "2" -byteorder = "1" -thiserror = "2" -tracing = "0.1" +byteorder-embedded-io = { version = "0.1", default-features = false, features = ["embedded-io"] } +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 } diff --git a/src/common/diagnostic_identifier.rs b/src/common/diagnostic_identifier.rs index 06f136c..0f179f1 100644 --- a/src/common/diagnostic_identifier.rs +++ b/src/common/diagnostic_identifier.rs @@ -190,15 +190,15 @@ impl From for u16 { } } -impl std::fmt::Display for UDSIdentifier { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { +impl core::fmt::Display for UDSIdentifier { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { let value: u16 = (*self).into(); write!(f, "{value:#06X?}") } } -impl std::fmt::Debug for UDSIdentifier { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { +impl core::fmt::Debug for UDSIdentifier { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { let value: u16 = (*self).into(); write!(f, "{value:#06X}") } diff --git a/src/common/dtc_ext_data.rs b/src/common/dtc_ext_data.rs index eba7055..52f4629 100644 --- a/src/common/dtc_ext_data.rs +++ b/src/common/dtc_ext_data.rs @@ -1,16 +1,10 @@ -use byteorder::{ReadBytesExt, WriteBytesExt}; - -use crate::{ - DTCRecord, DTCStatusMask, Error, IterableWireFormat, SingleValueWireFormat, WireFormat, -}; - /// The `DTCExtDataRecordNumber` is used in the request message to get a stored [`DTCExtDataRecord`] /// Its used to specify the type of `DTCExtDataRecord` to be reported. #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] #[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))] #[derive(Clone, Copy, Debug, Eq, PartialEq)] pub enum DTCExtDataRecordNumber { - /// ISO/SAE reserved record numbers (`0x00`, `0xF0–0xFD`). + /// ISO/SAE reserved record numbers (`0x00`, `0xF0-0xFD`). ISOSAEReserved(u8), /// Vehicle manufactured specific stored [`DTCExtDataRecord`]s @@ -71,135 +65,6 @@ impl PartialEq for DTCExtDataRecordNumber { } } -impl WireFormat for DTCExtDataRecordNumber { - fn required_size(&self) -> usize { - 1 - } - - fn encode(&self, writer: &mut T) -> Result { - writer.write_u8(self.value())?; - Ok(self.required_size()) - } -} - -impl SingleValueWireFormat for DTCExtDataRecordNumber { - fn decode(reader: &mut T) -> Result { - Ok(Self::new(reader.read_u8()?)) - } -} - -impl IterableWireFormat for DTCExtDataRecordNumber { - fn decode_next(reader: &mut T) -> Result, Error> { - match reader.read_u8() { - Ok(v) => Ok(Some(Self::new(v))), - Err(_) => Ok(None), - } - } -} - -#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] -#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))] -#[derive(Clone, Debug, PartialEq)] -/// A single DTC extended-data record containing user-defined payload items. -pub struct DTCExtDataRecord { - /// The decoded payload entries for this record. - pub data: Vec, -} - -impl WireFormat for DTCExtDataRecord { - fn required_size(&self) -> usize { - // n bytes of data per UserPayload - self.data - .iter() - .map(WireFormat::required_size) - .sum::() - } - - fn encode(&self, writer: &mut T) -> Result { - for d in &self.data { - d.encode(writer)?; - } - Ok(self.required_size()) - } -} - -impl SingleValueWireFormat for DTCExtDataRecord { - fn decode(reader: &mut T) -> Result { - let mut data = Vec::new(); - for payload in UserPayload::decode_iter(reader) { - match payload { - Ok(p) => data.push(p), - Err(_) => break, - } - } - Ok(Self { data }) - } -} - -impl IterableWireFormat for DTCExtDataRecord { - fn decode_next(reader: &mut T) -> Result, Error> { - let mut data = Vec::new(); - for payload in UserPayload::decode_iter(reader) { - match payload { - Err(_) => return Ok(None), - Ok(payload) => data.push(payload), - } - } - Ok(Some(Self { data })) - } -} - -#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] -#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))] -#[derive(Clone, Debug, PartialEq)] -/// A DTC extended-data record list: a DTC + status mask followed by one or more [`DTCExtDataRecord`]s. -pub struct DTCExtDataRecordList { - /// The DTC this extended data belongs to. - pub mask_record: DTCRecord, - /// The DTC status mask at the time of reporting. - pub status_mask: DTCStatusMask, - /// The extended-data records associated with this DTC. - pub record_data: Vec>, -} - -impl WireFormat for DTCExtDataRecordList { - fn required_size(&self) -> usize { - self.mask_record.required_size() - + self.status_mask.required_size() - + self - .record_data - .iter() - .map(WireFormat::required_size) - .sum::() - } - - fn encode(&self, writer: &mut T) -> Result { - self.mask_record.encode(writer)?; - self.status_mask.encode(writer)?; - for record in &self.record_data { - record.encode(writer)?; - } - Ok(self.required_size()) - } -} - -impl SingleValueWireFormat for DTCExtDataRecordList { - fn decode(reader: &mut T) -> Result { - let mask_record = DTCRecord::decode(reader)?; - let status_mask = DTCStatusMask::decode(reader)?; - let mut record_data = Vec::new(); - // Read the record number, and then the payload - if let Some(record) = DTCExtDataRecord::decode_next(reader)? { - record_data.push(record); - } - Ok(Self { - mask_record, - status_mask, - record_data, - }) - } -} - // tests #[cfg(test)] mod tests { diff --git a/src/common/dtc_snapshot.rs b/src/common/dtc_snapshot.rs index 3efc1ee..fea2021 100644 --- a/src/common/dtc_snapshot.rs +++ b/src/common/dtc_snapshot.rs @@ -1,159 +1,6 @@ //! Diagnostic Trouble Code (DTC) Snapshot Data //! Snapshot data represents a collection of sensor values captured when a DTC is triggered. //! Represents the state of the server at the time the DTC was triggered. -use byteorder::{ReadBytesExt, WriteBytesExt}; - -use crate::{ - DTCRecord, DTCStatusMask, Error, IterableWireFormat, SingleValueWireFormat, WireFormat, -}; - -#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] -#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))] -#[derive(Clone, Debug, PartialEq)] -/// A DTC snapshot record list: a DTC + status mask followed by one or more numbered snapshot records. -pub struct DTCSnapshotRecordList { - /// The DTC this snapshot data belongs to. - pub dtc_record: DTCRecord, - /// The DTC status mask at the time of reporting. - pub status_mask: DTCStatusMask, - /// The snapshot records, each paired with its record number. - pub snapshot_data: Vec<(DTCSnapshotRecordNumber, DTCSnapshotRecord)>, -} - -impl WireFormat for DTCSnapshotRecordList { - fn required_size(&self) -> usize { - self.dtc_record.required_size() - + self.status_mask.required_size() - + self - .snapshot_data - .iter() - .fold(0, |acc, (record_number, record)| { - acc + record_number.required_size() + record.required_size() - }) - } - - fn encode(&self, writer: &mut T) -> Result { - self.dtc_record.encode(writer)?; - self.status_mask.encode(writer)?; - for (record_number, record) in &self.snapshot_data { - record_number.encode(writer)?; - record.encode(writer)?; - } - - Ok(self.required_size()) - } -} - -impl SingleValueWireFormat for DTCSnapshotRecordList { - fn decode(reader: &mut T) -> Result { - let dtc_record = DTCRecord::decode(reader)?; - let status_mask = DTCStatusMask::decode(reader)?; - - // Loop until we can't read any more records - let mut snapshot_data = Vec::new(); - loop { - let record_number = match DTCSnapshotRecordNumber::decode_next(reader) { - Ok(Some(record_number)) => record_number, - Ok(None) => break, - Err(e) => return Err(e), - }; - - let record = DTCSnapshotRecord::decode(reader)?; - - snapshot_data.push((record_number, record)); - } - - Ok(Self { - dtc_record, - status_mask, - snapshot_data, - }) - } -} - -/// Contains a snapshot of data values from the time of the system malfunction occurrence. -#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] -#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))] -#[derive(Clone, Debug, PartialEq)] -pub struct DTCSnapshotRecord { - /// The data identifier (DID) for the data values taken at the time of the system malfunction occurrence - /// These can be vehicle manufacturer specific - /// See C.1 for broad categories of data identifiers - /// The data values taken at the time of the system malfunction occurrence - /// The data values are dependent on the data identifier, and are specified by the vehicle manufacturer/supplier - pub data: Vec, -} - -impl DTCSnapshotRecord { - /// Create a new snapshot record from a list of payload items. - #[must_use] - pub fn new(data: Vec) -> Self { - Self { data } - } - - /// The number of DIDs in the snapshot record - /// If the number of DIDs exceeds 0xFF, the value 0x00 shall be used - #[allow(clippy::cast_possible_truncation)] - #[must_use] - pub fn number_of_dids(&self) -> u8 { - if self.data.len() > 0xFF { - 0 - } else { - self.data.len() as u8 - } - } -} - -impl WireFormat for DTCSnapshotRecord { - fn required_size(&self) -> usize { - 1 + self - .data - .iter() - .map(WireFormat::required_size) - .sum::() - } - - // TODO: Must write the DIDs as well... - fn encode(&self, writer: &mut T) -> Result { - // write 0x00 if the number of DIDs exceed 0xFF - writer.write_u8(self.number_of_dids())?; - - let mut payload_written = 0; - for payload in &self.data { - // Assumes this writes the DID as well, I think that's safe? - payload_written += payload.encode(writer)?; - } - Ok(1 + payload_written) - } -} - -impl SingleValueWireFormat for DTCSnapshotRecord { - #[allow(clippy::cast_possible_truncation)] - fn decode(reader: &mut T) -> Result { - let number_of_dids = reader.read_u8()?; - // Make sure we read the correct number of DIDs, 0 means unlimited (or at least more than 0xFF) - let mut data = Vec::new(); - for payload in UserPayload::decode_iter(reader) { - match payload { - Ok(did) => { - data.push(did); - // Do not attempt to read more than the number of DIDs the server said it would send - if number_of_dids != 0 && data.len() == number_of_dids as usize { - break; - } - } - Err(e) => { - return Err(e); - } - } - } - if number_of_dids != 0x00 && number_of_dids != data.len() as u8 { - return Err(Error::IncorrectMessageLengthOrInvalidFormat); - } - - Ok(Self { data }) - } -} /// Identifies which DTC snapshot record is being requested or reported. #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] @@ -196,214 +43,17 @@ impl PartialEq for DTCSnapshotRecordNumber { } } -impl WireFormat for DTCSnapshotRecordNumber { - fn required_size(&self) -> usize { - 1 - } - - fn encode(&self, writer: &mut T) -> Result { - writer.write_u8(self.value())?; - Ok(1) - } -} - -impl SingleValueWireFormat for DTCSnapshotRecordNumber { - fn decode(reader: &mut T) -> Result { - Ok(Self::new(reader.read_u8()?)) - } -} - -impl IterableWireFormat for DTCSnapshotRecordNumber { - fn decode_next(reader: &mut T) -> Result, Error> { - let Ok(record_number) = reader.read_u8() else { - return Ok(None); - }; - Ok(Some(Self::new(record_number))) - } -} - #[cfg(test)] mod snapshot { - - pub enum ProtocolPayload { - Did4711([u8; 5]), - Did8711([u8; 5]), - Did8712(u8, u8, u16), - } - // Testing out a macro to make simplifying the enum to DID value "nicer" - macro_rules! value_map { - ($(($e:ident, $v:literal)),* $(,)?) => { - pub fn value(&self) -> u16 { - match self { - $(ProtocolPayload::$e(..) => $v,)* - } - } - } - } - impl ProtocolPayload { - #[rustfmt::skip] - value_map![ - (Did4711, 0x4711), - (Did8711, 0x8711), - (Did8712, 0x8712), - ]; - } - - impl WireFormat for ProtocolPayload { - #[allow(clippy::match_same_arms)] - fn required_size(&self) -> usize { - 2 + match self { - ProtocolPayload::Did4711(_) => 5, - ProtocolPayload::Did8711(_) => 5, - ProtocolPayload::Did8712(..) => 4, - } - } - - fn encode(&self, writer: &mut T) -> Result { - writer.write_u16::(self.value())?; - let mut written = 2; - - match self { - ProtocolPayload::Did8711(data) | ProtocolPayload::Did4711(data) => { - writer.write_all(data)?; - written += data.len(); - } - // bogus data - ProtocolPayload::Did8712(..) => { - writer.write_u32::(78)?; - written += 4; - } - } - Ok(written) - } - } - - impl IterableWireFormat for ProtocolPayload { - fn decode_next(reader: &mut T) -> Result, Error> { - let mut identifier_data: [u8; 2] = [0; 2]; - match reader.read(&mut identifier_data)? { - 0 => return Ok(None), - 1 => return Err(Error::IncorrectMessageLengthOrInvalidFormat), - 2 => (), - _ => unreachable!("Impossible to read more than 2 bytes into 2 byte array"), - } - // read the identifier - let identifier = u16::from_be_bytes(identifier_data); - match identifier { - 0x4711 => { - let mut did_4711 = [0u8; 5]; - match reader.read(&mut did_4711)? { - 0 => return Ok(None), - 1 => return Err(Error::IncorrectMessageLengthOrInvalidFormat), - 5 => (), - _ => unreachable!("Impossible to read more than 5 bytes into 5 byte array"), - } - Ok(Some(Self::Did4711(did_4711))) - } - 0x8711 => { - let mut did_8711 = [0u8; 5]; - match reader.read(&mut did_8711)? { - 0 => return Ok(None), - 1 => return Err(Error::IncorrectMessageLengthOrInvalidFormat), - 5 => (), - _ => unreachable!("Impossible to read more than 5 bytes into 5 byte array"), - } - Ok(Some(Self::Did8711(did_8711))) - } - _ => Err(Error::IncorrectMessageLengthOrInvalidFormat), - } - } - } - use super::*; #[test] - fn snapshot_record() { + fn snapshot_record_number() { let record = DTCSnapshotRecordNumber::new(0x01); - let mut writer = Vec::new(); - let written_number = record.encode(&mut writer).unwrap(); - assert_eq!(record.required_size(), 1); - assert_eq!(written_number, 1); - } - - #[test] - fn test_value() { - let did = ProtocolPayload::Did8712(1, 2, 3); - assert_eq!(did.value(), 0x8712); - - match did { - ProtocolPayload::Did8712(a, b, c) => { - assert_eq!(a, 1); - assert_eq!(b, 2); - assert_eq!(c, 3); - } - _ => panic!("Expected Did8712"), - } - } - - #[test] - #[allow(clippy::cast_possible_truncation)] - #[allow(clippy::match_wildcard_for_single_variants)] - fn snapshot_list() { - #[rustfmt::skip] - let bytes:[u8; 29] = [ - // DTC Number + Status - 0x12, 0x34, 0x56, 0x24, - // DTC Snapshot Record Number - 0x01, - // Number of DIDs to read - 0x02, - // DID (fake) - 0x47, 0x11, - // Snapshot data - 0xA6, 0x66, 0x07, 0x50, 0x20, - 0x87, 0x11, - 0x00, 0x00, 0x00, 0x00, 0x09, - // New DTC Snapshot record number - 0x02, - // Number of DIDs to read (0 indicates an unlimited number) - 0x01, - 0x47, 0x11, - 0xA6, 0x66, 0x07, 0x50, 0x20, - ]; - - let resp = DTCSnapshotRecordList::decode(&mut bytes.as_slice()).unwrap(); - - assert_eq!(resp.dtc_record, DTCRecord::from(0x0012_3456)); - let mut number: u8 = 1; + assert_eq!(record.value(), 0x01); + assert_eq!(record, DTCSnapshotRecordNumber::Number(0x01)); - resp.snapshot_data - .iter() - .for_each(|(record_number, record)| { - // Check the record numbers match for the ones we're expecting - assert_eq!(*record_number, number); - number += 1; - // Just check the helper function - assert_eq!(record.number_of_dids(), record.data.len() as u8); - // check the data of the payload - for payload in &record.data { - match payload { - ProtocolPayload::Did4711(data) => { - assert_eq!(data, &[0xA6, 0x66, 0x07, 0x50, 0x20]); - } - ProtocolPayload::Did8711(data) => { - assert_eq!(data, &[0x00, 0x00, 0x00, 0x00, 0x09]); - } - _ => panic!("Unexpected payload in bagging area"), - } - let mut writer = Vec::new(); - let written = payload.encode(&mut writer).unwrap(); - assert_eq!(written, payload.required_size()); - } - }); - let mut writer = Vec::new(); - let written = resp.encode(&mut writer).unwrap(); - assert_eq!(written, resp.required_size()); - assert_eq!( - written, - bytes.len(), - "Written bytes: \n{writer:?}\n{bytes:?}" - ); - assert_eq!(writer, bytes); + let all = DTCSnapshotRecordNumber::new(0xFF); + assert_eq!(all, DTCSnapshotRecordNumber::All); } } diff --git a/src/common/dtc_status.rs b/src/common/dtc_status.rs index 9f41da5..b1821fb 100644 --- a/src/common/dtc_status.rs +++ b/src/common/dtc_status.rs @@ -1,7 +1,6 @@ use bitmask_enum::bitmask; -use byteorder::{ReadBytesExt, WriteBytesExt}; -use crate::{Error, IterableWireFormat, SingleValueWireFormat, WireFormat}; +use crate::{Decode, DecodeIter, Encode, Error}; /// Bit-packed DTC status information used by the `ReadDTCInformation` service /// @@ -107,21 +106,22 @@ pub enum DTCStatusMask { WarningIndicatorRequested, } -impl WireFormat for DTCStatusMask { - fn required_size(&self) -> usize { +impl Encode for DTCStatusMask { + fn encoded_size(&self) -> usize { 1 } - - fn encode(&self, writer: &mut T) -> Result { - writer.write_u8(self.bits())?; + fn encode(&self, writer: &mut impl embedded_io::Write) -> Result { + writer.write_all(&[self.bits()]).map_err(Error::io)?; Ok(1) } } -impl SingleValueWireFormat for DTCStatusMask { - fn decode(reader: &mut T) -> Result { - let status_byte = reader.read_u8()?; - Ok(Self::from(status_byte)) +impl<'a> Decode<'a> for DTCStatusMask { + fn decode(buf: &'a [u8]) -> Result<(Self, &'a [u8]), Error> { + if buf.is_empty() { + return Err(Error::InsufficientData(1)); + } + Ok((Self::from(buf[0]), &buf[1..])) } } @@ -230,42 +230,41 @@ impl From for u32 { } } -impl WireFormat for DTCRecord { - fn required_size(&self) -> usize { +impl Encode for DTCRecord { + fn encoded_size(&self) -> usize { 3 } - fn encode(&self, writer: &mut T) -> Result { - writer.write_all(&[self.high_byte, self.middle_byte, self.low_byte])?; + fn encode(&self, writer: &mut impl embedded_io::Write) -> Result { + writer + .write_all(&[self.high_byte, self.middle_byte, self.low_byte]) + .map_err(Error::io)?; Ok(3) } } -impl SingleValueWireFormat for DTCRecord { - fn decode(reader: &mut T) -> Result { - let high_byte = reader.read_u8()?; - let middle_byte = reader.read_u8()?; - let low_byte = reader.read_u8()?; - Ok(Self { - high_byte, - middle_byte, - low_byte, - }) +impl<'a> Decode<'a> for DTCRecord { + fn decode(buf: &'a [u8]) -> Result<(Self, &'a [u8]), Error> { + if buf.len() < 3 { + return Err(Error::InsufficientData(3)); + } + Ok(( + Self { + high_byte: buf[0], + middle_byte: buf[1], + low_byte: buf[2], + }, + &buf[3..], + )) } } -impl IterableWireFormat for DTCRecord { - fn decode_next(reader: &mut T) -> Result, crate::Error> { - let Ok(high_byte) = reader.read_u8() else { +impl<'a> DecodeIter<'a> for DTCRecord { + fn decode_next(buf: &'a [u8]) -> Result, Error> { + if buf.is_empty() { return Ok(None); - }; - let middle_byte = reader.read_u8()?; - let low_byte = reader.read_u8()?; - Ok(Some(Self { - high_byte, - middle_byte, - low_byte, - })) + } + Decode::decode(buf).map(Some) } } @@ -385,7 +384,7 @@ pub enum DTCSeverityMask { } impl DTCSeverityMask { - /// Returns `true` if at least one DTC class bit (bits 0–4) is set. + /// Returns `true` if at least one DTC class bit (bits 0-4) is set. /// Multiple class bits may be set to query multiple DTC classes at once. #[must_use] pub fn is_valid(&self) -> bool { @@ -413,40 +412,12 @@ impl DTCStoredDataRecordNumber { /// Will return `Err(Error::ReservedForLegislativeUse()` if the record number == 0x00 or 0xF0 pub fn new(record_number: u8) -> Result { if record_number == 0 || record_number == 0xF0 { - return Err(Error::ReservedForLegislativeUse( - "DTCStoredDataRecordNumber".to_string(), - record_number, - )); + return Err(Error::ReservedForLegislativeUse(record_number)); } Ok(Self(record_number)) } } -impl WireFormat for DTCStoredDataRecordNumber { - fn required_size(&self) -> usize { - 1 - } - - fn encode(&self, writer: &mut T) -> Result { - writer.write_u8(self.0)?; - Ok(1) - } -} - -impl SingleValueWireFormat for DTCStoredDataRecordNumber { - fn decode(reader: &mut T) -> Result { - let value = reader.read_u8()?; - if value == 0x00 { - // Reserved for Legislative purposes - return Err(Error::ReservedForLegislativeUse( - "DTCStoredDataRecordNumber".to_string(), - value, - )); - } - Ok(Self(value)) - } -} - impl From for DTCStoredDataRecordNumber { fn from(value: u8) -> Self { Self(value) @@ -468,40 +439,6 @@ pub struct DTCSeverityRecord { pub dtc_status_mask: DTCStatusMask, } -impl WireFormat for DTCSeverityRecord { - fn required_size(&self) -> usize { - 6 - } - - fn encode(&self, writer: &mut T) -> Result { - writer.write_u8(self.severity.bits())?; - writer.write_u8(self.functional_group_identifier.value())?; - self.dtc_record.encode(writer)?; - self.dtc_status_mask.encode(writer)?; - Ok(self.required_size()) - } -} - -impl IterableWireFormat for DTCSeverityRecord { - fn decode_next(reader: &mut T) -> Result, Error> { - let Ok(sev) = reader.read_u8() else { - return Ok(None); - }; - - let severity = DTCSeverityMask::from(sev); - let functional_group_identifier = FunctionalGroupIdentifier::from(reader.read_u8()?); - let dtc_record = DTCRecord::decode(reader)?; - let dtc_status_mask = DTCStatusMask::from(reader.read_u8()?); - - Ok(Some(Self { - severity, - functional_group_identifier, - dtc_record, - dtc_status_mask, - })) - } -} - #[cfg(test)] mod dtc_status_tests { use super::*; @@ -531,11 +468,13 @@ mod dtc_status_tests { } #[test] - fn dtc_record() { + fn dtc_record_encode_decode() { let record = DTCRecord::new(0x01, 0x02, 0x03); - let mut writer = Vec::new(); - let written_number = record.encode(&mut writer).unwrap(); - assert_eq!(record.required_size(), 3); - assert_eq!(written_number, 3); + let mut buf = [0u8; 3]; + let written = Encode::encode(&record, &mut buf.as_mut_slice()).unwrap(); + assert_eq!(written, 3); + let (decoded, rest) = ::decode(&buf).unwrap(); + assert_eq!(decoded, record); + assert!(rest.is_empty()); } } diff --git a/src/common/format_identifiers.rs b/src/common/format_identifiers.rs index b71ae02..4bf3331 100644 --- a/src/common/format_identifiers.rs +++ b/src/common/format_identifiers.rs @@ -1,5 +1,4 @@ -use crate::{Error, SingleValueWireFormat, WireFormat}; -use byteorder::{ReadBytesExt, WriteBytesExt}; +use crate::Error; const LOW_NIBBLE_MASK: u8 = 0b0000_1111; const HIGH_NIBBLE_MASK: u8 = 0b1111_0000; @@ -30,19 +29,6 @@ pub(crate) struct MemoryFormatIdentifier { } impl MemoryFormatIdentifier { - /// Takes in the actual memory address to be used and the size of the memory to be used - /// and computes how many bytes are needed to represent them - #[allow(clippy::cast_possible_truncation)] - pub fn from_values(memory_size: u32, memory_address: u64) -> Self { - let memory_address_length = (u64::BITS - memory_address.leading_zeros()).div_ceil(8) as u8; - let memory_size_length = (u32::BITS - memory_size.leading_zeros()).div_ceil(8) as u8; - - Self { - memory_size_length, - memory_address_length, - } - } - /// Get the total length of the `memory_size` and `memory_address` fields pub fn len(self) -> usize { self.memory_size_length as usize + self.memory_address_length as usize @@ -160,24 +146,6 @@ impl PartialEq for DataFormatIdentifier { } } -impl WireFormat for DataFormatIdentifier { - fn required_size(&self) -> usize { - 1 - } - - fn encode(&self, writer: &mut T) -> Result { - writer.write_u8(u8::from(*self))?; - Ok(1) - } -} - -impl SingleValueWireFormat for DataFormatIdentifier { - fn decode(reader: &mut T) -> Result { - let value = reader.read_u8()?; - Ok(DataFormatIdentifier::from(value)) - } -} - #[cfg(test)] mod tests { use super::*; diff --git a/src/common/primitive_generics.rs b/src/common/primitive_generics.rs index 49f16f8..2a580a7 100644 --- a/src/common/primitive_generics.rs +++ b/src/common/primitive_generics.rs @@ -1,95 +1,104 @@ -use crate::{Error, SingleValueWireFormat, WireFormat}; -use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt}; +use crate::{Decode, Encode, Error}; -/// Implement [`WireFormat`] and [`SingleValueWireFormat`] for unsigned integer primitives. -#[macro_export] -macro_rules! unsigned_primitive_wire_format { +/// Implement [`Encode`] and [`Decode`] for unsigned integer primitives (no_std-compatible). +macro_rules! unsigned_primitive_encode_decode { ( $($primitive:ty), * ) => { $( - impl WireFormat for $primitive { - fn required_size(&self) -> usize { - std::mem::size_of::<$primitive>() + impl Encode for $primitive { + fn encoded_size(&self) -> usize { + core::mem::size_of::<$primitive>() } - fn encode(&self, writer: &mut W) -> Result { - writer.write_uint128::(u128::from(*self), self.required_size())?; - Ok(self.required_size()) + fn encode(&self, writer: &mut impl embedded_io::Write) -> Result { + writer.write_all(&self.to_be_bytes()).map_err(Error::io)?; + Ok(self.encoded_size()) } } - impl SingleValueWireFormat for $primitive { - fn decode(reader: &mut T) -> Result { - let value: $primitive = reader - .read_uint128::(std::mem::size_of::<$primitive>())? - .try_into() - .expect("Failed to convert value to the target primitive type"); - Ok(value) + impl<'a> Decode<'a> for $primitive { + fn decode(buf: &'a [u8]) -> Result<(Self, &'a [u8]), Error> { + const SIZE: usize = core::mem::size_of::<$primitive>(); + if buf.len() < SIZE { + return Err(Error::InsufficientData(SIZE)); + } + let (head, tail) = buf.split_at(SIZE); + let value = <$primitive>::from_be_bytes(head.try_into().unwrap()); + Ok((value, tail)) } } - )* + )* }; } -unsigned_primitive_wire_format!(u8, u16, u32, u64, u128); +unsigned_primitive_encode_decode!(u8, u16, u32, u64, u128); -/// Implement [`WireFormat`] and [`SingleValueWireFormat`] for signed integer primitives. -#[macro_export] -macro_rules! signed_primitive_wire_format { +/// Implement [`Encode`] and [`Decode`] for signed integer primitives (no_std-compatible). +macro_rules! signed_primitive_encode_decode { ( $($primitive:ty), * ) => { $( - impl WireFormat for $primitive { - fn required_size(&self) -> usize { - std::mem::size_of::<$primitive>() + impl Encode for $primitive { + fn encoded_size(&self) -> usize { + core::mem::size_of::<$primitive>() } - fn encode(&self, writer: &mut W) -> Result { - writer.write_int128::(i128::from(*self), self.required_size())?; - Ok(self.required_size()) + fn encode(&self, writer: &mut impl embedded_io::Write) -> Result { + writer.write_all(&self.to_be_bytes()).map_err(Error::io)?; + Ok(self.encoded_size()) } } - impl SingleValueWireFormat for $primitive { - fn decode(reader: &mut T) -> Result { - let value: $primitive = reader - .read_int128::(std::mem::size_of::<$primitive>())? - .try_into() - .expect("Failed to convert value to the target primitive type"); - Ok(value) + impl<'a> Decode<'a> for $primitive { + fn decode(buf: &'a [u8]) -> Result<(Self, &'a [u8]), Error> { + const SIZE: usize = core::mem::size_of::<$primitive>(); + if buf.len() < SIZE { + return Err(Error::InsufficientData(SIZE)); + } + let (head, tail) = buf.split_at(SIZE); + let value = <$primitive>::from_be_bytes(head.try_into().unwrap()); + Ok((value, tail)) } } - )* + )* }; } -signed_primitive_wire_format!(i8, i16, i32, i64, i128); +signed_primitive_encode_decode!(i8, i16, i32, i64, i128); -impl WireFormat for f32 { - fn required_size(&self) -> usize { +impl Encode for f32 { + fn encoded_size(&self) -> usize { 4 } - fn encode(&self, writer: &mut W) -> Result { - writer.write_f32::(*self)?; - Ok(self.required_size()) + fn encode(&self, writer: &mut impl embedded_io::Write) -> Result { + writer.write_all(&self.to_be_bytes()).map_err(Error::io)?; + Ok(4) } } -impl SingleValueWireFormat for f32 { - fn decode(reader: &mut T) -> Result { - let value: f32 = reader.read_f32::()?; - Ok(value) +impl<'a> Decode<'a> for f32 { + fn decode(buf: &'a [u8]) -> Result<(Self, &'a [u8]), Error> { + if buf.len() < 4 { + return Err(Error::InsufficientData(4)); + } + let (head, tail) = buf.split_at(4); + let value = f32::from_be_bytes(head.try_into().unwrap()); + Ok((value, tail)) } } -impl WireFormat for f64 { - fn required_size(&self) -> usize { +impl Encode for f64 { + fn encoded_size(&self) -> usize { 8 } - fn encode(&self, writer: &mut W) -> Result { - writer.write_f64::(*self)?; - Ok(self.required_size()) + fn encode(&self, writer: &mut impl embedded_io::Write) -> Result { + writer.write_all(&self.to_be_bytes()).map_err(Error::io)?; + Ok(8) } } -impl SingleValueWireFormat for f64 { - fn decode(reader: &mut T) -> Result { - let value: f64 = reader.read_f64::()?; - Ok(value) +impl<'a> Decode<'a> for f64 { + fn decode(buf: &'a [u8]) -> Result<(Self, &'a [u8]), Error> { + if buf.len() < 8 { + return Err(Error::InsufficientData(8)); + } + let (head, tail) = buf.split_at(8); + let value = f64::from_be_bytes(head.try_into().unwrap()); + Ok((value, tail)) } } @@ -98,80 +107,55 @@ mod tests { use super::*; #[test] - fn test_u8() { - // Read some bytes - let data = vec![0xFF]; - let mut reader = &data[..]; - - let u8_byte = u8::decode(&mut reader).unwrap(); - assert_eq!(u8_byte, 0xFF); - assert_eq!(u8_byte.required_size(), 1); - - let mut write_buffer = vec![]; - u8_byte.encode(&mut write_buffer).unwrap(); - assert_eq!(write_buffer, data); + fn test_u8_encode_decode() { + let val: u8 = 0xFF; + let mut buf = [0u8; 1]; + Encode::encode(&val, &mut buf.as_mut_slice()).unwrap(); + assert_eq!(buf, [0xFF]); + let (decoded, rest) = ::decode(&buf).unwrap(); + assert_eq!(decoded, 0xFF); + assert!(rest.is_empty()); } #[test] - fn test_u16() { - // Read some bytes - let data = vec![0xFF, 0x01]; - let mut reader = &data[..]; - - let u16_byte = u16::decode(&mut reader).unwrap(); - assert_eq!(u16_byte, 0xFF01); - assert_eq!(u16_byte.required_size(), 2); - - let mut write_buffer = vec![]; - u16_byte.encode(&mut write_buffer).unwrap(); - assert_eq!(write_buffer, data); + fn test_u16_encode_decode() { + let val: u16 = 0xFF01; + let mut buf = [0u8; 2]; + Encode::encode(&val, &mut buf.as_mut_slice()).unwrap(); + assert_eq!(buf, [0xFF, 0x01]); + let (decoded, rest) = ::decode(&buf).unwrap(); + assert_eq!(decoded, 0xFF01); + assert!(rest.is_empty()); } #[test] - fn test_u32() { - // Read some bytes - let data = vec![0xFF, 0x20, 0x02, 0x01]; - let mut reader = &data[..]; - - let u32_byte = u32::decode(&mut reader).unwrap(); - assert_eq!(u32_byte, 0xFF20_0201); - assert_eq!(u32_byte.required_size(), 4); - - let mut write_buffer = vec![]; - u32_byte.encode(&mut write_buffer).unwrap(); - assert_eq!(write_buffer, data); + fn test_u32_encode_decode() { + let val: u32 = 0xFF20_0201; + let mut buf = [0u8; 4]; + Encode::encode(&val, &mut buf.as_mut_slice()).unwrap(); + assert_eq!(buf, [0xFF, 0x20, 0x02, 0x01]); + let (decoded, rest) = ::decode(&buf).unwrap(); + assert_eq!(decoded, 0xFF20_0201); + assert!(rest.is_empty()); } #[test] - fn test_u64() { - // Read some bytes - let data = vec![0xFF, 0x20, 0x02, 0x01, 0xFF, 0x20, 0x02, 0x01]; - let mut reader = &data[..]; - - let u64_byte = u64::decode(&mut reader).unwrap(); - assert_eq!(u64_byte, 0xFF20_0201_FF20_0201); - assert_eq!(u64_byte.required_size(), 8); - - let mut write_buffer = vec![]; - u64_byte.encode(&mut write_buffer).unwrap(); - assert_eq!(write_buffer, data); + fn test_u64_encode_decode() { + let val: u64 = 0xFF20_0201_FF20_0201; + let mut buf = [0u8; 8]; + Encode::encode(&val, &mut buf.as_mut_slice()).unwrap(); + let (decoded, rest) = ::decode(&buf).unwrap(); + assert_eq!(decoded, val); + assert!(rest.is_empty()); } #[test] - fn test_u128() { - // Read some bytes - let data = vec![ - 0xFF, 0x20, 0x02, 0x01, 0xFF, 0x20, 0x02, 0x01, 0xFF, 0x20, 0x02, 0x01, 0xFF, 0x20, - 0x02, 0x01, - ]; - let mut reader = &data[..]; - - let u128_byte = u128::decode(&mut reader).unwrap(); - assert_eq!(u128_byte, 0xFF20_0201_FF20_0201_FF20_0201_FF20_0201); - assert_eq!(u128_byte.required_size(), 16); - - let mut write_buffer = vec![]; - u128_byte.encode(&mut write_buffer).unwrap(); - assert_eq!(write_buffer, data); + fn test_u128_encode_decode() { + let val: u128 = 0xFF20_0201_FF20_0201_FF20_0201_FF20_0201; + let mut buf = [0u8; 16]; + Encode::encode(&val, &mut buf.as_mut_slice()).unwrap(); + let (decoded, rest) = ::decode(&buf).unwrap(); + assert_eq!(decoded, val); + assert!(rest.is_empty()); } } diff --git a/src/error.rs b/src/error.rs index 9e8a1d0..20fd371 100644 --- a/src/error.rs +++ b/src/error.rs @@ -5,8 +5,8 @@ use thiserror::Error; #[non_exhaustive] pub enum Error { /// An underlying I/O error occurred while reading or writing. - #[error(transparent)] - IoError(#[from] std::io::Error), + #[error("I/O error: {0:?}")] + IoError(embedded_io::ErrorKind), /// The byte stream contained fewer bytes than expected. #[error("Insufficient data. Expected {0} bytes.")] InsufficientData(usize), @@ -14,8 +14,8 @@ pub enum Error { #[error("Invalid Diagnostic Identifier: {0:X}")] InvalidDiagnosticIdentifier(u16), /// The u16 identifier is unrecognised and carried an unexpected payload. - #[error("Invalid Diagnostic Identifier: {0:X} with payload {1:?}")] - InvalidDiagnosticIdentifierPayload(u16, Vec), + #[error("Invalid Diagnostic Identifier with payload: {0:X}")] + InvalidDiagnosticIdentifierPayload(u16), /// The session-type byte is not a valid [`DiagnosticSessionType`](crate::DiagnosticSessionType). #[error("Invalid diagnostic session type: {0}")] InvalidDiagnosticSessionType(u8), @@ -59,9 +59,31 @@ pub enum Error { #[error("Invalid DTC Format Identifier: {0}")] InvalidDtcFormatIdentifier(u8), /// The value is reserved for legislative use and must not be used. - #[error("Reserved for legislative use: {0} ({1})")] - ReservedForLegislativeUse(String, u8), + #[error("Reserved for legislative use: {0}")] + ReservedForLegislativeUse(u8), /// The service type is not yet implemented in this crate. #[error("UDS service not implemented: {0:?}")] ServiceNotImplemented(crate::UdsServiceType), } + +impl Error { + /// Convert any `embedded_io::Error` into [`Error::IoError`]. + #[inline] + #[allow(clippy::needless_pass_by_value)] + pub(crate) fn io(e: E) -> Self { + Self::IoError(e.kind()) + } +} + +impl From for Error { + fn from(kind: embedded_io::ErrorKind) -> Self { + Self::IoError(kind) + } +} + +#[cfg(feature = "std")] +impl From for Error { + fn from(_err: std::io::Error) -> Self { + Self::IoError(embedded_io::ErrorKind::Other) + } +} diff --git a/src/lib.rs b/src/lib.rs index 2589d00..83910a4 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,19 +1,21 @@ #![doc = include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/README.md"))] #![warn(clippy::pedantic, missing_docs)] +#![cfg_attr(not(feature = "std"), no_std)] + +#[cfg(feature = "alloc")] +extern crate alloc; + mod error; pub use error::Error; mod traits; -pub use traits::{ - DiagnosticDefinition, Identifier, IterableWireFormat, RoutineIdentifier, SingleValueWireFormat, - WireFormat, -}; +pub use traits::{Decode, DecodeIter, DiagnosticDefinition, Encode, Identifier, RoutineIdentifier}; mod common; pub use common::*; mod protocol_definitions; -pub use protocol_definitions::{ProtocolIdentifier, ProtocolPayload, ProtocolRoutinePayload}; +pub use protocol_definitions::{ProtocolIdentifier, ProtocolPayloadTx, ProtocolRoutinePayloadTx}; mod request; pub use request::Request; @@ -41,19 +43,14 @@ pub const PENDING: u8 = 0x78; #[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))] #[derive(Clone, Copy, Debug, Eq, PartialEq)] pub struct UdsSpec; + impl DiagnosticDefinition for UdsSpec { type RID = UDSRoutineIdentifier; type DID = ProtocolIdentifier; - type RoutinePayload = ProtocolRoutinePayload; - type DiagnosticPayload = ProtocolPayload; + type RoutinePayload = ProtocolRoutinePayloadTx<'static>; + type DiagnosticPayload = ProtocolPayloadTx<'static>; } -/// Type alias for a UDS Request type that only implements the messages explicitly defined by the UDS specification. -pub type ProtocolRequest = Request; - -/// Type alias for a UDS Response type that only implements the messages explicitly defined by the UDS specification. -pub type ProtocolResponse = Response; - /// What type of routine control to perform for a [`RoutineControlRequest`]. #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] #[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))] @@ -86,44 +83,18 @@ impl From for u8 { } } -impl From for RoutineControlSubFunction { - fn from(value: u8) -> Self { +impl TryFrom for RoutineControlSubFunction { + type Error = Error; + fn try_from(value: u8) -> Result { match value { - 0x01 => RoutineControlSubFunction::StartRoutine, - 0x02 => RoutineControlSubFunction::StopRoutine, - 0x03 => RoutineControlSubFunction::RequestRoutineResults, - _ => panic!("Invalid routine control subfunction: {value}"), + 0x01 => Ok(RoutineControlSubFunction::StartRoutine), + 0x02 => Ok(RoutineControlSubFunction::StopRoutine), + 0x03 => Ok(RoutineControlSubFunction::RequestRoutineResults), + _ => Err(Error::IncorrectMessageLengthOrInvalidFormat), } } } -impl WireFormat for Vec { - fn required_size(&self) -> usize { - self.len() - } - - fn encode(&self, writer: &mut T) -> Result { - writer.write_all(self)?; - Ok(self.len()) - } -} - -impl SingleValueWireFormat for Vec { - fn decode(reader: &mut T) -> Result { - let mut data = Vec::new(); - reader.read_to_end(&mut data)?; - Ok(data) - } -} - -impl IterableWireFormat for Vec { - fn decode_next(reader: &mut T) -> Result, Error> { - let mut data = Vec::new(); - reader.read_to_end(&mut data)?; - Ok(Some(data)) - } -} - #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] #[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))] #[cfg_attr(feature = "clap", derive(clap::ValueEnum))] @@ -148,12 +119,136 @@ impl From for u8 { } } -impl From for DtcSettings { - fn from(value: u8) -> Self { +impl TryFrom for DtcSettings { + type Error = Error; + 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::IncorrectMessageLengthOrInvalidFormat), } } } + +#[cfg(test)] +mod no_std_api_tests { + use super::*; + + #[test] + fn encode_decode_tester_present_roundtrip() { + let req = TesterPresentRequest::new(false); + let mut buf = [0u8; 8]; + let written = Encode::encode(&req, &mut buf.as_mut_slice()).unwrap(); + assert_eq!(written, 1); + + let (decoded, rest) = ::decode(&buf[..written]).unwrap(); + assert_eq!(decoded, req); + assert!(rest.is_empty()); + } + + #[test] + fn encode_decode_transfer_data_tx_roundtrip() { + let data = [0x01, 0x02, 0x03, 0x04]; + let req = TransferDataRequestTx::new(0x05, &data); + let mut buf = [0u8; 16]; + let written = Encode::encode(&req, &mut buf.as_mut_slice()).unwrap(); + assert_eq!(written, 5); + + let (decoded, _) = ::decode(&buf[..written]).unwrap(); + assert_eq!(decoded.block_sequence_counter, 0x05); + assert_eq!(decoded.data, &[0x01, 0x02, 0x03, 0x04]); + } + + #[test] + fn decode_response_tester_present() { + // TesterPresent response: SID=0x7E, sub=0x00 + let wire = [0x7E, 0x00]; + let (resp, _) = Response::decode(&wire).unwrap(); + assert!(matches!(resp, Response::TesterPresent(_))); + } + + #[test] + fn decode_response_negative() { + // NegativeResponse: SID=0x7F, service=0x10, NRC=0x12 + let wire = [0x7F, 0x10, 0x12]; + let (resp, _) = Response::decode(&wire).unwrap(); + assert!(matches!(resp, Response::NegativeResponse(_))); + } + + #[test] + fn decode_request_ecu_reset() { + // EcuReset request: SID=0x11, sub=0x01 (HardReset) + let wire = [0x11, 0x01]; + let (req, _) = Request::decode(&wire).unwrap(); + assert!(matches!(req, Request::EcuReset(_))); + assert_eq!(req.service(), UdsServiceType::EcuReset); + } + + #[test] + fn dtc_and_status_iter_roundtrip() { + // 2 DTC records: (0x01,0x02,0x03, status=0x0A), (0x04,0x05,0x06, status=0x0B) + let data = [0x01, 0x02, 0x03, 0x0A, 0x04, 0x05, 0x06, 0x0B]; + let iter = DtcAndStatusIter::new(&data); + assert_eq!(iter.len(), 2); + + let records: Vec<_> = iter.map(|r| r.unwrap()).collect(); + assert_eq!(records.len(), 2); + assert_eq!(u32::from(records[0].0), 0x010203); + assert_eq!(u32::from(records[1].0), 0x040506); + } + + #[test] + fn request_frame_roundtrip_prepends_sid() { + // EcuReset request: SID=0x11, sub=0x01 + let wire = [0x11, 0x01]; + let (req, _) = Request::decode(&wire).unwrap(); + let mut buf = [0u8; 8]; + let written = Encode::encode(&req, &mut buf.as_mut_slice()).unwrap(); + assert_eq!(&buf[..written], &wire); + assert_eq!(written, req.encoded_size()); + } + + #[test] + fn response_frame_roundtrip_prepends_sid() { + // NegativeResponse: SID=0x7F, service=0x10, NRC=0x12 + let wire = [0x7F, 0x10, 0x12]; + let (resp, _) = Response::decode(&wire).unwrap(); + let mut buf = [0u8; 8]; + let written = Encode::encode(&resp, &mut buf.as_mut_slice()).unwrap(); + assert_eq!(&buf[..written], &wire); + assert_eq!(written, resp.encoded_size()); + } + + #[test] + fn request_file_transfer_frame_roundtrip() { + // RequestFileTransfer: SID=0x38, DeleteFile(0x02), name_len=0x0003, "abc" + let wire = [0x38, 0x02, 0x00, 0x03, b'a', b'b', b'c']; + let (req, _) = Request::decode(&wire).unwrap(); + assert_eq!(req.service(), UdsServiceType::RequestFileTransfer); + let mut buf = [0u8; 16]; + let written = Encode::encode(&req, &mut buf.as_mut_slice()).unwrap(); + assert_eq!(&buf[..written], &wire); + } + + #[test] + fn read_dtc_info_response_frame_roundtrip() { + // ReadDTCInfo response: SID=0x59, sub=0x02, mask=0xFF, then DTC records + let wire = [0x59, 0x02, 0xFF, 0x01, 0x02, 0x03, 0x0A]; + let (resp, _) = Response::decode(&wire).unwrap(); + let mut buf = [0u8; 16]; + let written = Encode::encode(&resp, &mut buf.as_mut_slice()).unwrap(); + assert_eq!(&buf[..written], &wire); + } + + #[test] + fn const_construction() { + // Verify const construction works at compile time + const _REQ: TransferDataRequestTx<'static> = + TransferDataRequestTx::new(1, &[0x01, 0x02, 0x03]); + const _SEC: SecurityAccessRequestTx<'static> = SecurityAccessRequestTx::new( + false, + SecurityAccessType::RequestSeed(0x01), + &[0xAA, 0xBB], + ); + } +} diff --git a/src/protocol_definitions.rs b/src/protocol_definitions.rs index 8c75718..d3bca4d 100644 --- a/src/protocol_definitions.rs +++ b/src/protocol_definitions.rs @@ -1,9 +1,7 @@ use crate::{ - Error, IterableWireFormat, SingleValueWireFormat, UDSIdentifier, UDSRoutineIdentifier, - WireFormat, impl_identifier, + Decode, DecodeIter, Encode, Error, UDSIdentifier, UDSRoutineIdentifier, impl_identifier, }; -use std::ops::Deref; -use tracing::error; +use core::ops::Deref; /// Protocol Identifier provides an implementation of Diagnostics Identifiers that only supports Diagnostic Identifiers defined by UDS #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] @@ -20,14 +18,6 @@ impl ProtocolIdentifier { pub fn new(identifier: UDSIdentifier) -> Self { ProtocolIdentifier { identifier } } - - /// Convert an iterator of [`UDSIdentifier`]s into a `Vec`. - pub fn identifiers(list: I) -> Vec - where - I: IntoIterator, - { - list.into_iter().map(Self::new).collect() - } } impl TryFrom for ProtocolIdentifier { @@ -52,131 +42,87 @@ impl Deref for ProtocolIdentifier { } } -/// The UDS protocol does not define the structure of any payload, but exists as a container for diagnostic implementations that use the generic UDS identifiers -#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] -#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))] -#[derive(Clone, Eq, PartialEq)] -#[non_exhaustive] -pub struct ProtocolPayload { +/// Zero-alloc protocol payload. Borrows the raw payload bytes. +#[derive(Clone, Copy, Eq, PartialEq)] +pub struct ProtocolPayloadTx<'d> { /// The UDS data identifier this payload belongs to. pub identifier: UDSIdentifier, /// The raw payload bytes following the identifier. - pub payload: Vec, + pub payload: &'d [u8], } -impl ProtocolPayload { - /// Creates a new `ProtocolPayload` with the given identifier and payload +impl<'d> ProtocolPayloadTx<'d> { + /// Creates a new `ProtocolPayloadTx`. #[must_use] - pub fn new(identifier: UDSIdentifier, payload: Vec) -> Self { - ProtocolPayload { + pub const fn new(identifier: UDSIdentifier, payload: &'d [u8]) -> Self { + Self { identifier, payload, } } } -impl WireFormat for ProtocolPayload { - fn required_size(&self) -> usize { + +impl Encode for ProtocolPayloadTx<'_> { + fn encoded_size(&self) -> usize { 2 + self.payload.len() } - fn encode(&self, writer: &mut T) -> Result { - self.identifier.encode(writer)?; - writer.write_all(&self.payload)?; - Ok(self.required_size()) + fn encode(&self, writer: &mut impl embedded_io::Write) -> Result { + Encode::encode(&self.identifier, writer)?; + writer.write_all(self.payload).map_err(Error::io)?; + Ok(self.encoded_size()) } } -impl SingleValueWireFormat for ProtocolPayload { - fn decode(reader: &mut T) -> Result { - let mut identifier_data: [u8; 2] = [0; 2]; - match reader.read(&mut identifier_data)? { - 0 | 1 => { - error!( - "Only read 0 or 1 byte of identifier, need 2: read byte was: {}", - identifier_data[0] - ); - return Err(Error::IncorrectMessageLengthOrInvalidFormat); - } - 2 => (), - _ => unreachable!("Impossible to read more than 2 bytes into 2 byte array"), +impl<'a> Decode<'a> for ProtocolPayloadTx<'a> { + fn decode(buf: &'a [u8]) -> Result<(Self, &'a [u8]), Error> { + if buf.len() < 2 { + return Err(Error::IncorrectMessageLengthOrInvalidFormat); } - let identifier = UDSIdentifier::try_from(u16::from_be_bytes(identifier_data))?; - let mut payload: Vec = Vec::new(); - reader.read_to_end(&mut payload)?; - Ok(ProtocolPayload { - identifier, - payload, - }) - } -} - -impl IterableWireFormat for ProtocolPayload { - fn decode_next(reader: &mut T) -> Result, Error> { - let mut identifier_data: [u8; 2] = [0; 2]; - match reader.read(&mut identifier_data)? { - 0 => return Ok(None), - 1 => { - error!( - "Only read 1 byte of identifier, need 2: read byte was: {}", - identifier_data[0] - ); - return Err(Error::IncorrectMessageLengthOrInvalidFormat); - } - 2 => (), - _ => unreachable!("Impossible to read more than 2 bytes into 2 byte array"), + let (identifier, rest) = ::decode(buf)?; + // Consumes all remaining bytes as payload + Ok(( + Self { + identifier, + payload: rest, + }, + &[], + )) + } +} + +impl<'a> DecodeIter<'a> for ProtocolPayloadTx<'a> { + fn decode_next(buf: &'a [u8]) -> Result, Error> { + if buf.is_empty() { + return Ok(None); } - let identifier = UDSIdentifier::try_from(u16::from_be_bytes(identifier_data))?; - // Reads the entire payload, but does not have the ability to determine the amount of bytes to read - // depending on the Identifier, so all data is read until EOF - // - // TODO: We could be more clever, we do know the response size of some identifiers - let mut payload: Vec = Vec::new(); - reader.read_to_end(&mut payload)?; - Ok(Some(ProtocolPayload { - identifier, - payload, - })) + Decode::decode(buf).map(Some) } } -impl std::fmt::Debug for ProtocolPayload { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!( - f, - "{} => {}", - self.identifier, - self.payload - .iter() - .map(|b| format!("{b:02X}")) - .collect::>() - .join(" ") - ) +impl core::fmt::Debug for ProtocolPayloadTx<'_> { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!(f, "{} =>", self.identifier)?; + for b in self.payload { + write!(f, " {b:02X}")?; + } + Ok(()) } } -/// Routine-specific payload for [`UdsSpec`](crate::UdsSpec). -/// -/// Used as the `RoutinePayload` associated type in [`UdsSpec`](crate::UdsSpec). -/// On the wire, the routine identifier is already encoded by the -/// [`RoutineControlRequest`](crate::RoutineControlRequest), so `encode` writes -/// only the raw payload bytes. `decode` reads the identifier first (since -/// [`RoutineControlResponse`](crate::RoutineControlResponse) includes it in the -/// status record) followed by any remaining payload bytes. -#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] -#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))] -#[derive(Clone, Eq, PartialEq)] -#[non_exhaustive] -pub struct ProtocolRoutinePayload { +/// Zero-alloc routine payload. Borrows the raw payload bytes. +#[derive(Clone, Copy, Eq, PartialEq)] +pub struct ProtocolRoutinePayloadTx<'d> { /// The routine identifier this payload belongs to. pub identifier: UDSRoutineIdentifier, /// The raw payload bytes following the identifier. - pub payload: Vec, + pub payload: &'d [u8], } -impl ProtocolRoutinePayload { - /// Creates a new `ProtocolRoutinePayload` with the given identifier and payload. +impl<'d> ProtocolRoutinePayloadTx<'d> { + /// Creates a new `ProtocolRoutinePayloadTx`. #[must_use] - pub fn new(identifier: UDSRoutineIdentifier, payload: Vec) -> Self { + pub const fn new(identifier: UDSRoutineIdentifier, payload: &'d [u8]) -> Self { Self { identifier, payload, @@ -184,81 +130,51 @@ impl ProtocolRoutinePayload { } } -impl WireFormat for ProtocolRoutinePayload { - /// Size of the raw payload only — the identifier is written by the request. - fn required_size(&self) -> usize { +impl Encode for ProtocolRoutinePayloadTx<'_> { + /// Size of the raw payload only -- the identifier is written by the request. + fn encoded_size(&self) -> usize { self.payload.len() } - /// Writes only the raw payload bytes. The routine identifier is already - /// encoded by [`RoutineControlRequest::encode`](crate::RoutineControlRequest). - fn encode(&self, writer: &mut T) -> Result { - writer.write_all(&self.payload)?; + fn encode(&self, writer: &mut impl embedded_io::Write) -> Result { + writer.write_all(self.payload).map_err(Error::io)?; Ok(self.payload.len()) } } -impl SingleValueWireFormat for ProtocolRoutinePayload { - fn decode(reader: &mut T) -> Result { - let mut identifier_data: [u8; 2] = [0; 2]; - match reader.read(&mut identifier_data)? { - 0 | 1 => { - error!( - "Only read 0 or 1 byte of routine identifier, need 2: read byte was: {}", - identifier_data[0] - ); - return Err(Error::IncorrectMessageLengthOrInvalidFormat); - } - 2 => (), - _ => unreachable!("Impossible to read more than 2 bytes into 2 byte array"), +impl<'a> Decode<'a> for ProtocolRoutinePayloadTx<'a> { + fn decode(buf: &'a [u8]) -> Result<(Self, &'a [u8]), Error> { + if buf.len() < 2 { + return Err(Error::IncorrectMessageLengthOrInvalidFormat); } - let identifier = UDSRoutineIdentifier::from(u16::from_be_bytes(identifier_data)); - let mut payload: Vec = Vec::new(); - reader.read_to_end(&mut payload)?; - Ok(Self { - identifier, - payload, - }) - } -} - -impl IterableWireFormat for ProtocolRoutinePayload { - fn decode_next(reader: &mut T) -> Result, Error> { - let mut identifier_data: [u8; 2] = [0; 2]; - match reader.read(&mut identifier_data)? { - 0 => return Ok(None), - 1 => { - error!( - "Only read 1 byte of routine identifier, need 2: read byte was: {}", - identifier_data[0] - ); - return Err(Error::IncorrectMessageLengthOrInvalidFormat); - } - 2 => (), - _ => unreachable!("Impossible to read more than 2 bytes into 2 byte array"), + let raw = u16::from_be_bytes([buf[0], buf[1]]); + let identifier = UDSRoutineIdentifier::from(raw); + Ok(( + Self { + identifier, + payload: &buf[2..], + }, + &[], + )) + } +} + +impl<'a> DecodeIter<'a> for ProtocolRoutinePayloadTx<'a> { + fn decode_next(buf: &'a [u8]) -> Result, Error> { + if buf.is_empty() { + return Ok(None); } - let identifier = UDSRoutineIdentifier::from(u16::from_be_bytes(identifier_data)); - let mut payload: Vec = Vec::new(); - reader.read_to_end(&mut payload)?; - Ok(Some(Self { - identifier, - payload, - })) + Decode::decode(buf).map(Some) } } -impl std::fmt::Debug for ProtocolRoutinePayload { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!( - f, - "{:?} => {}", - self.identifier, - self.payload - .iter() - .map(|b| format!("{b:02X}")) - .collect::>() - .join(" ") - ) +impl core::fmt::Debug for ProtocolRoutinePayloadTx<'_> { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!(f, "{:?} =>", self.identifier)?; + for b in self.payload { + write!(f, " {b:02X}")?; + } + Ok(()) } } @@ -269,18 +185,20 @@ mod tests { #[test] fn test_construction_and_debug_format() { - let payload = ProtocolPayload::new(UDSIdentifier::ActiveDiagnosticSession, vec![0x01]); + let payload = ProtocolPayloadTx::new(UDSIdentifier::ActiveDiagnosticSession, &[0x01]); assert_eq!(format!("{payload:?}"), "0xF186 => 01"); - let mut buffer = Vec::new(); - assert_eq!(3, payload.encode(&mut buffer).unwrap()); + let mut buf = [0u8; 8]; + let written = Encode::encode(&payload, &mut buf.as_mut_slice()).unwrap(); + assert_eq!(written, 3); } #[test] - fn test_read_and_write() { - let payload = ProtocolPayload::new(UDSIdentifier::ActiveDiagnosticSession, vec![0x03]); - let mut buffer = Vec::new(); - assert_eq!(3, payload.encode(&mut buffer).unwrap()); - let deserialized_payload = ProtocolPayload::decode(&mut buffer.as_slice()).unwrap(); - assert_eq!(payload, deserialized_payload); + fn test_encode_and_decode() { + let payload = ProtocolPayloadTx::new(UDSIdentifier::ActiveDiagnosticSession, &[0x03]); + let mut buf = [0u8; 8]; + let written = Encode::encode(&payload, &mut buf.as_mut_slice()).unwrap(); + assert_eq!(written, 3); + let (decoded, _) = ProtocolPayloadTx::decode(&buf[..written]).unwrap(); + assert_eq!(payload, decoded); } } diff --git a/src/request.rs b/src/request.rs index 3d71b82..5d9f12a 100644 --- a/src/request.rs +++ b/src/request.rs @@ -1,554 +1,205 @@ //! Module for making and handling UDS Requests use crate::{ - DiagnosticDefinition, Error, NegativeResponseCode, ReadDTCInfoRequest, ResetType, - SecurityAccessType, SingleValueWireFormat, WireFormat, + Decode, Encode, Error, services::{ ClearDiagnosticInfoRequest, CommunicationControlRequest, ControlDTCSettingsRequest, - DiagnosticSessionControlRequest, EcuResetRequest, ReadDataByIdentifierRequest, - RequestDownloadRequest, RoutineControlRequest, SecurityAccessRequest, TesterPresentRequest, - TransferDataRequest, WriteDataByIdentifierRequest, + DiagnosticSessionControlRequest, EcuResetRequest, RequestDownloadRequest, + RequestFileTransferRequestTx, SecurityAccessRequestTx, TesterPresentRequest, + TransferDataRequestTx, }, }; -use byteorder::{ReadBytesExt, WriteBytesExt}; -use std::io::{Read, Write}; -use super::{ - CommunicationControlType, CommunicationType, DTCRecord, DataFormatIdentifier, - DiagnosticSessionType, DtcSettings, ReadDTCInfoSubFunction, RoutineControlSubFunction, - service::UdsServiceType, -}; +use super::service::UdsServiceType; -/// UDS Request types -/// Each variant corresponds to a request for a different UDS service -/// The variants contain all request data for each service -#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] -#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))] -#[derive(Clone, Debug, PartialEq)] +/// Zero-copy RX request. Borrows from the wire buffer. +/// +/// Variable-length payloads are stored as raw `&'a [u8]` slices that can be +/// further parsed on demand. +#[derive(Clone, Debug)] #[non_exhaustive] -pub enum Request { - /// Request to clear diagnostic information. See [`ClearDiagnosticInfoRequest`]. +pub enum Request<'a> { + /// Clear diagnostic information request. ClearDiagnosticInfo(ClearDiagnosticInfoRequest), - /// Request to control communication. See [`CommunicationControlRequest`]. + /// Communication control request. CommunicationControl(CommunicationControlRequest), - /// Request to enable or disable DTC setting. See [`ControlDTCSettingsRequest`]. + /// Control DTC settings request. ControlDTCSettings(ControlDTCSettingsRequest), - /// Request to change the diagnostic session. See [`DiagnosticSessionControlRequest`]. + /// Diagnostic session control request. DiagnosticSessionControl(DiagnosticSessionControlRequest), - /// Request to reset the ECU. See [`EcuResetRequest`]. + /// ECU reset request. EcuReset(EcuResetRequest), - /// Request to read data by identifier. See [`ReadDataByIdentifierRequest`]. - ReadDataByIdentifier(ReadDataByIdentifierRequest), - /// Request to read DTC information. See [`ReadDTCInfoRequest`]. - ReadDTCInfo(ReadDTCInfoRequest), - /// Request to initiate a download. See [`RequestDownloadRequest`]. + /// Read data by identifier request. Raw DID bytes. + ReadDataByIdentifier(&'a [u8]), + /// Read DTC information request. Raw sub-function + parameter bytes. + ReadDTCInfo(&'a [u8]), + /// Request download. RequestDownload(RequestDownloadRequest), - /// Request to exit an active transfer. + /// Request file transfer. + RequestFileTransfer(RequestFileTransferRequestTx<'a>), + /// Request transfer exit. RequestTransferExit, - /// Request to control a routine. See [`RoutineControlRequest`]. - RoutineControl(RoutineControlRequest), - /// Request for security access. See [`SecurityAccessRequest`]. - SecurityAccess(SecurityAccessRequest), - /// Tester present keep-alive request. See [`TesterPresentRequest`]. + /// Routine control request. Sub-function byte + raw payload. + RoutineControl { + /// Routine control sub-function byte. + sub_function: u8, + /// Raw routine ID + optional payload bytes. + raw_payload: &'a [u8], + }, + /// Security access request. + SecurityAccess(SecurityAccessRequestTx<'a>), + /// Tester present request. TesterPresent(TesterPresentRequest), - /// Request to transfer data. See [`TransferDataRequest`]. - TransferData(TransferDataRequest), - /// Request to write data by identifier. See [`WriteDataByIdentifierRequest`]. - WriteDataByIdentifier(WriteDataByIdentifierRequest), + /// Transfer data request. + TransferData(TransferDataRequestTx<'a>), + /// Write data by identifier request. Raw DID + payload bytes. + WriteDataByIdentifier(&'a [u8]), } -impl Request { - /// Create a `ClearDiagnosticInfo` request, clears diagnostic information in one or more servers' memory - #[must_use] - pub fn clear_diagnostic_info(group_of_dtc: DTCRecord, memory_selection: u8) -> Self { - Request::ClearDiagnosticInfo(ClearDiagnosticInfoRequest::new( - group_of_dtc, - memory_selection, - )) - } - /// Create a `ClearDiagnosticInfo` request that clears all DTC information in one or more servers' memory - #[must_use] - pub fn clear_all_dtc_info(memory_selection: u8) -> Self { - Request::ClearDiagnosticInfo(ClearDiagnosticInfoRequest::clear_all(memory_selection)) - } - - /// Create a `CommunicationControlRequest` with standard address information. - /// - /// # Panics - /// - /// Panics if one of the extended address control types is passed. - #[must_use] - pub fn communication_control( - communication_enable: CommunicationControlType, - communication_type: CommunicationType, - suppress_response: bool, - ) -> Self { - Request::CommunicationControl(CommunicationControlRequest::new( - suppress_response, - communication_enable, - communication_type, - )) - } - - /// Create a `CommunicationControl` request with extended address information. - /// This is used for the `EnableRxAndDisableTxWithEnhancedAddressInfo` and - /// `EnableRxAndTxWithEnhancedAddressInfo` communication control types. - /// - /// # Panics - /// - /// Panics if one of the standard address control types is passed. - #[must_use] - pub fn communication_control_with_node_id( - communication_enable: CommunicationControlType, - communication_type: CommunicationType, - node_id: u16, - suppress_response: bool, - ) -> Self { - Request::CommunicationControl(CommunicationControlRequest::new_with_node_id( - suppress_response, - communication_enable, - communication_type, - node_id, - )) - } - - /// 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)) - } - - /// Create a new `DiagnosticSessionControl` request - #[must_use] - pub fn diagnostic_session_control( - suppress_positive_response: bool, - session_type: DiagnosticSessionType, - ) -> Self { - Request::DiagnosticSessionControl(DiagnosticSessionControlRequest::new( - suppress_positive_response, - session_type, - )) - } - - /// Create a new `EcuReset` request - #[must_use] - pub fn ecu_reset(suppress_positive_response: bool, reset_type: ResetType) -> Self { - Request::EcuReset(EcuResetRequest::new(suppress_positive_response, reset_type)) - } - - /// Create a new `ReadDataByIdentifier` request - pub fn read_data_by_identifier(dids: I) -> Self - where - I: IntoIterator, - { - Request::ReadDataByIdentifier(ReadDataByIdentifierRequest::new(dids)) - } - - /// Create a new `ReadDTCInformation` request for the given sub-function. - #[must_use] - pub fn read_dtc_information(sub_function: ReadDTCInfoSubFunction) -> Self { - Request::ReadDTCInfo(ReadDTCInfoRequest::new(sub_function)) - } - - /// Create a new `RequestDownload` request - /// `encryption_method`: vehicle manufacturer specific (0x0 for no encryption) - /// `compression_method`: vehicle manufacturer specific (0x0 for no compression) - /// `memory_address`: the address in memory to start downloading from (Maximum 40 bits - 1024GB) - /// `memory_size`: the size of the memory to download (Max 4GB) - /// - /// # Errors - /// Will generate an error of type `Error::InvalidEncryptionCompressionMethod()`. - /// Generated when `compression_method` or `encryption_method` > 0x15 - pub fn request_download( - encryption_method: u8, - compression_method: u8, - memory_address: u64, - memory_size: u32, - ) -> Result { - let data_format_identifier = - DataFormatIdentifier::new(compression_method, encryption_method)?; - - Ok(Request::RequestDownload(RequestDownloadRequest::new( - data_format_identifier, - memory_address, - memory_size, - )?)) - } - - /// Create a `RequestTransferExit` request to end an active upload or download. - #[must_use] - pub fn request_transfer_exit() -> Self { - Self::RequestTransferExit - } - - /// Create a new `RoutineControl` request with no payload - /// - /// **Note**: This does not check if the server requires a payload to perform the routine - /// # Parameters: - /// * `sub_function`: The type of routine control to perform. - /// * [`RoutineControlSubFunction::StartRoutine`] - /// * [`RoutineControlSubFunction::StopRoutine`] - /// * [`RoutineControlSubFunction::RequestRoutineResults`] - /// * `routine_id`: The identifier of the routine to control - pub fn routine_control(sub_function: RoutineControlSubFunction, routine_id: D::RID) -> Self { - Request::RoutineControl(RoutineControlRequest::new(sub_function, routine_id, None)) - } - - /// Create a new `RoutineControl` request - /// - /// **Note**: This could be cleaner as the Identifier is technically represented in the `RoutinePayload` - /// and if the `RoutinePayload` is a single value, then the `RoutineIdentifier` is not needed - /// - /// This does not check if the server requires a payload - /// - /// # Parameters: - /// * `sub_function`: The type of routine control to perform. - /// * [`RoutineControlSubFunction::StartRoutine`] - /// * [`RoutineControlSubFunction::StopRoutine`] - /// * [`RoutineControlSubFunction::RequestRoutineResults`] - /// * `routine_id`: The identifier of the routine to control. User defined routine identifiers and payloads are allowed - /// * General purpose/UDS defined: [`crate::UDSRoutineIdentifier`] - /// * `data`: Optional payload for the routine control request - pub fn routine_control_payload( - sub_function: RoutineControlSubFunction, - routine_id: D::RID, - data: Option, - ) -> Self { - Request::RoutineControl(RoutineControlRequest::new(sub_function, routine_id, data)) - } - - /// Create a new `SecurityAccess` request (seed or key phase). - #[must_use] - pub fn security_access( - suppress_positive_response: bool, - access_type: SecurityAccessType, - data_record: Vec, - ) -> Self { - Request::SecurityAccess(SecurityAccessRequest::new( - suppress_positive_response, - access_type, - data_record, - )) - } - - /// Create a new `TesterPresent` keep-alive request. - #[must_use] - pub fn tester_present(suppress_positive_response: bool) -> Self { - Request::TesterPresent(TesterPresentRequest::new(suppress_positive_response)) - } - - /// Create a new `TransferData` request with the given block-sequence counter and payload. - #[must_use] - pub fn transfer_data(sequence: u8, data: Vec) -> Self { - Request::TransferData(TransferDataRequest::new(sequence, data)) - } - - /// Create a new `WriteDataByIdentifier` request with the given payload. - pub fn write_data_by_identifier(payload: D::DiagnosticPayload) -> Self { - Request::WriteDataByIdentifier(WriteDataByIdentifierRequest::new(payload)) - } - - /// Returns the [`UdsServiceType`] corresponding to this request variant. - pub fn service(&self) -> UdsServiceType { - match self { - Self::ClearDiagnosticInfo(_) => UdsServiceType::ClearDiagnosticInfo, - Self::CommunicationControl(_) => UdsServiceType::CommunicationControl, - Self::ControlDTCSettings(_) => UdsServiceType::ControlDTCSettings, - Self::DiagnosticSessionControl(_) => UdsServiceType::DiagnosticSessionControl, - Self::EcuReset(_) => UdsServiceType::EcuReset, - Self::ReadDataByIdentifier(_) => UdsServiceType::ReadDataByIdentifier, - Self::ReadDTCInfo(_) => UdsServiceType::ReadDTCInfo, - Self::RequestDownload(_) => UdsServiceType::RequestDownload, - Self::RequestTransferExit => UdsServiceType::RequestTransferExit, - Self::RoutineControl(_) => UdsServiceType::RoutineControl, - Self::SecurityAccess(_) => UdsServiceType::SecurityAccess, - Self::TesterPresent(_) => UdsServiceType::TesterPresent, - Self::TransferData(_) => UdsServiceType::TransferData, - Self::WriteDataByIdentifier(_) => UdsServiceType::WriteDataByIdentifier, +impl<'a> Decode<'a> for Request<'a> { + fn decode(buf: &'a [u8]) -> Result<(Self, &'a [u8]), Error> { + if buf.is_empty() { + return Err(Error::InsufficientData(1)); } - } + let service = UdsServiceType::service_from_request_byte(buf[0]); + let payload = &buf[1..]; - /// Returns the negative-response codes that are valid for this request's service. - pub fn allowed_nack_codes(&self) -> &'static [NegativeResponseCode] { - match self { - Self::ClearDiagnosticInfo(_) => ClearDiagnosticInfoRequest::allowed_nack_codes(), - Self::DiagnosticSessionControl(_) => { - DiagnosticSessionControlRequest::allowed_nack_codes() + let request = match service { + UdsServiceType::ClearDiagnosticInfo => { + let (req, _) = ::decode(payload)?; + Self::ClearDiagnosticInfo(req) } - Self::EcuReset(_) => EcuResetRequest::allowed_nack_codes(), - Self::SecurityAccess(_) => SecurityAccessRequest::allowed_nack_codes(), - Self::RequestDownload(_) => RequestDownloadRequest::allowed_nack_codes(), - _ => &[NegativeResponseCode::ServiceNotSupported], - } - } -} - -impl WireFormat for Request { - fn required_size(&self) -> usize { - 1 + match self { - Self::ClearDiagnosticInfo(cdi) => cdi.required_size(), - Self::CommunicationControl(cc) => cc.required_size(), - Self::ControlDTCSettings(ct) => ct.required_size(), - Self::DiagnosticSessionControl(ds) => ds.required_size(), - Self::EcuReset(er) => er.required_size(), - Self::ReadDataByIdentifier(rd) => rd.required_size(), - Self::ReadDTCInfo(rd) => rd.required_size(), - Self::RequestDownload(rd) => rd.required_size(), - Self::RequestTransferExit => 0, - Self::RoutineControl(rc) => rc.required_size(), - Self::SecurityAccess(sa) => sa.required_size(), - Self::TesterPresent(tp) => tp.required_size(), - Self::TransferData(td) => td.required_size(), - Self::WriteDataByIdentifier(wd) => wd.required_size(), - } - } - - /// Serialization function to write a [`Request`] to a [`Writer`](std::io::Write) - /// This function writes the service byte and then calls the appropriate - /// serialization function for the service represented by self. - fn encode(&self, writer: &mut W) -> Result { - // Write the service byte - writer.write_u8(self.service().request_service_to_byte())?; - // Write the payload - Ok(1 + match self { - Self::ClearDiagnosticInfo(cdi) => cdi.encode(writer), - Self::CommunicationControl(cc) => cc.encode(writer), - Self::ControlDTCSettings(ct) => ct.encode(writer), - Self::DiagnosticSessionControl(ds) => ds.encode(writer), - Self::EcuReset(er) => er.encode(writer), - Self::ReadDataByIdentifier(rd) => rd.encode(writer), - Self::ReadDTCInfo(rd) => rd.encode(writer), - Self::RequestDownload(rd) => rd.encode(writer), - Self::RequestTransferExit => Ok(0), - Self::RoutineControl(rc) => rc.encode(writer), - Self::SecurityAccess(sa) => sa.encode(writer), - Self::TesterPresent(tp) => tp.encode(writer), - Self::TransferData(td) => td.encode(writer), - Self::WriteDataByIdentifier(wd) => wd.encode(writer), - }?) - } - - fn is_positive_response_suppressed(&self) -> bool { - match self { - Self::CommunicationControl(cc) => cc.suppress_positive_response(), - Self::ControlDTCSettings(ct) => ct.is_positive_response_suppressed(), - Self::DiagnosticSessionControl(ds) => ds.suppress_positive_response(), - Self::EcuReset(er) => er.suppress_positive_response(), - Self::SecurityAccess(sa) => sa.suppress_positive_response(), - Self::TesterPresent(tp) => tp.suppress_positive_response(), - _ => false, - } - } -} - -impl SingleValueWireFormat for Request { - /// Deserialization function to read a [`Request`] from a [`Reader`](std::io::Read) - /// This function reads the service byte and then calls the appropriate - /// deserialization function for the service in question - /// - /// *Note*: - /// - /// Some services allow for custom byte arrays at the end of the request - /// It is important that only the request data is passed to this function - /// or the deserialization could read unexpected data - #[allow(clippy::too_many_lines)] - fn decode(reader: &mut R) -> Result { - let service = UdsServiceType::service_from_request_byte(reader.read_u8()?); - Ok(match service { UdsServiceType::CommunicationControl => { - Self::CommunicationControl(CommunicationControlRequest::decode(reader)?) + let (req, _) = ::decode(payload)?; + Self::CommunicationControl(req) } UdsServiceType::ControlDTCSettings => { - Self::ControlDTCSettings(ControlDTCSettingsRequest::decode(reader)?) + let (req, _) = ::decode(payload)?; + Self::ControlDTCSettings(req) } UdsServiceType::DiagnosticSessionControl => { - Self::DiagnosticSessionControl(DiagnosticSessionControlRequest::decode(reader)?) + let (req, _) = ::decode(payload)?; + Self::DiagnosticSessionControl(req) } - UdsServiceType::EcuReset => Self::EcuReset(EcuResetRequest::decode(reader)?), - UdsServiceType::ReadDataByIdentifier => { - Self::ReadDataByIdentifier(ReadDataByIdentifierRequest::decode(reader)?) + UdsServiceType::EcuReset => { + let (req, _) = ::decode(payload)?; + Self::EcuReset(req) } - UdsServiceType::ReadDTCInfo => Self::ReadDTCInfo(ReadDTCInfoRequest::decode(reader)?), + UdsServiceType::ReadDataByIdentifier => Self::ReadDataByIdentifier(payload), + UdsServiceType::ReadDTCInfo => Self::ReadDTCInfo(payload), UdsServiceType::RequestDownload => { - Self::RequestDownload(RequestDownloadRequest::decode(reader)?) + let (req, _) = ::decode(payload)?; + Self::RequestDownload(req) + } + UdsServiceType::RequestFileTransfer => { + let (req, _) = ::decode(payload)?; + Self::RequestFileTransfer(req) } UdsServiceType::RequestTransferExit => Self::RequestTransferExit, UdsServiceType::RoutineControl => { - Self::RoutineControl(RoutineControlRequest::decode(reader)?) + if payload.is_empty() { + return Err(Error::InsufficientData(2)); + } + Self::RoutineControl { + sub_function: payload[0], + raw_payload: &payload[1..], + } } UdsServiceType::SecurityAccess => { - Self::SecurityAccess(SecurityAccessRequest::decode(reader)?) + let (req, _) = ::decode(payload)?; + Self::SecurityAccess(req) } UdsServiceType::TesterPresent => { - Self::TesterPresent(TesterPresentRequest::decode(reader)?) + let (req, _) = ::decode(payload)?; + Self::TesterPresent(req) } UdsServiceType::TransferData => { - Self::TransferData(TransferDataRequest::decode(reader)?) - } - UdsServiceType::WriteDataByIdentifier => { - Self::WriteDataByIdentifier(WriteDataByIdentifierRequest::decode(reader)?) - } - UdsServiceType::Authentication => { - return Err(Error::ServiceNotImplemented(UdsServiceType::Authentication)); - } - UdsServiceType::AccessTimingParameters => { - return Err(Error::ServiceNotImplemented( - UdsServiceType::AccessTimingParameters, - )); - } - UdsServiceType::SecuredDataTransmission => { - return Err(Error::ServiceNotImplemented( - UdsServiceType::SecuredDataTransmission, - )); - } - UdsServiceType::ResponseOnEvent => { - return Err(Error::ServiceNotImplemented( - UdsServiceType::ResponseOnEvent, - )); - } - UdsServiceType::LinkControl => { - return Err(Error::ServiceNotImplemented(UdsServiceType::LinkControl)); - } - UdsServiceType::ReadMemoryByAddress => { - return Err(Error::ServiceNotImplemented( - UdsServiceType::ReadMemoryByAddress, - )); - } - UdsServiceType::ReadScalingDataByIdentifier => { - return Err(Error::ServiceNotImplemented( - UdsServiceType::ReadScalingDataByIdentifier, - )); - } - UdsServiceType::ReadDataByIdentifierPeriodic => { - return Err(Error::ServiceNotImplemented( - UdsServiceType::ReadDataByIdentifierPeriodic, - )); - } - UdsServiceType::DynamicallyDefinedDataIdentifier => { - return Err(Error::ServiceNotImplemented( - UdsServiceType::DynamicallyDefinedDataIdentifier, - )); - } - UdsServiceType::WriteMemoryByAddress => { - return Err(Error::ServiceNotImplemented( - UdsServiceType::WriteMemoryByAddress, - )); - } - UdsServiceType::ClearDiagnosticInfo => { - return Err(Error::ServiceNotImplemented( - UdsServiceType::ClearDiagnosticInfo, - )); - } - UdsServiceType::InputOutputControlByIdentifier => { - return Err(Error::ServiceNotImplemented( - UdsServiceType::InputOutputControlByIdentifier, - )); - } - UdsServiceType::RequestUpload => { - return Err(Error::ServiceNotImplemented(UdsServiceType::RequestUpload)); - } - UdsServiceType::RequestFileTransfer => { - return Err(Error::ServiceNotImplemented( - UdsServiceType::RequestFileTransfer, - )); - } - UdsServiceType::NegativeResponse => { - return Err(Error::ServiceNotImplemented( - UdsServiceType::NegativeResponse, - )); - } - UdsServiceType::UnsupportedDiagnosticService => { - return Err(Error::ServiceNotImplemented( - UdsServiceType::UnsupportedDiagnosticService, - )); + let (req, _) = ::decode(payload)?; + Self::TransferData(req) } - }) + UdsServiceType::WriteDataByIdentifier => Self::WriteDataByIdentifier(payload), + _ => return Err(Error::ServiceNotImplemented(service)), + }; + Ok((request, &[])) } } -#[cfg(test)] -mod tests { - use super::*; - use crate::{ - CommunicationControlType, CommunicationType, ProtocolRequest, ResetType, SecurityAccessType, - }; - - #[test] - fn test_is_positive_response_suppressed() { - let communication_control_request = ProtocolRequest::communication_control( - CommunicationControlType::EnableRxAndTx, - CommunicationType::Normal, - true, - ); - assert!(communication_control_request.is_positive_response_suppressed()); - - let control_dtc_settings_request = - ProtocolRequest::control_dtc_settings(DtcSettings::On, true); - assert!(control_dtc_settings_request.is_positive_response_suppressed()); - - let diagnostic_session_control_request = ProtocolRequest::diagnostic_session_control( - true, - DiagnosticSessionType::ProgrammingSession, - ); - assert!(diagnostic_session_control_request.is_positive_response_suppressed()); - let diagnostic_session_control_request = ProtocolRequest::diagnostic_session_control( - false, - DiagnosticSessionType::ProgrammingSession, - ); - let should_not_be_suppressed = - diagnostic_session_control_request.is_positive_response_suppressed(); - assert!(!should_not_be_suppressed); - - let ecu_reset_request = ProtocolRequest::ecu_reset(true, ResetType::HardReset); - assert!(ecu_reset_request.is_positive_response_suppressed()); - - let security_access_request = ProtocolRequest::security_access( - true, - SecurityAccessType::ISO26021_2SendKeyValues, - vec![0x01, 0x02], - ); - assert!(security_access_request.is_positive_response_suppressed()); - - let tester_present_request = ProtocolRequest::tester_present(true); - assert!(tester_present_request.is_positive_response_suppressed()); - - let clear_diagnostic_info_request = - ProtocolRequest::clear_diagnostic_info(DTCRecord::new(0x01, 0x02, 0x03), 0x01); - assert!(!clear_diagnostic_info_request.is_positive_response_suppressed()); +impl Encode for Request<'_> { + fn encoded_size(&self) -> usize { + let payload = match self { + Self::ClearDiagnosticInfo(req) => req.encoded_size(), + Self::CommunicationControl(req) => req.encoded_size(), + Self::ControlDTCSettings(req) => req.encoded_size(), + Self::DiagnosticSessionControl(req) => req.encoded_size(), + Self::EcuReset(req) => req.encoded_size(), + Self::ReadDataByIdentifier(bytes) + | Self::WriteDataByIdentifier(bytes) + | Self::ReadDTCInfo(bytes) => bytes.len(), + Self::RequestDownload(req) => req.encoded_size(), + Self::RequestFileTransfer(req) => req.encoded_size(), + Self::RequestTransferExit => 0, + Self::RoutineControl { raw_payload, .. } => 1 + raw_payload.len(), + Self::SecurityAccess(req) => req.encoded_size(), + Self::TesterPresent(req) => req.encoded_size(), + Self::TransferData(req) => req.encoded_size(), + }; + 1 + payload + } + + fn encode(&self, writer: &mut impl embedded_io::Write) -> Result { + writer + .write_all(&[self.service().request_service_to_byte()]) + .map_err(Error::io)?; + let payload = match self { + Self::ClearDiagnosticInfo(req) => req.encode(writer)?, + Self::CommunicationControl(req) => req.encode(writer)?, + Self::ControlDTCSettings(req) => req.encode(writer)?, + Self::DiagnosticSessionControl(req) => req.encode(writer)?, + Self::EcuReset(req) => req.encode(writer)?, + Self::ReadDataByIdentifier(bytes) + | Self::WriteDataByIdentifier(bytes) + | Self::ReadDTCInfo(bytes) => { + writer.write_all(bytes).map_err(Error::io)?; + bytes.len() + } + Self::RequestDownload(req) => req.encode(writer)?, + Self::RequestFileTransfer(req) => req.encode(writer)?, + Self::RequestTransferExit => 0, + Self::RoutineControl { + sub_function, + raw_payload, + } => { + writer.write_all(&[*sub_function]).map_err(Error::io)?; + writer.write_all(raw_payload).map_err(Error::io)?; + 1 + raw_payload.len() + } + Self::SecurityAccess(req) => req.encode(writer)?, + Self::TesterPresent(req) => req.encode(writer)?, + Self::TransferData(req) => req.encode(writer)?, + }; + Ok(1 + payload) } +} - #[test] - fn test_communication_control_sprmib_wire_bytes() { - // DisableRxAndTx with suppress=true (as used in firmware_flashing archive) - let request = ProtocolRequest::communication_control( - CommunicationControlType::DisableRxAndTx, - CommunicationType::Normal, - true, - ); - assert!(request.is_positive_response_suppressed()); - - let mut bytes = Vec::new(); - request.encode(&mut bytes).unwrap(); - // SID=0x28, sub-function=0x03|0x80=0x83, communication_type - assert_eq!(bytes[0], 0x28, "SID should be 0x28"); - assert_eq!( - bytes[1], 0x83, - "Sub-function should be 0x83 (DisableRxAndTx=0x03 | SPRMIB=0x80), got {:#04x}", - bytes[1] - ); - - // EnableRxAndTx with suppress=true - let request2 = ProtocolRequest::communication_control( - CommunicationControlType::EnableRxAndTx, - CommunicationType::Normal, - true, - ); - let mut bytes2 = Vec::new(); - request2.encode(&mut bytes2).unwrap(); - assert_eq!(bytes2[0], 0x28); - assert_eq!( - bytes2[1], 0x80, - "Sub-function should be 0x80 (EnableRxAndTx=0x00 | SPRMIB=0x80), got {:#04x}", - bytes2[1] - ); +impl Request<'_> { + /// Returns the [`UdsServiceType`] corresponding to this request variant. + #[must_use] + pub fn service(&self) -> UdsServiceType { + match self { + Self::ClearDiagnosticInfo(_) => UdsServiceType::ClearDiagnosticInfo, + Self::CommunicationControl(_) => UdsServiceType::CommunicationControl, + Self::ControlDTCSettings(_) => UdsServiceType::ControlDTCSettings, + Self::DiagnosticSessionControl(_) => UdsServiceType::DiagnosticSessionControl, + Self::EcuReset(_) => UdsServiceType::EcuReset, + Self::ReadDataByIdentifier(_) => UdsServiceType::ReadDataByIdentifier, + Self::ReadDTCInfo(_) => UdsServiceType::ReadDTCInfo, + Self::RequestDownload(_) => UdsServiceType::RequestDownload, + Self::RequestFileTransfer(_) => UdsServiceType::RequestFileTransfer, + Self::RequestTransferExit => UdsServiceType::RequestTransferExit, + Self::RoutineControl { .. } => UdsServiceType::RoutineControl, + Self::SecurityAccess(_) => UdsServiceType::SecurityAccess, + Self::TesterPresent(_) => UdsServiceType::TesterPresent, + Self::TransferData(_) => UdsServiceType::TransferData, + Self::WriteDataByIdentifier(_) => UdsServiceType::WriteDataByIdentifier, + } } } diff --git a/src/response.rs b/src/response.rs index de78a02..13c09c1 100644 --- a/src/response.rs +++ b/src/response.rs @@ -1,341 +1,239 @@ use crate::{ - CommunicationControlResponse, CommunicationControlType, ControlDTCSettingsResponse, - DiagnosticDefinition, DiagnosticSessionControlResponse, DiagnosticSessionType, DtcSettings, - EcuResetResponse, Error, NegativeResponse, NegativeResponseCode, ReadDTCInfoResponse, - ReadDataByIdentifierResponse, RequestDownloadResponse, RequestFileTransferResponse, ResetType, - RoutineControlResponse, SecurityAccessResponse, SecurityAccessType, SingleValueWireFormat, - TesterPresentResponse, TransferDataResponse, UdsServiceType, WireFormat, - WriteDataByIdentifierResponse, + CommunicationControlResponse, ControlDTCSettingsResponse, Decode, + DiagnosticSessionControlResponse, EcuResetResponse, Encode, Error, NegativeResponse, + ReadDTCInfoResponseRx, RequestDownloadResponseTx, RequestFileTransferResponseTx, + SecurityAccessResponseTx, TesterPresentResponse, TransferDataResponseTx, UdsServiceType, }; -use byteorder::{ReadBytesExt, WriteBytesExt}; -use std::io::{Read, Write}; -/// A raw UDS response consisting of the service type and its unparsed payload bytes. +/// Parsed zero-copy UDS response. Borrows from the wire buffer. +/// +/// Variable-length payloads are stored as raw `&'a [u8]` slices that can be +/// further parsed on demand. +#[derive(Clone, Debug)] #[non_exhaustive] -pub struct UdsResponse { - /// The service this response corresponds to. - pub service: UdsServiceType, - /// The raw payload bytes following the service identifier. - pub data: Vec, -} - -/// Parsed UDS response. Each variant corresponds to a different UDS service response. -#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] -#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))] -#[derive(Clone, Debug, PartialEq)] -#[non_exhaustive] -pub enum Response { - /// Response to a [`ClearDiagnosticInfoRequest`](crate::ClearDiagnosticInfoRequest) +pub enum Response<'a> { + /// Positive response to `ClearDiagnosticInfo`. ClearDiagnosticInfo, - /// Response to a [`CommunicationControlRequest`](crate::CommunicationControlRequest) + /// Positive response to `CommunicationControl`. CommunicationControl(CommunicationControlResponse), - /// Response to a [`ControlDTCSettingsRequest`](crate::ControlDTCSettingsRequest) + /// Positive response to `ControlDTCSettings`. ControlDTCSettings(ControlDTCSettingsResponse), - /// Response to a [`DiagnosticSessionControlRequest`](crate::DiagnosticSessionControlRequest) + /// Positive response to `DiagnosticSessionControl`. DiagnosticSessionControl(DiagnosticSessionControlResponse), - /// Response to a [`EcuResetRequest`](crate::EcuResetRequest) + /// Positive response to `EcuReset`. EcuReset(EcuResetResponse), - /// Negative response to any request + /// Negative response to any request. NegativeResponse(NegativeResponse), - /// Response to a [`ReadDataByIdentifierRequest`](crate::ReadDataByIdentifierRequest) - ReadDataByIdentifier(ReadDataByIdentifierResponse), - /// Response to a [`ReadDTCInfoRequest`](crate::ReadDTCInfoRequest) - ReadDTCInfo(ReadDTCInfoResponse), - /// Response to a [`RequestDownloadRequest`](crate::RequestDownloadRequest) - RequestDownload(RequestDownloadResponse), - /// Response to a [`RequestFileTransferRequest`](crate::RequestFileTransferRequest) - RequestFileTransfer(RequestFileTransferResponse), - /// Response to a `RequestTransferExit` request + /// Positive response to `ReadDataByIdentifier`. Raw payload bytes. + ReadDataByIdentifier(&'a [u8]), + /// Positive response to `ReadDTCInformation` with lazy iterators. + ReadDTCInfo(ReadDTCInfoResponseRx<'a>), + /// Positive response to `RequestDownload`. + RequestDownload(RequestDownloadResponseTx<'a>), + /// Positive response to `RequestFileTransfer`. + RequestFileTransfer(RequestFileTransferResponseTx<'a>), + /// Positive response to `RequestTransferExit`. RequestTransferExit, - /// Response to a [`RoutineControl` request](crate::RoutineControlRequest) - RoutineControl(RoutineControlResponse), - /// Response to a [`SecurityAccessRequest`](crate::SecurityAccessRequest) - SecurityAccess(SecurityAccessResponse), - /// Response to a [`TesterPresentRequest`](crate::TesterPresentRequest) + /// Positive response to `RoutineControl`. Raw status record bytes. + RoutineControl { + /// The routine control sub-function echo. + routine_control_type: u8, + /// Raw routine status record bytes. + raw_status_record: &'a [u8], + }, + /// Positive response to `SecurityAccess`. + SecurityAccess(SecurityAccessResponseTx<'a>), + /// Positive response to `TesterPresent`. TesterPresent(TesterPresentResponse), - /// Response to a [`TransferDataRequest`](crate::TransferDataRequest) - TransferData(TransferDataResponse), - /// Response to a [`WriteDataByIdentifierRequest`](crate::WriteDataByIdentifierRequest) - WriteDataByIdentifier(WriteDataByIdentifierResponse), -} - -impl Response { - /// Create a `ClearDiagnosticInfo` positive response. - #[must_use] - pub fn clear_diagnostic_info() -> Self { - Response::ClearDiagnosticInfo - } - /// Create a `CommunicationControl` positive response. - #[must_use] - pub fn communication_control(control_type: CommunicationControlType) -> Self { - Response::CommunicationControl(CommunicationControlResponse::new(control_type)) - } - - /// Create a `ControlDTCSettings` positive response. - #[must_use] - pub fn control_dtc_settings(setting: DtcSettings) -> Self { - Response::ControlDTCSettings(ControlDTCSettingsResponse::new(setting)) - } - - /// Create a `DiagnosticSessionControl` positive response with timing parameters. - #[must_use] - pub fn diagnostic_session_control( - session_type: DiagnosticSessionType, - p2_max: u16, - p2_star_max: u16, - ) -> Self { - Response::DiagnosticSessionControl(DiagnosticSessionControlResponse::new( - session_type, - p2_max, - p2_star_max, - )) - } - - /// Create an `EcuReset` positive response. - #[must_use] - pub fn ecu_reset(reset_type: ResetType, power_down_time: u8) -> Self { - Response::EcuReset(EcuResetResponse::new(reset_type, power_down_time)) - } - - /// Create a negative response for the given service and response code. - #[must_use] - pub fn negative_response(request_service: UdsServiceType, nrc: NegativeResponseCode) -> Self { - Response::NegativeResponse(NegativeResponse::new(request_service, nrc)) - } - - /// Create a `ReadDataByIdentifier` positive response from an iterator of payloads. - #[must_use] - pub fn read_data_by_identifier(payload: I) -> Self - where - I: IntoIterator, - { - Response::ReadDataByIdentifier(ReadDataByIdentifierResponse::new(payload)) - } - - /// Create a `RequestDownload` positive response. - #[must_use] - pub fn request_download( - length_format_identifier: u8, - max_number_of_block_length: Vec, - ) -> Self { - Response::RequestDownload(RequestDownloadResponse::new( - length_format_identifier, - max_number_of_block_length, - )) - } - - /// Create a `RequestFileTransfer` positive response. Not yet implemented. - #[must_use] - pub fn request_file_transfer() -> Self { - todo!() - } - - /// Create a `RoutineControl` positive response. - pub fn routine_control( - routine_control_type: crate::RoutineControlSubFunction, - data: D::RoutinePayload, - ) -> Self { - Response::RoutineControl(RoutineControlResponse::new(routine_control_type, data)) - } - - /// Create a `SecurityAccess` positive response carrying the security seed. - #[must_use] - pub fn security_access(access_type: SecurityAccessType, security_seed: Vec) -> Self { - Response::SecurityAccess(SecurityAccessResponse::new(access_type, security_seed)) - } - - /// Create a `TesterPresent` positive response. - #[must_use] - pub fn tester_present() -> Self { - Response::TesterPresent(TesterPresentResponse::new()) - } - - /// Create a `TransferData` positive response. - #[must_use] - pub fn transfer_data(block_sequence_counter: u8, data: Vec) -> Self { - Response::TransferData(TransferDataResponse::new(block_sequence_counter, data)) - } - - /// Returns the [`UdsServiceType`] corresponding to this response variant. - pub fn service(&self) -> UdsServiceType { - match self { - Self::ClearDiagnosticInfo => UdsServiceType::ClearDiagnosticInfo, - Self::CommunicationControl(_) => UdsServiceType::CommunicationControl, - Self::ControlDTCSettings(_) => UdsServiceType::ControlDTCSettings, - Self::DiagnosticSessionControl(_) => UdsServiceType::DiagnosticSessionControl, - Self::EcuReset(_) => UdsServiceType::EcuReset, - Self::NegativeResponse(_) => UdsServiceType::NegativeResponse, - Self::ReadDataByIdentifier(_) => UdsServiceType::ReadDataByIdentifier, - Self::ReadDTCInfo(_) => UdsServiceType::ReadDTCInfo, - Self::RequestDownload(_) => UdsServiceType::RequestDownload, - Self::RequestFileTransfer(_) => UdsServiceType::RequestFileTransfer, - Self::RequestTransferExit => UdsServiceType::RequestTransferExit, - Self::RoutineControl(_) => UdsServiceType::RoutineControl, - Self::SecurityAccess(_) => UdsServiceType::SecurityAccess, - Self::TesterPresent(_) => UdsServiceType::TesterPresent, - Self::TransferData(_) => UdsServiceType::TransferData, - Self::WriteDataByIdentifier(_) => UdsServiceType::WriteDataByIdentifier, - } - } + /// Positive response to `TransferData`. + TransferData(TransferDataResponseTx<'a>), + /// Positive response to `WriteDataByIdentifier`. Contains the echoed DID bytes. + WriteDataByIdentifier(&'a [u8]), } -impl WireFormat for Response { - #[allow(clippy::match_same_arms)] - fn required_size(&self) -> usize { - 1 + match self { - Self::ClearDiagnosticInfo => 0, - Self::CommunicationControl(cc) => cc.required_size(), - Self::ControlDTCSettings(dtc) => dtc.required_size(), - Self::DiagnosticSessionControl(ds) => ds.required_size(), - Self::EcuReset(reset) => reset.required_size(), - Self::NegativeResponse(nr) => nr.required_size(), - Self::ReadDataByIdentifier(rd) => rd.required_size(), - Self::ReadDTCInfo(rd) => rd.required_size(), - Self::RequestDownload(rd) => rd.required_size(), - Self::RequestFileTransfer(rft) => rft.required_size(), - Self::RequestTransferExit => 0, - Self::RoutineControl(rc) => rc.required_size(), - Self::SecurityAccess(sa) => sa.required_size(), - Self::TesterPresent(tp) => tp.required_size(), - Self::TransferData(td) => td.required_size(), - Self::WriteDataByIdentifier(wdbi) => wdbi.required_size(), +impl<'a> Decode<'a> for Response<'a> { + fn decode(buf: &'a [u8]) -> Result<(Self, &'a [u8]), Error> { + if buf.is_empty() { + return Err(Error::InsufficientData(1)); } - } + let service = UdsServiceType::response_from_byte(buf[0]); + let payload = &buf[1..]; - #[allow(clippy::match_same_arms)] - fn encode(&self, writer: &mut T) -> Result { - // Write the service byte - writer.write_u8(self.service().response_to_byte())?; - // Write the payload - Ok(1 + match self { - Self::ClearDiagnosticInfo => Ok(0), - Self::CommunicationControl(cc) => cc.encode(writer), - Self::ControlDTCSettings(dtc) => dtc.encode(writer), - Self::DiagnosticSessionControl(ds) => ds.encode(writer), - Self::EcuReset(reset) => reset.encode(writer), - Self::NegativeResponse(nr) => nr.encode(writer), - Self::ReadDataByIdentifier(rd) => rd.encode(writer), - Self::ReadDTCInfo(rd) => rd.encode(writer), - Self::RequestDownload(rd) => rd.encode(writer), - Self::RequestFileTransfer(rft) => rft.encode(writer), - Self::RequestTransferExit => Ok(0), - Self::RoutineControl(rc) => rc.encode(writer), - Self::SecurityAccess(sa) => sa.encode(writer), - Self::TesterPresent(tp) => tp.encode(writer), - Self::TransferData(td) => td.encode(writer), - Self::WriteDataByIdentifier(wdbi) => wdbi.encode(writer), - }?) - } -} - -impl SingleValueWireFormat for Response { - #[allow(clippy::too_many_lines)] - fn decode(reader: &mut T) -> Result { - let service = UdsServiceType::response_from_byte(reader.read_u8()?); - Ok(match service { + let response = match service { + UdsServiceType::ClearDiagnosticInfo => Self::ClearDiagnosticInfo, UdsServiceType::CommunicationControl => { - Self::CommunicationControl(CommunicationControlResponse::decode(reader)?) + let (resp, _) = ::decode(payload)?; + Self::CommunicationControl(resp) } UdsServiceType::ControlDTCSettings => { - Self::ControlDTCSettings(ControlDTCSettingsResponse::decode(reader)?) + let (resp, _) = ::decode(payload)?; + Self::ControlDTCSettings(resp) } UdsServiceType::DiagnosticSessionControl => { - Self::DiagnosticSessionControl(DiagnosticSessionControlResponse::decode(reader)?) + let (resp, _) = ::decode(payload)?; + Self::DiagnosticSessionControl(resp) + } + UdsServiceType::EcuReset => { + let (resp, _) = ::decode(payload)?; + Self::EcuReset(resp) + } + UdsServiceType::NegativeResponse => { + let (resp, _) = ::decode(payload)?; + Self::NegativeResponse(resp) } - UdsServiceType::EcuReset => Self::EcuReset(EcuResetResponse::decode(reader)?), - UdsServiceType::ReadDataByIdentifier => { - Self::ReadDataByIdentifier(ReadDataByIdentifierResponse::decode(reader)?) + UdsServiceType::ReadDataByIdentifier => Self::ReadDataByIdentifier(payload), + UdsServiceType::ReadDTCInfo => { + let (resp, _) = ::decode(payload)?; + Self::ReadDTCInfo(resp) } - UdsServiceType::ReadDTCInfo => Self::ReadDTCInfo(ReadDTCInfoResponse::decode(reader)?), UdsServiceType::RequestDownload => { - Self::RequestDownload(RequestDownloadResponse::decode(reader)?) + let (resp, _) = ::decode(payload)?; + Self::RequestDownload(resp) } UdsServiceType::RequestFileTransfer => { - Self::RequestFileTransfer(RequestFileTransferResponse::decode(reader)?) + let (resp, _) = ::decode(payload)?; + Self::RequestFileTransfer(resp) } UdsServiceType::RequestTransferExit => Self::RequestTransferExit, UdsServiceType::RoutineControl => { - Self::RoutineControl(RoutineControlResponse::decode(reader)?) + if payload.is_empty() { + return Err(Error::InsufficientData(2)); + } + Self::RoutineControl { + routine_control_type: payload[0], + raw_status_record: &payload[1..], + } } UdsServiceType::SecurityAccess => { - Self::SecurityAccess(SecurityAccessResponse::decode(reader)?) + let (resp, _) = ::decode(payload)?; + Self::SecurityAccess(resp) } UdsServiceType::TesterPresent => { - Self::TesterPresent(TesterPresentResponse::decode(reader)?) - } - UdsServiceType::NegativeResponse => { - Self::NegativeResponse(NegativeResponse::decode(reader)?) - } - UdsServiceType::WriteDataByIdentifier => { - Self::WriteDataByIdentifier(WriteDataByIdentifierResponse::decode(reader)?) - } - UdsServiceType::Authentication => { - return Err(Error::ServiceNotImplemented(UdsServiceType::Authentication)); - } - UdsServiceType::AccessTimingParameters => { - return Err(Error::ServiceNotImplemented( - UdsServiceType::AccessTimingParameters, - )); - } - UdsServiceType::SecuredDataTransmission => { - return Err(Error::ServiceNotImplemented( - UdsServiceType::SecuredDataTransmission, - )); - } - UdsServiceType::ResponseOnEvent => { - return Err(Error::ServiceNotImplemented( - UdsServiceType::ResponseOnEvent, - )); - } - UdsServiceType::LinkControl => { - return Err(Error::ServiceNotImplemented(UdsServiceType::LinkControl)); - } - UdsServiceType::ReadMemoryByAddress => { - return Err(Error::ServiceNotImplemented( - UdsServiceType::ReadMemoryByAddress, - )); - } - UdsServiceType::ReadScalingDataByIdentifier => { - return Err(Error::ServiceNotImplemented( - UdsServiceType::ReadScalingDataByIdentifier, - )); - } - UdsServiceType::ReadDataByIdentifierPeriodic => { - return Err(Error::ServiceNotImplemented( - UdsServiceType::ReadDataByIdentifierPeriodic, - )); - } - UdsServiceType::DynamicallyDefinedDataIdentifier => { - return Err(Error::ServiceNotImplemented( - UdsServiceType::DynamicallyDefinedDataIdentifier, - )); - } - UdsServiceType::WriteMemoryByAddress => { - return Err(Error::ServiceNotImplemented( - UdsServiceType::WriteMemoryByAddress, - )); - } - UdsServiceType::ClearDiagnosticInfo => { - return Err(Error::ServiceNotImplemented( - UdsServiceType::ClearDiagnosticInfo, - )); - } - UdsServiceType::InputOutputControlByIdentifier => { - return Err(Error::ServiceNotImplemented( - UdsServiceType::InputOutputControlByIdentifier, - )); - } - UdsServiceType::RequestUpload => { - return Err(Error::ServiceNotImplemented(UdsServiceType::RequestUpload)); + let (resp, _) = ::decode(payload)?; + Self::TesterPresent(resp) } UdsServiceType::TransferData => { - Self::TransferData(TransferDataResponse::decode(reader)?) + let (resp, _) = ::decode(payload)?; + Self::TransferData(resp) } - UdsServiceType::UnsupportedDiagnosticService => { - return Err(Error::ServiceNotImplemented( - UdsServiceType::UnsupportedDiagnosticService, - )); + UdsServiceType::WriteDataByIdentifier => Self::WriteDataByIdentifier(payload), + _ => return Err(Error::ServiceNotImplemented(service)), + }; + Ok((response, &[])) + } +} + +impl Response<'_> { + /// Returns the response service-ID byte that frames this response on the wire. + fn response_sid(&self) -> u8 { + match self { + Self::ClearDiagnosticInfo => UdsServiceType::ClearDiagnosticInfo.response_to_byte(), + Self::CommunicationControl(_) => { + UdsServiceType::CommunicationControl.response_to_byte() + } + Self::ControlDTCSettings(_) => UdsServiceType::ControlDTCSettings.response_to_byte(), + Self::DiagnosticSessionControl(_) => { + UdsServiceType::DiagnosticSessionControl.response_to_byte() + } + Self::EcuReset(_) => UdsServiceType::EcuReset.response_to_byte(), + Self::NegativeResponse(_) => UdsServiceType::NegativeResponse.response_to_byte(), + Self::ReadDataByIdentifier(_) => { + UdsServiceType::ReadDataByIdentifier.response_to_byte() + } + Self::ReadDTCInfo(_) => UdsServiceType::ReadDTCInfo.response_to_byte(), + Self::RequestDownload(_) => UdsServiceType::RequestDownload.response_to_byte(), + Self::RequestFileTransfer(_) => UdsServiceType::RequestFileTransfer.response_to_byte(), + Self::RequestTransferExit => UdsServiceType::RequestTransferExit.response_to_byte(), + Self::RoutineControl { .. } => UdsServiceType::RoutineControl.response_to_byte(), + Self::SecurityAccess(_) => UdsServiceType::SecurityAccess.response_to_byte(), + Self::TesterPresent(_) => UdsServiceType::TesterPresent.response_to_byte(), + Self::TransferData(_) => UdsServiceType::TransferData.response_to_byte(), + Self::WriteDataByIdentifier(_) => { + UdsServiceType::WriteDataByIdentifier.response_to_byte() } - }) + } + } +} + +impl Encode for Response<'_> { + fn encoded_size(&self) -> usize { + let payload = match self { + Self::ClearDiagnosticInfo | Self::RequestTransferExit => 0, + Self::CommunicationControl(resp) => resp.encoded_size(), + Self::ControlDTCSettings(resp) => resp.encoded_size(), + Self::DiagnosticSessionControl(resp) => resp.encoded_size(), + Self::EcuReset(resp) => resp.encoded_size(), + Self::NegativeResponse(resp) => resp.encoded_size(), + Self::ReadDataByIdentifier(bytes) | Self::WriteDataByIdentifier(bytes) => bytes.len(), + Self::ReadDTCInfo(resp) => resp.encoded_size(), + Self::RequestDownload(resp) => resp.encoded_size(), + Self::RequestFileTransfer(resp) => resp.encoded_size(), + Self::RoutineControl { + raw_status_record, .. + } => 1 + raw_status_record.len(), + Self::SecurityAccess(resp) => resp.encoded_size(), + Self::TesterPresent(resp) => resp.encoded_size(), + Self::TransferData(resp) => resp.encoded_size(), + }; + 1 + payload + } + + fn encode(&self, writer: &mut impl embedded_io::Write) -> Result { + writer + .write_all(&[self.response_sid()]) + .map_err(Error::io)?; + let payload = match self { + Self::ClearDiagnosticInfo | Self::RequestTransferExit => 0, + Self::CommunicationControl(resp) => resp.encode(writer)?, + Self::ControlDTCSettings(resp) => resp.encode(writer)?, + Self::DiagnosticSessionControl(resp) => resp.encode(writer)?, + Self::EcuReset(resp) => resp.encode(writer)?, + Self::NegativeResponse(resp) => resp.encode(writer)?, + Self::ReadDataByIdentifier(bytes) | Self::WriteDataByIdentifier(bytes) => { + writer.write_all(bytes).map_err(Error::io)?; + bytes.len() + } + Self::ReadDTCInfo(resp) => resp.encode(writer)?, + Self::RequestDownload(resp) => resp.encode(writer)?, + Self::RequestFileTransfer(resp) => resp.encode(writer)?, + Self::RoutineControl { + routine_control_type, + raw_status_record, + } => { + writer + .write_all(&[*routine_control_type]) + .map_err(Error::io)?; + writer.write_all(raw_status_record).map_err(Error::io)?; + 1 + raw_status_record.len() + } + Self::SecurityAccess(resp) => resp.encode(writer)?, + Self::TesterPresent(resp) => resp.encode(writer)?, + Self::TransferData(resp) => resp.encode(writer)?, + }; + Ok(1 + payload) + } +} + +/// Zero-copy raw RX response. Borrows from the wire buffer. +#[derive(Clone, Debug)] +pub struct UdsResponse<'a> { + /// The service this response corresponds to. + pub service: UdsServiceType, + /// The raw payload bytes following the service identifier. + pub data: &'a [u8], +} + +impl<'a> Decode<'a> for UdsResponse<'a> { + fn decode(buf: &'a [u8]) -> Result<(Self, &'a [u8]), Error> { + if buf.is_empty() { + return Err(Error::InsufficientData(1)); + } + Ok(( + Self { + service: UdsServiceType::response_from_byte(buf[0]), + data: &buf[1..], + }, + &[], + )) } } diff --git a/src/service.rs b/src/service.rs index 6db299f..475d527 100644 --- a/src/service.rs +++ b/src/service.rs @@ -283,8 +283,8 @@ impl UdsServiceType { } } -impl std::fmt::Display for UdsServiceType { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { +impl core::fmt::Display for UdsServiceType { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { write!(f, "{self:?}") } } diff --git a/src/services/clear_dtc_information.rs b/src/services/clear_dtc_information.rs index 2100886..b5f9780 100644 --- a/src/services/clear_dtc_information.rs +++ b/src/services/clear_dtc_information.rs @@ -1,6 +1,5 @@ //! `ClearDiagnosticInformation` (0x14) service implementation -use crate::{CLEAR_ALL_DTCS, DTCRecord, NegativeResponseCode, SingleValueWireFormat, WireFormat}; -use byteorder::{ReadBytesExt, WriteBytesExt}; +use crate::{CLEAR_ALL_DTCS, DTCRecord, Decode, Encode, NegativeResponseCode}; /// Negative response codes const CLEAR_DIAG_INFO_NEGATIVE_RESPONSE_CODES: [NegativeResponseCode; 4] = [ @@ -48,29 +47,34 @@ impl ClearDiagnosticInfoRequest { } } -impl WireFormat for ClearDiagnosticInfoRequest { - fn required_size(&self) -> usize { - self.group_of_dtc.required_size() + 1 +impl Encode for ClearDiagnosticInfoRequest { + fn encoded_size(&self) -> usize { + 4 // DTCRecord (3) + memory_selection (1) } - fn encode(&self, writer: &mut T) -> Result { - let mut size = 0; - size += self.group_of_dtc.encode(writer)?; - writer.write_u8(self.memory_selection)?; - size += 1; - Ok(size) + fn encode(&self, writer: &mut impl embedded_io::Write) -> Result { + let size = Encode::encode(&self.group_of_dtc, writer)?; + writer + .write_all(&[self.memory_selection]) + .map_err(crate::Error::io)?; + Ok(size + 1) } } -impl SingleValueWireFormat for ClearDiagnosticInfoRequest { - fn decode(reader: &mut T) -> Result { - let group_of_dtc = DTCRecord::decode(reader)?; - let memory_selection = reader.read_u8()?; - - Ok(Self { - group_of_dtc, - memory_selection, - }) +impl<'a> Decode<'a> for ClearDiagnosticInfoRequest { + fn decode(buf: &'a [u8]) -> Result<(Self, &'a [u8]), crate::Error> { + let (group_of_dtc, buf) = ::decode(buf)?; + if buf.is_empty() { + return Err(crate::Error::InsufficientData(4)); + } + let memory_selection = buf[0]; + Ok(( + Self { + group_of_dtc, + memory_selection, + }, + &buf[1..], + )) } } @@ -78,18 +82,19 @@ impl SingleValueWireFormat for ClearDiagnosticInfoRequest { #[cfg(test)] mod request { use super::*; + use crate::{Decode, Encode}; #[test] fn decode_clear_dtc_info_request() { let bytes = [0xFF, 0xFF, 0xFF, 0x00]; let compare = ClearDiagnosticInfoRequest::new(CLEAR_ALL_DTCS, 0); - let req = ClearDiagnosticInfoRequest::decode(&mut &bytes[..]).unwrap(); + let (req, _) = ::decode(&bytes).unwrap(); assert_eq!(req, compare); - let mut bytes = vec![]; - let written = req.encode(&mut bytes).unwrap(); - assert_eq!(bytes, [0xFF, 0xFF, 0xFF, 0x00]); - assert_eq!(req.required_size(), written); + let mut buf = vec![]; + let written = Encode::encode(&req, &mut buf).unwrap(); + assert_eq!(buf, [0xFF, 0xFF, 0xFF, 0x00]); + assert_eq!(req.encoded_size(), written); } #[test] diff --git a/src/services/communication_control.rs b/src/services/communication_control.rs index f8713c1..cef0fdf 100644 --- a/src/services/communication_control.rs +++ b/src/services/communication_control.rs @@ -1,9 +1,8 @@ //! `CommunicationControl` (0x28) service implementation use crate::{ - CommunicationControlType, CommunicationType, Error, NegativeResponseCode, - SingleValueWireFormat, SuppressablePositiveResponse, WireFormat, + CommunicationControlType, CommunicationType, Decode, Encode, Error, NegativeResponseCode, + SuppressablePositiveResponse, }; -use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt}; const COMMUNICATION_CONTROL_NEGATIVE_RESPONSE_CODES: [NegativeResponseCode; 4] = [ NegativeResponseCode::SubFunctionNotSupported, @@ -31,7 +30,12 @@ pub struct CommunicationControlRequest { } impl CommunicationControlRequest { - pub(crate) fn new( + /// Create a `CommunicationControlRequest` with standard address information. + /// + /// # Panics + /// Panics (debug) if an extended-address control type is passed. + #[must_use] + pub fn new( suppress_positive_response: bool, control_type: CommunicationControlType, communication_type: CommunicationType, @@ -47,7 +51,12 @@ impl CommunicationControlRequest { } } - pub(crate) fn new_with_node_id( + /// Create a `CommunicationControlRequest` with enhanced address information. + /// + /// # Panics + /// Panics if a non-extended control type is passed. + #[must_use] + pub fn new_with_node_id( suppress_positive_response: bool, control_type: CommunicationControlType, communication_type: CommunicationType, @@ -82,16 +91,20 @@ impl CommunicationControlRequest { &COMMUNICATION_CONTROL_NEGATIVE_RESPONSE_CODES } } -impl WireFormat for CommunicationControlRequest { - fn required_size(&self) -> usize { +impl Encode for CommunicationControlRequest { + fn encoded_size(&self) -> usize { if self.node_id.is_some() { 4 } else { 2 } } - fn encode(&self, writer: &mut T) -> Result { - writer.write_u8(u8::from(self.control_type))?; - writer.write_u8(u8::from(self.communication_type))?; + fn encode(&self, writer: &mut impl embedded_io::Write) -> Result { + writer + .write_all(&[ + u8::from(self.control_type), + u8::from(self.communication_type), + ]) + .map_err(Error::io)?; if let Some(id) = self.node_id { - writer.write_u16::(id)?; + writer.write_all(&id.to_be_bytes()).map_err(Error::io)?; Ok(4) } else { Ok(2) @@ -99,26 +112,37 @@ impl WireFormat for CommunicationControlRequest { } } -impl SingleValueWireFormat for CommunicationControlRequest { - fn decode(reader: &mut T) -> Result { - let enable_byte = reader.read_u8()?; - let communication_enable = SuppressablePositiveResponse::try_from(enable_byte)?; - let communication_type = CommunicationType::try_from(reader.read_u8()?)?; +impl<'a> Decode<'a> for CommunicationControlRequest { + fn decode(buf: &'a [u8]) -> Result<(Self, &'a [u8]), Error> { + if buf.len() < 2 { + return Err(Error::InsufficientData(2)); + } + let communication_enable = SuppressablePositiveResponse::try_from(buf[0])?; + let communication_type = CommunicationType::try_from(buf[1])?; match communication_enable.value() { CommunicationControlType::EnableRxAndDisableTxWithEnhancedAddressInfo | CommunicationControlType::EnableRxAndTxWithEnhancedAddressInfo => { - let node_id = Some(reader.read_u16::()?); - Ok(Self { + if buf.len() < 4 { + return Err(Error::InsufficientData(4)); + } + let node_id = Some(u16::from_be_bytes([buf[2], buf[3]])); + Ok(( + Self { + control_type: communication_enable, + communication_type, + node_id, + }, + &buf[4..], + )) + } + _ => Ok(( + Self { control_type: communication_enable, communication_type, - node_id, - }) - } - _ => Ok(Self { - control_type: communication_enable, - communication_type, - node_id: None, - }), + node_id: None, + }, + &buf[2..], + )), } } } @@ -134,37 +158,45 @@ pub struct CommunicationControlResponse { } impl CommunicationControlResponse { - pub(crate) fn new(control_type: CommunicationControlType) -> Self { + /// Create a new `CommunicationControlResponse`. + #[must_use] + pub fn new(control_type: CommunicationControlType) -> Self { Self { control_type } } } -impl WireFormat for CommunicationControlResponse { - fn required_size(&self) -> usize { +impl Encode for CommunicationControlResponse { + fn encoded_size(&self) -> usize { 1 } - fn encode(&self, writer: &mut T) -> Result { - writer.write_u8(u8::from(self.control_type))?; + fn encode(&self, writer: &mut impl embedded_io::Write) -> Result { + writer + .write_all(&[u8::from(self.control_type)]) + .map_err(Error::io)?; Ok(1) } } -impl SingleValueWireFormat for CommunicationControlResponse { - fn decode(reader: &mut T) -> Result { - let control_type = CommunicationControlType::try_from(reader.read_u8()?)?; - Ok(Self::new(control_type)) +impl<'a> Decode<'a> for CommunicationControlResponse { + fn decode(buf: &'a [u8]) -> Result<(Self, &'a [u8]), Error> { + if buf.is_empty() { + return Err(Error::InsufficientData(1)); + } + let control_type = CommunicationControlType::try_from(buf[0])?; + Ok((Self::new(control_type), &buf[1..])) } } #[cfg(test)] mod request { use super::*; + use crate::{Decode, Encode}; #[test] fn simple_request() { let bytes: [u8; 3] = [0x01, 0x02, 0x03]; - let req = CommunicationControlRequest::decode(&mut bytes.as_slice()).unwrap(); + let (req, _) = ::decode(&bytes).unwrap(); assert_eq!( req.control_type(), CommunicationControlType::EnableRxAndDisableTx @@ -173,15 +205,15 @@ mod request { assert_eq!(req.node_id, None); let mut buffer = Vec::new(); - let written = req.encode(&mut buffer).unwrap(); - assert_eq!(written, req.required_size()); - assert_eq!(buffer.len(), req.required_size()); + let written = Encode::encode(&req, &mut buffer).unwrap(); + assert_eq!(written, req.encoded_size()); + assert_eq!(buffer.len(), req.encoded_size()); } #[test] fn node_id() { let bytes: [u8; 4] = [0x05, 0x02, 0x01, 0x02]; - let req = CommunicationControlRequest::decode(&mut bytes.as_slice()).unwrap(); + let (req, _) = ::decode(&bytes).unwrap(); assert_eq!( req.control_type(), CommunicationControlType::EnableRxAndTxWithEnhancedAddressInfo @@ -190,9 +222,9 @@ mod request { assert_eq!(req.node_id, Some(258)); let mut buffer = Vec::new(); - let written = req.encode(&mut buffer).unwrap(); - assert_eq!(written, req.required_size()); - assert_eq!(buffer.len(), req.required_size()); + let written = Encode::encode(&req, &mut buffer).unwrap(); + assert_eq!(written, req.encoded_size()); + assert_eq!(buffer.len(), req.encoded_size()); } #[test] @@ -222,18 +254,19 @@ mod request { #[cfg(test)] mod response { use super::*; + use crate::{Decode, Encode}; #[test] fn simple_response() { let bytes: [u8; 1] = [0x01]; - let res = CommunicationControlResponse::decode(&mut bytes.as_slice()).unwrap(); + let (res, _) = ::decode(&bytes).unwrap(); assert_eq!( res.control_type, CommunicationControlType::EnableRxAndDisableTx ); let mut buffer = Vec::new(); - let written = res.encode(&mut buffer).unwrap(); + let written = Encode::encode(&res, &mut buffer).unwrap(); assert_eq!(written, 1); assert_eq!(buffer.len(), written); } diff --git a/src/services/control_dtc_settings.rs b/src/services/control_dtc_settings.rs index 81a33c6..db4b165 100644 --- a/src/services/control_dtc_settings.rs +++ b/src/services/control_dtc_settings.rs @@ -1,6 +1,5 @@ //! `ControlDTCSetting` (0x85) service implementation -use crate::{DtcSettings, Error, SUCCESS, SingleValueWireFormat, WireFormat}; -use byteorder::{ReadBytesExt, WriteBytesExt}; +use crate::{Decode, DtcSettings, Encode, Error, SUCCESS}; /// The `ControlDTCSettings` service is used to control the DTC settings of the ECU. #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] @@ -15,7 +14,9 @@ pub struct ControlDTCSettingsRequest { } impl ControlDTCSettingsRequest { - pub(crate) fn new(setting: DtcSettings, suppress_response: bool) -> Self { + /// Create a new `ControlDTCSettingsRequest`. + #[must_use] + pub fn new(setting: DtcSettings, suppress_response: bool) -> Self { Self { setting, suppress_response, @@ -23,15 +24,15 @@ impl ControlDTCSettingsRequest { } } -impl WireFormat for ControlDTCSettingsRequest { - fn required_size(&self) -> usize { +impl Encode for ControlDTCSettingsRequest { + fn encoded_size(&self) -> usize { 1 } - fn encode(&self, writer: &mut T) -> Result { + fn encode(&self, writer: &mut impl embedded_io::Write) -> Result { let request_byte = u8::from(self.setting) | if self.suppress_response { SUCCESS } else { 0 }; - writer.write_u8(request_byte)?; + writer.write_all(&[request_byte]).map_err(Error::io)?; Ok(1) } @@ -40,15 +41,21 @@ impl WireFormat for ControlDTCSettingsRequest { } } -impl SingleValueWireFormat for ControlDTCSettingsRequest { - fn decode(reader: &mut T) -> Result { - let request_byte = reader.read_u8()?; - let setting = DtcSettings::from(request_byte & !SUCCESS); +impl<'a> Decode<'a> for ControlDTCSettingsRequest { + fn decode(buf: &'a [u8]) -> Result<(Self, &'a [u8]), Error> { + if buf.is_empty() { + return Err(Error::InsufficientData(1)); + } + let request_byte = buf[0]; + let setting = DtcSettings::try_from(request_byte & !SUCCESS)?; let suppress_response = request_byte & SUCCESS != 0; - Ok(Self { - setting, - suppress_response, - }) + Ok(( + Self { + setting, + suppress_response, + }, + &buf[1..], + )) } } @@ -65,44 +72,51 @@ pub struct ControlDTCSettingsResponse { } impl ControlDTCSettingsResponse { - pub(crate) fn new(setting: DtcSettings) -> Self { + /// Create a new `ControlDTCSettingsResponse`. + #[must_use] + pub fn new(setting: DtcSettings) -> Self { Self { setting } } } -impl WireFormat for ControlDTCSettingsResponse { - fn required_size(&self) -> usize { +impl Encode for ControlDTCSettingsResponse { + fn encoded_size(&self) -> usize { 1 } - fn encode(&self, writer: &mut T) -> Result { - writer.write_u8(u8::from(self.setting))?; + fn encode(&self, writer: &mut impl embedded_io::Write) -> Result { + writer + .write_all(&[u8::from(self.setting)]) + .map_err(Error::io)?; Ok(1) } } -impl SingleValueWireFormat for ControlDTCSettingsResponse { - fn decode(reader: &mut T) -> Result { - let setting = DtcSettings::from(reader.read_u8()?); - Ok(Self { setting }) +impl<'a> Decode<'a> for ControlDTCSettingsResponse { + fn decode(buf: &'a [u8]) -> Result<(Self, &'a [u8]), Error> { + if buf.is_empty() { + return Err(Error::InsufficientData(1)); + } + let setting = DtcSettings::try_from(buf[0])?; + Ok((Self { setting }, &buf[1..])) } } #[cfg(test)] mod request { use super::*; - use crate::DtcSettings; + use crate::{Decode, DtcSettings, Encode}; #[test] fn simple_request() { let req = ControlDTCSettingsRequest::new(DtcSettings::On, true); let mut buffer = Vec::new(); - let written = req.encode(&mut buffer).unwrap(); + let written = Encode::encode(&req, &mut buffer).unwrap(); assert_eq!(buffer, vec![0x81]); assert_eq!(written, buffer.len()); - assert_eq!(req.required_size(), buffer.len()); + assert_eq!(req.encoded_size(), buffer.len()); - let parsed = ControlDTCSettingsRequest::decode(&mut buffer.as_slice()).unwrap(); + let (parsed, _) = ::decode(&buffer).unwrap(); assert_eq!(parsed.setting, DtcSettings::On); assert!(parsed.suppress_response); } @@ -111,18 +125,18 @@ mod request { #[cfg(test)] mod response { use super::*; - use crate::DtcSettings; + use crate::{Decode, DtcSettings, Encode}; #[test] fn simple_response() { let req = ControlDTCSettingsResponse::new(DtcSettings::On); let mut buffer = Vec::new(); - let written = req.encode(&mut buffer).unwrap(); + let written = Encode::encode(&req, &mut buffer).unwrap(); assert_eq!(buffer, vec![0x01]); assert_eq!(written, buffer.len()); - assert_eq!(req.required_size(), buffer.len()); + assert_eq!(req.encoded_size(), buffer.len()); - let parsed = ControlDTCSettingsResponse::decode(&mut buffer.as_slice()).unwrap(); + let (parsed, _) = ::decode(&buffer).unwrap(); assert_eq!(parsed.setting, DtcSettings::On); } } diff --git a/src/services/diagnostic_session_control.rs b/src/services/diagnostic_session_control.rs index 50c9ab5..8ed8b4c 100644 --- a/src/services/diagnostic_session_control.rs +++ b/src/services/diagnostic_session_control.rs @@ -10,10 +10,9 @@ //! as well as in other operation conditions defined by the vehicle manufacturer (e.g. limp home operation condition). use crate::{ - DiagnosticSessionType, Error, NegativeResponseCode, SingleValueWireFormat, - SuppressablePositiveResponse, WireFormat, + Decode, DiagnosticSessionType, Encode, Error, NegativeResponseCode, + SuppressablePositiveResponse, }; -use byteorder::{ReadBytesExt, WriteBytesExt}; const DIAGNOSTIC_SESSION_CONTROL_NEGATIVE_RESPONSE_CODES: [NegativeResponseCode; 3] = [ NegativeResponseCode::SubFunctionNotSupported, @@ -31,10 +30,8 @@ pub struct DiagnosticSessionControlRequest { impl DiagnosticSessionControlRequest { /// Create a new `DiagnosticSessionControlRequest` - pub(crate) fn new( - suppress_positive_response: bool, - session_type: DiagnosticSessionType, - ) -> Self { + #[must_use] + pub fn new(suppress_positive_response: bool, session_type: DiagnosticSessionType) -> Self { Self { session_type: SuppressablePositiveResponse::new( suppress_positive_response, @@ -61,13 +58,15 @@ impl DiagnosticSessionControlRequest { &DIAGNOSTIC_SESSION_CONTROL_NEGATIVE_RESPONSE_CODES } } -impl WireFormat for DiagnosticSessionControlRequest { - fn required_size(&self) -> usize { +impl Encode for DiagnosticSessionControlRequest { + fn encoded_size(&self) -> usize { 1 } - fn encode(&self, writer: &mut T) -> Result { - writer.write_u8(u8::from(self.session_type))?; + fn encode(&self, writer: &mut impl embedded_io::Write) -> Result { + writer + .write_all(&[u8::from(self.session_type)]) + .map_err(Error::io)?; Ok(1) } @@ -76,10 +75,13 @@ impl WireFormat for DiagnosticSessionControlRequest { } } -impl SingleValueWireFormat for DiagnosticSessionControlRequest { - fn decode(reader: &mut T) -> Result { - let session_type = SuppressablePositiveResponse::try_from(reader.read_u8()?)?; - Ok(Self { session_type }) +impl<'a> Decode<'a> for DiagnosticSessionControlRequest { + fn decode(buf: &'a [u8]) -> Result<(Self, &'a [u8]), Error> { + if buf.is_empty() { + return Err(Error::InsufficientData(1)); + } + let session_type = SuppressablePositiveResponse::try_from(buf[0])?; + Ok((Self { session_type }, &buf[1..])) } } @@ -99,7 +101,8 @@ pub struct DiagnosticSessionControlResponse { impl DiagnosticSessionControlResponse { /// Create a new `DiagnosticSessionControlResponse` - pub(crate) fn new( + #[must_use] + pub fn new( session_type: DiagnosticSessionType, p2_server_max: u16, p2_star_server_max: u16, @@ -111,43 +114,53 @@ impl DiagnosticSessionControlResponse { } } } -impl WireFormat for DiagnosticSessionControlResponse { - fn required_size(&self) -> usize { +impl Encode for DiagnosticSessionControlResponse { + fn encoded_size(&self) -> usize { 5 } - fn encode(&self, buffer: &mut T) -> Result { - buffer.write_u8(u8::from(self.session_type))?; - buffer.write_u16::(self.p2_server_max)?; - buffer.write_u16::(self.p2_star_server_max)?; - + fn encode(&self, writer: &mut impl embedded_io::Write) -> Result { + writer + .write_all(&[u8::from(self.session_type)]) + .map_err(Error::io)?; + writer + .write_all(&self.p2_server_max.to_be_bytes()) + .map_err(Error::io)?; + writer + .write_all(&self.p2_star_server_max.to_be_bytes()) + .map_err(Error::io)?; Ok(5) } } -impl SingleValueWireFormat for DiagnosticSessionControlResponse { - fn decode(reader: &mut T) -> Result { - let session_type = DiagnosticSessionType::try_from(reader.read_u8()?)?; - let p2_server_max = reader.read_u16::()?; - let p2_star_server_max = reader.read_u16::()?; - Ok(Self { - session_type, - p2_server_max, - p2_star_server_max, - }) +impl<'a> Decode<'a> for DiagnosticSessionControlResponse { + fn decode(buf: &'a [u8]) -> Result<(Self, &'a [u8]), Error> { + if buf.len() < 5 { + return Err(Error::InsufficientData(5)); + } + let session_type = DiagnosticSessionType::try_from(buf[0])?; + let p2_server_max = u16::from_be_bytes([buf[1], buf[2]]); + let p2_star_server_max = u16::from_be_bytes([buf[3], buf[4]]); + Ok(( + Self { + session_type, + p2_server_max, + p2_star_server_max, + }, + &buf[5..], + )) } } #[cfg(test)] mod request { use super::*; - use crate::DiagnosticSessionType; + use crate::{Decode, DiagnosticSessionType, Encode}; #[test] fn test_diagnostic_session_control_request() { let bytes: [u8; 1] = [0x02]; - let req: DiagnosticSessionControlRequest = - DiagnosticSessionControlRequest::decode(&mut bytes.as_slice()).unwrap(); + let (req, _) = ::decode(&bytes).unwrap(); assert!(!req.suppress_positive_response()); assert_eq!( req.session_type(), @@ -155,29 +168,28 @@ mod request { ); let mut buffer = Vec::new(); - req.encode(&mut buffer).unwrap(); + Encode::encode(&req, &mut buffer).unwrap(); assert_eq!(buffer, bytes); - assert_eq!(req.required_size(), 1); + assert_eq!(req.encoded_size(), 1); } } #[cfg(test)] mod response { use super::*; - use crate::DiagnosticSessionType; + use crate::{Decode, DiagnosticSessionType, Encode}; #[test] fn test_diagnostic_session_control_response() { let bytes = [0x02, 0x11, 0x22, 0x33, 0x44]; - let resp: DiagnosticSessionControlResponse = - DiagnosticSessionControlResponse::decode(&mut bytes.as_slice()).unwrap(); + let (resp, _) = ::decode(&bytes).unwrap(); assert_eq!(resp.session_type, DiagnosticSessionType::ProgrammingSession); assert_eq!(resp.p2_server_max, 0x1122); assert_eq!(resp.p2_star_server_max, 0x3344); let mut buffer = Vec::new(); - resp.encode(&mut buffer).unwrap(); + Encode::encode(&resp, &mut buffer).unwrap(); assert_eq!(buffer, bytes); - assert_eq!(resp.required_size(), 5); + assert_eq!(resp.encoded_size(), 5); } } diff --git a/src/services/ecu_reset.rs b/src/services/ecu_reset.rs index 3e879b6..5a90f00 100644 --- a/src/services/ecu_reset.rs +++ b/src/services/ecu_reset.rs @@ -1,10 +1,5 @@ //! `ECUReset` (0x11) service implementation -use crate::{ - Error, NegativeResponseCode, ResetType, SingleValueWireFormat, SuppressablePositiveResponse, - WireFormat, -}; -use byteorder::{ReadBytesExt, WriteBytesExt}; -use std::io::{Read, Write}; +use crate::{Decode, Encode, Error, NegativeResponseCode, ResetType, SuppressablePositiveResponse}; const ECU_RESET_NEGATIVE_RESPONSE_CODES: [NegativeResponseCode; 4] = [ NegativeResponseCode::SubFunctionNotSupported, @@ -23,7 +18,8 @@ pub struct EcuResetRequest { impl EcuResetRequest { /// Create a new '`EcuResetRequest`' - pub(crate) fn new(suppress_positive_response: bool, reset_type: ResetType) -> Self { + #[must_use] + pub fn new(suppress_positive_response: bool, reset_type: ResetType) -> Self { Self { reset_type: SuppressablePositiveResponse::new(suppress_positive_response, reset_type), } @@ -48,13 +44,15 @@ impl EcuResetRequest { } } -impl WireFormat for EcuResetRequest { - fn required_size(&self) -> usize { +impl Encode for EcuResetRequest { + fn encoded_size(&self) -> usize { 1 } - fn encode(&self, writer: &mut T) -> Result { - writer.write_u8(u8::from(self.reset_type))?; + fn encode(&self, writer: &mut impl embedded_io::Write) -> Result { + writer + .write_all(&[u8::from(self.reset_type)]) + .map_err(Error::io)?; Ok(1) } @@ -63,10 +61,13 @@ impl WireFormat for EcuResetRequest { } } -impl SingleValueWireFormat for EcuResetRequest { - fn decode(reader: &mut T) -> Result { - let reset_type = SuppressablePositiveResponse::try_from(reader.read_u8()?)?; - Ok(Self { reset_type }) +impl<'a> Decode<'a> for EcuResetRequest { + fn decode(buf: &'a [u8]) -> Result<(Self, &'a [u8]), Error> { + if buf.is_empty() { + return Err(Error::InsufficientData(1)); + } + let reset_type = SuppressablePositiveResponse::try_from(buf[0])?; + Ok((Self { reset_type }, &buf[1..])) } } @@ -84,7 +85,8 @@ pub struct EcuResetResponse { impl EcuResetResponse { /// Create a new '`EcuResetResponse`' - pub(crate) fn new(reset_type: ResetType, power_down_time: u8) -> Self { + #[must_use] + pub fn new(reset_type: ResetType, power_down_time: u8) -> Self { Self { reset_type, power_down_time, @@ -92,63 +94,72 @@ impl EcuResetResponse { } } -impl WireFormat for EcuResetResponse { - fn required_size(&self) -> usize { +impl Encode for EcuResetResponse { + fn encoded_size(&self) -> usize { 2 } - fn encode(&self, buffer: &mut T) -> Result { - buffer.write_u8(u8::from(self.reset_type))?; - buffer.write_u8(self.power_down_time)?; + fn encode(&self, writer: &mut impl embedded_io::Write) -> Result { + writer + .write_all(&[u8::from(self.reset_type), self.power_down_time]) + .map_err(Error::io)?; Ok(2) } } -impl SingleValueWireFormat for EcuResetResponse { - fn decode(reader: &mut T) -> Result { - let reset_type = ResetType::try_from(reader.read_u8()?)?; - // powerDownTime is conditional per ISO 14229-1 — only present when - // the server needs to report how long until power-down. - let power_down_time = reader.read_u8().unwrap_or(0); - Ok(Self { - reset_type, - power_down_time, - }) +impl<'a> Decode<'a> for EcuResetResponse { + fn decode(buf: &'a [u8]) -> Result<(Self, &'a [u8]), Error> { + if buf.is_empty() { + return Err(Error::InsufficientData(1)); + } + let reset_type = ResetType::try_from(buf[0])?; + // powerDownTime is conditional per ISO 14229-1 + let power_down_time = buf.get(1).copied().unwrap_or(0); + let consumed = core::cmp::min(buf.len(), 2); + Ok(( + Self { + reset_type, + power_down_time, + }, + &buf[consumed..], + )) } } #[cfg(test)] mod request { use super::*; + use crate::{Decode, Encode}; #[test] fn ecu_reset_request() { let bytes: [u8; 2] = [0x81, 0x00]; let req = EcuResetRequest::new(true, ResetType::HardReset); let mut buffer = Vec::new(); - let written = req.encode(&mut buffer).unwrap(); - let result = EcuResetRequest::decode(&mut bytes.as_slice()).unwrap(); + let written = Encode::encode(&req, &mut buffer).unwrap(); + let (result, _) = ::decode(&bytes).unwrap(); assert_eq!(result, req); assert_eq!(written, 1); - assert_eq!(written, req.required_size()); + assert_eq!(written, req.encoded_size()); } } #[cfg(test)] mod response { use super::*; + use crate::{Decode, Encode}; #[test] fn ecu_reset_response() { let bytes: [u8; 2] = [0x01, 0x20]; let resp = EcuResetResponse::new(ResetType::HardReset, 0x20); let mut buffer = Vec::new(); - let written = resp.encode(&mut buffer).unwrap(); - let result = EcuResetResponse::decode(&mut bytes.as_slice()).unwrap(); + let written = Encode::encode(&resp, &mut buffer).unwrap(); + let (result, _) = ::decode(&bytes).unwrap(); assert_eq!(result, resp); assert_eq!(written, 2); - assert_eq!(written, resp.required_size()); + assert_eq!(written, resp.encoded_size()); } } diff --git a/src/services/mod.rs b/src/services/mod.rs index 4fa5330..21a4a24 100644 --- a/src/services/mod.rs +++ b/src/services/mod.rs @@ -19,30 +19,34 @@ mod negative_response; pub use negative_response::NegativeResponse; mod read_data_by_identifier; -pub use read_data_by_identifier::{ReadDataByIdentifierRequest, ReadDataByIdentifierResponse}; +pub use read_data_by_identifier::ReadDataByIdentifierRequestTx; mod read_dtc_information; -pub use read_dtc_information::{ReadDTCInfoRequest, ReadDTCInfoResponse, ReadDTCInfoSubFunction}; +pub use read_dtc_information::{ + DtcAndStatusIter, DtcFaultDetectionIter, DtcSeverityAndStatusIter, ReadDTCInfoRequest, + ReadDTCInfoResponseRx, ReadDTCInfoSubFunction, +}; mod request_download; -pub use request_download::{RequestDownloadRequest, RequestDownloadResponse}; +pub use request_download::{RequestDownloadRequest, RequestDownloadResponseTx}; mod request_file_transfer; pub use request_file_transfer::{ - FileOperationMode, RequestFileTransferRequest, RequestFileTransferResponse, + DirSizePayload, FileOperationMode, FileSizePayload, NamePayloadTx, PositionPayload, + RequestFileTransferRequestTx, RequestFileTransferResponseTx, SentDataPayloadTx, SizePayload, }; mod routine_control; pub use routine_control::{RoutineControlRequest, RoutineControlResponse}; mod security_access; -pub use security_access::{SecurityAccessRequest, SecurityAccessResponse}; +pub use security_access::{SecurityAccessRequestTx, SecurityAccessResponseTx}; mod tester_present; pub use tester_present::{TesterPresentRequest, TesterPresentResponse}; mod transfer_data; -pub use transfer_data::{TransferDataRequest, TransferDataResponse}; +pub use transfer_data::{TransferDataRequestTx, TransferDataResponseTx}; mod write_data_by_identifier; pub use write_data_by_identifier::{WriteDataByIdentifierRequest, WriteDataByIdentifierResponse}; diff --git a/src/services/negative_response.rs b/src/services/negative_response.rs index 06d4a90..34a6a56 100644 --- a/src/services/negative_response.rs +++ b/src/services/negative_response.rs @@ -1,6 +1,5 @@ //! `NegativeResponse` (0x7F) service implementation -use crate::{Error, NegativeResponseCode, SingleValueWireFormat, UdsServiceType, WireFormat}; -use byteorder::{ReadBytesExt, WriteBytesExt}; +use crate::{Decode, Encode, Error, NegativeResponseCode, UdsServiceType}; /// A negative response from the server indicating a request could not be fulfilled #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] @@ -16,7 +15,8 @@ pub struct NegativeResponse { impl NegativeResponse { /// Create a new `NegativeResponse` - pub(crate) fn new(request_service: UdsServiceType, nrc: NegativeResponseCode) -> Self { + #[must_use] + pub fn new(request_service: UdsServiceType, nrc: NegativeResponseCode) -> Self { Self { request_service, nrc, @@ -24,25 +24,35 @@ impl NegativeResponse { } } -impl WireFormat for NegativeResponse { - fn required_size(&self) -> usize { +impl Encode for NegativeResponse { + fn encoded_size(&self) -> usize { 2 } - fn encode(&self, writer: &mut T) -> Result { - writer.write_u8(self.request_service.request_service_to_byte())?; - writer.write_u8(u8::from(self.nrc))?; + fn encode(&self, writer: &mut impl embedded_io::Write) -> Result { + writer + .write_all(&[ + self.request_service.request_service_to_byte(), + u8::from(self.nrc), + ]) + .map_err(Error::io)?; Ok(2) } } -impl SingleValueWireFormat for NegativeResponse { - fn decode(reader: &mut T) -> Result { - let request_service = UdsServiceType::service_from_request_byte(reader.read_u8()?); - let nrc = NegativeResponseCode::from(reader.read_u8()?); - Ok(Self { - request_service, - nrc, - }) +impl<'a> Decode<'a> for NegativeResponse { + fn decode(buf: &'a [u8]) -> Result<(Self, &'a [u8]), Error> { + if buf.len() < 2 { + return Err(Error::InsufficientData(2)); + } + let request_service = UdsServiceType::service_from_request_byte(buf[0]); + let nrc = NegativeResponseCode::from(buf[1]); + Ok(( + Self { + request_service, + nrc, + }, + &buf[2..], + )) } } diff --git a/src/services/read_data_by_identifier.rs b/src/services/read_data_by_identifier.rs index bd3fc99..d5eb7a2 100644 --- a/src/services/read_data_by_identifier.rs +++ b/src/services/read_data_by_identifier.rs @@ -1,7 +1,5 @@ //! `ReadDataByIdentifier` (0x22) service implementation -use crate::{ - Error, Identifier, IterableWireFormat, NegativeResponseCode, SingleValueWireFormat, WireFormat, -}; +use crate::{Encode, Error, Identifier, NegativeResponseCode}; const READ_DID_NEGATIVE_RESPONSE_CODES: [NegativeResponseCode; 5] = [ NegativeResponseCode::IncorrectMessageLengthOrInvalidFormat, @@ -11,23 +9,19 @@ const READ_DID_NEGATIVE_RESPONSE_CODES: [NegativeResponseCode; 5] = [ NegativeResponseCode::SecurityAccessDenied, ]; +/// Zero-alloc TX request to read data by identifier. Borrows DID list from caller. +/// /// See ISO-14229-1:2020, Table 11.2.1 for format information -#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] -#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))] -#[derive(Clone, Debug, Eq, PartialEq)] -#[non_exhaustive] -pub struct ReadDataByIdentifierRequest { +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub struct ReadDataByIdentifierRequestTx<'d, DataIdentifier> { /// The list of Data Identifiers to read. - pub dids: Vec, + pub dids: &'d [DataIdentifier], } -impl ReadDataByIdentifierRequest { - /// Create a new request from a sequence of data identifiers - pub(crate) fn new(dids: I) -> Self - where - I: IntoIterator, - { - let dids = dids.into_iter().collect(); +impl<'d, DataIdentifier: Identifier> ReadDataByIdentifierRequestTx<'d, DataIdentifier> { + /// Create a new request from a slice of data identifiers. + #[must_use] + pub const fn new(dids: &'d [DataIdentifier]) -> Self { Self { dids } } @@ -38,94 +32,16 @@ impl ReadDataByIdentifierRequest { } } -impl WireFormat for ReadDataByIdentifierRequest { - fn required_size(&self) -> usize { +impl Encode for ReadDataByIdentifierRequestTx<'_, DataIdentifier> { + fn encoded_size(&self) -> usize { self.dids.len() * 2 } - fn encode(&self, writer: &mut W) -> Result { - let mut count = 0; - for did in &self.dids { - did.encode(writer)?; - count += 2; - } - Ok(count) - } -} - -impl SingleValueWireFormat - for ReadDataByIdentifierRequest -{ - fn decode(reader: &mut R) -> Result { - let dids = DataIdentifier::parse_from_list(reader)?; - if dids.is_empty() { - Err(Error::NoDataAvailable) - } else { - Ok(ReadDataByIdentifierRequest::new(dids)) - } - } -} - -/// See ISO-14229-1:2020, Table 11.2.3 for format information -#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] -#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))] -#[derive(Clone, Eq, PartialEq)] -#[non_exhaustive] -pub struct ReadDataByIdentifierResponse { - /// The decoded payload entries returned by the server. - pub data: Vec, -} - -impl ReadDataByIdentifierResponse { - pub(crate) fn new(data: I) -> Self - where - I: IntoIterator, - { - let data = data.into_iter().collect(); - Self { data } - } -} - -impl WireFormat for ReadDataByIdentifierResponse { - fn required_size(&self) -> usize { - self.data.iter().map(WireFormat::required_size).sum() - } - - fn encode(&self, writer: &mut W) -> Result { - let mut total_written = 0; - for payload in &self.data { - total_written += payload.encode(writer)?; - } - Ok(total_written) - } -} - -impl SingleValueWireFormat - for ReadDataByIdentifierResponse -{ - fn decode(reader: &mut R) -> Result { - let mut data = Vec::new(); - for payload in UserPayload::decode_iter(reader) { - match payload { - Ok(p) => { - data.push(p); - } - Err(e) => { - return Err(e); - } - } - } - if data.is_empty() { - Err(Error::NoDataAvailable) - } else { - Ok(ReadDataByIdentifierResponse::new(data)) + fn encode(&self, writer: &mut impl embedded_io::Write) -> Result { + for did in self.dids { + Encode::encode(did, writer)?; } - } -} - -impl std::fmt::Debug for ReadDataByIdentifierResponse { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "ReadDataByIdentifierResponse\n{:?}", self.data) + Ok(self.encoded_size()) } } @@ -134,368 +50,15 @@ mod test { use super::*; use crate::{ProtocolIdentifier, UDSIdentifier}; - mod request { - use super::*; - - struct ReadRequestTestData { - pub test_name: String, - pub dids_bytes: Vec, - } - - impl ReadRequestTestData { - // Creates a Test Read Request from a list of data identifiers - fn from_ids(test_name: &str, dids: &[ProtocolIdentifier]) -> Self { - let dids_bytes = to_bytes(dids); - Self { - test_name: test_name.to_string(), - dids_bytes, - } - } - - // Create a Test Read Request from a list of bytes. - // Note: These bytes may not properly translate to a list of data identifiers - fn from_bytes(test_name: &str, dids_bytes: Vec) -> Self { - Self { - test_name: test_name.to_string(), - dids_bytes, - } - } - } - - // Holds a list of dids that will be transformed into a byte sequence - struct WriteRequestTestData { - pub test_name: String, - pub dids: Vec, - } - - impl WriteRequestTestData { - fn from_ids(test_name: &str, dids: &[ProtocolIdentifier]) -> Self { - Self { - test_name: test_name.to_string(), - dids: dids.to_vec(), - } - } - } - - fn to_bytes(ids: &[ProtocolIdentifier]) -> Vec { - ids.iter() - .flat_map(|id: &ProtocolIdentifier| { - let mut buffer = Vec::new(); - id.encode(&mut buffer).unwrap(); - buffer - }) - .collect() - } - - fn get_test_ids() -> Vec { - vec![ - ProtocolIdentifier::new(UDSIdentifier::BootSoftwareIdentification), - ProtocolIdentifier::new(UDSIdentifier::ApplicationSoftwareIdentification), - ProtocolIdentifier::new(UDSIdentifier::ApplicationDataIdentification), - ProtocolIdentifier::new(UDSIdentifier::BootSoftwareFingerprint), - ProtocolIdentifier::new(UDSIdentifier::ApplicationSoftwareFingerprint), - ProtocolIdentifier::new(UDSIdentifier::ApplicationDataFingerprint), - ProtocolIdentifier::new(UDSIdentifier::ActiveDiagnosticSession), - ProtocolIdentifier::new(UDSIdentifier::VehicleManufacturerSparePartNumber), - ProtocolIdentifier::new(UDSIdentifier::VehicleManufacturerECUSoftwareNumber), - ProtocolIdentifier::new(UDSIdentifier::VehicleManufacturerECUSoftwareVersionNumber), - ] - } - - #[test] - fn read_did_request_bytes() { - let test_ids = get_test_ids(); - - let test_data_sets: Vec = vec![ - ReadRequestTestData::from_bytes("No ids", vec![]), - ReadRequestTestData::from_bytes("Invalid byte length", vec![0x00]), - ReadRequestTestData::from_bytes("Invalid id", vec![0x00, 0x01]), - ReadRequestTestData::from_ids("1 id", &test_ids[0..1]), - ReadRequestTestData::from_ids("2 ids", &test_ids[0..2]), - ReadRequestTestData::from_ids("3 ids", &test_ids[0..3]), - ReadRequestTestData::from_ids("4 ids", &test_ids[0..4]), - ReadRequestTestData::from_ids("All ids", &test_ids), - ReadRequestTestData::from_ids( - "Repeated ids", - &test_ids - .clone() - .iter() - .cycle() - .take(100) - .copied() - .collect::>(), - ), - ]; - - for test_data in &test_data_sets { - let read_result = ReadDataByIdentifierRequest::::decode( - &mut test_data.dids_bytes.as_slice(), - ); - - match read_result { - Ok(response) => { - let mut translated_bytes = Vec::new(); - response.encode(&mut translated_bytes).unwrap(); - assert_eq!( - translated_bytes, *test_data.dids_bytes, - "Ok: Failed: {}", - test_data.test_name - ); - } - Err(e) => { - if test_data.dids_bytes.is_empty() { - assert!( - matches!(e, Error::NoDataAvailable), - "NoDataAvailable: Failed {}", - test_data.test_name - ); - } else { - assert!( - matches!(e, Error::IncorrectMessageLengthOrInvalidFormat), - "IncorrectMessageLengthOrInvalidFormat: Failed {}", - test_data.test_name - ); - } - } - } - } - } - - #[test] - fn write_did_request_bytes() { - let test_ids = get_test_ids(); - - let test_data_sets: Vec = vec![ - WriteRequestTestData::from_ids("No ids", &Vec::new()), - WriteRequestTestData::from_ids("1 id", &test_ids[0..1]), - WriteRequestTestData::from_ids("2 ids", &test_ids[0..2]), - WriteRequestTestData::from_ids("3 ids", &test_ids[0..3]), - WriteRequestTestData::from_ids("4 ids", &test_ids[0..4]), - WriteRequestTestData::from_ids("All ids", &test_ids), - WriteRequestTestData::from_ids( - "Repeated ids", - &test_ids - .clone() - .iter() - .cycle() - .take(100) - .copied() - .collect::>(), - ), - ]; - - for test_data in &test_data_sets { - let request = ReadDataByIdentifierRequest::new(test_data.dids.clone()); - let mut buffer = Vec::new(); - let write_result = request.encode(&mut buffer); - - match write_result { - Ok(bytes_read) => { - // 1 did is 2 bytes - let expected_byte_count = request.dids.len() * 2; - assert_eq!(bytes_read, expected_byte_count); - } - Err(e) => { - assert!( - matches!(e, Error::InsufficientData(_)), - "InsufficientData: Failed {}", - test_data.test_name - ); - } - } - } - } - } - - mod response { - use super::*; - - #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] - #[derive(Clone, Copy, Debug, Eq, PartialEq)] - pub struct BazData { - pub data: [u8; 16], - pub data2: u64, - pub data3: u16, - } - - // The UDSIdentifiers are vender defined and don't have interesting payloads, so we define our own types for - #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] - #[derive(Clone, Debug, Eq, PartialEq)] - pub enum TestPayload { - #[cfg_attr(feature = "serde", serde(with = "serde_bytes"))] - MeaningOfLife([u8; 42]), - Foo(u32), - Bar, - Baz(BazData), - UDSIdentifier(UDSIdentifier), - } - - impl TestPayload { - fn new(did: u16, reader: &mut R) -> Result { - match did { - 0xFF00 => { - let mut data = [0u8; 42]; - reader.read_exact(&mut data)?; - Ok(TestPayload::MeaningOfLife(data)) - } - 0xFF01 => { - let mut data = [0u8; 4]; - reader.read_exact(&mut data)?; - let value = u32::from_be_bytes(data); - Ok(TestPayload::Foo(value)) - } - 0xFF02 => Ok(TestPayload::Bar), - 0xFF03 => { - let data = BazData::decode(reader)?; - Ok(TestPayload::Baz(data)) - } - _ => { - let identifier = UDSIdentifier::try_from(did)?; - Ok(TestPayload::UDSIdentifier(identifier)) - } - } - } - } - - impl From for u16 { - fn from(value: TestPayload) -> Self { - match value { - TestPayload::MeaningOfLife(_) => 0xFF00, - TestPayload::Foo(_) => 0xFF01, - TestPayload::Bar => 0xFF02, - TestPayload::Baz(_) => 0xFF03, - TestPayload::UDSIdentifier(uds_id) => u16::from(uds_id), - } - } - } - - impl WireFormat for TestPayload { - #[allow(clippy::match_same_arms)] - fn required_size(&self) -> usize { - match self { - TestPayload::MeaningOfLife(_) => 42, - TestPayload::Foo(_) => 4, - TestPayload::Bar => 0, - TestPayload::Baz(_) => 26, - TestPayload::UDSIdentifier(_) => 0, - } - } - - #[allow(clippy::match_same_arms)] - fn encode(&self, writer: &mut W) -> Result { - let id_bytes = u16::from(self.clone()).to_be_bytes(); - let did_len = writer.write(&id_bytes)?; - match self { - TestPayload::MeaningOfLife(data) => { - writer.write_all(data)?; - Ok(did_len + data.len()) - } - TestPayload::Foo(value) => { - let bytes = value.to_be_bytes(); - writer.write_all(&bytes)?; - Ok(did_len + bytes.len()) - } - TestPayload::Bar => Ok(did_len), - TestPayload::Baz(data) => data.encode(writer), - TestPayload::UDSIdentifier(_) => Ok(did_len), - } - } - } - - impl IterableWireFormat for TestPayload { - fn decode_next(reader: &mut R) -> Result, Error> { - let mut identifier_data: [u8; 2] = [0; 2]; - match reader.read(&mut identifier_data)? { - 0 => return Ok(None), - 1 => return Err(Error::IncorrectMessageLengthOrInvalidFormat), - 2 => (), - _ => unreachable!("Impossible to read more than 2 bytes into 2 byte array"), - } - let did = u16::from_be_bytes(identifier_data); - Ok(Some(TestPayload::new(did, reader)?)) - } - } - - impl WireFormat for BazData { - fn required_size(&self) -> usize { - 26 - } - - fn encode(&self, writer: &mut W) -> Result { - writer.write_all(&self.data)?; - let mut count = 16; - count += writer.write(&self.data2.to_be_bytes())?; - count += writer.write(&self.data3.to_be_bytes())?; - // 2 for the initial did bytes - Ok(2 + count) - } - } - - impl SingleValueWireFormat for BazData { - fn decode(reader: &mut R) -> Result { - let mut data = [0u8; 16]; - reader.read_exact(&mut data)?; - - let mut data2_bytes = [0u8; 8]; - reader.read_exact(&mut data2_bytes)?; - let data2 = u64::from_be_bytes(data2_bytes); - - let mut data3_bytes = [0u8; 2]; - reader.read_exact(&mut data3_bytes)?; - let data3 = u16::from_be_bytes(data3_bytes); - - Ok(BazData { data, data2, data3 }) - } - } - - fn get_test_response_data() -> Vec { - vec![ - TestPayload::MeaningOfLife([0; 42]), - TestPayload::Foo(42), - TestPayload::Bar, - TestPayload::Baz(BazData { - data: [5; 16], - data2: 1_234_567_890, - data3: 54_321, - }), - TestPayload::UDSIdentifier(UDSIdentifier::BootSoftwareIdentification), - ] - } - - #[test] - fn read_did_response_bytes() { - let test_data = get_test_response_data(); - - let response = ReadDataByIdentifierResponse::new(test_data); - let mut buffer = Vec::new(); - response.encode(&mut buffer).unwrap(); - - let read_response: ReadDataByIdentifierResponse = - ReadDataByIdentifierResponse::::decode(&mut buffer.as_slice()) - .unwrap(); - - assert_eq!(response, read_response); - } - - #[test] - fn write_did_response_bytes() { - let test_data = get_test_response_data(); - - let response = ReadDataByIdentifierResponse::new(test_data.clone()); - let mut buffer = Vec::new(); - let bytes_written = response.encode(&mut buffer).unwrap(); - - let expected_bytes: Vec = test_data - .iter() - .flat_map(|payload| { - let mut buf = Vec::new(); - payload.encode(&mut buf).unwrap(); - buf - }) - .collect(); - - assert_eq!(buffer, expected_bytes); - assert_eq!(bytes_written, expected_bytes.len()); - } + #[test] + fn encode_read_did_request_tx() { + let ids = [ + ProtocolIdentifier::new(UDSIdentifier::BootSoftwareIdentification), + ProtocolIdentifier::new(UDSIdentifier::ActiveDiagnosticSession), + ]; + let req = ReadDataByIdentifierRequestTx::new(&ids); + let mut buf = [0u8; 16]; + let written = Encode::encode(&req, &mut buf.as_mut_slice()).unwrap(); + assert_eq!(written, 4); // 2 DIDs * 2 bytes each } } diff --git a/src/services/read_dtc_information.rs b/src/services/read_dtc_information.rs index 353ec81..4fb7f07 100644 --- a/src/services/read_dtc_information.rs +++ b/src/services/read_dtc_information.rs @@ -1,11 +1,9 @@ //! `ReadDTCInformation` (0x19) request and response service implementation -use byteorder::{ReadBytesExt, WriteBytesExt}; use crate::{ - DTCExtDataRecordList, DTCExtDataRecordNumber, DTCFormatIdentifier, DTCRecord, DTCSeverityMask, - DTCSeverityRecord, DTCSnapshotRecord, DTCSnapshotRecordList, DTCSnapshotRecordNumber, - DTCStatusMask, DTCStoredDataRecordNumber, Error, FunctionalGroupIdentifier, IterableWireFormat, - SingleValueWireFormat, WireFormat, + DTCExtDataRecordNumber, DTCFormatIdentifier, DTCRecord, DTCSeverityMask, + DTCSnapshotRecordNumber, DTCStatusMask, DTCStoredDataRecordNumber, Decode, Encode, Error, + FunctionalGroupIdentifier, }; /// Used for non-emissions related servers @@ -24,29 +22,13 @@ pub struct ReadDTCInfoRequest { } impl ReadDTCInfoRequest { - pub(crate) fn new(dtc_subfunction: ReadDTCInfoSubFunction) -> Self { + /// Create a new `ReadDTCInfoRequest`. + #[must_use] + pub fn new(dtc_subfunction: ReadDTCInfoSubFunction) -> Self { Self { dtc_subfunction } } } -impl WireFormat for ReadDTCInfoRequest { - fn required_size(&self) -> usize { - self.dtc_subfunction.required_size() - } - - fn encode(&self, writer: &mut T) -> Result { - self.dtc_subfunction.encode(writer) - } -} - -impl SingleValueWireFormat for ReadDTCInfoRequest { - fn decode(reader: &mut T) -> Result { - let dtc_subfunction = ReadDTCInfoSubFunction::decode(reader)?; - - Ok(Self { dtc_subfunction }) - } -} - /// A DTC paired with its fault detection counter value #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] #[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))] @@ -56,173 +38,6 @@ pub struct DTCFaultDetectionCounterRecord { pub dtc_fault_detection_counter: DTCFaultDetectionCounter, } -impl WireFormat for DTCFaultDetectionCounterRecord { - fn required_size(&self) -> usize { - 4 - } - - fn encode(&self, writer: &mut T) -> Result { - self.dtc_record.encode(writer)?; - writer.write_u8(self.dtc_fault_detection_counter)?; - Ok(self.required_size()) - } -} - -impl IterableWireFormat for DTCFaultDetectionCounterRecord { - #[allow(clippy::match_same_arms)] - fn decode_next(reader: &mut T) -> Result, Error> { - let dtc_record = match DTCRecord::decode_next(reader) { - Ok(None) => return Ok(None), - Ok(Some(record)) => record, - Err(_) => return Ok(None), - }; - let dtc_fault_detection_counter = reader.read_u8()?; - Ok(Some(Self { - dtc_record, - dtc_fault_detection_counter, - })) - } -} - -/// Record containing user-defined memory DTC information filtered by status mask -#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] -#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))] -#[derive(Clone, Debug, PartialEq)] -pub struct UserDefMemoryDTCByStatusMaskRecord { - // This parameter shall be used to address the respective user defined DTC memory when retrieving DTCs. - pub memory_selection: MemorySelection, - /// The status mask of the DTC, representing its current state. - pub status_availability_mask: DTCStatusMask, - /// Vector of DTC Records and Status of Corresponding DTC - pub record_data: Vec<(DTCRecord, DTCStatusMask)>, -} - -/// Record containing user-defined memory DTC snapshot data for a specific DTC -#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] -#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))] -#[derive(Clone, Debug, PartialEq)] -pub struct UserDefMemoryDTCSnapshotRecordByDTCNumRecord { - // This parameter shall be used to address the respective user defined DTC memory when retrieving DTCs. - pub memory_selection: MemorySelection, - pub dtc_record: DTCRecord, - pub dtc_status_mask: DTCStatusMask, - /// Contains a snapshot of data values from the time of the system malfunction occurrence. - pub dtc_snapshot_record: Vec<(DTCSnapshotRecordNumber, DTCSnapshotRecord)>, -} -impl WireFormat - for UserDefMemoryDTCSnapshotRecordByDTCNumRecord -{ - fn required_size(&self) -> usize { - 1 + self.dtc_record.required_size() - + self.dtc_status_mask.required_size() - + self - .dtc_snapshot_record - .iter() - .fold(0, |acc, (record_number, record)| { - acc + record_number.required_size() + record.required_size() - }) - } - - fn encode(&self, writer: &mut T) -> Result { - writer.write_u8(self.memory_selection)?; - self.dtc_record.encode(writer)?; - self.dtc_status_mask.encode(writer)?; - for (record_number, record) in &self.dtc_snapshot_record { - record_number.encode(writer)?; - record.encode(writer)?; - } - Ok(self.required_size()) - } -} - -impl SingleValueWireFormat - for UserDefMemoryDTCSnapshotRecordByDTCNumRecord -{ - fn decode(reader: &mut T) -> Result { - let memory_selection = reader.read_u8()?; - let dtc_record = DTCRecord::decode(reader)?; - let dtc_status_mask = DTCStatusMask::decode(reader)?; - let mut dtc_snapshot_record = Vec::new(); - - while let Ok(Some(dtc_snapshot_record_number)) = - DTCSnapshotRecordNumber::decode_next(reader) - { - let snapshot_record = DTCSnapshotRecord::decode(reader)?; - dtc_snapshot_record.push((dtc_snapshot_record_number, snapshot_record)); - } - - Ok(UserDefMemoryDTCSnapshotRecordByDTCNumRecord { - memory_selection, - dtc_record, - dtc_status_mask, - dtc_snapshot_record, - }) - } -} - -/// List of WWH OBD DTCs and corresponding status and severity information matching a client defined status mask and severity mask record -#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] -#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))] -#[derive(Clone, Debug, PartialEq)] -pub struct WWHOBDDTCByMaskRecord { - /// Echo from the request. - pub functional_group_identifier: FunctionalGroupIdentifier, - /// Same representation as [`DTCStatusMask`] but with the bits 'on' representing the DTC status supported by the server - pub status_availability_mask: DTCStatusAvailabilityMask, - pub severity_availability_mask: DTCSeverityMask, - /// Specifies the format of the DTC reported by the server. - /// Only possible options: - /// `DTCFormatIdentifier::SAE_J2012_DA_DTCFormat_04` - /// `DTCFormatIdentifier::SAE_J1939_73_DTCFormat` - pub format_identifier: DTCFormatIdentifier, - pub record_data: Vec<(DTCSeverityMask, DTCRecord, DTCStatusMask)>, -} - -/// List of WWH OBD DTCs with "permanent DTC" status as described in 3.12 -#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] -#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))] -#[derive(Clone, Debug, PartialEq)] -pub struct WWHOBDDTCWithPermanentStatusRecord { - /// Echo from the request. - pub functional_group_identifier: FunctionalGroupIdentifier, - /// Same representation as [`DTCStatusMask`] but with the bits 'on' representing the DTC status supported by the server - pub status_availability_mask: DTCStatusAvailabilityMask, - /// Specifies the format of the DTC reported by the server. - /// Only possible options: - /// `DTCFormatIdentifier::SAE_J2012_DA_DTCFormat_04` - /// `DTCFormatIdentifier::SAE_J1939_73_DTCFormat` - pub format_identifier: DTCFormatIdentifier, - pub record_data: Vec<(DTCRecord, DTCStatusMask)>, -} - -/// List of OBD DTCs which matches the `DTCReadiness` Group Identifier -#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] -#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))] -#[derive(Clone, Debug, PartialEq)] -pub struct DTCByReadinessGroupIdentifierRecord { - /// Echo from the request. - pub functional_group_identifier: FunctionalGroupIdentifier, - /// Same representation as [`DTCStatusMask`] but with the bits 'on' representing the DTC status supported by the server - pub status_availability_mask: DTCStatusAvailabilityMask, - /// Specifies the format of the DTC reported by the server. - pub format_identifier: DTCFormatIdentifier, - /// DTC readiness groups - pub readiness_group_identifier: DTCReadinessGroupIdentifier, - pub record_data: Vec<(DTCRecord, DTCStatusMask)>, -} - -/// List of DTCs that support a specific extended data record number -#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] -#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))] -#[derive(Clone, Debug, PartialEq)] -pub struct SupportedDTCExtDataRecord { - /// Same representation as [`DTCStatusMask`] but with the bits 'on' representing the DTC status supported by the server - pub status_availability_mask: DTCStatusAvailabilityMask, - /// Request message to get a stored [`DTCExtDataRecord`] - pub ext_data_record_number: Option, - pub dtc_and_status_records: Vec<(DTCRecord, DTCStatusMask)>, -} - /// Have to reference SAE J1979-DA for the corresponding DTC readiness groups and the [`FunctionalGroupIdentifier`]s /// This RGID depends on the functional group type DTCReadinessGroupIdentifier = u8; // RGID @@ -383,1592 +198,391 @@ impl ReadDTCInfoSubFunction { } } -impl WireFormat for ReadDTCInfoSubFunction { - #[allow(clippy::match_same_arms)] - fn required_size(&self) -> usize { - 1 + match self { - Self::ReportNumberOfDTC_ByStatusMask(_) => 1, - Self::ReportDTC_ByStatusMask(_) => 1, - Self::ReportDTCSnapshotIdentification => 0, - Self::ReportDTCSnapshotRecord_ByDTCNumber(_, _) => 4, - Self::ReportDTCStoredData_ByRecordNumber(_) => 2, - Self::ReportDTCExtDataRecord_ByDTCNumber(_, _) => 4, - Self::ReportNumberOfDTC_BySeverityMaskRecord(_, _) => 2, - Self::ReportDTC_BySeverityMaskRecord(_, _) => 2, - Self::ReportSeverityInfoOfDTC(_) => 3, - Self::ReportSupportedDTC => 0, - Self::ReportFirstTestFailedDTC => 0, - Self::ReportFirstConfirmedDTC => 0, - Self::ReportMostRecentTestFailedDTC => 0, - Self::ReportMostRecentConfirmedDTC => 0, - Self::ReportDTCFaultDetectionCounter => 0, - Self::ReportDTCWithPermanentStatus => 0, - Self::ReportDTCExtDataRecord_ByRecordNumber(_) => 1, - Self::ReportUserDefMemoryDTC_ByStatusMask(_) => 1, - Self::ReportUserDefMemoryDTCSnapshotRecord_ByDTCNumber(_, _, _) => 5, - Self::ReportUserDefMemoryDTCExtDataRecord_ByDTCNumber(_, _, _) => 5, - Self::ReportSupportedDTCExtDataRecord(_) => 1, - Self::ReportWWHOBDDTC_ByMaskRecord(_, _, _) => 3, - Self::ReportWWHOBDDTC_WithPermanentStatus(_) => 1, - Self::ReportDTCInformation_ByDTCReadinessGroupIdentifier(_, _) => 2, +/// Same representation as [`DTCStatusMask`] but with the bits 'on' representing the DTC status supported by the server +/// IE if the server doesn't support [`DTCStatusMask::WarningIndicatorRequested`] then the bit for that status will be 'off' +/// and all other bits will be 'on' +type DTCStatusAvailabilityMask = DTCStatusMask; + +// --------------------------------------------------------------------------- +// no_std RX types with lazy iterators +// --------------------------------------------------------------------------- - Self::ISOSAEReserved(_) => 0, - } +/// Lazy iterator over `(DTCRecord, DTCStatusMask)` pairs from raw bytes. +/// +/// Each pair is 4 bytes: 3 for the DTC record + 1 for the status mask. +#[derive(Clone, Debug)] +pub struct DtcAndStatusIter<'a> { + remaining: &'a [u8], +} + +impl<'a> DtcAndStatusIter<'a> { + /// Create an iterator over `(DTCRecord, DTCStatusMask)` pairs. + #[must_use] + pub const fn new(data: &'a [u8]) -> Self { + Self { remaining: data } } - #[allow(clippy::match_same_arms)] - fn encode(&self, writer: &mut T) -> Result { - // Write the subfunction value - writer.write_u8(self.value())?; - match self { - Self::ReportNumberOfDTC_ByStatusMask(mask) => { - mask.encode(writer)?; - } - Self::ReportDTC_ByStatusMask(mask) => { - mask.encode(writer)?; - } - Self::ReportDTCSnapshotIdentification => {} - Self::ReportDTCSnapshotRecord_ByDTCNumber(mask, record_number) => { - mask.encode(writer)?; - record_number.encode(writer)?; - } - Self::ReportDTCStoredData_ByRecordNumber(record_number) => { - record_number.encode(writer)?; - } - Self::ReportDTCExtDataRecord_ByDTCNumber(mask, record_number) => { - mask.encode(writer)?; - record_number.encode(writer)?; - } - Self::ReportNumberOfDTC_BySeverityMaskRecord(severity, status) => { - writer.write_u8(severity.bits())?; - status.encode(writer)?; - } - Self::ReportDTC_BySeverityMaskRecord(severity, status) => { - writer.write_u8(severity.bits())?; - status.encode(writer)?; - } - Self::ReportSeverityInfoOfDTC(mask) => { - mask.encode(writer)?; - } - Self::ReportSupportedDTC => {} - Self::ReportFirstTestFailedDTC => {} - Self::ReportFirstConfirmedDTC => {} - Self::ReportMostRecentTestFailedDTC => {} - Self::ReportMostRecentConfirmedDTC => {} - Self::ReportDTCFaultDetectionCounter => {} - Self::ReportDTCWithPermanentStatus => {} - Self::ReportDTCExtDataRecord_ByRecordNumber(record_number) => { - record_number.encode(writer)?; - } - Self::ReportUserDefMemoryDTC_ByStatusMask(mask) => { - mask.encode(writer)?; - } - Self::ReportUserDefMemoryDTCSnapshotRecord_ByDTCNumber(mask, number, selection) => { - mask.encode(writer)?; - number.encode(writer)?; - writer.write_u8(*selection)?; - } - Self::ReportUserDefMemoryDTCExtDataRecord_ByDTCNumber(mask, number, selection) => { - mask.encode(writer)?; - number.encode(writer)?; - writer.write_u8(*selection)?; - } - Self::ReportSupportedDTCExtDataRecord(number) => { - number.encode(writer)?; - } - Self::ReportWWHOBDDTC_ByMaskRecord(group, status, severity) => { - writer.write_u8(group.value())?; - status.encode(writer)?; - writer.write_u8(severity.bits())?; - } - Self::ReportWWHOBDDTC_WithPermanentStatus(group) => { - writer.write_u8(group.value())?; - } - Self::ReportDTCInformation_ByDTCReadinessGroupIdentifier(group, readiness) => { - writer.write_u8(group.value())?; - writer.write_u8(*readiness)?; - } - Self::ISOSAEReserved(value) => { - writer.write_u8(*value)?; - } - } - Ok(self.required_size()) + /// Number of complete records available. + #[must_use] + pub const fn len(&self) -> usize { + self.remaining.len() / 4 } -} -impl SingleValueWireFormat for ReadDTCInfoSubFunction { - #[allow(clippy::match_same_arms)] - fn decode(reader: &mut T) -> Result { - let report_type = reader.read_u8()?; + /// Whether there are no records. + #[must_use] + pub const fn is_empty(&self) -> bool { + self.remaining.is_empty() + } - let subfunction = match report_type { - 0x01 | 0x02 => { - let status = DTCStatusMask::from(reader.read_u8()?); - match report_type { - 0x01 => Self::ReportNumberOfDTC_ByStatusMask(status), - 0x02 => Self::ReportDTC_ByStatusMask(status), - _ => unreachable!(), - } - } - 0x03 => Self::ReportDTCSnapshotIdentification, - 0x04 => Self::ReportDTCSnapshotRecord_ByDTCNumber( - DTCRecord::decode(reader)?, - DTCSnapshotRecordNumber::decode(reader)?, - ), - 0x05 => { - Self::ReportDTCStoredData_ByRecordNumber(DTCStoredDataRecordNumber::decode(reader)?) - } - // 0xFF for all records, 0xFE for all OBD records - 0x06 => Self::ReportDTCExtDataRecord_ByDTCNumber( - DTCRecord::decode(reader)?, - DTCExtDataRecordNumber::decode(reader)?, - ), - 0x07 => Self::ReportNumberOfDTC_BySeverityMaskRecord( - DTCSeverityMask::from(reader.read_u8()?), - DTCStatusMask::from(reader.read_u8()?), - ), - 0x08 => Self::ReportDTC_BySeverityMaskRecord( - DTCSeverityMask::from(reader.read_u8()?), - DTCStatusMask::from(reader.read_u8()?), - ), - 0x09 => Self::ReportSeverityInfoOfDTC(DTCRecord::decode(reader)?), - 0x0A => Self::ReportSupportedDTC, - 0x0B => Self::ReportFirstTestFailedDTC, - 0x0C => Self::ReportFirstConfirmedDTC, - 0x0D => Self::ReportMostRecentTestFailedDTC, - 0x0E => Self::ReportMostRecentConfirmedDTC, - 0x14 => Self::ReportDTCFaultDetectionCounter, - 0x15 => Self::ReportDTCWithPermanentStatus, - 0x16 => { - Self::ReportDTCExtDataRecord_ByRecordNumber(DTCExtDataRecordNumber::decode(reader)?) - } - 0x17 => { - Self::ReportUserDefMemoryDTC_ByStatusMask(DTCStatusMask::from(reader.read_u8()?)) - } - // 0xFF for all records - 0x18 => Self::ReportUserDefMemoryDTCSnapshotRecord_ByDTCNumber( - DTCRecord::decode(reader)?, - DTCSnapshotRecordNumber::decode(reader)?, - reader.read_u8()?, - ), - 0x19 => Self::ReportUserDefMemoryDTCExtDataRecord_ByDTCNumber( - DTCRecord::decode(reader)?, - DTCExtDataRecordNumber::decode(reader)?, - reader.read_u8()?, - ), - 0x1A => Self::ReportSupportedDTCExtDataRecord(DTCExtDataRecordNumber::decode(reader)?), - 0x42 => Self::ReportWWHOBDDTC_ByMaskRecord( - FunctionalGroupIdentifier::from(reader.read_u8()?), - DTCStatusMask::from(reader.read_u8()?), - DTCSeverityMask::from(reader.read_u8()?), - ), - 0x43..=0x54 => Self::ISOSAEReserved(report_type), - 0x55 => Self::ReportWWHOBDDTC_WithPermanentStatus(FunctionalGroupIdentifier::from( - reader.read_u8()?, - )), - 0x56 => Self::ReportDTCInformation_ByDTCReadinessGroupIdentifier( - FunctionalGroupIdentifier::from(reader.read_u8()?), - reader.read_u8()?, - ), - 0x57..=0x7F => Self::ISOSAEReserved(report_type), - _ => return Err(Error::InvalidDtcSubfunctionType(report_type)), - }; - Ok(subfunction) + /// Collect all records into a `Vec`. + /// + /// # Errors + /// Returns an error if the byte data contains a partial record. + #[cfg(feature = "alloc")] + pub fn collect_all(self) -> Result, Error> { + self.collect() } } -type NumberOfDTCs = u16; -/// Same representation as [`DTCStatusMask`] but with the bits 'on' representing the DTC status supported by the server -/// IE if the server doesn't support [`DTCStatusMask::WarningIndicatorRequested`] then the bit for that status will be 'off' -/// and all other bits will be 'on' -type DTCStatusAvailabilityMask = DTCStatusMask; +impl Iterator for DtcAndStatusIter<'_> { + type Item = Result<(DTCRecord, DTCStatusMask), Error>; -/// Subfunction ID for the response -type SubFunctionID = u8; + fn next(&mut self) -> Option { + if self.remaining.is_empty() { + return None; + } + if self.remaining.len() < 4 { + return Some(Err(Error::IncorrectMessageLengthOrInvalidFormat)); + } + let record = DTCRecord::new(self.remaining[0], self.remaining[1], self.remaining[2]); + let status = DTCStatusMask::from(self.remaining[3]); + self.remaining = &self.remaining[4..]; + Some(Ok((record, status))) + } +} -/// Response payloads can be shared among multiple request subfunctions +/// Lazy iterator over `DTCFaultDetectionCounterRecord` from raw bytes. /// -/// For example, subfunction 0x01 and 0x07 both return the number of DTCs -/// and have the same response format -#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] -#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))] -#[derive(Clone, Debug, PartialEq)] -#[non_exhaustive] -pub enum ReadDTCInfoResponse { - /// * Parameter: [`DTCStatusMask`] (1) - /// * Parameter: `NumberOfDTCs`(2) - /// - /// For subfunctions 0x01, 0x07 - /// * 0x01: [`ReadDTCInfoSubFunction::ReportNumberOfDTC_ByStatusMask`] - /// * 0x07: [`ReadDTCInfoSubFunction::ReportNumberOfDTC_BySeverityMaskRecord`] - NumberOfDTCs(SubFunctionID, DTCStatusAvailabilityMask, NumberOfDTCs), +/// Each record is 4 bytes: 3 for the DTC record + 1 for the fault detection counter. +#[derive(Clone, Debug)] +pub struct DtcFaultDetectionIter<'a> { + remaining: &'a [u8], +} - /// A list of DTCs matching the subfunction request - /// - /// * Parameter: [`DTCStatusMask`] (1) - /// * Parameter: `Vec` (4 * n) - /// - /// Note: DTC list can be empty if there are none to report, - /// but the response will still be sent - /// - /// For subfunctions 0x02, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x15 - /// * 0x02: [`ReadDTCInfoSubFunction::ReportDTC_ByStatusMask`] - /// * 0x0A: [`ReadDTCInfoSubFunction::ReportSupportedDTC`] - /// * 0x0B: [`ReadDTCInfoSubFunction::ReportFirstTestFailedDTC`] - /// * 0x0C: [`ReadDTCInfoSubFunction::ReportFirstConfirmedDTC`] - /// * 0x0D: [`ReadDTCInfoSubFunction::ReportMostRecentTestFailedDTC`] - /// * 0x0E: [`ReadDTCInfoSubFunction::ReportMostRecentConfirmedDTC`] - /// * 0x15: [`ReadDTCInfoSubFunction::ReportDTCWithPermanentStatus`] - DTCList( - SubFunctionID, - DTCStatusAvailabilityMask, - Vec<(DTCRecord, DTCStatusMask)>, - ), +impl<'a> DtcFaultDetectionIter<'a> { + /// Create an iterator over `DTCFaultDetectionCounterRecord` values. + #[must_use] + pub const fn new(data: &'a [u8]) -> Self { + Self { remaining: data } + } - /// Snapshot identification - aka "Freeze Frame" + /// Collect all records into a `Vec`. /// - /// Parameter: Vec<(`DTCRecord`, `DTCSnapshotRecordNumber`> (4 * n) - /// - /// Note: `DTCSnapshot` list might be empty - /// - /// For subfunction 0x03 - /// * 0x03: [`ReadDTCInfoSubFunction::ReportDTCSnapshotIdentification`] - DTCSnapshotList(Vec<(DTCRecord, DTCSnapshotRecordNumber)>), + /// # Errors + /// Returns an error if the byte data contains a partial record. + #[cfg(feature = "alloc")] + pub fn collect_all(self) -> Result, Error> { + self.collect() + } +} - /// Get the DTC status and snapshot number and information w/ corresponding Data Identifier (DID) - /// - /// DTC, Status, snapshot number, # of identifiers, DID (times # of identifiers), Snapshot info. - /// - /// If all records are requested, it can be a theoretically large amount of data. - /// - /// Parameter: `DTCRecord` (3 bytes) - Echo of the request - /// Parameter: `DTCStatusMask` (1) - status of the requested DTC - /// C2/C4: There are multiple dataIdentifier/snapshotData combinations allowed to be present in a single `DTCSnapshotRecord`. - /// This can, for example be the case for the situation where a single dataIdentifier only references an integral part of data. When - /// the dataIdentifier references a block of data then a single dataIdentifier/snapshotData combination can be used. - /// - /// Note: See example 12.3.5.6.2 in ISO 14229-1:2020 for more information - /// - /// For subfunction 0x04 - /// * 0x04: [`ReadDTCInfoSubFunction::ReportDTCSnapshotRecord_ByDTCNumber`] - DTCSnapshotRecordList(DTCSnapshotRecordList), +impl Iterator for DtcFaultDetectionIter<'_> { + type Item = Result; - /// List of [`crate::DTCExtDataRecord`]s for a given DTC. - /// - /// `UserPayload` is so the data can be read according to a specific format - /// defined by the supplier/vehicle manufacturer - /// - /// * Parameter: [`DTCRecord`] (3 bytes) - Echo of the request - /// * Parameter: [`DTCStatusMask`] (1) - status of the requested DTC - /// * Parameter: [`crate::DTCExtDataRecord`] (n) - /// - /// For subfunction 0x06 - /// * 0x06: [`ReadDTCInfoSubFunction::ReportDTCExtDataRecord_ByDTCNumber`] - DTCExtDataRecordList(DTCExtDataRecordList), + fn next(&mut self) -> Option { + if self.remaining.is_empty() { + return None; + } + if self.remaining.len() < 4 { + return Some(Err(Error::IncorrectMessageLengthOrInvalidFormat)); + } + let dtc_record = DTCRecord::new(self.remaining[0], self.remaining[1], self.remaining[2]); + let dtc_fault_detection_counter = self.remaining[3]; + self.remaining = &self.remaining[4..]; + Some(Ok(DTCFaultDetectionCounterRecord { + dtc_record, + dtc_fault_detection_counter, + })) + } +} - /// List of DTC Records that either match a severity and status mask for subfunction [`ReadDTCInfoSubFunction::ReportDTC_BySeverityMaskRecord`], - /// or a single record if the request type was [`ReadDTCInfoSubFunction::ReportSeverityInfoOfDTC`]. - /// - /// * Parameter: [`DTCStatusMask`] (1 byte) - /// * Parameter: `Vec` (6 bytes) - /// - /// For Subfunctions 0x08, 0x09 - /// * 0x08: [`ReadDTCInfoSubFunction::ReportDTC_BySeverityMaskRecord`] - /// * 0x09: [`ReadDTCInfoSubFunction::ReportSeverityInfoOfDTC`] - DTCSeverityRecordList( - SubFunctionID, - DTCStatusAvailabilityMask, - Vec, - ), - /// List of DTC Records along with their fault detection counters for subfunction [`ReadDTCInfoSubFunction::ReportDTCFaultDetectionCounter`]. +/// Lazy iterator over `(DTCSeverityMask, DTCRecord, DTCStatusMask)` triples from raw bytes. +/// +/// Each triple is 5 bytes: 1 severity + 3 DTC record + 1 status mask. +#[derive(Clone, Debug)] +pub struct DtcSeverityAndStatusIter<'a> { + remaining: &'a [u8], +} - /// - /// * Parameter: [`DTCRecord`] - (3 bytes) - /// * Parameter: `DTCFaultDetectionCounter` - (1 byte) - /// - /// For Subfunction 0x14: - /// * 0x14: [`ReadDTCInfoSubFunction::ReportDTCFaultDetectionCounter`] - DTCFaultDetectionCounterRecordList(Vec), +impl<'a> DtcSeverityAndStatusIter<'a> { + /// Create an iterator over severity/DTC/status triples. + #[must_use] + pub const fn new(data: &'a [u8]) -> Self { + Self { remaining: data } + } - /// List of DTCs out of User Defined DTC Memory and corresponding statuses matching client - /// defined status mask - /// - /// * Parameter: `UserDefMemoryDTCByStatusMaskRecord` (n) + /// Collect all triples into a `Vec`. /// - /// For subfunction 0x17 - /// * 0x17: [`ReadDTCInfoSubFunction::ReportUserDefMemoryDTC_ByStatusMask`] - UserDefMemoryDTCByStatusMaskList(UserDefMemoryDTCByStatusMaskRecord), + /// # Errors + /// Returns an error if the byte data contains a partial record. + #[cfg(feature = "alloc")] + pub fn collect_all( + self, + ) -> Result, Error> { + self.collect() + } +} - /// List of [`crate::DTCSnapshotRecord`]s for a given DTC. - /// - /// `UserPayload` is so the data can be read according to a specific format - /// defined by the supplier/vehicle manufacturer - /// - /// Contains a snapshot of data values from the time of the system malfunction occurrence. - /// * Parameter: `MemorySelection` (1) - user defined DTC memory when retrieving DTCs. - /// * Parameter: [`DTCRecord`] (3 bytes) - /// * Parameter: [`DTCStatusMask`] (1 bytes) - /// * Parameter: `Vec<(DTCSnapshotRecordNumber, DTCSnapshotRecord)>` (m*(1+n) bytes) - Echo of the request - /// - /// For subfunction 0x18 - /// * 0x18: [`ReadDTCInfoSubFunction::ReportUserDefMemoryDTCSnapshotRecord_ByDTCNumber`] - UserDefMemoryDTCSnapshotRecordByDTCNumberList( - UserDefMemoryDTCSnapshotRecordByDTCNumRecord, - ), +impl Iterator for DtcSeverityAndStatusIter<'_> { + type Item = Result<(DTCSeverityMask, DTCRecord, DTCStatusMask), Error>; - /// DTCs which supports a `DTCExtendedDataRecord` - /// - /// * Parameter: [`DTCStatusMask`] (1) - /// * Parameter: `Option` (1) - /// * Parameter: `Vec<(DTCRecord, DTCStatusMask)>` (4 * n bytes) - /// - /// `Option` is only present if atleast one DTC supports the `DTCExtendedDataRecord` - /// `Vec<(DTCRecord, DTCStatusMask)>` length is non-zero only if atleast one DTC supports the `DTCExtendedDataRecord` - /// - /// For Subfunction 0x1A - /// * 0x1A: [`ReadDTCInfoSubFunction::ReportSupportedDTCExtDataRecord`] - SupportedDTCExtDataRecordList(SupportedDTCExtDataRecord), + fn next(&mut self) -> Option { + if self.remaining.is_empty() { + return None; + } + if self.remaining.len() < 5 { + return Some(Err(Error::IncorrectMessageLengthOrInvalidFormat)); + } + let severity = DTCSeverityMask::from(self.remaining[0]); + let record = DTCRecord::new(self.remaining[1], self.remaining[2], self.remaining[3]); + let status = DTCStatusMask::from(self.remaining[4]); + self.remaining = &self.remaining[5..]; + Some(Ok((severity, record, status))) + } +} - /// List of WWH OBD DTCs and corresponding status and severity information - /// matching a client defined status mask and severity mask record - /// - ///Contains a struct of `WWHOBDDTCByMaskRecord` - /// * Parameter: [`FunctionalGroupIdentifier`] (1) - /// * Parameter: [`DTCStatusMask`] (1) - /// * Parameter: [`DTCSeverityMask`] (1) - /// * Parameter: [`DTCFormatIdentifier`] (1) - /// * Parameter: `Vec<(DTCSeverityMask, DTCRecord, DTCStatusMask)>` (5*n) - /// - /// Only possible options for [`DTCFormatIdentifier`] : - /// `DTCFormatIdentifier::SAE_J2012_DA_DTCFormat_04` - /// `DTCFormatIdentifier::SAE_J1939_73_DTCFormat` - /// * Returns `Error::InvalidDtcFormatIdentifier` in case of incorrect `DTCFormatIdentifier` - /// - /// For Subfunction 0x42 - /// * 0x42: [`ReadDTCInfoSubFunction::ReportWWHOBDDTC_ByMaskRecord`] - WWHOBDDTCByMaskRecordList(WWHOBDDTCByMaskRecord), +/// Zero-copy RX response for `ReadDTCInformation` (0x19). +/// +/// Stores raw bytes for record collections and provides lazy iterators +/// that parse on demand without allocation. +#[derive(Clone, Debug)] +#[non_exhaustive] +pub enum ReadDTCInfoResponseRx<'a> { + /// Sub-functions 0x01, 0x07: count of DTCs matching a mask. + NumberOfDTCs { + /// Sub-function byte echo. + sub_function_id: u8, + /// DTC status availability mask. + status_availability_mask: DTCStatusAvailabilityMask, + /// Number of matching DTCs. + count: u16, + }, + /// Sub-functions 0x02, 0x0A-0x0E, 0x15: list of `(DTCRecord, DTCStatusMask)` pairs. + DTCList { + /// Sub-function byte echo. + sub_function_id: u8, + /// DTC status availability mask. + status_availability_mask: DTCStatusAvailabilityMask, + /// Raw record bytes — use [`DtcAndStatusIter`] to iterate. + raw_records: &'a [u8], + }, + /// Sub-function 0x14: list of DTC fault detection counter records. + DTCFaultDetectionCounterList { + /// Raw record bytes — use [`DtcFaultDetectionIter`] to iterate. + raw_records: &'a [u8], + }, + /// Sub-functions 0x08, 0x09: list of DTC severity records. + DTCSeverityList { + /// Sub-function byte echo. + sub_function_id: u8, + /// DTC status availability mask. + status_availability_mask: DTCStatusAvailabilityMask, + /// Raw record bytes (6 bytes per record) — use [`DTCSeverityRecord`] iteration. + raw_records: &'a [u8], + }, + /// Sub-function 0x42: WWH-OBD DTC by mask with severity info. + WWHOBDDTCByMaskRecord { + /// Functional group identifier echo. + functional_group_identifier: FunctionalGroupIdentifier, + /// DTC status availability mask. + status_availability_mask: DTCStatusAvailabilityMask, + /// Severity availability mask. + severity_availability_mask: DTCSeverityMask, + /// DTC format identifier. + format_identifier: DTCFormatIdentifier, + /// Raw record bytes (5 bytes per record) — use [`DtcSeverityAndStatusIter`]. + raw_records: &'a [u8], + }, +} - /// List of WWH OBD DTCs with "permanent DTC" status as described in 3.12 +impl<'a> ReadDTCInfoResponseRx<'a> { + /// Iterate `(DTCRecord, DTCStatusMask)` pairs for `DTCList` variants. /// - ///Contains a struct of `WWHOBDDTCWithPermanentStatusRecord` - /// * Parameter: [`FunctionalGroupIdentifier`] (1) - /// * Parameter: [`DTCStatusMask`] (1) - /// * Parameter: [`DTCFormatIdentifier`] (1) - /// * Parameter: `Vec<(DTCRecord, DTCStatusMask)>` (4*n) - /// - /// Only possible options for [`DTCFormatIdentifier`] : - /// `DTCFormatIdentifier::SAE_J2012_DA_DTCFormat_04` - /// `DTCFormatIdentifier::SAE_J1939_73_DTCFormat` - /// * Returns `Error::InvalidDtcFormatIdentifier` in case of incorrect `DTCFormatIdentifier` - /// - /// For Subfunction 0x55 - /// * 0x55: [`ReadDTCInfoSubFunction::ReportWWHOBDDTC_WithPermanentStatus`] - WWHOBDDTCWithPermanentStatusList(WWHOBDDTCWithPermanentStatusRecord), + /// Returns `None` if this is not a `DTCList` variant. + #[must_use] + pub fn dtc_and_status_iter(&self) -> Option> { + match self { + Self::DTCList { raw_records, .. } => Some(DtcAndStatusIter::new(raw_records)), + _ => None, + } + } - /// List of OBD DTCs which matches the `DTCReadiness` Group Identifier + /// Iterate fault detection counter records for the `DTCFaultDetectionCounterList` variant. /// - /// Contains a struct of `DTCByReadinessGroupIdentifierRecord` - /// * Parameter: [`FunctionalGroupIdentifier`] (1) - /// * Parameter: [`DTCStatusMask`] (1) - /// * Parameter: [`DTCFormatIdentifier`] (1) - /// * Parameter: `DTCReadinessGroupIdentifier` (1) - /// * Parameter: `Vec<(DTCRecord, DTCStatusMask)>` (5*n) - /// - /// For Subfunction 0x56 - /// * 0x56: [`ReadDTCInfoSubFunction::ReportDTCInformation_ByDTCReadinessGroupIdentifier`] - DTCByReadinessGroupIdentifierList(DTCByReadinessGroupIdentifierRecord), -} - -impl WireFormat for ReadDTCInfoResponse { - fn required_size(&self) -> usize { - // subfunction ID + subfunction contents - 1 + match self { - Self::NumberOfDTCs(_, _, _) => 3, - Self::DTCList(_, _, list) => 1 + list.len() * 4, - Self::DTCSnapshotList(list) => 1 + list.len() * 4, - Self::DTCSnapshotRecordList(list) => list.required_size(), - Self::DTCExtDataRecordList(list) => list.required_size(), - Self::DTCSeverityRecordList(_, _, list) => 1 + list.len() * 6, - Self::DTCFaultDetectionCounterRecordList(list) => list.len() * 4, - Self::UserDefMemoryDTCByStatusMaskList(list) => 2 + list.record_data.len() * 4, - Self::UserDefMemoryDTCSnapshotRecordByDTCNumberList(list) => list.required_size(), - Self::SupportedDTCExtDataRecordList(list) => { - if list.ext_data_record_number.is_some() { - 2 + list.dtc_and_status_records.len() * 4 - } else { - 1 - } - } - Self::WWHOBDDTCByMaskRecordList(response_struct) => { - 4 + response_struct.record_data.len() * 5 - } - Self::WWHOBDDTCWithPermanentStatusList(response_struct) => { - 3 + response_struct.record_data.len() * 4 - } - Self::DTCByReadinessGroupIdentifierList(response_struct) => { - 4 + response_struct.record_data.len() * 4 + /// Returns `None` if this is not that variant. + #[must_use] + pub fn fault_detection_iter(&self) -> Option> { + match self { + Self::DTCFaultDetectionCounterList { raw_records } => { + Some(DtcFaultDetectionIter::new(raw_records)) } + _ => None, } } - #[allow(clippy::too_many_lines)] - fn encode(&self, writer: &mut T) -> Result { + /// Iterate severity/DTC/status triples for WWH-OBD variants. + /// + /// Returns `None` if this is not a severity variant. + #[must_use] + pub fn severity_and_status_iter(&self) -> Option> { match self { - Self::NumberOfDTCs(id, mask, count) => { - writer.write_u8(*id)?; - writer.write_u8(mask.bits())?; - writer.write_u16::(*count)?; - } - Self::DTCList(id, mask, list) => { - writer.write_u8(*id)?; - writer.write_u8(mask.bits())?; - for (record, status) in list { - record.encode(writer)?; - status.encode(writer)?; - } - } - Self::DTCSnapshotList(list) => { - writer.write_u8(0x03)?; - for (record, number) in list { - record.encode(writer)?; - number.encode(writer)?; - } - } - Self::DTCSnapshotRecordList(list) => { - writer.write_u8(0x04)?; - list.encode(writer)?; - } - Self::DTCExtDataRecordList(list) => { - writer.write_u8(0x06)?; - list.encode(writer)?; - } - Self::DTCFaultDetectionCounterRecordList(list) => { - writer.write_u8(0x14)?; - for fault_detection_counter in list { - fault_detection_counter.encode(writer)?; - } - } - Self::DTCSeverityRecordList(id, status, list) => { - writer.write_u8(*id)?; - status.encode(writer)?; - for dtcs in list { - dtcs.encode(writer)?; - } - } - Self::UserDefMemoryDTCByStatusMaskList(data_record_struct) => { - writer.write_u8(0x17)?; - writer.write_u8(data_record_struct.memory_selection)?; - data_record_struct.status_availability_mask.encode(writer)?; - for (data_record, status) in &data_record_struct.record_data { - data_record.encode(writer)?; - status.encode(writer)?; - } - } - - Self::UserDefMemoryDTCSnapshotRecordByDTCNumberList(snapshot_struct) => { - writer.write_u8(0x18)?; - snapshot_struct.encode(writer)?; - } - Self::SupportedDTCExtDataRecordList(response_struct) => { - writer.write_u8(0x1A)?; - response_struct.status_availability_mask.encode(writer)?; - if let Some(record_number) = &response_struct.ext_data_record_number { - record_number.encode(writer)?; - for (record, status) in &response_struct.dtc_and_status_records { - record.encode(writer)?; - status.encode(writer)?; - } - } - } - Self::WWHOBDDTCByMaskRecordList(response_struct) => { - writer.write_u8(0x42)?; - writer.write_u8(response_struct.functional_group_identifier.value())?; - response_struct.status_availability_mask.encode(writer)?; - writer.write_u8(response_struct.severity_availability_mask.into())?; - writer.write_u8(response_struct.format_identifier.into())?; - for (dtc_severity, dtc_record, dtc_status) in &response_struct.record_data { - writer.write_u8((*dtc_severity).into())?; - dtc_record.encode(writer)?; - dtc_status.encode(writer)?; - } - } - Self::WWHOBDDTCWithPermanentStatusList(response_struct) => { - writer.write_u8(0x55)?; - writer.write_u8(response_struct.functional_group_identifier.value())?; - response_struct.status_availability_mask.encode(writer)?; - writer.write_u8(response_struct.format_identifier.into())?; - for (dtc_record, dtc_status) in &response_struct.record_data { - dtc_record.encode(writer)?; - dtc_status.encode(writer)?; - } - } - Self::DTCByReadinessGroupIdentifierList(response_struct) => { - writer.write_u8(0x56)?; - writer.write_u8(response_struct.functional_group_identifier.value())?; - response_struct.status_availability_mask.encode(writer)?; - writer.write_u8(response_struct.format_identifier.into())?; - writer.write_u8(response_struct.readiness_group_identifier)?; - for (dtc_record, dtc_status) in &response_struct.record_data { - dtc_record.encode(writer)?; - dtc_status.encode(writer)?; - } + Self::WWHOBDDTCByMaskRecord { raw_records, .. } => { + Some(DtcSeverityAndStatusIter::new(raw_records)) } + _ => None, } - Ok(self.required_size()) } } -impl SingleValueWireFormat for ReadDTCInfoResponse { - #[allow(clippy::too_many_lines)] - fn decode(reader: &mut T) -> Result { - let subfunction_id = reader.read_u8()?; +impl<'a> Decode<'a> for ReadDTCInfoResponseRx<'a> { + fn decode(buf: &'a [u8]) -> Result<(Self, &'a [u8]), Error> { + if buf.is_empty() { + return Err(Error::InsufficientData(1)); + } + let subfunction_id = buf[0]; + let buf = &buf[1..]; match subfunction_id { 0x01 | 0x07 => { - let status = DTCStatusAvailabilityMask::from(reader.read_u8()?); - let count = reader.read_u16::()?; - Ok(Self::NumberOfDTCs(subfunction_id, status, count)) - } - 0x02 | 0x0A | 0x0B | 0x0C | 0x0D | 0x0E | 0x15 => { - let status = DTCStatusAvailabilityMask::from(reader.read_u8()?); - let mut dtcs: Vec<(DTCRecord, DTCStatusMask)> = Vec::new(); - - // Loop until we're done with the reader and fill the DTC list - while let Ok(Some(record)) = DTCRecord::decode_next(reader) { - match reader.read_u8() { - Ok(status) => dtcs.push((record, DTCStatusMask::from(status))), - Err(_) => break, - } - } - - Ok(Self::DTCList(subfunction_id, status, dtcs)) - } - 0x03 => { - let mut dtcs: Vec<(DTCRecord, DTCSnapshotRecordNumber)> = Vec::new(); - - // Loop until we're done with the reader and fill the DTC list - while let Ok(Some(record)) = DTCRecord::decode_next(reader) { - match DTCSnapshotRecordNumber::decode_next(reader)? { - Some(number) => dtcs.push((record, number)), - None => break, - } - } - - Ok(Self::DTCSnapshotList(dtcs)) - } - 0x04 => { - let snapshot_list = DTCSnapshotRecordList::decode(reader)?; - Ok(Self::DTCSnapshotRecordList(snapshot_list)) - } - 0x06 => { - let ext_data_list = DTCExtDataRecordList::decode(reader)?; - Ok(Self::DTCExtDataRecordList(ext_data_list)) - } - 0x08 | 0x09 => { - let status = DTCStatusAvailabilityMask::from(reader.read_u8()?); - let mut dtcs = Vec::new(); - - for dtc_severity_record in DTCSeverityRecord::decode_iter(reader) { - match dtc_severity_record { - Ok(p) => { - dtcs.push(p); - } - Err(e) => { - return Err(e); - } - } - } - - Ok(Self::DTCSeverityRecordList(subfunction_id, status, dtcs)) - } - 0x14 => { - let mut dtcs = Vec::new(); - for dtc_fault_record in DTCFaultDetectionCounterRecord::decode_iter(reader) { - match dtc_fault_record { - Ok(p) => { - dtcs.push(p); - } - Err(e) => { - return Err(e); - } - } - } - Ok(Self::DTCFaultDetectionCounterRecordList(dtcs)) - } - 0x17 => { - let memory_selection = reader.read_u8()?; - let status_availability_mask = DTCStatusMask::decode(reader)?; - let mut record_data = Vec::new(); - - while let Ok(Some(record)) = DTCRecord::decode_next(reader) { - let status = DTCStatusMask::decode(reader)?; - record_data.push((record, status)); - } - - Ok(Self::UserDefMemoryDTCByStatusMaskList( - UserDefMemoryDTCByStatusMaskRecord { - memory_selection, + if buf.len() < 3 { + return Err(Error::InsufficientData(4)); + } + let status_availability_mask = DTCStatusAvailabilityMask::from(buf[0]); + let count = u16::from_be_bytes([buf[1], buf[2]]); + Ok(( + Self::NumberOfDTCs { + sub_function_id: subfunction_id, status_availability_mask, - record_data, + count, }, + &buf[3..], )) } - 0x18 => Ok(Self::UserDefMemoryDTCSnapshotRecordByDTCNumberList( - UserDefMemoryDTCSnapshotRecordByDTCNumRecord::decode(reader)?, - )), - 0x1A => { - let status_availability_mask = DTCStatusAvailabilityMask::decode(reader)?; - let mut dtc_and_status_records = Vec::new(); - let ext_data_record_number = DTCExtDataRecordNumber::decode_next(reader)?; - if ext_data_record_number.is_some() { - while let Ok(Some(dtc_record)) = DTCRecord::decode_next(reader) { - let dtc_status = DTCStatusMask::decode(reader)?; - dtc_and_status_records.push((dtc_record, dtc_status)); - } + 0x02 | 0x0A | 0x0B | 0x0C | 0x0D | 0x0E | 0x15 => { + if buf.is_empty() { + return Err(Error::InsufficientData(2)); } - Ok(Self::SupportedDTCExtDataRecordList( - SupportedDTCExtDataRecord { + let status_availability_mask = DTCStatusAvailabilityMask::from(buf[0]); + Ok(( + Self::DTCList { + sub_function_id: subfunction_id, status_availability_mask, - ext_data_record_number, - dtc_and_status_records, + raw_records: &buf[1..], }, + &[], )) } - - 0x42 => { - let functional_group_identifier = - FunctionalGroupIdentifier::from(reader.read_u8()?); - let status_availability_mask = DTCStatusAvailabilityMask::decode(reader)?; - let severity_availability_mask = DTCSeverityMask::from(reader.read_u8()?); - let format_identifier = DTCFormatIdentifier::from(reader.read_u8()?); - if (format_identifier != DTCFormatIdentifier::SAE_J2012_DA_DTCFormat_04) - && (format_identifier != DTCFormatIdentifier::SAE_J1939_73_DTCFormat) - { - return Err(Error::InvalidDtcFormatIdentifier(u8::from( - format_identifier, - ))); - } - let mut record_data = Vec::new(); - while let Ok(dtc_severity_mask) = reader.read_u8() { - let dtc_severity_mask = DTCSeverityMask::from(dtc_severity_mask); - let dtc_record = DTCRecord::decode(reader)?; - let dtc_status = DTCStatusMask::decode(reader)?; - record_data.push((dtc_severity_mask, dtc_record, dtc_status)); - } - - Ok(Self::WWHOBDDTCByMaskRecordList(WWHOBDDTCByMaskRecord { - functional_group_identifier, - status_availability_mask, - severity_availability_mask, - format_identifier, - record_data, - })) - } - 0x55 => { - let functional_group_identifier = - FunctionalGroupIdentifier::from(reader.read_u8()?); - let status_availability_mask = DTCStatusAvailabilityMask::decode(reader)?; - let format_identifier = DTCFormatIdentifier::from(reader.read_u8()?); - if !matches!( - format_identifier, - DTCFormatIdentifier::SAE_J2012_DA_DTCFormat_04 - | DTCFormatIdentifier::SAE_J1939_73_DTCFormat - ) { - return Err(Error::InvalidDtcFormatIdentifier(u8::from( - format_identifier, - ))); - } - let mut record_data = Vec::new(); - while let Ok(Some(dtc_record)) = DTCRecord::decode_next(reader) { - let dtc_status = DTCStatusMask::decode(reader)?; - record_data.push((dtc_record, dtc_status)); + 0x14 => Ok((Self::DTCFaultDetectionCounterList { raw_records: buf }, &[])), + 0x08 | 0x09 => { + if buf.is_empty() { + return Err(Error::InsufficientData(2)); } - - Ok(Self::WWHOBDDTCWithPermanentStatusList( - WWHOBDDTCWithPermanentStatusRecord { - functional_group_identifier, + let status_availability_mask = DTCStatusAvailabilityMask::from(buf[0]); + Ok(( + Self::DTCSeverityList { + sub_function_id: subfunction_id, status_availability_mask, - format_identifier, - record_data, + raw_records: &buf[1..], }, + &[], )) } - 0x56 => { - let functional_group_identifier = - FunctionalGroupIdentifier::from(reader.read_u8()?); - let status_availability_mask = DTCStatusAvailabilityMask::decode(reader)?; - let format_identifier = DTCFormatIdentifier::from(reader.read_u8()?); - let readiness_group_identifier = - DTCReadinessGroupIdentifier::from(reader.read_u8()?); - let mut record_data = Vec::new(); - while let Ok(Some(dtc_record)) = DTCRecord::decode_next(reader) { - let dtc_status = DTCStatusMask::decode(reader)?; - record_data.push((dtc_record, dtc_status)); - } - - Ok(Self::DTCByReadinessGroupIdentifierList( - DTCByReadinessGroupIdentifierRecord { + 0x42 => { + if buf.len() < 4 { + return Err(Error::InsufficientData(5)); + } + let functional_group_identifier = FunctionalGroupIdentifier::from(buf[0]); + let status_availability_mask = DTCStatusAvailabilityMask::from(buf[1]); + let severity_availability_mask = DTCSeverityMask::from(buf[2]); + let format_identifier = DTCFormatIdentifier::from(buf[3]); + Ok(( + Self::WWHOBDDTCByMaskRecord { functional_group_identifier, status_availability_mask, + severity_availability_mask, format_identifier, - readiness_group_identifier, - record_data, + raw_records: &buf[4..], }, + &[], )) } - _ => todo!(), // _ => Err(Error::InvalidDtcSubfunctionType(subfunction_id)), + _ => Err(Error::InvalidDtcSubfunctionType(subfunction_id)), } } } -#[cfg(test)] -mod response { - - use super::*; - - #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] - #[derive(Clone, Copy, Debug, PartialEq)] - pub enum TestIdentifier { - Abracadabra = 0xBEEF, - } - - impl PartialEq for TestIdentifier { - fn eq(&self, other: &u16) -> bool { - match self { - TestIdentifier::Abracadabra => *other == 0xBEEF, - } - } - } - - impl WireFormat for TestIdentifier { - fn encode(&self, writer: &mut T) -> Result { - writer.write_u16::(*self as u16)?; - Ok(self.required_size()) - } - - fn required_size(&self) -> usize { - 2 - } - } - - impl IterableWireFormat for TestIdentifier { - fn decode_next(reader: &mut T) -> Result, Error> { - let mut buf = [0u8; 2]; - reader.read_exact(&mut buf)?; - - let id = u16::from_be_bytes(buf); - if TestIdentifier::Abracadabra == id { - Ok(Some(TestIdentifier::Abracadabra)) - } else { - Err(Error::NoDataAvailable) - } - } - } - - /////////////////////////////////////////////////////////////////////////////////////////////// - - #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] - #[derive(Clone, Copy, Debug, PartialEq)] - enum TestPayload { - Abracadabra(u8), - } - - impl WireFormat for TestPayload { - fn encode(&self, writer: &mut T) -> Result { - let id_bytes: u16 = match self { - TestPayload::Abracadabra(_) => 0xBEEF, - }; - - writer.write_all(&id_bytes.to_be_bytes())?; - - match self { - TestPayload::Abracadabra(value) => { - writer.write_u8(*value)?; - Ok(self.required_size()) - } - } - } - - fn required_size(&self) -> usize { - 3 - } - } - impl IterableWireFormat for TestPayload { - fn decode_next(reader: &mut T) -> Result, Error> { - let mut buf = [0u8; 2]; - reader.read_exact(&mut buf)?; - - let value = u16::from_be_bytes(buf); - - if value == TestIdentifier::Abracadabra as u16 { - let mut byte = [0u8; 1]; - reader.read_exact(&mut byte)?; - Ok(Some(TestPayload::Abracadabra(byte[0]))) - } else { - Err(Error::NoDataAvailable) - } - } - } - - #[test] - fn dtc_list() { - // skip formatting - #[rustfmt::skip] - let bytes = [ - 0x02, // subfunction - 0x01, // Availability mask - // First DTC record - 0x01, 0x02, 0x03, (DTCStatusMask::PendingDTC | DTCStatusMask::TestFailed).into(), - // Second DTC record - 0x17, 0x04, 0x03, DTCStatusMask::TestNotCompletedThisOperationCycle.into(), - ]; - let mut reader = &bytes[..]; - let response: ReadDTCInfoResponse = - ReadDTCInfoResponse::decode(&mut reader).unwrap(); - assert_eq!( - response, - ReadDTCInfoResponse::DTCList( - 0x02, - DTCStatusMask::TestFailed, - vec![ - ( - DTCRecord::new(0x01, 0x02, 0x03), - DTCStatusMask::PendingDTC | DTCStatusMask::TestFailed - ), - ( - DTCRecord::new(0x17, 0x04, 0x03), - DTCStatusMask::TestNotCompletedThisOperationCycle - ) - ] - ) - ); - - // write - let mut writer = Vec::new(); - let written = response.encode(&mut writer).unwrap(); - assert_eq!(writer, bytes); - assert_eq!(written, bytes.len()); - assert_eq!(written, response.required_size()); - } - - #[test] - fn severity_list_test() { - let bytes: [u8; 8] = [ - 0x08, // subfunction - 0x01, // Availability mask - DTCSeverityMask::CheckImmediately.into(), - FunctionalGroupIdentifier::EmissionsSystemGroup.into(), - 0x01, - 0x02, - 0x03, - (DTCStatusMask::PendingDTC | DTCStatusMask::TestFailed).into(), - ]; - let mut reader = &bytes[..]; - let response: ReadDTCInfoResponse = - ReadDTCInfoResponse::decode(&mut reader).unwrap(); - assert_eq!( - response, - ReadDTCInfoResponse::DTCSeverityRecordList( - 0x08, - DTCStatusMask::TestFailed, - vec![ - (DTCSeverityRecord { - severity: DTCSeverityMask::CheckImmediately, - functional_group_identifier: - FunctionalGroupIdentifier::EmissionsSystemGroup, - dtc_record: DTCRecord::new(0x01, 0x02, 0x03), - dtc_status_mask: (DTCStatusMask::PendingDTC | DTCStatusMask::TestFailed), - }) - ] - ) - ); - - // write - let mut writer = Vec::new(); - let written = response.encode(&mut writer).unwrap(); - assert_eq!(writer, bytes); - assert_eq!(written, bytes.len()); - assert_eq!(written, response.required_size()); - } - - #[test] - fn severity_empty_list_test() { - let bytes: [u8; 2] = [ - 0x08, // subfunction - 0x01, // Availability mask - ]; - let mut reader = &bytes[..]; - let response: ReadDTCInfoResponse = - ReadDTCInfoResponse::decode(&mut reader).unwrap(); - assert_eq!( - response, - ReadDTCInfoResponse::DTCSeverityRecordList(0x08, DTCStatusMask::TestFailed, vec![]) - ); - - // write - let mut writer = Vec::new(); - let written = response.encode(&mut writer).unwrap(); - assert_eq!(writer, bytes); - assert_eq!(written, bytes.len()); - assert_eq!(written, response.required_size()); - } - - #[test] - fn fault_detection_test() { - let bytes = [ - 0x14, // subfunction - 0x01, 0x02, 0x03, //DTC Record - 0x04, //DTC Status - ]; - let mut reader = &bytes[..]; - let response: ReadDTCInfoResponse = - ReadDTCInfoResponse::decode(&mut reader).unwrap(); - assert_eq!( - response, - ReadDTCInfoResponse::DTCFaultDetectionCounterRecordList(vec![ - DTCFaultDetectionCounterRecord { - dtc_record: DTCRecord::new(0x01, 0x02, 0x03), - dtc_fault_detection_counter: 0x04 - } - ]) - ); - - // write - let mut writer = Vec::new(); - let written = response.encode(&mut writer).unwrap(); - assert_eq!(writer, bytes); - assert_eq!(written, bytes.len()); - assert_eq!(written, response.required_size()); - } - #[test] - fn fault_detection_empty_test() { - let bytes = [ - 0x14, // subfunction - ]; - let mut reader = &bytes[..]; - let response: ReadDTCInfoResponse = - ReadDTCInfoResponse::decode(&mut reader).unwrap(); - assert_eq!( - response, - ReadDTCInfoResponse::DTCFaultDetectionCounterRecordList(vec![]) - ); - - // write - let mut writer = Vec::new(); - let written = response.encode(&mut writer).unwrap(); - assert_eq!(writer, bytes); - assert_eq!(written, bytes.len()); - assert_eq!(written, response.required_size()); - } - - #[test] - fn user_def_memory_dtc_by_statusmask_empty_list() { - // skip formatting - #[rustfmt::skip] - let bytes = [ - 0x17, // subfunction - 0x15, // Memory Selection - DTCStatusAvailabilityMask::TestFailed.into(), //Availability Mask - ]; - let mut reader = &bytes[..]; - - let response: ReadDTCInfoResponse = - ReadDTCInfoResponse::decode(&mut reader).unwrap(); - - assert_eq!( - response, - ReadDTCInfoResponse::UserDefMemoryDTCByStatusMaskList( - UserDefMemoryDTCByStatusMaskRecord { - memory_selection: 0x15, - status_availability_mask: DTCStatusAvailabilityMask::TestFailed, - record_data: vec![] - } - ) - ); - // write - let mut writer = Vec::new(); - let written = response.encode(&mut writer).unwrap(); - assert_eq!(writer, bytes, "Written: \n{writer:02X?}\n{bytes:02X?}"); - assert_eq!(written, bytes.len(), "Written: \n{writer:?}\n{bytes:?}"); - assert_eq!(written, response.required_size()); - } - - #[test] - fn user_def_memory_dtc_by_statusmask_list() { - // skip formatting - #[rustfmt::skip] - let bytes = [ - 0x17, // subfunction - 0x15, // Memory Selection - DTCStatusAvailabilityMask::TestFailed.into(), // Availability Mask - 0x12, 0x34, 0x56, // DTC Mask - DTCStatusMask::TestFailed.into(), // Status - 0x12, 0x34, 0x56, // DTC Mask - DTCStatusMask::TestFailed.into(), // Status - ]; - let mut reader = &bytes[..]; - - let response: ReadDTCInfoResponse = - ReadDTCInfoResponse::decode(&mut reader).unwrap(); - - assert_eq!( - response, - ReadDTCInfoResponse::UserDefMemoryDTCByStatusMaskList( - UserDefMemoryDTCByStatusMaskRecord { - memory_selection: 0x15, - status_availability_mask: DTCStatusAvailabilityMask::TestFailed, - record_data: vec![ - (DTCRecord::new(0x12, 0x34, 0x56), DTCStatusMask::TestFailed), - (DTCRecord::new(0x12, 0x34, 0x56), DTCStatusMask::TestFailed), - ] - } - ) - ); - // write - let mut writer = Vec::new(); - let written = response.encode(&mut writer).unwrap(); - assert_eq!(writer, bytes, "Written: \n{writer:02X?}\n{bytes:02X?}"); - assert_eq!(written, bytes.len(), "Written: \n{writer:?}\n{bytes:?}"); - assert_eq!(written, response.required_size()); - } - #[test] - fn user_def_memory_dtc_by_dtc_number_empty_list() { - // skip formatting - #[rustfmt::skip] - let bytes = [ - 0x18, // subfunction - 0x01, // Memory Selection - 0x12, 0x34, 0x56, // DTC Mask - DTCStatusAvailabilityMask::TestFailed.into(), // Availability Mask - ]; - let mut reader = &bytes[..]; - - let response: ReadDTCInfoResponse = - ReadDTCInfoResponse::decode(&mut reader).unwrap(); - - assert_eq!( - response, - ReadDTCInfoResponse::UserDefMemoryDTCSnapshotRecordByDTCNumberList( - UserDefMemoryDTCSnapshotRecordByDTCNumRecord { - memory_selection: 0x1, - dtc_record: DTCRecord::new(0x12, 0x34, 0x56), - dtc_status_mask: DTCStatusMask::TestFailed, - dtc_snapshot_record: vec![] - } - ) - ); - // write - let mut writer = Vec::new(); - let written = response.encode(&mut writer).unwrap(); - assert_eq!(writer, bytes, "Written: \n{writer:02X?}\n{bytes:02X?}"); - assert_eq!(written, bytes.len(), "Written: \n{writer:?}\n{bytes:?}"); - assert_eq!(written, response.required_size()); - } - - #[test] - fn user_def_memory_dtc_by_dtc_number_list() { - // skip formatting - #[rustfmt::skip] - let bytes = [ - 0x18, // subfunction - 0x01, // Memory Selection - 0x12, 0x34, 0x56, // DTC Mask - DTCStatusAvailabilityMask::TestFailed.into(), // Availability Mask - 0x13, // DTCSnapshotRecordNumber - 0x02, // DTCSnapshotRecordNumberOfIdentifiers - 0xBE, 0xEF, // SnapshotDataIdentifier - 0x05, // SnapshotData - 0xBE, 0xEF, // SnapshotDataIdentifier - 0x05, // SnapshotData - ]; - let mut reader = &bytes[..]; - - let response: ReadDTCInfoResponse = - ReadDTCInfoResponse::decode(&mut reader).unwrap(); - - assert_eq!( - response, - ReadDTCInfoResponse::UserDefMemoryDTCSnapshotRecordByDTCNumberList( - UserDefMemoryDTCSnapshotRecordByDTCNumRecord { - memory_selection: 0x1, - dtc_record: DTCRecord::new(0x12, 0x34, 0x56), - dtc_status_mask: DTCStatusMask::TestFailed, - dtc_snapshot_record: vec![( - DTCSnapshotRecordNumber::new(0x13), - DTCSnapshotRecord { - data: vec![ - TestPayload::Abracadabra(0x05), - TestPayload::Abracadabra(0x05) - ] - } - )] - } - ) - ); - // write - let mut writer = Vec::new(); - let written = response.encode(&mut writer).unwrap(); - assert_eq!(writer, bytes, "Written: \n{writer:02X?}\n{bytes:02X?}"); - assert_eq!(written, bytes.len(), "Written: \n{writer:?}\n{bytes:?}"); - assert_eq!(written, response.required_size()); - } - - #[test] - fn supported_dtc_ext_data_record_list() { - // skip formatting - #[rustfmt::skip] - let bytes = [ - 0x1A, // subfunction - DTCStatusAvailabilityMask::TestFailed.into(), // Availability Mask - DTCExtDataRecordNumber::AllDTCExtDataRecords.value(), // DTC Extended Data Record Number - 0x15,0x17,0x19 ,// DTCRecord - DTCStatusMask::TestFailedSinceLastClear.into(),// DTC Status - 0x15,0x17,0x19 ,// DTCRecord - DTCStatusMask::TestFailedSinceLastClear.into(),// DTC Status - ]; - let mut reader = &bytes[..]; - - let response: ReadDTCInfoResponse = - ReadDTCInfoResponse::decode(&mut reader).unwrap(); - - assert_eq!( - response, - ReadDTCInfoResponse::SupportedDTCExtDataRecordList(SupportedDTCExtDataRecord { - status_availability_mask: DTCStatusAvailabilityMask::TestFailed, - ext_data_record_number: Some(DTCExtDataRecordNumber::AllDTCExtDataRecords), - dtc_and_status_records: vec![ - ( - DTCRecord::new(0x15, 0x17, 0x19), // DTCRecord - DTCStatusMask::TestFailedSinceLastClear - ), - ( - DTCRecord::new(0x15, 0x17, 0x19), // DTCRecord - DTCStatusMask::TestFailedSinceLastClear - ) - ] - }) - ); - // write - let mut writer = Vec::new(); - let written = response.encode(&mut writer).unwrap(); - assert_eq!(writer, bytes, "Written: \n{writer:02X?}\n{bytes:02X?}"); - assert_eq!(written, bytes.len(), "Written: \n{writer:?}\n{bytes:?}"); - assert_eq!(written, response.required_size()); - } - - #[test] - fn supported_dtc_ext_data_record_empty_list() { - // skip formatting - #[rustfmt::skip] - let bytes = [ - 0x1A, // subfunction - DTCStatusAvailabilityMask::TestFailed.into(), // Availability Mask - ]; - let mut reader = &bytes[..]; - let response: ReadDTCInfoResponse = - ReadDTCInfoResponse::decode(&mut reader).unwrap(); - assert_eq!( - response, - ReadDTCInfoResponse::SupportedDTCExtDataRecordList(SupportedDTCExtDataRecord { - status_availability_mask: DTCStatusAvailabilityMask::TestFailed, - ext_data_record_number: None, - dtc_and_status_records: vec![] - }) - ); - // write - let mut writer = Vec::new(); - let written = response.encode(&mut writer).unwrap(); - assert_eq!(writer, bytes, "Written: \n{writer:02X?}\n{bytes:02X?}"); - assert_eq!(written, bytes.len(), "Written: \n{writer:?}\n{bytes:?}"); - assert_eq!(written, response.required_size()); - } - - #[test] - fn report_wwhobd_dtc_by_mask_record_list() { - // skip formatting - #[rustfmt::skip] - let bytes = [ - 0x42, // subfunction - FunctionalGroupIdentifier::VODBSystem.into(), - DTCStatusAvailabilityMask::TestFailed.into(), - DTCSeverityMask::DTCClass_0.into(), - DTCFormatIdentifier::SAE_J2012_DA_DTCFormat_04.into(), - DTCSeverityMask::DTCClass_0.into(), - 0x15,0x17,0x19 ,// DTCRecord - DTCStatusAvailabilityMask::TestFailed.into(), - DTCSeverityMask::DTCClass_0.into(), - 0x15,0x17,0x19 ,// DTCRecord - DTCStatusAvailabilityMask::TestFailed.into(), - ]; - let mut reader = &bytes[..]; - - let response: ReadDTCInfoResponse = - ReadDTCInfoResponse::decode(&mut reader).unwrap(); - - assert_eq!( - response, - ReadDTCInfoResponse::WWHOBDDTCByMaskRecordList(WWHOBDDTCByMaskRecord { - functional_group_identifier: FunctionalGroupIdentifier::VODBSystem, - status_availability_mask: DTCStatusAvailabilityMask::TestFailed, - severity_availability_mask: DTCSeverityMask::DTCClass_0, - format_identifier: DTCFormatIdentifier::SAE_J2012_DA_DTCFormat_04, - record_data: vec![ - ( - DTCSeverityMask::DTCClass_0, - DTCRecord::new(0x15, 0x17, 0x19), - DTCStatusAvailabilityMask::TestFailed - ), - ( - DTCSeverityMask::DTCClass_0, - DTCRecord::new(0x15, 0x17, 0x19), - DTCStatusAvailabilityMask::TestFailed - ) - ] - }) - ); - // write - let mut writer = Vec::new(); - let written = response.encode(&mut writer).unwrap(); - assert_eq!(writer, bytes, "Written: \n{writer:02X?}\n{bytes:02X?}"); - assert_eq!(written, bytes.len(), "Written: \n{writer:?}\n{bytes:?}"); - assert_eq!(written, response.required_size()); - } - - #[test] - fn report_wwhobd_dtc_by_mask_record_empty_list() { - // skip formatting - #[rustfmt::skip] - let bytes = [ - 0x42, // subfunction - FunctionalGroupIdentifier::VODBSystem.into(), - DTCStatusAvailabilityMask::TestFailed.into(), - DTCSeverityMask::all_flags().into(), - DTCFormatIdentifier::SAE_J2012_DA_DTCFormat_04.into(), - ]; - let mut reader = &bytes[..]; - - let response: ReadDTCInfoResponse = - ReadDTCInfoResponse::decode(&mut reader).unwrap(); - - assert_eq!( - response, - ReadDTCInfoResponse::WWHOBDDTCByMaskRecordList(WWHOBDDTCByMaskRecord { - functional_group_identifier: FunctionalGroupIdentifier::VODBSystem, - status_availability_mask: DTCStatusAvailabilityMask::TestFailed, - severity_availability_mask: DTCSeverityMask::all_flags(), - format_identifier: DTCFormatIdentifier::SAE_J2012_DA_DTCFormat_04, - record_data: vec![] - }) - ); - // write - let mut writer = Vec::new(); - let written = response.encode(&mut writer).unwrap(); - assert_eq!(writer, bytes, "Written: \n{writer:02X?}\n{bytes:02X?}"); - assert_eq!(written, bytes.len(), "Written: \n{writer:?}\n{bytes:?}"); - assert_eq!(written, response.required_size()); - } - - #[test] - fn report_wwhobd_dtc_with_permanent_status_list() { - // skip formatting - #[rustfmt::skip] - let bytes = [ - 0x55, // subfunction - FunctionalGroupIdentifier::VODBSystem.into(), - DTCStatusAvailabilityMask::TestFailed.into(), - DTCFormatIdentifier::SAE_J2012_DA_DTCFormat_04.into(), - 0x15,0x17,0x19 ,// DTCRecord - DTCStatusMask::TestFailed.into(), - 0x51,0x71,0x91 ,// DTCRecord - DTCStatusMask::TestFailed.into(), - ]; - let mut reader = &bytes[..]; - - let response: ReadDTCInfoResponse = - ReadDTCInfoResponse::decode(&mut reader).unwrap(); - - assert_eq!( - response, - ReadDTCInfoResponse::WWHOBDDTCWithPermanentStatusList( - WWHOBDDTCWithPermanentStatusRecord { - functional_group_identifier: FunctionalGroupIdentifier::VODBSystem, - status_availability_mask: DTCStatusAvailabilityMask::TestFailed, - format_identifier: DTCFormatIdentifier::SAE_J2012_DA_DTCFormat_04, - record_data: vec![ - (DTCRecord::new(0x15, 0x17, 0x19), DTCStatusMask::TestFailed), - (DTCRecord::new(0x51, 0x71, 0x91), DTCStatusMask::TestFailed) - ] - } - ) - ); - // write - let mut writer = Vec::new(); - let written = response.encode(&mut writer).unwrap(); - assert_eq!(writer, bytes, "Written: \n{writer:02X?}\n{bytes:02X?}"); - assert_eq!(written, bytes.len(), "Written: \n{writer:?}\n{bytes:?}"); - assert_eq!(written, response.required_size()); - } - - #[test] - fn report_dtc_by_readiness_group_identifier_list() { - // skip formatting - #[rustfmt::skip] - let bytes = [ - 0x56, // subfunction - FunctionalGroupIdentifier::VODBSystem.into(), - DTCStatusAvailabilityMask::TestFailed.into(), - DTCFormatIdentifier::SAE_J2012_DA_DTCFormat_04.into(), - 0x72,// Readiness Group Identifier - 0x15,0x17,0x19 ,// DTCRecord - DTCStatusAvailabilityMask::TestFailed.into(), - 0x51,0x71,0x91 ,// DTCRecord - DTCStatusAvailabilityMask::TestFailed.into(), - ]; - let mut reader = &bytes[..]; - - let response: ReadDTCInfoResponse = - ReadDTCInfoResponse::decode(&mut reader).unwrap(); - - assert_eq!( - response, - ReadDTCInfoResponse::DTCByReadinessGroupIdentifierList( - DTCByReadinessGroupIdentifierRecord { - functional_group_identifier: FunctionalGroupIdentifier::VODBSystem, - status_availability_mask: DTCStatusAvailabilityMask::TestFailed, - format_identifier: DTCFormatIdentifier::SAE_J2012_DA_DTCFormat_04, - readiness_group_identifier: DTCReadinessGroupIdentifier::from(0x72), - record_data: vec![ - ( - DTCRecord::new(0x15, 0x17, 0x19), - DTCStatusAvailabilityMask::TestFailed - ), - ( - DTCRecord::new(0x51, 0x71, 0x91), - DTCStatusAvailabilityMask::TestFailed - ) - ] - } - ) - ); - // write - let mut writer = Vec::new(); - let written = response.encode(&mut writer).unwrap(); - assert_eq!(writer, bytes, "Written: \n{writer:02X?}\n{bytes:02X?}"); - assert_eq!(written, bytes.len(), "Written: \n{writer:?}\n{bytes:?}"); - assert_eq!(written, response.required_size()); - } - - #[test] - fn report_dtc_by_readiness_group_identifier_empty_list() { - // skip formatting - #[rustfmt::skip] - let bytes = [ - 0x56, // subfunction - FunctionalGroupIdentifier::VODBSystem.into(), - DTCStatusAvailabilityMask::TestFailed.into(), - DTCFormatIdentifier::SAE_J2012_DA_DTCFormat_04.into(), - 0x72,// Readiness Group Identifier - ]; - let mut reader = &bytes[..]; - - let response: ReadDTCInfoResponse = - ReadDTCInfoResponse::decode(&mut reader).unwrap(); - - assert_eq!( - response, - ReadDTCInfoResponse::DTCByReadinessGroupIdentifierList( - DTCByReadinessGroupIdentifierRecord { - functional_group_identifier: FunctionalGroupIdentifier::VODBSystem, - status_availability_mask: DTCStatusAvailabilityMask::TestFailed, - format_identifier: DTCFormatIdentifier::SAE_J2012_DA_DTCFormat_04, - readiness_group_identifier: DTCReadinessGroupIdentifier::from(0x72), - record_data: vec![] - } - ) - ); - // write - let mut writer = Vec::new(); - let written = response.encode(&mut writer).unwrap(); - assert_eq!(writer, bytes, "Written: \n{writer:02X?}\n{bytes:02X?}"); - assert_eq!(written, bytes.len(), "Written: \n{writer:?}\n{bytes:?}"); - assert_eq!(written, response.required_size()); - } -} - -#[cfg(test)] -mod ext_data { - use super::*; - - #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] - #[derive(Clone, Copy, Debug, PartialEq)] - pub enum TestDTCExtDataRecordNumber { - // DTC records - WarmUpCycleCount = 0x04, - FaultDetectionCounter = 0x05, - } - - impl WireFormat for TestDTCExtDataRecordNumber { - fn required_size(&self) -> usize { - 1 - } - - fn encode(&self, writer: &mut T) -> Result { - writer.write_u8(*self as u8)?; - Ok(self.required_size()) - } - } - - impl IterableWireFormat for TestDTCExtDataRecordNumber { - fn decode_next(reader: &mut T) -> Result, Error> { - let id = reader.read_u8(); - match id { - Ok(0x04) => Ok(Some(TestDTCExtDataRecordNumber::WarmUpCycleCount)), - Ok(0x05) => Ok(Some(TestDTCExtDataRecordNumber::FaultDetectionCounter)), - Err(_) => Ok(None), - _ => Err(Error::NoDataAvailable), - } - } - } - - #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] - #[derive(Clone, Copy, Debug, PartialEq)] - enum TestDTCExtData { - WarmUpCycleCount(u16), - FaultDetectionCounter(u8), - } - - impl WireFormat for TestDTCExtData { - fn required_size(&self) -> usize { - match self { - TestDTCExtData::WarmUpCycleCount(_) => 3, - TestDTCExtData::FaultDetectionCounter(_) => 2, - } - } - - fn encode(&self, writer: &mut T) -> Result { - match self { - TestDTCExtData::WarmUpCycleCount(count) => { - writer.write_u8(TestDTCExtDataRecordNumber::WarmUpCycleCount as u8)?; - writer.write_u16::(*count)?; - } - TestDTCExtData::FaultDetectionCounter(count) => { - writer.write_u8(TestDTCExtDataRecordNumber::FaultDetectionCounter as u8)?; - writer.write_u8(*count)?; - } - } - Ok(self.required_size()) - } - } - - impl IterableWireFormat for TestDTCExtData { - fn decode_next(reader: &mut T) -> Result, Error> { - let id = TestDTCExtDataRecordNumber::decode_next(reader)?; - match id { - Some(TestDTCExtDataRecordNumber::WarmUpCycleCount) => { - let count = reader.read_u16::()?; - Ok(Some(TestDTCExtData::WarmUpCycleCount(count))) - } - Some(TestDTCExtDataRecordNumber::FaultDetectionCounter) => { - let count = reader.read_u8()?; - Ok(Some(TestDTCExtData::FaultDetectionCounter(count))) - } - None => Ok(None), +impl Encode for ReadDTCInfoResponseRx<'_> { + fn encoded_size(&self) -> usize { + match self { + Self::NumberOfDTCs { .. } => 4, + Self::DTCList { raw_records, .. } | Self::DTCSeverityList { raw_records, .. } => { + 2 + raw_records.len() } + Self::DTCFaultDetectionCounterList { raw_records } => 1 + raw_records.len(), + Self::WWHOBDDTCByMaskRecord { raw_records, .. } => 5 + raw_records.len(), } } - #[test] - fn ext_data_list() { - // skip formatting - #[rustfmt::skip] - let bytes = [ - 0x06, // subfunction - // First DTC record - 0x12, 0x34, 0x56, // DTC Mask - 0x24, //Status - 0x04, // "WarmUpCycleCount" - //Ext data - 0xBE, 0xEF, - 0x05, // "FaultDetectionCounter" - 0x10, - - ]; - let mut reader = &bytes[..]; - let response: ReadDTCInfoResponse = - ReadDTCInfoResponse::decode(&mut reader).unwrap(); - - // write - let mut writer = Vec::new(); - let written = response.encode(&mut writer).unwrap(); - assert_eq!(writer, bytes, "Written: \n{writer:02X?}\n{bytes:02X?}"); - assert_eq!(written, bytes.len(), "Written: \n{writer:?}\n{bytes:?}"); - assert_eq!(written, response.required_size()); - } -} - -#[cfg(test)] -mod request { - use super::*; - use crate::DTCStatusMask; - - #[test] - fn test_read_dtc_information_request() { - let bytes = [0x01, 0x01]; - let mut reader = &bytes[..]; - let mut writer = Vec::new(); - ReadDTCInfoRequest::new(ReadDTCInfoSubFunction::ReportDTCStoredData_ByRecordNumber( - DTCStoredDataRecordNumber::new(5).unwrap(), - )) - .encode(&mut writer) - .unwrap(); - let request = ReadDTCInfoRequest::decode(&mut reader).unwrap(); - assert_eq!( - request, - ReadDTCInfoRequest { - dtc_subfunction: ReadDTCInfoSubFunction::ReportNumberOfDTC_ByStatusMask( - DTCStatusMask::TestFailed - ) + fn encode(&self, writer: &mut impl embedded_io::Write) -> Result { + match self { + Self::NumberOfDTCs { + sub_function_id, + status_availability_mask, + count, + } => { + writer + .write_all(&[*sub_function_id, status_availability_mask.bits()]) + .map_err(Error::io)?; + writer.write_all(&count.to_be_bytes()).map_err(Error::io)?; + } + Self::DTCList { + sub_function_id, + status_availability_mask, + raw_records, + } + | Self::DTCSeverityList { + sub_function_id, + status_availability_mask, + raw_records, + } => { + writer + .write_all(&[*sub_function_id, status_availability_mask.bits()]) + .map_err(Error::io)?; + writer.write_all(raw_records).map_err(Error::io)?; + } + Self::DTCFaultDetectionCounterList { raw_records } => { + writer.write_all(&[0x14]).map_err(Error::io)?; + writer.write_all(raw_records).map_err(Error::io)?; + } + Self::WWHOBDDTCByMaskRecord { + functional_group_identifier, + status_availability_mask, + severity_availability_mask, + format_identifier, + raw_records, + } => { + writer + .write_all(&[ + 0x42, + u8::from(*functional_group_identifier), + status_availability_mask.bits(), + severity_availability_mask.bits(), + u8::from(*format_identifier), + ]) + .map_err(Error::io)?; + writer.write_all(raw_records).map_err(Error::io)?; } - ); - } - - #[test] - fn test_read_dtc_information_subfunction() { - let mut writer = Vec::new(); - let b = ReadDTCInfoSubFunction::ReportDTCWithPermanentStatus; - b.encode(&mut writer).unwrap(); - - assert_eq!(writer, vec![0x15]); - - for id in 0x01..=0x07 { - let mut writer = Vec::new(); - let func = match id { - 0x01 => ReadDTCInfoSubFunction::ReportNumberOfDTC_ByStatusMask( - DTCStatusMask::TestFailed, - ), - 0x02 => ReadDTCInfoSubFunction::ReportDTC_ByStatusMask( - DTCStatusMask::WarningIndicatorRequested, - ), - 0x03 => ReadDTCInfoSubFunction::ReportDTCSnapshotIdentification, - 0x04 => ReadDTCInfoSubFunction::ReportDTCSnapshotRecord_ByDTCNumber( - DTCRecord::new(0x01, 0x02, 0x03), - DTCSnapshotRecordNumber::new(0x04), - ), - 0x05 => ReadDTCInfoSubFunction::ReportDTCStoredData_ByRecordNumber( - DTCStoredDataRecordNumber::new(0x20).unwrap(), - ), - 0x06 => ReadDTCInfoSubFunction::ReportDTCExtDataRecord_ByDTCNumber( - DTCRecord::new(0x01, 0x02, 0x03), - DTCExtDataRecordNumber::new(0x04), - ), - 0x07 => ReadDTCInfoSubFunction::ReportNumberOfDTC_BySeverityMaskRecord( - DTCSeverityMask::DTCClass_4, - DTCStatusMask::TestFailed, - ), - _ => unreachable!("Invalid loop value"), - }; - let written = func.encode(&mut writer).unwrap(); - assert_eq!(written, func.required_size()); } + Ok(self.encoded_size()) } } diff --git a/src/services/request_download.rs b/src/services/request_download.rs index 0493316..5fa2166 100644 --- a/src/services/request_download.rs +++ b/src/services/request_download.rs @@ -1,9 +1,8 @@ //! `RequestDownload` (0x34) service implementation -use byteorder::{ReadBytesExt, WriteBytesExt}; use crate::{ - DataFormatIdentifier, Error, LengthFormatIdentifier, MemoryFormatIdentifier, - NegativeResponseCode, SingleValueWireFormat, WireFormat, + DataFormatIdentifier, Decode, Encode, Error, LengthFormatIdentifier, MemoryFormatIdentifier, + NegativeResponseCode, }; const REQUEST_DOWNLOAD_NEGATIVE_RESPONSE_CODES: [NegativeResponseCode; 6] = [ @@ -17,7 +16,7 @@ const REQUEST_DOWNLOAD_NEGATIVE_RESPONSE_CODES: [NegativeResponseCode; 6] = [ /// A request to the server for it to download data from the client /// -/// A positive response to this request ([`RequestDownloadResponse`]) will happen +/// A positive response to this request ([`RequestDownloadResponseTx`]) will happen /// after the server takes all necessary actions to receive the data once the server is ready to receive /// /// This is a variable length Request, determined by the `address_and_length_format_identifier` value @@ -35,13 +34,18 @@ pub struct RequestDownloadRequest { /// Has a variable number of bytes, max of 5 pub memory_address: u64, /// Size of the data to be downloaded. Number of bytes sent is determined by `address_and_length_format_identifier` - /// Used by the server to validate the data transferred by the [`TransferDataRequest`](crate::TransferDataRequest) service + /// Used by the server to validate the data transferred by the [`TransferDataRequestTx`](crate::TransferDataRequestTx) service /// Has a variable number of bytes, max of 4 pub memory_size: u32, } impl RequestDownloadRequest { - pub(crate) fn new( + /// Create a new `RequestDownloadRequest` + /// + /// # Errors + /// Returns an error if `memory_address` exceeds 5 bytes (> `0xFF_FFFF_FFFF`). + #[allow(clippy::cast_possible_truncation)] + pub fn new( data_format_identifier: DataFormatIdentifier, memory_address: u64, memory_size: u32, @@ -49,8 +53,12 @@ impl RequestDownloadRequest { if memory_address > 0xFF_FFFF_FFFF { return Err(Error::InvalidMemoryAddress(memory_address)); } - let address_and_length_format_identifier = - MemoryFormatIdentifier::from_values(memory_size, memory_address); + let memory_address_length = (u64::BITS - memory_address.leading_zeros()).div_ceil(8) as u8; + let memory_size_length = (u32::BITS - memory_size.leading_zeros()).div_ceil(8) as u8; + let address_and_length_format_identifier = MemoryFormatIdentifier { + memory_size_length, + memory_address_length, + }; Ok(Self { data_format_identifier, address_and_length_format_identifier, @@ -59,131 +67,142 @@ impl RequestDownloadRequest { }) } - fn get_shortened_memory_address(&self) -> Vec { - self.memory_address - .to_be_bytes() - .iter() - .skip( - 8 - self - .address_and_length_format_identifier - .memory_address_length as usize, - ) - .copied() - .collect() - } - - fn get_shortened_memory_size(&self) -> Vec { - self.memory_size - .to_be_bytes() - .iter() - .skip(4 - self.address_and_length_format_identifier.memory_size_length as usize) - .copied() - .collect() - } - /// Get the allowed [`NegativeResponseCode`] variants for this request #[must_use] pub fn allowed_nack_codes() -> &'static [NegativeResponseCode] { &REQUEST_DOWNLOAD_NEGATIVE_RESPONSE_CODES } } -impl WireFormat for RequestDownloadRequest { - fn required_size(&self) -> usize { +impl Encode for RequestDownloadRequest { + fn encoded_size(&self) -> usize { 2 + self.address_and_length_format_identifier.len() } - fn encode(&self, writer: &mut T) -> Result { - writer.write_u8(self.data_format_identifier.into())?; - writer.write_u8(self.address_and_length_format_identifier.into())?; - - writer.write_all(self.get_shortened_memory_address().as_mut_slice())?; - writer.write_all(self.get_shortened_memory_size().as_mut_slice())?; - - Ok(self.required_size()) + fn encode(&self, writer: &mut impl embedded_io::Write) -> Result { + writer + .write_all(&[ + self.data_format_identifier.into(), + self.address_and_length_format_identifier.into(), + ]) + .map_err(Error::io)?; + + // Write shortened memory address using a stack buffer instead of Vec + let addr_bytes = self.memory_address.to_be_bytes(); + let addr_len = self + .address_and_length_format_identifier + .memory_address_length as usize; + writer + .write_all(&addr_bytes[8 - addr_len..]) + .map_err(Error::io)?; + + // Write shortened memory size using a stack buffer instead of Vec + let size_bytes = self.memory_size.to_be_bytes(); + let size_len = self.address_and_length_format_identifier.memory_size_length as usize; + writer + .write_all(&size_bytes[4 - size_len..]) + .map_err(Error::io)?; + + Ok(self.encoded_size()) } } -impl SingleValueWireFormat for RequestDownloadRequest { - fn decode(reader: &mut T) -> Result { - let data_format_identifier = DataFormatIdentifier::from(reader.read_u8()?); - let memory_identifier = MemoryFormatIdentifier::try_from(reader.read_u8()?)?; - - let mut memory_address: Vec = vec![0; memory_identifier.memory_address_length as usize]; - let mut memory_size: Vec = vec![0; memory_identifier.memory_size_length as usize]; - - reader.read_exact(&mut memory_address)?; - reader.read_exact(&mut memory_size)?; +impl<'a> Decode<'a> for RequestDownloadRequest { + fn decode(buf: &'a [u8]) -> Result<(Self, &'a [u8]), Error> { + if buf.len() < 2 { + return Err(Error::InsufficientData(2)); + } + let data_format_identifier = DataFormatIdentifier::from(buf[0]); + let memory_identifier = MemoryFormatIdentifier::try_from(buf[1])?; + let addr_len = memory_identifier.memory_address_length as usize; + let size_len = memory_identifier.memory_size_length as usize; + let total = 2 + addr_len + size_len; + if buf.len() < total { + return Err(Error::InsufficientData(total)); + } - Ok(Self { - data_format_identifier, - address_and_length_format_identifier: memory_identifier, - memory_address: u64::from_be_bytes({ - let mut bytes = [0; 8]; - bytes[8 - memory_address.len()..].copy_from_slice(&memory_address); - bytes - }), - memory_size: u32::from_be_bytes({ - let mut bytes = [0; 4]; - bytes[4 - memory_size.len()..].copy_from_slice(&memory_size); - bytes - }), - }) + let mut addr_bytes = [0u8; 8]; + addr_bytes[8 - addr_len..].copy_from_slice(&buf[2..2 + addr_len]); + let memory_address = u64::from_be_bytes(addr_bytes); + + let mut size_bytes = [0u8; 4]; + size_bytes[4 - size_len..].copy_from_slice(&buf[2 + addr_len..total]); + let memory_size = u32::from_be_bytes(size_bytes); + + Ok(( + Self { + data_format_identifier, + address_and_length_format_identifier: memory_identifier, + memory_address, + memory_size, + }, + &buf[total..], + )) } } +/// Zero-alloc TX response for request download. Borrows from the caller. +/// /// Positive response to a [`RequestDownloadRequest`] indicating the server is ready to receive data. -#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] -#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))] -#[derive(Clone, Debug, PartialEq)] -#[non_exhaustive] -pub struct RequestDownloadResponse { - /// Format is similar to `address_and_length_format_identifier` field of the [`RequestDownloadRequest`] struct. - /// In it is a byte with the high nibble being the length of the `max_number_of_block_length` field. +#[derive(Clone, Copy, Debug, PartialEq)] +pub struct RequestDownloadResponseTx<'d> { length_format_identifier: LengthFormatIdentifier, - /// Maximum number of bytes to include in each [`TransferDataRequest`](crate::TransferDataRequest). - /// Variable length, determined by `length_format_identifier`. - pub max_number_of_block_length: Vec, + /// Maximum number of bytes per [`TransferDataRequest`](crate::TransferDataRequest). + pub max_number_of_block_length: &'d [u8], } -impl RequestDownloadResponse { - pub(crate) fn new(length_format_identifier: u8, max_number_of_block_length: Vec) -> Self { +impl<'d> RequestDownloadResponseTx<'d> { + /// Create a new request download response from a raw format byte and block length. + #[must_use] + pub fn new(length_format_byte: u8, max_number_of_block_length: &'d [u8]) -> Self { Self { - length_format_identifier: LengthFormatIdentifier::from(length_format_identifier), + length_format_identifier: LengthFormatIdentifier::from(length_format_byte), max_number_of_block_length, } } } -impl WireFormat for RequestDownloadResponse { - fn required_size(&self) -> usize { +impl Encode for RequestDownloadResponseTx<'_> { + fn encoded_size(&self) -> usize { 1 + self.max_number_of_block_length.len() } - fn encode(&self, writer: &mut T) -> Result { - writer.write_u8(self.length_format_identifier.into())?; - writer.write_all(&self.max_number_of_block_length)?; - Ok(self.required_size()) + fn encode(&self, writer: &mut impl embedded_io::Write) -> Result { + writer + .write_all(&[self.length_format_identifier.into()]) + .map_err(Error::io)?; + writer + .write_all(self.max_number_of_block_length) + .map_err(Error::io)?; + Ok(self.encoded_size()) } } -impl SingleValueWireFormat for RequestDownloadResponse { - fn decode(reader: &mut T) -> Result { - let length_format_identifier = LengthFormatIdentifier::from(reader.read_u8()?); - - let mut max_number_of_block_length: Vec = - vec![0; length_format_identifier.max_number_of_block_length as usize]; - reader.read_exact(&mut max_number_of_block_length)?; - - Ok(Self { - length_format_identifier, - max_number_of_block_length, - }) +impl<'a> Decode<'a> for RequestDownloadResponseTx<'a> { + fn decode(buf: &'a [u8]) -> Result<(Self, &'a [u8]), Error> { + if buf.is_empty() { + return Err(Error::InsufficientData(1)); + } + let length_format_identifier = LengthFormatIdentifier::from(buf[0]); + let len = length_format_identifier.max_number_of_block_length as usize; + let total = 1 + len; + if buf.len() < total { + return Err(Error::InsufficientData(total)); + } + Ok(( + Self { + length_format_identifier, + max_number_of_block_length: &buf[1..total], + }, + &buf[total..], + )) } } #[cfg(test)] mod tests { use super::*; + use crate::{Decode, Encode}; + #[test] fn simple_request() { let bytes: [u8; 7] = [ @@ -192,7 +211,7 @@ mod tests { 0xF0, 0xFF, 0xFF, 0x67, // memory address 0x0A, ]; - let req = RequestDownloadRequest::decode(&mut bytes.as_slice()).unwrap(); + let (req, _) = ::decode(&bytes).unwrap(); assert_eq!(u8::from(req.data_format_identifier), 0); assert_eq!(u8::from(req.address_and_length_format_identifier), 0x14); @@ -208,12 +227,6 @@ mod tests { assert_eq!(req.memory_address, 0xF0FF_FF67); assert_eq!(req.memory_size, 0x0A); - - assert_eq!( - req.get_shortened_memory_address(), - vec![0xF0, 0xFF, 0xFF, 0x67] - ); - assert_eq!(req.get_shortened_memory_size(), vec![0x0A]); } #[test] @@ -223,8 +236,8 @@ mod tests { 0x11, // 1 byte for memory size, 1 byte for memory address 0x67, ]; - let req = RequestDownloadRequest::decode(&mut bytes.as_slice()); - assert!(matches!(req, Err(Error::IoError(_)))); + let result = ::decode(&bytes); + assert!(result.is_err()); } #[test] @@ -249,8 +262,8 @@ mod tests { let req = RequestDownloadRequest::new(0x00.into(), 0xF0_FF_FF_67, 0x0A).unwrap(); let mut vec = vec![]; - req.encode(&mut vec).unwrap(); + Encode::encode(&req, &mut vec).unwrap(); - assert_eq!(vec.len(), req.required_size()); + assert_eq!(vec.len(), req.encoded_size()); } } diff --git a/src/services/request_file_transfer.rs b/src/services/request_file_transfer.rs index f09b6ca..c25207a 100644 --- a/src/services/request_file_transfer.rs +++ b/src/services/request_file_transfer.rs @@ -1,8 +1,6 @@ //! `RequestFileTransfer` (0x38) service implementation -use byteorder::{ReadBytesExt, WriteBytesExt}; -use std::io::Read; -use crate::{DataFormatIdentifier, Error, SingleValueWireFormat, WireFormat}; +use crate::{DataFormatIdentifier, Decode, Encode, Error}; ///////////////////////////////////////// - Request - /////////////////////////////////////////////////// /// Mode of operation for file transfer requests @@ -60,7 +58,7 @@ impl TryFrom for FileOperationMode { } /// Holds the sizes of the file to be transferred (if applicable) -/// Used for both [`RequestFileTransferRequest`] and [`RequestFileTransferResponse`] +/// Used for both [`RequestFileTransferRequestTx`] and [`RequestFileTransferResponseTx`] /// /// | | [AddFile] | [DeleteFile] | [ReplaceFile] | [ReadFile] | [ReadDir] | [ResumeFile] | /// |--------------|-----------|--------------|---------------|------------|-----------|--------------| @@ -73,12 +71,12 @@ impl TryFrom for FileOperationMode { /// [ReadFile]: FileOperationMode::ReadFile /// [ReadDir]: FileOperationMode::ReadDir /// [ResumeFile]: FileOperationMode::ResumeFile -/// [Request]: RequestFileTransferRequest (RequestFileTransferRequest) -/// [Response]: RequestFileTransferResponse (RequestFileTransferResponse) +/// [Request]: RequestFileTransferRequestTx +/// [Response]: RequestFileTransferResponseTx #[allow(clippy::struct_field_names)] #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] #[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))] -#[derive(Clone, Debug, PartialEq)] +#[derive(Clone, Copy, Debug, PartialEq)] pub struct SizePayload { /// Length in bytes for both `file_size_uncompressed` and `file_size_compressed` /// @@ -108,58 +106,9 @@ pub struct SizePayload { pub file_size_compressed: u128, } -impl WireFormat for SizePayload { - fn required_size(&self) -> usize { - 1 + (2 * self.file_size_parameter_length as usize) - } - - fn encode(&self, writer: &mut T) -> Result { - // Always write the file size as 1 byte - writer.write_u8(self.file_size_parameter_length)?; - // write the file size only as many bytes as needed - // Slice off only the number of bytes we need from the end of the file_size bytes - let uncompressed = self.file_size_uncompressed.to_be_bytes(); - let compressed = self.file_size_compressed.to_be_bytes(); - // file_size_uncompressed - let mut bytes: Vec = Vec::new(); - bytes.extend_from_slice(&uncompressed[16 - self.file_size_parameter_length as usize..]); - // file_size_compressed - bytes.extend_from_slice(&compressed[16 - self.file_size_parameter_length as usize..]); - - writer.write_all(&bytes)?; - - Ok(self.required_size()) - } -} - -impl SingleValueWireFormat for SizePayload { - fn decode(reader: &mut T) -> Result { - let file_size_parameter_length = reader.read_u8()?; - let mut file_size_uncompressed = vec![0; file_size_parameter_length as usize]; - let mut file_size_compressed = vec![0; file_size_parameter_length as usize]; - - reader.read_exact(&mut file_size_uncompressed)?; - reader.read_exact(&mut file_size_compressed)?; - - Ok(Self { - file_size_parameter_length, - file_size_uncompressed: u128::from_be_bytes({ - let mut bytes = [0; 16]; - bytes[16 - file_size_parameter_length as usize..] - .copy_from_slice(&file_size_uncompressed); - bytes - }), - file_size_compressed: u128::from_be_bytes({ - let mut bytes = [0; 16]; - bytes[16 - file_size_parameter_length as usize..] - .copy_from_slice(&file_size_compressed); - bytes - }), - }) - } -} - -/// Payload used for all [`RequestFileTransfer` requests][RequestFileTransferRequest] +/// Payload used for all [`RequestFileTransferRequestTx`] requests. +/// +/// Borrows `file_path_and_name` from the caller. /// /// #### ***Request*** Message /// | | [AddFile] | [DeleteFile] | [ReplaceFile] | [ReadFile] | [ReadDir] | [ResumeFile] | @@ -172,57 +121,21 @@ impl SingleValueWireFormat for SizePayload { /// [ReadFile]: FileOperationMode::ReadFile /// [ReadDir]: FileOperationMode::ReadDir /// [ResumeFile]: FileOperationMode::ResumeFile -/// [Request]: RequestFileTransferRequest (RequestFileTransferRequest) +/// [Request]: RequestFileTransferRequestTx #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] #[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))] -#[derive(Clone, Debug, PartialEq)] -pub struct NamePayload { +#[derive(Clone, Copy, Debug, PartialEq)] +pub struct NamePayloadTx<'a> { /// 0x01 - 0x06, the type of operation to be applied to the file or directory specified in `file_path_and_name` - /// - /// Duplicated as we need to read and store it somewhere - mode_of_operation: FileOperationMode, + pub mode_of_operation: FileOperationMode, /// Length in bytes of the `file_path_and_name` field - file_path_and_name_length: u16, + pub file_path_and_name_length: u16, /// The path and name of the file or directory on the server - file_path_and_name: String, + pub file_path_and_name: &'a str, } -impl WireFormat for NamePayload { - fn required_size(&self) -> usize { - 1 + 2 + self.file_path_and_name.len() - } - - fn encode(&self, writer: &mut T) -> Result { - // Write the mode of operation - writer.write_u8((self.mode_of_operation).into())?; - // Write the file path and name length - writer.write_u16::(self.file_path_and_name_length)?; - // Write the file path and name - writer.write_all(self.file_path_and_name.as_bytes())?; - Ok(self.required_size()) - } -} - -impl SingleValueWireFormat for NamePayload { - fn decode(reader: &mut T) -> Result { - let mode_of_operation = FileOperationMode::try_from(reader.read_u8()?)?; - let file_path_and_name_length = reader.read_u16::()?; - - // Read # of bytes specified by `file_path_and_name_length` - let mut file_path_and_name = String::new(); - reader - .take(u64::from(file_path_and_name_length)) - .read_to_string(&mut file_path_and_name)?; - - Ok(Self { - mode_of_operation, - file_path_and_name_length, - file_path_and_name, - }) - } -} /// A request to the server to transfer a file, either upload or download. /// /// Capabilities: @@ -238,107 +151,28 @@ impl SingleValueWireFormat for NamePayload { /// there is no need to use the `TransferData` or [`crate::UdsServiceType::RequestTransferExit`] services. #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] #[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))] -#[derive(Clone, Debug, PartialEq)] +#[derive(Clone, Copy, Debug, PartialEq)] #[non_exhaustive] -pub enum RequestFileTransferRequest { +pub enum RequestFileTransferRequestTx<'a> { /// Add a file to the server - AddFile(NamePayload, DataFormatIdentifier, SizePayload), + AddFile(NamePayloadTx<'a>, DataFormatIdentifier, SizePayload), /// Delete the specified file from the server - DeleteFile(NamePayload), + DeleteFile(NamePayloadTx<'a>), /// Replace the specified file on the server, if it does not exist, add it - ReplaceFile(NamePayload, DataFormatIdentifier, SizePayload), + ReplaceFile(NamePayloadTx<'a>, DataFormatIdentifier, SizePayload), /// Read the specified file from the server (upload) - ReadFile(NamePayload, DataFormatIdentifier), + ReadFile(NamePayloadTx<'a>, DataFormatIdentifier), /// Read the directory from the server /// Implies that the request does not include a `fileName` - ReadDir(NamePayload), + ReadDir(NamePayloadTx<'a>), /// Resume a file transfer at the returned `filePosition` indicator /// The file must already exist in the ECU's filesystem - ResumeFile(NamePayload, DataFormatIdentifier, SizePayload), -} - -impl WireFormat for RequestFileTransferRequest { - fn required_size(&self) -> usize { - match self { - Self::AddFile(name_payload, data_format_identifier, file_size_payload) - | Self::ReplaceFile(name_payload, data_format_identifier, file_size_payload) - | Self::ResumeFile(name_payload, data_format_identifier, file_size_payload) => { - name_payload.required_size() - + data_format_identifier.required_size() - + file_size_payload.required_size() - } - Self::ReadFile(name_payload, data_format_identifier) => { - name_payload.required_size() + data_format_identifier.required_size() - } - Self::DeleteFile(name_payload) | Self::ReadDir(name_payload) => { - name_payload.required_size() - } - } - } - - fn encode(&self, writer: &mut T) -> Result { - let mut len = 0; - Ok(match self { - Self::AddFile(name_payload, data_format_identifier, file_size_payload) - | Self::ReplaceFile(name_payload, data_format_identifier, file_size_payload) - | Self::ResumeFile(name_payload, data_format_identifier, file_size_payload) => { - len += name_payload.encode(writer)?; - len += data_format_identifier.encode(writer)?; - len += file_size_payload.encode(writer)?; - len - } - Self::ReadFile(name_payload, data_format_identifier) => { - len += name_payload.encode(writer)?; - len += data_format_identifier.encode(writer)?; - len - } - Self::DeleteFile(name_payload) | Self::ReadDir(name_payload) => { - len += name_payload.encode(writer)?; - len - } - }) - } -} - -impl SingleValueWireFormat for RequestFileTransferRequest { - fn decode(reader: &mut T) -> Result { - let name_payload = NamePayload::decode(reader)?; - - // read the filename - Ok(match name_payload.mode_of_operation { - // Complicated - FileOperationMode::AddFile => Self::AddFile( - name_payload, - DataFormatIdentifier::decode(reader)?, - SizePayload::decode(reader)?, - ), - FileOperationMode::ReplaceFile => Self::ReplaceFile( - name_payload, - DataFormatIdentifier::decode(reader)?, - SizePayload::decode(reader)?, - ), - FileOperationMode::ResumeFile => Self::ResumeFile( - name_payload, - DataFormatIdentifier::decode(reader)?, - SizePayload::decode(reader)?, - ), - FileOperationMode::ReadFile => { - Self::ReadFile(name_payload, DataFormatIdentifier::decode(reader)?) - } - FileOperationMode::ReadDir => Self::ReadDir(name_payload), - FileOperationMode::DeleteFile => Self::DeleteFile(name_payload), - FileOperationMode::ISOSAEReserved(_) => { - return Err(Error::InvalidFileOperationMode( - name_payload.mode_of_operation.into(), - )); - } - }) - } + ResumeFile(NamePayloadTx<'a>, DataFormatIdentifier, SizePayload), } ///////////////////////////////////////// - Response - /////////////////////////////////////////////////// @@ -355,13 +189,13 @@ impl SingleValueWireFormat for RequestFileTransferRequest { /// [ReadFile]: FileOperationMode::ReadFile /// [ReadDir]: FileOperationMode::ReadDir /// [ResumeFile]: FileOperationMode::ResumeFile -/// [Response]: RequestFileTransferRequest (RequestFileTransferResponse) +/// [Response]: RequestFileTransferResponseTx #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] #[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))] -#[derive(Clone, Debug, PartialEq)] -pub struct SentDataPayload { +#[derive(Clone, Copy, Debug, PartialEq)] +pub struct SentDataPayloadTx<'a> { /// Not related to `RequestDownload` - length_format_identifier: u8, + pub length_format_identifier: u8, /// This parameter is used by the requestFileTransfer positive response message to inform the client how many /// data bytes (maxNumberOfBlockLength) to include in each `TransferData` request message from the client or how /// many data bytes the server will include in a `TransferData` positive response when uploading data. This length @@ -378,32 +212,7 @@ pub struct SentDataPayload { /// affect the memory address of where the subsequent transferData request data would be written. /// If the modeOfOperation parameter equals to 0x02 (`DeleteFile`) this parameter shall be not be included in the /// response message. - pub max_number_of_block_length: Vec, -} - -impl WireFormat for SentDataPayload { - fn required_size(&self) -> usize { - 1 + self.max_number_of_block_length.len() - } - - fn encode(&self, writer: &mut T) -> Result { - writer.write_u8(self.length_format_identifier)?; - writer.write_all(&self.max_number_of_block_length)?; - Ok(self.required_size()) - } -} - -impl SingleValueWireFormat for SentDataPayload { - fn decode(reader: &mut T) -> Result { - let length_format_identifier = reader.read_u8()?; - - let mut max_number_of_block_length: Vec = vec![0; length_format_identifier as usize]; - reader.read_exact(&mut max_number_of_block_length)?; - Ok(Self { - length_format_identifier, - max_number_of_block_length, - }) - } + pub max_number_of_block_length: &'a [u8], } /// Used to inform the client of the size of the file to be transferred @@ -418,11 +227,11 @@ impl SingleValueWireFormat for SentDataPayload { /// [ReadFile]: FileOperationMode::ReadFile /// [ReadDir]: FileOperationMode::ReadDir /// [ResumeFile]: FileOperationMode::ResumeFile -/// [Response]: RequestFileTransferRequest (RequestFileTransferResponse) +/// [Response]: RequestFileTransferResponseTx #[allow(clippy::struct_field_names)] #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] #[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))] -#[derive(Clone, Debug, PartialEq)] +#[derive(Clone, Copy, Debug, PartialEq)] pub struct FileSizePayload { /// Length in bytes of both `file_size_uncompressed` and `file_size_compressed`. pub file_size_parameter_length: u16, @@ -432,58 +241,6 @@ pub struct FileSizePayload { pub file_size_compressed: u128, } -impl WireFormat for FileSizePayload { - fn required_size(&self) -> usize { - 2 + (2 * self.file_size_parameter_length as usize) - } - - fn encode(&self, writer: &mut T) -> Result { - // Always write the file size as 1 byte - - writer.write_u16::(self.file_size_parameter_length)?; - // write the file size only as many bytes as needed - // Slice off only the number of bytes we need from the end of the file_size bytes - let uncompressed = self.file_size_uncompressed.to_be_bytes(); - let compressed = self.file_size_compressed.to_be_bytes(); - // file_size_uncompressed - let mut bytes: Vec = Vec::new(); - bytes.extend_from_slice(&uncompressed[16 - self.file_size_parameter_length as usize..]); - // file_size_compressed - bytes.extend_from_slice(&compressed[16 - self.file_size_parameter_length as usize..]); - - writer.write_all(&bytes)?; - - Ok(self.required_size()) - } -} - -impl SingleValueWireFormat for FileSizePayload { - fn decode(reader: &mut T) -> Result { - let file_size_parameter_length = reader.read_u16::()?; - let mut file_size_uncompressed = vec![0; file_size_parameter_length as usize]; - let mut file_size_compressed = vec![0; file_size_parameter_length as usize]; - - reader.read_exact(&mut file_size_uncompressed)?; - reader.read_exact(&mut file_size_compressed)?; - - Ok(Self { - file_size_parameter_length, - file_size_uncompressed: u128::from_be_bytes({ - let mut bytes = [0; 16]; - bytes[16 - file_size_parameter_length as usize..] - .copy_from_slice(&file_size_uncompressed); - bytes - }), - file_size_compressed: u128::from_be_bytes({ - let mut bytes = [0; 16]; - bytes[16 - file_size_parameter_length as usize..] - .copy_from_slice(&file_size_compressed); - bytes - }), - }) - } -} - /// Used to inform the client of the size of the directory to be transferred /// /// | | [AddFile] | [DeleteFile] | [ReplaceFile] | [ReadFile] | [ReadDir] | [ResumeFile] | @@ -496,10 +253,10 @@ impl SingleValueWireFormat for FileSizePayload { /// [ReadFile]: FileOperationMode::ReadFile /// [ReadDir]: FileOperationMode::ReadDir /// [ResumeFile]: FileOperationMode::ResumeFile -/// [Response]: RequestFileTransferRequest (RequestFileTransferResponse) +/// [Response]: RequestFileTransferResponseTx #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] #[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))] -#[derive(Clone, Debug, PartialEq)] +#[derive(Clone, Copy, Debug, PartialEq)] pub struct DirSizePayload { /// Length in bytes of the `dir_info_length` field. pub dir_info_parameter_length: u16, @@ -507,46 +264,6 @@ pub struct DirSizePayload { pub dir_info_length: u128, } -impl WireFormat for DirSizePayload { - fn required_size(&self) -> usize { - 2 + self.dir_info_parameter_length as usize - } - - fn encode(&self, writer: &mut T) -> Result { - let mut len = 0; - writer.write_u16::(self.dir_info_parameter_length)?; - len += 2; - // write the file size only as many bytes as needed - // Slice off only the number of bytes we need from the end of the file_size bytes - let dir_info_length = self.dir_info_length.to_be_bytes(); - let mut bytes: Vec = Vec::new(); - - bytes.extend_from_slice(&dir_info_length[16 - self.dir_info_parameter_length as usize..]); - writer.write_all(&bytes)?; - - len += bytes.len(); - - Ok(len) - } -} - -impl SingleValueWireFormat for DirSizePayload { - fn decode(reader: &mut T) -> Result { - let dir_info_parameter_length = reader.read_u16::()?; - let mut dir_info_length = vec![0; dir_info_parameter_length as usize]; - reader.read_exact(&mut dir_info_length)?; - - Ok(Self { - dir_info_parameter_length, - dir_info_length: u128::from_be_bytes({ - let mut bytes = [0; 16]; - bytes[16 - dir_info_parameter_length as usize..].copy_from_slice(&dir_info_length); - bytes - }), - }) - } -} - /// Used to inform the client of the byte position within the file at which the Tester will resume downloading after an initial download is suspended /// /// | | [AddFile] | [DeleteFile] | [ReplaceFile] | [ReadFile] | [ReadDir] | [ResumeFile] | @@ -559,13 +276,13 @@ impl SingleValueWireFormat for DirSizePayload { /// [ReadFile]: FileOperationMode::ReadFile /// [ReadDir]: FileOperationMode::ReadDir /// [ResumeFile]: FileOperationMode::ResumeFile -/// [Response]: RequestFileTransferRequest (RequestFileTransferResponse) +/// [Response]: RequestFileTransferResponseTx #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] #[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))] #[derive(Clone, Copy, Debug, PartialEq)] pub struct PositionPayload { /// Specifies the byte position within the file at which the Tester will resume downloading after an initial download is suspended - /// A download is suspended when the ECU stops receiving [`crate::TransferDataRequest`] requests and does not receive the + /// A download is suspended when the ECU stops receiving [`crate::TransferDataRequestTx`] requests and does not receive the /// `RequestTransferExit` request to end the transfer before returning to the default session /// /// Fixed size: 8 bytes @@ -575,386 +292,499 @@ pub struct PositionPayload { pub file_position: u64, } -impl WireFormat for PositionPayload { - // For PositionPayload - fn required_size(&self) -> usize { - 8 - } - - fn encode(&self, writer: &mut T) -> Result { - writer.write_u64::(self.file_position)?; - Ok(8) - } -} - -impl SingleValueWireFormat for PositionPayload { - fn decode(reader: &mut T) -> Result { - Ok(Self { - file_position: reader.read_u64::()?, - }) - } -} - -/// Response to a [`RequestFileTransferRequest`] from the server +/// Response to a [`RequestFileTransferRequestTx`] from the server /// -/// The server will respond with a [`RequestFileTransferResponse`] to indicate the status of the request +/// The server will respond with a [`RequestFileTransferResponseTx`] to indicate the status of the request /// `DataFormatIdentifier` - Echoes the value of the request #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] #[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))] -#[derive(Clone, Debug, PartialEq)] +#[derive(Clone, Copy, Debug, PartialEq)] #[non_exhaustive] -pub enum RequestFileTransferResponse { +pub enum RequestFileTransferResponseTx<'a> { /// Positive response to an [`AddFile`](FileOperationMode::AddFile) request. - AddFile(FileOperationMode, SentDataPayload, DataFormatIdentifier), + AddFile( + FileOperationMode, + SentDataPayloadTx<'a>, + DataFormatIdentifier, + ), /// Positive response to a [`DeleteFile`](FileOperationMode::DeleteFile) request. DeleteFile(FileOperationMode), /// Positive response to a [`ReplaceFile`](FileOperationMode::ReplaceFile) request. - ReplaceFile(FileOperationMode, SentDataPayload, DataFormatIdentifier), + ReplaceFile( + FileOperationMode, + SentDataPayloadTx<'a>, + DataFormatIdentifier, + ), /// Positive response to a [`ReadFile`](FileOperationMode::ReadFile) request, including file size. ReadFile( FileOperationMode, - SentDataPayload, + SentDataPayloadTx<'a>, DataFormatIdentifier, FileSizePayload, ), /// Positive response to a [`ReadDir`](FileOperationMode::ReadDir) request, including directory size. ReadDir( FileOperationMode, - SentDataPayload, + SentDataPayloadTx<'a>, DataFormatIdentifier, DirSizePayload, ), /// Positive response to a [`ResumeFile`](FileOperationMode::ResumeFile) request, including file position. ResumeFile( FileOperationMode, - SentDataPayload, + SentDataPayloadTx<'a>, DataFormatIdentifier, PositionPayload, ), } -impl WireFormat for RequestFileTransferResponse { - // For RequestFileTransferResponse - fn required_size(&self) -> usize { - match self { - Self::AddFile(_, sent_data, data_format) - | Self::ReplaceFile(_, sent_data, data_format) => { - 1 + sent_data.required_size() + data_format.required_size() - } - Self::DeleteFile(_) => 1, - Self::ReadFile(_, sent_data, data_format, file_size) => { - 1 + sent_data.required_size() - + data_format.required_size() - + file_size.required_size() - } - Self::ReadDir(_, sent_data, data_format, dir_size) => { - 1 + sent_data.required_size() - + data_format.required_size() - + dir_size.required_size() - } - Self::ResumeFile(_, sent_data, data_format, position) => { - 1 + sent_data.required_size() - + data_format.required_size() - + position.required_size() - } - } +// --------------------------------------------------------------------------- +// Encode / Decode impls +// --------------------------------------------------------------------------- + +// `file_size_parameter_length` must fit in a u128 (≤ 16 bytes per value). +const U128_MAX_BYTES: usize = 16; + +impl Encode for NamePayloadTx<'_> { + fn encoded_size(&self) -> usize { + 1 + 2 + self.file_path_and_name.len() } - fn encode(&self, writer: &mut T) -> Result { - // Every variant has a mode of operation - let mut len = 1; - match self { - Self::AddFile(mode_of_operation, sent_data_payload, data_format_identifier) - | Self::ReplaceFile(mode_of_operation, sent_data_payload, data_format_identifier) => { - writer.write_u8((*mode_of_operation).into())?; - len += sent_data_payload.encode(writer)?; - len += data_format_identifier.encode(writer)?; - } - Self::DeleteFile(mode_of_operation) => { - writer.write_u8((*mode_of_operation).into())?; - } - Self::ReadFile( - mode_of_operation, - sent_data_payload, - data_format_identifier, - size_payload, - ) => { - writer.write_u8((*mode_of_operation).into())?; - len += sent_data_payload.encode(writer)?; - len += data_format_identifier.encode(writer)?; - len += size_payload.encode(writer)?; - } - Self::ReadDir( - mode_of_operation, - sent_data_payload, - data_format_identifier, - dir_size_payload, - ) => { - writer.write_u8((*mode_of_operation).into())?; - len += sent_data_payload.encode(writer)?; - len += data_format_identifier.encode(writer)?; - len += dir_size_payload.encode(writer)?; - } - Self::ResumeFile( + fn encode(&self, writer: &mut impl embedded_io::Write) -> Result { + writer + .write_all(&[u8::from(self.mode_of_operation)]) + .map_err(Error::io)?; + writer + .write_all(&self.file_path_and_name_length.to_be_bytes()) + .map_err(Error::io)?; + writer + .write_all(self.file_path_and_name.as_bytes()) + .map_err(Error::io)?; + Ok(self.encoded_size()) + } +} + +impl<'a> Decode<'a> for NamePayloadTx<'a> { + fn decode(buf: &'a [u8]) -> Result<(Self, &'a [u8]), Error> { + if buf.len() < 3 { + return Err(Error::InsufficientData(3)); + } + let mode_of_operation = FileOperationMode::try_from(buf[0])?; + let file_path_and_name_length = u16::from_be_bytes([buf[1], buf[2]]); + let name_len = file_path_and_name_length as usize; + let total = 3 + name_len; + if buf.len() < total { + return Err(Error::InsufficientData(total)); + } + let file_path_and_name = core::str::from_utf8(&buf[3..total]) + .map_err(|_| Error::IncorrectMessageLengthOrInvalidFormat)?; + Ok(( + Self { mode_of_operation, - sent_data_payload, - data_format_identifier, - position_payload, - ) => { - writer.write_u8((*mode_of_operation).into())?; - len += sent_data_payload.encode(writer)?; - len += data_format_identifier.encode(writer)?; - len += position_payload.encode(writer)?; - } + file_path_and_name_length, + file_path_and_name, + }, + &buf[total..], + )) + } +} + +impl Encode for SizePayload { + fn encoded_size(&self) -> usize { + 1 + 2 * self.file_size_parameter_length as usize + } + + fn encode(&self, writer: &mut impl embedded_io::Write) -> Result { + let n = self.file_size_parameter_length as usize; + if n > U128_MAX_BYTES { + return Err(Error::IncorrectMessageLengthOrInvalidFormat); } - Ok(len) + writer + .write_all(&[self.file_size_parameter_length]) + .map_err(Error::io)?; + let uncompressed = self.file_size_uncompressed.to_be_bytes(); + let compressed = self.file_size_compressed.to_be_bytes(); + writer + .write_all(&uncompressed[U128_MAX_BYTES - n..]) + .map_err(Error::io)?; + writer + .write_all(&compressed[U128_MAX_BYTES - n..]) + .map_err(Error::io)?; + Ok(self.encoded_size()) } } -impl SingleValueWireFormat for RequestFileTransferResponse { - fn decode(reader: &mut T) -> Result { - // Read the mode of operation - let mode_of_operation = FileOperationMode::try_from(reader.read_u8()?)?; - Ok(match mode_of_operation { - FileOperationMode::AddFile => Self::AddFile( - mode_of_operation, - SentDataPayload::decode(reader)?, - DataFormatIdentifier::decode(reader)?, - ), - FileOperationMode::DeleteFile => Self::DeleteFile(mode_of_operation), - FileOperationMode::ReplaceFile => Self::ReplaceFile( - mode_of_operation, - SentDataPayload::decode(reader)?, - DataFormatIdentifier::decode(reader)?, - ), - FileOperationMode::ReadFile => Self::ReadFile( - mode_of_operation, - SentDataPayload::decode(reader)?, - DataFormatIdentifier::decode(reader)?, - FileSizePayload::decode(reader)?, - ), - FileOperationMode::ReadDir => Self::ReadDir( - mode_of_operation, - SentDataPayload::decode(reader)?, - DataFormatIdentifier::decode(reader)?, - DirSizePayload::decode(reader)?, - ), - FileOperationMode::ResumeFile => Self::ResumeFile( - mode_of_operation, - SentDataPayload::decode(reader)?, - DataFormatIdentifier::decode(reader)?, - PositionPayload::decode(reader)?, - ), - FileOperationMode::ISOSAEReserved(_) => { - return Err(Error::InvalidFileOperationMode(mode_of_operation.into())); - } - }) +impl<'a> Decode<'a> for SizePayload { + fn decode(buf: &'a [u8]) -> Result<(Self, &'a [u8]), Error> { + if buf.is_empty() { + return Err(Error::InsufficientData(1)); + } + let file_size_parameter_length = buf[0]; + let n = file_size_parameter_length as usize; + if n > U128_MAX_BYTES { + return Err(Error::IncorrectMessageLengthOrInvalidFormat); + } + let total = 1 + 2 * n; + if buf.len() < total { + return Err(Error::InsufficientData(total)); + } + let mut u_bytes = [0u8; U128_MAX_BYTES]; + u_bytes[U128_MAX_BYTES - n..].copy_from_slice(&buf[1..=n]); + let mut c_bytes = [0u8; U128_MAX_BYTES]; + c_bytes[U128_MAX_BYTES - n..].copy_from_slice(&buf[1 + n..total]); + Ok(( + Self { + file_size_parameter_length, + file_size_uncompressed: u128::from_be_bytes(u_bytes), + file_size_compressed: u128::from_be_bytes(c_bytes), + }, + &buf[total..], + )) } } -#[cfg(test)] -mod request_tests { - use super::*; - use crate::param_length_u128; - - // helper function to get some bytes to read from - #[allow(clippy::cast_possible_truncation)] - fn get_bytes(mode: FileOperationMode, file_name: &str, file_size: u128) -> Vec { - let mut bytes: Vec = Vec::new(); - bytes.push(mode.into()); // AddFile (u8) - // write file_name len as 2 bytes - bytes - .write_u16::(file_name.len() as u16) - .unwrap(); - bytes.extend_from_slice(file_name.as_bytes()); - - if mode != FileOperationMode::DeleteFile && mode != FileOperationMode::ReadDir { - bytes.push(0x00); // No compression or encryption (u8) +impl Encode for SentDataPayloadTx<'_> { + fn encoded_size(&self) -> usize { + 1 + self.max_number_of_block_length.len() + } + + fn encode(&self, writer: &mut impl embedded_io::Write) -> Result { + writer + .write_all(&[self.length_format_identifier]) + .map_err(Error::io)?; + writer + .write_all(self.max_number_of_block_length) + .map_err(Error::io)?; + Ok(self.encoded_size()) + } +} + +impl<'a> Decode<'a> for SentDataPayloadTx<'a> { + fn decode(buf: &'a [u8]) -> Result<(Self, &'a [u8]), Error> { + if buf.is_empty() { + return Err(Error::InsufficientData(1)); } - // only add file size if not DeleteFile, ReadDir, or ReadFile - if mode != FileOperationMode::DeleteFile - && mode != FileOperationMode::ReadDir - && mode != FileOperationMode::ReadFile - { - // count the number of bytes occupied by the file size - let num = param_length_u128(file_size) as u8; - // use write exact - bytes.write_u8(num).unwrap(); - // write the file size only as many bytes as needed - // Slice off only the number of bytes we need from the end of the file_size bytes - let source = file_size.to_be_bytes(); - // file_size_uncompressed - bytes.extend_from_slice(&source[16 - num as usize..]); - // file_size_compressed - bytes.extend_from_slice(&source[16 - num as usize..]); + let length_format_identifier = buf[0]; + let n = length_format_identifier as usize; + let total = 1 + n; + if buf.len() < total { + return Err(Error::InsufficientData(total)); } - bytes + Ok(( + Self { + length_format_identifier, + max_number_of_block_length: &buf[1..total], + }, + &buf[total..], + )) } +} - #[test] - #[allow(clippy::cast_possible_truncation)] - #[allow(clippy::cast_lossless)] - fn add_file() { - let compare_string = "test.txt"; - let file_size: u128 = (u64::MAX as u128) + 1000u128; - let bytes = get_bytes(FileOperationMode::AddFile, compare_string, file_size); - let req: crate::RequestFileTransferRequest = - RequestFileTransferRequest::decode(&mut bytes.as_slice()).unwrap(); - - let mut written_bytes = Vec::new(); - let written = req.encode(&mut written_bytes).unwrap(); - assert_eq!(written, written_bytes.len()); - assert_eq!(written, req.required_size()); - - match req { - RequestFileTransferRequest::AddFile(pl, data_format_pl, file_size_pl) => { - assert_eq!(pl.mode_of_operation, FileOperationMode::AddFile); - assert_eq!(pl.file_path_and_name_length, compare_string.len() as u16); - assert_eq!(pl.file_path_and_name, compare_string); - assert_eq!(data_format_pl, DataFormatIdentifier::new(0, 0).unwrap()); - assert_eq!(file_size_pl.file_size_parameter_length, 9); - assert_eq!(file_size_pl.file_size_uncompressed, file_size); - assert_eq!(file_size_pl.file_size_compressed, file_size); - } - _ => panic!("Expected AddFile"), +impl Encode for FileSizePayload { + fn encoded_size(&self) -> usize { + 2 + 2 * self.file_size_parameter_length as usize + } + + fn encode(&self, writer: &mut impl embedded_io::Write) -> Result { + let n = self.file_size_parameter_length as usize; + if n > U128_MAX_BYTES { + return Err(Error::IncorrectMessageLengthOrInvalidFormat); } + writer + .write_all(&self.file_size_parameter_length.to_be_bytes()) + .map_err(Error::io)?; + let uncompressed = self.file_size_uncompressed.to_be_bytes(); + let compressed = self.file_size_compressed.to_be_bytes(); + writer + .write_all(&uncompressed[U128_MAX_BYTES - n..]) + .map_err(Error::io)?; + writer + .write_all(&compressed[U128_MAX_BYTES - n..]) + .map_err(Error::io)?; + Ok(self.encoded_size()) } +} - #[test] - #[allow(clippy::cast_possible_truncation)] - fn delete_file() { - let compare_string = "/var/tmp/delete_file.bin"; - let bytes = get_bytes(FileOperationMode::DeleteFile, compare_string, 0); - let req = RequestFileTransferRequest::decode(&mut bytes.as_slice()).unwrap(); - - let mut written_bytes = Vec::new(); - let written = req.encode(&mut written_bytes).unwrap(); - assert_eq!(written, written_bytes.len()); - assert_eq!(written, req.required_size()); - - match req { - RequestFileTransferRequest::DeleteFile(pl) => { - assert_eq!(pl.mode_of_operation, FileOperationMode::DeleteFile); - assert_eq!(pl.file_path_and_name_length, compare_string.len() as u16); - assert_eq!(pl.file_path_and_name, compare_string); - } - _ => panic!("Expected DeleteFile"), +impl<'a> Decode<'a> for FileSizePayload { + fn decode(buf: &'a [u8]) -> Result<(Self, &'a [u8]), Error> { + if buf.len() < 2 { + return Err(Error::InsufficientData(2)); } + let file_size_parameter_length = u16::from_be_bytes([buf[0], buf[1]]); + let n = file_size_parameter_length as usize; + if n > U128_MAX_BYTES { + return Err(Error::IncorrectMessageLengthOrInvalidFormat); + } + let total = 2 + 2 * n; + if buf.len() < total { + return Err(Error::InsufficientData(total)); + } + let mut u_bytes = [0u8; U128_MAX_BYTES]; + u_bytes[U128_MAX_BYTES - n..].copy_from_slice(&buf[2..2 + n]); + let mut c_bytes = [0u8; U128_MAX_BYTES]; + c_bytes[U128_MAX_BYTES - n..].copy_from_slice(&buf[2 + n..total]); + Ok(( + Self { + file_size_parameter_length, + file_size_uncompressed: u128::from_be_bytes(u_bytes), + file_size_compressed: u128::from_be_bytes(c_bytes), + }, + &buf[total..], + )) } +} - #[test] - #[allow(clippy::cast_possible_truncation)] - fn write_add_file() { - let compare_string = "test.txt"; - let file_size: u128 = 0x1234; - let bytes = get_bytes(FileOperationMode::AddFile, compare_string, file_size); - let req = RequestFileTransferRequest::decode(&mut bytes.as_slice()).unwrap(); - - let mut bytes = Vec::new(); - let written = req.encode(&mut bytes).unwrap(); - assert_eq!(written, bytes.len()); - assert_eq!(written, req.required_size()); - - // Should be equivalent to our helper function - let expected_bytes = get_bytes(FileOperationMode::AddFile, compare_string, file_size); - assert_eq!(bytes, expected_bytes); +impl Encode for DirSizePayload { + fn encoded_size(&self) -> usize { + 2 + self.dir_info_parameter_length as usize } - #[test] - #[allow(clippy::cast_possible_truncation)] - fn write_delete_file() { - let compare_string = "/var/tmp/delete_file.bin"; - let req = RequestFileTransferRequest::DeleteFile(NamePayload { - mode_of_operation: FileOperationMode::DeleteFile, - file_path_and_name_length: compare_string.len() as u16, - file_path_and_name: compare_string.to_string(), - }); - let mut bytes = Vec::new(); - let written = req.encode(&mut bytes).unwrap(); - // Should be equivalent to our helper function - let expected_bytes = get_bytes(FileOperationMode::DeleteFile, compare_string, 0); - assert_eq!(bytes, expected_bytes); - assert_eq!(bytes.len(), written); - assert_eq!(req.required_size(), written); + fn encode(&self, writer: &mut impl embedded_io::Write) -> Result { + let n = self.dir_info_parameter_length as usize; + if n > U128_MAX_BYTES { + return Err(Error::IncorrectMessageLengthOrInvalidFormat); + } + writer + .write_all(&self.dir_info_parameter_length.to_be_bytes()) + .map_err(Error::io)?; + let bytes = self.dir_info_length.to_be_bytes(); + writer + .write_all(&bytes[U128_MAX_BYTES - n..]) + .map_err(Error::io)?; + Ok(self.encoded_size()) } +} - #[test] - #[allow(clippy::cast_possible_truncation)] - fn replace_file() { - let compare_string = "/opt/testing/replace_file.bin"; - let file_size: u128 = 0x1234; - let bytes = get_bytes(FileOperationMode::ReplaceFile, compare_string, file_size); - let req = RequestFileTransferRequest::decode(&mut bytes.as_slice()).unwrap(); - - let mut written_bytes = Vec::new(); - let written = req.encode(&mut written_bytes).unwrap(); - assert_eq!(written, written_bytes.len()); - assert_eq!(written, req.required_size()); - - match req { - RequestFileTransferRequest::ReplaceFile(pl, data_format_pl, file_size_pl) => { - assert_eq!(pl.mode_of_operation, FileOperationMode::ReplaceFile); - assert_eq!(pl.file_path_and_name_length, compare_string.len() as u16); - assert_eq!(pl.file_path_and_name, compare_string); - assert_eq!(data_format_pl, DataFormatIdentifier::new(0, 0).unwrap()); - assert_eq!(file_size_pl.file_size_parameter_length, 2); - assert_eq!(file_size_pl.file_size_uncompressed, file_size); - assert_eq!(file_size_pl.file_size_compressed, file_size); +impl<'a> Decode<'a> for DirSizePayload { + fn decode(buf: &'a [u8]) -> Result<(Self, &'a [u8]), Error> { + if buf.len() < 2 { + return Err(Error::InsufficientData(2)); + } + let dir_info_parameter_length = u16::from_be_bytes([buf[0], buf[1]]); + let n = dir_info_parameter_length as usize; + if n > U128_MAX_BYTES { + return Err(Error::IncorrectMessageLengthOrInvalidFormat); + } + let total = 2 + n; + if buf.len() < total { + return Err(Error::InsufficientData(total)); + } + let mut bytes = [0u8; U128_MAX_BYTES]; + bytes[U128_MAX_BYTES - n..].copy_from_slice(&buf[2..total]); + Ok(( + Self { + dir_info_parameter_length, + dir_info_length: u128::from_be_bytes(bytes), + }, + &buf[total..], + )) + } +} + +impl Encode for PositionPayload { + fn encoded_size(&self) -> usize { + 8 + } + + fn encode(&self, writer: &mut impl embedded_io::Write) -> Result { + writer + .write_all(&self.file_position.to_be_bytes()) + .map_err(Error::io)?; + Ok(8) + } +} + +impl<'a> Decode<'a> for PositionPayload { + fn decode(buf: &'a [u8]) -> Result<(Self, &'a [u8]), Error> { + if buf.len() < 8 { + return Err(Error::InsufficientData(8)); + } + let file_position = u64::from_be_bytes([ + buf[0], buf[1], buf[2], buf[3], buf[4], buf[5], buf[6], buf[7], + ]); + Ok((Self { file_position }, &buf[8..])) + } +} + +impl Encode for RequestFileTransferRequestTx<'_> { + fn encoded_size(&self) -> usize { + match self { + Self::AddFile(name, _, size) + | Self::ReplaceFile(name, _, size) + | Self::ResumeFile(name, _, size) => name.encoded_size() + 1 + size.encoded_size(), + Self::ReadFile(name, _) => name.encoded_size() + 1, + Self::DeleteFile(name) | Self::ReadDir(name) => name.encoded_size(), + } + } + + fn encode(&self, writer: &mut impl embedded_io::Write) -> Result { + let mut len; + match self { + Self::AddFile(name, dfi, size) + | Self::ReplaceFile(name, dfi, size) + | Self::ResumeFile(name, dfi, size) => { + len = name.encode(writer)?; + writer.write_all(&[u8::from(*dfi)]).map_err(Error::io)?; + len += 1; + len += size.encode(writer)?; + } + Self::ReadFile(name, dfi) => { + len = name.encode(writer)?; + writer.write_all(&[u8::from(*dfi)]).map_err(Error::io)?; + len += 1; + } + Self::DeleteFile(name) | Self::ReadDir(name) => { + len = name.encode(writer)?; } - _ => panic!("Expected ReplaceFile"), } + Ok(len) } +} - #[test] - #[allow(clippy::cast_possible_truncation)] - fn read_file() { - let compare_string = "/opt/testing/just_reading_stuff.txt"; - let file_size: u128 = 0x0; - let bytes = get_bytes(FileOperationMode::ReadFile, compare_string, file_size); - let req = RequestFileTransferRequest::decode(&mut bytes.as_slice()).unwrap(); - - let mut written_bytes = Vec::new(); - let written = req.encode(&mut written_bytes).unwrap(); - assert_eq!(written, written_bytes.len()); - assert_eq!(written, req.required_size()); - - match req { - RequestFileTransferRequest::ReadFile(pl, data_format_pl) => { - assert_eq!(pl.mode_of_operation, FileOperationMode::ReadFile); - assert_eq!(pl.file_path_and_name_length, compare_string.len() as u16); - assert_eq!(pl.file_path_and_name, compare_string); - assert_eq!(data_format_pl, DataFormatIdentifier::new(0, 0).unwrap()); +impl<'a> Decode<'a> for RequestFileTransferRequestTx<'a> { + fn decode(buf: &'a [u8]) -> Result<(Self, &'a [u8]), Error> { + let (name, rest) = NamePayloadTx::decode(buf)?; + match name.mode_of_operation { + FileOperationMode::DeleteFile => Ok((Self::DeleteFile(name), rest)), + FileOperationMode::ReadDir => Ok((Self::ReadDir(name), rest)), + FileOperationMode::ReadFile => { + if rest.is_empty() { + return Err(Error::InsufficientData(1)); + } + let dfi = DataFormatIdentifier::from(rest[0]); + Ok((Self::ReadFile(name, dfi), &rest[1..])) + } + mode @ (FileOperationMode::AddFile + | FileOperationMode::ReplaceFile + | FileOperationMode::ResumeFile) => { + if rest.is_empty() { + return Err(Error::InsufficientData(1)); + } + let dfi = DataFormatIdentifier::from(rest[0]); + let (size, rest) = SizePayload::decode(&rest[1..])?; + let value = match mode { + FileOperationMode::AddFile => Self::AddFile(name, dfi, size), + FileOperationMode::ReplaceFile => Self::ReplaceFile(name, dfi, size), + FileOperationMode::ResumeFile => Self::ResumeFile(name, dfi, size), + _ => unreachable!(), + }; + Ok((value, rest)) } - _ => panic!("Expected ReadFile"), + FileOperationMode::ISOSAEReserved(b) => Err(Error::InvalidFileOperationMode(b)), } } +} - #[test] - #[allow(clippy::cast_possible_truncation)] - fn resume_file() { - let compare_string = "/var/tmp/resume_file.bin"; - let file_size = 0x1234; - let bytes = get_bytes(FileOperationMode::ResumeFile, compare_string, file_size); - let req = RequestFileTransferRequest::decode(&mut bytes.as_slice()).unwrap(); - let mut written_bytes = Vec::new(); - let written = req.encode(&mut written_bytes).unwrap(); - assert_eq!(written, written_bytes.len()); - assert_eq!(written, req.required_size()); - - match req { - RequestFileTransferRequest::ResumeFile(pl, data_format_pl, file_size_pl) => { - assert_eq!(pl.mode_of_operation, FileOperationMode::ResumeFile); - assert_eq!(pl.file_path_and_name_length, compare_string.len() as u16); - assert_eq!(pl.file_path_and_name, compare_string); - assert_eq!(data_format_pl, DataFormatIdentifier::new(0, 0).unwrap()); - assert_eq!(file_size_pl.file_size_parameter_length, 2); - assert_eq!(file_size_pl.file_size_uncompressed, file_size); - assert_eq!(file_size_pl.file_size_compressed, file_size); +impl Encode for RequestFileTransferResponseTx<'_> { + fn encoded_size(&self) -> usize { + match self { + Self::DeleteFile(_) => 1, + Self::AddFile(_, sent, _) | Self::ReplaceFile(_, sent, _) => { + 1 + sent.encoded_size() + 1 } - _ => panic!("Expected ResumeFile"), + Self::ReadFile(_, sent, _, fs) => 1 + sent.encoded_size() + 1 + fs.encoded_size(), + Self::ReadDir(_, sent, _, ds) => 1 + sent.encoded_size() + 1 + ds.encoded_size(), + Self::ResumeFile(_, sent, _, pos) => 1 + sent.encoded_size() + 1 + pos.encoded_size(), } } + fn encode(&self, writer: &mut impl embedded_io::Write) -> Result { + let mut len = 1; + match self { + Self::DeleteFile(mode) => { + writer.write_all(&[u8::from(*mode)]).map_err(Error::io)?; + } + Self::AddFile(mode, sent, dfi) | Self::ReplaceFile(mode, sent, dfi) => { + writer.write_all(&[u8::from(*mode)]).map_err(Error::io)?; + len += sent.encode(writer)?; + writer.write_all(&[u8::from(*dfi)]).map_err(Error::io)?; + len += 1; + } + Self::ReadFile(mode, sent, dfi, fs) => { + writer.write_all(&[u8::from(*mode)]).map_err(Error::io)?; + len += sent.encode(writer)?; + writer.write_all(&[u8::from(*dfi)]).map_err(Error::io)?; + len += 1; + len += fs.encode(writer)?; + } + Self::ReadDir(mode, sent, dfi, ds) => { + writer.write_all(&[u8::from(*mode)]).map_err(Error::io)?; + len += sent.encode(writer)?; + writer.write_all(&[u8::from(*dfi)]).map_err(Error::io)?; + len += 1; + len += ds.encode(writer)?; + } + Self::ResumeFile(mode, sent, dfi, pos) => { + writer.write_all(&[u8::from(*mode)]).map_err(Error::io)?; + len += sent.encode(writer)?; + writer.write_all(&[u8::from(*dfi)]).map_err(Error::io)?; + len += 1; + len += pos.encode(writer)?; + } + } + Ok(len) + } +} + +impl<'a> Decode<'a> for RequestFileTransferResponseTx<'a> { + fn decode(buf: &'a [u8]) -> Result<(Self, &'a [u8]), Error> { + if buf.is_empty() { + return Err(Error::InsufficientData(1)); + } + let mode = FileOperationMode::try_from(buf[0])?; + let rest = &buf[1..]; + match mode { + FileOperationMode::DeleteFile => Ok((Self::DeleteFile(mode), rest)), + FileOperationMode::AddFile | FileOperationMode::ReplaceFile => { + let (sent, rest) = SentDataPayloadTx::decode(rest)?; + if rest.is_empty() { + return Err(Error::InsufficientData(1)); + } + let dfi = DataFormatIdentifier::from(rest[0]); + let rest = &rest[1..]; + let value = match mode { + FileOperationMode::AddFile => Self::AddFile(mode, sent, dfi), + FileOperationMode::ReplaceFile => Self::ReplaceFile(mode, sent, dfi), + _ => unreachable!(), + }; + Ok((value, rest)) + } + FileOperationMode::ReadFile => { + let (sent, rest) = SentDataPayloadTx::decode(rest)?; + if rest.is_empty() { + return Err(Error::InsufficientData(1)); + } + let dfi = DataFormatIdentifier::from(rest[0]); + let (fs, rest) = FileSizePayload::decode(&rest[1..])?; + Ok((Self::ReadFile(mode, sent, dfi, fs), rest)) + } + FileOperationMode::ReadDir => { + let (sent, rest) = SentDataPayloadTx::decode(rest)?; + if rest.is_empty() { + return Err(Error::InsufficientData(1)); + } + let dfi = DataFormatIdentifier::from(rest[0]); + let (ds, rest) = DirSizePayload::decode(&rest[1..])?; + Ok((Self::ReadDir(mode, sent, dfi, ds), rest)) + } + FileOperationMode::ResumeFile => { + let (sent, rest) = SentDataPayloadTx::decode(rest)?; + if rest.is_empty() { + return Err(Error::InsufficientData(1)); + } + let dfi = DataFormatIdentifier::from(rest[0]); + let (pos, rest) = PositionPayload::decode(&rest[1..])?; + Ok((Self::ResumeFile(mode, sent, dfi, pos), rest)) + } + FileOperationMode::ISOSAEReserved(b) => Err(Error::InvalidFileOperationMode(b)), + } + } +} + +#[cfg(test)] +mod request_tests { + use super::*; + #[test] fn test_file_operation_mode() { use FileOperationMode::*; @@ -969,208 +799,210 @@ mod request_tests { FileOperationMode::try_from(0x07).unwrap() ); } -} -#[cfg(test)] -mod response_tests { + fn name_payload(mode: FileOperationMode, path: &str) -> NamePayloadTx<'_> { + NamePayloadTx { + mode_of_operation: mode, + file_path_and_name_length: path.len() as u16, + file_path_and_name: path, + } + } - use crate::{param_length_u32, param_length_u128}; + #[test] + fn name_payload_roundtrip() { + let path = "/tmp/foo.bin"; + let n = name_payload(FileOperationMode::AddFile, path); + let mut buf = [0u8; 64]; + let written = Encode::encode(&n, &mut buf.as_mut_slice()).unwrap(); + assert_eq!(written, n.encoded_size()); + let (decoded, rest) = NamePayloadTx::decode(&buf[..written]).unwrap(); + assert!(rest.is_empty()); + assert_eq!(decoded, n); + } - use super::*; + #[test] + fn size_payload_roundtrip() { + let s = SizePayload { + file_size_parameter_length: 9, + file_size_uncompressed: (u64::MAX as u128) + 1000, + file_size_compressed: 0x12_3456, + }; + let mut buf = [0u8; 32]; + let written = Encode::encode(&s, &mut buf.as_mut_slice()).unwrap(); + assert_eq!(written, s.encoded_size()); + let (decoded, rest) = SizePayload::decode(&buf[..written]).unwrap(); + assert!(rest.is_empty()); + assert_eq!(decoded, s); + } - fn get_bytes( - mode: FileOperationMode, - max_block_len: u32, - data_format: u8, - file_size: u128, - file_position: u64, - ) -> Vec { - let mut bytes: Vec = Vec::new(); - bytes.push(mode.into()); - - // SentDataPayload - if mode != FileOperationMode::DeleteFile { - let len_max_block_len = param_length_u32(max_block_len); - bytes.write_u8(len_max_block_len).unwrap(); - let source = max_block_len.to_be_bytes(); - bytes.extend_from_slice(&source[4 - len_max_block_len as usize..]); - - let mut data_format = data_format; - if mode == FileOperationMode::ReadDir { - data_format = 0x00; - } - // DataFormatIdentifier - bytes.write_u8(data_format).unwrap(); - } + #[test] + fn add_file_request_roundtrip() { + let path = "test.txt"; + let req = RequestFileTransferRequestTx::AddFile( + name_payload(FileOperationMode::AddFile, path), + DataFormatIdentifier::from(0x00), + SizePayload { + file_size_parameter_length: 2, + file_size_uncompressed: 0x1234, + file_size_compressed: 0x1234, + }, + ); + let mut buf = [0u8; 64]; + let written = Encode::encode(&req, &mut buf.as_mut_slice()).unwrap(); + assert_eq!(written, req.encoded_size()); + let (decoded, rest) = RequestFileTransferRequestTx::decode(&buf[..written]).unwrap(); + assert!(rest.is_empty()); + assert_eq!(decoded, req); + } - // File or dir size - let num = param_length_u128(file_size); - if mode == FileOperationMode::ReadFile { - print!("{mode:?}"); - - bytes.write_u16::(num).unwrap(); - let source = file_size.to_be_bytes(); - // Compressed - bytes.extend_from_slice(&source[16 - num as usize..]); - // Uncompressed - bytes.extend_from_slice(&source[16 - num as usize..]); - } else if mode == FileOperationMode::ReadDir { - bytes.write_u16::(num).unwrap(); - let source = file_size.to_be_bytes(); - // Compressed - bytes.extend_from_slice(&source[16 - num as usize..]); - } + #[test] + fn delete_file_request_roundtrip() { + let path = "/var/tmp/delete_file.bin"; + let req = RequestFileTransferRequestTx::DeleteFile(name_payload( + FileOperationMode::DeleteFile, + path, + )); + let mut buf = [0u8; 64]; + let written = Encode::encode(&req, &mut buf.as_mut_slice()).unwrap(); + assert_eq!(written, req.encoded_size()); + let (decoded, rest) = RequestFileTransferRequestTx::decode(&buf[..written]).unwrap(); + assert!(rest.is_empty()); + assert_eq!(decoded, req); + } - if mode == FileOperationMode::ResumeFile { - bytes.write_u64::(file_position).unwrap(); - } - bytes + #[test] + fn read_file_request_roundtrip() { + let path = "/etc/passwd"; + let req = RequestFileTransferRequestTx::ReadFile( + name_payload(FileOperationMode::ReadFile, path), + DataFormatIdentifier::from(0x11), + ); + let mut buf = [0u8; 64]; + let written = Encode::encode(&req, &mut buf.as_mut_slice()).unwrap(); + assert_eq!(written, req.encoded_size()); + let (decoded, rest) = RequestFileTransferRequestTx::decode(&buf[..written]).unwrap(); + assert!(rest.is_empty()); + assert_eq!(decoded, req); } #[test] - fn response_add() { - let bytes = get_bytes(FileOperationMode::AddFile, 0x1234, 0x00, 0x1234, 0); - let reader = &mut &bytes[..]; - let resp = RequestFileTransferResponse::decode(reader).unwrap(); - assert!(reader.is_empty()); - - let mut written_bytes = Vec::new(); - let written = resp.encode(&mut written_bytes).unwrap(); - assert_eq!(written, bytes.len()); - assert_eq!(resp.required_size(), bytes.len()); - - match resp { - RequestFileTransferResponse::AddFile(mode, sent_data, data_format) => { - assert_eq!(mode, FileOperationMode::AddFile); - assert_eq!(sent_data.length_format_identifier, 2); - assert_eq!(sent_data.max_number_of_block_length, vec![0x12, 0x34]); - assert_eq!(data_format, DataFormatIdentifier::new(0, 0).unwrap()); - } - _ => panic!("Expected AddFile"), - } + fn read_dir_request_roundtrip() { + let path = "/var/log"; + let req = + RequestFileTransferRequestTx::ReadDir(name_payload(FileOperationMode::ReadDir, path)); + let mut buf = [0u8; 64]; + let written = Encode::encode(&req, &mut buf.as_mut_slice()).unwrap(); + let (decoded, _) = RequestFileTransferRequestTx::decode(&buf[..written]).unwrap(); + assert_eq!(decoded, req); } #[test] - fn delete_file() { - let bytes = get_bytes(FileOperationMode::DeleteFile, 0, 0, 0, 0); - let reader = &mut &bytes[..]; - let resp = RequestFileTransferResponse::decode(reader).unwrap(); - assert!(reader.is_empty()); - - let mut written_bytes = Vec::new(); - let written = resp.encode(&mut written_bytes).unwrap(); - assert_eq!(written, bytes.len()); - assert_eq!(resp.required_size(), bytes.len()); - - match resp { - RequestFileTransferResponse::DeleteFile(mode) => { - assert_eq!(mode, FileOperationMode::DeleteFile); - } - _ => panic!("Expected DeleteFile"), + fn resume_file_request_roundtrip() { + let path = "/big/file.bin"; + let req = RequestFileTransferRequestTx::ResumeFile( + name_payload(FileOperationMode::ResumeFile, path), + DataFormatIdentifier::from(0x00), + SizePayload { + file_size_parameter_length: 4, + file_size_uncompressed: 0xDEAD_BEEF, + file_size_compressed: 0xDEAD_BEEF, + }, + ); + let mut buf = [0u8; 64]; + let written = Encode::encode(&req, &mut buf.as_mut_slice()).unwrap(); + let (decoded, _) = RequestFileTransferRequestTx::decode(&buf[..written]).unwrap(); + assert_eq!(decoded, req); + } +} + +#[cfg(test)] +mod response_tests { + use super::*; + + fn sent_data<'a>(block: &'a [u8]) -> SentDataPayloadTx<'a> { + SentDataPayloadTx { + length_format_identifier: block.len() as u8, + max_number_of_block_length: block, } } #[test] - fn replace_file() { - let bytes = get_bytes(FileOperationMode::ReplaceFile, 0x1_1234, 0, 0, 0); - let reader = &mut &bytes[..]; - let resp = RequestFileTransferResponse::decode(reader).unwrap(); - assert!(reader.is_empty()); - - let mut written_bytes = Vec::new(); - let written = resp.encode(&mut written_bytes).unwrap(); - assert_eq!(written, bytes.len()); - assert_eq!(resp.required_size(), bytes.len()); - - match resp { - RequestFileTransferResponse::ReplaceFile(mode, sent_data, data_format) => { - assert_eq!(mode, FileOperationMode::ReplaceFile); - assert_eq!(sent_data.length_format_identifier, 3); - assert_eq!(sent_data.max_number_of_block_length, vec![0x01, 0x12, 0x34]); - assert_eq!(data_format, DataFormatIdentifier::new(0, 0).unwrap()); - } - _ => panic!("Expected ReplaceFile"), - } + fn add_file_response_roundtrip() { + let block = [0x10u8, 0x00]; + let resp = RequestFileTransferResponseTx::AddFile( + FileOperationMode::AddFile, + sent_data(&block), + DataFormatIdentifier::from(0x00), + ); + let mut buf = [0u8; 32]; + let written = Encode::encode(&resp, &mut buf.as_mut_slice()).unwrap(); + assert_eq!(written, resp.encoded_size()); + let (decoded, rest) = RequestFileTransferResponseTx::decode(&buf[..written]).unwrap(); + assert!(rest.is_empty()); + assert_eq!(decoded, resp); } #[test] - fn read_file() { - let bytes = get_bytes(FileOperationMode::ReadFile, 0x1, 0x11, 0x11_1111_1111, 0); - let reader = &mut &bytes[..]; - let resp = RequestFileTransferResponse::decode(reader).unwrap(); - assert!(reader.is_empty()); - - let mut written_bytes = Vec::new(); - let written = resp.encode(&mut written_bytes).unwrap(); - assert_eq!(written, bytes.len()); - assert_eq!(resp.required_size(), bytes.len()); - - match resp { - RequestFileTransferResponse::ReadFile(mode, sent_data, df, size) => { - assert_eq!(mode, FileOperationMode::ReadFile); - assert_eq!(sent_data.length_format_identifier, 1); - assert_eq!(sent_data.max_number_of_block_length, vec![0x01]); - assert_eq!(df, DataFormatIdentifier::new(0x01, 0x01).unwrap()); - assert_eq!(size.file_size_parameter_length, 5); - assert_eq!(size.file_size_uncompressed, 0x11_1111_1111); - assert_eq!(size.file_size_compressed, 0x11_1111_1111); - } - _ => panic!("Expected ReadFile"), - } + fn delete_file_response_roundtrip() { + let resp = RequestFileTransferResponseTx::DeleteFile(FileOperationMode::DeleteFile); + let mut buf = [0u8; 8]; + let written = Encode::encode(&resp, &mut buf.as_mut_slice()).unwrap(); + assert_eq!(written, 1); + let (decoded, _) = RequestFileTransferResponseTx::decode(&buf[..written]).unwrap(); + assert_eq!(decoded, resp); } #[test] - fn read_dir() { - let bytes = get_bytes(FileOperationMode::ReadDir, 0x1_1234, 0, 0x11_1111_1111, 0); - let reader = &mut &bytes[..]; - let resp = RequestFileTransferResponse::decode(reader).unwrap(); - assert!(reader.is_empty()); - - let mut written_bytes = Vec::new(); - let written = resp.encode(&mut written_bytes).unwrap(); - assert_eq!(written, bytes.len()); - assert_eq!(resp.required_size(), bytes.len()); - - match resp { - RequestFileTransferResponse::ReadDir(mode, sent_data, df, size) => { - assert_eq!(mode, FileOperationMode::ReadDir); - assert_eq!(sent_data.length_format_identifier, 3); - assert_eq!(sent_data.max_number_of_block_length, vec![0x01, 0x12, 0x34]); - assert_eq!(df, DataFormatIdentifier::new(0, 0).unwrap()); - assert_eq!(size.dir_info_parameter_length, 5); - assert_eq!(size.dir_info_length, 0x11_1111_1111); - } - _ => panic!("Expected ReadDir"), - } + fn read_file_response_roundtrip() { + let block = [0x04u8, 0x00]; + let resp = RequestFileTransferResponseTx::ReadFile( + FileOperationMode::ReadFile, + sent_data(&block), + DataFormatIdentifier::from(0x00), + FileSizePayload { + file_size_parameter_length: 4, + file_size_uncompressed: 0xAABB_CCDD, + file_size_compressed: 0x1122_3344, + }, + ); + let mut buf = [0u8; 64]; + let written = Encode::encode(&resp, &mut buf.as_mut_slice()).unwrap(); + let (decoded, _) = RequestFileTransferResponseTx::decode(&buf[..written]).unwrap(); + assert_eq!(decoded, resp); + } + + #[test] + fn read_dir_response_roundtrip() { + let block = [0x04u8, 0x00]; + let resp = RequestFileTransferResponseTx::ReadDir( + FileOperationMode::ReadDir, + sent_data(&block), + DataFormatIdentifier::from(0x00), + DirSizePayload { + dir_info_parameter_length: 4, + dir_info_length: 0x1234_5678, + }, + ); + let mut buf = [0u8; 64]; + let written = Encode::encode(&resp, &mut buf.as_mut_slice()).unwrap(); + let (decoded, _) = RequestFileTransferResponseTx::decode(&buf[..written]).unwrap(); + assert_eq!(decoded, resp); } #[test] - fn resume_file() { - let bytes = get_bytes( + fn resume_file_response_roundtrip() { + let block = [0x04u8, 0x00]; + let resp = RequestFileTransferResponseTx::ResumeFile( FileOperationMode::ResumeFile, - 0x1_1234, - 0x11, - 0x11_1111_1111, - 0x1234_5678_9ABC_DEF0, + sent_data(&block), + DataFormatIdentifier::from(0x00), + PositionPayload { + file_position: 0xDEAD_BEEF_CAFE_BABE, + }, ); - let reader = &mut &bytes[..]; - let resp = RequestFileTransferResponse::decode(reader).unwrap(); - assert!(reader.is_empty()); - - let mut written_bytes = Vec::new(); - let written = resp.encode(&mut written_bytes).unwrap(); - assert_eq!(written, bytes.len()); - assert_eq!(resp.required_size(), bytes.len()); - - match resp { - RequestFileTransferResponse::ResumeFile(mode, sent_data, df, pos) => { - assert_eq!(mode, FileOperationMode::ResumeFile); - assert_eq!(sent_data.length_format_identifier, 3); - assert_eq!(sent_data.max_number_of_block_length, vec![0x01, 0x12, 0x34]); - assert_eq!(df, DataFormatIdentifier::new(1, 1).unwrap()); - assert_eq!(pos.file_position, 0x1234_5678_9ABC_DEF0); - } - _ => panic!("Expected ResumeFile"), - } + let mut buf = [0u8; 64]; + let written = Encode::encode(&resp, &mut buf.as_mut_slice()).unwrap(); + let (decoded, _) = RequestFileTransferResponseTx::decode(&buf[..written]).unwrap(); + assert_eq!(decoded, resp); } } diff --git a/src/services/routine_control.rs b/src/services/routine_control.rs index 87004d6..57f9b91 100644 --- a/src/services/routine_control.rs +++ b/src/services/routine_control.rs @@ -1,13 +1,8 @@ //! Routine Control (0x31) Service is used to perform functions on the ECU that are may not be covered by other services. //! -//! It can also be used to check the ECU’s health, erase memory, or other custom manufacturer/supplier routines. +//! It can also be used to check the ECU's health, erase memory, or other custom manufacturer/supplier routines. //! However, some routines may have side effects or require certain preconditions to be met. -use crate::{ - Error, Identifier, IterableWireFormat, RoutineControlSubFunction, SingleValueWireFormat, - WireFormat, -}; -use byteorder::{ReadBytesExt, WriteBytesExt}; -use std::io::{Read, Write}; +use crate::{Encode, Error, Identifier, RoutineControlSubFunction}; /// Used by a client to execute a defined sequence of events and obtain any relevant results #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] @@ -23,14 +18,9 @@ pub struct RoutineControlRequest { pub data: Option, } -impl - RoutineControlRequest -{ - pub(crate) fn new( - sub_function: RoutineControlSubFunction, - routine_id: RoutineIdentifier, - data: Option, - ) -> Self { +impl RoutineControlRequest { + /// Create a new `RoutineControlRequest`. + pub fn new(sub_function: RoutineControlSubFunction, routine_id: RI, data: Option) -> Self { Self { sub_function, routine_id, @@ -39,38 +29,20 @@ impl } } -impl WireFormat - for RoutineControlRequest -{ - fn required_size(&self) -> usize { - 3 + match &self.data { - Some(record) => record.required_size(), - None => 0, - } +impl Encode for RoutineControlRequest { + fn encoded_size(&self) -> usize { + 1 + 2 + self.data.as_ref().map_or(0, Encode::encoded_size) } - fn encode(&self, writer: &mut T) -> Result { - writer.write_u8(u8::from(self.sub_function))?; - self.routine_id.encode(writer)?; - if let Some(record) = &self.data { - record.encode(writer)?; + fn encode(&self, writer: &mut impl embedded_io::Write) -> Result { + writer + .write_all(&[u8::from(self.sub_function)]) + .map_err(Error::io)?; + Encode::encode(&self.routine_id, writer)?; + if let Some(payload) = &self.data { + Encode::encode(payload, writer)?; } - Ok(self.required_size()) - } -} - -impl SingleValueWireFormat - for RoutineControlRequest -{ - fn decode(reader: &mut T) -> Result { - let sub_function = RoutineControlSubFunction::from(reader.read_u8()?); - let routine_id = RoutineIdentifier::decode(reader)?; - let data = RoutinePayload::decode_next(reader)?; - Ok(Self { - sub_function, - routine_id, - data, - }) + Ok(self.encoded_size()) } } @@ -84,143 +56,32 @@ pub struct RoutineControlResponse { pub routine_control_type: RoutineControlSubFunction, /// Should contain the `routine_info` (u8) and the `routine_status_record` (u8 * n) information. n can be 0 - /// - /// `routine_info`: The routine information that the response is for (vehicle manufacturer specific) - /// `routine_status_record`: The status of the routine (optional) - /// - /// Mandatory for any routine where the `routine_status_record` is defined by ISO/SAE specs, even if it is 0 bytes. - /// Optional if the routine is defined by a manufacturer. pub routine_status_record: RoutineInfoStatusRecord, } -impl RoutineControlResponse { - pub(crate) fn new( +impl RoutineControlResponse { + /// Create a new `RoutineControlResponse`. + pub fn new( routine_control_type: RoutineControlSubFunction, - data: RoutineStatusRecord, + routine_status_record: RSR, ) -> Self { Self { - routine_control_type, - routine_status_record: data, - } - } - - /// Get the raw data of the status record - /// # Errors - /// - if the stream is not in the expected format - /// - if the stream contains partial data - pub fn status_record_data(&self) -> Result, Error> { - let mut writer: Vec = Vec::new(); - self.routine_status_record.encode(&mut writer)?; - - Ok(writer) - } -} - -impl WireFormat for RoutineControlResponse { - fn required_size(&self) -> usize { - // control type + (routine identifier + routine info + status record) - 1 + self.routine_status_record.required_size() - } - - fn encode(&self, writer: &mut T) -> Result { - writer.write_u8(self.routine_control_type.into())?; - self.routine_status_record.encode(writer)?; - Ok(self.required_size()) - } -} - -impl SingleValueWireFormat - for RoutineControlResponse -{ - fn decode(reader: &mut T) -> Result { - let routine_control_type = RoutineControlSubFunction::from(reader.read_u8()?); - // Reads the identifier, then can read 0 bytes, 1 byte, or more - let routine_status_record = RoutineStatusRecord::decode(reader)?; - Ok(Self { routine_control_type, routine_status_record, - }) - } -} - -#[cfg(test)] -mod request { - use super::*; - use crate::impl_identifier; - - #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] - #[derive(Clone, Copy, Debug, Eq, PartialEq)] - struct TestIdentifier(pub u16); - impl_identifier!(TestIdentifier); - - impl From for TestIdentifier { - fn from(value: u16) -> Self { - TestIdentifier(value) } } +} - impl From for u16 { - fn from(val: TestIdentifier) -> Self { - val.0 - } - } - - type RoutineControlRequestType = RoutineControlRequest>; - - #[test] - fn simple_request() { - // Fake data: StartRoutine, RoutineID of 0x8606 for "Start O2 Sensor Heater Test" or something - let bytes: [u8; 6] = [0x01, 0x00, 0x01, 0x02, 0x03, 0x04]; - let req: RoutineControlRequestType = - RoutineControlRequest::decode(&mut bytes.as_slice()).unwrap(); - - assert_eq!(u8::from(req.sub_function), 0x01); - assert_eq!(req.routine_id, TestIdentifier::from(0x0001)); - let data = req.data.clone().unwrap(); - assert_eq!(data, vec![0x02, 0x03, 0x04]); - - let mut buf = Vec::new(); - let written = req.encode(&mut buf).unwrap(); - assert_eq!(written, bytes.len()); - assert_eq!(written, req.required_size()); - - let new_req: RoutineControlRequestType = RoutineControlRequest::new( - RoutineControlSubFunction::StopRoutine, - TestIdentifier::from(0x0002), - Some(vec![]), - ); - - assert_eq!(new_req.sub_function, RoutineControlSubFunction::StopRoutine); - assert_eq!(new_req.routine_id, TestIdentifier::from(0x0002)); +impl Encode for RoutineControlResponse { + fn encoded_size(&self) -> usize { + 1 + self.routine_status_record.encoded_size() } - #[test] - fn simple_response() { - let bytes: [u8; 6] = [0x01, 0x00, 0x01, 0x02, 0x03, 0x04]; - let resp: RoutineControlResponse> = - RoutineControlResponse::decode(&mut bytes.as_slice()).unwrap(); - - assert_eq!( - resp.routine_control_type, - RoutineControlSubFunction::StartRoutine - ); - // Vec as payload just reads until the end, including the identifier - assert_eq!( - resp.routine_status_record, - vec![0x00, 0x01, 0x02, 0x03, 0x04] - ); - - let mut buf = Vec::new(); - let written = resp.encode(&mut buf).unwrap(); - assert_eq!(written, bytes.len()); - assert_eq!(written, resp.required_size()); - - let new_resp: RoutineControlResponse> = - RoutineControlResponse::new(RoutineControlSubFunction::StopRoutine, buf); - - assert_eq!( - new_resp.routine_control_type, - RoutineControlSubFunction::StopRoutine - ); + fn encode(&self, writer: &mut impl embedded_io::Write) -> Result { + writer + .write_all(&[u8::from(self.routine_control_type)]) + .map_err(Error::io)?; + Encode::encode(&self.routine_status_record, writer)?; + Ok(self.encoded_size()) } } diff --git a/src/services/security_access.rs b/src/services/security_access.rs index bae59ee..39e4e02 100644 --- a/src/services/security_access.rs +++ b/src/services/security_access.rs @@ -1,10 +1,7 @@ //! `SecurityAccess` (0x27) service implementation use crate::{ - Error, NegativeResponseCode, SecurityAccessType, SingleValueWireFormat, - SuppressablePositiveResponse, WireFormat, + Decode, Encode, Error, NegativeResponseCode, SecurityAccessType, SuppressablePositiveResponse, }; -use byteorder::{ReadBytesExt, WriteBytesExt}; -use std::io::{Read, Write}; /// List of allowed [`NegativeResponseCode`] variants for the `SecurityAccess` service const SECURITY_ACCESS_NEGATIVE_RESPONSE_CODES: [NegativeResponseCode; 8] = [ @@ -37,21 +34,21 @@ const SECURITY_ACCESS_NEGATIVE_RESPONSE_CODES: [NegativeResponseCode; 8] = [ /// The server will then validate the key and respond with a positive or negative response. /// Successful verification of the key will result in the server unlocking the requested security level. /// Suppressing a positive response to this request is allowed. -#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] -#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))] -#[derive(Clone, Debug, Eq, PartialEq)] -#[non_exhaustive] -pub struct SecurityAccessRequest { +/// +/// Zero-alloc TX request for security access. Borrows from the caller. +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub struct SecurityAccessRequestTx<'d> { access_type: SuppressablePositiveResponse, - request_data: Vec, + request_data: &'d [u8], } -impl SecurityAccessRequest { - /// Create a new '`SecurityAccessRequest`' - pub(crate) fn new( +impl<'d> SecurityAccessRequestTx<'d> { + /// Create a new security access request. + #[must_use] + pub const fn new( suppress_positive_response: bool, access_type: SecurityAccessType, - request_data: Vec, + request_data: &'d [u8], ) -> Self { Self { access_type: SuppressablePositiveResponse::new(suppress_positive_response, access_type), @@ -73,8 +70,8 @@ impl SecurityAccessRequest { /// Getter for the request data #[must_use] - pub fn request_data(&self) -> &[u8] { - &self.request_data + pub const fn request_data(&self) -> &[u8] { + self.request_data } /// Get the allowed [`NegativeResponseCode`] variants for this request @@ -84,15 +81,17 @@ impl SecurityAccessRequest { } } -impl WireFormat for SecurityAccessRequest { - fn required_size(&self) -> usize { - 1 + self.request_data().len() +impl Encode for SecurityAccessRequestTx<'_> { + fn encoded_size(&self) -> usize { + 1 + self.request_data.len() } - fn encode(&self, writer: &mut T) -> Result { - writer.write_u8(u8::from(self.access_type))?; - writer.write_all(&self.request_data)?; - Ok(self.required_size()) + fn encode(&self, writer: &mut impl embedded_io::Write) -> Result { + writer + .write_all(&[u8::from(self.access_type)]) + .map_err(Error::io)?; + writer.write_all(self.request_data).map_err(Error::io)?; + Ok(self.encoded_size()) } fn is_positive_response_suppressed(&self) -> bool { @@ -100,41 +99,35 @@ impl WireFormat for SecurityAccessRequest { } } -impl SingleValueWireFormat for SecurityAccessRequest { - fn decode(reader: &mut T) -> Result { - let access_type = SuppressablePositiveResponse::try_from(reader.read_u8()?)?; - let mut request_data: Vec = Vec::new(); - _ = reader.read_to_end(&mut request_data)?; - Ok(Self { - access_type, - request_data, - }) +impl<'a> Decode<'a> for SecurityAccessRequestTx<'a> { + fn decode(buf: &'a [u8]) -> Result<(Self, &'a [u8]), Error> { + if buf.is_empty() { + return Err(Error::InsufficientData(1)); + } + let access_type = SuppressablePositiveResponse::try_from(buf[0])?; + Ok(( + Self { + access_type, + request_data: &buf[1..], + }, + &[], + )) } } -/// Response to `SecurityAccessRequest` -/// -/// ## Request Seed -/// -/// When responding to a seed request, the `security_seed` field shall contain the seed value. -/// -/// ## Send Key -/// -/// The positive response to a `SendKey` request shall not have any data in the security seed field. -#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] -#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))] -#[derive(Clone, Debug, Eq, PartialEq)] -#[non_exhaustive] -pub struct SecurityAccessResponse { +/// Zero-alloc TX response for security access. Borrows from the caller. +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub struct SecurityAccessResponseTx<'d> { /// The security access type echoed from the request. pub access_type: SecurityAccessType, /// The security seed bytes (empty for a `SendKey` positive response). - pub security_seed: Vec, + pub security_seed: &'d [u8], } -impl SecurityAccessResponse { - /// Create a new '`SecurityAccessResponse`' - pub(crate) fn new(access_type: SecurityAccessType, security_seed: Vec) -> Self { +impl<'d> SecurityAccessResponseTx<'d> { + /// Create a new security access response. + #[must_use] + pub const fn new(access_type: SecurityAccessType, security_seed: &'d [u8]) -> Self { Self { access_type, security_seed, @@ -142,33 +135,40 @@ impl SecurityAccessResponse { } } -impl WireFormat for SecurityAccessResponse { - fn required_size(&self) -> usize { +impl Encode for SecurityAccessResponseTx<'_> { + fn encoded_size(&self) -> usize { 1 + self.security_seed.len() } - fn encode(&self, writer: &mut T) -> Result { - writer.write_u8(u8::from(self.access_type))?; - writer.write_all(&self.security_seed)?; - Ok(self.required_size()) + fn encode(&self, writer: &mut impl embedded_io::Write) -> Result { + writer + .write_all(&[u8::from(self.access_type)]) + .map_err(Error::io)?; + writer.write_all(self.security_seed).map_err(Error::io)?; + Ok(self.encoded_size()) } } -impl SingleValueWireFormat for SecurityAccessResponse { - fn decode(reader: &mut T) -> Result { - let access_type = SecurityAccessType::try_from(reader.read_u8()?)?; - let mut security_seed = Vec::new(); - let _ = reader.read_to_end(&mut security_seed)?; - Ok(Self { - access_type, - security_seed, - }) +impl<'a> Decode<'a> for SecurityAccessResponseTx<'a> { + fn decode(buf: &'a [u8]) -> Result<(Self, &'a [u8]), Error> { + if buf.is_empty() { + return Err(Error::InsufficientData(1)); + } + let access_type = SecurityAccessType::try_from(buf[0])?; + Ok(( + Self { + access_type, + security_seed: &buf[1..], + }, + &[], + )) } } #[cfg(test)] mod request { use super::*; + use crate::{Decode, Encode}; #[test] fn request_seed() { @@ -176,23 +176,22 @@ mod request { 0x01, // aka SecurityAccessType::RequestSeed(0x01) 0x00, 0x01, 0x02, 0x03, 0x04, // fake data ]; - let req = SecurityAccessRequest::decode(&mut bytes.as_slice()).unwrap(); + let (req, _) = ::decode(&bytes).unwrap(); - assert_eq!( - req.access_type, - SuppressablePositiveResponse::new(false, SecurityAccessType::RequestSeed(0x01)) - ); + assert_eq!(req.access_type(), SecurityAccessType::RequestSeed(0x01)); + assert_eq!(req.request_data(), &[0x00, 0x01, 0x02, 0x03, 0x04]); let mut buf = Vec::new(); - let written = req.encode(&mut buf).unwrap(); + let written = Encode::encode(&req, &mut buf).unwrap(); assert_eq!(written, bytes.len()); - assert_eq!(written, req.required_size()); + assert_eq!(written, req.encoded_size()); } } #[cfg(test)] mod response { use super::*; + use crate::{Decode, Encode}; #[test] fn response_send() { @@ -200,14 +199,14 @@ mod response { 0x02, // aka SecurityAccessType::SendKey(0x02) 0x00, 0x01, 0x02, 0x03, 0x04, // fake data ]; - let resp = SecurityAccessResponse::decode(&mut bytes.as_slice()).unwrap(); + let (resp, _) = ::decode(&bytes).unwrap(); assert_eq!(resp.access_type, SecurityAccessType::SendKey(0x02)); - assert_eq!(resp.security_seed, vec![0x00, 0x01, 0x02, 0x03, 0x04]); + assert_eq!(resp.security_seed, &[0x00, 0x01, 0x02, 0x03, 0x04]); let mut buf = Vec::new(); - let written = resp.encode(&mut buf).unwrap(); + let written = Encode::encode(&resp, &mut buf).unwrap(); assert_eq!(written, bytes.len()); - assert_eq!(written, resp.required_size()); + assert_eq!(written, resp.encoded_size()); } } diff --git a/src/services/tester_present.rs b/src/services/tester_present.rs index eaeccce..8c15db6 100644 --- a/src/services/tester_present.rs +++ b/src/services/tester_present.rs @@ -1,9 +1,5 @@ //! `TesterPresent` (0x3E) service implementation -use crate::{ - Error, NegativeResponseCode, SingleValueWireFormat, SuppressablePositiveResponse, WireFormat, -}; - -use byteorder::{ReadBytesExt, WriteBytesExt}; +use crate::{Decode, Encode, Error, NegativeResponseCode, SuppressablePositiveResponse}; const TESTER_PRESENT_NEGATIVE_RESPONSE_CODES: [NegativeResponseCode; 2] = [ NegativeResponseCode::SubFunctionNotSupported, @@ -26,13 +22,6 @@ enum ZeroSubFunction { ISOSAEReserved(u8), } -impl ZeroSubFunction { - #[inline] - fn new() -> Self { - Self::default() - } -} - impl Default for ZeroSubFunction { #[inline] fn default() -> Self { @@ -71,8 +60,9 @@ pub struct TesterPresentRequest { impl TesterPresentRequest { /// Create a new `TesterPresentRequest` - pub(crate) fn new(suppress_positive_response: bool) -> Self { - Self::with_subfunction(suppress_positive_response, ZeroSubFunction::new()) + #[must_use] + pub fn new(suppress_positive_response: bool) -> Self { + Self::with_subfunction(suppress_positive_response, ZeroSubFunction::default()) } fn with_subfunction( @@ -100,13 +90,15 @@ impl TesterPresentRequest { } } -impl WireFormat for TesterPresentRequest { - fn required_size(&self) -> usize { +impl Encode for TesterPresentRequest { + fn encoded_size(&self) -> usize { 1 } - fn encode(&self, writer: &mut T) -> Result { - writer.write_u8(u8::from(self.zero_sub_function))?; + fn encode(&self, writer: &mut impl embedded_io::Write) -> Result { + writer + .write_all(&[u8::from(self.zero_sub_function)]) + .map_err(Error::io)?; Ok(1) } @@ -115,10 +107,13 @@ impl WireFormat for TesterPresentRequest { } } -impl SingleValueWireFormat for TesterPresentRequest { - fn decode(reader: &mut T) -> Result { - let zero_sub_function = SuppressablePositiveResponse::try_from(reader.read_u8()?)?; - Ok(Self { zero_sub_function }) +impl<'a> Decode<'a> for TesterPresentRequest { + fn decode(buf: &'a [u8]) -> Result<(Self, &'a [u8]), Error> { + if buf.is_empty() { + return Err(Error::InsufficientData(1)); + } + let zero_sub_function = SuppressablePositiveResponse::try_from(buf[0])?; + Ok((Self { zero_sub_function }, &buf[1..])) } } @@ -133,34 +128,47 @@ pub struct TesterPresentResponse { impl TesterPresentResponse { /// Create a new `TesterPresentResponse` - pub(crate) fn new() -> Self { + #[must_use] + pub fn new() -> Self { Self { - zero_sub_function: ZeroSubFunction::new(), + zero_sub_function: ZeroSubFunction::default(), } } } -impl WireFormat for TesterPresentResponse { - fn required_size(&self) -> usize { +impl Default for TesterPresentResponse { + fn default() -> Self { + Self::new() + } +} + +impl Encode for TesterPresentResponse { + fn encoded_size(&self) -> usize { 1 } - fn encode(&self, writer: &mut T) -> Result { - writer.write_u8(u8::from(self.zero_sub_function))?; + fn encode(&self, writer: &mut impl embedded_io::Write) -> Result { + writer + .write_all(&[u8::from(self.zero_sub_function)]) + .map_err(Error::io)?; Ok(1) } } -impl SingleValueWireFormat for TesterPresentResponse { - fn decode(reader: &mut T) -> Result { - let zero_sub_function = ZeroSubFunction::try_from(reader.read_u8()?)?; - Ok(Self { zero_sub_function }) +impl<'a> Decode<'a> for TesterPresentResponse { + fn decode(buf: &'a [u8]) -> Result<(Self, &'a [u8]), Error> { + if buf.is_empty() { + return Err(Error::InsufficientData(1)); + } + let zero_sub_function = ZeroSubFunction::try_from(buf[0])?; + Ok((Self { zero_sub_function }, &buf[1..])) } } #[cfg(test)] mod test { use super::*; + use crate::{Decode, Encode}; #[test] fn try_from_all_zero_subfunction() { @@ -195,7 +203,8 @@ mod test { fn make_request(byte: u8) -> Result { let bytes = vec![byte]; - TesterPresentRequest::decode(&mut bytes.as_slice()) + let (val, _) = ::decode(&bytes)?; + Ok(val) } #[test] @@ -235,7 +244,7 @@ mod test { fn write_request_type() { let test_type = TesterPresentRequest::new(false); let mut buffer = Vec::new(); - test_type.encode(&mut buffer).unwrap(); + Encode::encode(&test_type, &mut buffer).unwrap(); let expected_bytes = vec![0]; assert_eq!(buffer, expected_bytes); @@ -244,7 +253,7 @@ mod test { #[test] fn read_response_type() { let bytes = vec![0u8]; - let test_type = TesterPresentResponse::decode(&mut bytes.as_slice()).unwrap(); + let (test_type, _) = ::decode(&bytes).unwrap(); assert_eq!(test_type, TesterPresentResponse::new()); } @@ -252,7 +261,7 @@ mod test { fn write_response_type() { let test_type = TesterPresentResponse::new(); let mut buffer = Vec::new(); - test_type.encode(&mut buffer).unwrap(); + Encode::encode(&test_type, &mut buffer).unwrap(); let expected_bytes = vec![0]; assert_eq!(buffer, expected_bytes); diff --git a/src/services/transfer_data.rs b/src/services/transfer_data.rs index c24234e..046d67b 100644 --- a/src/services/transfer_data.rs +++ b/src/services/transfer_data.rs @@ -1,7 +1,6 @@ //! `TransferData` (0x36) service implementation -use byteorder::{ReadBytesExt, WriteBytesExt}; -use crate::{Error, SingleValueWireFormat, WireFormat}; +use crate::{Decode, Encode, Error}; /// A request to the server to transfer data (either upload or download) /// @@ -9,34 +8,32 @@ use crate::{Error, SingleValueWireFormat, WireFormat}; /// 34 .. 11 .. 33 .. 60 20 00 .. 00 FF FF << -- Bytes sent by the client /// RID .. DFI .. ALFID .. `MA_B`# .. `UCMS_B`# /// -/// Step 1 Response: The server sends a [`RequestDownloadResponse`](crate::RequestDownloadResponse) or `RequestUploadResponse` message to the client +/// Step 1 Response: The server sends a [`RequestDownloadResponseTx`](crate::RequestDownloadResponseTx) or `RequestUploadResponse` message to the client /// -/// Step 2: The client shall send many `TransferDataRequest` messages written in blocks +/// Step 2: The client shall send many [`TransferDataRequestTx`] messages written in blocks /// to the server with a max number of bytes equal to `MNROB_B`# from the `RequestDownloadResponse` message /// 74 .. 20 .. 00 81 /// RSID .. LFID .. `MNROB_B`# /// -/// Step 2 Response: The server sends a [`crate::TransferDataResponse`] message confirming the block sequence +/// Step 2 Response: The server sends a [`TransferDataResponseTx`] message confirming the block sequence /// /// Step 3: The client sends a [`crate::UdsServiceType::RequestTransferExit`] message to the server (SID 0x37) /// /// Step 3 Response: The server sends a [`crate::UdsServiceType::RequestTransferExit`] response message to the client (RID 0x77) -#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] -#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))] -#[derive(Clone, Debug, PartialEq)] -#[non_exhaustive] -pub struct TransferDataRequest { - /// Starts at 0x01 from the server when a `RequestDownload` or `RequestUpload` or `RequestFileTransfer` is received - /// Increments by 0x01 for each `TransferDataRequest` message - /// At 0xFF the counter wraps around to 0x00 +/// +/// Zero-alloc TX request to transfer data. Borrows from the caller. +#[derive(Clone, Copy, Debug, PartialEq)] +pub struct TransferDataRequestTx<'d> { + /// Block sequence counter (wraps 0xFF → 0x00). pub block_sequence_counter: u8, - /// The data to be transferred, the server sends the amount of data (# of bytes) it can handle in the - /// [`crate::RequestDownloadResponse`] message - pub data: Vec, + /// The data to be transferred. + pub data: &'d [u8], } -impl TransferDataRequest { - pub(crate) fn new(block_sequence_counter: u8, data: Vec) -> Self { +impl<'d> TransferDataRequestTx<'d> { + /// Create a new transfer data request. + #[must_use] + pub const fn new(block_sequence_counter: u8, data: &'d [u8]) -> Self { Self { block_sequence_counter, data, @@ -44,56 +41,48 @@ impl TransferDataRequest { } } -impl WireFormat for TransferDataRequest { - fn required_size(&self) -> usize { +impl Encode for TransferDataRequestTx<'_> { + fn encoded_size(&self) -> usize { 1 + self.data.len() } - fn encode(&self, writer: &mut T) -> Result { - writer.write_u8(self.block_sequence_counter)?; - writer.write_all(&self.data)?; - Ok(self.required_size()) + fn encode(&self, writer: &mut impl embedded_io::Write) -> Result { + writer + .write_all(&[self.block_sequence_counter]) + .map_err(Error::io)?; + writer.write_all(self.data).map_err(Error::io)?; + Ok(self.encoded_size()) } } -impl SingleValueWireFormat for TransferDataRequest { - fn decode(reader: &mut T) -> Result { - let block_sequence_counter = reader.read_u8()?; - let mut data = Vec::new(); - reader.read_to_end(&mut data)?; - Ok(Self { - block_sequence_counter, - data, - }) +impl<'a> Decode<'a> for TransferDataRequestTx<'a> { + fn decode(buf: &'a [u8]) -> Result<(Self, &'a [u8]), Error> { + if buf.is_empty() { + return Err(Error::InsufficientData(1)); + } + Ok(( + Self { + block_sequence_counter: buf[0], + data: &buf[1..], + }, + &[], + )) } } -/// Positive response to a [`TransferDataRequest`]. -#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] -#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))] -#[derive(Clone, Debug, PartialEq)] -#[non_exhaustive] -pub struct TransferDataResponse { - /// Starts at 0x01 from the server when a `RequestDownload` or `RequestUpload` or `RequestFileTransfer` is received - /// Increments by 0x01 for each `TransferDataRequest` message - /// At 0xFF the counter wraps around to 0x00 - /// - /// This is an ECHO of the `block_sequence_counter` from the [`TransferDataRequest`] message - /// Check against the request to ensure the correct block is being acknowledged - /// If the `block_sequence_counter` is not as expected or does not arrive, the client should retransmit the block +/// Zero-alloc TX response for transfer data. Borrows from the caller. +#[derive(Clone, Copy, Debug, PartialEq)] +pub struct TransferDataResponseTx<'d> { + /// Echo of the block sequence counter. pub block_sequence_counter: u8, - - /// Contains data required by the client to support the transfer of data. - /// Vehicle manufacturer specific - /// - /// For download (client to server), this might be a checksum for the client to verify correct transfer - /// This should not repeat the data sent from the client - /// For upload (server to client), this will include the data from the server - pub data: Vec, + /// Response data (vendor-specific). + pub data: &'d [u8], } -impl TransferDataResponse { - pub(crate) fn new(block_sequence_counter: u8, data: Vec) -> Self { +impl<'d> TransferDataResponseTx<'d> { + /// Create a new transfer data response. + #[must_use] + pub const fn new(block_sequence_counter: u8, data: &'d [u8]) -> Self { Self { block_sequence_counter, data, @@ -101,68 +90,73 @@ impl TransferDataResponse { } } -impl WireFormat for TransferDataResponse { - fn required_size(&self) -> usize { +impl Encode for TransferDataResponseTx<'_> { + fn encoded_size(&self) -> usize { 1 + self.data.len() } - fn encode(&self, writer: &mut T) -> Result { - writer.write_u8(self.block_sequence_counter)?; - writer.write_all(&self.data)?; - Ok(self.required_size()) + fn encode(&self, writer: &mut impl embedded_io::Write) -> Result { + writer + .write_all(&[self.block_sequence_counter]) + .map_err(Error::io)?; + writer.write_all(self.data).map_err(Error::io)?; + Ok(self.encoded_size()) } } -impl SingleValueWireFormat for TransferDataResponse { - fn decode(reader: &mut T) -> Result { - let block_sequence_counter = reader.read_u8()?; - let mut data = Vec::new(); - reader.read_to_end(&mut data)?; - Ok(Self { - block_sequence_counter, - data, - }) +impl<'a> Decode<'a> for TransferDataResponseTx<'a> { + fn decode(buf: &'a [u8]) -> Result<(Self, &'a [u8]), Error> { + if buf.is_empty() { + return Err(Error::InsufficientData(1)); + } + Ok(( + Self { + block_sequence_counter: buf[0], + data: &buf[1..], + }, + &[], + )) } } #[cfg(test)] mod request { use super::*; + use crate::{Decode, Encode}; #[test] fn test_transfer_data_request() { - let bytes = [0x01, 0x02, 0x03, 0x04]; - let req = TransferDataRequest::new(0x01, bytes.to_vec()); - let bytes = req.data.clone(); - let expected = vec![0x01, 0x02, 0x03, 0x04]; + let data = [0x01, 0x02, 0x03, 0x04]; + let req = TransferDataRequestTx::new(0x01, &data); assert_eq!(1, req.block_sequence_counter); - assert_eq!(bytes, expected); + assert_eq!(req.data, &[0x01, 0x02, 0x03, 0x04]); } #[test] fn read_request() { let bytes = [0x01, 0x02, 0x03, 0x04]; - let req = TransferDataRequest::decode(&mut bytes.as_slice()).unwrap(); + let (req, _) = ::decode(&bytes).unwrap(); let mut written_bytes = Vec::new(); - let written = req.encode(&mut written_bytes).unwrap(); + let written = Encode::encode(&req, &mut written_bytes).unwrap(); assert_eq!(written, written_bytes.len()); - assert_eq!(written, req.required_size()); + assert_eq!(written, req.encoded_size()); } } #[cfg(test)] mod response { use super::*; + use crate::{Decode, Encode}; #[test] fn simple_response() { let bytes = [0x01, 0x02, 0x03, 0x04]; - let resp = TransferDataResponse::decode(&mut bytes.as_slice()).unwrap(); + let (resp, _) = ::decode(&bytes).unwrap(); let mut written_bytes = Vec::new(); - let written = resp.encode(&mut written_bytes).unwrap(); + let written = Encode::encode(&resp, &mut written_bytes).unwrap(); assert_eq!(written, written_bytes.len()); - assert_eq!(written, resp.required_size()); + assert_eq!(written, resp.encoded_size()); } } diff --git a/src/services/write_data_by_identifier.rs b/src/services/write_data_by_identifier.rs index 91003da..35224cc 100644 --- a/src/services/write_data_by_identifier.rs +++ b/src/services/write_data_by_identifier.rs @@ -1,13 +1,5 @@ //! `WriteDataByIdentifier` (0x2E) service implementation -use crate::{Error, Identifier, NegativeResponseCode, SingleValueWireFormat, WireFormat}; - -const WRITE_DID_NEGATIVE_RESPONSE_CODES: [NegativeResponseCode; 5] = [ - NegativeResponseCode::IncorrectMessageLengthOrInvalidFormat, - NegativeResponseCode::ConditionsNotCorrect, - NegativeResponseCode::RequestOutOfRange, - NegativeResponseCode::SecurityAccessDenied, - NegativeResponseCode::GeneralProgrammingFailure, -]; +use crate::{Encode, Error, Identifier}; /// See ISO-14229-1:2020, Section 11.7.2.1 #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] @@ -19,39 +11,23 @@ pub struct WriteDataByIdentifierRequest { pub payload: Payload, } -impl WriteDataByIdentifierRequest { - /// Create a new request with the given payload. +impl WriteDataByIdentifierRequest { + /// Create a new write-by-identifier request. pub fn new(payload: Payload) -> Self { Self { payload } } - - /// Get the allowed Nack codes for this request - #[must_use] - pub fn allowed_nack_codes() -> &'static [NegativeResponseCode] { - &WRITE_DID_NEGATIVE_RESPONSE_CODES - } } -impl WireFormat for WriteDataByIdentifierRequest { - fn required_size(&self) -> usize { - self.payload.required_size() +impl Encode for WriteDataByIdentifierRequest { + fn encoded_size(&self) -> usize { + self.payload.encoded_size() } - fn encode(&self, writer: &mut T) -> Result { - // Payload must implement the extra bytes, because `decode` needs to know how to interpret payload message + fn encode(&self, writer: &mut impl embedded_io::Write) -> Result { self.payload.encode(writer) } } -impl SingleValueWireFormat - for WriteDataByIdentifierRequest -{ - fn decode(reader: &mut R) -> Result { - let payload = Payload::decode(reader)?; - Ok(Self { payload }) - } -} - /////////////////////////////////////////////////////////////////////////////////////////////////// /// See ISO-14229-1:2020, Section 11.7.3.1 @@ -71,134 +47,59 @@ impl WriteDataByIdentifierResponse { } } -impl WireFormat for WriteDataByIdentifierResponse { - fn required_size(&self) -> usize { - self.identifier.required_size() +impl Encode for WriteDataByIdentifierResponse { + fn encoded_size(&self) -> usize { + 2 } - fn encode(&self, writer: &mut T) -> Result { - // Payload must implement the extra bytes, because `decode` needs to know how to interpret payload message - self.identifier.encode(writer) + fn encode(&self, writer: &mut impl embedded_io::Write) -> Result { + Encode::encode(&self.identifier, writer) } } -impl SingleValueWireFormat - for WriteDataByIdentifierResponse -{ - fn decode(reader: &mut R) -> Result { - let identifier = DataIdentifier::decode(reader)?; - Ok(Self::new(identifier)) - } -} -/////////////////////////////////////////////////////////////////////////////////////////////////// - #[cfg(test)] mod test { use super::*; - use crate::impl_identifier; - use byteorder::WriteBytesExt; - - #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] - #[derive(Clone, Copy, Debug, PartialEq)] - pub enum TestIdentifier { - Abracadabra = 0xBEEF, - } - impl_identifier!(TestIdentifier); - impl From for TestIdentifier { - fn from(value: u16) -> Self { - match value { - 0xBEEF => TestIdentifier::Abracadabra, - _ => panic!("Invalid test identifier: {value}"), - } - } - } + use crate::{ProtocolPayloadTx, UDSIdentifier, impl_identifier}; - impl From for u16 { - fn from(value: TestIdentifier) -> Self { - match value { - TestIdentifier::Abracadabra => 0xBEEF, - } - } - } - - impl PartialEq for TestIdentifier { - fn eq(&self, other: &u16) -> bool { - match self { - TestIdentifier::Abracadabra => *other == 0xBEEF, - } + #[test] + fn test_write_response_encode() { + #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] + #[derive(Clone, Copy, Debug, PartialEq)] + pub enum TestIdentifier { + Abracadabra = 0xBEEF, } - } - - /////////////////////////////////////////////////////////////////////////////////////////////// - - #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] - #[derive(Clone, Copy, Debug, PartialEq)] - enum TestPayload { - Abracadabra(u8), - } - - impl WireFormat for TestPayload { - fn encode(&self, writer: &mut T) -> Result { - let id_bytes: u16 = match self { - TestPayload::Abracadabra(_) => 0xBEEF, - }; - - writer.write_all(&id_bytes.to_be_bytes())?; - - match self { - TestPayload::Abracadabra(value) => { - writer.write_u8(*value)?; - Ok(self.required_size()) + impl_identifier!(TestIdentifier); + impl From for TestIdentifier { + fn from(value: u16) -> Self { + match value { + 0xBEEF => TestIdentifier::Abracadabra, + _ => panic!("Invalid test identifier: {value}"), } } } - - fn required_size(&self) -> usize { - 3 - } - } - - impl SingleValueWireFormat for TestPayload { - fn decode(reader: &mut T) -> Result { - let mut buf = [0u8; 2]; - reader.read_exact(&mut buf)?; - - let value = u16::from_be_bytes(buf); - - if value == TestIdentifier::Abracadabra as u16 { - let mut byte = [0u8; 1]; - reader.read_exact(&mut byte)?; - Ok(TestPayload::Abracadabra(byte[0])) - } else { - Err(Error::NoDataAvailable) + impl From for u16 { + fn from(value: TestIdentifier) -> Self { + match value { + TestIdentifier::Abracadabra => 0xBEEF, + } } } - } - - /////////////////////////////////////////////////////////////////////////////////////////////// - - #[test] - fn test_write_request() { - let request = WriteDataByIdentifierRequest::new(TestPayload::Abracadabra(42)); - - let mut written_bytes = Vec::new(); - let written = request.encode(&mut written_bytes).unwrap(); - assert_eq!(written, request.required_size()); - assert_eq!(written, written_bytes.len()); - let request2 = - WriteDataByIdentifierRequest::::decode(&mut written_bytes.as_slice()) - .unwrap(); - assert_eq!(request, request2); + let response = WriteDataByIdentifierResponse::new(TestIdentifier::Abracadabra); + let mut buf = [0u8; 4]; + let written = Encode::encode(&response, &mut buf.as_mut_slice()).unwrap(); + assert_eq!(written, 2); + assert_eq!(buf[0], 0xBE); + assert_eq!(buf[1], 0xEF); } #[test] - fn test_write_response() { - let response = WriteDataByIdentifierResponse::new(TestIdentifier::Abracadabra); - - let mut written_bytes = Vec::new(); - let written = response.encode(&mut written_bytes).unwrap(); - assert_eq!(written, written_bytes.len()); - assert_eq!(written, response.required_size()); + fn test_write_request_encode() { + let payload = ProtocolPayloadTx::new(UDSIdentifier::ActiveDiagnosticSession, &[0x01]); + let request = WriteDataByIdentifierRequest::new(payload); + let mut buf = [0u8; 8]; + let written = Encode::encode(&request, &mut buf.as_mut_slice()).unwrap(); + assert_eq!(written, 3); } } diff --git a/src/traits.rs b/src/traits.rs index adc380d..1efe729 100644 --- a/src/traits.rs +++ b/src/traits.rs @@ -1,156 +1,58 @@ use crate::Error; -use byteorder::{BigEndian, WriteBytesExt}; -/// Base trait for types that can be serialized to a byte stream. -/// -/// `WireFormat` provides the encoding half of the serialization contract. -/// Decoding is split into two separate traits with distinct return types: -/// - [`SingleValueWireFormat`] for types whose decode always produces a value -/// - [`IterableWireFormat`] for types that may return `None` when a stream is exhausted -/// -/// This split enforces at compile time the distinction between types that always -/// decode successfully (given valid data) and types that can signal "no more items." -pub trait WireFormat: Sized { - /// Returns the number of bytes required to serialize this value. - fn required_size(&self) -> usize; +// --------------------------------------------------------------------------- +// New no_std-compatible traits (TX: Encode, RX: Decode / DecodeIter) +// --------------------------------------------------------------------------- - /// Serialize a value to a byte stream. - /// Returns the number of bytes written. - /// # Errors - /// - If the data cannot be written to the stream - fn encode(&self, writer: &mut T) -> Result; +/// TX-side trait: encode a value into an [`embedded_io::Write`] implementor. +pub trait Encode { + /// Number of bytes this value will write. + fn encoded_size(&self) -> usize; - /// For some UDS messages, positive replies can be suppressed via the SPRMIB (bit 7 position) of the request. + /// Serialize into `writer`, returning the number of bytes written. /// - /// Default to false, meaning that the positive response is not suppressed. Some services do not support this feature, - /// so this function should not be used to assume that a positive response can be suppressed. + /// # Errors + /// Returns [`Error::IoError`] if the writer fails. + fn encode(&self, writer: &mut impl embedded_io::Write) -> Result; + + /// Whether the positive response for this message is suppressed (SPRMIB). fn is_positive_response_suppressed(&self) -> bool { false } } -/// Types whose decode always produces a value. An empty stream is an error, not `None`. +/// RX-side trait: zero-copy decode from a byte slice. /// -/// This trait enforces at compile time that `decode` cannot return `None`. -/// The return type is `Result` rather than `Result, Error>`. -pub trait SingleValueWireFormat: WireFormat { - /// Deserialize a value from a byte stream. +/// Implementations borrow directly from the input buffer where possible. +/// Returns the decoded value together with the unconsumed remainder of the +/// buffer. +pub trait Decode<'a>: Sized { + /// Decode from `buf`, returning `(value, remaining_bytes)`. + /// /// # Errors - /// - if the stream is empty - /// - if the stream is not in the expected format - /// - if the stream contains partial data - fn decode(reader: &mut T) -> Result; + /// Returns an error if `buf` is too short or contains invalid data. + fn decode(buf: &'a [u8]) -> Result<(Self, &'a [u8]), Error>; } -struct WireFormatIterator<'a, T, R> { - reader: &'a mut R, - _phantom: std::marker::PhantomData, -} - -/// For types that can appear in lists of unknown length, this trait provides an iterator -/// that can be used to deserialize a stream of values. -impl Iterator for WireFormatIterator<'_, T, R> { - type Item = Result; - fn next(&mut self) -> Option { - match T::decode_next(self.reader.by_ref()) { - Ok(Some(value)) => Some(Ok(value)), - Ok(None) => None, - Err(e) => Some(Err(e)), - } - } -} - -/// Types that can be decoded from a stream of unknown length. +/// RX-side trait: streaming / iterable zero-copy decode. /// -/// `decode_next` returns `Ok(None)` when the stream is exhausted, allowing -/// iteration over variable-length sequences without prior knowledge of their size. -pub trait IterableWireFormat: WireFormat { - /// Attempt to decode the next value from the stream. - /// Returns `Ok(None)` if the stream is exhausted. +/// Used for variable-length sequences where the number of items is not known +/// ahead of time. Each call consumes one item and returns the remainder, or +/// `Ok(None)` when the buffer is exhausted. +pub trait DecodeIter<'a>: Sized { + /// Try to decode the next item from `buf`. + /// + /// Returns `Ok(None)` when the buffer is empty (sequence exhausted). + /// /// # Errors - /// - if the stream contains partial or invalid data - fn decode_next(reader: &mut T) -> Result, Error>; - - /// Return an iterator that decodes successive values from the stream until exhausted. - fn decode_iter(reader: &mut T) -> impl Iterator> { - WireFormatIterator { - reader, - _phantom: std::marker::PhantomData, - } - } -} - -#[cfg(feature = "serde")] -mod maybe_serde { - // When `serde` feature is ON, require Serialize + Deserialize - pub trait Bound: serde::Serialize + for<'de> serde::Deserialize<'de> {} - impl Bound for T where T: serde::Serialize + for<'de> serde::Deserialize<'de> {} -} -#[cfg(not(feature = "serde"))] -mod maybe_serde { - // When `serde` feature is OFF, require nothing - pub trait Bound {} - impl Bound for T {} -} - -#[cfg(feature = "utoipa")] -mod maybe_utoipa { - // When `utoipa` feature is ON, require ToSchema - pub trait Bound: utoipa::ToSchema {} - impl Bound for T where T: utoipa::ToSchema {} -} - -#[cfg(not(feature = "utoipa"))] -mod maybe_utoipa { - // When `utoipa` feature is OFF, require nothing - pub trait Bound {} - impl Bound for T {} + /// Returns an error if the buffer contains a partial or invalid item. + fn decode_next(buf: &'a [u8]) -> Result, Error>; } /// Trait for types that can be used as identifiers (ie Data Identifiers and Routine Identifiers) /// /// Use the [`impl_identifier!`] macro to implement this trait for your types. -pub trait Identifier: TryFrom + Into + Clone + Copy + maybe_serde::Bound { - /// Returns a `Vec` from a reader that contains a list of Identifier values - /// # Errors - /// - if the list is not in the expected format - /// - if the list contains partial data - fn parse_from_list(reader: &mut R) -> Result, Error> { - // Create an iterator to collect. Will use the blanket implementation of IterableWireFormat for Identifier - // to read the values from the reader - WireFormatIterator { - reader, - _phantom: std::marker::PhantomData, - } - .collect() - } - - /// Intended to be used in a payload where the identifier is the first value and not a list of identifiers - /// IE `DataIdentifer` (DID) payloads and `RoutineIdentifier` (RID) payloads - /// - /// Returns the identifier, or None if the reader is empty - /// - /// ## Example reading a payload that has multiple identifiers - /// ```rust,ignore - /// while let Some(identifier) = MyIdentifier::parse_from_payload(&mut buffer).unwrap() { - /// match identifier { - /// MyIdentifier::Identifier1 | MyIdentifier::Identifier2 => { - /// let payload = MyPayload::decode(&mut buffer).unwrap(); - /// } - /// // No payload for Identifier3 - /// MyIdentifier::MyIdentifier3 => (), - /// MyIdentifier::UDSIdentifier(_) => (), - /// } - /// } - /// ``` - /// - /// # Errors - /// - if the stream is not in the expected format - /// - if the stream contains partial data - fn parse_from_payload(reader: &mut R) -> Result, Error> { - Self::decode_next(reader) - } -} +pub trait Identifier: TryFrom + Into + Clone + Copy {} /// Implement the [`Identifier`] trait for a type. /// @@ -170,128 +72,73 @@ macro_rules! impl_identifier { /// Marker subtrait of [`Identifier`] to distinguish routine identifiers from data identifiers. pub trait RoutineIdentifier: Identifier {} -/// Blanket implementation of [`WireFormat`] for types that implement [`Identifier`] -impl WireFormat for T +/// Blanket implementation of [`Encode`] for types that implement [`Identifier`] +impl Encode for T where T: Identifier, { - fn required_size(&self) -> usize { + fn encoded_size(&self) -> usize { 2 } - fn encode(&self, writer: &mut W) -> Result { - writer.write_u16::((*self).into())?; + fn encode(&self, writer: &mut impl embedded_io::Write) -> Result { + writer + .write_all(&>::into((*self).into()).to_be_bytes()) + .map_err(Error::io)?; Ok(2) } } -/// Blanket implementation of [`SingleValueWireFormat`] for types that implement [`Identifier`] -impl SingleValueWireFormat for T +/// Blanket implementation of [`Decode`] for types that implement [`Identifier`] +impl<'a, T> Decode<'a> for T where T: Identifier, { - fn decode(reader: &mut R) -> Result { - let mut identifier_data: [u8; 2] = [0; 2]; - match reader.read(&mut identifier_data)? { - 0 | 1 => return Err(Error::IncorrectMessageLengthOrInvalidFormat), - 2 => (), - _ => unreachable!("Impossible to read more than 2 bytes into 2 byte array"), + fn decode(buf: &'a [u8]) -> Result<(Self, &'a [u8]), Error> { + if buf.len() < 2 { + return Err(Error::IncorrectMessageLengthOrInvalidFormat); } - - match Self::try_from(u16::from_be_bytes(identifier_data)) { - Ok(identifier) => Ok(identifier), - Err(_) => Err(Error::InvalidDiagnosticIdentifier(u16::from_be_bytes( - identifier_data, - ))), + let raw = u16::from_be_bytes([buf[0], buf[1]]); + match Self::try_from(raw) { + Ok(identifier) => Ok((identifier, &buf[2..])), + Err(_) => Err(Error::InvalidDiagnosticIdentifier(raw)), } } } -/// Blanket implementation of [`IterableWireFormat`] for types that implement [`Identifier`] -impl IterableWireFormat for T +/// Blanket implementation of [`DecodeIter`] for types that implement [`Identifier`] +impl<'a, T> DecodeIter<'a> for T where T: Identifier, { - fn decode_next(reader: &mut R) -> Result, Error> { - let mut identifier_data: [u8; 2] = [0; 2]; - match reader.read(&mut identifier_data)? { - 0 => return Ok(None), - 1 => return Err(Error::IncorrectMessageLengthOrInvalidFormat), - 2 => (), - _ => unreachable!("Impossible to read more than 2 bytes into 2 byte array"), + fn decode_next(buf: &'a [u8]) -> Result, Error> { + if buf.is_empty() { + return Ok(None); } - - 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, - ))), + if buf.len() < 2 { + return Err(Error::IncorrectMessageLengthOrInvalidFormat); } + Decode::decode(buf).map(Some) } } -/// A trait that defines the user-defined diagnostic definitions/specifiers for UDS requests and responses. -/// -/// Used to specify the types of the identifiers and payloads used in UDS requests and responses. -/// It allows for flexibility in defining custom data types while adhering to the UDS protocol. +/// Trait for diagnostic definitions that specifies the identifier and payload +/// types used when constructing and parsing UDS requests and responses. pub trait DiagnosticDefinition: 'static { - /// UDS Data Identifier - /// - /// Requests : [`ReadDataByIdentifierRequest`](crate::ReadDataByIdentifierRequest), [`WriteDataByIdentifierRequest`](crate::WriteDataByIdentifierRequest), and [`ReadDTCInfoRequest`](crate::ReadDTCInfoRequest) - /// Responses: [`ReadDataByIdentifierResponse`](crate::ReadDataByIdentifierResponse), [`WriteDataByIdentifierResponse`](crate::WriteDataByIdentifierResponse), and [`ReadDTCInfoResponse`](crate::ReadDTCInfoResponse) - type DID: Identifier - + Clone - + std::fmt::Debug - + Send - + Sync - + PartialEq - + 'static - + maybe_serde::Bound - + maybe_utoipa::Bound; - /// Response payload for [`ReadDataByIdentifierRequest`](crate::ReadDataByIdentifierRequest) - type DiagnosticPayload: SingleValueWireFormat - + IterableWireFormat - + Clone - + std::fmt::Debug - + Send - + Sync - + PartialEq - + maybe_serde::Bound - + maybe_utoipa::Bound - + 'static; - - /// UDS Routine Identifier - /// - /// This is used to identify the routine to be controlled in a [`RoutineControlRequest`](crate::RoutineControlRequest) - type RID: RoutineIdentifier - + Clone - + std::fmt::Debug - + Send - + Sync - + PartialEq - + 'static - + maybe_serde::Bound - + maybe_utoipa::Bound; - /// Payload for both requests and responses of [`RoutineControlRequest`](crate::RoutineControlRequest) and [`RoutineControlResponse`](crate::RoutineControlResponse) - type RoutinePayload: SingleValueWireFormat - + IterableWireFormat - + Clone - + std::fmt::Debug - + Send - + Sync - + PartialEq - + 'static - + maybe_serde::Bound - + maybe_utoipa::Bound; + /// UDS Data Identifier type. + type DID: Identifier + Clone + core::fmt::Debug + PartialEq + 'static; + /// Payload type for read/write data by identifier etc. + type DiagnosticPayload: Encode + Clone + core::fmt::Debug + PartialEq + 'static; + /// UDS Routine Identifier type. + type RID: RoutineIdentifier + Clone + core::fmt::Debug + PartialEq + 'static; + /// Payload type for routine control requests/responses. + type RoutinePayload: Encode + Clone + core::fmt::Debug + PartialEq + 'static; } -/// tests #[cfg(test)] mod tests { use super::*; use crate::{Identifier, UDSIdentifier}; - use byteorder::ReadBytesExt; - use std::io::Cursor; #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] #[derive(Clone, Copy, Debug, Eq, PartialEq)] @@ -326,44 +173,32 @@ mod tests { } } - #[derive(Debug)] - pub struct MyPayload { - identifier: MyIdentifier, - u8_value: u8, - } - #[test] - fn test_identifier() { - let mut buffer = Cursor::new(vec![0u8; 2]); + fn test_identifier_encode_decode() { let identifier = MyIdentifier::Identifier1; - identifier.encode(&mut buffer).unwrap(); - buffer.set_position(0); - let read_identifier = MyIdentifier::parse_from_list(&mut buffer).unwrap(); - assert_eq!(identifier, read_identifier[0]); + let mut buf = [0u8; 2]; + Encode::encode(&identifier, &mut buf.as_mut_slice()).unwrap(); + let (decoded, rest) = ::decode(&buf).unwrap(); + assert_eq!(identifier, decoded); + assert!(rest.is_empty()); } #[test] #[allow(clippy::match_same_arms)] - fn test_payload() { - let mut buffer = Cursor::new(vec![0x01, 0x01, 0xFF, 0x02, 0x02, 0xFF, 0x03, 0x03]); - // Read until the end of the buffer - while let Some(identifier) = MyIdentifier::parse_from_payload(&mut buffer).unwrap() { - match identifier { - MyIdentifier::Identifier1 | MyIdentifier::Identifier2 => { - let payload = MyPayload { - identifier, - u8_value: buffer.read_u8().unwrap(), - }; - assert!(matches!( - payload.identifier, - MyIdentifier::Identifier1 | MyIdentifier::Identifier2 - )); - assert_eq!(payload.u8_value, 0xFF); - } - MyIdentifier::Identifier3 => (), - MyIdentifier::UDSIdentifier(_) => (), + fn test_identifier_decode_iter() { + let data = [0x01u8, 0x01, 0x02, 0x02, 0x03, 0x03]; + let mut remaining = &data[..]; + let mut count = 0; + while let Some((id, rest)) = MyIdentifier::decode_next(remaining).unwrap() { + remaining = rest; + count += 1; + match id { + MyIdentifier::Identifier1 => assert_eq!(count, 1), + MyIdentifier::Identifier2 => assert_eq!(count, 2), + MyIdentifier::Identifier3 => assert_eq!(count, 3), + MyIdentifier::UDSIdentifier(_) => panic!("Unexpected"), } } - println!("Testing printing"); + assert_eq!(count, 3); } }