Skip to main content

zerodds_time_service/
service.rs

1// SPDX-License-Identifier: Apache-2.0
2// Copyright 2026 ZeroDDS Contributors
3//! TimeService Interface — OMG Time Service 1.1 §2.1.
4//!
5//! Spec-IDL:
6//! ```idl
7//! interface TimeService {
8//!     UTO universal_time() raises(TimeUnavailable);
9//!     UTO secure_universal_time() raises(TimeUnavailable);
10//!     UTO new_universal_time(in TimeT, in InaccuracyT, in TdfT);
11//!     UTO uto_from_utc(in UtcT);
12//!     TIO new_interval(in TimeT lower, in TimeT upper);
13//! };
14//! ```
15//!
16//! Wir bilden das ohne CORBA-Interface ab — die Operations werden als
17//! Methoden auf der [`TimeService`]-Struct realisiert.
18
19use core::fmt;
20
21#[cfg(feature = "std")]
22use crate::time_base::current_time;
23use crate::time_base::{InaccuracyT, IntervalT, TdfT, TimeT, UtcT};
24use crate::tio::Tio;
25use crate::uto::Uto;
26
27/// Spec §1.3.3.1 — `TimeUnavailable`-Exception.
28#[derive(Debug, Clone, Copy, PartialEq, Eq)]
29pub struct TimeUnavailable;
30
31impl fmt::Display for TimeUnavailable {
32    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
33        write!(f, "underlying time service unavailable")
34    }
35}
36
37#[cfg(feature = "std")]
38impl std::error::Error for TimeUnavailable {}
39
40/// `TimeService`-Object — Spec §2.1. ZeroDDS-Implementation als
41/// plain Rust-Struct (kein CORBA-Object).
42#[derive(Debug, Clone, Copy, Default)]
43pub struct TimeService {
44    /// Tdf, der bei `universal_time()` in den UTOs gesetzt wird.
45    /// Default 0 (Greenwich).
46    pub default_tdf: TdfT,
47    /// Inaccuracy, die bei `universal_time()` in den UTOs angegeben
48    /// wird. Default 0 (Spec erlaubt Implementations, ihre eigene
49    /// Inaccuracy zu kennen).
50    pub default_inaccuracy: InaccuracyT,
51    /// Wenn `true`, dann ist die zugrundeliegende Zeitquelle als
52    /// "secure" markiert (Spec §2.1.2 + Appendix A). Sonst wirft
53    /// `secure_universal_time()` `TimeUnavailable`.
54    pub secure_source: bool,
55}
56
57impl TimeService {
58    /// Spec §2.1.1 — `universal_time()`. Liefert die aktuelle Zeit.
59    /// Raises `TimeUnavailable`, wenn die Time-Source nicht
60    /// verfuegbar ist.
61    ///
62    /// # Errors
63    /// `TimeUnavailable` wenn `current_time()` 0 zurueckliefert
64    /// (z.B. no_std ohne Real-Clock).
65    #[cfg(feature = "std")]
66    pub fn universal_time(&self) -> Result<Uto, TimeUnavailable> {
67        let now = current_time();
68        if now == 0 {
69            return Err(TimeUnavailable);
70        }
71        Ok(Uto::from_utc(UtcT::new(
72            now,
73            self.default_inaccuracy,
74            self.default_tdf,
75        )))
76    }
77
78    /// Spec §2.1.2 — `secure_universal_time()`. Liefert Zeit nur, wenn
79    /// die Time-Source als "secure" konfiguriert ist (Spec Appendix A).
80    ///
81    /// # Errors
82    /// `TimeUnavailable` wenn `secure_source = false` oder die
83    /// Time-Source nicht verfuegbar ist.
84    #[cfg(feature = "std")]
85    pub fn secure_universal_time(&self) -> Result<Uto, TimeUnavailable> {
86        if !self.secure_source {
87            return Err(TimeUnavailable);
88        }
89        self.universal_time()
90    }
91
92    /// Spec §2.1.2.1 — `new_universal_time(time, inaccuracy, tdf)`.
93    ///
94    /// # Errors
95    /// Spec sagt `CORBA::BAD_PARAM` bei out-of-range Inaccuracy. Wir
96    /// kappen statt dessen still auf 48 bit (siehe [`UtcT::new`]).
97    #[must_use]
98    pub fn new_universal_time(time: TimeT, inaccuracy: InaccuracyT, tdf: TdfT) -> Uto {
99        Uto::new(time, inaccuracy, tdf)
100    }
101
102    /// Spec §2.1.2.2 — `uto_from_utc(utc)`.
103    #[must_use]
104    pub fn uto_from_utc(utc: UtcT) -> Uto {
105        Uto::from_utc(utc)
106    }
107
108    /// Spec §2.1.2.3 — `new_interval(lower, upper)`. Raises
109    /// `CORBA::BAD_PARAM` wenn `lower > upper`. Wir liefern `None`.
110    #[must_use]
111    pub fn new_interval(lower: TimeT, upper: TimeT) -> Option<Tio> {
112        IntervalT::new(lower, upper).map(Tio::from_interval)
113    }
114}
115
116#[cfg(test)]
117#[allow(clippy::expect_used)]
118mod tests {
119    use super::*;
120
121    #[test]
122    fn new_universal_time_creates_uto_from_components() {
123        // Spec §2.1.2.1.
124        let uto = TimeService::new_universal_time(1_000, 50, 60);
125        assert_eq!(uto.time(), 1_000);
126        assert_eq!(uto.inaccuracy(), 50);
127        assert_eq!(uto.tdf(), 60);
128    }
129
130    #[test]
131    fn uto_from_utc_wraps_passed_struct() {
132        // Spec §2.1.2.2.
133        let utc = UtcT::new(100, 0, 0);
134        let uto = TimeService::uto_from_utc(utc);
135        assert_eq!(uto.utc_time(), utc);
136    }
137
138    #[test]
139    fn new_interval_rejects_lower_greater_than_upper() {
140        // Spec §2.1.2.3 — BAD_PARAM bei lower > upper.
141        assert!(TimeService::new_interval(200, 100).is_none());
142    }
143
144    #[test]
145    fn new_interval_creates_tio_for_valid_bounds() {
146        let tio = TimeService::new_interval(100, 200).expect("ok");
147        assert_eq!(tio.time_interval().lower_bound, 100);
148        assert_eq!(tio.time_interval().upper_bound, 200);
149    }
150
151    #[cfg(feature = "std")]
152    #[test]
153    fn universal_time_returns_recent_value() {
154        let service = TimeService::default();
155        let uto = service.universal_time().expect("ok");
156        // Spec §2.1.1 — Time muss > 0 sein und im plausiblen Bereich
157        // (post-2020, pre-2200).
158        assert!(uto.time() > 130_000_000_000_000_000);
159    }
160
161    #[cfg(feature = "std")]
162    #[test]
163    fn secure_universal_time_fails_when_source_not_marked_secure() {
164        // Spec §2.1.2 — secure source = false -> TimeUnavailable.
165        let service = TimeService::default();
166        assert_eq!(service.secure_universal_time(), Err(TimeUnavailable));
167    }
168
169    #[cfg(feature = "std")]
170    #[test]
171    fn secure_universal_time_returns_when_source_marked_secure() {
172        let service = TimeService {
173            secure_source: true,
174            ..TimeService::default()
175        };
176        assert!(service.secure_universal_time().is_ok());
177    }
178
179    #[test]
180    fn time_unavailable_display_describes_failure_mode() {
181        let s = alloc::format!("{TimeUnavailable}");
182        assert!(s.contains("time service unavailable"));
183    }
184}