Doip server initial code #4
Conversation
|
Is this code supposed to build? When I ran cargo check and cargo build, I encountered some errors. IMO, code should build successfully. |
a11f029 to
c674ff3
Compare
build is successful is now. |
src/doip/diagnostic_message.rs
Outdated
| pub fn parse(payload: &[u8]) -> Result<Self, Error> { | ||
| let header: [u8; 4] = | ||
| payload | ||
| .get(..4) |
There was a problem hiding this comment.
magic number? other places I could see used const
Not just here, please check below ones
There was a problem hiding this comment.
Fixed all magic numbers across the codebase (named constants , protocol versions, and message sizes). Will push in the next commit.
|
fix minor formatting inconsistencies |
arvindr19
left a comment
There was a problem hiding this comment.
Only header_parser.rs has any tracing at all. Every other file has no tracing.
| /// when the `DoIP` entity cannot process a received message due to header-level issues. | ||
| #[derive(Debug, Clone, Copy, PartialEq, Eq)] | ||
| #[repr(u8)] | ||
| pub enum GenericNackCode { |
There was a problem hiding this comment.
same defined in errors.rs
why so?
There was a problem hiding this comment.
Fixed , re-exported now
| // Response codes per ISO 13400-2:2019 Table 25 | ||
| #[derive(Debug, Clone, Copy, PartialEq, Eq)] | ||
| #[repr(u8)] | ||
| pub enum ResponseCode { |
There was a problem hiding this comment.
Fixed , re-exported now.
| // Diagnostic message negative ack codes per ISO 13400-2:2019 Table 28 | ||
| #[derive(Debug, Clone, Copy, PartialEq, Eq)] | ||
| #[repr(u8)] | ||
| pub enum NackCode { |
src/doip/header_parser.rs
Outdated
| inverse_version: DEFAULT_PROTOCOL_VERSION_INV, | ||
| payload_type: payload_type as u16, | ||
| payload_length: u32::try_from(payload.len()) | ||
| .expect("DoIP payload exceeds u32::MAX (protocol limit is 4MB)"), |
There was a problem hiding this comment.
lib should never panic, return Result instead
There was a problem hiding this comment.
Fixed, retruns Result now.
darkwisebear
left a comment
There was a problem hiding this comment.
I didn't go into details yet, but from what I see there are some general things that need cleanup before I will do that:
- Consolidate error types and use
thiserroreverywhere. - Introduce a trait for message types and use everywhere
- Deduplicate common parsing code
src/doip/alive_check.rs
Outdated
| use bytes::{BufMut, Bytes, BytesMut}; | ||
|
|
||
| #[derive(Debug, Clone, PartialEq, Eq)] | ||
| pub enum Error { |
There was a problem hiding this comment.
This crate references thiserror. You could use it to declare this error in a more convenient way.
src/doip/vehicle_id.rs
Outdated
| use bytes::{BufMut, Bytes, BytesMut}; | ||
|
|
||
| #[derive(Debug, Clone, PartialEq, Eq)] | ||
| pub enum Error { |
There was a problem hiding this comment.
A similar definition exists in alive_check.rs. Imo we should have a common definition of an error type for the entire module.
There was a problem hiding this comment.
moved to mod.rs and shared across
|
|
||
| // Vehicle Identification Request (0x0001) - no payload | ||
| #[derive(Debug, Clone, PartialEq, Eq, Default)] | ||
| pub struct Request; |
There was a problem hiding this comment.
The shape of this module is very similar to that of alive_check.rs. I think there should be a trait for message types so that there is an impl Request for VehicleIdRequest or the like.
src/doip/vehicle_id.rs
Outdated
| let eid: [u8; 6] = payload | ||
| .get(..Self::LEN) | ||
| .and_then(|s| s.try_into().ok()) | ||
| .ok_or(Error::PayloadTooShort { | ||
| expected: Self::LEN, | ||
| actual: payload.len(), | ||
| })?; |
There was a problem hiding this comment.
Such code could be factored out, as there is similar code also in alive_check.rs. I think there should be a submodule messages with common definitions and then types for each message.
src/doip/header_parser.rs
Outdated
|
|
||
| impl PayloadType { | ||
| #[must_use] | ||
| pub fn from_u16(value: u16) -> Option<Self> { |
There was a problem hiding this comment.
Why not impl TryFrom<u16> for PayloadType? This is more canonical than such a custom function.
src/server/config.rs
Outdated
| let bind = server | ||
| .bind_address | ||
| .as_deref() | ||
| .unwrap_or(DEFAULT_BIND_ADDRESS); |
There was a problem hiding this comment.
serde allows for specifying defaults inside the type definition. This would save you a lot of code that of the scheme "if value { copy } else { use default }".
There was a problem hiding this comment.
Yes , it is updated now
src/doip/alive_check.rs
Outdated
| @@ -0,0 +1,144 @@ | |||
| /* | |||
| * Copyright (c) 2025 The Contributors to Eclipse OpenSOVD (see CONTRIBUTORS) | |||
There was a problem hiding this comment.
year should be 2026, please check all the license headers
81e9ff5 to
9fa0c6e
Compare
|
Please run cargo fmt --check; it currently fails. Once the CI workflow PR is merged, this PR will need to be reworked, so it’s better to fix it beforehand. |
lh-sag
left a comment
There was a problem hiding this comment.
Just had a brief look at the PR.
@VinaykumarRS1995 did you had a chance to evaluate any of these crates to ease development?
- https://github.com/samp-reston/doip-definitions
- https://github.com/samp-reston/doip-codec
- https://github.com/samp-reston/doip-sockets
- https://github.com/samp-reston/doip-server
Adding @alexmohr and @theswiftfox to share their experience with some of the crates above.
Cargo.toml
Outdated
| [dependencies] | ||
| # Async runtime | ||
| tokio = { version = "1.35", features = ["full"] } | ||
| tokio-util = { version = "0.7", features = ["codec"] } | ||
|
|
||
| # Serialization | ||
| serde = { version = "1.0", features = ["derive"] } | ||
| toml = "0.9" | ||
|
|
||
| # Logging | ||
| tracing = "0.1" | ||
| tracing-subscriber = { version = "0.3", features = ["env-filter"] } | ||
|
|
||
| # Error handling | ||
| thiserror = "2.0" | ||
| anyhow = "1.0" | ||
|
|
||
| # Data structures | ||
| bytes = "1.0" | ||
| dashmap = "6.0" | ||
| parking_lot = "0.12" | ||
|
|
||
| # Utilities | ||
| uuid = { version = "1.6", features = ["v4"] } | ||
| clap = { version = "4.4", features = ["derive"] } |
There was a problem hiding this comment.
Always use default-features = false and explicitly add feature as needed. This reduced sbom and binary size often significantly.
src/uds/mod.rs
Outdated
| // #[cfg(any(test, feature = "test-handlers"))] | ||
| // pub mod dummy_handler; |
src/server/config.rs
Outdated
| fn parse_vin(s: &str) -> anyhow::Result<[u8; 17]> { | ||
| let bytes = s.as_bytes(); | ||
| if bytes.len() != 17 { | ||
| anyhow::bail!("VIN must be exactly 17 characters"); |
There was a problem hiding this comment.
Optional: I would rather return a proper error type than a string.
Cargo.toml
Outdated
| dashmap = "6.0" | ||
| parking_lot = "0.12" |
There was a problem hiding this comment.
Can you please double check that all crates are used/referenced in your code?
We're only using codec and definitions in the CDA as we had some issues with sockets. But using codecs and definitions would already prevent re-implemeting some things here. The sockets connections can be re-used from CDA. Currently both doip libraries we use are forked by @theswiftfox. I believe it would be a good idea to use her fork as a base to officially provide an eclipse fork we use in all of our projects. |
As we've also run into some issues with the original code, I think we need should fork them into the eclipse-opensovd repo, so we can adapt them quickly to our needs. I'll put it on the agenda for the next architecture board. |
@lh-sag personally, I would be reluctant with using creates where there is only exactly one maintainer and no updates in the github repo since quite some time. This is risk in my eyes. What's your opinion on this situation? |
The consideration for integrating DoIP crates is tracked in #9 - will revisit once they are officially part of the OpenSOVD project. Not blocking this PR. |
src/doip/alive_check.rs
Outdated
| } | ||
|
|
||
| impl DoipParseable for Response { | ||
| fn parse(payload: &[u8]) -> std::result::Result<Self, DoipError> { |
There was a problem hiding this comment.
| fn parse(payload: &[u8]) -> std::result::Result<Self, DoipError> { | |
| fn parse(payload: &[u8]) -> crate::DoipResult<Self> { |
There was a problem hiding this comment.
This repeats in other files, please also fix there.
There was a problem hiding this comment.
Done. Added pub use error::DoipResult in lib.rs and updated all signatures across the codebase — no raw Result<_, DoipError> left anywhere outside error.rs.
src/doip/diagnostic_message.rs
Outdated
| // Diagnostic message positive ack codes per ISO 13400-2:2019 Table 27 | ||
| #[derive(Debug, Clone, Copy, PartialEq, Eq)] | ||
| #[repr(u8)] | ||
| pub enum AckCode { |
There was a problem hiding this comment.
Why would this need to be exposed? Isn't that simply signalling "no error", which would be encoded as Ok(...)?
There was a problem hiding this comment.
AckCode::Acknowledged was just encoding "no error" as a type — removed it. Replaced with AckResult::Positive (no code needed) and AckResult::Negative(NackCode) for the failure case.
src/doip/diagnostic_message.rs
Outdated
| pub struct PositiveAck { | ||
| source_address: u16, | ||
| target_address: u16, | ||
| ack_code: AckCode, |
There was a problem hiding this comment.
That seems redundant. The pure existence of the instance indicates success.
There was a problem hiding this comment.
Merged PositiveAck and NegativeAck into a single DiagnosticAck with result: AckResult (Positive | Negative(NackCode)).
src/doip/diagnostic_message.rs
Outdated
| /// # Wire Format | ||
| /// Payload: SA(2) + TA(2) + nack_code(1) + optional previous_diag_data | ||
| #[derive(Debug, Clone, PartialEq, Eq)] | ||
| pub struct NegativeAck { |
There was a problem hiding this comment.
This looks exactly like PositiveAck. I have the feeling that it makes sense to merge both structs, and also the code, into one entity that simply represents what was received, no matter whether it was negative or positive.
There was a problem hiding this comment.
Merged PositiveAck and NegativeAck into a single DiagnosticAck with result: AckResult (Positive | Negative(NackCode)).
c8ccfbf to
0c12dc0
Compare
doip-server/src/doip/alive_check.rs
Outdated
| Some(0) | ||
| } | ||
|
|
||
| fn write_to(&self, _buf: &mut BytesMut) {} |
There was a problem hiding this comment.
maybe add todo! ("...") or unimplemented!("...") so if this method then it will through error.
There was a problem hiding this comment.
Done. Added comment: // Intentionally empty — Alive Check Request has zero-length payload per ISO 13400-2.
| } | ||
|
|
||
| impl Response { | ||
| pub const LEN: usize = 2; |
There was a problem hiding this comment.
Added /// doc comments to all constants explaining wire-format size.
| /// Override this to enable pre-allocated buffers in [`to_bytes`], avoiding | ||
| /// incremental `BytesMut` reallocations for large messages. | ||
| fn serialized_len(&self) -> Option<usize> { | ||
| None |
There was a problem hiding this comment.
Returning None in the default implementation doesn’t seem correct to me. If a user calls this method on a type that doesn’t override it, they still get a valid Option, which may be misleading.
There was a problem hiding this comment.
Done, Changes are pushed
doip-server/src/doip/mod.rs
Outdated
| .and_then(|s| s.try_into().ok()) | ||
| .ok_or_else(|| { | ||
| let e = too_short(payload, N); | ||
| warn!("{} parse failed: {}", context, e); |
There was a problem hiding this comment.
Done. Changed all parse-failure sites to error!. warn! kept only for recoverable NACK paths.
doip-server/src/doip/mod.rs
Outdated
| }) | ||
| } | ||
|
|
||
| // Re-export core types and constants for convenient access. |
There was a problem hiding this comment.
Consider keeping the header at the start of the file. If it’s user-facing, re-exporting it via lib.rs could be a good option.
There was a problem hiding this comment.
Done. Moved all pub use re-exports to top mod.rs
doip-server/src/server/session.rs
Outdated
| pub struct SessionManager { | ||
| sessions: RwLock<HashMap<u64, Session>>, | ||
| addr_to_session: RwLock<HashMap<SocketAddr, u64>>, | ||
| next_id: RwLock<u64>, |
There was a problem hiding this comment.
Do we really need RwLock for next_id ?
|
In general I would like to see structured logs instead of strings. This is done in CDA and Core. Can you please apply this to your logs? With structured logging (simplified for demonstration purpose): |
doip-server/src/server/session.rs
Outdated
| sessions: RwLock<HashMap<u64, Session>>, | ||
| addr_to_session: RwLock<HashMap<SocketAddr, u64>>, | ||
| next_id: RwLock<u64>, |
There was a problem hiding this comment.
Do you need such fine granular locking? Instead you could have an inner session manager and have a single rwlock.
Lock contention might be longer but you have atomicity.
I did not looked further into detail but please consider this if it is applicable.
There was a problem hiding this comment.
Done. Consolidated to single RwLock for atomic map consistency + AtomicU64 for lock-free ID allocation.
doip-server/src/error.rs
Outdated
| pub use crate::doip::routing_activation::ResponseCode as RoutingActivationCode; | ||
|
|
||
| /// Result type alias for `DoIP` operations | ||
| pub type DoipResult<T> = std::result::Result<T, DoipError>; |
There was a problem hiding this comment.
Usually the alias is Result and this is how it is named across the opensovd code base. Please also check the with the diag lib guys about the naming.
There was a problem hiding this comment.
Renamed to pub type Result.
src/doip/hearder_parser.rs
Outdated
| pub enum ParseError { | ||
| InvalidHeader(String), | ||
| Io(io::Error), | ||
| } | ||
|
|
||
| impl std::fmt::Display for ParseError { | ||
| fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { | ||
| match self { | ||
| Self::InvalidHeader(msg) => write!(f, "Invalid header: {}", msg), | ||
| Self::Io(e) => write!(f, "IO error: {}", e), | ||
| } | ||
| } | ||
| } | ||
|
|
||
| impl std::error::Error for ParseError {} | ||
|
|
||
| impl From<io::Error> for ParseError { | ||
| fn from(e: io::Error) -> Self { | ||
| Self::Io(e) | ||
| } | ||
| } | ||
|
|
There was a problem hiding this comment.
You already use thiserror, please use it here as well if it is applicable.
There was a problem hiding this comment.
Done — ParseError removed, all errors unified under DoipError with #[derive(thiserror::Error)].
- Renamed hearder_parser.rs to header_parser.rs (typo fix) - Added unit tests for header parsing - Updated MAX_DOIP_MESSAGE_SIZE to 4MB
- Consolidate errors into DoipError; add TryFrom<u8> on all enums - Strongly type activation_type field (ActivationType, not u8) - Add DoipParseable / DoipSerializable traits + DoipPayload dispatch enum - Override serialized_len() on all impls; fix array size magic numbers - Make ParseError pub(crate); add serde(default), tracing
- Split header_parser.rs into header.rs + codec.rs - Merge ParseError into DoipError (single error type) - Add parse_fixed_slice helper, remove duplicated parse patterns - Add DoipParseable / DoipSerializable traits - Make struct fields private, add public getters - Set default-features = false on all dependencies - Trim tokio features to io-util only - Remove dead test-handlers feature from Cargo.toml
Replace std::result::Result<_, DoipError> with crate::DoipResult across all files; add pub use error::DoipResult in lib.rs - remove redundant AckCode enum - remove redundant ack_code field from PositiveAck - merge PositiveAck + NegativeAck into DiagnosticAck with AckResult enum (Positive | Negative(NackCode))
- structured tracing logging; error! for parse failures - rename DoipResult → Result - single RwLock<SessionManagerInner> + AtomicU64 - merge DiagnosticAck + AckResult; remove AckCode - doc comments on all public API items
3ce6bcb to
3e77b11
Compare
| /// UDS service layer – bridges DoIP transport to ISO 14229-1 request/response handling. | ||
| pub mod uds; | ||
| pub use error::DoipError; | ||
| pub use error::Result; |
There was a problem hiding this comment.
@VinaykumarRS1995 how would this Result type get used on user side now? will there still be a prefix? and how would it look like?
There was a problem hiding this comment.
Yes,the crate name acts as the natural prefix. Re-exported via pub use error::Result in lib.rs, it's accessible as:
// fully qualified
fn parse() -> doip_server::Result
// or with import
use doip_server::Result;
fn connect() -> Result { ... }
Summary
Checklist