Skip to main content

tsaur_core/
manifest.rs

1//! Manifest and chunk index data model (serialised as CBOR; field order is fixed by the structs,
2//! which keeps the encoding deterministic for identical inputs).
3
4use crate::chunk::ChunkParams;
5use serde::{Deserialize, Serialize};
6use serde_bytes::ByteBuf;
7
8fn bytebuf_is_empty(b: &ByteBuf) -> bool {
9    b.as_ref().is_empty()
10}
11
12fn is_zero_u8(x: &u8) -> bool {
13    *x == 0
14}
15
16#[derive(Clone, Debug, Serialize, Deserialize)]
17pub struct ChunkRef {
18    /// BLAKE3-256 of the chunk plaintext.
19    pub h: ByteBuf,
20    pub s: u32,
21    /// 0 = stored in this archive; 1 = external (available in a referenced archive, see `Manifest::refs`)
22    #[serde(default, skip_serializing_if = "is_zero_u8")]
23    pub x: u8,
24}
25
26/// An archive whose chunks this archive references instead of storing ("compression by reference").
27#[derive(Clone, Debug, Serialize, Deserialize)]
28pub struct RefArchive {
29    /// Merkle root (content identity) of the referenced archive.
30    pub root: ByteBuf,
31    /// Advisory file name / locator.
32    pub hint: String,
33    /// Number of chunks of this archive that live in the referenced one.
34    pub chunks: u32,
35    pub bytes: u64,
36}
37
38#[derive(Clone, Debug, Serialize, Deserialize)]
39pub struct BlobRecord {
40    /// Offset inside the blobs section.
41    pub off: u64,
42    pub clen: u32,
43    pub ulen: u32,
44    pub codec: u8,
45    /// Indices (into the chunk table) of the chunks stored in this blob, in order.
46    pub n: Vec<u32>,
47    /// Codec parameters needed for decoding (e.g. PPMd: [order, mem_size]); empty for zstd/xz/store.
48    #[serde(default, skip_serializing_if = "Vec::is_empty")]
49    pub p: Vec<u32>,
50    /// Pre-filter applied before compression: low nibble = id (0 none, 1 x86 BCJ, 2 ARM64 BCJ),
51    /// high nibble = parameter (ARM64: byte offset of the first aligned instruction word).
52    #[serde(default, skip_serializing_if = "is_zero_u8")]
53    pub f: u8,
54    /// Codec 5 (zstd+delta): indices of the chunks whose concatenation is the raw-content
55    /// dictionary (an earlier version of the data, possibly external). Never delta blobs themselves.
56    #[serde(default, skip_serializing_if = "Vec::is_empty")]
57    pub d: Vec<u32>,
58}
59
60#[derive(Clone, Debug, Serialize, Deserialize, Default)]
61pub struct ChunkIndex {
62    pub chunks: Vec<ChunkRef>,
63    pub blobs: Vec<BlobRecord>,
64}
65
66#[derive(Clone, Copy, Debug, Serialize, Deserialize)]
67pub struct Limits {
68    pub max_total: u64,
69    pub max_entry: u64,
70    pub max_ratio: u32,
71}
72
73impl Default for Limits {
74    fn default() -> Self {
75        Self { max_total: 4 << 30, max_entry: 1 << 30, max_ratio: 1000 }
76    }
77}
78
79#[derive(Clone, Debug, Serialize, Deserialize)]
80pub struct ZipMemberMeta {
81    pub name: String,
82    /// 0 = stored (payload is plaintext), 1 = deflate inverted with preflate, 2 = raw compressed bytes kept
83    pub kind: u8,
84    /// Exact bytes of the local file header (fixed part + name + extra).
85    pub local_header: ByteBuf,
86    /// Exact bytes of the data descriptor that follows the payload (empty if none).
87    #[serde(default, skip_serializing_if = "bytebuf_is_empty")]
88    pub descriptor: ByteBuf,
89    /// Exact bytes of the central directory record for this member.
90    pub central: ByteBuf,
91    /// preflate corrections needed to recreate the deflate stream bit-exact (kind 1 only).
92    #[serde(default, skip_serializing_if = "bytebuf_is_empty")]
93    pub corrections: ByteBuf,
94    /// Length of the payload that was chunked (plaintext for kinds 0/1, raw bytes for kind 2).
95    pub size: u64,
96    pub chunks: Vec<u32>,
97}
98
99#[derive(Clone, Debug, Serialize, Deserialize)]
100pub struct ZipRecipe {
101    /// Member indices in the order their local headers appear in the file.
102    pub local_order: Vec<u32>,
103    /// Exact bytes from the end-of-central-directory record to the end of the file.
104    pub eocd: ByteBuf,
105    pub members: Vec<ZipMemberMeta>,
106}
107
108/// One segment of a file that embeds zlib/deflate streams (PDF FlateDecode objects and similar).
109#[derive(Clone, Debug, Serialize, Deserialize)]
110pub struct StreamSegment {
111    /// 0 = literal bytes (chunked as-is), 1 = zlib stream inverted with preflate
112    pub kind: u8,
113    /// zlib 2-byte header for kind 1 (empty otherwise)
114    #[serde(default, skip_serializing_if = "bytebuf_is_empty")]
115    pub header: ByteBuf,
116    /// zlib 4-byte Adler-32 trailer for kind 1 (empty otherwise)
117    #[serde(default, skip_serializing_if = "bytebuf_is_empty")]
118    pub trailer: ByteBuf,
119    #[serde(default, skip_serializing_if = "bytebuf_is_empty")]
120    pub corrections: ByteBuf,
121    /// Length of the chunked payload (literal bytes or inverted plaintext).
122    pub size: u64,
123    pub chunks: Vec<u32>,
124}
125
126#[derive(Clone, Debug, Serialize, Deserialize)]
127pub struct StreamRecipe {
128    pub segments: Vec<StreamSegment>,
129}
130
131/// A stored derivative generated at pack time from another entry (hybrid fidelity): the source
132/// stays bit-exact, the view is pre-computed so that agents read it without converting.
133#[derive(Clone, Debug, Serialize, Deserialize)]
134pub struct Derived {
135    /// Path of the source entry.
136    pub from: String,
137    /// "markdown" | "text"
138    pub view: String,
139    /// Tool that produced the view (name/version), for provenance.
140    pub generator: String,
141    /// Planning estimate (chars / 3.5), not a tokenizer measurement.
142    pub tokens_est: u64,
143}
144
145#[derive(Clone, Debug, Serialize, Deserialize)]
146pub struct Entry {
147    pub path: String,
148    pub size: u64,
149    /// BLAKE3-256 of the original file bytes.
150    pub h: ByteBuf,
151    /// "raw" | "zip" | "streams"
152    pub mode: String,
153    #[serde(default)]
154    pub chunks: Vec<u32>,
155    #[serde(default, skip_serializing_if = "Option::is_none")]
156    pub mtime: Option<i64>,
157    #[serde(default, skip_serializing_if = "Option::is_none")]
158    pub zip: Option<ZipRecipe>,
159    #[serde(default, skip_serializing_if = "Option::is_none")]
160    pub streams: Option<StreamRecipe>,
161    #[serde(default, skip_serializing_if = "Option::is_none")]
162    pub note: Option<String>,
163    /// Present when this entry is a stored canonical view of another entry.
164    #[serde(default, skip_serializing_if = "Option::is_none")]
165    pub derived: Option<Derived>,
166}
167
168#[derive(Clone, Debug, Serialize, Deserialize)]
169pub struct Manifest {
170    pub tsaur: u16,
171    pub profile: String,
172    pub fidelity: String,
173    pub chunking: ChunkParams,
174    pub solid_block: u32,
175    pub limits: Limits,
176    /// Merkle root over the chunk table (content identity of the archive).
177    pub root: ByteBuf,
178    pub entries: Vec<Entry>,
179    #[serde(default, skip_serializing_if = "Option::is_none")]
180    pub created: Option<u64>,
181    #[serde(default)]
182    pub generator: String,
183    #[serde(default, skip_serializing_if = "Vec::is_empty")]
184    pub refs: Vec<RefArchive>,
185}