1use creusot_std::prelude::*;
4
5#[ensures(result == (high_watermark@ > epoch_start_offset@))]
10#[must_use]
11pub fn wal_reservation_epoch_ready(high_watermark: i64, epoch_start_offset: i64) -> bool {
12 high_watermark > epoch_start_offset
13}
14
15#[ensures(match result {
20 Some(next) => current@ >= 0
21 && pending_base@ == current@
22 && pending_count@ > 0
23 && next@ == current@ + pending_count@
24 && next@ <= i64::MAX@,
25 None => current@ < 0
26 || pending_base@ != current@
27 || pending_count@ <= 0
28 || current@ + pending_count@ > i64::MAX@,
29})]
30#[must_use]
31pub fn wal_reservation_frontier(
32 current: i64,
33 pending_base: i64,
34 pending_count: i64,
35) -> Option<i64> {
36 if current < 0 || pending_base != current || pending_count <= 0 {
37 return None;
38 }
39 current.checked_add(pending_count)
40}
41
42#[ensures(match result {
48 Some(admitted_base) => request_matches
49 && response_epoch@ >= 0
50 && (request_epoch@ == -1
51 || (request_epoch@ >= 0
52 && observed_epoch@ == request_epoch@
53 && response_epoch@ == request_epoch@))
54 && base@ >= 0
55 && count@ > 0
56 && admitted_base@ == base@
57 && base@ + count@ <= i64::MAX@,
58 None => !request_matches
59 || response_epoch@ < 0
60 || (request_epoch@ != -1
61 && (request_epoch@ < 0
62 || observed_epoch@ != request_epoch@
63 || response_epoch@ != request_epoch@))
64 || base@ < 0
65 || count@ <= 0
66 || base@ + count@ > i64::MAX@,
67})]
68#[must_use]
69pub fn wal_reservation_response(
70 request_matches: bool,
71 request_epoch: i64,
72 observed_epoch: i64,
73 response_epoch: i64,
74 base: i64,
75 count: i64,
76) -> Option<i64> {
77 if !request_matches || response_epoch < 0 {
78 return None;
79 }
80 if request_epoch != -1
81 && (request_epoch < 0 || observed_epoch != request_epoch || response_epoch != request_epoch)
82 {
83 return None;
84 }
85 if base < 0 || count <= 0 {
86 return None;
87 }
88 base.checked_add(count).map(|_| base)
89}
90
91#[ensures((result != None) == (next@ >= 0 && count@ > 0 && next@ + count@ <= i64::MAX@))]
97#[ensures(forall<base: i64, next_after: i64> result == Some((base, next_after))
98 ==> base@ == next@ && next_after@ == next@ + count@)]
99#[must_use]
100pub const fn reserve_offsets(next: i64, count: i64) -> Option<(i64, i64)> {
101 if next < 0 || count <= 0 {
102 return None;
103 }
104 match next.checked_add(count) {
105 Some(next_after) => Some((next, next_after)),
106 None => None,
107 }
108}
109
110#[cfg(test)]
111mod tests {
112 use {super::*, assert2::assert};
113
114 #[test]
115 fn reserve_offsets_admits_exactly_representable_ranges() {
116 let cases = [
118 (0, 1, Some((0, 1))),
119 (11, 3, Some((11, 14))),
120 (i64::MAX - 3, 3, Some((i64::MAX - 3, i64::MAX))),
121 (i64::MAX - 1, 3, None),
122 (11, 0, None),
123 (11, -1, None),
124 (-1, 3, None),
125 ];
126 for (next, count, reservation) in cases {
127 assert!(reserve_offsets(next, count) == reservation);
128 }
129 }
130
131 #[test]
132 fn pending_frontier_is_exact_contiguous_and_bounded() {
133 assert!(wal_reservation_frontier(0, 0, 1) == Some(1));
134 assert!(wal_reservation_frontier(10, 10, 3) == Some(13));
135 assert!(wal_reservation_frontier(10, 9, 3).is_none());
136 assert!(wal_reservation_frontier(10, 11, 3).is_none());
137 assert!(wal_reservation_frontier(10, 10, 0).is_none());
138 assert!(wal_reservation_frontier(-1, -1, 1).is_none());
139 assert!(wal_reservation_frontier(i64::MAX, i64::MAX, 1).is_none());
140 }
141
142 #[test]
143 fn leader_epoch_must_be_committed_before_reservation() {
144 assert!(!wal_reservation_epoch_ready(7, 7));
145 assert!(wal_reservation_epoch_ready(8, 7));
146 }
147
148 #[yare::parameterized(
149 first_offset = { true, 0, 0, 0, 0, 1, Some(0) },
150 current_epoch = { true, 7, 7, 7, 11, 3, Some(11) },
151 unknown_epoch_sentinel = { true, -1, -1, 7, 11, 3, Some(11) },
152 not_leader = { false, 7, 7, 7, 11, 3, None },
153 claimed_identity_mismatch = { true, 7, 8, 7, 11, 3, None },
154 local_epoch_mismatch = { true, 7, 7, 6, 11, 3, None },
155 invalid_identity = { true, -2, -2, 7, 11, 3, None },
156 unknown_epoch_with_no_local_epoch = { true, -1, -1, -1, 11, 3, None },
157 negative_next = { true, 7, 7, 7, -1, 3, None },
158 empty_count = { true, 7, 7, 7, 11, 0, None },
159 range_overflow = { true, 7, 7, 7, i64::MAX, 1, None },
160 )]
161 fn response_admission_binds_identity_epoch_and_exact_range(
162 is_leader: bool,
163 authenticated: i64,
164 claimed: i64,
165 local: i64,
166 next: i64,
167 count: i64,
168 expected: Option<i64>,
169 ) {
170 assert2::assert!(
171 wal_reservation_response(is_leader, authenticated, claimed, local, next, count)
172 == expected
173 );
174 }
175}