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