1use std::borrow::Cow;
4
5use serde::Serialize;
6use usize_cast::IntoUsize as _;
7
8use super::model::{BlobInfo, DecodeHint, DumpTree, Region};
9use crate::codecs::fsst::decode_fsst_bytes;
10use crate::decoder::strings::rebuild_dictionary;
11use crate::decoder::{DictLayout, DictionaryType, LengthType, RawFsstData, RawStream, StreamType};
12use crate::{Decoder, GeometryType, MltError, MltResult};
13
14#[derive(Debug, Clone, PartialEq, Serialize)]
19#[serde(
20 tag = "kind",
21 rename_all = "camelCase",
22 rename_all_fields = "camelCase"
23)]
24pub enum DecodedBlob {
25 Numbers {
27 values: Vec<f64>,
28 truncated_from: Option<u32>,
30 },
31 #[serde(rename = "bigints")]
33 BigInts {
34 values: Vec<i128>,
35 truncated_from: Option<u32>,
36 },
37 Bools {
38 values: Vec<bool>,
39 truncated_from: Option<u32>,
40 },
41 Enum {
43 values: Vec<u32>,
44 names: Vec<Cow<'static, str>>,
45 truncated_from: Option<u32>,
46 },
47 Strings {
49 values: Vec<String>,
50 truncated_from: Option<u32>,
51 },
52 Text { value: String },
54 Binary { len: u32 },
56 Error { message: String },
58}
59
60pub fn decode_blob(
66 info: BlobInfo,
67 data: &[u8],
68 max_values: usize,
69 dec: &mut Decoder,
70) -> DecodedBlob {
71 dec.reset_budget();
73 let meta = info.meta;
74 match info.hint {
75 DecodeHint::Presence => match RawStream::new(meta, data).decode_bitvec(dec) {
76 Ok(bits) => bools(bits.iter().by_vals().collect(), max_values),
77 Err(e) => error(&e),
78 },
79 #[cfg(feature = "unstable-v2")]
80 DecodeHint::PresenceCoded(coding) => {
81 use crate::codecs::presence_coding::{Unmetered, read};
82 match read(data, meta.num_values, coding, &mut Unmetered) {
83 Ok((_, bits)) => bools(bits.iter().by_vals().collect(), max_values),
84 Err(e) => DecodedBlob::Error {
85 message: e.to_string(),
86 },
87 }
88 }
89 #[cfg(feature = "unstable-v2")]
90 DecodeHint::PackedBits => {
91 let n = meta.num_values.into_usize();
92 let available = data.len() * 8;
93 if available < n {
94 return DecodedBlob::Error {
95 message: format!("needs {n} bits, got {available}"),
96 };
97 }
98 bools(
99 (0..n).map(|i| data[i / 8] >> (i % 8) & 1 == 1).collect(),
100 max_values,
101 )
102 }
103 #[cfg(feature = "unstable-v2")]
104 DecodeHint::Rans => DecodedBlob::Binary {
105 len: count(data.len()),
106 },
107 DecodeHint::Bool => match RawStream::new(meta, data).decode_bools(dec) {
108 Ok(v) => bools(v, max_values),
109 Err(e) => error(&e),
110 },
111 DecodeHint::GeometryType => geometry_types(
112 RawStream::new(meta, data).decode_ints::<u32>(dec),
113 max_values,
114 ),
115 DecodeHint::I32 => numbers(
116 RawStream::new(meta, data).decode_ints::<i32>(dec),
117 max_values,
118 ),
119 DecodeHint::U32 => numbers(
120 RawStream::new(meta, data).decode_ints::<u32>(dec),
121 max_values,
122 ),
123 DecodeHint::F32 => numbers(
124 RawStream::new(meta, data).decode_floats::<f32>(dec),
125 max_values,
126 ),
127 DecodeHint::F64 => numbers(
128 RawStream::new(meta, data).decode_floats::<f64>(dec),
129 max_values,
130 ),
131 DecodeHint::I64 => bigints(
132 RawStream::new(meta, data).decode_ints::<i64>(dec),
133 max_values,
134 ),
135 DecodeHint::U64 => bigints(
136 RawStream::new(meta, data).decode_ints::<u64>(dec),
137 max_values,
138 ),
139 #[cfg(feature = "unstable-v2")]
140 DecodeHint::Alp(params) => bigints(
141 RawStream::new(meta, data).decode_alp_codes(params, dec),
142 max_values,
143 ),
144 DecodeHint::Bytes => match std::str::from_utf8(data) {
145 Ok(s) => DecodedBlob::Text {
146 value: s.to_string(),
147 },
148 Err(_) => DecodedBlob::Binary {
149 len: count(data.len()),
150 },
151 },
152 }
153}
154
155pub fn decode_region(
160 tree: &DumpTree,
161 buf: &[u8],
162 index: usize,
163 max_values: usize,
164 dec: &mut Decoder,
165) -> DecodedBlob {
166 let Some(region) = tree.regions.get(index) else {
167 return DecodedBlob::Error {
168 message: format!("the tree has no region {index}"),
169 };
170 };
171 let Some(info) = region.blob else {
172 return DecodedBlob::Error {
173 message: format!("region {index} carries no stream metadata"),
174 };
175 };
176 if let Some(streams) = fsst_streams(&tree.regions, index) {
177 dec.reset_budget();
178 let layout = corpus_layout(&tree.regions, buf, index);
179 return fsst_strings(streams, layout, buf, max_values, dec).unwrap_or_else(|e| error(&e));
180 }
181 match buf.get(region.offset..region.offset + region.len) {
182 Some(bytes) => decode_blob(info, bytes, max_values, dec),
183 None => DecodedBlob::Error {
184 message: format!("region {index} lies outside the tile buffer"),
185 },
186 }
187}
188
189fn fsst_streams(regions: &[Region], at: usize) -> Option<[&Region; 4]> {
194 let corpus = ®ions[at];
195 if !matches!(
196 corpus.blob?.meta.stream_type,
197 StreamType::Data(DictionaryType::Single | DictionaryType::Shared)
198 ) {
199 return None;
200 }
201 let column = parent(regions, parent(regions, at)?)?;
203 let depth = regions[column].depth;
204 let after = ®ions[column + 1..];
205 let column_streams = &after[..after
206 .iter()
207 .position(|r| r.depth <= depth)
208 .unwrap_or(after.len())];
209 let only = |want: StreamType| {
210 let mut hits = column_streams
211 .iter()
212 .filter(|r| r.blob.is_some_and(|b| b.meta.stream_type == want));
213 let first = hits.next()?;
214 hits.next().is_none().then_some(first)
215 };
216 Some([
217 only(StreamType::Length(LengthType::Symbol))?,
218 only(StreamType::Data(DictionaryType::Fsst))?,
219 only(StreamType::Length(LengthType::Dictionary))?,
220 corpus,
221 ])
222}
223
224fn parent(regions: &[Region], at: usize) -> Option<usize> {
226 regions[..at]
227 .iter()
228 .rposition(|r| r.depth < regions[at].depth)
229}
230
231#[cfg(feature = "unstable-v2")]
235fn corpus_layout(regions: &[Region], buf: &[u8], at: usize) -> DictLayout {
236 let is_v2 = regions[..at]
237 .iter()
238 .rposition(|r| r.depth == 0)
239 .and_then(|layer| {
240 regions[layer + 1..at]
241 .iter()
242 .find(|r| r.depth == 1 && r.label == "tag")
243 })
244 .and_then(|tag| buf.get(tag.offset))
245 == Some(&2);
246 let enc_byte = parent(regions, at).and_then(|stream| buf.get(regions[stream].offset));
247 match enc_byte {
248 Some(&b) if is_v2 => DictLayout::from_bits(b).unwrap_or(DictLayout::Plain),
249 _ => DictLayout::Plain,
250 }
251}
252
253#[cfg(not(feature = "unstable-v2"))]
254fn corpus_layout(_: &[Region], _: &[u8], _: usize) -> DictLayout {
255 DictLayout::Plain
256}
257
258fn fsst_strings(
259 [symbol_lengths, symbol_table, lengths, corpus]: [&Region; 4],
260 layout: DictLayout,
261 buf: &[u8],
262 max_values: usize,
263 dec: &mut Decoder,
264) -> MltResult<DecodedBlob> {
265 let stream = |r: &Region| -> MltResult<RawStream<'_>> {
266 let meta = r
267 .blob
268 .ok_or(MltError::MalformedFsst("a stream without metadata"))?
269 .meta;
270 let bytes = buf
271 .get(r.offset..r.offset + r.len)
272 .ok_or(MltError::MalformedFsst("a stream outside the tile"))?;
273 Ok(RawStream::new(meta, bytes))
274 };
275 let raw = RawFsstData::new(
276 stream(symbol_lengths)?,
277 stream(symbol_table)?,
278 stream(lengths)?,
279 stream(corpus)?,
280 )?;
281 let (bytes, lengths) = decode_fsst_bytes(raw, dec)?;
282 let (text, lengths) = rebuild_dictionary(layout, &lengths, &bytes)?;
283 if let Cow::Owned(entries) = &text {
284 dec.consume_items::<u8>(entries.len())?;
285 }
286 let (kept, truncated_from) = cap(lengths.into_owned(), max_values);
287
288 let mut rest = &*text;
289 let mut values = dec.alloc(kept.len())?;
290 for len in kept {
291 let (string, tail) = rest
292 .split_at_checked(len.into_usize())
293 .ok_or(MltError::MalformedFsst("string lengths overrun the corpus"))?;
294 dec.consume_items::<u8>(string.len())?;
295 values.push(string.to_owned());
296 rest = tail;
297 }
298 Ok(DecodedBlob::Strings {
299 values,
300 truncated_from,
301 })
302}
303
304fn numbers<T: Into<f64>>(res: MltResult<Vec<T>>, max_values: usize) -> DecodedBlob {
305 match res {
306 Ok(v) => {
307 let (v, truncated_from) = cap(v, max_values);
308 DecodedBlob::Numbers {
309 values: v.into_iter().map(Into::into).collect(),
310 truncated_from,
311 }
312 }
313 Err(e) => error(&e),
314 }
315}
316
317fn geometry_types(res: MltResult<Vec<u32>>, max_values: usize) -> DecodedBlob {
318 match res {
319 Ok(v) => {
320 let (values, truncated_from) = cap(v, max_values);
321 let names = values
322 .iter()
323 .map(|&v| {
324 u8::try_from(v)
325 .ok()
326 .and_then(|b| GeometryType::try_from(b).ok())
327 .map_or_else(
328 || format!("unknown({v})").into(),
329 |t| <&str>::from(t).into(),
330 )
331 })
332 .collect();
333 DecodedBlob::Enum {
334 values,
335 names,
336 truncated_from,
337 }
338 }
339 Err(e) => error(&e),
340 }
341}
342
343fn bigints<T: Into<i128>>(res: MltResult<Vec<T>>, max_values: usize) -> DecodedBlob {
344 match res {
345 Ok(v) => {
346 let (v, truncated_from) = cap(v, max_values);
347 DecodedBlob::BigInts {
348 values: v.into_iter().map(Into::into).collect(),
349 truncated_from,
350 }
351 }
352 Err(e) => error(&e),
353 }
354}
355
356fn bools(v: Vec<bool>, max_values: usize) -> DecodedBlob {
357 let (values, truncated_from) = cap(v, max_values);
358 DecodedBlob::Bools {
359 values,
360 truncated_from,
361 }
362}
363
364fn error(e: &MltError) -> DecodedBlob {
365 DecodedBlob::Error {
366 message: e.to_string(),
367 }
368}
369
370fn cap<T>(mut values: Vec<T>, max_values: usize) -> (Vec<T>, Option<u32>) {
372 let total = values.len();
373 if max_values == 0 || total <= max_values {
374 return (values, None);
375 }
376 values.truncate(max_values);
377 (values, Some(count(total)))
378}
379
380fn count(n: usize) -> u32 {
381 u32::try_from(n).unwrap_or(u32::MAX)
382}