Skip to main content

doip_definitions/builder/
mod.rs

1use crate::header::{PayloadType, ProtocolVersion};
2use crate::payload::AliveCheckRequest;
3use crate::{header::DoipHeader, message::DoipMessage, payload::DoipPayload};
4
5/// A builder for constructing `DoipMessage` instances with specified headers and payloads.
6///
7/// This struct provides a fluent interface to configure the protocol version,
8/// payload, and automatically populate corresponding header fields based on the payload.
9#[derive(Default, Debug)]
10pub struct DoipMessageBuilder {
11    /// The header portion of the `DoIP` message, containing metadata like protocol version and payload type.
12    header: DoipHeader,
13
14    /// The payload content of the `DoIP` message, which varies depending on the message type.
15    payload: DoipPayload,
16}
17
18impl Default for DoipPayload {
19    /// Provides a default payload of type `AliveCheckRequest`.
20    fn default() -> Self {
21        DoipPayload::AliveCheckRequest(AliveCheckRequest {})
22    }
23}
24
25impl Default for DoipHeader {
26    /// Constructs a default `DoipHeader` with:
27    /// - Protocol version set to `DefaultValue`
28    /// - Inverse protocol version calculated automatically
29    /// - Payload type set to `AliveCheckRequest`
30    /// - Payload length initialized to 0
31    fn default() -> Self {
32        Self {
33            protocol_version: ProtocolVersion::DefaultValue,
34            inverse_protocol_version: !(ProtocolVersion::DefaultValue as u8),
35            payload_type: PayloadType::AliveCheckRequest,
36            payload_length: Default::default(),
37        }
38    }
39}
40
41impl DoipMessageBuilder {
42    /// Creates a new `DoipMessageBuilder` instance using the default header and payload.
43    ///
44    /// # Example
45    /// ```
46    /// use doip_definitions::builder::DoipMessageBuilder;
47    /// let builder = DoipMessageBuilder::new();
48    /// ```
49    #[must_use]
50    pub fn new() -> Self {
51        DoipMessageBuilder::default()
52    }
53
54    /// Sets the protocol version in the header and updates the inverse protocol version accordingly.
55    ///
56    /// # Arguments
57    ///
58    /// * `protocol_version` - An object that can be converted into a `ProtocolVersion`.
59    ///
60    /// # Returns
61    ///
62    /// The updated builder instance for chaining.
63    ///
64    /// # Example
65    /// ```
66    /// use doip_definitions::builder::DoipMessageBuilder;
67    /// use doip_definitions::header::ProtocolVersion;
68    /// let builder = DoipMessageBuilder::new().protocol_version(ProtocolVersion::Iso13400_2012);
69    /// ```
70    #[must_use]
71    pub fn protocol_version(mut self, protocol_version: impl Into<ProtocolVersion>) -> Self {
72        self.header.protocol_version = protocol_version.into();
73        self.header.inverse_protocol_version = !(self.header.protocol_version as u8);
74        self
75    }
76
77    /// Sets the payload of the message and updates the header's payload type and length accordingly.
78    ///
79    /// # Arguments
80    ///
81    /// * `payload` - An object that can be converted into a `DoipPayload`.
82    ///
83    /// # Returns
84    ///
85    /// The updated builder instance for chaining.
86    ///
87    /// # Panics
88    ///
89    /// This method panics if the size of the provided payload cannot be determined using
90    /// [`core::mem::size_of_val`]—which may occur if dynamically sized types or trait objects
91    /// are passed in as payload components.
92    ///
93    /// # Example
94    /// ```
95    /// use doip_definitions::builder::DoipMessageBuilder;
96    /// use doip_definitions::header::PayloadType;
97    /// use doip_definitions::payload::AliveCheckRequest;
98    /// use doip_definitions::payload::DoipPayload;
99    ///
100    /// let builder = DoipMessageBuilder::new().payload(DoipPayload::AliveCheckRequest(AliveCheckRequest {}));
101    /// ```
102    #[must_use]
103    pub fn payload(mut self, payload: impl Into<DoipPayload>) -> Self {
104        self.payload = payload.into();
105
106        let (payload_type, size) = match self.payload {
107            DoipPayload::GenericNack(ref pay) => (PayloadType::GenericNack, size_of_val(pay)),
108            DoipPayload::VehicleIdentificationRequest(ref pay) => {
109                (PayloadType::VehicleIdentificationRequest, size_of_val(pay))
110            }
111            DoipPayload::VehicleIdentificationRequestEid(ref pay) => (
112                PayloadType::VehicleIdentificationRequestEid,
113                size_of_val(pay),
114            ),
115            DoipPayload::VehicleIdentificationRequestVin(ref pay) => (
116                PayloadType::VehicleIdentificationRequestVin,
117                size_of_val(pay),
118            ),
119            DoipPayload::VehicleAnnouncementMessage(ref pay) => {
120                (PayloadType::VehicleAnnouncementMessage, size_of_val(pay))
121            }
122            DoipPayload::RoutingActivationRequest(ref pay) => {
123                (PayloadType::RoutingActivationRequest, size_of_val(pay))
124            }
125            DoipPayload::RoutingActivationResponse(ref pay) => {
126                (PayloadType::RoutingActivationResponse, size_of_val(pay))
127            }
128            DoipPayload::AliveCheckRequest(ref pay) => {
129                (PayloadType::AliveCheckRequest, size_of_val(pay))
130            }
131            DoipPayload::AliveCheckResponse(ref pay) => {
132                (PayloadType::AliveCheckResponse, size_of_val(pay))
133            }
134            DoipPayload::EntityStatusRequest(ref pay) => {
135                (PayloadType::EntityStatusRequest, size_of_val(pay))
136            }
137            DoipPayload::EntityStatusResponse(ref pay) => {
138                (PayloadType::EntityStatusResponse, size_of_val(pay))
139            }
140            DoipPayload::PowerInformationRequest(ref pay) => {
141                (PayloadType::PowerInformationRequest, size_of_val(pay))
142            }
143            DoipPayload::PowerInformationResponse(ref pay) => {
144                (PayloadType::PowerInformationResponse, size_of_val(pay))
145            }
146            DoipPayload::DiagnosticMessage(ref pay) => (PayloadType::DiagnosticMessage, pay.size()),
147            DoipPayload::DiagnosticMessageAck(ref pay) => {
148                (PayloadType::DiagnosticMessageAck, size_of_val(pay))
149            }
150            DoipPayload::DiagnosticMessageNack(ref pay) => {
151                (PayloadType::DiagnosticMessageNack, size_of_val(pay))
152            }
153        };
154
155        self.header.payload_type = payload_type;
156        self.header.payload_length =
157            u32::try_from(size).expect("This should never be larger than u32.");
158
159        self
160    }
161
162    /// Finalizes the builder and returns the constructed `DoipMessage`.
163    ///
164    /// # Returns
165    ///
166    /// The fully constructed `DoipMessage`.
167    ///
168    /// # Example
169    /// ```
170    /// use doip_definitions::builder::DoipMessageBuilder;
171    /// use doip_definitions::header::PayloadType;
172    /// use doip_definitions::payload::AliveCheckRequest;
173    /// use doip_definitions::payload::DoipPayload;
174    ///
175    /// let message = DoipMessageBuilder::new()
176    ///     .payload(DoipPayload::AliveCheckRequest(AliveCheckRequest {}))
177    ///     .build();
178    /// ```
179    #[must_use]
180    pub fn build(self) -> DoipMessage {
181        DoipMessage {
182            header: self.header,
183            payload: self.payload,
184        }
185    }
186}
187#[cfg(test)]
188mod tests {
189    use super::*;
190    use crate::{
191        definitions::{DOIP_DIAG_COMMON_SOURCE_LEN, DOIP_DIAG_COMMON_TARGET_LEN},
192        payload::{DiagnosticMessage, DoipPayload},
193    };
194
195    #[test]
196    fn test_diagnostic_message_payload() {
197        let message = vec![0x01, 0x02, 0x03];
198        let message_length = message.len();
199        let payload = DoipPayload::DiagnosticMessage(DiagnosticMessage {
200            source_address: [0x12, 0x34],
201            target_address: [0x56, 0x78],
202            message,
203        });
204        let builder = DoipMessageBuilder::new().payload(payload);
205        let doip_message = builder.build();
206
207        assert_eq!(
208            doip_message.header.payload_type,
209            PayloadType::DiagnosticMessage
210        );
211        assert_eq!(
212            doip_message.header.payload_length,
213            (DOIP_DIAG_COMMON_SOURCE_LEN + DOIP_DIAG_COMMON_TARGET_LEN + message_length) as u32
214        );
215    }
216}