uqa_client/notifications/
timing.rs1use super::ProtocolError;
10use std::num::NonZeroU64;
11
12#[derive(Clone, Copy, Debug, PartialEq, Eq)]
14pub struct TimerLimits {
15 maximum_timer_ms: NonZeroU64,
16 maximum_idle_ms: Option<NonZeroU64>,
17}
18
19impl TimerLimits {
20 pub const fn new(maximum_timer_ms: NonZeroU64, maximum_idle_ms: Option<NonZeroU64>) -> Self {
21 Self {
22 maximum_timer_ms,
23 maximum_idle_ms,
24 }
25 }
26}
27
28#[derive(Clone, Copy, Debug, PartialEq, Eq)]
29pub struct NotificationTiming {
30 heartbeat_interval: u64,
31 idle_timeout: u64,
32 timing_margin: u64,
33}
34
35impl NotificationTiming {
36 pub fn new(
37 heartbeat_interval_ms: u64,
38 idle_timeout_ms: u64,
39 timing_margin_ms: u64,
40 limits: TimerLimits,
41 ) -> Result<Self, ProtocolError> {
42 if [heartbeat_interval_ms, idle_timeout_ms, timing_margin_ms].contains(&0) {
43 return Err(ProtocolError::Timing);
44 }
45 if [heartbeat_interval_ms, idle_timeout_ms, timing_margin_ms]
46 .into_iter()
47 .any(|value| value > limits.maximum_timer_ms.get())
48 || limits
49 .maximum_idle_ms
50 .is_some_and(|limit| idle_timeout_ms > limit.get())
51 {
52 return Err(ProtocolError::TimerRange);
53 }
54 let minimum = heartbeat_interval_ms
55 .checked_mul(3)
56 .and_then(|value| value.checked_add(timing_margin_ms))
57 .ok_or(ProtocolError::Timing)?;
58 if idle_timeout_ms <= minimum {
59 return Err(ProtocolError::Timing);
60 }
61 Ok(Self {
62 heartbeat_interval: heartbeat_interval_ms,
63 idle_timeout: idle_timeout_ms,
64 timing_margin: timing_margin_ms,
65 })
66 }
67
68 pub const fn heartbeat_interval_ms(self) -> u64 {
69 self.heartbeat_interval
70 }
71 pub const fn idle_timeout_ms(self) -> u64 {
72 self.idle_timeout
73 }
74 pub const fn timing_margin_ms(self) -> u64 {
75 self.timing_margin
76 }
77}
78
79pub(super) fn decimal(value: &str) -> Option<u64> {
80 if value.is_empty()
81 || value.len() > 20
82 || value.starts_with('0')
83 || !value.bytes().all(|byte| byte.is_ascii_digit())
84 {
85 return None;
86 }
87 value.parse().ok()
88}