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}