1use creusot_std::prelude::*;
4
5#[krabka_macros::model_type]
7pub enum ScramAlterationKind {
8 Delete,
9 Upsert,
10}
11
12#[krabka_macros::model_type]
14pub enum ScramPriorState {
15 Unseen,
16 Accepted,
17 Rejected,
18}
19
20#[krabka_macros::model_type]
22pub struct ScramAlterationFacts {
23 pub kind: ScramAlterationKind,
24 pub prior: ScramPriorState,
25 pub authorized: bool,
26 pub name_empty: bool,
27 pub mechanism: i8,
28 pub iterations: i32,
29 pub min_iterations: i32,
30 pub max_iterations: i32,
31 pub deletion_target_exists: bool,
32}
33
34#[krabka_macros::model_type]
36pub enum ScramAlterationDecision {
37 KeepPriorError,
38 Duplicate,
39 Unauthorized,
40 EmptyName,
41 UnsupportedMechanism,
42 TooFewIterations,
43 TooManyIterations,
44 MissingCredential,
45 AcceptSha256,
46 AcceptSha512,
47}
48
49#[cfg(creusot)]
51#[cfg_attr(test, mutants::skip)]
52#[logic(open)]
53fn alteration_mechanism_admitted(facts: ScramAlterationFacts) -> bool {
54 pearlite! { alteration_name_admitted(facts)
55 && (facts.mechanism@ == 1 || facts.mechanism@ == 2) }
56}
57
58#[cfg(creusot)]
60#[cfg_attr(test, mutants::skip)]
61#[logic(open)]
62pub fn alteration_name_admitted(facts: ScramAlterationFacts) -> bool {
63 pearlite! { facts.prior == ScramPriorState::Unseen
64 && facts.authorized
65 && !facts.name_empty }
66}
67
68#[ensures(match result {
74 ScramAlterationDecision::KeepPriorError => facts.prior == ScramPriorState::Rejected,
75 ScramAlterationDecision::Duplicate => facts.prior == ScramPriorState::Accepted,
76 ScramAlterationDecision::Unauthorized => {
77 facts.prior == ScramPriorState::Unseen && !facts.authorized
78 }
79 ScramAlterationDecision::EmptyName => {
80 facts.prior == ScramPriorState::Unseen && facts.authorized && facts.name_empty
81 }
82 ScramAlterationDecision::UnsupportedMechanism => {
83 alteration_name_admitted(facts)
84 && facts.mechanism@ != 1
85 && facts.mechanism@ != 2
86 }
87 ScramAlterationDecision::TooFewIterations => {
88 alteration_mechanism_admitted(facts)
89 && facts.kind == ScramAlterationKind::Upsert
90 && facts.iterations@ < facts.min_iterations@
91 }
92 ScramAlterationDecision::TooManyIterations => {
93 alteration_mechanism_admitted(facts)
94 && facts.kind == ScramAlterationKind::Upsert
95 && facts.iterations@ >= facts.min_iterations@
96 && facts.iterations@ > facts.max_iterations@
97 }
98 ScramAlterationDecision::MissingCredential => {
99 alteration_mechanism_admitted(facts)
100 && facts.kind == ScramAlterationKind::Delete
101 && !facts.deletion_target_exists
102 }
103 ScramAlterationDecision::AcceptSha256 | ScramAlterationDecision::AcceptSha512 => {
104 alteration_mechanism_admitted(facts)
105 && match facts.kind {
106 ScramAlterationKind::Delete => facts.deletion_target_exists,
107 ScramAlterationKind::Upsert => {
108 facts.iterations@ >= facts.min_iterations@
109 && facts.iterations@ <= facts.max_iterations@
110 }
111 }
112 }
113})]
114#[ensures(result == ScramAlterationDecision::AcceptSha256 ==> facts.mechanism@ == 1)]
115#[ensures(result == ScramAlterationDecision::AcceptSha512 ==> facts.mechanism@ == 2)]
116#[must_use]
117pub fn scram_alteration_decision(facts: ScramAlterationFacts) -> ScramAlterationDecision {
118 match facts.prior {
119 ScramPriorState::Rejected => return ScramAlterationDecision::KeepPriorError,
120 ScramPriorState::Accepted => return ScramAlterationDecision::Duplicate,
121 ScramPriorState::Unseen => {}
122 }
123
124 if !facts.authorized {
125 return ScramAlterationDecision::Unauthorized;
126 }
127 if facts.name_empty {
128 return ScramAlterationDecision::EmptyName;
129 }
130 if facts.mechanism != 1 && facts.mechanism != 2 {
131 return ScramAlterationDecision::UnsupportedMechanism;
132 }
133
134 match facts.kind {
135 ScramAlterationKind::Delete if !facts.deletion_target_exists => {
136 ScramAlterationDecision::MissingCredential
137 }
138 ScramAlterationKind::Upsert if facts.iterations < facts.min_iterations => {
139 ScramAlterationDecision::TooFewIterations
140 }
141 ScramAlterationKind::Upsert if facts.iterations > facts.max_iterations => {
142 ScramAlterationDecision::TooManyIterations
143 }
144 ScramAlterationKind::Delete | ScramAlterationKind::Upsert => {
145 if facts.mechanism == 1 {
146 ScramAlterationDecision::AcceptSha256
147 } else {
148 ScramAlterationDecision::AcceptSha512
149 }
150 }
151 }
152}
153
154#[cfg(test)]
155mod tests {
156 use super::{
157 ScramAlterationDecision, ScramAlterationFacts, ScramAlterationKind, ScramPriorState,
158 scram_alteration_decision,
159 };
160
161 fn valid_upsert() -> ScramAlterationFacts {
162 ScramAlterationFacts {
163 kind: ScramAlterationKind::Upsert,
164 prior: ScramPriorState::Unseen,
165 authorized: true,
166 name_empty: false,
167 mechanism: 1,
168 iterations: 4096,
169 min_iterations: 4096,
170 max_iterations: 16384,
171 deletion_target_exists: false,
172 }
173 }
174
175 #[test]
176 fn errors_follow_kafka_precedence() {
177 let mut facts = valid_upsert();
178 facts.prior = ScramPriorState::Rejected;
179 facts.authorized = false;
180 facts.name_empty = true;
181 facts.mechanism = 0;
182 facts.iterations = i32::MIN;
183 assert2::check!(
184 scram_alteration_decision(facts) == ScramAlterationDecision::KeepPriorError
185 );
186
187 facts.prior = ScramPriorState::Accepted;
188 assert2::check!(scram_alteration_decision(facts) == ScramAlterationDecision::Duplicate);
189
190 facts.prior = ScramPriorState::Unseen;
191 assert2::check!(scram_alteration_decision(facts) == ScramAlterationDecision::Unauthorized);
192
193 facts.authorized = true;
194 assert2::check!(scram_alteration_decision(facts) == ScramAlterationDecision::EmptyName);
195
196 facts.name_empty = false;
197 assert2::check!(
198 scram_alteration_decision(facts) == ScramAlterationDecision::UnsupportedMechanism
199 );
200
201 facts.mechanism = 1;
202 assert2::check!(
203 scram_alteration_decision(facts) == ScramAlterationDecision::TooFewIterations
204 );
205
206 facts.iterations = i32::MAX;
207 assert2::check!(
208 scram_alteration_decision(facts) == ScramAlterationDecision::TooManyIterations
209 );
210 }
211
212 #[test]
213 fn accepts_only_supported_complete_rows() {
214 let facts = valid_upsert();
215 assert2::check!(scram_alteration_decision(facts) == ScramAlterationDecision::AcceptSha256);
216
217 let sha512 = ScramAlterationFacts {
218 mechanism: 2,
219 iterations: 16384,
220 ..facts
221 };
222 assert2::check!(scram_alteration_decision(sha512) == ScramAlterationDecision::AcceptSha512);
223
224 let missing = ScramAlterationFacts {
225 kind: ScramAlterationKind::Delete,
226 deletion_target_exists: false,
227 ..facts
228 };
229 assert2::check!(
230 scram_alteration_decision(missing) == ScramAlterationDecision::MissingCredential
231 );
232
233 let deletion = ScramAlterationFacts {
234 deletion_target_exists: true,
235 ..missing
236 };
237 assert2::check!(
238 scram_alteration_decision(deletion) == ScramAlterationDecision::AcceptSha256
239 );
240 let sha512_deletion = ScramAlterationFacts {
242 mechanism: 2,
243 iterations: 0,
244 ..deletion
245 };
246 assert2::check!(
247 scram_alteration_decision(sha512_deletion) == ScramAlterationDecision::AcceptSha512
248 );
249 }
250}