1pub mod io;
4
5use std::collections::BTreeSet;
6use std::path::PathBuf;
7
8use crate::epoch::{
9 ComputeUnits, EmbeddingEpoch, EmbeddingRuntime, EmbeddingTower, EpochId, EpochIdentity,
10 Normalization,
11};
12use crate::format::FormatFamily;
13use crate::format::frame::{FormatError, decode_artifact, encode_artifact};
14use crate::fts::tokenizer::TokenizerEpoch;
15use crate::meta::{ColumnDefinition, ColumnId, ColumnType, Schema};
16use crate::segment::{ClusteringKeyRange, SegmentId, SegmentMeta};
17
18const CLUSTERING_RANGE_EXTENSION_MAGIC: [u8; 4] = *b"TSR1";
19const CLUSTERING_RANGE_RECORD_LEN: usize = 24;
20
21#[derive(Clone, Debug, Eq, PartialEq)]
23pub struct EpochMeta {
24 pub id: EpochId,
26 pub embedding: EmbeddingEpoch,
28 pub tokenizer: TokenizerEpoch,
30}
31
32#[derive(Clone, Debug, Eq, PartialEq)]
34pub struct Manifest {
35 pub generation: u64,
37 pub log_seq: u64,
39 pub segments: Vec<SegmentMeta>,
41 pub epochs: Vec<EpochMeta>,
43 pub epoch_alias: Option<EpochIdentity>,
45 pub schema: Schema,
47}
48
49#[derive(Debug)]
51pub enum ManifestError {
52 Io {
54 path: PathBuf,
56 source: std::io::Error,
58 },
59 Format(FormatError),
61 Decode(String),
63 AheadOfLog {
65 snapshot: u64,
67 durable: u64,
69 },
70 Segment(crate::segment::SegmentError),
72 UnknownEpochAlias {
74 alias: EpochIdentity,
76 },
77 UnknownSegmentEpoch {
79 segment: SegmentId,
81 epoch: EpochId,
83 },
84}
85
86impl ManifestError {
87 pub(crate) fn io(path: impl Into<PathBuf>, source: std::io::Error) -> Self {
88 Self::Io {
89 path: path.into(),
90 source,
91 }
92 }
93}
94
95impl std::fmt::Display for ManifestError {
96 fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
97 match self {
98 Self::Io { path, source } => {
99 write!(formatter, "manifest I/O {}: {source}", path.display())
100 }
101 Self::Format(error) => error.fmt(formatter),
102 Self::Decode(detail) => write!(formatter, "manifest decode failed: {detail}"),
103 Self::AheadOfLog { snapshot, durable } => write!(
104 formatter,
105 "manifest log sequence {snapshot} is ahead of durable WAL end {durable}"
106 ),
107 Self::Segment(error) => {
108 write!(formatter, "manifest segment validation failed: {error}")
109 }
110 Self::UnknownEpochAlias { alias } => write!(
111 formatter,
112 "manifest epoch alias ({}, {}) does not name a registry entry",
113 alias.embedding, alias.tokenizer
114 ),
115 Self::UnknownSegmentEpoch { segment, epoch } => write!(
116 formatter,
117 "manifest segment {segment} names unknown embedding epoch {epoch}"
118 ),
119 }
120 }
121}
122
123impl std::error::Error for ManifestError {
124 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
125 match self {
126 Self::Io { source, .. } => Some(source),
127 Self::Format(error) => Some(error),
128 Self::Segment(error) => Some(error),
129 Self::Decode(_)
130 | Self::AheadOfLog { .. }
131 | Self::UnknownEpochAlias { .. }
132 | Self::UnknownSegmentEpoch { .. } => None,
133 }
134 }
135}
136
137impl From<FormatError> for ManifestError {
138 fn from(error: FormatError) -> Self {
139 Self::Format(error)
140 }
141}
142
143impl From<crate::segment::SegmentError> for ManifestError {
144 fn from(error: crate::segment::SegmentError) -> Self {
145 Self::Segment(error)
146 }
147}
148
149pub fn encode_manifest(manifest: &Manifest) -> Result<Vec<u8>, ManifestError> {
151 validate_epoch_references(manifest)?;
152 let mut payload = Vec::new();
153 payload.extend_from_slice(&manifest.generation.to_le_bytes());
154 payload.extend_from_slice(&manifest.log_seq.to_le_bytes());
155 append_u32_len(manifest.segments.len(), "segments", &mut payload)?;
156 append_u32_len(manifest.epochs.len(), "epochs", &mut payload)?;
157 append_u32_len(
158 manifest.schema.user_column_count(),
159 "schema columns",
160 &mut payload,
161 )?;
162 payload.extend_from_slice(&0_u32.to_le_bytes());
163 append_optional_identity(manifest.epoch_alias, &mut payload);
164 for segment in &manifest.segments {
165 payload.extend_from_slice(segment.id.as_bytes());
166 payload.extend_from_slice(&segment.row_count.to_le_bytes());
167 payload.extend_from_slice(&segment.scheme.to_le_bytes());
168 payload.extend_from_slice(&0_u16.to_le_bytes());
169 payload.extend_from_slice(&segment.dims.to_le_bytes());
170 payload.extend_from_slice(&segment.file_size.to_le_bytes());
171 append_optional_epoch_id(segment.epoch_id, &mut payload);
172 }
173 for epoch in &manifest.epochs {
174 payload.extend_from_slice(&epoch.id.value().to_le_bytes());
175 payload.extend_from_slice(&epoch.tokenizer.value().to_le_bytes());
176 append_embedding_epoch(&epoch.embedding, &mut payload)?;
177 }
178 for definition in manifest.schema.columns().iter().skip(1) {
179 payload.extend_from_slice(&definition.id().get().to_le_bytes());
180 payload.extend_from_slice(&column_type_id(definition.column_type()).to_le_bytes());
181 payload.extend_from_slice(&u16::from(definition.is_nullable()).to_le_bytes());
182 append_string(definition.name(), &mut payload)?;
183 }
184 append_clustering_ranges(&manifest.segments, &mut payload)?;
185 Ok(encode_artifact(FormatFamily::Manifest, 0, &payload))
186}
187
188fn append_clustering_ranges(
189 segments: &[SegmentMeta],
190 output: &mut Vec<u8>,
191) -> Result<(), ManifestError> {
192 if segments
193 .iter()
194 .all(|segment| segment.clustering_key_range == ClusteringKeyRange::Unstamped)
195 {
196 return Ok(());
197 }
198 output.extend_from_slice(&CLUSTERING_RANGE_EXTENSION_MAGIC);
199 append_u32_len(segments.len(), "clustering ranges", output)?;
200 for segment in segments {
201 let (tag, min_ts, max_ts) = match segment.clustering_key_range {
202 ClusteringKeyRange::Unstamped => (0_u8, 0_i64, 0_i64),
203 ClusteringKeyRange::Empty => (1_u8, 0_i64, 0_i64),
204 ClusteringKeyRange::Bounded { min_ts, max_ts } => {
205 if min_ts > max_ts {
206 return Err(ManifestError::Decode(format!(
207 "segment {} clustering range {min_ts}..={max_ts} is inverted",
208 segment.id
209 )));
210 }
211 (2_u8, min_ts, max_ts)
212 }
213 };
214 output.push(tag);
215 output.extend_from_slice(&[0_u8; 7]);
216 output.extend_from_slice(&min_ts.to_le_bytes());
217 output.extend_from_slice(&max_ts.to_le_bytes());
218 }
219 Ok(())
220}
221
222pub fn decode_manifest(artifact: &str, bytes: &[u8]) -> Result<Manifest, ManifestError> {
224 #[cfg(any(test, feature = "test-seams"))]
225 let actual_family = bytes
226 .get(8..10)
227 .and_then(|family| <[u8; 2]>::try_from(family).ok())
228 .map(u16::from_le_bytes);
229 let framed = decode_artifact(artifact, FormatFamily::Manifest, bytes).map_err(|error| {
230 #[cfg(any(test, feature = "test-seams"))]
231 crate::lifecycle::record_storage_manifest_format_fault(&error, actual_family);
232 ManifestError::Format(error)
233 })?;
234 let mut cursor = ManifestCursor::new(framed.payload);
235 let generation = cursor.u64()?;
236 let log_seq = cursor.u64()?;
237 let segment_count = cursor.usize_from_u32()?;
238 let epoch_count = cursor.usize_from_u32()?;
239 let schema_count = cursor.usize_from_u32()?;
240 if cursor.u32()? != 0 {
241 return Err(ManifestError::Decode(
242 "manifest reserved field is non-zero".to_owned(),
243 ));
244 }
245 let epoch_alias = cursor.optional_identity()?;
246 let mut segments = Vec::with_capacity(segment_count);
247 for _ in 0..segment_count {
248 let id_bytes: [u8; 16] = cursor
249 .take(16)?
250 .try_into()
251 .map_err(|_| ManifestError::Decode("invalid segment id".to_owned()))?;
252 let row_count = cursor.u32()?;
253 let scheme = cursor.u16()?;
254 crate::format::FormatRegistry::require_scheme(scheme)
255 .map_err(|error| ManifestError::Decode(error.to_string()))?;
256 if cursor.u16()? != 0 {
257 return Err(ManifestError::Decode(
258 "segment reserved field is non-zero".to_owned(),
259 ));
260 }
261 segments.push(SegmentMeta {
262 id: SegmentId::from_bytes(id_bytes),
263 row_count,
264 scheme,
265 dims: cursor.u32()?,
266 file_size: cursor.u64()?,
267 epoch_id: cursor.optional_epoch_id()?,
268 clustering_key_range: crate::segment::ClusteringKeyRange::Unstamped,
269 });
270 }
271 let mut epochs = Vec::with_capacity(epoch_count);
272 for _ in 0..epoch_count {
273 epochs.push(EpochMeta {
274 id: EpochId::from_value(cursor.u64()?),
275 tokenizer: TokenizerEpoch::from_value(cursor.u64()?),
276 embedding: cursor.embedding_epoch()?,
277 });
278 }
279 let mut definitions = Vec::with_capacity(schema_count);
280 for _ in 0..schema_count {
281 let id = ColumnId::new(cursor.u32()?);
282 let column_type = column_type_from_id(cursor.u16()?)?;
283 let nullable = match cursor.u16()? {
284 0 => false,
285 1 => true,
286 value => {
287 return Err(ManifestError::Decode(format!(
288 "invalid nullable flag {value}"
289 )));
290 }
291 };
292 definitions.push(ColumnDefinition::new(
293 id,
294 cursor.string()?,
295 column_type,
296 nullable,
297 ));
298 }
299 if cursor.remaining() != 0 {
300 decode_clustering_ranges(&mut cursor, &mut segments)?;
301 }
302 cursor.finish()?;
303 let schema =
304 Schema::new(definitions).map_err(|error| ManifestError::Decode(error.to_string()))?;
305 let manifest = Manifest {
306 generation,
307 log_seq,
308 segments,
309 epochs,
310 epoch_alias,
311 schema,
312 };
313 validate_epoch_references(&manifest)?;
314 Ok(manifest)
315}
316
317fn validate_epoch_references(manifest: &Manifest) -> Result<(), ManifestError> {
318 let mut identities = BTreeSet::new();
319 for epoch in &manifest.epochs {
320 let identity = EpochIdentity::from_meta(epoch)
321 .map_err(|error| ManifestError::Decode(error.to_string()))?;
322 if !identities.insert(identity) {
323 return Err(ManifestError::Decode(format!(
324 "duplicate epoch registry identity ({}, {})",
325 identity.embedding, identity.tokenizer
326 )));
327 }
328 }
329
330 match manifest.epoch_alias {
331 Some(alias) if !identities.contains(&alias) => {
332 return Err(ManifestError::UnknownEpochAlias { alias });
333 }
334 None if !identities.is_empty() => {
335 return Err(ManifestError::Decode(
336 "non-empty epoch registry has no published alias".to_owned(),
337 ));
338 }
339 Some(_) | None => {}
340 }
341
342 for segment in &manifest.segments {
343 match segment.epoch_id {
344 Some(epoch)
345 if !identities
346 .iter()
347 .any(|identity| identity.embedding == epoch) =>
348 {
349 return Err(ManifestError::UnknownSegmentEpoch {
350 segment: segment.id,
351 epoch,
352 });
353 }
354 None if !identities.is_empty() => {
355 return Err(ManifestError::Decode(format!(
356 "segment {} has no embedding epoch tag",
357 segment.id
358 )));
359 }
360 Some(_) | None => {}
361 }
362 }
363 Ok(())
364}
365
366fn append_optional_identity(identity: Option<EpochIdentity>, output: &mut Vec<u8>) {
367 let (tag, embedding, tokenizer) = match identity {
368 Some(identity) => (1_u8, identity.embedding.value(), identity.tokenizer.value()),
369 None => (0, 0, 0),
370 };
371 output.push(tag);
372 output.extend_from_slice(&[0_u8; 7]);
373 output.extend_from_slice(&embedding.to_le_bytes());
374 output.extend_from_slice(&tokenizer.to_le_bytes());
375}
376
377fn append_optional_epoch_id(epoch: Option<EpochId>, output: &mut Vec<u8>) {
378 let (tag, value) = epoch.map_or((0_u8, 0_u64), |epoch| (1, epoch.value()));
379 output.push(tag);
380 output.extend_from_slice(&[0_u8; 7]);
381 output.extend_from_slice(&value.to_le_bytes());
382}
383
384fn append_embedding_epoch(
385 epoch: &EmbeddingEpoch,
386 output: &mut Vec<u8>,
387) -> Result<(), ManifestError> {
388 append_embedding_tower(&epoch.document, output)?;
389 append_embedding_tower(&epoch.query, output)?;
390 append_bytes(&epoch.alignment_digest, output)
391}
392
393fn append_embedding_tower(
394 tower: &EmbeddingTower,
395 output: &mut Vec<u8>,
396) -> Result<(), ManifestError> {
397 append_string(&tower.model_id, output)?;
398 append_string(&tower.model_version, output)?;
399 append_bytes(&tower.weights_digest, output)?;
400 output.extend_from_slice(&tower.dims.to_le_bytes());
401 output.extend_from_slice(&(tower.normalization as u16).to_le_bytes());
402 append_string(&tower.prompt_prefix, output)?;
403 output.extend_from_slice(&tower.max_tokens.to_le_bytes());
404 output.extend_from_slice(&(tower.runtime as u16).to_le_bytes());
405 output.extend_from_slice(&(tower.compute_units as u16).to_le_bytes());
406 match &tower.os_build {
407 Some(build) => {
408 output.push(1);
409 output.extend_from_slice(&[0_u8; 3]);
410 append_string(build, output)?;
411 }
412 None => {
413 output.push(0);
414 output.extend_from_slice(&[0_u8; 3]);
415 }
416 }
417 Ok(())
418}
419
420fn decode_clustering_ranges(
421 cursor: &mut ManifestCursor<'_>,
422 segments: &mut [SegmentMeta],
423) -> Result<(), ManifestError> {
424 if cursor.take(CLUSTERING_RANGE_EXTENSION_MAGIC.len())? != CLUSTERING_RANGE_EXTENSION_MAGIC {
425 return Err(ManifestError::Decode(
426 "unknown manifest extension after schema".to_owned(),
427 ));
428 }
429 let count = cursor.usize_from_u32()?;
430 if count != segments.len() {
431 return Err(ManifestError::Decode(format!(
432 "clustering range count {count} does not match segment count {}",
433 segments.len()
434 )));
435 }
436 let expected_bytes = count
437 .checked_mul(CLUSTERING_RANGE_RECORD_LEN)
438 .ok_or_else(|| ManifestError::Decode("clustering range bytes overflow".to_owned()))?;
439 if cursor.remaining() != expected_bytes {
440 return Err(ManifestError::Decode(format!(
441 "clustering range extension has {} bytes, expected {expected_bytes}",
442 cursor.remaining()
443 )));
444 }
445 for segment in segments {
446 let tag = cursor.u8()?;
447 if cursor.take(7)?.iter().any(|byte| *byte != 0) {
448 return Err(ManifestError::Decode(
449 "clustering range reserved bytes are non-zero".to_owned(),
450 ));
451 }
452 let min_ts = cursor.i64()?;
453 let max_ts = cursor.i64()?;
454 segment.clustering_key_range = match tag {
455 0 if min_ts == 0 && max_ts == 0 => ClusteringKeyRange::Unstamped,
456 1 if min_ts == 0 && max_ts == 0 => ClusteringKeyRange::Empty,
457 2 if min_ts <= max_ts => ClusteringKeyRange::Bounded { min_ts, max_ts },
458 0 | 1 => {
459 return Err(ManifestError::Decode(format!(
460 "clustering range tag {tag} requires zero bounds"
461 )));
462 }
463 2 => {
464 return Err(ManifestError::Decode(format!(
465 "clustering range {min_ts}..={max_ts} is inverted"
466 )));
467 }
468 unknown => {
469 return Err(ManifestError::Decode(format!(
470 "unknown clustering range tag {unknown}"
471 )));
472 }
473 };
474 }
475 Ok(())
476}
477
478fn append_u32_len(length: usize, name: &str, output: &mut Vec<u8>) -> Result<(), ManifestError> {
479 let value = u32::try_from(length)
480 .map_err(|_| ManifestError::Decode(format!("{name} length exceeds u32")))?;
481 output.extend_from_slice(&value.to_le_bytes());
482 Ok(())
483}
484
485fn append_string(value: &str, output: &mut Vec<u8>) -> Result<(), ManifestError> {
486 append_bytes(value.as_bytes(), output)
487}
488
489fn append_bytes(value: &[u8], output: &mut Vec<u8>) -> Result<(), ManifestError> {
490 append_u32_len(value.len(), "byte field", output)?;
491 output.extend_from_slice(value);
492 Ok(())
493}
494
495fn column_type_id(column_type: ColumnType) -> u16 {
496 match column_type {
497 ColumnType::Id128 => 7,
498 ColumnType::U64 => 1,
499 ColumnType::I64 => 2,
500 ColumnType::F64 => 3,
501 ColumnType::Bool => 4,
502 ColumnType::DictionaryString => 5,
503 ColumnType::RawString => 6,
504 }
505}
506
507fn column_type_from_id(id: u16) -> Result<ColumnType, ManifestError> {
508 match id {
509 7 => Ok(ColumnType::Id128),
510 1 => Ok(ColumnType::U64),
511 2 => Ok(ColumnType::I64),
512 3 => Ok(ColumnType::F64),
513 4 => Ok(ColumnType::Bool),
514 5 => Ok(ColumnType::DictionaryString),
515 6 => Ok(ColumnType::RawString),
516 _ => Err(ManifestError::Decode(format!("unknown column type {id}"))),
517 }
518}
519
520struct ManifestCursor<'a> {
521 bytes: &'a [u8],
522 position: usize,
523}
524
525impl<'a> ManifestCursor<'a> {
526 const fn new(bytes: &'a [u8]) -> Self {
527 Self { bytes, position: 0 }
528 }
529
530 fn take(&mut self, length: usize) -> Result<&'a [u8], ManifestError> {
531 let end = self
532 .position
533 .checked_add(length)
534 .ok_or_else(|| ManifestError::Decode("payload offset overflow".to_owned()))?;
535 let value = self.bytes.get(self.position..end).ok_or_else(|| {
536 ManifestError::Decode(format!(
537 "payload truncated at {}, need {length}, total {}",
538 self.position,
539 self.bytes.len()
540 ))
541 })?;
542 self.position = end;
543 Ok(value)
544 }
545
546 fn u16(&mut self) -> Result<u16, ManifestError> {
547 let raw: [u8; 2] = self
548 .take(2)?
549 .try_into()
550 .map_err(|_| ManifestError::Decode("invalid u16".to_owned()))?;
551 Ok(u16::from_le_bytes(raw))
552 }
553
554 fn u8(&mut self) -> Result<u8, ManifestError> {
555 self.take(1)?
556 .first()
557 .copied()
558 .ok_or_else(|| ManifestError::Decode("invalid u8".to_owned()))
559 }
560
561 fn u32(&mut self) -> Result<u32, ManifestError> {
562 let raw: [u8; 4] = self
563 .take(4)?
564 .try_into()
565 .map_err(|_| ManifestError::Decode("invalid u32".to_owned()))?;
566 Ok(u32::from_le_bytes(raw))
567 }
568
569 fn u64(&mut self) -> Result<u64, ManifestError> {
570 let raw: [u8; 8] = self
571 .take(8)?
572 .try_into()
573 .map_err(|_| ManifestError::Decode("invalid u64".to_owned()))?;
574 Ok(u64::from_le_bytes(raw))
575 }
576
577 fn optional_identity(&mut self) -> Result<Option<EpochIdentity>, ManifestError> {
578 let tag = self.u8()?;
579 if self.take(7)?.iter().any(|byte| *byte != 0) {
580 return Err(ManifestError::Decode(
581 "epoch alias reserved bytes are non-zero".to_owned(),
582 ));
583 }
584 let embedding = self.u64()?;
585 let tokenizer = self.u64()?;
586 match (tag, embedding, tokenizer) {
587 (0, 0, 0) => Ok(None),
588 (1, embedding, tokenizer) => Ok(Some(EpochIdentity {
589 embedding: EpochId::from_value(embedding),
590 tokenizer: TokenizerEpoch::from_value(tokenizer),
591 })),
592 (0, _, _) => Err(ManifestError::Decode(
593 "absent epoch alias carries non-zero identity bytes".to_owned(),
594 )),
595 (unknown, _, _) => Err(ManifestError::Decode(format!(
596 "unknown epoch alias presence tag {unknown}"
597 ))),
598 }
599 }
600
601 fn optional_epoch_id(&mut self) -> Result<Option<EpochId>, ManifestError> {
602 let tag = self.u8()?;
603 if self.take(7)?.iter().any(|byte| *byte != 0) {
604 return Err(ManifestError::Decode(
605 "segment epoch reserved bytes are non-zero".to_owned(),
606 ));
607 }
608 let epoch = self.u64()?;
609 match (tag, epoch) {
610 (0, 0) => Ok(None),
611 (1, epoch) => Ok(Some(EpochId::from_value(epoch))),
612 (0, _) => Err(ManifestError::Decode(
613 "absent segment epoch tag carries a non-zero id".to_owned(),
614 )),
615 (unknown, _) => Err(ManifestError::Decode(format!(
616 "unknown segment epoch presence tag {unknown}"
617 ))),
618 }
619 }
620
621 fn embedding_epoch(&mut self) -> Result<EmbeddingEpoch, ManifestError> {
622 Ok(EmbeddingEpoch {
623 document: self.embedding_tower()?,
624 query: self.embedding_tower()?,
625 alignment_digest: self.bytes()?,
626 })
627 }
628
629 fn embedding_tower(&mut self) -> Result<EmbeddingTower, ManifestError> {
630 let model_id = self.string()?;
631 let model_version = self.string()?;
632 let weights_digest = self.bytes()?;
633 let dims = self.u32()?;
634 let normalization = match self.u16()? {
635 0 => Normalization::None,
636 1 => Normalization::L2,
637 unknown => {
638 return Err(ManifestError::Decode(format!(
639 "unknown embedding normalization {unknown}"
640 )));
641 }
642 };
643 let prompt_prefix = self.string()?;
644 let max_tokens = self.u32()?;
645 let runtime = match self.u16()? {
646 1 => EmbeddingRuntime::CoreMl,
647 2 => EmbeddingRuntime::Mlx,
648 3 => EmbeddingRuntime::CpuReference,
649 unknown => {
650 return Err(ManifestError::Decode(format!(
651 "unknown embedding runtime {unknown}"
652 )));
653 }
654 };
655 let compute_units = match self.u16()? {
656 1 => ComputeUnits::Cpu,
657 2 => ComputeUnits::CpuAndGpu,
658 3 => ComputeUnits::CpuAndNeuralEngine,
659 4 => ComputeUnits::All,
660 unknown => {
661 return Err(ManifestError::Decode(format!(
662 "unknown embedding compute units {unknown}"
663 )));
664 }
665 };
666 let os_tag = self.u8()?;
667 if self.take(3)?.iter().any(|byte| *byte != 0) {
668 return Err(ManifestError::Decode(
669 "embedding OS-build reserved bytes are non-zero".to_owned(),
670 ));
671 }
672 let os_build = match os_tag {
673 0 => None,
674 1 => Some(self.string()?),
675 unknown => {
676 return Err(ManifestError::Decode(format!(
677 "unknown embedding OS-build presence tag {unknown}"
678 )));
679 }
680 };
681 Ok(EmbeddingTower {
682 model_id,
683 model_version,
684 weights_digest,
685 dims,
686 normalization,
687 prompt_prefix,
688 max_tokens,
689 runtime,
690 compute_units,
691 os_build,
692 })
693 }
694
695 fn i64(&mut self) -> Result<i64, ManifestError> {
696 let raw: [u8; 8] = self
697 .take(8)?
698 .try_into()
699 .map_err(|_| ManifestError::Decode("invalid i64".to_owned()))?;
700 Ok(i64::from_le_bytes(raw))
701 }
702
703 const fn remaining(&self) -> usize {
704 self.bytes.len().saturating_sub(self.position)
705 }
706
707 fn usize_from_u32(&mut self) -> Result<usize, ManifestError> {
708 usize::try_from(self.u32()?)
709 .map_err(|_| ManifestError::Decode("u32 length exceeds usize".to_owned()))
710 }
711
712 fn string(&mut self) -> Result<String, ManifestError> {
713 let length = self.usize_from_u32()?;
714 std::str::from_utf8(self.take(length)?)
715 .map(str::to_owned)
716 .map_err(|error| ManifestError::Decode(format!("invalid UTF-8: {error}")))
717 }
718
719 fn bytes(&mut self) -> Result<Vec<u8>, ManifestError> {
720 let length = self.usize_from_u32()?;
721 Ok(self.take(length)?.to_vec())
722 }
723
724 fn finish(&self) -> Result<(), ManifestError> {
725 if self.position == self.bytes.len() {
726 Ok(())
727 } else {
728 Err(ManifestError::Decode(format!(
729 "{} trailing payload bytes",
730 self.bytes.len().saturating_sub(self.position)
731 )))
732 }
733 }
734}