s2n_quic_core/
state.rs

1// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2// SPDX-License-Identifier: Apache-2.0
3
4use core::fmt;
5
6#[cfg(test)]
7mod tests;
8
9pub type Result<T> = core::result::Result<(), Error<T>>;
10
11#[cfg(feature = "state-tracing")]
12#[doc(hidden)]
13pub use tracing::debug as _debug;
14
15#[cfg(not(feature = "state-tracing"))]
16#[doc(hidden)]
17pub use crate::__tracing_noop__ as _debug;
18
19#[macro_export]
20#[doc(hidden)]
21macro_rules! __state_transition__ {
22    ($state:ident, $valid:pat => $target:expr) => {
23        $crate::state::transition!(@build [], _, $state, [$valid => $target])
24    };
25    (@build [$($targets:expr),*], $event:ident, $state:ident, [$valid:pat => $target:expr] $($remaining:tt)*) => {{
26        // if the transition is valid, then perform it
27        if matches!($state, $valid) {
28            let __event__ = stringify!($event);
29            if __event__.is_empty() || __event__ == "_" {
30                $crate::state::_debug!(prev = ?$state, next = ?$target, location = %core::panic::Location::caller());
31            } else {
32                $crate::state::_debug!(event = %__event__, prev = ?$state, next = ?$target, location = %core::panic::Location::caller());
33            }
34
35            *$state = $target;
36            Ok(())
37        } else {
38            $crate::state::transition!(
39                @build [$($targets,)* $target],
40                $event,
41                $state,
42                $($remaining)*
43            )
44        }
45    }};
46    (@build [$($targets:expr),*], $event:ident, $state:ident $(,)?) => {{
47        let targets = [$($targets),*];
48
49        // if we only have a single target and the current state matches it, then return a no-op
50        if targets.len() == 1 && targets[0].eq($state) {
51            let current = targets[0].clone();
52            Err($crate::state::Error::NoOp { current })
53        } else {
54            // if we didn't get a valid match then error out
55            Err($crate::state::Error::InvalidTransition {
56                current: $state.clone(),
57                event: stringify!($event),
58            })
59        }
60    }};
61}
62
63pub use crate::__state_transition__ as transition;
64
65#[macro_export]
66#[doc(hidden)]
67macro_rules! __state_event__ {
68    (
69        $(#[doc = $doc:literal])*
70        $event:ident (
71            $(
72                $($valid:ident)|* => $target:ident
73            ),*
74            $(,)?
75        )
76    ) => {
77        $(
78            #[doc = $doc]
79        )*
80        #[inline]
81        #[track_caller]
82        pub fn $event(&mut self) -> $crate::state::Result<Self> {
83            $crate::state::transition!(
84                @build [],
85                $event,
86                self,
87                $(
88                    [$(Self::$valid)|* => Self::$target]
89                )*
90            )
91        }
92    };
93    ($(
94        $(#[doc = $doc:literal])*
95        $event:ident (
96            $(
97                $($valid:ident)|* => $target:ident
98            ),*
99            $(,)?
100        );
101    )*) => {
102        $(
103            $crate::state::event!(
104                $(#[doc = $doc])*
105                $event($($($valid)|* => $target),*)
106            );
107        )*
108
109        #[cfg(test)]
110        pub fn test_transitions() -> impl ::core::fmt::Debug {
111            use $crate::state::Error;
112            use ::core::{fmt, result::Result};
113
114            let mut all_states = [
115                // collect all of the states we've observed
116                $($(
117                    $(
118                        (stringify!($valid), Self::$valid),
119                    )*
120                    (stringify!($target), Self::$target),
121                )*)*
122            ];
123
124            all_states.sort_unstable_by_key(|v| v.0);
125            let (sorted, _) = $crate::slice::partition_dedup(&mut all_states);
126
127            const EVENT_LEN: usize = {
128                let mut len = 0;
129                $({
130                    let _ = stringify!($event);
131                    len += 1;
132                })*
133                len
134            };
135
136            let apply = |state: &Self| {
137                [$({
138                    let mut state = state.clone();
139                    let result = state.$event().map(|_| state);
140                    (stringify!($event), result)
141                }),*]
142            };
143
144            struct Transitions<const L: usize, T, A> {
145                states: [(&'static str, T); L],
146                count: usize,
147                apply: A,
148            }
149
150            impl<const L: usize, T, A> fmt::Debug for Transitions<L, T, A>
151            where
152                T: fmt::Debug,
153                A: Fn(&T) -> [(&'static str, Result<T, Error<T>>); EVENT_LEN],
154            {
155                fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
156                    let mut m = f.debug_map();
157
158                    for (name, state) in self.states.iter().take(self.count) {
159                        let events = (self.apply)(state);
160                        m.entry(&format_args!("{name}"), &Entry(events));
161                    }
162
163                    m.finish()
164                }
165            }
166
167            struct Entry<T>([(&'static str, Result<T, Error<T>>); EVENT_LEN]);
168
169            impl<T> fmt::Debug for Entry<T>
170            where
171                T: fmt::Debug
172            {
173                fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
174                    let mut m = f.debug_map();
175
176                    for (event, outcome) in self.0.iter() {
177                        m.entry(&format_args!("{event}"), outcome);
178                    }
179
180                    m.finish()
181                }
182            }
183
184            let count = sorted.len();
185            Transitions {
186                states: all_states,
187                count,
188                apply,
189            }
190        }
191
192        /// Generates a dot graph of all state transitions
193        pub fn dot() -> impl ::core::fmt::Display {
194            struct Dot(&'static str);
195
196            impl ::core::fmt::Display for Dot {
197                fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
198                    writeln!(f, "digraph {{")?;
199                    writeln!(f, "  label = {:?};", self.0)?;
200
201                    let mut all_states = [
202                        // collect all of the states we've observed
203                        $($(
204                            $(
205                                stringify!($valid),
206                            )*
207                            stringify!($target),
208                        )*)*
209                    ];
210
211                    all_states.sort_unstable();
212                    let (all_states, _) = $crate::slice::partition_dedup(&mut all_states);
213
214                    for state in all_states {
215                        writeln!(f, "  {state};")?;
216                    }
217
218                    $($(
219                        $(
220                            writeln!(
221                                f,
222                                "  {} -> {} [label = {:?}];",
223                                stringify!($valid),
224                                stringify!($target),
225                                stringify!($event),
226                            )?;
227                        )*
228                    )*)*
229
230                    writeln!(f, "}}")?;
231                    Ok(())
232                }
233            }
234
235            Dot(::core::any::type_name::<Self>())
236        }
237    }
238}
239
240pub use crate::__state_event__ as event;
241
242#[macro_export]
243#[doc(hidden)]
244macro_rules! __state_is__ {
245    ($(#[doc = $doc:literal])* $function:ident, $($state:ident)|+) => {
246        $(
247            #[doc = $doc]
248        )*
249        #[inline]
250        pub fn $function(&self) -> bool {
251            matches!(self, $(Self::$state)|*)
252        }
253    };
254}
255
256pub use crate::__state_is__ as is;
257
258#[derive(Clone, Copy, Debug, PartialEq, Eq)]
259pub enum Error<T> {
260    NoOp { current: T },
261    InvalidTransition { current: T, event: &'static str },
262}
263
264impl<T: fmt::Debug> fmt::Display for Error<T> {
265    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
266        match self {
267            Self::NoOp { current } => {
268                write!(f, "state is already set to {current:?}")
269            }
270            Self::InvalidTransition { current, event } => {
271                write!(f, "invalid event {event:?} for state {current:?}")
272            }
273        }
274    }
275}
276
277#[cfg(feature = "std")]
278impl<T: fmt::Debug> std::error::Error for Error<T> {}