cubecl_environment/bundle/
embedded.rs1use alloc::string::String;
33use alloc::vec::Vec;
34
35use super::Bundle;
36use crate::bytes::Bytes;
37use crate::persistence::NamespaceSummary;
38
39pub const MAGIC: &[u8; 8] = b"CUBECLB\x01";
41
42pub const FORMAT_VERSION: u32 = 1;
45
46pub(crate) const ENTRY_SIZE: usize = 20;
48
49pub fn flat_bundle_version(bytes: &[u8]) -> Option<u32> {
55 if bytes.len() < MAGIC.len() + 4 || &bytes[..MAGIC.len()] != MAGIC {
56 return None;
57 }
58
59 read_u32(bytes, MAGIC.len())
60}
61
62#[derive(Debug, Clone, PartialEq, Eq)]
64pub enum EmbeddedBundleError {
65 NotABundle,
67 UnsupportedFormat(u32),
69 Corrupted(&'static str),
71}
72
73impl core::fmt::Display for EmbeddedBundleError {
74 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
75 match self {
76 Self::NotABundle => write!(f, "not a cubecl flat bundle"),
77 Self::UnsupportedFormat(found) => write!(
78 f,
79 "flat bundle format {found} is not {FORMAT_VERSION}, its entries are unreadable"
80 ),
81 Self::Corrupted(what) => write!(f, "corrupted flat bundle: {what}"),
82 }
83 }
84}
85
86impl core::error::Error for EmbeddedBundleError {}
87
88#[derive(Debug)]
108pub struct EmbeddedBundle {
109 bytes: Bytes,
110 metadata: (usize, usize),
112 namespaces: Vec<(usize, usize)>,
115 entries: (usize, usize),
117 data: usize,
119}
120
121impl EmbeddedBundle {
122 pub fn open(bytes: Bytes) -> Result<Self, EmbeddedBundleError> {
128 Self::parse(bytes.shared())
129 }
130
131 pub fn from_static(blob: &'static [u8]) -> Result<Self, EmbeddedBundleError> {
137 #[cfg(feature = "shared-bytes")]
138 let bytes = Bytes::from_shared(
139 bytes::Bytes::from_static(blob),
140 crate::bytes::AllocationProperty::Other,
141 );
142 #[cfg(not(feature = "shared-bytes"))]
143 let bytes = Bytes::from_bytes_vec(blob.to_vec());
144
145 Self::open(bytes)
146 }
147
148 fn parse(bytes: Bytes) -> Result<Self, EmbeddedBundleError> {
149 use EmbeddedBundleError::{Corrupted, NotABundle, UnsupportedFormat};
150
151 if bytes.len() < MAGIC.len() + 8 || &bytes[..MAGIC.len()] != MAGIC {
152 return Err(NotABundle);
153 }
154
155 let mut cursor = MAGIC.len();
156 let take_u32 = |cursor: &mut usize| -> Option<u32> {
157 let value = read_u32(&bytes, *cursor)?;
158 *cursor += 4;
159 Some(value)
160 };
161
162 let format = take_u32(&mut cursor).ok_or(Corrupted("truncated header"))?;
163 if format != FORMAT_VERSION {
164 return Err(UnsupportedFormat(format));
165 }
166
167 let metadata_len = take_u32(&mut cursor).ok_or(Corrupted("truncated header"))? as usize;
168 let metadata_start = cursor;
169 let metadata_end = metadata_start
170 .checked_add(metadata_len)
171 .ok_or(Corrupted("metadata length overflows"))?;
172 if metadata_end > bytes.len() {
173 return Err(Corrupted("metadata runs past the end"));
174 }
175 cursor = metadata_end;
176
177 let namespace_count = take_u32(&mut cursor).ok_or(Corrupted("truncated header"))? as usize;
178 let mut namespaces = Vec::with_capacity(namespace_count.min(bytes.len() / 4));
181 for _ in 0..namespace_count {
182 let len =
183 read_u32(&bytes, cursor).ok_or(Corrupted("truncated namespace table"))? as usize;
184 let start = cursor + 4;
185 cursor = start
186 .checked_add(len)
187 .ok_or(Corrupted("namespace length overflows"))?;
188 if cursor > bytes.len() {
189 return Err(Corrupted("namespace table runs past the end"));
190 }
191 namespaces.push((start, len));
192 }
193
194 let entry_count = read_u32(&bytes, cursor).ok_or(Corrupted("truncated header"))? as usize;
195 cursor += 4;
196 let entries_start = cursor;
197 let index_len = entry_count
198 .checked_mul(ENTRY_SIZE)
199 .ok_or(Corrupted("entry count overflows"))?;
200 let data_start = entries_start
201 .checked_add(index_len)
202 .ok_or(Corrupted("entry index overflows"))?;
203 if data_start > bytes.len() {
204 return Err(Corrupted("entry index runs past the end"));
205 }
206
207 let this = Self {
208 bytes,
209 metadata: (metadata_start, metadata_end),
210 namespaces,
211 entries: (entries_start, entry_count),
212 data: data_start,
213 };
214
215 this.validate_entries()?;
216
217 Ok(this)
218 }
219
220 fn validate_entries(&self) -> Result<(), EmbeddedBundleError> {
223 use EmbeddedBundleError::Corrupted;
224
225 let available = self.bytes.len() - self.data;
226 let namespace_count = self.namespaces.len();
227 let mut previous: Option<Entry> = None;
228
229 for index in 0..self.entries.1 {
230 let entry = self
231 .entry(index)
232 .ok_or(Corrupted("truncated entry index"))?;
233
234 if entry.namespace as usize >= namespace_count {
235 return Err(Corrupted("entry points at an unknown namespace"));
236 }
237 for (offset, len) in [
238 (entry.key_offset, entry.key_len),
239 (entry.value_offset, entry.value_len),
240 ] {
241 let end = (offset as usize)
242 .checked_add(len as usize)
243 .ok_or(Corrupted("entry span overflows"))?;
244 if end > available {
245 return Err(Corrupted("entry span runs past the end"));
246 }
247 }
248
249 if let Some(previous) = &previous {
253 let order = (previous.namespace, self.key_of(previous))
254 .cmp(&(entry.namespace, self.key_of(&entry)));
255 if order != core::cmp::Ordering::Less {
256 return Err(Corrupted("entry index is not sorted by (namespace, key)"));
257 }
258 }
259 previous = Some(entry);
260 }
261
262 for index in 0..namespace_count {
264 self.namespace(index)
265 .ok_or(Corrupted("namespace is not valid UTF-8"))?;
266 }
267
268 Ok(())
269 }
270
271 pub fn metadata(&self) -> &[u8] {
274 &self.bytes[self.metadata.0..self.metadata.1]
275 }
276
277 pub fn manifest(&self) -> Result<super::BundleManifest, super::BundleError> {
284 let manifest = super::BundleManifest::parse(self.metadata())?;
285 manifest.warn_on_version_mismatch();
286
287 Ok(manifest)
288 }
289
290 pub fn len(&self) -> usize {
292 self.entries.1
293 }
294
295 pub fn is_empty(&self) -> bool {
297 self.len() == 0
298 }
299
300 pub fn summary(&self) -> Vec<NamespaceSummary> {
302 let mut summary: Vec<NamespaceSummary> = (0..self.namespaces.len())
303 .filter_map(|index| {
304 Some(NamespaceSummary {
305 namespace: alloc::string::ToString::to_string(self.namespace(index)?),
306 entries: 0,
307 bytes: 0,
308 })
309 })
310 .collect();
311
312 for index in 0..self.entries.1 {
313 let Some(entry) = self.entry(index) else {
314 break;
315 };
316 if let Some(namespace) = summary.get_mut(entry.namespace as usize) {
317 namespace.entries += 1;
318 namespace.bytes += u64::from(entry.key_len) + u64::from(entry.value_len);
319 }
320 }
321
322 summary
323 }
324
325 fn namespace(&self, index: usize) -> Option<&str> {
327 let &(start, len) = self.namespaces.get(index)?;
328 core::str::from_utf8(self.bytes.get(start..start + len)?).ok()
329 }
330
331 fn namespace_id(&self, namespace: &str) -> Option<u32> {
333 let at = lower_bound(self.namespaces.len(), |index| {
334 Some(self.namespace(index)?.cmp(namespace))
335 })?;
336
337 (self.namespace(at)? == namespace).then_some(at as u32)
338 }
339
340 fn entry(&self, index: usize) -> Option<Entry> {
341 let at = self.entries.0 + index * ENTRY_SIZE;
342
343 Some(Entry {
344 namespace: read_u32(&self.bytes, at)?,
345 key_offset: read_u32(&self.bytes, at + 4)?,
346 key_len: read_u32(&self.bytes, at + 8)?,
347 value_offset: read_u32(&self.bytes, at + 12)?,
348 value_len: read_u32(&self.bytes, at + 16)?,
349 })
350 }
351
352 fn span(&self, offset: u32, len: u32) -> core::ops::Range<usize> {
358 let start = self.data + offset as usize;
359 start..start + len as usize
360 }
361
362 fn key_of(&self, entry: &Entry) -> &[u8] {
363 &self.bytes[self.span(entry.key_offset, entry.key_len)]
364 }
365
366 fn value_of(&self, entry: &Entry) -> &[u8] {
367 &self.bytes[self.span(entry.value_offset, entry.value_len)]
368 }
369
370 fn value_window(&self, entry: &Entry) -> Option<Bytes> {
372 let span = self.span(entry.value_offset, entry.value_len);
373 self.bytes
374 .view(span.start, span.end)
375 .inspect_err(|err| log::warn!("Embedded bundle: can't view an entry: {err:?}"))
376 .ok()
377 }
378
379 fn first_of(&self, namespace: u32) -> Option<usize> {
381 let at = lower_bound(self.entries.1, |index| {
382 Some(self.entry(index)?.namespace.cmp(&namespace))
383 })?;
384
385 (self.entry(at)?.namespace == namespace).then_some(at)
386 }
387}
388
389impl Bundle for EmbeddedBundle {
390 fn get(&self, namespace: &str, key: &[u8]) -> Option<Bytes> {
391 let namespace = self.namespace_id(namespace)?;
392
393 let at = lower_bound(self.entries.1, |index| {
396 let entry = self.entry(index)?;
397 Some((entry.namespace, self.key_of(&entry)).cmp(&(namespace, key)))
398 })?;
399
400 let entry = self.entry(at)?;
401 ((entry.namespace, self.key_of(&entry)) == (namespace, key))
402 .then(|| self.value_window(&entry))
403 .flatten()
404 }
405
406 fn scan(&self, namespace: &str, visit: &mut dyn FnMut(&[u8], &[u8])) {
407 let Some(id) = self.namespace_id(namespace) else {
408 return;
409 };
410 let Some(first) = self.first_of(id) else {
411 return;
412 };
413
414 for index in first..self.entries.1 {
415 let Some(entry) = self.entry(index) else {
416 return;
417 };
418 if entry.namespace != id {
419 return;
420 }
421 visit(self.key_of(&entry), self.value_of(&entry));
422 }
423 }
424
425 fn namespaces(&self) -> Vec<String> {
426 (0..self.namespaces.len())
427 .filter_map(|index| Some(alloc::string::ToString::to_string(self.namespace(index)?)))
428 .collect()
429 }
430
431 fn describe(&self) -> String {
432 alloc::format!("embedded bundle ({} entries)", self.entries.1)
433 }
434}
435
436struct Entry {
438 namespace: u32,
439 key_offset: u32,
440 key_len: u32,
441 value_offset: u32,
442 value_len: u32,
443}
444
445fn lower_bound(
453 len: usize,
454 compare: impl Fn(usize) -> Option<core::cmp::Ordering>,
455) -> Option<usize> {
456 let (mut low, mut high) = (0usize, len);
457
458 while low < high {
459 let mid = low + (high - low) / 2;
460 if compare(mid)? == core::cmp::Ordering::Less {
461 low = mid + 1;
462 } else {
463 high = mid;
464 }
465 }
466
467 Some(low)
468}
469
470fn read_u32(bytes: &[u8], at: usize) -> Option<u32> {
471 let raw: [u8; 4] = bytes.get(at..at + 4)?.try_into().ok()?;
472 Some(u32::from_le_bytes(raw))
473}
474
475#[cfg(test)]
476mod tests {
477 use super::*;
478 use alloc::vec;
479
480 fn empty_bundle() -> Bytes {
482 let mut bytes = Vec::new();
483 bytes.extend_from_slice(MAGIC);
484 bytes.extend_from_slice(&FORMAT_VERSION.to_le_bytes());
485 bytes.extend_from_slice(&0u32.to_le_bytes()); bytes.extend_from_slice(&0u32.to_le_bytes()); bytes.extend_from_slice(&0u32.to_le_bytes()); Bytes::from_bytes_vec(bytes)
489 }
490
491 #[test]
492 fn an_empty_bundle_reads_as_empty() {
493 let bundle = EmbeddedBundle::open(empty_bundle()).unwrap();
494
495 assert!(bundle.is_empty());
496 assert_eq!(bundle.get("anything", b"key"), None);
497 bundle.scan("anything", &mut |_, _| panic!("no entries to visit"));
498 }
499
500 #[test]
501 fn foreign_bytes_are_rejected() {
502 assert_eq!(
503 EmbeddedBundle::open(Bytes::from_bytes_vec(b"definitely not a bundle".to_vec()))
504 .unwrap_err(),
505 EmbeddedBundleError::NotABundle
506 );
507 assert_eq!(
508 EmbeddedBundle::open(Bytes::from_bytes_vec(vec![])).unwrap_err(),
509 EmbeddedBundleError::NotABundle
510 );
511 }
512
513 #[test]
514 fn another_format_version_is_rejected() {
515 let mut bytes = empty_bundle().to_vec();
516 bytes[MAGIC.len()..MAGIC.len() + 4].copy_from_slice(&99u32.to_le_bytes());
517 let bytes = Bytes::from_bytes_vec(bytes);
518
519 assert_eq!(
520 EmbeddedBundle::open(bytes).unwrap_err(),
521 EmbeddedBundleError::UnsupportedFormat(99)
522 );
523 }
524
525 #[test]
527 fn truncated_bytes_are_rejected() {
528 let full = empty_bundle();
529
530 for len in MAGIC.len()..full.len() {
531 let result = EmbeddedBundle::open(Bytes::from_bytes_vec(full[..len].to_vec()));
532 assert!(result.is_err(), "a {len}-byte prefix must not open");
533 }
534 }
535
536 #[test]
539 fn out_of_range_spans_are_rejected() {
540 let mut bytes = Vec::new();
541 bytes.extend_from_slice(MAGIC);
542 bytes.extend_from_slice(&FORMAT_VERSION.to_le_bytes());
543 bytes.extend_from_slice(&0u32.to_le_bytes()); bytes.extend_from_slice(&1u32.to_le_bytes()); bytes.extend_from_slice(&2u32.to_le_bytes());
546 bytes.extend_from_slice(b"ns");
547 bytes.extend_from_slice(&1u32.to_le_bytes()); bytes.extend_from_slice(&0u32.to_le_bytes()); bytes.extend_from_slice(&0u32.to_le_bytes()); bytes.extend_from_slice(&99u32.to_le_bytes()); bytes.extend_from_slice(&0u32.to_le_bytes()); bytes.extend_from_slice(&0u32.to_le_bytes()); assert!(matches!(
555 EmbeddedBundle::open(Bytes::from_bytes_vec(bytes)).unwrap_err(),
556 EmbeddedBundleError::Corrupted(_)
557 ));
558 }
559
560 #[test]
563 fn an_unsorted_entry_index_is_rejected() {
564 let bundle = |keys: [&[u8]; 2]| {
565 let mut data = Vec::new();
566 let mut index = Vec::new();
567 for key in keys {
568 index.extend_from_slice(&0u32.to_le_bytes()); index.extend_from_slice(&(data.len() as u32).to_le_bytes());
570 index.extend_from_slice(&(key.len() as u32).to_le_bytes());
571 data.extend_from_slice(key);
572 index.extend_from_slice(&(data.len() as u32).to_le_bytes());
573 index.extend_from_slice(&0u32.to_le_bytes()); }
575
576 let mut bytes = Vec::new();
577 bytes.extend_from_slice(MAGIC);
578 bytes.extend_from_slice(&FORMAT_VERSION.to_le_bytes());
579 bytes.extend_from_slice(&0u32.to_le_bytes()); bytes.extend_from_slice(&1u32.to_le_bytes()); bytes.extend_from_slice(&2u32.to_le_bytes());
582 bytes.extend_from_slice(b"ns");
583 bytes.extend_from_slice(&2u32.to_le_bytes()); bytes.extend_from_slice(&index);
585 bytes.extend_from_slice(&data);
586
587 EmbeddedBundle::open(Bytes::from_bytes_vec(bytes))
588 };
589
590 assert!(bundle([b"a", b"b"]).is_ok());
591 assert!(matches!(
592 bundle([b"b", b"a"]).unwrap_err(),
593 EmbeddedBundleError::Corrupted(_)
594 ));
595 assert!(matches!(
596 bundle([b"a", b"a"]).unwrap_err(),
597 EmbeddedBundleError::Corrupted(_)
598 ));
599 }
600
601 #[test]
602 fn an_unknown_namespace_id_is_rejected() {
603 let mut bytes = Vec::new();
604 bytes.extend_from_slice(MAGIC);
605 bytes.extend_from_slice(&FORMAT_VERSION.to_le_bytes());
606 bytes.extend_from_slice(&0u32.to_le_bytes());
607 bytes.extend_from_slice(&0u32.to_le_bytes()); bytes.extend_from_slice(&1u32.to_le_bytes()); bytes.extend_from_slice(&7u32.to_le_bytes()); bytes.extend_from_slice(&0u32.to_le_bytes());
611 bytes.extend_from_slice(&0u32.to_le_bytes());
612 bytes.extend_from_slice(&0u32.to_le_bytes());
613 bytes.extend_from_slice(&0u32.to_le_bytes());
614
615 assert!(matches!(
616 EmbeddedBundle::open(Bytes::from_bytes_vec(bytes)).unwrap_err(),
617 EmbeddedBundleError::Corrupted(_)
618 ));
619 }
620}