citadel_crypt/
sync_toggle.rs

1//! Thread-Safe Toggle State Management
2//!
3//! This module provides a thread-safe toggle implementation for managing atomic
4//! state transitions. It supports one-way state changes with detection of previous
5//! state, making it ideal for managing cryptographic state transitions.
6//!
7//! # Features
8//!
9//! - Thread-safe state management
10//! - Atomic state transitions
11//! - One-way toggle operations
12//! - State change detection
13//! - Serialization support
14//! - Default state handling
15//!
16//! # Examples
17//!
18//! ```rust
19//! use citadel_crypt::sync_toggle::{SyncToggle, CurrentToggleState};
20//!
21//! fn manage_state() {
22//!     // Create new toggle
23//!     let toggle = SyncToggle::new();
24//!     
25//!     // Try to toggle on
26//!     match toggle.toggle_on_if_untoggled() {
27//!         CurrentToggleState::JustToggled => println!("First toggle"),
28//!         CurrentToggleState::AlreadyToggled => println!("Already on"),
29//!         CurrentToggleState::Untoggled => println!("Still off"),
30//!     }
31//!     
32//!     // Check current state
33//!     let state = toggle.state();
34//!     
35//!     // Reset to off
36//!     toggle.toggle_off();
37//! }
38//! # manage_state();
39//! ```
40//!
41//! # Important Notes
42//!
43//! - Uses sequential consistency ordering
44//! - Thread-safe through atomic operations
45//! - Serializes to untoggled state by default
46//! - No blocking operations
47//! - Safe for concurrent access
48//!
49//! # Related Components
50//!
51//! - [`crate::ratchets::entropy_bank`] - Uses toggle for state transitions
52//! - [`crate::ratchets::stacked::ratchet`] - Ratchet state management
53//!
54
55use serde::{Deserialize, Serialize};
56
57/// A thread-safe toggle for managing atomic state transitions.
58#[derive(Default, Serialize, Deserialize, Debug, Clone)]
59pub struct SyncToggle {
60    // when serializing this, always reset to default (untoggled/false)
61    #[serde(default)]
62    inner: std::sync::Arc<std::sync::atomic::AtomicBool>,
63}
64
65const ORDERING: std::sync::atomic::Ordering = std::sync::atomic::Ordering::Relaxed;
66
67/// Represents the current state of a toggle operation.
68#[derive(Debug, Eq, PartialEq, Copy, Clone)]
69pub enum CurrentToggleState {
70    /// Toggle was just changed from false to true
71    JustToggled,
72    /// Toggle was already in true state
73    AlreadyToggled,
74    /// Toggle is in false state
75    Untoggled,
76}
77
78impl SyncToggle {
79    /// Creates a new toggle instance.
80    pub fn new() -> Self {
81        Self {
82            inner: Default::default(),
83        }
84    }
85
86    /// Attempts to toggle the state to true if it's currently false.
87    ///
88    /// Returns `JustToggled` if the state was changed to true, `AlreadyToggled` if the state was already true
89    pub fn toggle_on_if_untoggled(&self) -> CurrentToggleState {
90        if self.inner.fetch_nand(false, ORDERING) {
91            CurrentToggleState::AlreadyToggled
92        } else {
93            CurrentToggleState::JustToggled
94        }
95    }
96
97    /// Resets the toggle state to false and returns the previous state.
98    ///
99    /// Returns `AlreadyToggled` if it was previously true, `Untoggled` if it was already false
100    pub fn reset_and_get_previous(&self) -> CurrentToggleState {
101        if self.inner.fetch_and(false, ORDERING) {
102            CurrentToggleState::AlreadyToggled
103        } else {
104            CurrentToggleState::Untoggled
105        }
106    }
107
108    /// Resets the toggle state to false.
109    pub fn toggle_off(&self) {
110        self.inner.store(false, ORDERING)
111    }
112
113    /// Returns the current state of the toggle.
114    pub fn state(&self) -> CurrentToggleState {
115        if self.inner.load(ORDERING) {
116            CurrentToggleState::AlreadyToggled
117        } else {
118            CurrentToggleState::Untoggled
119        }
120    }
121}
122
123#[cfg(test)]
124mod tests {
125    use crate::sync_toggle::{CurrentToggleState, SyncToggle};
126
127    #[test]
128    fn test_sync_toggle() {
129        let toggle = SyncToggle::new();
130        assert_eq!(toggle.state(), CurrentToggleState::Untoggled);
131        assert_eq!(
132            toggle.toggle_on_if_untoggled(),
133            CurrentToggleState::JustToggled
134        );
135        toggle.toggle_off();
136        assert_eq!(toggle.state(), CurrentToggleState::Untoggled);
137        assert_eq!(
138            toggle.toggle_on_if_untoggled(),
139            CurrentToggleState::JustToggled
140        );
141        assert_eq!(toggle.state(), CurrentToggleState::AlreadyToggled);
142        assert_eq!(
143            toggle.toggle_on_if_untoggled(),
144            CurrentToggleState::AlreadyToggled
145        );
146        assert_eq!(toggle.state(), CurrentToggleState::AlreadyToggled);
147        assert_eq!(
148            toggle.toggle_on_if_untoggled(),
149            CurrentToggleState::AlreadyToggled
150        );
151        assert_eq!(toggle.state(), CurrentToggleState::AlreadyToggled);
152        toggle.toggle_off();
153        assert_eq!(toggle.state(), CurrentToggleState::Untoggled);
154        assert_eq!(
155            toggle.toggle_on_if_untoggled(),
156            CurrentToggleState::JustToggled
157        );
158    }
159
160    #[test]
161    fn test_sync_toggle_reset_and_get_previous() {
162        let toggle = SyncToggle::new();
163
164        // Test resetting when already false
165        assert_eq!(toggle.state(), CurrentToggleState::Untoggled);
166        assert_eq!(
167            toggle.reset_and_get_previous(),
168            CurrentToggleState::Untoggled
169        );
170        assert_eq!(toggle.state(), CurrentToggleState::Untoggled);
171
172        // Test resetting when true
173        let _ = toggle.toggle_on_if_untoggled();
174        assert_eq!(toggle.state(), CurrentToggleState::AlreadyToggled);
175        assert_eq!(
176            toggle.reset_and_get_previous(),
177            CurrentToggleState::AlreadyToggled
178        );
179        assert_eq!(toggle.state(), CurrentToggleState::Untoggled);
180    }
181}