Skip to main content

ace_server/
config.rs

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