Skip to main content

iris_format/
meta.rs

1//! The records that make up the footer.
2//!
3//! Each one encodes and decodes itself against the `iris-abi` reader and writer, so the framing
4//! rules are the ones already written down in `docs/ABI.md`. In particular a record may grow a
5//! field at the end without a version bump, and a reader that does not know about the field steps
6//! over it, which is why every `decode` here reads what it knows and stops.
7
8use iris_abi::{CapabilitySet, Reader, Result as AbiResult, Writer};
9
10use crate::digest::Digest;
11use crate::layout::{DIGEST_SIZE, DecoderLocation, SchemaEncoding, SectionKind};
12
13/// What the dataset is and how big it is.
14#[derive(Clone, PartialEq, Eq, Debug, serde::Serialize, serde::Deserialize)]
15pub struct Dataset {
16    /// How many rows there are in total.
17    ///
18    /// A `u64` because a `u32` runs out at four billion rows, which is a size people already have.
19    pub rows: u64,
20    /// A human readable name, for error messages and for `iris describe`. Not an identifier and
21    /// nothing looks it up.
22    pub name: String,
23}
24
25impl Dataset {
26    /// The record version this build writes.
27    pub const VERSION: u16 = 1;
28
29    /// Writes this record's payload.
30    ///
31    /// # Errors
32    ///
33    /// Returns whatever the writer returns, which in practice means the buffer was too small.
34    pub fn encode(&self, w: &mut Writer<'_>) -> AbiResult<()> {
35        w.u64(self.rows)?;
36        w.var_str(&self.name)
37    }
38
39    /// Reads this record's payload.
40    ///
41    /// # Errors
42    ///
43    /// Returns [`iris_abi::Error`] if the payload is truncated or the name is not UTF-8.
44    pub fn decode(p: &mut Reader<'_>) -> AbiResult<Self> {
45        Ok(Self {
46            rows: p.u64()?,
47            name: p.var_str()?.to_owned(),
48        })
49    }
50}
51
52/// The Arrow schema, carried as bytes this crate does not look inside.
53#[derive(Clone, PartialEq, Eq, Debug)]
54pub struct Schema<'a> {
55    /// How the bytes are encoded.
56    pub encoding: SchemaEncoding,
57    /// The bytes.
58    pub bytes: &'a [u8],
59}
60
61impl<'a> Schema<'a> {
62    /// The record version this build writes.
63    pub const VERSION: u16 = 1;
64
65    /// Writes this record's payload.
66    ///
67    /// # Errors
68    ///
69    /// Returns whatever the writer returns.
70    pub fn encode(&self, w: &mut Writer<'_>) -> AbiResult<()> {
71        w.u32(self.encoding.code())?;
72        w.var_bytes(self.bytes)
73    }
74
75    /// Reads this record's payload.
76    ///
77    /// # Errors
78    ///
79    /// Returns [`iris_abi::Error`] if the payload is truncated.
80    pub fn decode(p: &mut Reader<'a>) -> AbiResult<Self> {
81        Ok(Self {
82            encoding: SchemaEncoding::from_code(p.u32()?),
83            bytes: p.var_bytes()?,
84        })
85    }
86}
87
88/// Which decoder reads this dataset.
89#[derive(Clone, PartialEq, Eq, Debug)]
90pub struct DecoderRef<'a> {
91    /// The ABI major version the decoder was built against.
92    pub abi_major: u16,
93    /// The ABI minor version the decoder was built against.
94    pub abi_minor: u16,
95    /// Where the module is.
96    pub location: DecoderLocation,
97    /// The digest of the module.
98    ///
99    /// This is the identity of the decoder. A host that already trusts a native implementation of
100    /// this exact module substitutes it here and skips the sandbox, and a host that does not runs
101    /// the bytes it hashed.
102    pub digest: Digest,
103    /// What the decoder needs the host to be able to do.
104    ///
105    /// Carrying this in the container as well as in the handshake means a host can refuse a dataset
106    /// before it has loaded a single instruction of it, which is a much better error than one that
107    /// arrives halfway through a query.
108    pub required: CapabilitySet,
109    /// A human readable name, for error messages.
110    pub name: &'a str,
111}
112
113impl<'a> DecoderRef<'a> {
114    /// The record version this build writes.
115    pub const VERSION: u16 = 1;
116
117    /// Writes this record's payload.
118    ///
119    /// # Errors
120    ///
121    /// Returns whatever the writer returns.
122    pub fn encode(&self, w: &mut Writer<'_>) -> AbiResult<()> {
123        w.u16(self.abi_major)?;
124        w.u16(self.abi_minor)?;
125        let (kind, section) = match self.location {
126            DecoderLocation::Embedded { section } => (0, section),
127            DecoderLocation::External => (1, 0),
128        };
129        w.u32(kind)?;
130        w.u32(section)?;
131        w.u32(0)?;
132        w.raw(self.digest.as_bytes())?;
133        w.var_bytes(self.required.as_bytes())?;
134        w.var_str(self.name)
135    }
136
137    /// Reads this record's payload.
138    ///
139    /// An unknown location kind decodes as [`DecoderLocation::External`], because the one thing a
140    /// reader must not do with a decoder it cannot place is run something else.
141    ///
142    /// # Errors
143    ///
144    /// Returns [`iris_abi::Error`] if the payload is truncated or the name is not UTF-8.
145    pub fn decode(p: &mut Reader<'a>) -> AbiResult<Self> {
146        let abi_major = p.u16()?;
147        let abi_minor = p.u16()?;
148        let kind = p.u32()?;
149        let section = p.u32()?;
150        let _reserved = p.u32()?;
151        let raw = p.bytes(DIGEST_SIZE)?;
152        let mut digest = [0u8; DIGEST_SIZE];
153        digest.copy_from_slice(raw);
154        Ok(Self {
155            abi_major,
156            abi_minor,
157            location: if kind == 0 {
158                DecoderLocation::Embedded { section }
159            } else {
160                DecoderLocation::External
161            },
162            digest: Digest(digest),
163            required: p.capability_set()?,
164            name: p.var_str()?,
165        })
166    }
167}
168
169/// One run of bytes in the payload area.
170#[derive(Clone, Copy, PartialEq, Eq, Debug, serde::Serialize, serde::Deserialize)]
171pub struct Section {
172    /// What the rest of the container refers to this section by.
173    pub id: u32,
174    /// What it holds.
175    pub kind: SectionKind,
176    /// Where it starts, counted from the start of the file.
177    pub offset: u64,
178    /// How many bytes long it is.
179    pub len: u64,
180    /// The digest of those bytes.
181    pub digest: Digest,
182}
183
184impl Section {
185    /// The record version this build writes.
186    pub const VERSION: u16 = 1;
187
188    /// Where the section ends, or `None` if the arithmetic overflows.
189    ///
190    /// The `Option` is the point. These two numbers came out of a file somebody else wrote, and the
191    /// first thing a hostile one does is pick an offset and a length that add up to something
192    /// small.
193    #[must_use]
194    pub const fn end(&self) -> Option<u64> {
195        self.offset.checked_add(self.len)
196    }
197
198    /// Writes this record's payload.
199    ///
200    /// # Errors
201    ///
202    /// Returns whatever the writer returns.
203    pub fn encode(&self, w: &mut Writer<'_>) -> AbiResult<()> {
204        w.u32(self.id)?;
205        w.u32(self.kind.code())?;
206        w.u64(self.offset)?;
207        w.u64(self.len)?;
208        w.raw(self.digest.as_bytes())
209    }
210
211    /// Reads this record's payload.
212    ///
213    /// # Errors
214    ///
215    /// Returns [`iris_abi::Error`] if the payload is truncated.
216    pub fn decode(p: &mut Reader<'_>) -> AbiResult<Self> {
217        let id = p.u32()?;
218        let kind = SectionKind::from_code(p.u32()?);
219        let offset = p.u64()?;
220        let len = p.u64()?;
221        let raw = p.bytes(DIGEST_SIZE)?;
222        let mut digest = [0u8; DIGEST_SIZE];
223        digest.copy_from_slice(raw);
224        Ok(Self {
225            id,
226            kind,
227            offset,
228            len,
229            digest: Digest(digest),
230        })
231    }
232}