Skip to main content

krabka_verified/diskless/
trim_decision.rs

1use {
2    super::{DisklessBatchStep, DisklessTrimDecision},
3    creusot_std::prelude::*,
4};
5
6/// Extend an object byte span only across a contiguous whole indexed range.
7///
8/// The span extends exactly when the next range sits in the same object,
9/// starts where the span ends, and the grown span stays within `max_bytes`.
10#[ensures(match result {
11    Some(total) => same_object
12        && current_start@ + current_len@ == next_start@
13        && total@ == current_len@ + next_len@
14        && total@ <= max_bytes@,
15    None => !same_object
16        || current_start@ + current_len@ != next_start@
17        || current_len@ + next_len@ > max_bytes@,
18})]
19#[must_use]
20pub fn diskless_span_extension(
21    current_start: u64,
22    current_len: u64,
23    next_start: u64,
24    next_len: u64,
25    same_object: bool,
26    max_bytes: u64,
27) -> Option<u64> {
28    if !same_object || current_start.checked_add(current_len) != Some(next_start) {
29        return None;
30    }
31    let total = current_len.checked_add(next_len)?;
32    (total <= max_bytes).then_some(total)
33}
34
35// cargo-mutants: #[cfg(creusot)] spec function; not compiled outside Creusot, so no test can tell.
36#[cfg(creusot)]
37#[cfg_attr(test, mutants::skip)]
38#[logic]
39fn batch_step_valid(
40    selected_start: Option<usize>,
41    batch_start: usize,
42    encoded_len: usize,
43    base_offset: i64,
44    last_offset_delta: i32,
45) -> bool {
46    pearlite! {
47        encoded_len@ > 0
48            && last_offset_delta@ >= 0
49            && batch_start@ + encoded_len@ <= usize::MAX@
50            && base_offset@ + last_offset_delta@ <= i64::MAX@
51            && match selected_start {
52                Some(start) => start@ <= batch_start@,
53                None => true,
54            }
55    }
56}
57
58/// Classify one decoded batch without splitting it or overflowing coordinates.
59///
60/// A batch is `Invalid` exactly when it is empty, has a negative last offset
61/// delta, ends past `usize` or `i64`, or starts before the selected run. Every
62/// valid batch lands in exactly one of the other four steps, so each valid
63/// input advances the read by the batch's encoded length or stops it.
64#[ensures((result == DisklessBatchStep::Invalid) == !batch_step_valid(
65    selected_start,
66    batch_start,
67    encoded_len,
68    base_offset,
69    last_offset_delta,
70))]
71#[ensures(match result {
72    DisklessBatchStep::Skip(next) => selected_start == None
73        && next@ == batch_start@ + encoded_len@
74        && base_offset@ + last_offset_delta@ < floor@,
75    DisklessBatchStep::Start(next) => selected_start == None
76        && next@ == batch_start@ + encoded_len@
77        && floor@ <= base_offset@ + last_offset_delta@,
78    DisklessBatchStep::Continue(next) => match selected_start {
79        Some(start) => start@ <= batch_start@
80            && next@ == batch_start@ + encoded_len@
81            && next@ - start@ <= max_bytes@,
82        None => false,
83    },
84    DisklessBatchStep::Stop => match selected_start {
85        Some(start) => start@ <= batch_start@
86            && batch_start@ + encoded_len@ - start@ > max_bytes@,
87        None => false,
88    },
89    // Pinned by the `Invalid` iff above.
90    DisklessBatchStep::Invalid => true,
91})]
92#[must_use]
93pub fn diskless_batch_step(
94    selected_start: Option<usize>,
95    batch_start: usize,
96    encoded_len: usize,
97    base_offset: i64,
98    last_offset_delta: i32,
99    floor: i64,
100    max_bytes: usize,
101) -> DisklessBatchStep {
102    if encoded_len == 0 || last_offset_delta < 0 {
103        return DisklessBatchStep::Invalid;
104    }
105    let Some(next) = batch_start.checked_add(encoded_len) else {
106        return DisklessBatchStep::Invalid;
107    };
108    let Some(last_offset) = base_offset.checked_add(i64::from(last_offset_delta)) else {
109        return DisklessBatchStep::Invalid;
110    };
111    if let Some(start) = selected_start {
112        if start > batch_start {
113            return DisklessBatchStep::Invalid;
114        }
115        if next - start > max_bytes {
116            DisklessBatchStep::Stop
117        } else {
118            DisklessBatchStep::Continue(next)
119        }
120    } else if last_offset < floor {
121        DisklessBatchStep::Skip(next)
122    } else {
123        DisklessBatchStep::Start(next)
124    }
125}
126
127// cargo-mutants: #[cfg(creusot)] spec function; not compiled outside Creusot, so no test can tell.
128#[cfg(creusot)]
129#[cfg_attr(test, mutants::skip)]
130#[logic(open)]
131pub fn effective_trim_lag(safety_lag: i64) -> Int {
132    pearlite! { if safety_lag@ < 0 { 0 } else { safety_lag@ } }
133}
134
135// cargo-mutants: #[cfg(creusot)] spec function; not compiled outside Creusot, so no test can tell.
136#[cfg(creusot)]
137#[cfg_attr(test, mutants::skip)]
138#[logic(open)]
139fn trim_target(frontier: i64, high_watermark: i64, safety_lag: i64) -> Int {
140    pearlite! {
141        let high_watermark_floor = high_watermark@ - effective_trim_lag(safety_lag);
142        if frontier@ < high_watermark_floor { frontier@ } else { high_watermark_floor }
143    }
144}
145
146/// Plan a local trim behind both the committed object-store frontier and the
147/// high watermark's configured safety lag.
148///
149/// Negative offsets and a lag larger than the high watermark fail closed. A
150/// negative lag retains the caller's previous behavior and is treated as zero.
151#[ensures(result.should_trim == (
152    frontier@ >= 0
153        && high_watermark@ >= 0
154        && current_start@ >= 0
155        && effective_trim_lag(safety_lag) <= high_watermark@
156        && current_start@ < trim_target(frontier, high_watermark, safety_lag)
157))]
158#[ensures(result.target@ == if result.should_trim {
159    trim_target(frontier, high_watermark, safety_lag)
160} else {
161    current_start@
162})]
163#[ensures(result.target@ >= current_start@)]
164#[ensures(result.should_trim ==> result.target@ <= frontier@)]
165#[ensures(result.should_trim ==>
166    result.target@ + effective_trim_lag(safety_lag) <= high_watermark@)]
167#[must_use]
168pub fn diskless_trim_decision(
169    frontier: i64,
170    high_watermark: i64,
171    safety_lag: i64,
172    current_start: i64,
173) -> DisklessTrimDecision {
174    if frontier < 0 || high_watermark < 0 || current_start < 0 {
175        return DisklessTrimDecision {
176            should_trim: false,
177            target: current_start,
178        };
179    }
180
181    let safety_lag = safety_lag.max(0);
182    if safety_lag > high_watermark {
183        return DisklessTrimDecision {
184            should_trim: false,
185            target: current_start,
186        };
187    }
188
189    let target = frontier.min(high_watermark - safety_lag);
190    if target <= current_start {
191        DisklessTrimDecision {
192            should_trim: false,
193            target: current_start,
194        }
195    } else {
196        DisklessTrimDecision {
197            should_trim: true,
198            target,
199        }
200    }
201}