Skip to main content

iris_format/
build.rs

1//! Writing a container.
2//!
3//! The writer here builds the whole file in memory. That is the right shape for the sizes this
4//! project starts at and the wrong shape for the sizes it ends at, and it is deliberately the
5//! simple one first: the format puts its directory at the end precisely so that a streaming writer
6//! can be added later without changing a byte of the layout.
7
8use iris_abi::{CapabilitySet, Writer, wire::align_up};
9
10use crate::digest::Digest;
11use crate::error::Result;
12use crate::layout::{
13    DecoderLocation, FORMAT_MAJOR, FORMAT_MINOR, HEADER_SIZE, MAGIC, SchemaEncoding, SectionKind,
14    TRAILER_SIZE, tag,
15};
16use crate::meta::{Dataset, DecoderRef, Schema, Section};
17
18/// A decoder reference before it has been written, holding its own name.
19#[derive(Clone, Debug)]
20struct PendingDecoder {
21    abi_major: u16,
22    abi_minor: u16,
23    location: DecoderLocation,
24    digest: Digest,
25    required: CapabilitySet,
26    name: String,
27}
28
29/// Builds a container.
30///
31/// ```
32/// use iris_format::{Builder, Container, SectionKind};
33///
34/// let mut builder = Builder::new("readings", 3);
35/// let data = builder.section(SectionKind::Data, b"three rows go here".to_vec());
36/// let bytes = builder.build()?;
37///
38/// let container = Container::parse(&bytes)?;
39/// container.verify()?;
40/// assert_eq!(container.dataset().rows, 3);
41/// assert_eq!(
42///     container.section_bytes(container.section(data).unwrap()),
43///     b"three rows go here"
44/// );
45/// # Ok::<(), iris_format::Error>(())
46/// ```
47#[derive(Clone, Debug)]
48pub struct Builder {
49    dataset: Dataset,
50    schema: Option<(SchemaEncoding, Vec<u8>)>,
51    decoder: Option<PendingDecoder>,
52    sections: Vec<(u32, SectionKind, Vec<u8>)>,
53    next_id: u32,
54}
55
56impl Builder {
57    /// Starts a container for a dataset with this name and this many rows.
58    #[must_use]
59    pub fn new(name: impl Into<String>, rows: u64) -> Self {
60        Self {
61            dataset: Dataset {
62                rows,
63                name: name.into(),
64            },
65            schema: None,
66            decoder: None,
67            sections: Vec::new(),
68            next_id: 0,
69        }
70    }
71
72    /// Sets the schema.
73    pub fn schema(&mut self, encoding: SchemaEncoding, bytes: impl Into<Vec<u8>>) -> &mut Self {
74        self.schema = Some((encoding, bytes.into()));
75        self
76    }
77
78    /// Adds a section and returns the id the rest of the container refers to it by.
79    pub fn section(&mut self, kind: SectionKind, bytes: impl Into<Vec<u8>>) -> u32 {
80        let id = self.next_id;
81        self.next_id += 1;
82        self.sections.push((id, kind, bytes.into()));
83        id
84    }
85
86    /// Puts a decoder module in the container and points the dataset at it.
87    ///
88    /// Returns the id of the section the module went into, which is worth having for a tool that
89    /// wants to print the layout.
90    pub fn embed_decoder(
91        &mut self,
92        name: impl Into<String>,
93        abi: (u16, u16),
94        required: CapabilitySet,
95        module: impl Into<Vec<u8>>,
96    ) -> u32 {
97        let module = module.into();
98        let digest = Digest::of(&module);
99        let section = self.section(SectionKind::Decoder, module);
100        self.decoder = Some(PendingDecoder {
101            abi_major: abi.0,
102            abi_minor: abi.1,
103            location: DecoderLocation::Embedded { section },
104            digest,
105            required,
106            name: name.into(),
107        });
108        section
109    }
110
111    /// Points the dataset at a decoder that lives somewhere else, named by its digest.
112    pub fn external_decoder(
113        &mut self,
114        name: impl Into<String>,
115        abi: (u16, u16),
116        required: CapabilitySet,
117        digest: Digest,
118    ) -> &mut Self {
119        self.decoder = Some(PendingDecoder {
120            abi_major: abi.0,
121            abi_minor: abi.1,
122            location: DecoderLocation::External,
123            digest,
124            required,
125            name: name.into(),
126        });
127        self
128    }
129
130    /// Lays the container out and returns the bytes.
131    ///
132    /// # Errors
133    ///
134    /// Returns [`crate::Error::Footer`] if a name or a schema is longer than the wire format can
135    /// describe, which takes four gigabytes of it.
136    pub fn build(&self) -> Result<Vec<u8>> {
137        let mut out = Vec::new();
138        out.extend_from_slice(&MAGIC);
139        out.extend_from_slice(&FORMAT_MAJOR.to_le_bytes());
140        out.extend_from_slice(&FORMAT_MINOR.to_le_bytes());
141        out.extend_from_slice(&0u32.to_le_bytes());
142        debug_assert_eq!(out.len(), HEADER_SIZE);
143
144        let mut placed = Vec::with_capacity(self.sections.len());
145        for (id, kind, bytes) in &self.sections {
146            pad(&mut out);
147            placed.push(Section {
148                id: *id,
149                kind: *kind,
150                offset: out.len() as u64,
151                len: bytes.len() as u64,
152                digest: Digest::of(bytes),
153            });
154            out.extend_from_slice(bytes);
155        }
156
157        pad(&mut out);
158        let footer_offset = out.len() as u64;
159        let footer = self.encode_footer(&placed)?;
160        out.extend_from_slice(&footer);
161
162        let mut hasher = blake3::Hasher::new();
163        hasher.update(&out[..HEADER_SIZE]);
164        hasher.update(&footer);
165        let root = Digest(*hasher.finalize().as_bytes());
166
167        out.extend_from_slice(&footer_offset.to_le_bytes());
168        // The footer is metadata about a dataset, not the dataset. Four gigabytes of it would mean
169        // something has gone wrong that a wider length field would not fix.
170        let footer_len =
171            u32::try_from(footer.len()).map_err(|_| iris_abi::Error::LengthOverflow)?;
172        out.extend_from_slice(&footer_len.to_le_bytes());
173        out.extend_from_slice(&0u32.to_le_bytes());
174        out.extend_from_slice(root.as_bytes());
175        out.extend_from_slice(&MAGIC);
176        debug_assert_eq!(
177            out.len() as u64,
178            footer_offset + footer.len() as u64 + TRAILER_SIZE as u64
179        );
180
181        Ok(out)
182    }
183
184    /// Encodes the footer, growing the buffer until it fits.
185    ///
186    /// The `iris-abi` writer never grows its own buffer, which is what makes it usable from a guest
187    /// with no allocator. On the host side the cost of guessing wrong is one memcpy of a footer, so
188    /// guessing and retrying is simpler than computing the exact size twice and keeping the two
189    /// computations in agreement.
190    fn encode_footer(&self, sections: &[Section]) -> Result<Vec<u8>> {
191        let mut capacity = 1024 + sections.len() * 128;
192        loop {
193            let mut buf = vec![0u8; capacity];
194            match self.write_footer(&mut Writer::new(&mut buf), sections) {
195                Ok(written) => {
196                    buf.truncate(written);
197                    return Ok(buf);
198                }
199                Err(iris_abi::Error::BufferFull { .. }) => capacity *= 2,
200                Err(other) => return Err(other.into()),
201            }
202        }
203    }
204
205    fn write_footer(
206        &self,
207        w: &mut Writer<'_>,
208        sections: &[Section],
209    ) -> core::result::Result<usize, iris_abi::Error> {
210        w.record(tag::DATASET, Dataset::VERSION, |w| self.dataset.encode(w))?;
211        if let Some((encoding, bytes)) = &self.schema {
212            let schema = Schema {
213                encoding: *encoding,
214                bytes,
215            };
216            w.record(tag::SCHEMA, Schema::VERSION, |w| schema.encode(w))?;
217        }
218        if let Some(decoder) = &self.decoder {
219            let reference = DecoderRef {
220                abi_major: decoder.abi_major,
221                abi_minor: decoder.abi_minor,
222                location: decoder.location,
223                digest: decoder.digest,
224                required: decoder.required,
225                name: &decoder.name,
226            };
227            w.record(tag::DECODER, DecoderRef::VERSION, |w| reference.encode(w))?;
228        }
229        for section in sections {
230            w.record(tag::SECTION, Section::VERSION, |w| section.encode(w))?;
231        }
232        Ok(w.position())
233    }
234}
235
236/// Pads out to the next eight byte boundary, so that a section starts somewhere a decoder can point
237/// a wider load at.
238fn pad(out: &mut Vec<u8>) {
239    out.resize(align_up(out.len()), 0);
240}