status/
status.rs

1#![warn(unsafe_op_in_unsafe_fn)]
2#![allow(non_camel_case_types)]
3#![allow(unused_unsafe)]
4#![allow(clippy::missing_safety_doc)]
5#![allow(clippy::new_without_default)]
6
7/// A simple little state machine for a system's status.  This module
8/// represents the Rust library being exposed via FFI.
9mod hittr {
10    #[derive(Clone, Copy, PartialEq, Eq)]
11    pub enum Status {
12        Ready,
13        Running { count: u32 },
14        Failed,
15    }
16
17    pub struct System {
18        pub status: Status,
19    }
20
21    /// ```c
22    /// typedef struct hittr_system_t hittr_system_t;
23    /// ```
24    impl System {
25        pub fn new() -> System {
26            System {
27                status: Status::Ready,
28            }
29        }
30
31        pub fn new_network(_port: u16) -> Result<System, ()> {
32            // (this constructor is just to have an example of a fallible constructor)
33            Ok(System {
34                status: Status::Ready,
35            })
36        }
37
38        pub fn run(&mut self) {
39            if self.status != Status::Ready {
40                self.status = Status::Failed;
41            } else {
42                self.status = Status::Running { count: 0 };
43            }
44        }
45
46        pub fn count_hit(&mut self) {
47            if let Status::Running { count } = self.status {
48                if count >= 5 {
49                    self.status = Status::Failed;
50                    return;
51                }
52                self.status = Status::Running { count: count + 1 };
53            } else {
54                self.status = Status::Failed;
55            }
56        }
57    }
58}
59
60mod status {
61    use super::hittr::Status;
62    use ffizz_passby::Value;
63
64    #[allow(non_camel_case_types)]
65    #[repr(C)]
66    pub struct hittr_status_t {
67        pub status: u8,
68        pub count: u32,
69    }
70
71    pub const HITTR_STATUS_READY: u8 = 1;
72    pub const HITTR_STATUS_RUNNING: u8 = 2;
73    pub const HITTR_STATUS_FAILED: u8 = 3;
74
75    impl Into<Status> for hittr_status_t {
76        fn into(self) -> Status {
77            match self.status {
78                HITTR_STATUS_READY => Status::Ready,
79                HITTR_STATUS_RUNNING => Status::Running { count: self.count },
80                HITTR_STATUS_FAILED => Status::Failed,
81                _ => panic!("invalid status value"),
82            }
83        }
84    }
85
86    impl From<Status> for hittr_status_t {
87        fn from(rval: Status) -> hittr_status_t {
88            match rval {
89                Status::Ready => hittr_status_t {
90                    status: HITTR_STATUS_READY,
91                    count: 0,
92                },
93                Status::Running { count } => hittr_status_t {
94                    status: HITTR_STATUS_RUNNING,
95                    count,
96                },
97                Status::Failed => hittr_status_t {
98                    status: HITTR_STATUS_FAILED,
99                    count: 0,
100                },
101            }
102        }
103    }
104
105    pub type StatusValue = Value<Status, hittr_status_t>;
106}
107
108use ffizz_passby::Boxed;
109use hittr::*;
110use status::*;
111
112type BoxedSystem = Boxed<System>;
113
114/// Create a new Hittr system.
115///
116/// # Safety
117///
118/// The returned hittr_system_t must be freed with hittr_system_free.
119///
120/// ```c
121/// hittr_system_t *hittr_system_new();
122/// ```
123#[no_mangle]
124pub unsafe extern "C" fn hittr_system_new() -> *mut System {
125    let sys = System::new();
126    // SAFETY: function docs indicate value must be freed
127    unsafe { BoxedSystem::return_val(sys) }
128}
129
130/// Create a new Hittr system with a network port.  This returns true
131/// on success.  On failure, the output argument is not changed.
132///
133/// # Safety
134///
135/// The system_out argument must ne non-NULL and point to a valid, properly aligned
136/// `*hittr_system_t`.  The returned hittr_system_t must be freed with hittr_system_free.
137///
138/// ```c
139/// bool hittr_system_new_network(hittr_system_t **system_out, uint16_t port);
140/// ```
141#[no_mangle]
142pub unsafe extern "C" fn hittr_system_new_network(system_out: *mut *mut System, port: u16) -> bool {
143    if let Ok(sys) = System::new_network(port) {
144        // SAFETY: see docstring
145        unsafe { BoxedSystem::to_out_param(sys, system_out) }
146        true
147    } else {
148        false
149    }
150}
151
152/// Free a Hittr system.
153///
154/// # Safety
155///
156/// The system must be non-NULL and point to a valid hittr_system_t. After this call it is no
157/// longer valid and must not be used.
158///
159/// ```c
160/// void hittr_system_free(hittr_system_t *system);
161/// ```
162#[no_mangle]
163pub unsafe extern "C" fn hittr_system_free(system: *mut System) {
164    // SAFETY:
165    //  - system is valid and not NULL (see docstring)
166    //  - caller will not use system after this call (see docstring)
167    unsafe { BoxedSystem::take_nonnull(system) };
168    // (System is implicitly dropped)
169}
170
171/// Run the Hittr system.
172///
173/// If the sytem is already running, it will enter the failed state.
174///
175/// # Safety
176///
177/// The system must be non-NULL and point to a valid hittr_system_t.
178///
179/// ```c
180/// void hittr_system_run(hittr_system_t *system);
181/// ```
182#[no_mangle]
183pub unsafe extern "C" fn hittr_system_run(system: *mut System) {
184    // SAFETY:
185    // - system is not NULL and valid (see docstring)
186    // - system is valid for the life of this function (documented as not threadsafe)
187    // - system will not be accessed during the life of this function (documented as not threadsafe)
188    unsafe {
189        BoxedSystem::with_ref_mut_nonnull(system, |system| {
190            system.run();
191        });
192    }
193}
194
195/// Record a hit on thi Hittr system.
196///
197/// If the sytem is not running, it will enter the failed state.  If it counts 5
198/// or more hits, it will enter the failed.state.
199///
200/// # Safety
201///
202/// The system must be non-NULL and point to a valid hittr_system_t.
203///
204/// ```c
205/// void hittr_system_count_hit(hittr_system_t *system);
206/// ```
207#[no_mangle]
208pub unsafe extern "C" fn hittr_system_count_hit(system: *mut System) {
209    // SAFETY:
210    // - system is not NULL and valid (see docstring)
211    // - system is valid for the life of this function (documented as not threadsafe)
212    // - system will not be accessed during the life of this function (documented as not threadsafe)
213    unsafe {
214        BoxedSystem::with_ref_mut_nonnull(system, |system| {
215            system.count_hit();
216        });
217    }
218}
219
220/// Get the current system status.
221///
222/// The system must be non-NULL and point to a valid hittr_system_t.
223///
224/// ```c
225/// hittr_status_t hittr_system_status(hittr_system_t *system);
226/// ```
227#[no_mangle]
228pub unsafe extern "C" fn hittr_system_status(system: *const System) -> hittr_status_t {
229    // SAFETY:
230    // - system is not NULL and valid (see docstring)
231    // - system is valid for the life of this function (documented as not threadsafe)
232    // - system will not be modified during the life of this function (documented as not threadsafe)
233    unsafe {
234        BoxedSystem::with_ref_nonnull(system, |system| {
235            // SAFETY:
236            // - hittr_status_t is not allocated, so no issues
237            unsafe { StatusValue::return_val(system.status) }
238        })
239    }
240}
241
242fn main() {
243    let sys = unsafe { hittr_system_new() };
244
245    let st = unsafe { hittr_system_status(sys) };
246    assert_eq!(st.status, HITTR_STATUS_READY);
247    assert_eq!(st.count, 0);
248
249    unsafe { hittr_system_run(sys) };
250
251    let st = unsafe { hittr_system_status(sys) };
252    assert_eq!(st.status, HITTR_STATUS_RUNNING);
253    assert_eq!(st.count, 0);
254
255    for i in 1..=5 {
256        unsafe { hittr_system_count_hit(sys) };
257        let st = unsafe { hittr_system_status(sys) };
258        assert_eq!(st.status, HITTR_STATUS_RUNNING);
259        assert_eq!(st.count, i);
260    }
261
262    unsafe { hittr_system_count_hit(sys) }; // 5th hit causes system failure
263    let st = unsafe { hittr_system_status(sys) };
264    assert_eq!(st.status, HITTR_STATUS_FAILED);
265    assert_eq!(st.count, 0);
266
267    unsafe { hittr_system_free(sys) };
268
269    // this is awkward to call from Rust, but would be pretty natural in C
270    let mut sys: *mut System = std::ptr::null_mut();
271    assert!(unsafe { hittr_system_new_network(&mut sys as *mut *mut System, 1300) });
272    let st = unsafe { hittr_system_status(sys) };
273    assert_eq!(st.status, HITTR_STATUS_READY);
274    assert_eq!(st.count, 0);
275}