timer_deque_rs/timer_portable/
timer.rs

1/*-
2 * timer-rs - a Rust crate which provides timer and timer queues based on target OS
3 *  functionality.
4 * 
5 * Copyright (C) 2025 Aleksandr Morozov
6 * 
7 * The timer-rs crate can be redistributed and/or modified
8 * under the terms of either of the following licenses:
9 *
10 *   1. the Mozilla Public License Version 2.0 (the “MPL”) OR
11 *                     
12 *   2. EUROPEAN UNION PUBLIC LICENCE v. 1.2 EUPL © the European Union 2007, 2016
13 */
14
15use std::{borrow::Cow, cmp::Ordering, fmt, io::{self}};
16
17use chrono::{DateTime, Local};
18use nix::libc::{self, ECANCELED, EWOULDBLOCK};
19use bitflags::bitflags;
20
21use crate::timer_portable::portable_error::TimerPortResult;
22
23#[cfg(target_os = "linux")]
24pub use super::linux::timer_fd_linux::*;
25
26#[cfg(any(
27    target_os = "freebsd",
28    target_os = "dragonfly",
29    target_os = "netbsd",
30    target_os = "openbsd",
31    target_os = "macos",
32))]
33pub use super::bsd::timer_kqueue_bsd::*;
34
35
36/// A timer type
37#[allow(non_camel_case_types)]
38#[derive(Debug)]
39pub enum TimerType
40{
41    /// A settable system-wide real-time clock.
42    CLOCK_REALTIME,
43
44    /// A nonsettable monotonically increasing clock that measures  time
45    /// from some unspecified point in the past that does not change af‐
46    /// ter system startup.
47
48    CLOCK_MONOTONIC,
49
50    /// Like CLOCK_MONOTONIC, this is a monotonically increasing  clock.
51    /// However,  whereas the CLOCK_MONOTONIC clock does not measure the
52    /// time while a system is suspended, the CLOCK_BOOTTIME clock  does
53    /// include  the time during which the system is suspended.  This is
54    /// useful  for  applications  that  need   to   be   suspend-aware.
55    /// CLOCK_REALTIME is not suitable for such applications, since that
56    /// clock is affected by discontinuous changes to the system clock.
57    CLOCK_BOOTTIME,
58
59    /// This clock is like CLOCK_REALTIME, but will wake the  system  if
60    /// it  is suspended.  The caller must have the CAP_WAKE_ALARM capa‐
61    /// bility in order to set a timer against this clock.
62    CLOCK_REALTIME_ALARM,
63
64    /// This clock is like CLOCK_BOOTTIME, but will wake the  system  if
65    /// it  is suspended.  The caller must have the CAP_WAKE_ALARM capa‐
66    /// bility in order to set a timer against this clock.
67    CLOCK_BOOTTIME_ALARM,
68}
69
70impl Into<libc::clockid_t> for TimerType 
71{
72    fn into(self) -> libc::clockid_t
73    {
74        match self
75        {
76            Self::CLOCK_REALTIME        => return libc::CLOCK_REALTIME,
77            Self::CLOCK_MONOTONIC       => return libc::CLOCK_MONOTONIC,
78            #[cfg(target_os = "linux")]
79            Self::CLOCK_BOOTTIME        => return libc::CLOCK_BOOTTIME,
80            #[cfg(target_os = "linux")]
81            Self::CLOCK_REALTIME_ALARM  => return libc::CLOCK_REALTIME_ALARM,
82            #[cfg(target_os = "linux")]
83            Self::CLOCK_BOOTTIME_ALARM  => return libc::CLOCK_BOOTTIME_ALARM,
84            #[cfg(any(
85                target_os = "freebsd",
86                target_os = "dragonfly",
87                target_os = "netbsd",
88                target_os = "openbsd",
89                target_os = "macos",
90            ))]
91            _ => return libc::CLOCK_REALTIME,
92        }
93    }
94}
95
96#[cfg(target_os = "linux")]
97bitflags! {     
98    /// Flags controling the type of the timer type.  
99    #[derive(Default)] 
100    pub struct TimerFlags: i32  
101    {     
102        /// Set the O_NONBLOCK file status flag on the open file  de‐
103        /// scription  (see  open(2)) referred to by the new file de‐
104        /// scriptor.  Using this flag saves extra calls to  fcntl(2)
105        /// to achieve the same result.
106        const TFD_NONBLOCK = libc::TFD_NONBLOCK;
107
108        /// Set  the  close-on-exec (FD_CLOEXEC) flag on the new file
109        /// descriptor.  See the description of the O_CLOEXEC flag in
110        /// open(2) for reasons why this may be useful.
111        const TFD_CLOEXEC = libc::TFD_CLOEXEC;
112    }
113}
114
115#[cfg(any(
116    target_os = "freebsd",
117    target_os = "dragonfly",
118    target_os = "netbsd",
119    target_os = "openbsd",
120    target_os = "macos",
121))]
122bitflags! {     
123    /// Flags controling the type of the timer type.   
124    #[derive(Default)] 
125    pub struct TimerFlags: i32  
126    {     
127        /// Not appliciable
128        const TFD_NONBLOCK = 0;
129
130        /// Not appliciable.
131        const TFD_CLOEXEC = 0;
132    }
133}
134
135#[cfg(target_os = "linux")]
136bitflags! {     
137    /// A bit mask that can include the values.
138    #[derive(Default)] 
139    pub struct TimerSetTimeFlags: i32  
140    {     
141        /// > Interpret  new_value.it_value as an absolute value on the timer's
142        /// > clock.  The timer will expire when the value of the timer's clock
143        /// > reaches the value specified in new_value.it_value.
144        const TFD_TIMER_ABSTIME = libc::TFD_TIMER_ABSTIME;
145
146        /// > If this flag is specified along with  TFD_TIMER_ABSTIME  and  the
147        /// > clock  for  this timer is CLOCK_REALTIME or CLOCK_REALTIME_ALARM,
148        /// > then mark this timer as cancelable if the real-time clock  under‐
149        /// > goes  a  discontinuous change (settimeofday(2), clock_settime(2),
150        /// > or similar).  When  such  changes  occur,  a  current  or  future
151        /// > read(2)  from  the file descriptor will fail with the error 
152        /// > ECANCELED.
153        const TFD_TIMER_CANCEL_ON_SET = (1 << 1);
154    }
155}
156
157#[cfg(any(
158    target_os = "freebsd",
159    target_os = "dragonfly",
160    target_os = "netbsd",
161    target_os = "openbsd",
162    target_os = "macos",
163))]
164
165bitflags! {     
166    /// A bit mask that can include the values. 
167    #[derive(Default)] 
168    pub struct TimerSetTimeFlags: i32  
169    {     
170        /// Not appliciable.
171        const TFD_TIMER_ABSTIME = 0;
172
173        /// Not appliciable.
174        const TFD_TIMER_CANCEL_ON_SET = 0;
175    }
176}
177
178/// A timer expiry modes. Not every OS supports 
179/// every mode. Read comments for the OS specific
180/// timer. For the `nanoseconds` param the max value
181/// is 999_999_999 for most OSes.
182#[derive(Clone, Copy, Debug, PartialEq, Eq)]
183pub enum TimerExpMode
184{
185    /// Disarmed
186    None, 
187
188    /// A timer which is triggered once.
189    OneShot
190    {
191        /// Seconds
192        sec: i64,
193
194        /// Nanoseconds. 
195        nsec: i64,
196    },
197    
198    /// Interval with the initial delay.
199    IntervalDelayed
200    {
201        /// First event delay seconds.
202        delay_sec: i64, 
203
204        /// First event delay nanoseconds. 
205        delay_nsec: i64,
206
207        /// Interval seconds.
208        interv_sec: i64,
209
210        /// Interval nanoseconds.
211        interv_nsec: i64
212    },
213
214    /// Interval, with the first timeout event 
215    /// equal to interval values.
216    Interval
217    {
218        /// Interval seconds.
219        sec: i64,
220
221        /// Interval nanoseconds.
222        nsec: i64,
223    },
224}
225
226impl Ord for TimerExpMode
227{
228    fn cmp(&self, other: &Self) -> Ordering 
229    {
230        match (self, other)
231        {
232            (TimerExpMode::None, TimerExpMode::None) => 
233                return Ordering::Equal,
234            (TimerExpMode::OneShot{ sec, nsec }, TimerExpMode::OneShot { sec: sec2, nsec: nsec2 }) => 
235            { 
236                let s1 = sec.cmp(sec2);
237                let s2 = nsec.cmp(nsec2);
238
239                return s1.then(s2);
240            },
241            (
242                TimerExpMode::IntervalDelayed
243                    { delay_sec, delay_nsec, interv_sec, interv_nsec }, 
244                TimerExpMode::IntervalDelayed 
245                    { delay_sec: delay_sec2, delay_nsec: delay_nsec2, interv_sec: interv_sec2, interv_nsec: interv_nsec2 }) => 
246            {
247                return 
248                    delay_sec.cmp(delay_sec2)
249                        .then(delay_nsec.cmp(delay_nsec2)
250                            .then(interv_sec.cmp(interv_sec2)
251                                .then(interv_nsec.cmp(interv_nsec2))
252                            )
253                        );
254            },
255            (TimerExpMode::Interval { sec, nsec }, TimerExpMode::Interval { sec: sec2, nsec: nsec2 }) => 
256            {
257                return sec.cmp(sec2).then(nsec.cmp(nsec2));
258            },
259            _ => 
260                panic!("cannot compare different types {} and {}", self, other)
261        }
262    }
263}
264
265impl PartialOrd for TimerExpMode
266{
267    fn partial_cmp(&self, other: &Self) -> Option<Ordering> 
268    {
269        return Some(self.cmp(other));
270    }
271}
272
273
274impl TimerExpMode
275{
276    /// Disarms the timer.
277    pub 
278    fn reset() -> Self
279    {
280        return Self::None;
281    }
282
283    /// Checks value for the `set_time` function. To disarm timer 
284    /// use `unset_time`.
285    pub 
286    fn is_valid(&self) -> bool
287    {
288        match *self
289        {
290            Self::None => 
291                return false,
292            Self::OneShot{ sec, nsec } => 
293                return sec != 0 || nsec != 0,
294            Self::IntervalDelayed{ delay_sec, delay_nsec, interv_sec, interv_nsec} => 
295                return (delay_sec != 0 || delay_nsec != 0) && (interv_sec != 0 || interv_nsec != 0),
296            Self::Interval{ sec, nsec } => 
297                return sec != 0 || nsec != 0
298        }
299    }
300}
301
302impl fmt::Display for TimerExpMode
303{
304    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result 
305    {
306        match self
307        {
308            Self::None => 
309                write!(f, "disarmed"),
310            Self::OneShot{ sec, nsec } => 
311                write!(f, "oneshot sec: {} nsec: {}", sec, nsec),
312            Self::IntervalDelayed
313                { delay_sec, delay_nsec, interv_sec, interv_nsec } => 
314                write!(f, "interval sec: {} nsec: {} with delay sec: {} nsec: {}",
315                    interv_sec, interv_nsec, delay_sec, delay_nsec),
316            Self::Interval{ sec, nsec } => 
317                write!(f, "interval sec: {} nsec: {}", sec, nsec),
318        }
319    }
320}
321
322#[cfg(any(
323    target_os = "freebsd",
324    target_os = "dragonfly",
325    target_os = "netbsd",
326    target_os = "openbsd",
327    target_os = "macos",
328    target_os = "linux",
329))]
330use nix::libc::{itimerspec, timespec};
331
332#[cfg(target_os = "macos")]
333#[repr(C)]
334pub struct timespec 
335{
336    pub tv_sec: libc::time_t,
337    pub tv_nsec: libc::c_long,
338}
339#[cfg(target_os = "macos")]
340#[repr(C)]
341pub struct itimerspec 
342{
343    pub it_interval: timespec,
344    pub it_value: timespec,
345}
346
347impl From<DateTime<Local>> for TimerExpMode
348{
349    fn from(value: DateTime<Local>) -> Self 
350    {
351         return Self::OneShot { sec: value.timestamp(), nsec: value.timestamp_subsec_nanos() as i64 };
352    }
353}
354
355impl From<itimerspec> for TimerExpMode
356{
357    fn from(value: itimerspec) -> Self 
358    {
359        if value.it_interval.tv_sec == 0 && value.it_interval.tv_nsec == 0 &&
360            value.it_value.tv_sec == 0 && value.it_value.tv_nsec == 0
361        {
362            // unset
363            return Self::None;
364        }
365        else if value.it_interval.tv_sec == 0 && value.it_interval.tv_nsec == 0
366        {
367            // one shot
368            return Self::OneShot{ sec: value.it_interval.tv_sec, nsec: value.it_interval.tv_nsec };
369        }
370        else if value.it_interval.tv_sec == value.it_value.tv_sec &&
371            value.it_interval.tv_nsec == value.it_value.tv_nsec
372        {
373            // interval
374            return Self::Interval { sec: value.it_value.tv_sec, nsec: value.it_value.tv_nsec };
375        }
376        else
377        {
378            // delayed interval
379            return 
380                Self::IntervalDelayed 
381                { 
382                    delay_sec: value.it_value.tv_sec, 
383                    delay_nsec: value.it_value.tv_nsec, 
384                    interv_sec: value.it_interval.tv_sec, 
385                    interv_nsec: value.it_interval.tv_nsec 
386                };
387        }
388    }
389}
390
391impl From<TimerExpMode> for itimerspec
392{
393    fn from(value: TimerExpMode) -> Self 
394    {
395        match value
396        {
397            TimerExpMode::None => 
398                return 
399                    itimerspec 
400                    {
401                        it_interval: timespec 
402                        {
403                            tv_sec: 0,
404                            tv_nsec: 0,
405                        },
406                        it_value: timespec
407                        {
408                            tv_sec: 0,
409                            tv_nsec: 0,
410                        },
411                    },
412            TimerExpMode::OneShot{ sec, nsec} =>
413                return 
414                    itimerspec 
415                    {
416                        it_interval: timespec 
417                        {
418                            tv_sec: 0,
419                            tv_nsec: 0,
420                        },
421                        it_value: timespec
422                        {
423                            tv_sec: sec,
424                            tv_nsec: nsec,
425                        },
426                    },
427            TimerExpMode::IntervalDelayed 
428                { delay_sec, delay_nsec, interv_sec, interv_nsec } => 
429                return 
430                    itimerspec 
431                    {
432                        it_interval: timespec 
433                        {
434                            tv_sec: interv_sec,
435                            tv_nsec: interv_nsec,
436                        },
437                        it_value: timespec
438                        {
439                            tv_sec: delay_sec,
440                            tv_nsec: delay_nsec,
441                        },
442                    },
443            TimerExpMode::Interval{ sec, nsec } => 
444                return 
445                    itimerspec 
446                    {
447                        it_interval: timespec 
448                        {
449                            tv_sec: sec,
450                            tv_nsec: nsec,
451                        },
452                        it_value: timespec
453                        {
454                            tv_sec: sec,
455                            tv_nsec: nsec,
456                        },
457                    }
458        }
459    }
460}
461
462/// A timer FD read result.
463#[derive(Debug, Clone, PartialEq, Eq)]
464pub enum TimerReadRes<T: Sized + fmt::Debug + fmt::Display + Clone + Eq + PartialEq>
465{
466    /// Read successfull.
467    Ok(T),
468
469    /// TFD_TIMER_ABSTIME 
470    /// > Marks this timer as cancelable if the real-time clock  under‐
471    /// > goes  a  discontinuous change (settimeofday(2), clock_settime(2),
472    /// > or similar).  When  such  changes  occur,  a  current  or  future
473    /// > read(2)  from  the file descriptor will fail with the error 
474    /// > ECANCELED.
475    Cancelled,
476
477    /// EAGAIN
478    /// If FD is nonblocking then this will be returned.
479    WouldBlock,
480}
481
482impl TimerReadRes<u64>
483{
484    pub 
485    fn ok() -> Self
486    {
487        return Self::Ok(1);
488    }
489}
490
491impl<T: Sized + fmt::Debug + fmt::Display + Clone + Eq + PartialEq> From<io::Error> for TimerReadRes<T>
492{
493    fn from(value: io::Error) -> Self 
494    {
495        if let Some(errn) = value.raw_os_error()
496        {
497            if errn == ECANCELED
498            {
499                return Self::Cancelled;
500            }
501            else if errn == EWOULDBLOCK
502            {
503                return Self::WouldBlock;
504            }
505        }
506
507        return Self::Cancelled;
508    }
509}
510
511impl<T: Sized + fmt::Debug + fmt::Display + Clone + Eq + PartialEq> fmt::Display for TimerReadRes<T>
512{
513    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result 
514    {
515        match self
516        {
517            Self::Ok(ovfl) => 
518                write!(f, "OK(overflows:{})", ovfl),
519            Self::Cancelled => 
520                write!(f, "CANCELLED"),
521            Self::WouldBlock => 
522                write!(f, "WOULDBLOCK"),
523        }
524    }
525}
526
527impl<T: Sized + fmt::Debug + fmt::Display + Clone + Eq + PartialEq> TimerReadRes<T>
528{
529    pub 
530    fn unwrap(self) -> T
531    {
532        let Self::Ok(t) = self
533        else { panic!("can not unwrap {:?}", self)};
534
535        return t;
536    }
537}
538
539/// A common trait which is implemented by all timer realizationss for different OSes.
540pub trait FdTimerCom
541{
542    /// Creates new isntance of the timer.
543    /// 
544    /// # Arguments
545    /// 
546    /// * `label` - a [Cow] string which defines a title of the timer for identification.
547    /// 
548    /// * `timer_type` - a [TimerType] a clock source
549    /// 
550    /// * `timer_flags` - a [TimerFlags] configuration of the timer's FD.
551    /// 
552    /// # Returns
553    /// 
554    /// A [Result] is retuned with instance on success.
555    fn new(label: Cow<'static, str>, timer_type: TimerType, timer_flags: TimerFlags) -> TimerPortResult<Self>
556    where Self: Sized;
557
558    /// Attempts to read the timer. The realization is different on different OS. The main purpose 
559    /// is to check if timer is ready (ended).
560    fn read(&self) -> TimerPortResult<TimerReadRes<u64>>;
561
562    /// The same as `read` but the buffer is external. The amount of read bytes are returned as
563    /// a result.
564    fn read_buf(&self, unfilled: &mut [u8]) -> std::io::Result<usize>;
565
566    /// Sets the timer. The timer starts immidiatly.
567    fn set_time(&self, flags: TimerSetTimeFlags, timer_exp: TimerExpMode) -> TimerPortResult<()>;
568
569    /// Unsets the timer. The timer stops immidiatly.
570    fn unset_time(&self) -> TimerPortResult<()>;
571}
572
573
574#[cfg(test)]
575mod tests
576{
577
578    use crate::{common, timer_portable::timer::TimerExpMode};
579
580    #[test]
581    fn test_0() 
582    {
583        let ts = common::get_current_timestamp();
584
585        let texp1 = TimerExpMode::from(ts);
586
587        assert_eq!(
588            TimerExpMode::OneShot { sec: ts.timestamp(), nsec: ts.timestamp_subsec_nanos() as i64 },
589            texp1
590        );
591        
592    }
593
594    #[test]
595    fn test_1() 
596    {
597        let ts = common::get_current_timestamp();
598
599        let texp1 = TimerExpMode::from(ts);
600        let texp2 = TimerExpMode::OneShot { sec: ts.timestamp()+1, nsec: ts.timestamp_subsec_nanos() as i64 };
601
602        assert_eq!(texp1 < texp2, true);
603        assert_eq!(texp2 > texp1, true);
604        assert_eq!(texp2 != texp1, true);
605        assert_eq!(texp2 < texp1, false);
606    }
607
608    #[test]
609    fn test_2() 
610    {
611        let ts = common::get_current_timestamp();
612
613        let texp1 = TimerExpMode::from(ts);
614        let (sec, nanos) = 
615            if ts.timestamp_subsec_nanos() == 999_999_999
616            {
617                (ts.timestamp()+1, 0)
618            }
619            else
620            {
621                (ts.timestamp(), ts.timestamp_subsec_nanos() + 1)
622            };
623        let texp2 = TimerExpMode::OneShot { sec: sec, nsec: nanos as i64 };
624
625        assert_eq!(texp1 < texp2, true);
626        assert_eq!(texp2 > texp1, true);
627        assert_eq!(texp2 != texp1, true);
628        assert_eq!(texp2 < texp1, false);
629    }
630
631    #[should_panic]
632    #[test]
633    fn test_2_fail() 
634    {
635        let ts = common::get_current_timestamp();
636
637        let texp1 = TimerExpMode::from(ts);
638        let texp2 = TimerExpMode::Interval { sec: 4, nsec: 4 };
639
640        assert_eq!(texp1 < texp2, true);
641    }
642}