satrs_core/pus/
scheduler.rs

1//! # PUS Service 11 Scheduling Module
2//!
3//! The core data structure of this module is the [PusScheduler]. This structure can be used
4//! to perform the scheduling of telecommands like specified in the ECSS standard.
5use core::fmt::{Debug, Display, Formatter};
6use core::time::Duration;
7#[cfg(feature = "serde")]
8use serde::{Deserialize, Serialize};
9use spacepackets::ecss::scheduling::TimeWindowType;
10use spacepackets::ecss::tc::{GenericPusTcSecondaryHeader, IsPusTelecommand, PusTcReader};
11use spacepackets::ecss::{PusError, PusPacket, WritablePusPacket};
12use spacepackets::time::{
13    CcsdsTimeProvider, TimeReader, TimeWriter, TimestampError, UnixTimestamp,
14};
15use spacepackets::{ByteConversionError, CcsdsPacket};
16#[cfg(feature = "std")]
17use std::error::Error;
18
19use crate::pool::{PoolProvider, StoreError};
20#[cfg(feature = "alloc")]
21pub use alloc_mod::*;
22
23/// This is the request ID as specified in ECSS-E-ST-70-41C 5.4.11.2 of the standard.
24///
25/// This version of the request ID is used to identify scheduled commands and also contains
26/// the source ID found in the secondary header of PUS telecommands.
27#[derive(Debug, Copy, Clone, PartialEq, Eq)]
28#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
29pub struct RequestId {
30    pub(crate) source_id: u16,
31    pub(crate) apid: u16,
32    pub(crate) seq_count: u16,
33}
34
35impl RequestId {
36    pub fn source_id(&self) -> u16 {
37        self.source_id
38    }
39
40    pub fn apid(&self) -> u16 {
41        self.apid
42    }
43
44    pub fn seq_count(&self) -> u16 {
45        self.seq_count
46    }
47
48    pub fn from_tc(
49        tc: &(impl CcsdsPacket + GenericPusTcSecondaryHeader + IsPusTelecommand),
50    ) -> Self {
51        RequestId {
52            source_id: tc.source_id(),
53            apid: tc.apid(),
54            seq_count: tc.seq_count(),
55        }
56    }
57
58    pub fn as_u64(&self) -> u64 {
59        ((self.source_id as u64) << 32) | ((self.apid as u64) << 16) | self.seq_count as u64
60    }
61}
62
63pub type AddrInStore = u64;
64
65/// This is the format stored internally by the TC scheduler for each scheduled telecommand.
66/// It consists of a generic address for that telecommand in the TC pool and a request ID.
67#[derive(Debug, Copy, Clone, PartialEq, Eq)]
68#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
69pub struct TcInfo {
70    addr: AddrInStore,
71    request_id: RequestId,
72}
73
74impl TcInfo {
75    pub fn addr(&self) -> AddrInStore {
76        self.addr
77    }
78
79    pub fn request_id(&self) -> RequestId {
80        self.request_id
81    }
82
83    pub fn new(addr: u64, request_id: RequestId) -> Self {
84        TcInfo { addr, request_id }
85    }
86}
87
88pub struct TimeWindow<TimeProvder> {
89    time_window_type: TimeWindowType,
90    start_time: Option<TimeProvder>,
91    end_time: Option<TimeProvder>,
92}
93
94impl<TimeProvider> TimeWindow<TimeProvider> {
95    pub fn new_select_all() -> Self {
96        Self {
97            time_window_type: TimeWindowType::SelectAll,
98            start_time: None,
99            end_time: None,
100        }
101    }
102
103    pub fn time_window_type(&self) -> TimeWindowType {
104        self.time_window_type
105    }
106
107    pub fn start_time(&self) -> Option<&TimeProvider> {
108        self.start_time.as_ref()
109    }
110
111    pub fn end_time(&self) -> Option<&TimeProvider> {
112        self.end_time.as_ref()
113    }
114}
115
116impl<TimeProvider: CcsdsTimeProvider + Clone> TimeWindow<TimeProvider> {
117    pub fn new_from_time_to_time(start_time: &TimeProvider, end_time: &TimeProvider) -> Self {
118        Self {
119            time_window_type: TimeWindowType::TimeTagToTimeTag,
120            start_time: Some(start_time.clone()),
121            end_time: Some(end_time.clone()),
122        }
123    }
124
125    pub fn new_from_time(start_time: &TimeProvider) -> Self {
126        Self {
127            time_window_type: TimeWindowType::FromTimeTag,
128            start_time: Some(start_time.clone()),
129            end_time: None,
130        }
131    }
132
133    pub fn new_to_time(end_time: &TimeProvider) -> Self {
134        Self {
135            time_window_type: TimeWindowType::ToTimeTag,
136            start_time: None,
137            end_time: Some(end_time.clone()),
138        }
139    }
140}
141
142#[derive(Debug, Clone, PartialEq, Eq)]
143#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
144pub enum ScheduleError {
145    PusError(PusError),
146    /// The release time is within the time-margin added on top of the current time.
147    /// The first parameter is the current time, the second one the time margin, and the third one
148    /// the release time.
149    ReleaseTimeInTimeMargin {
150        current_time: UnixTimestamp,
151        time_margin: Duration,
152        release_time: UnixTimestamp,
153    },
154    /// Nested time-tagged commands are not allowed.
155    NestedScheduledTc,
156    StoreError(StoreError),
157    TcDataEmpty,
158    TimestampError(TimestampError),
159    WrongSubservice(u8),
160    WrongService(u8),
161    ByteConversionError(ByteConversionError),
162}
163
164impl Display for ScheduleError {
165    fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
166        match self {
167            ScheduleError::PusError(e) => {
168                write!(f, "Pus Error: {e}")
169            }
170            ScheduleError::ReleaseTimeInTimeMargin {
171                current_time,
172                time_margin,
173                release_time,
174            } => {
175                write!(
176                    f,
177                    "time margin too short, current time: {current_time:?}, time margin: {time_margin:?}, release time: {release_time:?}"
178                )
179            }
180            ScheduleError::NestedScheduledTc => {
181                write!(f, "nested scheduling is not allowed")
182            }
183            ScheduleError::StoreError(e) => {
184                write!(f, "pus scheduling: {e}")
185            }
186            ScheduleError::TcDataEmpty => {
187                write!(f, "empty TC data field")
188            }
189            ScheduleError::TimestampError(e) => {
190                write!(f, "pus scheduling: {e}")
191            }
192            ScheduleError::WrongService(srv) => {
193                write!(f, "pus scheduling: wrong service number {srv}")
194            }
195            ScheduleError::WrongSubservice(subsrv) => {
196                write!(f, "pus scheduling: wrong subservice number {subsrv}")
197            }
198            ScheduleError::ByteConversionError(e) => {
199                write!(f, "pus scheduling: {e}")
200            }
201        }
202    }
203}
204
205impl From<PusError> for ScheduleError {
206    fn from(e: PusError) -> Self {
207        Self::PusError(e)
208    }
209}
210
211impl From<StoreError> for ScheduleError {
212    fn from(e: StoreError) -> Self {
213        Self::StoreError(e)
214    }
215}
216
217impl From<TimestampError> for ScheduleError {
218    fn from(e: TimestampError) -> Self {
219        Self::TimestampError(e)
220    }
221}
222impl From<ByteConversionError> for ScheduleError {
223    fn from(e: ByteConversionError) -> Self {
224        Self::ByteConversionError(e)
225    }
226}
227
228#[cfg(feature = "std")]
229impl Error for ScheduleError {
230    fn source(&self) -> Option<&(dyn Error + 'static)> {
231        match self {
232            ScheduleError::PusError(e) => Some(e),
233            ScheduleError::StoreError(e) => Some(e),
234            ScheduleError::TimestampError(e) => Some(e),
235            ScheduleError::ByteConversionError(e) => Some(e),
236            _ => None,
237        }
238    }
239}
240
241/// Generic trait for scheduler objects which are able to schedule ECSS PUS C packets.
242pub trait PusSchedulerProvider {
243    type TimeProvider: CcsdsTimeProvider + TimeReader;
244
245    fn reset(&mut self, store: &mut (impl PoolProvider + ?Sized)) -> Result<(), StoreError>;
246
247    fn is_enabled(&self) -> bool;
248
249    fn enable(&mut self);
250
251    /// A disabled scheduler should still delete commands where the execution time has been reached
252    /// but should not release them to be executed.
253    fn disable(&mut self);
254
255    /// Insert a telecommand which was already unwrapped from the outer Service 11 packet and stored
256    /// inside the telecommand packet pool.
257    fn insert_unwrapped_and_stored_tc(
258        &mut self,
259        time_stamp: UnixTimestamp,
260        info: TcInfo,
261    ) -> Result<(), ScheduleError>;
262
263    /// Insert a telecommand based on the fully wrapped time-tagged telecommand. The timestamp
264    /// provider needs to be supplied via a generic.
265    fn insert_wrapped_tc<TimeProvider>(
266        &mut self,
267        pus_tc: &(impl IsPusTelecommand + PusPacket + GenericPusTcSecondaryHeader),
268        pool: &mut (impl PoolProvider + ?Sized),
269    ) -> Result<TcInfo, ScheduleError> {
270        if PusPacket::service(pus_tc) != 11 {
271            return Err(ScheduleError::WrongService(PusPacket::service(pus_tc)));
272        }
273        if PusPacket::subservice(pus_tc) != 4 {
274            return Err(ScheduleError::WrongSubservice(PusPacket::subservice(
275                pus_tc,
276            )));
277        }
278        if pus_tc.user_data().is_empty() {
279            return Err(ScheduleError::TcDataEmpty);
280        }
281        let user_data = pus_tc.user_data();
282        let stamp: Self::TimeProvider = TimeReader::from_bytes(user_data)?;
283        let unix_stamp = stamp.unix_stamp();
284        let stamp_len = stamp.len_as_bytes();
285        self.insert_unwrapped_tc(unix_stamp, &user_data[stamp_len..], pool)
286    }
287
288    /// Insert a telecommand which was already unwrapped from the outer Service 11 packet but still
289    /// needs to be stored inside the telecommand pool.
290    fn insert_unwrapped_tc(
291        &mut self,
292        time_stamp: UnixTimestamp,
293        tc: &[u8],
294        pool: &mut (impl PoolProvider + ?Sized),
295    ) -> Result<TcInfo, ScheduleError> {
296        let check_tc = PusTcReader::new(tc)?;
297        if PusPacket::service(&check_tc.0) == 11 && PusPacket::subservice(&check_tc.0) == 4 {
298            return Err(ScheduleError::NestedScheduledTc);
299        }
300        let req_id = RequestId::from_tc(&check_tc.0);
301
302        match pool.add(tc) {
303            Ok(addr) => {
304                let info = TcInfo::new(addr, req_id);
305                self.insert_unwrapped_and_stored_tc(time_stamp, info)?;
306                Ok(info)
307            }
308            Err(err) => Err(err.into()),
309        }
310    }
311}
312
313/// Helper function to generate the application data for a PUS telecommand to insert an
314/// activity into a time-based schedule according to ECSS-E-ST-70-41C 8.11.2.4
315///
316/// Please note that the N field is set to a [u16] unsigned bytefield with the value 1.
317pub fn generate_insert_telecommand_app_data(
318    buf: &mut [u8],
319    release_time: &impl TimeWriter,
320    request: &impl WritablePusPacket,
321) -> Result<usize, ScheduleError> {
322    let required_len = 2 + release_time.len_written() + request.len_written();
323    if required_len > buf.len() {
324        return Err(ByteConversionError::ToSliceTooSmall {
325            found: buf.len(),
326            expected: required_len,
327        }
328        .into());
329    }
330    let mut current_len = 0;
331    let n = 1_u16;
332    buf[current_len..current_len + 2].copy_from_slice(&n.to_be_bytes());
333    current_len += 2;
334    current_len += release_time
335        .write_to_bytes(&mut buf[current_len..current_len + release_time.len_written()])?;
336    current_len +=
337        request.write_to_bytes(&mut buf[current_len..current_len + request.len_written()])?;
338    Ok(current_len)
339}
340
341#[cfg(feature = "alloc")]
342pub mod alloc_mod {
343    use super::*;
344    use crate::pool::{PoolProvider, StoreAddr, StoreError};
345    use alloc::collections::btree_map::{Entry, Range};
346    use alloc::collections::BTreeMap;
347    use alloc::vec;
348    use alloc::vec::Vec;
349    use core::time::Duration;
350    use spacepackets::ecss::scheduling::TimeWindowType;
351    use spacepackets::ecss::tc::{PusTc, PusTcReader};
352    use spacepackets::ecss::PusPacket;
353    use spacepackets::time::cds::DaysLen24Bits;
354    use spacepackets::time::{cds, CcsdsTimeProvider, UnixTimestamp};
355
356    #[cfg(feature = "std")]
357    use std::time::SystemTimeError;
358
359    /// This function is similar to [generate_insert_telecommand_app_data] but returns the application
360    /// data as a [alloc::vec::Vec].
361    pub fn generate_insert_telecommand_app_data_as_vec(
362        release_time: &impl TimeWriter,
363        request: &impl WritablePusPacket,
364    ) -> Result<alloc::vec::Vec<u8>, ScheduleError> {
365        let mut vec = alloc::vec::Vec::new();
366        vec.extend_from_slice(&1_u16.to_be_bytes());
367        vec.append(&mut release_time.to_vec()?);
368        vec.append(&mut request.to_vec()?);
369        Ok(vec)
370    }
371
372    enum DeletionResult {
373        WithoutStoreDeletion(Option<StoreAddr>),
374        WithStoreDeletion(Result<bool, StoreError>),
375    }
376
377    /// This is the core data structure for scheduling PUS telecommands with [alloc] support.
378    ///
379    /// It is assumed that the actual telecommand data is stored in a separate TC pool offering
380    /// a [crate::pool::PoolProvider] API. This data structure just tracks the store
381    /// addresses and their release times and offers a convenient API to insert and release
382    /// telecommands and perform other functionality specified by the ECSS standard in section 6.11.
383    /// The time is tracked as a [spacepackets::time::UnixTimestamp] but the only requirement to
384    /// the timekeeping of the user is that it is convertible to that timestamp.
385    ///
386    /// The standard also specifies that the PUS scheduler can be enabled and disabled.
387    /// A disabled scheduler should still delete commands where the execution time has been reached
388    /// but should not release them to be executed.
389    ///
390    /// The implementation uses an ordered map internally with the release timestamp being the key.
391    /// This allows efficient time based insertions and extractions which should be the primary use-case
392    /// for a time-based command scheduler.
393    /// There is no way to avoid duplicate [RequestId]s during insertion, which can occur even if the
394    /// user always correctly increment for sequence counter due to overflows. To avoid this issue,
395    /// it can make sense to split up telecommand groups by the APID to avoid overflows.
396    ///
397    /// Currently, sub-schedules and groups are not supported.
398    #[derive(Debug)]
399    pub struct PusScheduler {
400        tc_map: BTreeMap<UnixTimestamp, Vec<TcInfo>>,
401        pub(crate) current_time: UnixTimestamp,
402        time_margin: Duration,
403        enabled: bool,
404    }
405    impl PusScheduler {
406        /// Create a new PUS scheduler.
407        ///
408        /// # Arguments
409        ///
410        /// * `init_current_time` - The time to initialize the scheduler with.
411        /// * `time_margin` - This time margin is used when inserting new telecommands into the
412        ///      schedule. If the release time of a new telecommand is earlier than the time margin
413        ///      added to the current time, it will not be inserted into the schedule.
414        /// * `tc_buf_size` - Buffer for temporary storage of telecommand packets. This buffer
415        ///      should be large enough to accomodate the largest expected TC packets.
416        pub fn new(init_current_time: UnixTimestamp, time_margin: Duration) -> Self {
417            PusScheduler {
418                tc_map: Default::default(),
419                current_time: init_current_time,
420                time_margin,
421                enabled: true,
422            }
423        }
424
425        /// Like [Self::new], but sets the `init_current_time` parameter to the current system time.
426        #[cfg(feature = "std")]
427        #[cfg_attr(doc_cfg, doc(cfg(feature = "std")))]
428        pub fn new_with_current_init_time(time_margin: Duration) -> Result<Self, SystemTimeError> {
429            Ok(Self::new(UnixTimestamp::from_now()?, time_margin))
430        }
431
432        pub fn num_scheduled_telecommands(&self) -> u64 {
433            let mut num_entries = 0;
434            for entries in &self.tc_map {
435                num_entries += entries.1.len() as u64;
436            }
437            num_entries
438        }
439
440        pub fn update_time(&mut self, current_time: UnixTimestamp) {
441            self.current_time = current_time;
442        }
443
444        pub fn current_time(&self) -> &UnixTimestamp {
445            &self.current_time
446        }
447
448        /// Insert a telecommand which was already unwrapped from the outer Service 11 packet and stored
449        /// inside the telecommand packet pool.
450        pub fn insert_unwrapped_and_stored_tc(
451            &mut self,
452            time_stamp: UnixTimestamp,
453            info: TcInfo,
454        ) -> Result<(), ScheduleError> {
455            if time_stamp < self.current_time + self.time_margin {
456                return Err(ScheduleError::ReleaseTimeInTimeMargin {
457                    current_time: self.current_time,
458                    time_margin: self.time_margin,
459                    release_time: time_stamp,
460                });
461            }
462            match self.tc_map.entry(time_stamp) {
463                Entry::Vacant(e) => {
464                    e.insert(vec![info]);
465                }
466                Entry::Occupied(mut v) => {
467                    v.get_mut().push(info);
468                }
469            }
470            Ok(())
471        }
472
473        /// Insert a telecommand which was already unwrapped from the outer Service 11 packet but still
474        /// needs to be stored inside the telecommand pool.
475        pub fn insert_unwrapped_tc(
476            &mut self,
477            time_stamp: UnixTimestamp,
478            tc: &[u8],
479            pool: &mut (impl PoolProvider + ?Sized),
480        ) -> Result<TcInfo, ScheduleError> {
481            let check_tc = PusTcReader::new(tc)?;
482            if PusPacket::service(&check_tc.0) == 11 && PusPacket::subservice(&check_tc.0) == 4 {
483                return Err(ScheduleError::NestedScheduledTc);
484            }
485            let req_id = RequestId::from_tc(&check_tc.0);
486
487            match pool.add(tc) {
488                Ok(addr) => {
489                    let info = TcInfo::new(addr, req_id);
490                    self.insert_unwrapped_and_stored_tc(time_stamp, info)?;
491                    Ok(info)
492                }
493                Err(err) => Err(err.into()),
494            }
495        }
496
497        /// Insert a telecommand based on the fully wrapped time-tagged telecommand using a CDS
498        /// short timestamp with 16-bit length of days field.
499        pub fn insert_wrapped_tc_cds_short(
500            &mut self,
501            pus_tc: &PusTc,
502            pool: &mut (impl PoolProvider + ?Sized),
503        ) -> Result<TcInfo, ScheduleError> {
504            self.insert_wrapped_tc::<cds::TimeProvider>(pus_tc, pool)
505        }
506
507        /// Insert a telecommand based on the fully wrapped time-tagged telecommand using a CDS
508        /// long timestamp with a 24-bit length of days field.
509        pub fn insert_wrapped_tc_cds_long(
510            &mut self,
511            pus_tc: &PusTc,
512            pool: &mut (impl PoolProvider + ?Sized),
513        ) -> Result<TcInfo, ScheduleError> {
514            self.insert_wrapped_tc::<cds::TimeProvider<DaysLen24Bits>>(pus_tc, pool)
515        }
516
517        /// This function uses [Self::retrieve_by_time_filter] to extract all scheduled commands inside
518        /// the time range and then deletes them from the provided store.
519        ///
520        /// Like specified in the documentation of [Self::retrieve_by_time_filter], the range extraction
521        /// for deletion is always inclusive.
522        ///
523        /// This function returns the number of deleted commands on success. In case any deletion fails,
524        /// the last deletion will be supplied in addition to the number of deleted commands.
525        pub fn delete_by_time_filter<TimeProvider: CcsdsTimeProvider + Clone>(
526            &mut self,
527            time_window: TimeWindow<TimeProvider>,
528            pool: &mut (impl PoolProvider + ?Sized),
529        ) -> Result<u64, (u64, StoreError)> {
530            let range = self.retrieve_by_time_filter(time_window);
531            let mut del_packets = 0;
532            let mut res_if_fails = None;
533            let mut keys_to_delete = Vec::new();
534            for time_bucket in range {
535                for tc in time_bucket.1 {
536                    match pool.delete(tc.addr) {
537                        Ok(_) => del_packets += 1,
538                        Err(e) => res_if_fails = Some(e),
539                    }
540                }
541                keys_to_delete.push(*time_bucket.0);
542            }
543            for key in keys_to_delete {
544                self.tc_map.remove(&key);
545            }
546            if let Some(err) = res_if_fails {
547                return Err((del_packets, err));
548            }
549            Ok(del_packets)
550        }
551
552        /// Deletes all the scheduled commands. This also deletes the packets from the passed TC pool.
553        ///
554        /// This function returns the number of deleted commands on success. In case any deletion fails,
555        /// the last deletion will be supplied in addition to the number of deleted commands.
556        pub fn delete_all(
557            &mut self,
558            pool: &mut (impl PoolProvider + ?Sized),
559        ) -> Result<u64, (u64, StoreError)> {
560            self.delete_by_time_filter(TimeWindow::<cds::TimeProvider>::new_select_all(), pool)
561        }
562
563        /// Retrieve a range over all scheduled commands.
564        pub fn retrieve_all(&mut self) -> Range<'_, UnixTimestamp, Vec<TcInfo>> {
565            self.tc_map.range(..)
566        }
567
568        /// This retrieves scheduled telecommands which are inside the provided time window.
569        ///
570        /// It should be noted that the ranged extraction is always inclusive. For example, a range
571        /// from 50 to 100 unix seconds would also include command scheduled at 100 unix seconds.
572        pub fn retrieve_by_time_filter<TimeProvider: CcsdsTimeProvider>(
573            &mut self,
574            time_window: TimeWindow<TimeProvider>,
575        ) -> Range<'_, UnixTimestamp, Vec<TcInfo>> {
576            match time_window.time_window_type() {
577                TimeWindowType::SelectAll => self.tc_map.range(..),
578                TimeWindowType::TimeTagToTimeTag => {
579                    // This should be guaranteed to be valid by library API, so unwrap is okay
580                    let start_time = time_window.start_time().unwrap().unix_stamp();
581                    let end_time = time_window.end_time().unwrap().unix_stamp();
582                    self.tc_map.range(start_time..=end_time)
583                }
584                TimeWindowType::FromTimeTag => {
585                    // This should be guaranteed to be valid by library API, so unwrap is okay
586                    let start_time = time_window.start_time().unwrap().unix_stamp();
587                    self.tc_map.range(start_time..)
588                }
589                TimeWindowType::ToTimeTag => {
590                    // This should be guaranteed to be valid by library API, so unwrap is okay
591                    let end_time = time_window.end_time().unwrap().unix_stamp();
592                    self.tc_map.range(..=end_time)
593                }
594            }
595        }
596
597        /// Deletes a scheduled command with the given request  ID. Returns the store address if a
598        /// scheduled command was found in the map and deleted, and None otherwise.
599        ///
600        /// Please note that this function will stop on the first telecommand with a request ID match.
601        /// In case of duplicate IDs (which should generally not happen), this function needs to be
602        /// called repeatedly.
603        pub fn delete_by_request_id(&mut self, req_id: &RequestId) -> Option<StoreAddr> {
604            if let DeletionResult::WithoutStoreDeletion(v) =
605                self.delete_by_request_id_internal_without_store_deletion(req_id)
606            {
607                return v;
608            }
609            panic!("unexpected deletion result");
610        }
611
612        /// This behaves like [Self::delete_by_request_id] but deletes the packet from the pool as well.
613        pub fn delete_by_request_id_and_from_pool(
614            &mut self,
615            req_id: &RequestId,
616            pool: &mut (impl PoolProvider + ?Sized),
617        ) -> Result<bool, StoreError> {
618            if let DeletionResult::WithStoreDeletion(v) =
619                self.delete_by_request_id_internal_with_store_deletion(req_id, pool)
620            {
621                return v;
622            }
623            panic!("unexpected deletion result");
624        }
625
626        fn delete_by_request_id_internal_without_store_deletion(
627            &mut self,
628            req_id: &RequestId,
629        ) -> DeletionResult {
630            let mut idx_found = None;
631            for time_bucket in &mut self.tc_map {
632                for (idx, tc_info) in time_bucket.1.iter().enumerate() {
633                    if &tc_info.request_id == req_id {
634                        idx_found = Some(idx);
635                    }
636                }
637                if let Some(idx) = idx_found {
638                    let addr = time_bucket.1.remove(idx).addr;
639                    return DeletionResult::WithoutStoreDeletion(Some(addr));
640                }
641            }
642            DeletionResult::WithoutStoreDeletion(None)
643        }
644
645        fn delete_by_request_id_internal_with_store_deletion(
646            &mut self,
647            req_id: &RequestId,
648            pool: &mut (impl PoolProvider + ?Sized),
649        ) -> DeletionResult {
650            let mut idx_found = None;
651            for time_bucket in &mut self.tc_map {
652                for (idx, tc_info) in time_bucket.1.iter().enumerate() {
653                    if &tc_info.request_id == req_id {
654                        idx_found = Some(idx);
655                    }
656                }
657                if let Some(idx) = idx_found {
658                    let addr = time_bucket.1.remove(idx).addr;
659                    return match pool.delete(addr) {
660                        Ok(_) => DeletionResult::WithStoreDeletion(Ok(true)),
661                        Err(e) => DeletionResult::WithStoreDeletion(Err(e)),
662                    };
663                }
664            }
665            DeletionResult::WithStoreDeletion(Ok(false))
666        }
667
668        #[cfg(feature = "std")]
669        #[cfg_attr(doc_cfg, doc(cfg(feature = "std")))]
670        pub fn update_time_from_now(&mut self) -> Result<(), SystemTimeError> {
671            self.current_time = UnixTimestamp::from_now()?;
672            Ok(())
673        }
674
675        /// Utility method which calls [Self::telecommands_to_release] and then calls a releaser
676        /// closure for each telecommand which should be released. This function will also delete
677        /// the telecommands from the holding store after calling the release closure if the user
678        /// returns [true] from the release closure. A buffer must be provided to hold the
679        /// telecommands for the release process.
680        ///
681        /// # Arguments
682        ///
683        /// * `releaser` - Closure where the first argument is whether the scheduler is enabled and
684        ///     the second argument is the telecommand information also containing the store
685        ///     address. This closure should return whether the command should be deleted. Please
686        ///     note that returning false might lead to memory leaks if the TC is not cleared from
687        ///     the store in some other way.
688        /// * `tc_store` - The holding store of the telecommands.
689        /// * `tc_buf` - Buffer to hold each telecommand being released.
690        pub fn release_telecommands_with_buffer<R: FnMut(bool, &TcInfo, &[u8]) -> bool>(
691            &mut self,
692            releaser: R,
693            tc_store: &mut (impl PoolProvider + ?Sized),
694            tc_buf: &mut [u8],
695        ) -> Result<u64, (u64, StoreError)> {
696            self.release_telecommands_internal(releaser, tc_store, Some(tc_buf))
697        }
698
699        /// This functions is almost identical to [Self::release_telecommands_with_buffer] but does
700        /// not require a user provided TC buffer because it will always use the
701        /// [PoolProvider::read_as_vec] API to read the TC packets.
702        ///
703        /// However, this might also perform frequent allocations for all telecommands being
704        /// released.
705        pub fn release_telecommands<R: FnMut(bool, &TcInfo, &[u8]) -> bool>(
706            &mut self,
707            releaser: R,
708            tc_store: &mut (impl PoolProvider + ?Sized),
709        ) -> Result<u64, (u64, StoreError)> {
710            self.release_telecommands_internal(releaser, tc_store, None)
711        }
712
713        fn release_telecommands_internal<R: FnMut(bool, &TcInfo, &[u8]) -> bool>(
714            &mut self,
715            mut releaser: R,
716            tc_store: &mut (impl PoolProvider + ?Sized),
717            mut tc_buf: Option<&mut [u8]>,
718        ) -> Result<u64, (u64, StoreError)> {
719            let tcs_to_release = self.telecommands_to_release();
720            let mut released_tcs = 0;
721            let mut store_error = Ok(());
722            for tc in tcs_to_release {
723                for info in tc.1 {
724                    let should_delete = match tc_buf.as_mut() {
725                        Some(buf) => {
726                            tc_store
727                                .read(&info.addr, buf)
728                                .map_err(|e| (released_tcs, e))?;
729                            releaser(self.enabled, info, buf)
730                        }
731                        None => {
732                            let tc = tc_store
733                                .read_as_vec(&info.addr)
734                                .map_err(|e| (released_tcs, e))?;
735                            releaser(self.enabled, info, &tc)
736                        }
737                    };
738                    released_tcs += 1;
739                    if should_delete {
740                        let res = tc_store.delete(info.addr);
741                        if res.is_err() {
742                            store_error = res;
743                        }
744                    }
745                }
746            }
747            self.tc_map.retain(|k, _| k > &self.current_time);
748            store_error
749                .map(|_| released_tcs)
750                .map_err(|e| (released_tcs, e))
751        }
752
753        /// This utility method is similar to [Self::release_telecommands] but will not perform
754        /// store deletions and thus does not require a mutable reference of the TC store.
755        ///
756        /// It will returns a [Vec] of [TcInfo]s to transfer the list of released
757        /// telecommands to the user. The user should take care of deleting those telecommands
758        /// from the holding store to prevent memory leaks.
759        pub fn release_telecommands_no_deletion<R: FnMut(bool, &TcInfo, &[u8])>(
760            &mut self,
761            mut releaser: R,
762            tc_store: &(impl PoolProvider + ?Sized),
763            tc_buf: &mut [u8],
764        ) -> Result<Vec<TcInfo>, (Vec<TcInfo>, StoreError)> {
765            let tcs_to_release = self.telecommands_to_release();
766            let mut released_tcs = Vec::new();
767            for tc in tcs_to_release {
768                for info in tc.1 {
769                    tc_store
770                        .read(&info.addr, tc_buf)
771                        .map_err(|e| (released_tcs.clone(), e))?;
772                    releaser(self.is_enabled(), info, tc_buf);
773                    released_tcs.push(*info);
774                }
775            }
776            self.tc_map.retain(|k, _| k > &self.current_time);
777            Ok(released_tcs)
778        }
779
780        /// Retrieve all telecommands which should be release based on the current time.
781        pub fn telecommands_to_release(&self) -> Range<'_, UnixTimestamp, Vec<TcInfo>> {
782            self.tc_map.range(..=self.current_time)
783        }
784    }
785
786    impl PusSchedulerProvider for PusScheduler {
787        type TimeProvider = cds::TimeProvider;
788
789        /// This will disable the scheduler and clear the schedule as specified in 6.11.4.4.
790        /// Be careful with this command as it will delete all the commands in the schedule.
791        ///
792        /// The holding store for the telecommands needs to be passed so all the stored telecommands
793        /// can be deleted to avoid a memory leak. If at last one deletion operation fails, the error
794        /// will be returned but the method will still try to delete all the commands in the schedule.
795        fn reset(&mut self, store: &mut (impl PoolProvider + ?Sized)) -> Result<(), StoreError> {
796            self.enabled = false;
797            let mut deletion_ok = Ok(());
798            for tc_lists in &mut self.tc_map {
799                for tc in tc_lists.1 {
800                    let res = store.delete(tc.addr);
801                    if res.is_err() {
802                        deletion_ok = res;
803                    }
804                }
805            }
806            self.tc_map.clear();
807            deletion_ok
808        }
809
810        fn is_enabled(&self) -> bool {
811            self.enabled
812        }
813
814        fn enable(&mut self) {
815            self.enabled = true;
816        }
817
818        /// A disabled scheduler should still delete commands where the execution time has been reached
819        /// but should not release them to be executed.
820        fn disable(&mut self) {
821            self.enabled = false;
822        }
823
824        fn insert_unwrapped_and_stored_tc(
825            &mut self,
826            time_stamp: UnixTimestamp,
827            info: TcInfo,
828        ) -> Result<(), ScheduleError> {
829            if time_stamp < self.current_time + self.time_margin {
830                return Err(ScheduleError::ReleaseTimeInTimeMargin {
831                    current_time: self.current_time,
832                    time_margin: self.time_margin,
833                    release_time: time_stamp,
834                });
835            }
836            match self.tc_map.entry(time_stamp) {
837                Entry::Vacant(e) => {
838                    e.insert(vec![info]);
839                }
840                Entry::Occupied(mut v) => {
841                    v.get_mut().push(info);
842                }
843            }
844            Ok(())
845        }
846    }
847}
848
849#[cfg(test)]
850mod tests {
851    use super::*;
852    use crate::pool::{
853        PoolProvider, StaticMemoryPool, StaticPoolAddr, StaticPoolConfig, StoreAddr, StoreError,
854    };
855    use alloc::collections::btree_map::Range;
856    use spacepackets::ecss::tc::{PusTcCreator, PusTcReader, PusTcSecondaryHeader};
857    use spacepackets::ecss::WritablePusPacket;
858    use spacepackets::time::{cds, TimeWriter, UnixTimestamp};
859    use spacepackets::{PacketId, PacketSequenceCtrl, PacketType, SequenceFlags, SpHeader};
860    use std::time::Duration;
861    use std::vec::Vec;
862    #[allow(unused_imports)]
863    use std::{println, vec};
864
865    fn pus_tc_base(timestamp: UnixTimestamp, buf: &mut [u8]) -> (SpHeader, usize) {
866        let cds_time = cds::TimeProvider::from_unix_secs_with_u16_days(&timestamp).unwrap();
867        let len_time_stamp = cds_time.write_to_bytes(buf).unwrap();
868        let len_packet = base_ping_tc_simple_ctor(0, None)
869            .write_to_bytes(&mut buf[len_time_stamp..])
870            .unwrap();
871        (
872            SpHeader::tc_unseg(0x02, 0x34, len_packet as u16).unwrap(),
873            len_packet + len_time_stamp,
874        )
875    }
876
877    fn scheduled_tc(timestamp: UnixTimestamp, buf: &mut [u8]) -> PusTcCreator {
878        let (mut sph, len_app_data) = pus_tc_base(timestamp, buf);
879        PusTcCreator::new_simple(&mut sph, 11, 4, Some(&buf[..len_app_data]), true)
880    }
881
882    fn wrong_tc_service(timestamp: UnixTimestamp, buf: &mut [u8]) -> PusTcCreator {
883        let (mut sph, len_app_data) = pus_tc_base(timestamp, buf);
884        PusTcCreator::new_simple(&mut sph, 12, 4, Some(&buf[..len_app_data]), true)
885    }
886
887    fn wrong_tc_subservice(timestamp: UnixTimestamp, buf: &mut [u8]) -> PusTcCreator {
888        let (mut sph, len_app_data) = pus_tc_base(timestamp, buf);
889        PusTcCreator::new_simple(&mut sph, 11, 5, Some(&buf[..len_app_data]), true)
890    }
891
892    fn double_wrapped_time_tagged_tc(timestamp: UnixTimestamp, buf: &mut [u8]) -> PusTcCreator {
893        let cds_time = cds::TimeProvider::from_unix_secs_with_u16_days(&timestamp).unwrap();
894        let len_time_stamp = cds_time.write_to_bytes(buf).unwrap();
895        let mut sph = SpHeader::tc_unseg(0x02, 0x34, 0).unwrap();
896        // app data should not matter, double wrapped time-tagged commands should be rejected right
897        // away
898        let inner_time_tagged_tc = PusTcCreator::new_simple(&mut sph, 11, 4, None, true);
899        let packet_len = inner_time_tagged_tc
900            .write_to_bytes(&mut buf[len_time_stamp..])
901            .expect("writing inner time tagged tc failed");
902        PusTcCreator::new_simple(
903            &mut sph,
904            11,
905            4,
906            Some(&buf[..len_time_stamp + packet_len]),
907            true,
908        )
909    }
910
911    fn invalid_time_tagged_cmd() -> PusTcCreator<'static> {
912        let mut sph = SpHeader::tc_unseg(0x02, 0x34, 1).unwrap();
913        PusTcCreator::new_simple(&mut sph, 11, 4, None, true)
914    }
915
916    fn base_ping_tc_simple_ctor(
917        seq_count: u16,
918        app_data: Option<&'static [u8]>,
919    ) -> PusTcCreator<'static> {
920        let mut sph = SpHeader::tc_unseg(0x02, seq_count, 0).unwrap();
921        PusTcCreator::new_simple(&mut sph, 17, 1, app_data, true)
922    }
923
924    fn ping_tc_to_store(
925        pool: &mut StaticMemoryPool,
926        buf: &mut [u8],
927        seq_count: u16,
928        app_data: Option<&'static [u8]>,
929    ) -> TcInfo {
930        let ping_tc = base_ping_tc_simple_ctor(seq_count, app_data);
931        let ping_size = ping_tc.write_to_bytes(buf).expect("writing ping TC failed");
932        let first_addr = pool.add(&buf[0..ping_size]).unwrap();
933        TcInfo::new(first_addr, RequestId::from_tc(&ping_tc))
934    }
935
936    #[test]
937    fn test_enable_api() {
938        let mut scheduler =
939            PusScheduler::new(UnixTimestamp::new_only_seconds(0), Duration::from_secs(5));
940        assert!(scheduler.is_enabled());
941        scheduler.disable();
942        assert!(!scheduler.is_enabled());
943        scheduler.enable();
944        assert!(scheduler.is_enabled());
945    }
946
947    #[test]
948    fn test_reset() {
949        let mut pool = StaticMemoryPool::new(StaticPoolConfig::new(vec![(10, 32), (5, 64)], false));
950        let mut scheduler =
951            PusScheduler::new(UnixTimestamp::new_only_seconds(0), Duration::from_secs(5));
952
953        let mut buf: [u8; 32] = [0; 32];
954        let tc_info_0 = ping_tc_to_store(&mut pool, &mut buf, 0, None);
955
956        scheduler
957            .insert_unwrapped_and_stored_tc(
958                UnixTimestamp::new_only_seconds(100),
959                TcInfo::new(tc_info_0.addr, tc_info_0.request_id),
960            )
961            .unwrap();
962
963        let app_data = &[0, 1, 2];
964        let tc_info_1 = ping_tc_to_store(&mut pool, &mut buf, 1, Some(app_data));
965        scheduler
966            .insert_unwrapped_and_stored_tc(
967                UnixTimestamp::new_only_seconds(200),
968                TcInfo::new(tc_info_1.addr, tc_info_1.request_id),
969            )
970            .unwrap();
971
972        let app_data = &[0, 1, 2];
973        let tc_info_2 = ping_tc_to_store(&mut pool, &mut buf, 2, Some(app_data));
974        scheduler
975            .insert_unwrapped_and_stored_tc(
976                UnixTimestamp::new_only_seconds(300),
977                TcInfo::new(tc_info_2.addr(), tc_info_2.request_id()),
978            )
979            .unwrap();
980
981        assert_eq!(scheduler.num_scheduled_telecommands(), 3);
982        assert!(scheduler.is_enabled());
983        scheduler.reset(&mut pool).expect("deletion of TCs failed");
984        assert!(!scheduler.is_enabled());
985        assert_eq!(scheduler.num_scheduled_telecommands(), 0);
986        assert!(!pool.has_element_at(&tc_info_0.addr()).unwrap());
987        assert!(!pool.has_element_at(&tc_info_1.addr()).unwrap());
988        assert!(!pool.has_element_at(&tc_info_2.addr()).unwrap());
989    }
990
991    #[test]
992    fn insert_multi_with_same_time() {
993        let mut scheduler =
994            PusScheduler::new(UnixTimestamp::new_only_seconds(0), Duration::from_secs(5));
995
996        scheduler
997            .insert_unwrapped_and_stored_tc(
998                UnixTimestamp::new_only_seconds(100),
999                TcInfo::new(
1000                    StoreAddr::from(StaticPoolAddr {
1001                        pool_idx: 0,
1002                        packet_idx: 1,
1003                    }),
1004                    RequestId {
1005                        seq_count: 1,
1006                        apid: 0,
1007                        source_id: 0,
1008                    },
1009                ),
1010            )
1011            .unwrap();
1012
1013        scheduler
1014            .insert_unwrapped_and_stored_tc(
1015                UnixTimestamp::new_only_seconds(100),
1016                TcInfo::new(
1017                    StoreAddr::from(StaticPoolAddr {
1018                        pool_idx: 0,
1019                        packet_idx: 2,
1020                    }),
1021                    RequestId {
1022                        seq_count: 2,
1023                        apid: 1,
1024                        source_id: 5,
1025                    },
1026                ),
1027            )
1028            .unwrap();
1029
1030        scheduler
1031            .insert_unwrapped_and_stored_tc(
1032                UnixTimestamp::new_only_seconds(300),
1033                TcInfo::new(
1034                    StaticPoolAddr {
1035                        pool_idx: 0,
1036                        packet_idx: 2,
1037                    }
1038                    .into(),
1039                    RequestId {
1040                        source_id: 10,
1041                        seq_count: 20,
1042                        apid: 23,
1043                    },
1044                ),
1045            )
1046            .unwrap();
1047
1048        assert_eq!(scheduler.num_scheduled_telecommands(), 3);
1049    }
1050
1051    #[test]
1052    fn test_time_update() {
1053        let mut scheduler =
1054            PusScheduler::new(UnixTimestamp::new_only_seconds(0), Duration::from_secs(5));
1055        let time = UnixTimestamp::new(1, 2).unwrap();
1056        scheduler.update_time(time);
1057        assert_eq!(scheduler.current_time(), &time);
1058    }
1059
1060    fn common_check(
1061        enabled: bool,
1062        store_addr: &StoreAddr,
1063        expected_store_addrs: Vec<StoreAddr>,
1064        counter: &mut usize,
1065    ) {
1066        assert!(enabled);
1067        assert!(expected_store_addrs.contains(store_addr));
1068        *counter += 1;
1069    }
1070    fn common_check_disabled(
1071        enabled: bool,
1072        store_addr: &StoreAddr,
1073        expected_store_addrs: Vec<StoreAddr>,
1074        counter: &mut usize,
1075    ) {
1076        assert!(!enabled);
1077        assert!(expected_store_addrs.contains(store_addr));
1078        *counter += 1;
1079    }
1080
1081    #[test]
1082    fn test_request_id() {
1083        let src_id_to_set = 12;
1084        let apid_to_set = 0x22;
1085        let seq_count = 105;
1086        let mut sp_header = SpHeader::tc_unseg(apid_to_set, 105, 0).unwrap();
1087        let mut sec_header = PusTcSecondaryHeader::new_simple(17, 1);
1088        sec_header.source_id = src_id_to_set;
1089        let ping_tc = PusTcCreator::new_no_app_data(&mut sp_header, sec_header, true);
1090        let req_id = RequestId::from_tc(&ping_tc);
1091        assert_eq!(req_id.source_id(), src_id_to_set);
1092        assert_eq!(req_id.apid(), apid_to_set);
1093        assert_eq!(req_id.seq_count(), seq_count);
1094        assert_eq!(
1095            req_id.as_u64(),
1096            ((src_id_to_set as u64) << 32) | (apid_to_set as u64) << 16 | seq_count as u64
1097        );
1098    }
1099    #[test]
1100    fn test_release_telecommands() {
1101        let mut pool = StaticMemoryPool::new(StaticPoolConfig::new(vec![(10, 32), (5, 64)], false));
1102        let mut scheduler =
1103            PusScheduler::new(UnixTimestamp::new_only_seconds(0), Duration::from_secs(5));
1104
1105        let mut buf: [u8; 32] = [0; 32];
1106        let tc_info_0 = ping_tc_to_store(&mut pool, &mut buf, 0, None);
1107
1108        scheduler
1109            .insert_unwrapped_and_stored_tc(UnixTimestamp::new_only_seconds(100), tc_info_0)
1110            .expect("insertion failed");
1111
1112        let tc_info_1 = ping_tc_to_store(&mut pool, &mut buf, 1, None);
1113        scheduler
1114            .insert_unwrapped_and_stored_tc(UnixTimestamp::new_only_seconds(200), tc_info_1)
1115            .expect("insertion failed");
1116
1117        let mut i = 0;
1118        let mut test_closure_1 = |boolvar: bool, tc_info: &TcInfo, _tc: &[u8]| {
1119            common_check(boolvar, &tc_info.addr, vec![tc_info_0.addr()], &mut i);
1120            true
1121        };
1122
1123        // test 1: too early, no tcs
1124        scheduler.update_time(UnixTimestamp::new_only_seconds(99));
1125
1126        let mut tc_buf: [u8; 128] = [0; 128];
1127        scheduler
1128            .release_telecommands_with_buffer(&mut test_closure_1, &mut pool, &mut tc_buf)
1129            .expect("deletion failed");
1130
1131        // test 2: exact time stamp of tc, releases 1 tc
1132        scheduler.update_time(UnixTimestamp::new_only_seconds(100));
1133
1134        let mut released = scheduler
1135            .release_telecommands(&mut test_closure_1, &mut pool)
1136            .expect("deletion failed");
1137        assert_eq!(released, 1);
1138        // TC is deleted.
1139        assert!(!pool.has_element_at(&tc_info_0.addr()).unwrap());
1140
1141        // test 3, late timestamp, release 1 overdue tc
1142        let mut test_closure_2 = |boolvar: bool, tc_info: &TcInfo, _tc: &[u8]| {
1143            common_check(boolvar, &tc_info.addr, vec![tc_info_1.addr()], &mut i);
1144            true
1145        };
1146
1147        scheduler.update_time(UnixTimestamp::new_only_seconds(206));
1148
1149        released = scheduler
1150            .release_telecommands_with_buffer(&mut test_closure_2, &mut pool, &mut tc_buf)
1151            .expect("deletion failed");
1152        assert_eq!(released, 1);
1153        // TC is deleted.
1154        assert!(!pool.has_element_at(&tc_info_1.addr()).unwrap());
1155
1156        //test 4: no tcs left
1157        scheduler
1158            .release_telecommands(&mut test_closure_2, &mut pool)
1159            .expect("deletion failed");
1160
1161        // check that 2 total tcs have been released
1162        assert_eq!(i, 2);
1163    }
1164
1165    #[test]
1166    fn release_multi_with_same_time() {
1167        let mut pool = StaticMemoryPool::new(StaticPoolConfig::new(vec![(10, 32), (5, 64)], false));
1168        let mut scheduler =
1169            PusScheduler::new(UnixTimestamp::new_only_seconds(0), Duration::from_secs(5));
1170
1171        let mut buf: [u8; 32] = [0; 32];
1172        let tc_info_0 = ping_tc_to_store(&mut pool, &mut buf, 0, None);
1173
1174        scheduler
1175            .insert_unwrapped_and_stored_tc(UnixTimestamp::new_only_seconds(100), tc_info_0)
1176            .expect("insertion failed");
1177
1178        let tc_info_1 = ping_tc_to_store(&mut pool, &mut buf, 1, None);
1179        scheduler
1180            .insert_unwrapped_and_stored_tc(UnixTimestamp::new_only_seconds(100), tc_info_1)
1181            .expect("insertion failed");
1182
1183        let mut i = 0;
1184        let mut test_closure = |boolvar: bool, store_addr: &TcInfo, _tc: &[u8]| {
1185            common_check(
1186                boolvar,
1187                &store_addr.addr,
1188                vec![tc_info_0.addr(), tc_info_1.addr()],
1189                &mut i,
1190            );
1191            true
1192        };
1193
1194        // test 1: too early, no tcs
1195        scheduler.update_time(UnixTimestamp::new_only_seconds(99));
1196        let mut tc_buf: [u8; 128] = [0; 128];
1197
1198        let mut released = scheduler
1199            .release_telecommands_with_buffer(&mut test_closure, &mut pool, &mut tc_buf)
1200            .expect("deletion failed");
1201        assert_eq!(released, 0);
1202
1203        // test 2: exact time stamp of tc, releases 2 tc
1204        scheduler.update_time(UnixTimestamp::new_only_seconds(100));
1205
1206        released = scheduler
1207            .release_telecommands(&mut test_closure, &mut pool)
1208            .expect("deletion failed");
1209        assert_eq!(released, 2);
1210        assert!(!pool.has_element_at(&tc_info_0.addr()).unwrap());
1211        assert!(!pool.has_element_at(&tc_info_1.addr()).unwrap());
1212
1213        //test 3: no tcs left
1214        released = scheduler
1215            .release_telecommands(&mut test_closure, &mut pool)
1216            .expect("deletion failed");
1217        assert_eq!(released, 0);
1218
1219        // check that 2 total tcs have been released
1220        assert_eq!(i, 2);
1221    }
1222
1223    #[test]
1224    fn release_with_scheduler_disabled() {
1225        let mut pool = StaticMemoryPool::new(StaticPoolConfig::new(vec![(10, 32), (5, 64)], false));
1226        let mut scheduler =
1227            PusScheduler::new(UnixTimestamp::new_only_seconds(0), Duration::from_secs(5));
1228
1229        scheduler.disable();
1230
1231        let mut buf: [u8; 32] = [0; 32];
1232        let tc_info_0 = ping_tc_to_store(&mut pool, &mut buf, 0, None);
1233
1234        scheduler
1235            .insert_unwrapped_and_stored_tc(UnixTimestamp::new_only_seconds(100), tc_info_0)
1236            .expect("insertion failed");
1237
1238        let tc_info_1 = ping_tc_to_store(&mut pool, &mut buf, 1, None);
1239        scheduler
1240            .insert_unwrapped_and_stored_tc(UnixTimestamp::new_only_seconds(200), tc_info_1)
1241            .expect("insertion failed");
1242
1243        let mut i = 0;
1244        let mut test_closure_1 = |boolvar: bool, tc_info: &TcInfo, _tc: &[u8]| {
1245            common_check_disabled(boolvar, &tc_info.addr, vec![tc_info_0.addr()], &mut i);
1246            true
1247        };
1248
1249        let mut tc_buf: [u8; 128] = [0; 128];
1250
1251        // test 1: too early, no tcs
1252        scheduler.update_time(UnixTimestamp::new_only_seconds(99));
1253
1254        scheduler
1255            .release_telecommands_with_buffer(&mut test_closure_1, &mut pool, &mut tc_buf)
1256            .expect("deletion failed");
1257
1258        // test 2: exact time stamp of tc, releases 1 tc
1259        scheduler.update_time(UnixTimestamp::new_only_seconds(100));
1260
1261        let mut released = scheduler
1262            .release_telecommands(&mut test_closure_1, &mut pool)
1263            .expect("deletion failed");
1264        assert_eq!(released, 1);
1265        assert!(!pool.has_element_at(&tc_info_0.addr()).unwrap());
1266
1267        // test 3, late timestamp, release 1 overdue tc
1268        let mut test_closure_2 = |boolvar: bool, tc_info: &TcInfo, _tc: &[u8]| {
1269            common_check_disabled(boolvar, &tc_info.addr, vec![tc_info_1.addr()], &mut i);
1270            true
1271        };
1272
1273        scheduler.update_time(UnixTimestamp::new_only_seconds(206));
1274
1275        released = scheduler
1276            .release_telecommands(&mut test_closure_2, &mut pool)
1277            .expect("deletion failed");
1278        assert_eq!(released, 1);
1279        assert!(!pool.has_element_at(&tc_info_1.addr()).unwrap());
1280
1281        //test 4: no tcs left
1282        scheduler
1283            .release_telecommands(&mut test_closure_2, &mut pool)
1284            .expect("deletion failed");
1285
1286        // check that 2 total tcs have been released
1287        assert_eq!(i, 2);
1288    }
1289
1290    #[test]
1291    fn insert_unwrapped_tc() {
1292        let mut scheduler =
1293            PusScheduler::new(UnixTimestamp::new_only_seconds(0), Duration::from_secs(5));
1294
1295        let mut pool = StaticMemoryPool::new(StaticPoolConfig::new(vec![(10, 32), (5, 64)], false));
1296        let mut buf: [u8; 32] = [0; 32];
1297        let tc_info_0 = ping_tc_to_store(&mut pool, &mut buf, 0, None);
1298
1299        let info = scheduler
1300            .insert_unwrapped_tc(
1301                UnixTimestamp::new_only_seconds(100),
1302                &buf[..pool.len_of_data(&tc_info_0.addr()).unwrap()],
1303                &mut pool,
1304            )
1305            .unwrap();
1306
1307        assert!(pool.has_element_at(&tc_info_0.addr()).unwrap());
1308
1309        let mut read_buf: [u8; 64] = [0; 64];
1310        pool.read(&tc_info_0.addr(), &mut read_buf).unwrap();
1311        let check_tc = PusTcReader::new(&read_buf).expect("incorrect Pus tc raw data");
1312        assert_eq!(check_tc.0, base_ping_tc_simple_ctor(0, None));
1313
1314        assert_eq!(scheduler.num_scheduled_telecommands(), 1);
1315
1316        scheduler.update_time(UnixTimestamp::new_only_seconds(101));
1317
1318        let mut addr_vec = Vec::new();
1319
1320        let mut i = 0;
1321        let mut test_closure = |boolvar: bool, tc_info: &TcInfo, _tc: &[u8]| {
1322            common_check(boolvar, &tc_info.addr, vec![info.addr], &mut i);
1323            // check that tc remains unchanged
1324            addr_vec.push(tc_info.addr);
1325            false
1326        };
1327
1328        scheduler
1329            .release_telecommands(&mut test_closure, &mut pool)
1330            .unwrap();
1331
1332        let read_len = pool.read(&addr_vec[0], &mut read_buf).unwrap();
1333        let check_tc = PusTcReader::new(&read_buf).expect("incorrect Pus tc raw data");
1334        assert_eq!(read_len, check_tc.1);
1335        assert_eq!(check_tc.0, base_ping_tc_simple_ctor(0, None));
1336    }
1337
1338    #[test]
1339    fn insert_wrapped_tc() {
1340        let mut scheduler =
1341            PusScheduler::new(UnixTimestamp::new_only_seconds(0), Duration::from_secs(5));
1342
1343        let mut pool = StaticMemoryPool::new(StaticPoolConfig::new(vec![(10, 32), (5, 64)], false));
1344
1345        let mut buf: [u8; 32] = [0; 32];
1346        let tc = scheduled_tc(UnixTimestamp::new_only_seconds(100), &mut buf);
1347
1348        let info = match scheduler.insert_wrapped_tc::<cds::TimeProvider>(&tc, &mut pool) {
1349            Ok(addr) => addr,
1350            Err(e) => {
1351                panic!("unexpected error {e}");
1352            }
1353        };
1354
1355        assert!(pool.has_element_at(&info.addr).unwrap());
1356
1357        let read_len = pool.read(&info.addr, &mut buf).unwrap();
1358        let check_tc = PusTcReader::new(&buf).expect("incorrect Pus tc raw data");
1359        assert_eq!(read_len, check_tc.1);
1360        assert_eq!(check_tc.0, base_ping_tc_simple_ctor(0, None));
1361
1362        assert_eq!(scheduler.num_scheduled_telecommands(), 1);
1363
1364        scheduler.update_time(UnixTimestamp::new_only_seconds(101));
1365
1366        let mut addr_vec = Vec::new();
1367
1368        let mut i = 0;
1369        let mut test_closure = |boolvar: bool, tc_info: &TcInfo, _tc: &[u8]| {
1370            common_check(boolvar, &tc_info.addr, vec![info.addr], &mut i);
1371            // check that tc remains unchanged
1372            addr_vec.push(tc_info.addr);
1373            false
1374        };
1375
1376        let mut tc_buf: [u8; 64] = [0; 64];
1377
1378        scheduler
1379            .release_telecommands_with_buffer(&mut test_closure, &mut pool, &mut tc_buf)
1380            .unwrap();
1381
1382        let read_len = pool.read(&addr_vec[0], &mut buf).unwrap();
1383        let check_tc = PusTcReader::new(&buf).expect("incorrect PUS tc raw data");
1384        assert_eq!(read_len, check_tc.1);
1385        assert_eq!(check_tc.0, base_ping_tc_simple_ctor(0, None));
1386    }
1387
1388    #[test]
1389    fn insert_wrong_service() {
1390        let mut scheduler =
1391            PusScheduler::new(UnixTimestamp::new_only_seconds(0), Duration::from_secs(5));
1392
1393        let mut pool = StaticMemoryPool::new(StaticPoolConfig::new(vec![(10, 32), (5, 64)], false));
1394
1395        let mut buf: [u8; 32] = [0; 32];
1396        let tc = wrong_tc_service(UnixTimestamp::new_only_seconds(100), &mut buf);
1397
1398        let err = scheduler.insert_wrapped_tc::<cds::TimeProvider>(&tc, &mut pool);
1399        assert!(err.is_err());
1400        let err = err.unwrap_err();
1401        match err {
1402            ScheduleError::WrongService(wrong_service) => {
1403                assert_eq!(wrong_service, 12);
1404            }
1405            _ => {
1406                panic!("unexpected error")
1407            }
1408        }
1409    }
1410
1411    #[test]
1412    fn insert_wrong_subservice() {
1413        let mut scheduler =
1414            PusScheduler::new(UnixTimestamp::new_only_seconds(0), Duration::from_secs(5));
1415
1416        let mut pool = StaticMemoryPool::new(StaticPoolConfig::new(vec![(10, 32), (5, 64)], false));
1417
1418        let mut buf: [u8; 32] = [0; 32];
1419        let tc = wrong_tc_subservice(UnixTimestamp::new_only_seconds(100), &mut buf);
1420
1421        let err = scheduler.insert_wrapped_tc::<cds::TimeProvider>(&tc, &mut pool);
1422        assert!(err.is_err());
1423        let err = err.unwrap_err();
1424        match err {
1425            ScheduleError::WrongSubservice(wrong_subsrv) => {
1426                assert_eq!(wrong_subsrv, 5);
1427            }
1428            _ => {
1429                panic!("unexpected error")
1430            }
1431        }
1432    }
1433
1434    #[test]
1435    fn insert_wrapped_tc_faulty_app_data() {
1436        let mut scheduler =
1437            PusScheduler::new(UnixTimestamp::new_only_seconds(0), Duration::from_secs(5));
1438        let mut pool = StaticMemoryPool::new(StaticPoolConfig::new(vec![(10, 32), (5, 64)], false));
1439        let tc = invalid_time_tagged_cmd();
1440        let insert_res = scheduler.insert_wrapped_tc::<cds::TimeProvider>(&tc, &mut pool);
1441        assert!(insert_res.is_err());
1442        let err = insert_res.unwrap_err();
1443        match err {
1444            ScheduleError::TcDataEmpty => {}
1445            _ => panic!("unexpected error {err}"),
1446        }
1447    }
1448
1449    #[test]
1450    fn insert_doubly_wrapped_time_tagged_cmd() {
1451        let mut scheduler =
1452            PusScheduler::new(UnixTimestamp::new_only_seconds(0), Duration::from_secs(5));
1453        let mut pool = StaticMemoryPool::new(StaticPoolConfig::new(vec![(10, 32), (5, 64)], false));
1454        let mut buf: [u8; 64] = [0; 64];
1455        let tc = double_wrapped_time_tagged_tc(UnixTimestamp::new_only_seconds(50), &mut buf);
1456        let insert_res = scheduler.insert_wrapped_tc::<cds::TimeProvider>(&tc, &mut pool);
1457        assert!(insert_res.is_err());
1458        let err = insert_res.unwrap_err();
1459        match err {
1460            ScheduleError::NestedScheduledTc => {}
1461            _ => panic!("unexpected error {err}"),
1462        }
1463    }
1464
1465    #[test]
1466    fn test_ctor_from_current() {
1467        let scheduler = PusScheduler::new_with_current_init_time(Duration::from_secs(5))
1468            .expect("creation from current time failed");
1469        let current_time = scheduler.current_time;
1470        assert!(current_time.unix_seconds > 0);
1471    }
1472
1473    #[test]
1474    fn test_update_from_current() {
1475        let mut scheduler =
1476            PusScheduler::new(UnixTimestamp::new_only_seconds(0), Duration::from_secs(5));
1477        assert_eq!(scheduler.current_time.unix_seconds, 0);
1478        scheduler
1479            .update_time_from_now()
1480            .expect("updating scheduler time from now failed");
1481        assert!(scheduler.current_time.unix_seconds > 0);
1482    }
1483
1484    #[test]
1485    fn release_time_within_time_margin() {
1486        let mut scheduler =
1487            PusScheduler::new(UnixTimestamp::new_only_seconds(0), Duration::from_secs(5));
1488
1489        let mut pool = StaticMemoryPool::new(StaticPoolConfig::new(vec![(10, 32), (5, 64)], false));
1490
1491        let mut buf: [u8; 32] = [0; 32];
1492
1493        let tc = scheduled_tc(UnixTimestamp::new_only_seconds(4), &mut buf);
1494        let insert_res = scheduler.insert_wrapped_tc::<cds::TimeProvider>(&tc, &mut pool);
1495        assert!(insert_res.is_err());
1496        let err = insert_res.unwrap_err();
1497        match err {
1498            ScheduleError::ReleaseTimeInTimeMargin {
1499                current_time,
1500                time_margin,
1501                release_time,
1502            } => {
1503                assert_eq!(current_time, UnixTimestamp::new_only_seconds(0));
1504                assert_eq!(time_margin, Duration::from_secs(5));
1505                assert_eq!(release_time, UnixTimestamp::new_only_seconds(4));
1506            }
1507            _ => panic!("unexepcted error {err}"),
1508        }
1509    }
1510
1511    #[test]
1512    fn test_store_error_propagation_release() {
1513        let mut pool = StaticMemoryPool::new(StaticPoolConfig::new(vec![(10, 32), (5, 64)], false));
1514        let mut scheduler =
1515            PusScheduler::new(UnixTimestamp::new_only_seconds(0), Duration::from_secs(5));
1516        let mut buf: [u8; 32] = [0; 32];
1517        let tc_info_0 = ping_tc_to_store(&mut pool, &mut buf, 0, None);
1518        scheduler
1519            .insert_unwrapped_and_stored_tc(UnixTimestamp::new_only_seconds(100), tc_info_0)
1520            .expect("insertion failed");
1521
1522        let mut i = 0;
1523        let test_closure_1 = |boolvar: bool, tc_info: &TcInfo, _tc: &[u8]| {
1524            common_check_disabled(boolvar, &tc_info.addr, vec![tc_info_0.addr()], &mut i);
1525            true
1526        };
1527
1528        // premature deletion
1529        pool.delete(tc_info_0.addr()).expect("deletion failed");
1530        // scheduler will only auto-delete if it is disabled.
1531        scheduler.disable();
1532        scheduler.update_time(UnixTimestamp::new_only_seconds(100));
1533        let release_res = scheduler.release_telecommands(test_closure_1, &mut pool);
1534        assert!(release_res.is_err());
1535        let err = release_res.unwrap_err();
1536        // TC could not even be read..
1537        assert_eq!(err.0, 0);
1538        match err.1 {
1539            StoreError::DataDoesNotExist(addr) => {
1540                assert_eq!(tc_info_0.addr(), addr);
1541            }
1542            _ => panic!("unexpected error {}", err.1),
1543        }
1544    }
1545
1546    #[test]
1547    fn test_store_error_propagation_reset() {
1548        let mut pool = StaticMemoryPool::new(StaticPoolConfig::new(vec![(10, 32), (5, 64)], false));
1549        let mut scheduler =
1550            PusScheduler::new(UnixTimestamp::new_only_seconds(0), Duration::from_secs(5));
1551        let mut buf: [u8; 32] = [0; 32];
1552        let tc_info_0 = ping_tc_to_store(&mut pool, &mut buf, 0, None);
1553        scheduler
1554            .insert_unwrapped_and_stored_tc(UnixTimestamp::new_only_seconds(100), tc_info_0)
1555            .expect("insertion failed");
1556
1557        // premature deletion
1558        pool.delete(tc_info_0.addr()).expect("deletion failed");
1559        let reset_res = scheduler.reset(&mut pool);
1560        assert!(reset_res.is_err());
1561        let err = reset_res.unwrap_err();
1562        match err {
1563            StoreError::DataDoesNotExist(addr) => {
1564                assert_eq!(addr, tc_info_0.addr());
1565            }
1566            _ => panic!("unexpected error {err}"),
1567        }
1568    }
1569
1570    #[test]
1571    fn test_delete_by_req_id_simple_retrieve_addr() {
1572        let mut pool = StaticMemoryPool::new(StaticPoolConfig::new(vec![(10, 32), (5, 64)], false));
1573        let mut scheduler =
1574            PusScheduler::new(UnixTimestamp::new_only_seconds(0), Duration::from_secs(5));
1575        let mut buf: [u8; 32] = [0; 32];
1576        let tc_info_0 = ping_tc_to_store(&mut pool, &mut buf, 0, None);
1577        scheduler
1578            .insert_unwrapped_and_stored_tc(UnixTimestamp::new_only_seconds(100), tc_info_0)
1579            .expect("inserting tc failed");
1580        assert_eq!(scheduler.num_scheduled_telecommands(), 1);
1581        let addr = scheduler
1582            .delete_by_request_id(&tc_info_0.request_id())
1583            .unwrap();
1584        assert!(pool.has_element_at(&tc_info_0.addr()).unwrap());
1585        assert_eq!(tc_info_0.addr(), addr);
1586        assert_eq!(scheduler.num_scheduled_telecommands(), 0);
1587    }
1588
1589    #[test]
1590    fn test_delete_by_req_id_simple_delete_all() {
1591        let mut pool = StaticMemoryPool::new(StaticPoolConfig::new(vec![(10, 32), (5, 64)], false));
1592        let mut scheduler =
1593            PusScheduler::new(UnixTimestamp::new_only_seconds(0), Duration::from_secs(5));
1594        let mut buf: [u8; 32] = [0; 32];
1595        let tc_info_0 = ping_tc_to_store(&mut pool, &mut buf, 0, None);
1596        scheduler
1597            .insert_unwrapped_and_stored_tc(UnixTimestamp::new_only_seconds(100), tc_info_0)
1598            .expect("inserting tc failed");
1599        assert_eq!(scheduler.num_scheduled_telecommands(), 1);
1600        let del_res =
1601            scheduler.delete_by_request_id_and_from_pool(&tc_info_0.request_id(), &mut pool);
1602        assert!(del_res.is_ok());
1603        assert!(del_res.unwrap());
1604        assert!(!pool.has_element_at(&tc_info_0.addr()).unwrap());
1605        assert_eq!(scheduler.num_scheduled_telecommands(), 0);
1606    }
1607
1608    #[test]
1609    fn test_delete_by_req_id_complex() {
1610        let mut pool = StaticMemoryPool::new(StaticPoolConfig::new(vec![(10, 32), (5, 64)], false));
1611        let mut scheduler =
1612            PusScheduler::new(UnixTimestamp::new_only_seconds(0), Duration::from_secs(5));
1613        let mut buf: [u8; 32] = [0; 32];
1614        let tc_info_0 = ping_tc_to_store(&mut pool, &mut buf, 0, None);
1615        scheduler
1616            .insert_unwrapped_and_stored_tc(UnixTimestamp::new_only_seconds(100), tc_info_0)
1617            .expect("inserting tc failed");
1618        let tc_info_1 = ping_tc_to_store(&mut pool, &mut buf, 1, None);
1619        scheduler
1620            .insert_unwrapped_and_stored_tc(UnixTimestamp::new_only_seconds(100), tc_info_1)
1621            .expect("inserting tc failed");
1622        let tc_info_2 = ping_tc_to_store(&mut pool, &mut buf, 2, None);
1623        scheduler
1624            .insert_unwrapped_and_stored_tc(UnixTimestamp::new_only_seconds(100), tc_info_2)
1625            .expect("inserting tc failed");
1626        assert_eq!(scheduler.num_scheduled_telecommands(), 3);
1627
1628        // Delete first packet
1629        let addr_0 = scheduler.delete_by_request_id(&tc_info_0.request_id());
1630        assert!(addr_0.is_some());
1631        assert_eq!(addr_0.unwrap(), tc_info_0.addr());
1632        assert!(pool.has_element_at(&tc_info_0.addr()).unwrap());
1633        assert_eq!(scheduler.num_scheduled_telecommands(), 2);
1634
1635        // Delete next packet
1636        let del_res =
1637            scheduler.delete_by_request_id_and_from_pool(&tc_info_2.request_id(), &mut pool);
1638        assert!(del_res.is_ok());
1639        assert!(del_res.unwrap());
1640        assert!(!pool.has_element_at(&tc_info_2.addr()).unwrap());
1641        assert_eq!(scheduler.num_scheduled_telecommands(), 1);
1642
1643        // Delete last packet
1644        let addr_1 =
1645            scheduler.delete_by_request_id_and_from_pool(&tc_info_1.request_id(), &mut pool);
1646        assert!(addr_1.is_ok());
1647        assert!(addr_1.unwrap());
1648        assert!(!pool.has_element_at(&tc_info_1.addr()).unwrap());
1649        assert_eq!(scheduler.num_scheduled_telecommands(), 0);
1650    }
1651
1652    #[test]
1653    fn insert_full_store_test() {
1654        let mut scheduler =
1655            PusScheduler::new(UnixTimestamp::new_only_seconds(0), Duration::from_secs(5));
1656
1657        let mut pool = StaticMemoryPool::new(StaticPoolConfig::new(vec![(1, 64)], false));
1658
1659        let mut buf: [u8; 32] = [0; 32];
1660        // Store is full after this.
1661        pool.add(&[0, 1, 2]).unwrap();
1662        let tc = scheduled_tc(UnixTimestamp::new_only_seconds(100), &mut buf);
1663
1664        let insert_res = scheduler.insert_wrapped_tc::<cds::TimeProvider>(&tc, &mut pool);
1665        assert!(insert_res.is_err());
1666        let err = insert_res.unwrap_err();
1667        match err {
1668            ScheduleError::StoreError(e) => match e {
1669                StoreError::StoreFull(_) => {}
1670                _ => panic!("unexpected store error {e}"),
1671            },
1672            _ => panic!("unexpected error {err}"),
1673        }
1674    }
1675
1676    fn insert_command_with_release_time(
1677        pool: &mut StaticMemoryPool,
1678        scheduler: &mut PusScheduler,
1679        seq_count: u16,
1680        release_secs: u64,
1681    ) -> TcInfo {
1682        let mut buf: [u8; 32] = [0; 32];
1683        let tc_info = ping_tc_to_store(pool, &mut buf, seq_count, None);
1684
1685        scheduler
1686            .insert_unwrapped_and_stored_tc(
1687                UnixTimestamp::new_only_seconds(release_secs as i64),
1688                tc_info,
1689            )
1690            .expect("inserting tc failed");
1691        tc_info
1692    }
1693
1694    #[test]
1695    fn test_time_window_retrieval_select_all() {
1696        let mut pool = StaticMemoryPool::new(StaticPoolConfig::new(vec![(10, 32), (5, 64)], false));
1697        let mut scheduler =
1698            PusScheduler::new(UnixTimestamp::new_only_seconds(0), Duration::from_secs(5));
1699        let tc_info_0 = insert_command_with_release_time(&mut pool, &mut scheduler, 0, 50);
1700        let tc_info_1 = insert_command_with_release_time(&mut pool, &mut scheduler, 0, 100);
1701        assert_eq!(scheduler.num_scheduled_telecommands(), 2);
1702        let check_range = |range: Range<UnixTimestamp, Vec<TcInfo>>| {
1703            let mut tcs_in_range = 0;
1704            for (idx, time_bucket) in range.enumerate() {
1705                tcs_in_range += 1;
1706                if idx == 0 {
1707                    assert_eq!(*time_bucket.0, UnixTimestamp::new_only_seconds(50));
1708                    assert_eq!(time_bucket.1.len(), 1);
1709                    assert_eq!(time_bucket.1[0].request_id, tc_info_0.request_id);
1710                } else if idx == 1 {
1711                    assert_eq!(*time_bucket.0, UnixTimestamp::new_only_seconds(100));
1712                    assert_eq!(time_bucket.1.len(), 1);
1713                    assert_eq!(time_bucket.1[0].request_id, tc_info_1.request_id);
1714                }
1715            }
1716            assert_eq!(tcs_in_range, 2);
1717        };
1718        let range = scheduler.retrieve_all();
1719        check_range(range);
1720        let range =
1721            scheduler.retrieve_by_time_filter(TimeWindow::<cds::TimeProvider>::new_select_all());
1722        check_range(range);
1723    }
1724
1725    #[test]
1726    fn test_time_window_retrieval_select_from_stamp() {
1727        let mut pool = StaticMemoryPool::new(StaticPoolConfig::new(vec![(10, 32), (5, 64)], false));
1728        let mut scheduler =
1729            PusScheduler::new(UnixTimestamp::new_only_seconds(0), Duration::from_secs(5));
1730        let _ = insert_command_with_release_time(&mut pool, &mut scheduler, 0, 50);
1731        let tc_info_1 = insert_command_with_release_time(&mut pool, &mut scheduler, 0, 100);
1732        let tc_info_2 = insert_command_with_release_time(&mut pool, &mut scheduler, 0, 150);
1733        let start_stamp =
1734            cds::TimeProvider::from_unix_secs_with_u16_days(&UnixTimestamp::new_only_seconds(100))
1735                .expect("creating start stamp failed");
1736        let time_window = TimeWindow::new_from_time(&start_stamp);
1737        assert_eq!(scheduler.num_scheduled_telecommands(), 3);
1738
1739        let range = scheduler.retrieve_by_time_filter(time_window);
1740        let mut tcs_in_range = 0;
1741        for (idx, time_bucket) in range.enumerate() {
1742            tcs_in_range += 1;
1743            if idx == 0 {
1744                assert_eq!(*time_bucket.0, UnixTimestamp::new_only_seconds(100));
1745                assert_eq!(time_bucket.1.len(), 1);
1746                assert_eq!(time_bucket.1[0].request_id, tc_info_1.request_id());
1747            } else if idx == 1 {
1748                assert_eq!(*time_bucket.0, UnixTimestamp::new_only_seconds(150));
1749                assert_eq!(time_bucket.1.len(), 1);
1750                assert_eq!(time_bucket.1[0].request_id, tc_info_2.request_id());
1751            }
1752        }
1753        assert_eq!(tcs_in_range, 2);
1754    }
1755
1756    #[test]
1757    fn test_time_window_retrieval_select_to_time() {
1758        let mut pool = StaticMemoryPool::new(StaticPoolConfig::new(vec![(10, 32), (5, 64)], false));
1759        let mut scheduler =
1760            PusScheduler::new(UnixTimestamp::new_only_seconds(0), Duration::from_secs(5));
1761        let tc_info_0 = insert_command_with_release_time(&mut pool, &mut scheduler, 0, 50);
1762        let tc_info_1 = insert_command_with_release_time(&mut pool, &mut scheduler, 0, 100);
1763        let _ = insert_command_with_release_time(&mut pool, &mut scheduler, 0, 150);
1764        assert_eq!(scheduler.num_scheduled_telecommands(), 3);
1765
1766        let end_stamp =
1767            cds::TimeProvider::from_unix_secs_with_u16_days(&UnixTimestamp::new_only_seconds(100))
1768                .expect("creating start stamp failed");
1769        let time_window = TimeWindow::new_to_time(&end_stamp);
1770        let range = scheduler.retrieve_by_time_filter(time_window);
1771        let mut tcs_in_range = 0;
1772        for (idx, time_bucket) in range.enumerate() {
1773            tcs_in_range += 1;
1774            if idx == 0 {
1775                assert_eq!(*time_bucket.0, UnixTimestamp::new_only_seconds(50));
1776                assert_eq!(time_bucket.1.len(), 1);
1777                assert_eq!(time_bucket.1[0].request_id, tc_info_0.request_id());
1778            } else if idx == 1 {
1779                assert_eq!(*time_bucket.0, UnixTimestamp::new_only_seconds(100));
1780                assert_eq!(time_bucket.1.len(), 1);
1781                assert_eq!(time_bucket.1[0].request_id, tc_info_1.request_id());
1782            }
1783        }
1784        assert_eq!(tcs_in_range, 2);
1785    }
1786
1787    #[test]
1788    fn test_time_window_retrieval_select_from_time_to_time() {
1789        let mut pool = StaticMemoryPool::new(StaticPoolConfig::new(vec![(10, 32), (5, 64)], false));
1790        let mut scheduler =
1791            PusScheduler::new(UnixTimestamp::new_only_seconds(0), Duration::from_secs(5));
1792        let _ = insert_command_with_release_time(&mut pool, &mut scheduler, 0, 50);
1793        let tc_info_1 = insert_command_with_release_time(&mut pool, &mut scheduler, 0, 100);
1794        let tc_info_2 = insert_command_with_release_time(&mut pool, &mut scheduler, 0, 150);
1795        let _ = insert_command_with_release_time(&mut pool, &mut scheduler, 0, 200);
1796        assert_eq!(scheduler.num_scheduled_telecommands(), 4);
1797
1798        let start_stamp =
1799            cds::TimeProvider::from_unix_secs_with_u16_days(&UnixTimestamp::new_only_seconds(100))
1800                .expect("creating start stamp failed");
1801        let end_stamp =
1802            cds::TimeProvider::from_unix_secs_with_u16_days(&UnixTimestamp::new_only_seconds(150))
1803                .expect("creating end stamp failed");
1804        let time_window = TimeWindow::new_from_time_to_time(&start_stamp, &end_stamp);
1805        let range = scheduler.retrieve_by_time_filter(time_window);
1806        let mut tcs_in_range = 0;
1807        for (idx, time_bucket) in range.enumerate() {
1808            tcs_in_range += 1;
1809            if idx == 0 {
1810                assert_eq!(*time_bucket.0, UnixTimestamp::new_only_seconds(100));
1811                assert_eq!(time_bucket.1.len(), 1);
1812                assert_eq!(time_bucket.1[0].request_id, tc_info_1.request_id());
1813            } else if idx == 1 {
1814                assert_eq!(*time_bucket.0, UnixTimestamp::new_only_seconds(150));
1815                assert_eq!(time_bucket.1.len(), 1);
1816                assert_eq!(time_bucket.1[0].request_id, tc_info_2.request_id());
1817            }
1818        }
1819        assert_eq!(tcs_in_range, 2);
1820    }
1821
1822    #[test]
1823    fn test_deletion_all() {
1824        let mut pool = StaticMemoryPool::new(StaticPoolConfig::new(vec![(10, 32), (5, 64)], false));
1825        let mut scheduler =
1826            PusScheduler::new(UnixTimestamp::new_only_seconds(0), Duration::from_secs(5));
1827        insert_command_with_release_time(&mut pool, &mut scheduler, 0, 50);
1828        insert_command_with_release_time(&mut pool, &mut scheduler, 0, 100);
1829        assert_eq!(scheduler.num_scheduled_telecommands(), 2);
1830        let del_res = scheduler.delete_all(&mut pool);
1831        assert!(del_res.is_ok());
1832        assert_eq!(del_res.unwrap(), 2);
1833        assert_eq!(scheduler.num_scheduled_telecommands(), 0);
1834        // Contrary to reset, this does not disable the scheduler.
1835        assert!(scheduler.is_enabled());
1836
1837        insert_command_with_release_time(&mut pool, &mut scheduler, 0, 50);
1838        insert_command_with_release_time(&mut pool, &mut scheduler, 0, 100);
1839        assert_eq!(scheduler.num_scheduled_telecommands(), 2);
1840        let del_res = scheduler
1841            .delete_by_time_filter(TimeWindow::<cds::TimeProvider>::new_select_all(), &mut pool);
1842        assert!(del_res.is_ok());
1843        assert_eq!(del_res.unwrap(), 2);
1844        assert_eq!(scheduler.num_scheduled_telecommands(), 0);
1845        // Contrary to reset, this does not disable the scheduler.
1846        assert!(scheduler.is_enabled());
1847    }
1848
1849    #[test]
1850    fn test_deletion_from_start_time() {
1851        let mut pool = StaticMemoryPool::new(StaticPoolConfig::new(vec![(10, 32), (5, 64)], false));
1852        let mut scheduler =
1853            PusScheduler::new(UnixTimestamp::new_only_seconds(0), Duration::from_secs(5));
1854        insert_command_with_release_time(&mut pool, &mut scheduler, 0, 50);
1855        let cmd_0_to_delete = insert_command_with_release_time(&mut pool, &mut scheduler, 0, 100);
1856        let cmd_1_to_delete = insert_command_with_release_time(&mut pool, &mut scheduler, 0, 150);
1857        assert_eq!(scheduler.num_scheduled_telecommands(), 3);
1858        let start_stamp =
1859            cds::TimeProvider::from_unix_secs_with_u16_days(&UnixTimestamp::new_only_seconds(100))
1860                .expect("creating start stamp failed");
1861        let time_window = TimeWindow::new_from_time(&start_stamp);
1862        let del_res = scheduler.delete_by_time_filter(time_window, &mut pool);
1863        assert!(del_res.is_ok());
1864        assert_eq!(del_res.unwrap(), 2);
1865        assert_eq!(scheduler.num_scheduled_telecommands(), 1);
1866        assert!(!pool.has_element_at(&cmd_0_to_delete.addr()).unwrap());
1867        assert!(!pool.has_element_at(&cmd_1_to_delete.addr()).unwrap());
1868    }
1869
1870    #[test]
1871    fn test_deletion_to_end_time() {
1872        let mut pool = StaticMemoryPool::new(StaticPoolConfig::new(vec![(10, 32), (5, 64)], false));
1873        let mut scheduler =
1874            PusScheduler::new(UnixTimestamp::new_only_seconds(0), Duration::from_secs(5));
1875        let cmd_0_to_delete = insert_command_with_release_time(&mut pool, &mut scheduler, 0, 50);
1876        let cmd_1_to_delete = insert_command_with_release_time(&mut pool, &mut scheduler, 0, 100);
1877        insert_command_with_release_time(&mut pool, &mut scheduler, 0, 150);
1878        assert_eq!(scheduler.num_scheduled_telecommands(), 3);
1879
1880        let end_stamp =
1881            cds::TimeProvider::from_unix_secs_with_u16_days(&UnixTimestamp::new_only_seconds(100))
1882                .expect("creating start stamp failed");
1883        let time_window = TimeWindow::new_to_time(&end_stamp);
1884        let del_res = scheduler.delete_by_time_filter(time_window, &mut pool);
1885        assert!(del_res.is_ok());
1886        assert_eq!(del_res.unwrap(), 2);
1887        assert_eq!(scheduler.num_scheduled_telecommands(), 1);
1888        assert!(!pool.has_element_at(&cmd_0_to_delete.addr()).unwrap());
1889        assert!(!pool.has_element_at(&cmd_1_to_delete.addr()).unwrap());
1890    }
1891
1892    #[test]
1893    fn test_deletion_from_start_time_to_end_time() {
1894        let mut pool = StaticMemoryPool::new(StaticPoolConfig::new(vec![(10, 32), (5, 64)], false));
1895        let mut scheduler =
1896            PusScheduler::new(UnixTimestamp::new_only_seconds(0), Duration::from_secs(5));
1897        let cmd_out_of_range_0 = insert_command_with_release_time(&mut pool, &mut scheduler, 0, 50);
1898        let cmd_0_to_delete = insert_command_with_release_time(&mut pool, &mut scheduler, 0, 100);
1899        let cmd_1_to_delete = insert_command_with_release_time(&mut pool, &mut scheduler, 0, 150);
1900        let cmd_out_of_range_1 =
1901            insert_command_with_release_time(&mut pool, &mut scheduler, 0, 200);
1902        assert_eq!(scheduler.num_scheduled_telecommands(), 4);
1903
1904        let start_stamp =
1905            cds::TimeProvider::from_unix_secs_with_u16_days(&UnixTimestamp::new_only_seconds(100))
1906                .expect("creating start stamp failed");
1907        let end_stamp =
1908            cds::TimeProvider::from_unix_secs_with_u16_days(&UnixTimestamp::new_only_seconds(150))
1909                .expect("creating end stamp failed");
1910        let time_window = TimeWindow::new_from_time_to_time(&start_stamp, &end_stamp);
1911        let del_res = scheduler.delete_by_time_filter(time_window, &mut pool);
1912        assert!(del_res.is_ok());
1913        assert_eq!(del_res.unwrap(), 2);
1914        assert_eq!(scheduler.num_scheduled_telecommands(), 2);
1915        assert!(pool.has_element_at(&cmd_out_of_range_0.addr()).unwrap());
1916        assert!(!pool.has_element_at(&cmd_0_to_delete.addr()).unwrap());
1917        assert!(!pool.has_element_at(&cmd_1_to_delete.addr()).unwrap());
1918        assert!(pool.has_element_at(&cmd_out_of_range_1.addr()).unwrap());
1919    }
1920
1921    #[test]
1922    fn test_release_without_deletion() {
1923        let mut pool = StaticMemoryPool::new(StaticPoolConfig::new(vec![(10, 32), (5, 64)], false));
1924        let mut scheduler =
1925            PusScheduler::new(UnixTimestamp::new_only_seconds(0), Duration::from_secs(5));
1926
1927        let mut buf: [u8; 32] = [0; 32];
1928        let tc_info_0 = ping_tc_to_store(&mut pool, &mut buf, 0, None);
1929
1930        scheduler
1931            .insert_unwrapped_and_stored_tc(UnixTimestamp::new_only_seconds(100), tc_info_0)
1932            .expect("insertion failed");
1933
1934        let tc_info_1 = ping_tc_to_store(&mut pool, &mut buf, 1, None);
1935        scheduler
1936            .insert_unwrapped_and_stored_tc(UnixTimestamp::new_only_seconds(200), tc_info_1)
1937            .expect("insertion failed");
1938
1939        let mut i = 0;
1940        let mut test_closure_1 = |boolvar: bool, tc_info: &TcInfo, _tc: &[u8]| {
1941            common_check(
1942                boolvar,
1943                &tc_info.addr,
1944                vec![tc_info_0.addr(), tc_info_1.addr()],
1945                &mut i,
1946            );
1947        };
1948
1949        scheduler.update_time(UnixTimestamp::new_only_seconds(205));
1950
1951        let mut tc_buf: [u8; 64] = [0; 64];
1952        let tc_info_vec = scheduler
1953            .release_telecommands_no_deletion(&mut test_closure_1, &pool, &mut tc_buf)
1954            .expect("deletion failed");
1955        assert_eq!(tc_info_vec[0], tc_info_0);
1956        assert_eq!(tc_info_vec[1], tc_info_1);
1957    }
1958
1959    #[test]
1960    fn test_generic_insert_app_data_test() {
1961        let time_writer = cds::TimeProvider::new_with_u16_days(1, 1);
1962        let mut sph = SpHeader::new(
1963            PacketId::const_new(PacketType::Tc, true, 0x002),
1964            PacketSequenceCtrl::const_new(SequenceFlags::Unsegmented, 5),
1965            0,
1966        );
1967        let sec_header = PusTcSecondaryHeader::new_simple(17, 1);
1968        let ping_tc = PusTcCreator::new_no_app_data(&mut sph, sec_header, true);
1969        let mut buf: [u8; 64] = [0; 64];
1970        let result = generate_insert_telecommand_app_data(&mut buf, &time_writer, &ping_tc);
1971        assert!(result.is_ok());
1972        assert_eq!(result.unwrap(), 2 + 7 + ping_tc.len_written());
1973        let n = u16::from_be_bytes(buf[0..2].try_into().unwrap());
1974        assert_eq!(n, 1);
1975        let time_reader = cds::TimeProvider::from_bytes_with_u16_days(&buf[2..2 + 7]).unwrap();
1976        assert_eq!(time_reader, time_writer);
1977        let pus_tc_reader = PusTcReader::new(&buf[9..]).unwrap().0;
1978        assert_eq!(pus_tc_reader, ping_tc);
1979    }
1980
1981    #[test]
1982    fn test_generic_insert_app_data_test_byte_conv_error() {
1983        let time_writer = cds::TimeProvider::new_with_u16_days(1, 1);
1984        let mut sph = SpHeader::new(
1985            PacketId::const_new(PacketType::Tc, true, 0x002),
1986            PacketSequenceCtrl::const_new(SequenceFlags::Unsegmented, 5),
1987            0,
1988        );
1989        let sec_header = PusTcSecondaryHeader::new_simple(17, 1);
1990        let ping_tc = PusTcCreator::new_no_app_data(&mut sph, sec_header, true);
1991        let mut buf: [u8; 16] = [0; 16];
1992        let result = generate_insert_telecommand_app_data(&mut buf, &time_writer, &ping_tc);
1993        assert!(result.is_err());
1994        let error = result.unwrap_err();
1995        if let ScheduleError::ByteConversionError(ByteConversionError::ToSliceTooSmall {
1996            found,
1997            expected,
1998        }) = error
1999        {
2000            assert_eq!(found, 16);
2001            assert_eq!(
2002                expected,
2003                2 + time_writer.len_written() + ping_tc.len_written()
2004            );
2005        } else {
2006            panic!("unexpected error {error}")
2007        }
2008    }
2009
2010    #[test]
2011    fn test_generic_insert_app_data_test_as_vec() {
2012        let time_writer = cds::TimeProvider::new_with_u16_days(1, 1);
2013        let mut sph = SpHeader::new(
2014            PacketId::const_new(PacketType::Tc, true, 0x002),
2015            PacketSequenceCtrl::const_new(SequenceFlags::Unsegmented, 5),
2016            0,
2017        );
2018        let sec_header = PusTcSecondaryHeader::new_simple(17, 1);
2019        let ping_tc = PusTcCreator::new_no_app_data(&mut sph, sec_header, true);
2020        let mut buf: [u8; 64] = [0; 64];
2021        generate_insert_telecommand_app_data(&mut buf, &time_writer, &ping_tc).unwrap();
2022        let vec = generate_insert_telecommand_app_data_as_vec(&time_writer, &ping_tc)
2023            .expect("vec generation failed");
2024        assert_eq!(&buf[..vec.len()], vec);
2025    }
2026}