bacnet_mstp_core/config.rs
1//! Configuration and Clause 9 timing validation for the MS/TP master core.
2
3use core::fmt;
4
5use bacnet_mstp_codec::{
6 MAX_EXTENDED_FRAME_LENGTH, MAX_EXTENDED_MPDU_DATA, MAX_MASTER, MAX_STANDARD_FRAME_LENGTH,
7 MAX_STANDARD_MPDU_DATA,
8};
9use bacnet_time::Duration;
10
11const DEFAULT_BAUD_RATE: u32 = 9_600;
12/// Npoll (Clause 9.5.3): the token uses between two maintenance Poll For
13/// Master cycles, 50. Fixed by the standard, so not a configuration field.
14pub const N_POLL: u8 = 50;
15/// Nretry_token (Clause 9.5.3): how many times the core sends its Token
16/// again to a successor that shows no sign of using it before it looks for
17/// another, once. Fixed by the standard.
18pub const N_RETRY_TOKEN: u8 = 1;
19/// Nmin_octets (Clause 9.5.3): a station that hears more receive events
20/// (octets and receive errors) than this takes the line to be in use, 4.
21/// Fixed by the standard.
22pub const N_MIN_OCTETS: u8 = 4;
23/// T_no_token (Clause 9.5.3): the silence after which a station takes the
24/// token to be lost, 500 ms. Fixed by the standard.
25pub const T_NO_TOKEN: Duration = Duration::from_millis(500);
26/// T_slot (Clause 9.5.3): the width of each station's turn to generate a
27/// lost token, 10 ms. Fixed by the standard.
28pub const T_SLOT: Duration = Duration::from_millis(10);
29/// The longest T_frame_abort a station may use (Clause 9.5.3): 100 ms.
30/// [`MstpCoreConfig::validate`] refuses a longer one.
31pub const MAX_FRAME_ABORT: Duration = Duration::from_millis(100);
32/// The shortest T_reply_timeout a station may use (Clause 9.5.3): 255 ms,
33/// the baud-rate constructors' value. [`MstpCoreConfig::validate`] refuses a
34/// shorter one. No requester stops waiting for a reply sooner, so the core
35/// also uses it to bound how late its own answer may start.
36pub const MIN_REPLY_TIMEOUT: Duration = Duration::from_millis(255);
37/// The longest T_reply_timeout a station may use (Clause 9.5.3): 300 ms.
38/// [`MstpCoreConfig::validate`] refuses a longer one.
39pub const MAX_REPLY_TIMEOUT: Duration = Duration::from_millis(300);
40/// The shortest T_usage_timeout a station may use (Clause 9.5.3): 20 ms, the
41/// baud-rate constructors' value. [`MstpCoreConfig::validate`] refuses a
42/// shorter one. No poller stops waiting for the answer to its poll sooner,
43/// so the core also uses it to bound how late its own answer may start.
44pub const MIN_USAGE_TIMEOUT: Duration = Duration::from_millis(20);
45/// The longest T_usage_timeout a station may use (Clause 9.5.3): 35 ms.
46/// [`MstpCoreConfig::validate`] refuses a longer one.
47pub const MAX_USAGE_TIMEOUT: Duration = Duration::from_millis(35);
48/// The longest T_reply_delay (Clause 9.5.3), the constructors' value.
49const MAX_REPLY_DELAY: Duration = Duration::from_millis(250);
50/// The longest T_usage_delay (Clause 9.5.3), the constructors' value.
51const MAX_USAGE_DELAY: Duration = Duration::from_millis(15);
52/// Default for [`MstpCoreConfig::starvation_limit`], which queues also use
53/// until a core hands them the configured one.
54pub(crate) const DEFAULT_STARVATION_LIMIT: u8 = 8;
55/// Default for [`MstpCoreConfig::reply_transmit_margin`], the value the host
56/// MS/TP transport reserves.
57const DEFAULT_REPLY_TRANSMIT_MARGIN: Duration = Duration::from_millis(25);
58/// Default for [`MstpCoreConfig::transmit_watchdog`] where it clears the
59/// longest frame.
60const DEFAULT_TRANSMIT_WATCHDOG: Duration = Duration::from_secs(1);
61/// Kept in [`MstpCoreConfig::minimum_transmit_watchdog`] beyond the longest
62/// frame and T_postdrive, for the adapter to report the completion.
63const WATCHDOG_COMPLETION_MARGIN: Duration = Duration::from_millis(1);
64/// Default for [`MstpCoreConfig::late_deadline`].
65pub const DEFAULT_LATE_DEADLINE: Duration = Duration::from_millis(1);
66
67/// A configuration value that violates a protocol or local safety bound.
68#[derive(Debug, Clone, Copy, PartialEq, Eq)]
69#[non_exhaustive]
70pub enum ConfigError {
71 /// A baud rate of zero cannot be used to derive bit-time limits.
72 ZeroBaudRate,
73 /// A derived timing calculation overflowed its arithmetic.
74 TimingOverflow,
75 /// Max_Master is a seven-bit station address.
76 MaxMasterOutOfRange {
77 /// Supplied Max_Master value.
78 value: u8,
79 },
80 /// This station cannot be beyond Max_Master.
81 StationExceedsMaxMaster {
82 /// Local station address.
83 station: u8,
84 /// Configured Max_Master value.
85 max_master: u8,
86 },
87 /// Max_Info_Frames must be in the inclusive 1..=255 range.
88 MaxInfoFramesOutOfRange {
89 /// Supplied Max_Info_Frames value.
90 value: u8,
91 },
92 /// T_frame_abort is outside the 60-bit-time through 100 ms range.
93 FrameAbortOutOfRange {
94 /// Supplied frame-abort duration.
95 value: Duration,
96 /// Minimum permitted duration.
97 minimum: Duration,
98 /// Maximum permitted duration.
99 maximum: Duration,
100 },
101 /// T_frame_gap is longer than 20 bit times.
102 FrameGapTooLong {
103 /// Supplied frame-gap duration.
104 value: Duration,
105 /// Maximum permitted duration.
106 maximum: Duration,
107 },
108 /// T_postdrive is longer than 15 bit times.
109 PostdriveTooLong {
110 /// Supplied postdrive duration.
111 value: Duration,
112 /// Maximum permitted duration.
113 maximum: Duration,
114 },
115 /// T_reply_delay cannot exceed 250 ms.
116 ReplyDelayTooLong {
117 /// Supplied reply-delay duration.
118 value: Duration,
119 /// Maximum permitted duration.
120 maximum: Duration,
121 },
122 /// The reply transmit margin must leave part of T_reply_delay for the
123 /// application to answer.
124 ReplyMarginTooLong {
125 /// Supplied margin.
126 margin: Duration,
127 /// Configured T_reply_delay.
128 reply_delay: Duration,
129 },
130 /// T_reply_timeout must be between 255 and 300 ms.
131 ReplyTimeoutOutOfRange {
132 /// Supplied reply-timeout duration.
133 value: Duration,
134 /// Minimum permitted duration.
135 minimum: Duration,
136 /// Maximum permitted duration.
137 maximum: Duration,
138 },
139 /// T_turnaround must cover at least 40 bit times.
140 TurnaroundTooShort {
141 /// Supplied turnaround duration.
142 value: Duration,
143 /// Minimum permitted duration.
144 minimum: Duration,
145 },
146 /// T_turnaround cannot exceed the maximum T_usage_delay response window.
147 TurnaroundExceedsUsageDelay {
148 /// Supplied turnaround duration.
149 turnaround: Duration,
150 /// Configured usage-delay duration.
151 usage_delay: Duration,
152 },
153 /// T_usage_delay cannot exceed 15 ms.
154 UsageDelayTooLong {
155 /// Supplied usage-delay duration.
156 value: Duration,
157 /// Maximum permitted duration.
158 maximum: Duration,
159 },
160 /// T_usage_timeout must be between 20 and 35 ms.
161 UsageTimeoutOutOfRange {
162 /// Supplied usage-timeout duration.
163 value: Duration,
164 /// Minimum permitted duration.
165 minimum: Duration,
166 /// Maximum permitted duration.
167 maximum: Duration,
168 },
169 /// A local queue age or watchdog of zero would disable the safety bound.
170 ZeroLocalDuration {
171 /// Local duration setting that was zero.
172 field: LocalDurationField,
173 },
174 /// A local scheduling ratio or starvation limit of zero is invalid.
175 ZeroLocalLimit {
176 /// Local count or ratio setting that was zero.
177 field: LocalLimitField,
178 },
179 /// Multiple-frame ratio is a percentage and cannot exceed 100.
180 MultipleFrameRatioOutOfRange {
181 /// Supplied percentage value.
182 value: u8,
183 },
184 /// The transmit watchdog would abort the longest frame this
185 /// configuration sends before the driver could go off: a standard frame
186 /// of 511 octets takes 1.06 s at 4800 baud, and an extended one of 1516
187 /// octets 1.58 s at 9600. See
188 /// [`MstpCoreConfig::minimum_transmit_watchdog`]; the baud-rate
189 /// constructors and [`MstpCoreConfig::with_extended_frames`] set one
190 /// that clears it.
191 TransmitWatchdogTooShort {
192 /// Supplied watchdog.
193 value: Duration,
194 /// The shortest watchdog accepted,
195 /// [`MstpCoreConfig::minimum_transmit_watchdog`].
196 minimum: Duration,
197 },
198 /// [`MstpCoreConfig::extended_frames`] is on, but a buffer the core was
199 /// given can't hold the largest extended frame or NPDU. Only
200 /// [`MasterCore::new`](crate::MasterCore::new) and
201 /// [`MasterCore::from_parts`](crate::MasterCore::from_parts) report it,
202 /// since only they see the buffers.
203 ExtendedStorageTooSmall {
204 /// The buffer that is too small.
205 buffer: CoreBuffer,
206 /// Its size in octets.
207 length: usize,
208 /// The size extended frames need: the longest extended frame on the
209 /// wire for the receive storage and the transmit frame, the longest
210 /// NPDU one carries for a delivery slot and a queue slot.
211 minimum: usize,
212 },
213 /// The receive storage the core was given can't hold even the shortest
214 /// frame. Only [`MasterCore::new`](crate::MasterCore::new) and
215 /// [`MasterCore::from_parts`](crate::MasterCore::from_parts) report it.
216 ReceiveStorageTooSmall {
217 /// Supplied storage length.
218 length: usize,
219 /// [`MIN_RX_STORAGE`](crate::MIN_RX_STORAGE).
220 minimum: usize,
221 },
222}
223
224impl fmt::Display for ConfigError {
225 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
226 match *self {
227 Self::ZeroBaudRate => f.write_str("the baud rate is zero"),
228 Self::TimingOverflow => f.write_str("a bit-time bound overflowed"),
229 Self::MaxMasterOutOfRange { value } => {
230 write!(f, "Max_Master {value} is over {MAX_MASTER}")
231 }
232 Self::StationExceedsMaxMaster {
233 station,
234 max_master,
235 } => write!(f, "station {station} is over Max_Master {max_master}"),
236 Self::MaxInfoFramesOutOfRange { value } => {
237 write!(f, "Max_Info_Frames {value} is not 1 to 255")
238 }
239 Self::FrameAbortOutOfRange {
240 value,
241 minimum,
242 maximum,
243 } => write!(
244 f,
245 "T_frame_abort {value:?} is not {minimum:?} to {maximum:?}"
246 ),
247 Self::FrameGapTooLong { value, maximum } => {
248 write!(f, "T_frame_gap {value:?} is over {maximum:?}")
249 }
250 Self::PostdriveTooLong { value, maximum } => {
251 write!(f, "T_postdrive {value:?} is over {maximum:?}")
252 }
253 Self::ReplyDelayTooLong { value, maximum } => {
254 write!(f, "T_reply_delay {value:?} is over {maximum:?}")
255 }
256 Self::ReplyMarginTooLong {
257 margin,
258 reply_delay,
259 } => write!(
260 f,
261 "the reply transmit margin {margin:?} leaves nothing of T_reply_delay {reply_delay:?}"
262 ),
263 Self::ReplyTimeoutOutOfRange {
264 value,
265 minimum,
266 maximum,
267 } => write!(
268 f,
269 "T_reply_timeout {value:?} is not {minimum:?} to {maximum:?}"
270 ),
271 Self::TurnaroundTooShort { value, minimum } => {
272 write!(f, "T_turnaround {value:?} is under {minimum:?}")
273 }
274 Self::TurnaroundExceedsUsageDelay {
275 turnaround,
276 usage_delay,
277 } => write!(
278 f,
279 "T_turnaround {turnaround:?} is over T_usage_delay {usage_delay:?}"
280 ),
281 Self::UsageDelayTooLong { value, maximum } => {
282 write!(f, "T_usage_delay {value:?} is over {maximum:?}")
283 }
284 Self::UsageTimeoutOutOfRange {
285 value,
286 minimum,
287 maximum,
288 } => write!(
289 f,
290 "T_usage_timeout {value:?} is not {minimum:?} to {maximum:?}"
291 ),
292 Self::ZeroLocalDuration { field } => write!(f, "{field} is zero"),
293 Self::ZeroLocalLimit { field } => write!(f, "{field} is zero"),
294 Self::MultipleFrameRatioOutOfRange { value } => {
295 write!(f, "multiple_frame_ratio {value} is over 100")
296 }
297 Self::TransmitWatchdogTooShort { value, minimum } => write!(
298 f,
299 "the transmit watchdog {value:?} is under {minimum:?}, the longest frame's"
300 ),
301 Self::ExtendedStorageTooSmall {
302 buffer,
303 length,
304 minimum,
305 } => write!(
306 f,
307 "the {buffer} holds {length} octets; extended frames need {minimum}"
308 ),
309 Self::ReceiveStorageTooSmall { length, minimum } => write!(
310 f,
311 "receive storage of {length} octets is under the {minimum} of the shortest frame"
312 ),
313 }
314 }
315}
316
317impl core::error::Error for ConfigError {}
318
319/// The buffers [`ConfigError::ExtendedStorageTooSmall`] names.
320#[derive(Debug, Clone, Copy, PartialEq, Eq)]
321#[non_exhaustive]
322pub enum CoreBuffer {
323 /// The receive storage, which must hold [`MAX_EXTENDED_FRAME_LENGTH`]
324 /// octets.
325 ReceiveStorage,
326 /// The [`ActionBuffers`](crate::ActionBuffers) frame buffer, its `FRAME`,
327 /// which must hold as much.
328 TransmitFrame,
329 /// Each [`ActionBuffers`](crate::ActionBuffers) delivery slot, its
330 /// `NPDU`, which must hold
331 /// [`MAX_EXTENDED_MPDU_DATA`]
332 /// octets.
333 DeliverySlot,
334 /// Each queue slot, as
335 /// [`QueueStorage::max_npdu`](crate::QueueStorage::max_npdu)
336 /// reports it, which must hold as much.
337 QueueSlot,
338}
339
340/// Names of local duration settings used by [`ConfigError::ZeroLocalDuration`].
341#[derive(Debug, Clone, Copy, PartialEq, Eq)]
342#[non_exhaustive]
343pub enum LocalDurationField {
344 /// [`MstpCoreConfig::queue_max_age`].
345 QueueMaxAge,
346 /// [`MstpCoreConfig::transmit_watchdog`].
347 TransmitWatchdog,
348}
349
350/// Names of local count/ratio settings used by [`ConfigError::ZeroLocalLimit`].
351#[derive(Debug, Clone, Copy, PartialEq, Eq)]
352#[non_exhaustive]
353pub enum LocalLimitField {
354 /// [`MstpCoreConfig::broadcast_unicast_ratio`].
355 BroadcastUnicastRatio,
356 /// [`MstpCoreConfig::multiple_frame_ratio`].
357 MultipleFrameRatio,
358 /// [`MstpCoreConfig::starvation_limit`].
359 StarvationLimit,
360}
361
362/// The buffer, as an error message names it.
363impl fmt::Display for CoreBuffer {
364 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
365 f.write_str(match self {
366 Self::ReceiveStorage => "receive storage",
367 Self::TransmitFrame => "transmit frame buffer",
368 Self::DeliverySlot => "delivery slot",
369 Self::QueueSlot => "queue slot",
370 })
371 }
372}
373
374/// The field's name.
375impl fmt::Display for LocalDurationField {
376 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
377 f.write_str(match self {
378 Self::QueueMaxAge => "queue_max_age",
379 Self::TransmitWatchdog => "transmit_watchdog",
380 })
381 }
382}
383
384/// The field's name.
385impl fmt::Display for LocalLimitField {
386 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
387 f.write_str(match self {
388 Self::BroadcastUnicastRatio => "broadcast_unicast_ratio",
389 Self::MultipleFrameRatio => "multiple_frame_ratio",
390 Self::StarvationLimit => "starvation_limit",
391 })
392 }
393}
394
395/// All timing, addressing, and bounded-queue parameters needed by an MS/TP
396/// master.
397///
398/// Durations are `bacnet-time` [`Duration`]s, in nanoseconds. Values whose
399/// limits are stated in bit times are still stored as absolute durations so a
400/// core instance can keep absolute deadlines without retaining a platform
401/// clock or baud-rate conversion helper. The bit-time values the constructors
402/// derive from the baud rate, and the bounds [`Self::validate`] checks them
403/// against, are whole microseconds (see [`Self::octet_time`]).
404///
405/// The values Clause 9.5.3 fixes are constants, not fields: [`N_POLL`],
406/// [`N_RETRY_TOKEN`], [`N_MIN_OCTETS`], [`T_NO_TOKEN`] and [`T_SLOT`].
407///
408/// It is `#[non_exhaustive]`, so a new setting isn't a breaking change:
409/// start from [`Self::STANDARD`] or [`Self::for_baud_rate`] and set the
410/// fields to change, then let [`MasterCore::new`](crate::MasterCore::new)
411/// or [`Self::validate`] check the result.
412///
413/// ```
414/// use bacnet_mstp_core::MstpCoreConfig;
415///
416/// const CONFIG: MstpCoreConfig = {
417/// let mut config = MstpCoreConfig::STANDARD;
418/// config.station = 3;
419/// config.max_master = 15;
420/// config
421/// };
422/// assert_eq!(CONFIG.validate(), Ok(()));
423/// ```
424#[derive(Debug, Clone, Copy, PartialEq, Eq)]
425#[non_exhaustive]
426pub struct MstpCoreConfig {
427 /// This station's MAC address.
428 pub station: u8,
429 /// Max_Master, inclusive.
430 pub max_master: u8,
431 /// Max_Info_Frames, the number of information frames per token use.
432 pub max_info_frames: u8,
433 /// Serial baud rate used to validate bit-time-derived limits.
434 pub baud_rate: u32,
435
436 /// T_frame_abort.
437 pub t_frame_abort: Duration,
438 /// T_frame_gap.
439 pub t_frame_gap: Duration,
440 /// T_postdrive.
441 pub t_postdrive: Duration,
442 /// T_reply_delay: the longest a reply to a data-expecting-reply frame may
443 /// take to start.
444 pub t_reply_delay: Duration,
445 /// T_reply_timeout.
446 pub t_reply_timeout: Duration,
447 /// T_turnaround.
448 pub t_turnaround: Duration,
449 /// T_usage_delay.
450 pub t_usage_delay: Duration,
451 /// T_usage_timeout.
452 pub t_usage_timeout: Duration,
453
454 /// Time kept back from T_reply_delay for the platform to get a reply onto
455 /// the wire.
456 ///
457 /// The core waits for the application's answer until T_reply_delay less
458 /// this margin, then sends Reply Postponed itself, so that adapter and
459 /// UART latency still lets the frame start within T_reply_delay. It must
460 /// be shorter than T_reply_delay. Default 25 ms.
461 pub reply_transmit_margin: Duration,
462 /// The lateness at which a serviced deadline counts in
463 /// [`MstpCounters::late_deadlines`](crate::MstpCounters::late_deadlines):
464 /// a deadline serviced this long or longer after it fell due counts,
465 /// one serviced sooner doesn't. Every serviced deadline counts in the
466 /// lateness histogram whatever this is.
467 ///
468 /// A local setting, which nothing in the core's timing depends on. Set
469 /// it from the adapter's timer: lateness under its resolution is
470 /// ordinary, not a sign of load. Default [`DEFAULT_LATE_DEADLINE`],
471 /// 1 ms, for an adapter with a fine timer; a host whose timer rounds
472 /// sleeps to the millisecond sets more.
473 pub late_deadline: Duration,
474 /// Maximum time an item may remain in a local transmit queue.
475 pub queue_max_age: Duration,
476 /// Number of broadcast frames scheduled per unicast frame while both
477 /// classes remain eligible.
478 pub broadcast_unicast_ratio: u8,
479 /// Percentage of `max_info_frames` usable during one token hold.
480 ///
481 /// The effective limit is rounded up and is always at least one. A value
482 /// of 100 leaves Clause 9 `Max_Info_Frames` unchanged.
483 pub multiple_frame_ratio: u8,
484 /// Number of service opportunities after which an item is promoted.
485 ///
486 /// The one source of the queues' starvation limit:
487 /// [`MasterCore::new`](crate::MasterCore::new) hands it to them through
488 /// [`QueueStorage::set_starvation_limit`](crate::QueueStorage::set_starvation_limit).
489 /// Default 8.
490 pub starvation_limit: u8,
491 /// Maximum time allowed for one platform transmit operation, from its
492 /// confirmed start to its completion.
493 ///
494 /// It must clear [`Self::minimum_transmit_watchdog`], the longest
495 /// frame's time on the wire plus T_postdrive, or validation refuses it
496 /// with [`ConfigError::TransmitWatchdogTooShort`]. The baud-rate
497 /// constructors give one second, or, where the longest standard frame
498 /// would not fit in that (below about 5.1 kbaud), the longest frame's
499 /// time plus one second.
500 pub transmit_watchdog: Duration,
501
502 /// Take and send extended frames, Types 32 and 33, which carry NPDUs of
503 /// 502 to 1497 octets COBS-encoded (Clauses 9.3.9, 9.3.10). Off by
504 /// default.
505 ///
506 /// Clause 9.3 requires COBS-encoded frames of a routing node and leaves
507 /// them optional for any other node, so a router turns this on for
508 /// every MS/TP port it routes and a device leaves it off unless it wants
509 /// the larger NPDUs.
510 ///
511 /// - **Off**, the core is a node without COBS support. It refuses an
512 /// NPDU over 501 octets, and an extended frame it hears counts as an
513 /// invalid frame when it is for this station or broadcast.
514 /// - **On**, a Type 32 for this station is a data request like a Type 5,
515 /// and a Type 33 data like a Type 6, a reply to this station's request
516 /// included; broadcasts of either go up as for Types 5 and 6. A
517 /// reserved COBS type (34-127) is checked and, with a good CRC-32K,
518 /// taken as a valid frame of a type the core doesn't know (Clause
519 /// 9.5.4.9). An NPDU of 502 to 1497 octets, queued or as a reply, goes
520 /// out as Type 32 when it expects a reply and Type 33 otherwise.
521 /// [`MasterCore::new`](crate::MasterCore::new) then needs buffers
522 /// sized for extended frames, and refuses smaller ones with
523 /// [`ConfigError::ExtendedStorageTooSmall`]. The transmit watchdog
524 /// must clear the longest extended frame
525 /// ([`ConfigError::TransmitWatchdogTooShort`]);
526 /// [`Self::with_extended_frames`] sets both.
527 pub extended_frames: bool,
528}
529
530/// [`MstpCoreConfig::STANDARD`].
531impl Default for MstpCoreConfig {
532 fn default() -> Self {
533 Self::STANDARD
534 }
535}
536
537impl MstpCoreConfig {
538 /// The standard settings at 9600 baud: station 0, Max_Master 127, one
539 /// information frame per token use, the bit-time timers derived from the
540 /// baud rate, the shortest T_reply_timeout and T_usage_timeout Clause
541 /// 9.5.3 allows, the longest T_reply_delay and T_usage_delay, and the
542 /// local defaults below; extended frames off.
543 pub const STANDARD: Self = Self::for_baud_rate_unchecked(DEFAULT_BAUD_RATE);
544
545 /// The standard settings at `baud_rate`, as [`Self::STANDARD`] has them
546 /// at 9600, validated.
547 ///
548 /// # Errors
549 ///
550 /// [`ConfigError::ZeroBaudRate`] for zero, and what [`Self::validate`]
551 /// refuses at a rate too low for Clause 9 timing (below 2667 baud).
552 pub const fn for_baud_rate(baud_rate: u32) -> Result<Self, ConfigError> {
553 if baud_rate == 0 {
554 return Err(ConfigError::ZeroBaudRate);
555 }
556 let config = Self::for_baud_rate_unchecked(baud_rate);
557 match config.validate() {
558 Ok(()) => Ok(config),
559 Err(error) => Err(error),
560 }
561 }
562
563 /// The time one octet takes on the wire: ten bit times, rounded up to a
564 /// whole microsecond.
565 ///
566 /// Every time derived from the baud rate is a whole number of
567 /// microseconds, rounded up for a lower bound and down for an upper one:
568 /// the octet time, T_frame_abort, T_frame_gap, T_postdrive and
569 /// T_turnaround, and validation's bounds on them. That keeps the
570 /// arithmetic in 32 bits, with no 64-bit division on a 32-bit target.
571 #[must_use]
572 pub const fn octet_time(&self) -> Duration {
573 or_max(ceil_bit_times(self.baud_rate, 10))
574 }
575
576 /// Validate all protocol and local bounds.
577 ///
578 /// # Errors
579 ///
580 /// The first bound the configuration breaks, as a [`ConfigError`].
581 pub const fn validate(&self) -> Result<(), ConfigError> {
582 if self.baud_rate == 0 {
583 return Err(ConfigError::ZeroBaudRate);
584 }
585 if self.max_master > MAX_MASTER {
586 return Err(ConfigError::MaxMasterOutOfRange {
587 value: self.max_master,
588 });
589 }
590 if self.station > self.max_master {
591 return Err(ConfigError::StationExceedsMaxMaster {
592 station: self.station,
593 max_master: self.max_master,
594 });
595 }
596 if self.max_info_frames == 0 {
597 return Err(ConfigError::MaxInfoFramesOutOfRange {
598 value: self.max_info_frames,
599 });
600 }
601
602 let (Some(frame_abort_min), Some(frame_gap_max), Some(postdrive_max), Some(turnaround_min)) = (
603 ceil_bit_times(self.baud_rate, 60),
604 floor_bit_times(self.baud_rate, 20),
605 floor_bit_times(self.baud_rate, 15),
606 ceil_bit_times(self.baud_rate, 40),
607 ) else {
608 return Err(ConfigError::TimingOverflow);
609 };
610 if shorter(self.t_frame_abort, frame_abort_min)
611 || longer(self.t_frame_abort, MAX_FRAME_ABORT)
612 {
613 return Err(ConfigError::FrameAbortOutOfRange {
614 value: self.t_frame_abort,
615 minimum: frame_abort_min,
616 maximum: MAX_FRAME_ABORT,
617 });
618 }
619 if longer(self.t_frame_gap, frame_gap_max) {
620 return Err(ConfigError::FrameGapTooLong {
621 value: self.t_frame_gap,
622 maximum: frame_gap_max,
623 });
624 }
625 if longer(self.t_postdrive, postdrive_max) {
626 return Err(ConfigError::PostdriveTooLong {
627 value: self.t_postdrive,
628 maximum: postdrive_max,
629 });
630 }
631 if longer(self.t_reply_delay, MAX_REPLY_DELAY) {
632 return Err(ConfigError::ReplyDelayTooLong {
633 value: self.t_reply_delay,
634 maximum: MAX_REPLY_DELAY,
635 });
636 }
637 if !shorter(self.reply_transmit_margin, self.t_reply_delay) {
638 return Err(ConfigError::ReplyMarginTooLong {
639 margin: self.reply_transmit_margin,
640 reply_delay: self.t_reply_delay,
641 });
642 }
643 if shorter(self.t_reply_timeout, MIN_REPLY_TIMEOUT)
644 || longer(self.t_reply_timeout, MAX_REPLY_TIMEOUT)
645 {
646 return Err(ConfigError::ReplyTimeoutOutOfRange {
647 value: self.t_reply_timeout,
648 minimum: MIN_REPLY_TIMEOUT,
649 maximum: MAX_REPLY_TIMEOUT,
650 });
651 }
652 if shorter(self.t_turnaround, turnaround_min) {
653 return Err(ConfigError::TurnaroundTooShort {
654 value: self.t_turnaround,
655 minimum: turnaround_min,
656 });
657 }
658 if longer(self.t_turnaround, self.t_usage_delay) {
659 return Err(ConfigError::TurnaroundExceedsUsageDelay {
660 turnaround: self.t_turnaround,
661 usage_delay: self.t_usage_delay,
662 });
663 }
664 if longer(self.t_usage_delay, MAX_USAGE_DELAY) {
665 return Err(ConfigError::UsageDelayTooLong {
666 value: self.t_usage_delay,
667 maximum: MAX_USAGE_DELAY,
668 });
669 }
670 if shorter(self.t_usage_timeout, MIN_USAGE_TIMEOUT)
671 || longer(self.t_usage_timeout, MAX_USAGE_TIMEOUT)
672 {
673 return Err(ConfigError::UsageTimeoutOutOfRange {
674 value: self.t_usage_timeout,
675 minimum: MIN_USAGE_TIMEOUT,
676 maximum: MAX_USAGE_TIMEOUT,
677 });
678 }
679 if self.queue_max_age.is_zero() {
680 return Err(ConfigError::ZeroLocalDuration {
681 field: LocalDurationField::QueueMaxAge,
682 });
683 }
684 if self.transmit_watchdog.is_zero() {
685 return Err(ConfigError::ZeroLocalDuration {
686 field: LocalDurationField::TransmitWatchdog,
687 });
688 }
689 if shorter(self.transmit_watchdog, self.minimum_transmit_watchdog()) {
690 return Err(ConfigError::TransmitWatchdogTooShort {
691 value: self.transmit_watchdog,
692 minimum: self.minimum_transmit_watchdog(),
693 });
694 }
695 if self.broadcast_unicast_ratio == 0 {
696 return Err(ConfigError::ZeroLocalLimit {
697 field: LocalLimitField::BroadcastUnicastRatio,
698 });
699 }
700 if self.multiple_frame_ratio == 0 {
701 return Err(ConfigError::ZeroLocalLimit {
702 field: LocalLimitField::MultipleFrameRatio,
703 });
704 }
705 if self.multiple_frame_ratio > 100 {
706 return Err(ConfigError::MultipleFrameRatioOutOfRange {
707 value: self.multiple_frame_ratio,
708 });
709 }
710 if self.starvation_limit == 0 {
711 return Err(ConfigError::ZeroLocalLimit {
712 field: LocalLimitField::StarvationLimit,
713 });
714 }
715 Ok(())
716 }
717
718 const fn for_baud_rate_unchecked(baud_rate: u32) -> Self {
719 let config = Self {
720 station: 0,
721 max_master: MAX_MASTER,
722 max_info_frames: 1,
723 baud_rate,
724 t_frame_abort: or_max(ceil_bit_times(baud_rate, 60)),
725 t_frame_gap: or_max(floor_bit_times(baud_rate, 20)),
726 t_postdrive: or_max(floor_bit_times(baud_rate, 15)),
727 t_reply_delay: MAX_REPLY_DELAY,
728 t_reply_timeout: MIN_REPLY_TIMEOUT,
729 t_turnaround: or_max(ceil_bit_times(baud_rate, 40)),
730 t_usage_delay: MAX_USAGE_DELAY,
731 t_usage_timeout: MIN_USAGE_TIMEOUT,
732 reply_transmit_margin: DEFAULT_REPLY_TRANSMIT_MARGIN,
733 late_deadline: DEFAULT_LATE_DEADLINE,
734 queue_max_age: Duration::from_secs(5),
735 broadcast_unicast_ratio: 1,
736 multiple_frame_ratio: 100,
737 starvation_limit: DEFAULT_STARVATION_LIMIT,
738 transmit_watchdog: DEFAULT_TRANSMIT_WATCHDOG,
739 extended_frames: false,
740 };
741 config.with_covering_watchdog()
742 }
743
744 /// The longest NPDU this configuration sends or takes: 1497 octets with
745 /// [`Self::extended_frames`] on (Clauses 9.3.9, 9.3.10), else 501
746 /// (Clauses 9.3.6, 9.3.7).
747 #[must_use]
748 pub const fn max_npdu(&self) -> usize {
749 if self.extended_frames {
750 MAX_EXTENDED_MPDU_DATA
751 } else {
752 MAX_STANDARD_MPDU_DATA
753 }
754 }
755
756 /// The time the longest frame this configuration sends takes on the
757 /// wire: [`MAX_EXTENDED_FRAME_LENGTH`] octets with
758 /// [`Self::extended_frames`] on, else [`MAX_STANDARD_FRAME_LENGTH`].
759 #[must_use]
760 pub const fn longest_frame_time(&self) -> Duration {
761 let octets = if self.extended_frames {
762 MAX_EXTENDED_FRAME_LENGTH
763 } else {
764 MAX_STANDARD_FRAME_LENGTH
765 };
766 // At most 1516 octets, so the cast is exact.
767 self.octet_time().saturating_mul(octets as u32)
768 }
769
770 /// The shortest [`Self::transmit_watchdog`] validation accepts: the
771 /// longest frame's time on the wire ([`Self::longest_frame_time`]) plus
772 /// T_postdrive, which the watchdog's span from the confirmed start to
773 /// completion also covers, plus 1 ms for the adapter to report the
774 /// completion, the default lateness at which the core counts a deadline
775 /// late ([`DEFAULT_LATE_DEADLINE`]). A watchdog at this floor
776 /// leaves nothing more for a slow adapter; the constructors' defaults
777 /// leave a second or more.
778 #[must_use]
779 pub const fn minimum_transmit_watchdog(&self) -> Duration {
780 self.longest_frame_time()
781 .saturating_add(self.t_postdrive)
782 .saturating_add(WATCHDOG_COMPLETION_MARGIN)
783 }
784
785 /// This configuration with [`Self::extended_frames`] on, and a transmit
786 /// watchdog that clears the longest extended frame: the one it has if
787 /// that clears it already, otherwise that frame's time on the wire plus
788 /// one second.
789 #[must_use]
790 pub const fn with_extended_frames(mut self) -> Self {
791 self.extended_frames = true;
792 self.with_covering_watchdog()
793 }
794
795 /// This configuration, with a transmit watchdog that clears
796 /// [`Self::minimum_transmit_watchdog`]: the one it has if it does, else
797 /// the longest frame's time plus the default one second.
798 const fn with_covering_watchdog(mut self) -> Self {
799 if self.transmit_watchdog.as_nanos() < self.minimum_transmit_watchdog().as_nanos() {
800 self.transmit_watchdog = self
801 .longest_frame_time()
802 .saturating_add(DEFAULT_TRANSMIT_WATCHDOG);
803 }
804 self
805 }
806}
807
808/// `bits` bit times at `baud_rate`, rounded up to a whole microsecond, for
809/// minimum bounds. `None` for a zero baud rate or on overflow.
810///
811/// The arithmetic is 32-bit: every caller asks for at most 60 bit times,
812/// whose 60,000,000 microsecond-bits fit a `u32`, so the result is exact and
813/// a 32-bit target links no 64-bit division routine for it. Nanosecond steps
814/// would need 60,000,000,000, past a `u32`, and whole microseconds keep each
815/// value what it was before the core counted nanoseconds.
816const fn ceil_bit_times(baud_rate: u32, bits: u32) -> Option<Duration> {
817 match bits.checked_mul(1_000_000) {
818 Some(numerator) if baud_rate != 0 => {
819 Some(Duration::from_micros(numerator.div_ceil(baud_rate) as u64))
820 }
821 _ => None,
822 }
823}
824
825/// `bits` bit times at `baud_rate`, rounded down to a whole microsecond, for
826/// maximum bounds. `None` for a zero baud rate or on overflow. 32-bit, as
827/// [`ceil_bit_times`] is.
828const fn floor_bit_times(baud_rate: u32, bits: u32) -> Option<Duration> {
829 match bits.checked_mul(1_000_000) {
830 Some(numerator) => match numerator.checked_div(baud_rate) {
831 Some(micros) => Some(Duration::from_micros(micros as u64)),
832 None => None,
833 },
834 None => None,
835 }
836}
837
838/// Whether `value` is shorter than `bound`: `<`, which a `const fn` can't
839/// call on [`Duration`].
840const fn shorter(value: Duration, bound: Duration) -> bool {
841 value.as_nanos() < bound.as_nanos()
842}
843
844/// Whether `value` is longer than `bound`.
845const fn longer(value: Duration, bound: Duration) -> bool {
846 value.as_nanos() > bound.as_nanos()
847}
848
849/// A bit-time default for [`MstpCoreConfig::for_baud_rate_unchecked`]: a
850/// zero baud rate gives [`Duration::MAX`], which `validate` then rejects.
851const fn or_max(duration: Option<Duration>) -> Duration {
852 match duration {
853 Some(duration) => duration,
854 None => Duration::MAX,
855 }
856}
857
858#[cfg(test)]
859mod tests;