1use std::collections::BTreeSet;
2
3use amiss_wire::controls::ResourceName;
4
5use crate::Error;
6
7#[derive(Clone, Copy, Debug, PartialEq, Eq)]
8pub struct GitLimits {
9 pub inflated_object_bytes: u64,
10 pub compressed_stream_bytes: u64,
11 pub aggregate_compressed_bytes: u64,
12 pub pack_directory_entries: u64,
13 pub pack_files: u64,
14 pub pack_index_bytes: u64,
15 pub aggregate_pack_index_bytes: u64,
16 pub delta_depth: u64,
17 pub index_bytes: u64,
18 pub tree_entries_per_snapshot: u64,
19 pub raw_path_bytes: u64,
20}
21
22#[derive(Clone, Copy, Debug, PartialEq, Eq)]
26pub struct ValueCap {
27 pub resource: ResourceName,
28 pub limit: u64,
29}
30
31impl GitLimits {
32 pub const CONTRACT: Self = Self {
33 inflated_object_bytes: 134_217_728,
34 compressed_stream_bytes: 268_435_456,
35 aggregate_compressed_bytes: 2_147_483_648,
36 pack_directory_entries: 8_192,
37 pack_files: 4_096,
38 pack_index_bytes: 536_870_912,
39 aggregate_pack_index_bytes: 1_073_741_824,
40 delta_depth: 128,
41 index_bytes: 268_435_456,
42 tree_entries_per_snapshot: 1_000_000,
43 raw_path_bytes: 4_096,
44 };
45}
46
47impl Default for GitLimits {
48 fn default() -> Self {
49 Self::CONTRACT
50 }
51}
52
53pub(crate) fn crossing(resource: ResourceName, configured_limit: u64, observed: u64) -> Error {
54 Error::ResourceLimit {
55 resource,
56 configured_limit,
57 observed_lower_bound: observed,
58 }
59}
60
61#[derive(Clone, Debug, PartialEq, Eq)]
64pub struct GitResources {
65 limits: GitLimits,
66 aggregate_compressed: u64,
67 aggregate_index: u64,
68 charged: BTreeSet<String>,
69}
70
71impl GitResources {
72 #[must_use]
73 pub fn new(limits: GitLimits) -> Self {
74 Self {
75 limits,
76 aggregate_compressed: 0,
77 aggregate_index: 0,
78 charged: BTreeSet::new(),
79 }
80 }
81
82 #[must_use]
83 pub const fn limits(&self) -> GitLimits {
84 self.limits
85 }
86
87 pub fn charge_compressed(&mut self, member: &str, bytes: u64) -> Result<(), Error> {
92 if bytes > self.limits.compressed_stream_bytes {
93 return Err(crossing(
94 ResourceName::GitCompressedObjectBytes,
95 self.limits.compressed_stream_bytes,
96 bytes,
97 ));
98 }
99 if self.charged.contains(member) {
100 return Ok(());
101 }
102 let total = self.aggregate_compressed.saturating_add(bytes);
103 if total > self.limits.aggregate_compressed_bytes {
104 return Err(crossing(
105 ResourceName::AggregateGitCompressedObjectBytesPerEvaluation,
106 self.limits.aggregate_compressed_bytes,
107 total,
108 ));
109 }
110 self.aggregate_compressed = total;
111 self.charged.insert(member.to_owned());
112 Ok(())
113 }
114
115 pub fn charge_index(&mut self, member: &str, bytes: u64) -> Result<(), Error> {
120 if bytes > self.limits.pack_index_bytes {
121 return Err(crossing(
122 ResourceName::GitPackIndexBytes,
123 self.limits.pack_index_bytes,
124 bytes,
125 ));
126 }
127 let key = format!("idx:{member}");
128 if self.charged.contains(&key) {
129 return Ok(());
130 }
131 let total = self.aggregate_index.saturating_add(bytes);
132 if total > self.limits.aggregate_pack_index_bytes {
133 return Err(crossing(
134 ResourceName::AggregateGitPackIndexBytes,
135 self.limits.aggregate_pack_index_bytes,
136 total,
137 ));
138 }
139 self.aggregate_index = total;
140 self.charged.insert(key);
141 Ok(())
142 }
143}