1use creusot_std::prelude::*;
4
5#[krabka_macros::model_type]
6pub struct ProducerIdBlockPlan {
7 pub first: i64,
8 pub len: i32,
9 pub next: i64,
10}
11
12#[krabka_macros::model_type]
13pub enum ProducerIdBlockAllocationDecision {
14 BrokerNotRegistered,
15 StaleBrokerEpoch,
16 InvalidFrontier,
17 Exhausted,
18 Allocate(ProducerIdBlockPlan),
19}
20
21#[ensures(match result {
24 ProducerIdBlockAllocationDecision::BrokerNotRegistered =>
25 !broker_id_valid || !broker_registered,
26 ProducerIdBlockAllocationDecision::StaleBrokerEpoch =>
27 broker_id_valid && broker_registered
28 && (requested_broker_epoch@ < 0
29 || registered_broker_epoch@ < 0
30 || requested_broker_epoch@ != registered_broker_epoch@),
31 ProducerIdBlockAllocationDecision::InvalidFrontier =>
32 broker_id_valid && broker_registered
33 && requested_broker_epoch@ >= 0
34 && requested_broker_epoch@ == registered_broker_epoch@
35 && (first@ < 0 || block_len@ <= 0),
36 ProducerIdBlockAllocationDecision::Exhausted =>
37 broker_id_valid && broker_registered
38 && requested_broker_epoch@ >= 0
39 && requested_broker_epoch@ == registered_broker_epoch@
40 && first@ >= 0 && block_len@ > 0
41 && first@ + block_len@ > i64::MAX@,
42 ProducerIdBlockAllocationDecision::Allocate(plan) =>
43 broker_id_valid && broker_registered
44 && requested_broker_epoch@ >= 0
45 && requested_broker_epoch@ == registered_broker_epoch@
46 && plan.first@ == first@
47 && plan.len@ == block_len@
48 && plan.next@ == first@ + block_len@
49 && plan.first@ >= 0
50 && plan.len@ > 0
51 && plan.first@ < plan.next@
52 && plan.next@ <= i64::MAX@,
53})]
54#[must_use]
55pub fn producer_id_block_allocation(
56 broker_id_valid: bool,
57 broker_registered: bool,
58 requested_broker_epoch: i64,
59 registered_broker_epoch: i64,
60 first: i64,
61 block_len: i32,
62) -> ProducerIdBlockAllocationDecision {
63 if !broker_id_valid || !broker_registered {
64 return ProducerIdBlockAllocationDecision::BrokerNotRegistered;
65 }
66 if requested_broker_epoch < 0
67 || registered_broker_epoch < 0
68 || requested_broker_epoch != registered_broker_epoch
69 {
70 return ProducerIdBlockAllocationDecision::StaleBrokerEpoch;
71 }
72 if first < 0 || block_len <= 0 {
73 return ProducerIdBlockAllocationDecision::InvalidFrontier;
74 }
75 let Some(next) = first.checked_add(i64::from(block_len)) else {
76 return ProducerIdBlockAllocationDecision::Exhausted;
77 };
78 ProducerIdBlockAllocationDecision::Allocate(ProducerIdBlockPlan {
79 first,
80 len: block_len,
81 next,
82 })
83}
84
85#[cfg(test)]
86mod tests {
87 use {
88 super::{
89 ProducerIdBlockAllocationDecision, ProducerIdBlockPlan, producer_id_block_allocation,
90 },
91 assert2::assert,
92 };
93
94 #[test]
95 fn allocation_fences_broker_and_builds_only_exact_positive_blocks() {
96 use ProducerIdBlockAllocationDecision::{
97 Allocate, BrokerNotRegistered, Exhausted, InvalidFrontier, StaleBrokerEpoch,
98 };
99
100 assert!(producer_id_block_allocation(false, true, 3, 3, 0, 1_000) == BrokerNotRegistered);
101 assert!(producer_id_block_allocation(true, false, 3, 3, 0, 1_000) == BrokerNotRegistered);
102 assert!(producer_id_block_allocation(true, true, -1, 3, 0, 1_000) == StaleBrokerEpoch);
103 assert!(producer_id_block_allocation(true, true, 1, -1, 0, 1_000) == StaleBrokerEpoch);
104 assert!(producer_id_block_allocation(true, true, 2, 3, 0, 1_000) == StaleBrokerEpoch);
105 assert!(
106 producer_id_block_allocation(true, true, 0, 0, 0, 1_000)
107 == Allocate(ProducerIdBlockPlan {
108 first: 0,
109 len: 1_000,
110 next: 1_000,
111 })
112 );
113 assert!(producer_id_block_allocation(true, true, 3, 3, -1, 1_000) == InvalidFrontier);
114 assert!(producer_id_block_allocation(true, true, 3, 3, 0, 0) == InvalidFrontier);
115 assert!(producer_id_block_allocation(true, true, 3, 3, i64::MAX - 999, 1_000) == Exhausted);
116 assert!(
117 producer_id_block_allocation(true, true, 3, 3, i64::MAX - 1_000, 1_000)
118 == Allocate(ProducerIdBlockPlan {
119 first: i64::MAX - 1_000,
120 len: 1_000,
121 next: i64::MAX,
122 })
123 );
124 }
125}