krabka_verified/
remote_metadata.rs1use creusot_std::prelude::*;
4
5const MURMUR2_SEED: u32 = 0x9747_b28c;
6const MURMUR2_M: u32 = 0x5bd1_e995;
7
8#[cfg(creusot)]
15#[cfg_attr(test, mutants::skip)]
16#[logic]
17fn java_long_hash_model(bits: u64) -> u32 {
18 pearlite! { ((bits ^ (bits >> 32u32)) & 0xffff_ffffu64) as u32 }
19}
20
21#[cfg_attr(creusot, ensures(result == java_long_hash_model(bits)))]
22fn java_long_hash(bits: u64) -> u32 { ((bits ^ (bits >> 32)) & 0xffff_ffff) as u32 }
23
24#[cfg(creusot)]
28#[cfg_attr(test, mutants::skip)]
29#[logic]
30fn java_int_image_model(value: i32) -> u32 {
31 pearlite! {
32 if value@ >= 0 { value as u32 } else { u32::MAX - (-1i32 - value) as u32 }
33 }
34}
35
36#[cfg_attr(creusot, ensures(result == java_int_image_model(value)))]
37fn java_int_image(value: i32) -> u32 {
38 if value < 0 {
39 u32::MAX - value.abs_diff(-1)
41 } else {
42 value.abs_diff(0)
43 }
44}
45
46#[cfg(creusot)]
50#[cfg_attr(test, mutants::skip)]
51#[logic]
52fn java_objects_hash_model(topic_id: u128, user_partition: i32) -> u32 {
53 pearlite! {
54 let most_significant_bits = (topic_id >> 64u32) as u64;
55 let least_significant_bits = (topic_id & 0xffff_ffff_ffff_ffffu128) as u64;
56 let first = 1u32 * 31u32 + java_long_hash_model(least_significant_bits);
57 let second = first * 31u32 + java_long_hash_model(most_significant_bits);
58 second * 31u32 + java_int_image_model(user_partition)
59 }
60}
61
62#[cfg_attr(
63 creusot,
64 ensures(result == java_objects_hash_model(topic_id, user_partition))
65)]
66fn java_objects_hash(topic_id: u128, user_partition: i32) -> u32 {
67 let most_significant_bits = (topic_id >> 64) as u64;
68 let least_significant_bits = (topic_id & 0xffff_ffff_ffff_ffff) as u64;
69
70 let mut hash = 1_u32;
71 hash = hash
72 .wrapping_mul(31)
73 .wrapping_add(java_long_hash(least_significant_bits));
74 hash = hash
75 .wrapping_mul(31)
76 .wrapping_add(java_long_hash(most_significant_bits));
77 hash.wrapping_mul(31)
78 .wrapping_add(java_int_image(user_partition))
79}
80
81#[cfg(creusot)]
83#[cfg_attr(test, mutants::skip)]
84#[logic]
85fn reverse_bytes_model(value: u32) -> u32 {
86 pearlite! {
87 (value >> 24u32)
88 | ((value >> 8u32) & 0x0000_ff00u32)
89 | ((value << 8u32) & 0x00ff_0000u32)
90 | (value << 24u32)
91 }
92}
93
94#[cfg_attr(creusot, ensures(result == reverse_bytes_model(value)))]
95fn reverse_bytes(value: u32) -> u32 {
96 (value >> 24) | ((value >> 8) & 0x0000_ff00) | ((value << 8) & 0x00ff_0000) | (value << 24)
97}
98
99#[cfg(creusot)]
101#[cfg_attr(test, mutants::skip)]
102#[logic]
103fn kafka_murmur2_int_model(value: u32) -> u32 {
104 pearlite! {
105 let chunk0 = reverse_bytes_model(value);
106 let chunk1 = chunk0 * 0x5bd1_e995u32;
107 let chunk2 = (chunk1 ^ (chunk1 >> 24u32)) * 0x5bd1_e995u32;
108 let hash0 = (0x9747_b28cu32 ^ 4u32) * 0x5bd1_e995u32;
109 let hash1 = hash0 ^ chunk2;
110 let hash2 = (hash1 ^ (hash1 >> 13u32)) * 0x5bd1_e995u32;
111 hash2 ^ (hash2 >> 15u32)
112 }
113}
114
115#[cfg_attr(creusot, ensures(result == kafka_murmur2_int_model(value)))]
118fn kafka_murmur2_int(value: u32) -> u32 {
119 let mut hash = MURMUR2_SEED ^ 4;
120 let mut chunk = reverse_bytes(value);
121 chunk = chunk.wrapping_mul(MURMUR2_M);
122 chunk ^= chunk >> 24;
123 chunk = chunk.wrapping_mul(MURMUR2_M);
124 hash = hash.wrapping_mul(MURMUR2_M);
125 hash ^= chunk;
126 hash ^= hash >> 13;
127 hash = hash.wrapping_mul(MURMUR2_M);
128 hash ^ (hash >> 15)
129}
130
131#[cfg(creusot)]
133#[cfg_attr(test, mutants::skip)]
134#[logic]
135fn kafka_to_positive_model(hash: u32) -> Int {
136 pearlite! { hash@ % 2_147_483_648 }
137}
138
139#[cfg_attr(creusot, ensures(result@ == kafka_to_positive_model(hash)))]
142#[ensures(0 <= result@)]
143#[ensures(result@ <= i32::MAX@)]
144fn kafka_to_positive(hash: u32) -> i32 { (u64::from(hash) % 0x8000_0000) as i32 }
145
146#[cfg(creusot)]
148#[cfg_attr(test, mutants::skip)]
149#[logic]
150fn remote_metadata_partition_model(
151 topic_id: u128,
152 user_partition: i32,
153 metadata_partition_count: i32,
154) -> Int {
155 pearlite! {
156 if metadata_partition_count <= 0i32 {
157 0
158 } else {
159 kafka_to_positive_model(kafka_murmur2_int_model(
160 java_objects_hash_model(topic_id, user_partition)
161 )) % metadata_partition_count@
162 }
163 }
164}
165
166#[cfg_attr(
174 creusot,
175 ensures(metadata_partition_count@ > 0 ==>
176 exists<partition: i32> result == Some(partition)
177 && partition@ == remote_metadata_partition_model(
178 topic_id,
179 user_partition,
180 metadata_partition_count
181 ))
182)]
183#[ensures((result == None) == (metadata_partition_count@ <= 0))]
184#[ensures(forall<partition: i32> result == Some(partition) ==>
185 0 <= partition@ && partition@ < metadata_partition_count@)]
186#[must_use]
187pub fn remote_metadata_partition(
188 topic_id: u128,
189 user_partition: i32,
190 metadata_partition_count: i32,
191) -> Option<i32> {
192 if metadata_partition_count <= 0 {
193 return None;
194 }
195
196 let objects_hash = java_objects_hash(topic_id, user_partition);
197 let murmur2 = kafka_murmur2_int(objects_hash);
198 Some(kafka_to_positive(murmur2) % metadata_partition_count)
199}
200
201#[cfg_attr(
209 creusot,
210 ensures(match result {
211 Some(cursor) => -1 <= committed@ && committed@ < i64::MAX@ && cursor@ == committed@ + 1,
212 None => committed@ < -1 || committed@ == i64::MAX@,
213 })
214)]
215#[ensures(forall<cursor: i64> result == Some(cursor) ==> 0 <= cursor@)]
216#[must_use]
217pub const fn remote_metadata_resume_cursor(committed: i64) -> Option<i64> {
218 if committed < -1 {
219 return None;
220 }
221 committed.checked_add(1)
222}
223
224#[cfg(test)]
225mod tests {
226 use {super::*, assert2::check};
227
228 #[yare::parameterized(
229 partition_0 = { 0, Some(6) },
230 partition_1 = { 1, Some(36) },
231 partition_3 = { 3, Some(2) },
232 partition_7 = { 7, Some(26) },
233 )]
234 fn matches_jvm_remote_metadata_partitioner_goldens(partition: i32, expected: Option<i32>) {
235 check!(
236 remote_metadata_partition(0x1234_5678_9abc_def0_0011_2233_4455_6677, partition, 50,)
237 == expected
238 );
239 }
240
241 #[yare::parameterized(
242 zero_uuid = { 0_u128, i32::MIN, Some(96) },
243 max_uuid = { u128::MAX, i32::MAX, Some(5) },
244 signed_boundary = { 0xffff_ffff_ffff_ffff_8000_0000_0000_0000, i32::MIN, Some(45) },
245 )]
246 fn matches_java_signed_uuid_and_partition_extremes(
247 topic_id: u128,
248 partition: i32,
249 expected: Option<i32>,
250 ) {
251 check!(remote_metadata_partition(topic_id, partition, 97) == expected);
252 }
253
254 #[test]
255 fn rejects_nonpositive_metadata_partition_counts() {
256 check!(remote_metadata_partition(0, 0, 0) == None);
257 check!(remote_metadata_partition(0, 0, -1) == None);
258 }
259
260 #[test]
261 fn resume_cursor_maps_none_to_zero_and_advances_exactly() {
262 check!(remote_metadata_resume_cursor(-1) == Some(0));
263 check!(remote_metadata_resume_cursor(0) == Some(1));
264 check!(remote_metadata_resume_cursor(41) == Some(42));
265 check!(remote_metadata_resume_cursor(i64::MAX - 1) == Some(i64::MAX));
266 }
267
268 #[test]
269 fn resume_cursor_rejects_malformed_and_overflowing_offsets() {
270 check!(remote_metadata_resume_cursor(-2) == None);
271 check!(remote_metadata_resume_cursor(i64::MIN) == None);
272 check!(remote_metadata_resume_cursor(i64::MAX) == None);
273 }
274}