1use crate::NQuin;
2
3pub const OP_LTL_GLOBALLY: u8 = 0x40;
4pub const OP_LTL_FINALLY: u8 = 0x41;
5pub const OP_LTL_NEXT: u8 = 0x42;
6pub const OP_LTL_UNTIL: u8 = 0x43;
7pub const OP_LTL_RELEASE: u8 = 0x44;
8
9#[repr(C)]
10#[derive(Debug, Clone, Copy, PartialEq, Eq)]
11pub enum LtlFormula {
12 Globally(u64),
13 Finally(u64),
14 Next(u64),
15 Until { ante: u64, consequent: u64 },
16 Release { trigger: u64, invariant: u64 },
17}
18
19#[derive(Debug)]
20pub enum TemporalError {
21 AbortedTimeout,
22}
23
24pub fn evaluate_lock_lease(
25 lock_granted_at: u64,
26 current_time: u64,
27 ttl_seconds: u64,
28) -> Result<(), TemporalError> {
29 if current_time > lock_granted_at + ttl_seconds {
30 return Err(TemporalError::AbortedTimeout);
31 }
32 Ok(())
33}
34
35pub fn evaluate_ltl_trace(trace: &[NQuin], formula: &LtlFormula) -> bool {
36 match formula {
37 LtlFormula::Globally(p) => {
38 if trace.is_empty() {
39 return false;
40 }
41 for quin in trace {
42 if quin.predicate != *p {
43 return false;
44 }
45 }
46 true
47 }
48 LtlFormula::Finally(p) => {
49 if trace.is_empty() {
50 return false;
51 }
52 for quin in trace {
53 if quin.predicate == *p {
54 return true;
55 }
56 }
57 false
58 }
59 LtlFormula::Next(p) => {
60 if trace.len() < 2 {
61 return false;
62 }
63 trace[1].predicate == *p
64 }
65 LtlFormula::Until { ante, consequent } => {
66 if trace.is_empty() {
67 return false;
68 }
69 for (i, quin) in trace.iter().enumerate() {
70 if quin.predicate == *consequent {
71 let mut ante_held = true;
72 for j in 0..i {
73 if trace[j].predicate != *ante {
74 ante_held = false;
75 break;
76 }
77 }
78 if ante_held {
79 return true;
80 }
81 }
82 }
83 false
84 }
85 LtlFormula::Release { trigger, invariant } => {
86 if trace.is_empty() {
87 return true;
88 }
89 for (i, quin) in trace.iter().enumerate() {
90 if quin.predicate != *invariant {
91 let mut triggered = false;
92 for j in 0..=i {
93 if trace[j].predicate == *trigger {
94 triggered = true;
95 break;
96 }
97 }
98 if !triggered {
99 return false;
100 }
101 }
102 }
103 true
104 }
105 }
106}
107
108pub fn holds_within(trace: &[NQuin], trigger: u64, target: u64, window: u64) -> bool {
113 let mut t0: Option<u64> = None;
115 for q in trace {
116 if q.predicate == trigger {
117 t0 = Some(match t0 {
118 Some(prev) => prev.min(q.metadata),
119 None => q.metadata,
120 });
121 }
122 }
123 let t0 = match t0 {
124 Some(t) => t,
125 None => return false, };
127 let deadline = t0.saturating_add(window);
128 for q in trace {
129 if q.predicate == target && q.metadata >= t0 && q.metadata <= deadline {
130 return true;
131 }
132 }
133 false
134}
135
136pub fn holds_globally_within(trace: &[NQuin], trigger: u64, invariant: u64, window: u64) -> bool {
140 let mut t0: Option<u64> = None;
141 for q in trace {
142 if q.predicate == trigger {
143 t0 = Some(t0.map_or(q.metadata, |p| p.min(q.metadata)));
144 }
145 }
146 let t0 = match t0 {
147 Some(t) => t,
148 None => return false,
149 };
150 let deadline = t0.saturating_add(window);
151 for q in trace {
153 if q.metadata >= t0
154 && q.metadata <= deadline
155 && q.predicate != invariant
156 && q.predicate != trigger
157 {
158 return false;
159 }
160 }
161 true
162}
163
164pub fn evaluate_once(trace: &[NQuin], p: u64) -> bool {
168 trace.iter().any(|q| q.predicate == p)
169}
170
171pub fn evaluate_historically(trace: &[NQuin], p: u64) -> bool {
174 trace.iter().all(|q| q.predicate == p)
175}
176
177pub fn evaluate_since(trace: &[NQuin], p: u64, q: u64) -> bool {
181 let mut q_at: Option<usize> = None;
183 for (i, ev) in trace.iter().enumerate() {
184 if ev.predicate == q {
185 q_at = Some(i);
186 }
187 }
188 match q_at {
189 None => false,
190 Some(qi) => trace[qi + 1..].iter().all(|ev| ev.predicate == p),
191 }
192}
193
194#[derive(Debug, Clone, Copy, PartialEq, Eq)]
200pub struct SafetyMonitor {
201 pub invariant: u64,
202 pub violated: bool,
203}
204
205impl SafetyMonitor {
206 pub fn new(invariant: u64) -> Self {
208 Self {
209 invariant,
210 violated: false,
211 }
212 }
213 pub fn step(&mut self, event_predicate: u64) -> bool {
215 if event_predicate != self.invariant {
216 self.violated = true;
217 }
218 !self.violated
219 }
220 #[inline]
222 pub fn is_safe(&self) -> bool {
223 !self.violated
224 }
225}
226
227pub fn predicate_interval(trace: &[NQuin], predicate: u64) -> Option<(u64, u64)> {
233 let mut lo: Option<u64> = None;
234 let mut hi: Option<u64> = None;
235 for q in trace {
236 if q.predicate == predicate {
237 lo = Some(lo.map_or(q.metadata, |v| v.min(q.metadata)));
238 hi = Some(hi.map_or(q.metadata, |v| v.max(q.metadata)));
239 }
240 }
241 Some((lo?, hi?))
242}
243
244#[cfg(test)]
245mod tests {
246 use super::*;
247 use crate::NQuin;
248
249 fn timed(predicate: u64, t: u64) -> NQuin {
250 NQuin {
251 subject: 0,
252 predicate,
253 object: 0,
254 context: 0,
255 metadata: t,
256 parity: 0,
257 }
258 }
259
260 #[test]
261 fn test_mtl_holds_within() {
262 let breach = 1u64;
263 let remedy = 2u64;
264 let trace = [timed(breach, 10), timed(remedy, 25)];
266 assert!(
267 holds_within(&trace, breach, remedy, 30),
268 "remedy 15 after breach ≤ 30 window"
269 );
270 assert!(
271 !holds_within(&trace, breach, remedy, 10),
272 "remedy 15 after breach > 10 window"
273 );
274 let late = [timed(remedy, 5), timed(breach, 10)];
276 assert!(
277 !holds_within(&late, breach, remedy, 30),
278 "a remedy before the breach is not within"
279 );
280 assert!(!holds_within(&[timed(remedy, 25)], breach, remedy, 30));
282 }
283
284 fn make_quin(predicate: u64) -> NQuin {
285 NQuin {
286 subject: 0,
287 predicate,
288 object: 0,
289 context: 0,
290 metadata: 0,
291 parity: 0,
292 }
293 }
294
295 #[test]
296 fn test_ltl_globally() {
297 let p = 100;
298 let q_p = make_quin(p);
299 let q_not_p = make_quin(99);
300
301 assert!(evaluate_ltl_trace(
302 &[q_p, q_p, q_p],
303 &LtlFormula::Globally(p)
304 ));
305 assert!(!evaluate_ltl_trace(
306 &[q_p, q_not_p, q_p],
307 &LtlFormula::Globally(p)
308 ));
309 assert!(!evaluate_ltl_trace(&[], &LtlFormula::Globally(p)));
310 }
311
312 #[test]
313 fn test_ltl_finally() {
314 let p = 100;
315 let q_p = make_quin(p);
316 let q_not_p = make_quin(99);
317
318 assert!(evaluate_ltl_trace(
319 &[q_not_p, q_not_p, q_p],
320 &LtlFormula::Finally(p)
321 ));
322 assert!(!evaluate_ltl_trace(
323 &[q_not_p, q_not_p],
324 &LtlFormula::Finally(p)
325 ));
326 assert!(!evaluate_ltl_trace(&[], &LtlFormula::Finally(p)));
327 }
328
329 #[test]
330 fn test_ltl_next() {
331 let p = 100;
332 let q_p = make_quin(p);
333 let q_not_p = make_quin(99);
334
335 assert!(evaluate_ltl_trace(&[q_not_p, q_p], &LtlFormula::Next(p)));
336 assert!(!evaluate_ltl_trace(&[q_p, q_not_p], &LtlFormula::Next(p)));
337 assert!(!evaluate_ltl_trace(&[q_p], &LtlFormula::Next(p)));
338 assert!(!evaluate_ltl_trace(&[], &LtlFormula::Next(p)));
339 }
340
341 #[test]
342 fn test_ltl_until() {
343 let p = 100;
344 let q = 200;
345 let q_p = make_quin(p);
346 let q_q = make_quin(q);
347 let q_other = make_quin(99);
348
349 assert!(evaluate_ltl_trace(
350 &[q_p, q_p, q_q],
351 &LtlFormula::Until {
352 ante: p,
353 consequent: q
354 }
355 ));
356 assert!(evaluate_ltl_trace(
357 &[q_q],
358 &LtlFormula::Until {
359 ante: p,
360 consequent: q
361 }
362 ));
363 assert!(!evaluate_ltl_trace(
364 &[q_p, q_p, q_p],
365 &LtlFormula::Until {
366 ante: p,
367 consequent: q
368 }
369 ));
370 assert!(!evaluate_ltl_trace(
371 &[q_p, q_other, q_q],
372 &LtlFormula::Until {
373 ante: p,
374 consequent: q
375 }
376 ));
377 assert!(!evaluate_ltl_trace(
378 &[],
379 &LtlFormula::Until {
380 ante: p,
381 consequent: q
382 }
383 ));
384 }
385
386 #[test]
387 fn test_ltl_release() {
388 let trigger = 100;
389 let invariant = 200;
390 let q_t = make_quin(trigger);
391 let q_i = make_quin(invariant);
392 let q_other = make_quin(99);
393
394 assert!(evaluate_ltl_trace(
395 &[q_i, q_i, q_i],
396 &LtlFormula::Release { trigger, invariant }
397 ));
398 assert!(evaluate_ltl_trace(
399 &[q_i, q_t, q_other],
400 &LtlFormula::Release { trigger, invariant }
401 ));
402 assert!(!evaluate_ltl_trace(
403 &[q_i, q_other, q_t],
404 &LtlFormula::Release { trigger, invariant }
405 ));
406 assert!(evaluate_ltl_trace(
407 &[],
408 &LtlFormula::Release { trigger, invariant }
409 ));
410 }
411
412 #[test]
413 fn past_time_ltl_once_historically_since() {
414 let (breach, duty, repaired) = (1u64, 2u64, 3u64);
415 let trace = [make_quin(breach), make_quin(duty), make_quin(duty)];
416 assert!(evaluate_once(&trace, breach));
418 assert!(!evaluate_once(&trace, repaired));
419 assert!(!evaluate_historically(&trace, duty));
421 assert!(evaluate_historically(
422 &[make_quin(duty), make_quin(duty)],
423 duty
424 ));
425 assert!(evaluate_since(&trace, duty, breach));
427 assert!(!evaluate_since(&trace, duty, repaired));
429 }
430
431 #[test]
432 fn streaming_safety_monitor_latches_on_violation() {
433 let safe = 100u64;
434 let mut mon = SafetyMonitor::new(safe);
435 assert!(mon.step(safe) && mon.step(safe), "still safe");
436 assert!(!mon.step(999), "a non-conforming event violates G(p)");
437 assert!(!mon.step(safe), "violation latches permanently");
438 assert!(!mon.is_safe());
439 }
440
441 #[test]
442 fn metric_globally_and_allen_interval() {
443 let (start, ok, bad) = (1u64, 2u64, 3u64);
444 let good = [timed(start, 10), timed(ok, 20), timed(ok, 35)];
446 assert!(holds_globally_within(&good, start, ok, 30));
447 let broken = [timed(start, 10), timed(bad, 25)];
449 assert!(!holds_globally_within(&broken, start, ok, 30));
450 assert_eq!(predicate_interval(&good, ok), Some((20, 35)));
452 assert_eq!(predicate_interval(&good, bad), None);
453 }
454}