krabka_verified/
timestamp.rs1use creusot_std::prelude::ensures;
4#[cfg(creusot)]
5use creusot_std::prelude::{Int, Seq, invariant, logic};
6
7#[cfg(creusot)]
9#[cfg_attr(test, mutants::skip)]
10#[logic(open)]
11pub(crate) fn first_timestamp_match(
13 timestamps: Seq<i64>,
14 target: Int,
15 matched: Option<usize>,
16) -> bool {
17 pearlite! {
18 match matched {
19 Some(index) => index@ < timestamps.len()
20 && timestamps[index@]@ >= target
21 && forall<i: Int> 0 <= i && i < index@ ==> timestamps[i]@ < target,
22 None => forall<i: Int> 0 <= i && i < timestamps.len() ==> timestamps[i]@ < target,
23 }
24 }
25}
26
27#[ensures(first_timestamp_match(timestamps@, target@, result))]
29#[must_use]
30pub fn first_timestamp_index(timestamps: &[i64], target: i64) -> Option<usize> {
31 let mut index = 0usize;
32 #[invariant(index@ <= [email protected]())]
33 #[invariant(forall<i: Int> 0 <= i && i < index@ ==> timestamps@[i]@ < target@)]
34 #[variant([email protected]() - index@)]
35 while index < timestamps.len() {
36 if matches!(timestamps.get(index), Some(×tamp) if timestamp >= target) {
37 return Some(index);
38 }
39 index += 1;
40 }
41 None
42}
43
44#[ensures(match result {
46 Some(index) => index@ < [email protected]()
47 && forall<i: Int> 0 <= i && i < [email protected]() ==>
48 timestamps@[i]@ <= timestamps@[index@]@
49 && forall<i: Int> 0 <= i && i < index@ ==>
50 timestamps@[i]@ < timestamps@[index@]@,
51 None => [email protected]() == 0,
52})]
53#[must_use]
54pub fn earliest_max_timestamp_index(timestamps: &[i64]) -> Option<usize> {
55 if matches!(timestamps.len(), 0) {
56 return None;
57 }
58 let mut best = 0usize;
59 let mut index = 1usize;
60 #[invariant(best@ < index@)]
61 #[invariant(index@ <= [email protected]())]
62 #[invariant(forall<i: Int> 0 <= i && i < index@ ==>
63 timestamps@[i]@ <= timestamps@[best@]@)]
64 #[invariant(forall<i: Int> 0 <= i && i < best@ ==>
65 timestamps@[i]@ < timestamps@[best@]@)]
66 #[variant([email protected]() - index@)]
67 while index < timestamps.len() {
68 if let (Some(&candidate), Some(¤t)) = (timestamps.get(index), timestamps.get(best))
69 && candidate > current
70 {
71 best = index;
72 }
73 index += 1;
74 }
75 Some(best)
76}
77
78#[ensures(match result {
80 Some((offset, timestamp)) => offset@ == batch_base@ + offset_delta@
81 && timestamp@ == batch_timestamp@ + timestamp_delta@,
82 None => batch_base@ + offset_delta@ < i64::MIN@
83 || batch_base@ + offset_delta@ > i64::MAX@
84 || batch_timestamp@ + timestamp_delta@ < i64::MIN@
85 || batch_timestamp@ + timestamp_delta@ > i64::MAX@,
86})]
87#[must_use]
88pub fn timestamp_record_coordinates(
89 batch_base: i64,
90 offset_delta: i32,
91 batch_timestamp: i64,
92 timestamp_delta: i64,
93) -> Option<(i64, i64)> {
94 Some((
95 batch_base.checked_add(i64::from(offset_delta))?,
96 batch_timestamp.checked_add(timestamp_delta)?,
97 ))
98}
99
100#[ensures(match result {
102 Some(next) => next@ > cursor@
103 && next@ == batch_base@ + last_offset_delta@ + 1,
104 None => last_offset_delta@ < 0
105 || batch_base@ + last_offset_delta@ + 1 > i64::MAX@
106 || batch_base@ + last_offset_delta@ < cursor@,
107})]
108#[must_use]
109pub fn timestamp_scan_next(cursor: i64, batch_base: i64, last_offset_delta: i32) -> Option<i64> {
110 if last_offset_delta < 0 {
111 return None;
112 }
113 let last = batch_base.checked_add(i64::from(last_offset_delta))?;
114 if last < cursor {
115 return None;
116 }
117 last.checked_add(1)
118}
119
120#[ensures(match result {
122 Some(next) => next@ == window@ * 2 && next@ > window@,
123 None => window@ == 0 || window@ * 2 > u32::MAX@,
124})]
125#[must_use]
126pub fn timestamp_scan_window(window: u32) -> Option<u32> {
127 if window == 0 {
128 return None;
129 }
130 window.checked_mul(2)
131}
132
133#[ensures(match result {
137 Some(index) => index@ < [email protected]()
138 && records@[index@].0@ >= minimum@ && records@[index@].1@ >= target@
139 && forall<i: Int> 0 <= i && i < index@
140 ==> records@[i].0@ < minimum@ || records@[i].1@ < target@,
141 None => forall<i: Int> 0 <= i && i < [email protected]()
142 ==> records@[i].0@ < minimum@ || records@[i].1@ < target@,
143})]
144#[must_use]
145pub fn first_timestamp_record_index(
146 records: &[(i64, i64)],
147 minimum: i64,
148 target: i64,
149) -> Option<usize> {
150 let mut index = 0usize;
151 #[invariant(index@ <= [email protected]())]
152 #[invariant(forall<i: Int> 0 <= i && i < index@
153 ==> records@[i].0@ < minimum@ || records@[i].1@ < target@)]
154 #[variant([email protected]() - index@)]
155 while index < records.len() {
156 if matches!(
157 records.get(index),
158 Some(&(offset, timestamp)) if offset >= minimum && timestamp >= target
159 ) {
160 return Some(index);
161 }
162 index += 1;
163 }
164 None
165}
166
167#[ensures(match result {
170 Some(timestamp) => timestamp@ == match append_time {
171 Some(stamp) => stamp@,
172 None => batch_timestamp@ + timestamp_delta@,
173 },
174 None => append_time == None && (batch_timestamp@ + timestamp_delta@ < i64::MIN@
175 || batch_timestamp@ + timestamp_delta@ > i64::MAX@),
176})]
177#[must_use]
178pub fn timestamp_record_time(
179 batch_timestamp: i64,
180 timestamp_delta: i64,
181 append_time: Option<i64>,
182) -> Option<i64> {
183 if let Some(stamp) = append_time {
184 return Some(stamp);
185 }
186 batch_timestamp.checked_add(timestamp_delta)
187}
188
189#[cfg(test)]
190mod tests {
191 use super::{
192 earliest_max_timestamp_index, first_timestamp_index, timestamp_record_coordinates,
193 timestamp_scan_next, timestamp_scan_window,
194 };
195
196 #[test]
197 fn selection_progress_and_overflow_are_fail_closed() {
198 let timestamps = [5, 9, 9, 3];
199 assert2::assert!(first_timestamp_index(×tamps, 9) == Some(1));
200 assert2::assert!(first_timestamp_index(×tamps, 10).is_none());
201 assert2::assert!(earliest_max_timestamp_index(×tamps) == Some(1));
202 assert2::assert!(earliest_max_timestamp_index(&[]).is_none());
203 assert2::assert!(timestamp_record_coordinates(10, 2, 100, 3) == Some((12, 103)));
204 assert2::assert!(timestamp_record_coordinates(i64::MAX, 1, 0, 0).is_none());
205 assert2::assert!(timestamp_record_coordinates(0, 0, i64::MAX, 1).is_none());
206 assert2::assert!(timestamp_scan_next(10, 8, 2) == Some(11));
207 assert2::assert!(timestamp_scan_next(10, 10, 0) == Some(11));
208 assert2::assert!(timestamp_scan_next(10, 8, 1).is_none());
209 assert2::assert!(timestamp_scan_next(i64::MAX, i64::MAX, 0).is_none());
210 assert2::assert!(timestamp_scan_window(1) == Some(2));
211 assert2::assert!(timestamp_scan_window(u32::MAX).is_none());
212 }
213}