Skip to main content

ace_server/
config.rs

1// region: Imports
2
3use ace_core::Vec;
4use ace_sim::clock::Duration;
5
6// endregion: Imports
7
8// region: Periodic Rate Presets
9
10/// Periodic transmission rate presets.
11/// Arbitrary intervals are supported - these are provided for convenience.
12pub mod periodic {
13    use ace_sim::clock::Duration;
14
15    /// 2000ms - Slow
16    pub const SLOW: Duration = Duration::from_millis(2_000);
17
18    /// 500ms - Medium
19    pub const MEDIUM: Duration = Duration::from_millis(500);
20
21    /// 50ms - Fast
22    pub const FAST: Duration = Duration::from_millis(50);
23}
24
25// endregion: Periodic Rate Presets
26
27// region: Session Config
28
29/// Configuration for a single UDS diagnostic session.
30///
31/// Mirrors the session configuration in an ODX ECU-DESC container.
32#[derive(Debug, Clone)]
33pub struct SessionConfig {
34    /// UDS session type byte.
35    /// 0x01 Default Session, 0x02 Programming Session, 0x03 ExtendedSession.
36    pub session_type: u8,
37
38    /// P2 server - max time to respond before the tester times out (ms)
39    pub p2_timeout: Duration,
40
41    /// P2* server - max time after sending 0x78 Response Pending before the final response must be
42    /// sent (ms)
43    pub p2_extended_timeout: Duration,
44
45    /// S3 server - max time between Tester Present messages before the server drops back to
46    /// Default Session (ms)
47    pub s3_timeout: Duration,
48}
49
50impl SessionConfig {
51    pub const fn default_session() -> Self {
52        Self {
53            session_type: 0x01,
54            p2_timeout: Duration::from_millis(50),
55            p2_extended_timeout: Duration::from_millis(5_000),
56            s3_timeout: Duration::from_millis(5_000),
57        }
58    }
59
60    pub const fn programming_session() -> Self {
61        Self {
62            session_type: 0x02,
63            p2_timeout: Duration::from_millis(50),
64            p2_extended_timeout: Duration::from_millis(5_000),
65            s3_timeout: Duration::from_millis(5_000),
66        }
67    }
68
69    pub const fn extended_session() -> Self {
70        Self {
71            session_type: 0x03,
72            p2_timeout: Duration::from_millis(50),
73            p2_extended_timeout: Duration::from_millis(5_000),
74            s3_timeout: Duration::from_millis(5_000),
75        }
76    }
77}
78
79// endregion: Session Config
80
81// region: Service Config
82
83/// Configuration for a supported UDS service.
84///
85/// Mirrors a DiagService entry in an ODX file.
86#[derive(Debug, Clone)]
87pub struct ServiceConfig {
88    /// UDS service ID byte (e.g 0x22 for ReadDataByIdentifier).
89    pub service_id: u8,
90
91    /// Session types in which this service is available. The server returns 0x7F NRC Service Not
92    /// Supported In Active Session if a request arrives outside these sessions.
93    pub supported_in: &'static [u8],
94
95    /// Minimum security level required. 0 = no security required.
96    pub security_level: u8,
97}
98
99impl ServiceConfig {
100    pub const fn new(service_id: u8, supported_in: &'static [u8]) -> Self {
101        ServiceConfig {
102            service_id,
103            supported_in,
104            security_level: 0,
105        }
106    }
107
108    pub const fn secured(service_id: u8, supported_in: &'static [u8], security_level: u8) -> Self {
109        Self {
110            service_id,
111            supported_in,
112            security_level,
113        }
114    }
115}
116
117// endregion: Service Config
118
119// region: DID Config
120
121/// Configuration for a single Data Identifier (DID).
122///
123/// Mirrors a DataObject entry in an ODX file.
124#[derive(Debug, Clone)]
125pub struct DidConfig {
126    /// 2-byte DID value.
127    pub identifier: u16,
128
129    /// Sessions in which this DID may be read. Empty = not readable.
130    pub readable_in: &'static [u8],
131
132    /// Sessions in which this DID may be written. Empty = not writable.
133    pub writable_in: &'static [u8],
134
135    /// Minimum security level to access this DID. 0 = no security.
136    pub security_level: u8,
137
138    /// Whether this DID may be scheduled for periodic transmission (0x2A).
139    pub periodic: bool,
140
141    /// Minimum interval the server will honor for periodic scheduling. Client-requested intervals
142    /// shorter than this are clamped up.
143    pub min_periodic_interval: Duration,
144}
145
146impl DidConfig {
147    pub const fn read_only(identifier: u16, readable_in: &'static [u8]) -> Self {
148        Self {
149            identifier,
150            readable_in,
151            writable_in: &[],
152            security_level: 0,
153            periodic: false,
154            min_periodic_interval: Duration::from_millis(50),
155        }
156    }
157
158    pub const fn read_write(
159        identifier: u16,
160        readable_in: &'static [u8],
161        writable_in: &'static [u8],
162    ) -> Self {
163        Self {
164            identifier,
165            readable_in,
166            writable_in,
167            security_level: 0,
168            periodic: false,
169            min_periodic_interval: Duration::from_millis(50),
170        }
171    }
172
173    pub const fn periodic(mut self, min_interval: Duration) -> Self {
174        self.periodic = true;
175        self.min_periodic_interval = min_interval;
176        self
177    }
178
179    pub const fn secured(mut self, level: u8) -> Self {
180        self.security_level = level;
181        self
182    }
183}
184
185// endregion: DID Config
186
187// region: Security Level Config
188
189/// Configuration for a single security access level.
190///
191/// Mirrors a Security entry in an ODX file.
192#[derive(Debug, Clone)]
193pub struct SecurityLevelConfig {
194    /// Request Seed byte for this level (always odd: 0x01, 0x03, 0x05 ...).
195    pub level: u8,
196
197    /// Max failed key attempts before lockout is applied.
198    pub max_attempts: u8,
199
200    /// Duration of the lockout after exceeding max attempts.
201    pub lockout_duration: Duration,
202
203    /// Expected seed length in bytes.
204    pub seed_length: usize,
205
206    /// Expected key length in bytes.
207    pub key_length: usize,
208}
209
210// endregion: Security Level Config
211
212// region: Server Config
213
214/// Complete server configuration - mirrors what an ODX ECU description provides.
215///
216/// Constructed once (typically as a static or const) and referenced by the server state machine.
217/// All look-ups are O(n) over the small, fixed-size `heapless::Vec` collections - appropriate for
218/// the sizes involved.
219#[derive(Debug, Clone)]
220pub struct ServerConfig<
221    const MAX_SESSIONS: usize,
222    const MAX_SERVICES: usize,
223    const MAX_DIDS: usize,
224    const MAX_SECURITY_LEVELS: usize,
225> {
226    /// Physical address this server responds to.
227    pub physical_address: u16,
228
229    /// Functional (broadcast) address this server listens on.
230    pub functional_address: u16,
231
232    pub sessions: Vec<SessionConfig, MAX_SESSIONS>,
233    pub services: Vec<ServiceConfig, MAX_SERVICES>,
234    pub data_identifiers: Vec<DidConfig, MAX_DIDS>,
235    pub security_levels: Vec<SecurityLevelConfig, MAX_SECURITY_LEVELS>,
236}
237
238impl<
239        const MAX_SESSIONS: usize,
240        const MAX_SERVICES: usize,
241        const MAX_DIDS: usize,
242        const MAX_SECURITY_LEVELS: usize,
243    > ServerConfig<MAX_SESSIONS, MAX_SERVICES, MAX_DIDS, MAX_SECURITY_LEVELS>
244{
245    pub fn new(physical_address: u16, functional_address: u16) -> Self {
246        Self {
247            physical_address,
248            functional_address,
249            sessions: Vec::new(),
250            services: Vec::new(),
251            data_identifiers: Vec::new(),
252            security_levels: Vec::new(),
253        }
254    }
255
256    // region: Builder methods
257
258    pub fn with_session(mut self, s: SessionConfig) -> Self {
259        let _ = self.sessions.push(s);
260        self
261    }
262
263    pub fn with_service(mut self, s: ServiceConfig) -> Self {
264        let _ = self.services.push(s);
265        self
266    }
267
268    pub fn with_did(mut self, d: DidConfig) -> Self {
269        let _ = self.data_identifiers.push(d);
270        self
271    }
272
273    pub fn with_security_level(mut self, l: SecurityLevelConfig) -> Self {
274        let _ = self.security_levels.push(l);
275        self
276    }
277
278    // endregion: Builder methods
279
280    // region: Lookup helpers
281
282    pub fn find_session(&self, session_type: u8) -> Option<&SessionConfig> {
283        self.sessions
284            .iter()
285            .find(|s| s.session_type == session_type)
286    }
287
288    pub fn find_service(&self, service_id: u8) -> Option<&ServiceConfig> {
289        self.services.iter().find(|s| s.service_id == service_id)
290    }
291
292    pub fn find_did(&self, identifier: u16) -> Option<&DidConfig> {
293        self.data_identifiers
294            .iter()
295            .find(|d| d.identifier == identifier)
296    }
297
298    pub fn find_security_level(&self, level: u8) -> Option<&SecurityLevelConfig> {
299        self.security_levels.iter().find(|l| l.level == level)
300    }
301
302    pub fn service_allowed(&self, service_id: u8, session_type: u8) -> bool {
303        self.find_service(service_id)
304            .map(|s| s.supported_in.contains(&session_type))
305            .unwrap_or(false)
306    }
307
308    pub fn did_readable(&self, identifier: u16, session_type: u8) -> bool {
309        self.find_did(identifier)
310            .map(|s| s.readable_in.contains(&session_type))
311            .unwrap_or(false)
312    }
313
314    pub fn did_writable(&self, identifier: u16, session_type: u8) -> bool {
315        self.find_did(identifier)
316            .map(|s| s.writable_in.contains(&session_type))
317            .unwrap_or(false)
318    }
319
320    // endregion: Lookup helpers
321}
322
323// endregion: Server Config