1use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt};
22use std::io::{Read, Write};
23
24use super::canon::{CLASSIFICATION_MASK, ChunkClassification};
25use super::error::{FafbError, FafbResult};
26use super::priority::Priority;
27
28pub const SECTION_ENTRY_SIZE: usize = 16;
30
31#[derive(Debug, Clone, PartialEq, Eq)]
33pub struct SectionEntry {
34 pub name_index: u8,
36 pub priority: Priority,
38 pub offset: u32,
40 pub length: u32,
42 pub token_count: u16,
44 pub flags: u32,
46}
47
48impl SectionEntry {
49 pub fn new(name_index: u8, offset: u32, length: u32) -> Self {
52 Self {
53 name_index,
54 priority: Priority::medium(),
55 offset,
56 length,
57 token_count: estimate_tokens(length),
58 flags: 0,
59 }
60 }
61
62 pub fn with_priority(mut self, priority: Priority) -> Self {
64 self.priority = priority;
65 self
66 }
67
68 pub fn with_token_count(mut self, count: u16) -> Self {
70 self.token_count = count;
71 self
72 }
73
74 pub fn with_flags(mut self, flags: u32) -> Self {
76 self.flags = flags;
77 self
78 }
79
80 pub fn with_classification(mut self, classification: ChunkClassification) -> Self {
82 self.flags = (self.flags & !CLASSIFICATION_MASK) | classification.bits();
84 self
85 }
86
87 pub fn classification(&self) -> ChunkClassification {
89 ChunkClassification::from_bits(self.flags)
90 }
91
92 pub fn section_flags(&self) -> u32 {
94 self.flags & !CLASSIFICATION_MASK
95 }
96
97 pub fn write<W: Write>(&self, writer: &mut W) -> FafbResult<()> {
99 writer.write_u8(self.name_index)?;
100 writer.write_u8(self.priority.value())?;
101 writer.write_u32::<LittleEndian>(self.offset)?;
102 writer.write_u32::<LittleEndian>(self.length)?;
103 writer.write_u16::<LittleEndian>(self.token_count)?;
104 writer.write_u32::<LittleEndian>(self.flags)?;
105 Ok(())
106 }
107
108 pub fn to_bytes(&self) -> FafbResult<Vec<u8>> {
110 let mut buf = Vec::with_capacity(SECTION_ENTRY_SIZE);
111 self.write(&mut buf)?;
112 Ok(buf)
113 }
114
115 pub fn read<R: Read>(reader: &mut R) -> FafbResult<Self> {
117 let name_index = reader.read_u8()?;
118 let priority = Priority::from(reader.read_u8()?);
119 let offset = reader.read_u32::<LittleEndian>()?;
120 let length = reader.read_u32::<LittleEndian>()?;
121 let token_count = reader.read_u16::<LittleEndian>()?;
122 let flags = reader.read_u32::<LittleEndian>()?;
123
124 Ok(Self {
125 name_index,
126 priority,
127 offset,
128 length,
129 token_count,
130 flags,
131 })
132 }
133
134 pub fn from_bytes(data: &[u8]) -> FafbResult<Self> {
136 if data.len() < SECTION_ENTRY_SIZE {
137 return Err(FafbError::FileTooSmall {
138 expected: SECTION_ENTRY_SIZE,
139 actual: data.len(),
140 });
141 }
142 let mut cursor = std::io::Cursor::new(data);
143 Self::read(&mut cursor)
144 }
145
146 pub fn validate_bounds(&self, file_size: u32) -> FafbResult<()> {
148 let end =
152 self.offset
153 .checked_add(self.length)
154 .ok_or(FafbError::InvalidSectionTableOffset {
155 offset: self.offset,
156 file_size,
157 })?;
158
159 if end > file_size {
161 return Err(FafbError::InvalidSectionTableOffset {
162 offset: self.offset,
163 file_size,
164 });
165 }
166
167 Ok(())
168 }
169}
170
171#[derive(Debug, Clone, Default)]
173pub struct SectionTable {
174 entries: Vec<SectionEntry>,
175}
176
177impl SectionTable {
178 pub fn new() -> Self {
180 Self {
181 entries: Vec::new(),
182 }
183 }
184
185 pub fn with_capacity(capacity: usize) -> Self {
187 Self {
188 entries: Vec::with_capacity(capacity),
189 }
190 }
191
192 pub fn push(&mut self, entry: SectionEntry) {
194 self.entries.push(entry);
195 }
196
197 pub fn len(&self) -> usize {
199 self.entries.len()
200 }
201
202 pub fn is_empty(&self) -> bool {
204 self.entries.is_empty()
205 }
206
207 pub fn get(&self, index: usize) -> Option<&SectionEntry> {
209 self.entries.get(index)
210 }
211
212 pub fn get_by_name_index(&self, name_index: u8) -> Option<&SectionEntry> {
214 self.entries.iter().find(|e| e.name_index == name_index)
215 }
216
217 pub fn entries(&self) -> &[SectionEntry] {
219 &self.entries
220 }
221
222 pub fn entries_by_priority(&self) -> Vec<&SectionEntry> {
224 let mut sorted: Vec<_> = self.entries.iter().collect();
225 sorted.sort_by_key(|e| std::cmp::Reverse(e.priority));
226 sorted
227 }
228
229 pub fn entries_within_budget(&self, budget: u16) -> Vec<&SectionEntry> {
236 let budget = budget as u32;
237 let mut keep: Vec<&SectionEntry> = self.entries.iter().collect();
238 for tier in [TierDrop::P64, TierDrop::P128, TierDrop::P150_200] {
239 if token_sum(&keep) <= budget {
240 break;
241 }
242 keep.retain(|e| e.priority.is_critical() || !tier.matches(e.priority.value()));
243 }
244 keep
245 }
246
247 pub fn total_tokens(&self) -> u32 {
249 self.entries.iter().map(|e| e.token_count as u32).sum()
250 }
251
252 pub fn table_size(&self) -> usize {
254 self.entries.len() * SECTION_ENTRY_SIZE
255 }
256
257 pub fn write<W: Write>(&self, writer: &mut W) -> FafbResult<()> {
259 for entry in &self.entries {
260 entry.write(writer)?;
261 }
262 Ok(())
263 }
264
265 pub fn to_bytes(&self) -> FafbResult<Vec<u8>> {
267 let mut buf = Vec::with_capacity(self.table_size());
268 self.write(&mut buf)?;
269 Ok(buf)
270 }
271
272 pub fn read<R: Read>(reader: &mut R, count: usize) -> FafbResult<Self> {
274 let mut entries = Vec::with_capacity(count);
275 for _ in 0..count {
276 entries.push(SectionEntry::read(reader)?);
277 }
278 Ok(Self { entries })
279 }
280
281 pub fn from_bytes(data: &[u8], count: usize) -> FafbResult<Self> {
283 let expected_size = count * SECTION_ENTRY_SIZE;
284 if data.len() < expected_size {
285 return Err(FafbError::FileTooSmall {
286 expected: expected_size,
287 actual: data.len(),
288 });
289 }
290 let mut cursor = std::io::Cursor::new(data);
291 Self::read(&mut cursor, count)
292 }
293
294 pub fn validate_bounds(&self, file_size: u32) -> FafbResult<()> {
296 for entry in &self.entries {
297 entry.validate_bounds(file_size)?;
298 }
299 Ok(())
300 }
301}
302
303impl IntoIterator for SectionTable {
304 type Item = SectionEntry;
305 type IntoIter = std::vec::IntoIter<SectionEntry>;
306
307 fn into_iter(self) -> Self::IntoIter {
308 self.entries.into_iter()
309 }
310}
311
312impl<'a> IntoIterator for &'a SectionTable {
313 type Item = &'a SectionEntry;
314 type IntoIter = std::slice::Iter<'a, SectionEntry>;
315
316 fn into_iter(self) -> Self::IntoIter {
317 self.entries.iter()
318 }
319}
320
321fn token_sum(entries: &[&SectionEntry]) -> u32 {
322 entries.iter().map(|e| e.token_count as u32).sum()
323}
324
325#[derive(Clone, Copy)]
326enum TierDrop {
327 P64,
328 P128,
329 P150_200,
330}
331
332impl TierDrop {
333 fn matches(self, priority: u8) -> bool {
334 match self {
335 Self::P64 => priority == 64,
336 Self::P128 => priority == 128,
337 Self::P150_200 => (150..=200).contains(&priority),
338 }
339 }
340}
341
342fn estimate_tokens(byte_length: u32) -> u16 {
345 std::cmp::min(byte_length / 4, u16::MAX as u32) as u16
350}
351
352#[cfg(test)]
353mod tests {
354 use super::*;
355
356 #[test]
357 fn test_section_entry_size() {
358 let entry = SectionEntry::new(0, 32, 100);
359 let bytes = entry.to_bytes().unwrap();
360 assert_eq!(bytes.len(), SECTION_ENTRY_SIZE);
361 assert_eq!(bytes.len(), 16);
362 }
363
364 #[test]
365 fn test_section_entry_roundtrip() {
366 let original = SectionEntry::new(5, 64, 256)
367 .with_priority(Priority::high())
368 .with_token_count(100)
369 .with_flags(0xDEADBEEF);
370
371 let bytes = original.to_bytes().unwrap();
372 let recovered = SectionEntry::from_bytes(&bytes).unwrap();
373
374 assert_eq!(original.name_index, recovered.name_index);
375 assert_eq!(original.priority, recovered.priority);
376 assert_eq!(original.offset, recovered.offset);
377 assert_eq!(original.length, recovered.length);
378 assert_eq!(original.token_count, recovered.token_count);
379 assert_eq!(original.flags, recovered.flags);
380 }
381
382 #[test]
383 fn test_token_estimation() {
384 assert_eq!(estimate_tokens(0), 0);
385 assert_eq!(estimate_tokens(4), 1);
386 assert_eq!(estimate_tokens(100), 25);
387 assert_eq!(estimate_tokens(1000), 250);
388 }
389
390 #[test]
391 fn test_token_estimation_cap() {
392 let huge = estimate_tokens(u32::MAX);
394 assert_eq!(huge, u16::MAX);
395 }
396
397 #[test]
398 fn test_section_table_empty() {
399 let table = SectionTable::new();
400 assert!(table.is_empty());
401 assert_eq!(table.len(), 0);
402 assert_eq!(table.table_size(), 0);
403 }
404
405 #[test]
406 fn test_section_table_push() {
407 let mut table = SectionTable::new();
408 table.push(SectionEntry::new(0, 32, 100));
409 table.push(SectionEntry::new(1, 132, 200));
410
411 assert_eq!(table.len(), 2);
412 assert_eq!(table.table_size(), 32);
413 }
414
415 #[test]
416 fn test_section_table_roundtrip() {
417 let mut original = SectionTable::new();
418 original.push(SectionEntry::new(0, 32, 100));
419 original.push(SectionEntry::new(1, 132, 200));
420 original.push(SectionEntry::new(2, 332, 500));
421
422 let bytes = original.to_bytes().unwrap();
423 assert_eq!(bytes.len(), 48); let recovered = SectionTable::from_bytes(&bytes, 3).unwrap();
426 assert_eq!(recovered.len(), 3);
427
428 for (orig, recv) in original.entries().iter().zip(recovered.entries().iter()) {
429 assert_eq!(orig.name_index, recv.name_index);
430 assert_eq!(orig.offset, recv.offset);
431 assert_eq!(orig.length, recv.length);
432 }
433 }
434
435 #[test]
436 fn test_section_table_get_by_name_index() {
437 let mut table = SectionTable::new();
438 table.push(SectionEntry::new(0, 32, 100));
439 table.push(SectionEntry::new(1, 132, 200));
440
441 let first = table.get_by_name_index(0);
442 assert!(first.is_some());
443 assert_eq!(first.unwrap().offset, 32);
444
445 assert!(table.get_by_name_index(9).is_none());
446 }
447
448 #[test]
449 fn test_section_table_priority_sorting() {
450 let mut table = SectionTable::new();
451 table.push(SectionEntry::new(2, 0, 100).with_priority(Priority::low()));
452 table.push(SectionEntry::new(0, 0, 100).with_priority(Priority::critical()));
453 table.push(SectionEntry::new(1, 0, 100).with_priority(Priority::high()));
454
455 let sorted = table.entries_by_priority();
456 assert_eq!(sorted[0].name_index, 0); assert_eq!(sorted[1].name_index, 1); assert_eq!(sorted[2].name_index, 2); }
460
461 #[test]
462 fn test_section_table_budget() {
463 let mut table = SectionTable::new();
464 table.push(
465 SectionEntry::new(0, 0, 100)
466 .with_priority(Priority::critical())
467 .with_token_count(50),
468 );
469 table.push(
470 SectionEntry::new(1, 0, 200)
471 .with_priority(Priority::high())
472 .with_token_count(100),
473 );
474 table.push(
475 SectionEntry::new(2, 0, 1000)
476 .with_priority(Priority::low())
477 .with_token_count(500),
478 );
479
480 let within_budget = table.entries_within_budget(200);
482 assert_eq!(within_budget.len(), 2);
483
484 assert!(within_budget.iter().any(|e| e.name_index == 0));
486 assert_eq!(within_budget[0].name_index, 0);
488 assert_eq!(within_budget[1].name_index, 1);
489 }
490
491 #[test]
492 fn test_budget_does_not_punch_holes() {
493 let mut table = SectionTable::new();
497 table.push(
498 SectionEntry::new(0, 0, 80)
499 .with_priority(Priority::new(150))
500 .with_token_count(80),
501 );
502 table.push(
503 SectionEntry::new(1, 0, 40)
504 .with_priority(Priority::high())
505 .with_token_count(40),
506 );
507 table.push(
508 SectionEntry::new(2, 0, 40)
509 .with_priority(Priority::medium())
510 .with_token_count(40),
511 );
512
513 let kept = table.entries_within_budget(90);
514 assert!(kept.is_empty());
517 }
518
519 #[test]
520 fn test_section_table_total_tokens() {
521 let mut table = SectionTable::new();
522 table.push(SectionEntry::new(0, 0, 100).with_token_count(50));
523 table.push(SectionEntry::new(1, 0, 200).with_token_count(100));
524
525 assert_eq!(table.total_tokens(), 150);
526 }
527
528 #[test]
529 fn test_section_entry_validate_bounds() {
530 let entry = SectionEntry::new(0, 100, 50);
531
532 assert!(entry.validate_bounds(200).is_ok());
534
535 assert!(entry.validate_bounds(100).is_err());
537 }
538
539 #[test]
540 fn test_unknown_name_index_preserved() {
541 let entry = SectionEntry::new(0x99, 0, 100)
543 .with_priority(Priority::medium())
544 .with_token_count(25);
545
546 let bytes = entry.to_bytes().unwrap();
547 let recovered = SectionEntry::from_bytes(&bytes).unwrap();
548
549 assert_eq!(recovered.name_index, 0x99);
550 }
551}