1use crate::cursor::ManifestCursor;
8use crate::error::CoreError;
9use limnifs_format::SlabId;
10
11pub const EC_PARAMS_SECTION_VERSION: u8 = 1;
13
14pub const DEFAULT_EC_POLYNOMIAL: u16 = 0x011D;
16
17pub const MAX_SHARDS: u8 = 255;
19
20const PREFIX_LEN: usize = 1 + 1 + 1 + 2 + 4;
22
23const OVERRIDE_ENTRY_LEN: usize = 40 + 1 + 1;
25
26#[derive(Clone, Copy, Debug, Eq, PartialEq)]
28pub struct EcOverride {
29 pub slab_id: SlabId,
30 pub k: u8,
31 pub m: u8,
32}
33
34#[derive(Clone, Debug, Eq, PartialEq)]
36pub struct EcParams {
37 pub k: u8,
38 pub m: u8,
39 pub polynomial: u16,
40 pub overrides: Vec<EcOverride>,
41}
42
43impl EcParams {
44 #[must_use]
45 pub fn default_params(k: u8, m: u8) -> Self {
46 Self {
47 k,
48 m,
49 polynomial: DEFAULT_EC_POLYNOMIAL,
50 overrides: Vec::new(),
51 }
52 }
53}
54
55pub fn parse_ec_params(cursor: &mut ManifestCursor<'_>) -> Result<EcParams, CoreError> {
67 let section_version = cursor.read_u8()?;
68 if section_version != EC_PARAMS_SECTION_VERSION {
69 return Err(CoreError::UnsupportedFeature {
70 feature: format!(
71 "ec_params section version {section_version} (supported: {EC_PARAMS_SECTION_VERSION})"
72 ),
73 });
74 }
75 let k = cursor.read_u8()?;
76 let m = cursor.read_u8()?;
77 validate_shard_counts(k, m, "default")?;
78 let polynomial = cursor.read_u16_le()?;
79 if polynomial != DEFAULT_EC_POLYNOMIAL {
80 return Err(CoreError::UnsupportedFeature {
81 feature: format!(
82 "ec_params polynomial 0x{polynomial:04X} (supported: 0x{DEFAULT_EC_POLYNOMIAL:04X})"
83 ),
84 });
85 }
86 let raw_count = cursor.read_u32_le()?;
87 let override_count = usize::try_from(raw_count).map_err(|_| CoreError::Corrupt {
88 reason: format!("ec_params override_count {raw_count} exceeds usize"),
89 })?;
90 let min_size = override_count
92 .checked_mul(OVERRIDE_ENTRY_LEN)
93 .ok_or_else(|| CoreError::Corrupt {
94 reason: format!("ec_params override_count {override_count} overflows usize"),
95 })?;
96 if cursor.remaining_len() < min_size {
97 return Err(CoreError::TooShort {
98 have: cursor.remaining_len(),
99 need: min_size,
100 });
101 }
102 let mut overrides = Vec::with_capacity(override_count);
103 for index in 0..override_count {
104 let ordinal = cursor.read_u64_le()?;
105 let hash_bytes = cursor.read_n(32)?;
106 let mut hash = [0u8; 32];
107 hash.copy_from_slice(hash_bytes);
108 let slab_id = SlabId::new(ordinal, hash);
109 let ok = cursor.read_u8()?;
110 let om = cursor.read_u8()?;
111 validate_shard_counts(
112 ok,
113 om,
114 format!("override {index} for slab ordinal {ordinal}").as_str(),
115 )?;
116 if overrides
117 .iter()
118 .any(|existing: &EcOverride| existing.slab_id == slab_id)
119 {
120 return Err(CoreError::Corrupt {
121 reason: format!(
122 "ec_params override {index}: duplicate slab_id (ordinal {ordinal})"
123 ),
124 });
125 }
126 overrides.push(EcOverride {
127 slab_id,
128 k: ok,
129 m: om,
130 });
131 }
132 let _ = PREFIX_LEN; Ok(EcParams {
134 k,
135 m,
136 polynomial,
137 overrides,
138 })
139}
140
141fn validate_shard_counts(k: u8, m: u8, label: &str) -> Result<(), CoreError> {
142 if k == 0 {
143 return Err(CoreError::Corrupt {
144 reason: format!("ec_params {label}: k must be >= 1, got 0"),
145 });
146 }
147 if m == 0 {
148 return Err(CoreError::Corrupt {
149 reason: format!("ec_params {label}: m must be >= 1, got 0"),
150 });
151 }
152 let total = u16::from(k) + u16::from(m);
153 if total > u16::from(MAX_SHARDS) {
154 return Err(CoreError::Corrupt {
155 reason: format!(
156 "ec_params {label}: k + m = {total} exceeds GF(2^8) limit ({MAX_SHARDS})"
157 ),
158 });
159 }
160 Ok(())
161}
162
163#[cfg(test)]
164mod tests {
165 use super::*;
166
167 fn make_ec_params_bytes(
168 version: u8,
169 k: u8,
170 m: u8,
171 polynomial: u16,
172 overrides: &[(u64, [u8; 32], u8, u8)],
173 ) -> Vec<u8> {
174 let mut bytes = Vec::new();
175 bytes.push(version);
176 bytes.push(k);
177 bytes.push(m);
178 bytes.extend_from_slice(&polynomial.to_le_bytes());
179 let count = u32::try_from(overrides.len()).expect("count fits u32");
180 bytes.extend_from_slice(&count.to_le_bytes());
181 for (ordinal, hash, ok, om) in overrides {
182 bytes.extend_from_slice(&ordinal.to_le_bytes());
183 bytes.extend_from_slice(hash);
184 bytes.push(*ok);
185 bytes.push(*om);
186 }
187 bytes
188 }
189
190 fn sample_hash(byte: u8) -> [u8; 32] {
191 let mut h = [0u8; 32];
192 h[0] = byte;
193 h
194 }
195
196 #[test]
197 fn parses_default_only() {
198 let bytes =
199 make_ec_params_bytes(EC_PARAMS_SECTION_VERSION, 4, 2, DEFAULT_EC_POLYNOMIAL, &[]);
200 let mut cursor = ManifestCursor::new(&bytes);
201 let parsed = parse_ec_params(&mut cursor).expect("default parses");
202 assert_eq!(parsed.k, 4);
203 assert_eq!(parsed.m, 2);
204 assert_eq!(parsed.polynomial, DEFAULT_EC_POLYNOMIAL);
205 assert!(parsed.overrides.is_empty());
206 assert_eq!(cursor.position(), bytes.len());
207 }
208
209 #[test]
210 fn parses_with_one_override() {
211 let bytes = make_ec_params_bytes(
212 EC_PARAMS_SECTION_VERSION,
213 4,
214 2,
215 DEFAULT_EC_POLYNOMIAL,
216 &[(7, sample_hash(0xAA), 8, 4)],
217 );
218 let mut cursor = ManifestCursor::new(&bytes);
219 let parsed = parse_ec_params(&mut cursor).expect("override parses");
220 assert_eq!(parsed.overrides.len(), 1);
221 assert_eq!(parsed.overrides[0].slab_id.ordinal, 7);
222 assert_eq!(parsed.overrides[0].k, 8);
223 assert_eq!(parsed.overrides[0].m, 4);
224 }
225
226 #[test]
227 fn parses_with_multiple_overrides() {
228 let bytes = make_ec_params_bytes(
229 EC_PARAMS_SECTION_VERSION,
230 4,
231 2,
232 DEFAULT_EC_POLYNOMIAL,
233 &[
234 (0, sample_hash(0x01), 6, 3),
235 (1, sample_hash(0x02), 8, 4),
236 (2, sample_hash(0x03), 16, 8),
237 ],
238 );
239 let mut cursor = ManifestCursor::new(&bytes);
240 let parsed = parse_ec_params(&mut cursor).expect("multi parses");
241 assert_eq!(parsed.overrides.len(), 3);
242 }
243
244 #[test]
245 fn rejects_unknown_section_version() {
246 let bytes = make_ec_params_bytes(7, 4, 2, DEFAULT_EC_POLYNOMIAL, &[]);
247 let mut cursor = ManifestCursor::new(&bytes);
248 match parse_ec_params(&mut cursor) {
249 Err(CoreError::UnsupportedFeature { feature }) => {
250 assert!(feature.contains("version 7"), "got: {feature}");
251 }
252 other => panic!("expected UnsupportedFeature, got {other:?}"),
253 }
254 }
255
256 #[test]
257 fn rejects_zero_k() {
258 let bytes =
259 make_ec_params_bytes(EC_PARAMS_SECTION_VERSION, 0, 2, DEFAULT_EC_POLYNOMIAL, &[]);
260 let mut cursor = ManifestCursor::new(&bytes);
261 match parse_ec_params(&mut cursor) {
262 Err(CoreError::Corrupt { reason }) => {
263 assert!(reason.contains("k must be >= 1"), "got: {reason}");
264 }
265 other => panic!("expected Corrupt, got {other:?}"),
266 }
267 }
268
269 #[test]
270 fn rejects_zero_m() {
271 let bytes =
272 make_ec_params_bytes(EC_PARAMS_SECTION_VERSION, 4, 0, DEFAULT_EC_POLYNOMIAL, &[]);
273 let mut cursor = ManifestCursor::new(&bytes);
274 match parse_ec_params(&mut cursor) {
275 Err(CoreError::Corrupt { reason }) => {
276 assert!(reason.contains("m must be >= 1"), "got: {reason}");
277 }
278 other => panic!("expected Corrupt, got {other:?}"),
279 }
280 }
281
282 #[test]
283 fn rejects_shard_count_above_gf_limit() {
284 let bytes = make_ec_params_bytes(
286 EC_PARAMS_SECTION_VERSION,
287 200,
288 100,
289 DEFAULT_EC_POLYNOMIAL,
290 &[],
291 );
292 let mut cursor = ManifestCursor::new(&bytes);
293 match parse_ec_params(&mut cursor) {
294 Err(CoreError::Corrupt { reason }) => {
295 assert!(reason.contains("GF(2^8)"), "got: {reason}");
296 }
297 other => panic!("expected Corrupt, got {other:?}"),
298 }
299 }
300
301 #[test]
302 fn rejects_unknown_polynomial() {
303 let bytes = make_ec_params_bytes(EC_PARAMS_SECTION_VERSION, 4, 2, 0x002B, &[]);
304 let mut cursor = ManifestCursor::new(&bytes);
305 match parse_ec_params(&mut cursor) {
306 Err(CoreError::UnsupportedFeature { feature }) => {
307 assert!(feature.contains("polynomial"), "got: {feature}");
308 assert!(feature.contains("0x002B"));
309 }
310 other => panic!("expected UnsupportedFeature, got {other:?}"),
311 }
312 }
313
314 #[test]
315 fn rejects_duplicate_override_slab_id() {
316 let bytes = make_ec_params_bytes(
317 EC_PARAMS_SECTION_VERSION,
318 4,
319 2,
320 DEFAULT_EC_POLYNOMIAL,
321 &[(5, sample_hash(0xAA), 6, 3), (5, sample_hash(0xAA), 8, 4)],
322 );
323 let mut cursor = ManifestCursor::new(&bytes);
324 match parse_ec_params(&mut cursor) {
325 Err(CoreError::Corrupt { reason }) => {
326 assert!(reason.contains("duplicate"), "got: {reason}");
327 assert!(reason.contains("ordinal 5"));
328 }
329 other => panic!("expected Corrupt, got {other:?}"),
330 }
331 }
332
333 #[test]
334 fn rejects_override_with_bad_shard_counts() {
335 let bytes = make_ec_params_bytes(
336 EC_PARAMS_SECTION_VERSION,
337 4,
338 2,
339 DEFAULT_EC_POLYNOMIAL,
340 &[(7, sample_hash(0xAA), 0, 4)], );
342 let mut cursor = ManifestCursor::new(&bytes);
343 match parse_ec_params(&mut cursor) {
344 Err(CoreError::Corrupt { reason }) => {
345 assert!(reason.contains("override 0"), "got: {reason}");
346 assert!(reason.contains("k must be >= 1"));
347 }
348 other => panic!("expected Corrupt, got {other:?}"),
349 }
350 }
351
352 #[test]
353 fn rejects_truncated_prefix() {
354 let bytes = [EC_PARAMS_SECTION_VERSION];
356 let mut cursor = ManifestCursor::new(&bytes);
357 match parse_ec_params(&mut cursor) {
358 Err(CoreError::TooShort { .. }) => {}
359 other => panic!("expected TooShort, got {other:?}"),
360 }
361 }
362
363 #[test]
364 fn rejects_override_count_overrunning_buffer() {
365 let mut bytes = Vec::new();
366 bytes.push(EC_PARAMS_SECTION_VERSION);
367 bytes.push(4);
368 bytes.push(2);
369 bytes.extend_from_slice(&DEFAULT_EC_POLYNOMIAL.to_le_bytes());
370 bytes.extend_from_slice(&10u32.to_le_bytes()); let mut cursor = ManifestCursor::new(&bytes);
372 match parse_ec_params(&mut cursor) {
373 Err(CoreError::TooShort { have, need }) => {
374 assert_eq!(have, 0);
375 assert_eq!(need, 10 * OVERRIDE_ENTRY_LEN);
376 }
377 other => panic!("expected TooShort, got {other:?}"),
378 }
379 }
380
381 #[test]
382 fn default_params_helper_uses_default_polynomial() {
383 let params = EcParams::default_params(4, 2);
384 assert_eq!(params.polynomial, DEFAULT_EC_POLYNOMIAL);
385 assert!(params.overrides.is_empty());
386 }
387
388 #[test]
389 fn max_shards_constant_is_255() {
390 assert_eq!(MAX_SHARDS, 255);
392 }
393}