Skip to main content

tycho_types/boc/
de.rs

1use std::ops::Deref;
2
3use smallvec::SmallVec;
4
5use super::BocTag;
6use crate::cell::{Cell, CellContext, CellDescriptor, CellParts, LevelMask, MAX_REF_COUNT};
7use crate::util::{ArrayVec, read_be_u32_fast, read_be_u64_fast, unlikely};
8
9/// BOC deserialization options.
10#[derive(Debug, Default, Clone)]
11pub struct Options {
12    /// The minimum allowed root count.
13    pub min_roots: Option<usize>,
14    /// The maximum allowed root count.
15    pub max_roots: Option<usize>,
16}
17
18impl Options {
19    /// Constructs decoder options to expect exactly the specified number of roots.
20    pub const fn exact(number: usize) -> Self {
21        Self {
22            min_roots: Some(number),
23            max_roots: Some(number),
24        }
25    }
26}
27
28/// Parsed BOC header.
29pub struct BocHeader<'a> {
30    ref_size: usize,
31    cells: SmallVec<[&'a [u8]; CELLS_ON_STACK]>,
32    roots: SmallVec<[u32; ROOTS_ON_STACK]>,
33}
34
35impl<'a> BocHeader<'a> {
36    /// Decodes boc info from the specified bytes.
37    #[allow(clippy::needless_late_init)]
38    pub fn decode(data: &'a [u8], options: &Options) -> Result<Self, Error> {
39        let mut reader = BocReader::new(data.len());
40
41        // 4 bytes - tag
42        // 1 byte - flags
43        // 1 byte - offset size
44        if unlikely(!reader.require(6)) {
45            return Err(Error::UnexpectedEof);
46        }
47        debug_assert!(data.len() >= 6);
48
49        // SAFETY: we have already requested more than 6 bytes
50        let [flags, offset_size] = unsafe { *(data.as_ptr().add(4) as *const [u8; 2]) };
51
52        let has_index;
53        let has_crc;
54        let has_cache_bits;
55        let ref_size;
56        let supports_multiple_roots;
57
58        // SAFETY: we have already requested more than 4 bytes
59        let boc_tag = unsafe { reader.read_boc_tag(data) };
60        match boc_tag {
61            Some(BocTag::Indexed) => {
62                has_index = true;
63                has_crc = false;
64                has_cache_bits = false;
65                ref_size = flags as usize;
66                supports_multiple_roots = false;
67            }
68            Some(BocTag::IndexedCrc32) => {
69                has_index = true;
70                has_crc = true;
71                has_cache_bits = false;
72                ref_size = flags as usize;
73                supports_multiple_roots = false;
74            }
75            Some(BocTag::Generic) => {
76                has_index = flags & 0b1000_0000 != 0;
77                has_crc = flags & 0b0100_0000 != 0;
78                has_cache_bits = flags & 0b0010_0000 != 0;
79                ref_size = (flags & 0b0000_0111) as usize;
80                supports_multiple_roots = true;
81            }
82            None => return Err(Error::UnknownBocTag),
83        }
84
85        if unlikely(has_cache_bits && !has_index) {
86            return Err(Error::InvalidHeader);
87        }
88        if unlikely(ref_size == 0 || ref_size > std::mem::size_of::<u32>()) {
89            return Err(Error::InvalidRefSize);
90        }
91        debug_assert!((1..=4).contains(&ref_size));
92
93        let offset_size = offset_size as usize;
94        if unlikely(offset_size == 0 || offset_size > std::mem::size_of::<usize>()) {
95            return Err(Error::InvalidOffsetSize);
96        }
97        debug_assert!((1..=8).contains(&offset_size));
98
99        reader.advance(6);
100
101        // {ref_size} bytes - cell count
102        // {ref_size} bytes - root count
103        // {ref_size} bytes - absent cell count
104        // {offset_size} bytes - total cells size
105        if unlikely(!reader.require(ref_size * 3 + offset_size)) {
106            return Err(Error::InvalidHeader);
107        }
108        debug_assert!(data.len() >= (6 + ref_size * 3 + offset_size));
109
110        // SAFETY: we have already requested more than {ref_size}*3
111        // and {ref_size} is in range 1..=4
112        let (cell_count, root_count, absent_count) = unsafe {
113            (
114                reader.read_next_be_uint_fast(data, ref_size),
115                reader.read_next_be_uint_fast(data, ref_size),
116                reader.read_next_be_uint_fast(data, ref_size),
117            )
118        };
119
120        // Validate root or absent cells
121        if unlikely(root_count == 0) {
122            return Err(Error::RootCellNotFound);
123        }
124        if unlikely(!supports_multiple_roots && root_count > 1) {
125            return Err(Error::UnexpectedMultipleRoots);
126        }
127        if unlikely(root_count.saturating_add(absent_count) > cell_count) {
128            return Err(Error::TooManyRootCells);
129        }
130        if unlikely(absent_count > 0) {
131            return Err(Error::AbsentCellsNotSupported);
132        }
133        if let Some(min_roots) = options.min_roots
134            && unlikely(root_count < min_roots)
135        {
136            return Err(Error::TooFewRootCells);
137        }
138
139        {
140            let max_roots = options.max_roots.unwrap_or(MAX_ROOTS);
141            if unlikely(root_count > max_roots) {
142                return Err(Error::TooManyRootCells);
143            }
144            debug_assert!(absent_count == 0 && (1..=max_roots).contains(&root_count))
145        }
146
147        // SAFETY: we have already requested at least {ref_size}*3+{offset_size}
148        // and {ref_size} is in range 1..=8
149        let total_cells_size = unsafe { reader.read_next_be_uint_full(data, offset_size) };
150
151        const MIN_CELL_SIZE: u64 = 2; // [d1, d2]
152
153        // NOTE: `cell_count` is guaranteed to be in range of `u32`, so
154        // `u32::MAX * (2 + 4)` fits into u64 and doesn't require saturating/checked mul,
155        // `root_count` <= `cell_count` so this expression doesn't overflow
156        let min_total_cell_size = (cell_count as u64) * (MIN_CELL_SIZE + ref_size as u64)
157            - (root_count * ref_size) as u64;
158        if unlikely(total_cells_size < min_total_cell_size) {
159            return Err(Error::InvalidTotalSize);
160        }
161
162        // NOTE: `cell_count` is guaranteed to be in range of `u32`, so
163        // `u32::MAX * 282` fits into u64 and doesn't require saturating/checked mul
164        // 2 bytes - descriptor
165        // 4 * (2 + 32) - inline hashes and depths if presented
166        // 128 - max data length
167        // 4*{ref_size} - max references
168        let max_cell_size = 2 + 4 * (2 + 32) + 128 + (MAX_REF_COUNT as u64) * ref_size as u64; // ~282 bytes
169        if unlikely(total_cells_size > (cell_count as u64) * max_cell_size) {
170            return Err(Error::InvalidTotalSize);
171        }
172
173        // NOTE: `root_count` is in range ..=u32::MAX and `ref_size` is in range 1..=4
174        if unlikely(!reader.require(root_count * ref_size)) {
175            return Err(Error::UnexpectedEof);
176        }
177        debug_assert!(data.len() >= (6 + ref_size * 3 + offset_size + root_count * ref_size));
178
179        let mut roots = SmallVec::with_capacity(root_count);
180        if supports_multiple_roots {
181            for _ in 0..root_count {
182                // SAFETY: we have already requested for {root_count}*{ref_size}
183                let root_index = unsafe { reader.read_next_be_uint_fast(data, ref_size) };
184                if unlikely(root_index >= cell_count) {
185                    return Err(Error::RootOutOfBounds);
186                }
187                roots.push(root_index as u32);
188            }
189        } else {
190            roots.push(0);
191        }
192
193        // NOTE: `cell_count` is in range ..=u32::MAX, `offset_size` is in range 1..=8
194        let index_size = has_index as u64 * cell_count as u64 * offset_size as u64;
195        if unlikely(!reader.require((index_size + total_cells_size + has_crc as u64 * 4) as usize))
196        {
197            return Err(Error::UnexpectedEof);
198        }
199
200        if has_index {
201            reader.advance(cell_count * offset_size);
202        }
203
204        #[cfg(not(fuzzing))]
205        let cells_start_offset = reader.offset;
206
207        let mut cells = SmallVec::with_capacity(cell_count);
208
209        let data_ptr = data.as_ptr();
210        for _ in 0..cell_count {
211            // SAFETY: there are manual bounds checks for bytes offset
212            let start_ptr = unsafe { data_ptr.add(reader.offset) };
213            let total_len = ok!(CellParts::read_raw_cell_from_ptr(
214                start_ptr,
215                reader.len - reader.offset,
216                ref_size
217            ));
218            reader.advance(total_len);
219
220            // SAFETY: We have already requested {total_len} bytes
221            let cell = unsafe { std::slice::from_raw_parts(start_ptr, total_len) };
222            cells.push(cell);
223        }
224
225        // Check that `total_cells_size` is correct
226        #[cfg(not(fuzzing))]
227        if (cells_start_offset as u64).saturating_add(total_cells_size) != reader.offset as u64 {
228            return Err(Error::InvalidTotalSize);
229        }
230
231        // Verify checksum if specified
232        #[cfg(not(fuzzing))]
233        if has_crc {
234            if unlikely(!reader.require(4)) {
235                return Err(Error::UnexpectedEof);
236            }
237
238            // SAFETY: we have already requested 4 bytes
239            let is_checksum_correct = unsafe { reader.check_crc(data) };
240            if !is_checksum_correct {
241                return Err(Error::InvalidChecksum);
242            }
243        }
244
245        Ok(Self {
246            ref_size,
247            cells,
248            roots,
249        })
250    }
251
252    /// Assembles cell tree from slices using the specified cell context.
253    pub fn finalize(&self, context: &dyn CellContext) -> Result<ProcessedCells, Error> {
254        let ref_size = self.ref_size;
255        let cell_count = self.cells.len() as u32;
256
257        // TODO: somehow reuse `cells` vec
258        let mut res = SmallVec::<[Cell; CELLS_ON_STACK]>::new();
259        if res.try_reserve_exact(cell_count as usize).is_err() {
260            return Err(Error::InvalidTotalSize);
261        }
262
263        for raw_cell in self.cells().iter().rev() {
264            // SAFETY: it is safe to construct `CellParts` from a `read_raw_cell_from_ptr` output
265            let ctx = unsafe {
266                ok!(CellParts::from_raw_cell(
267                    raw_cell, &res, cell_count, ref_size
268                ))
269            };
270
271            let cell = match context.finalize_cell(ctx) {
272                Ok(cell) => cell,
273                Err(_) => return Err(Error::InvalidCell),
274            };
275            res.push(cell);
276        }
277
278        Ok(ProcessedCells(res))
279    }
280
281    /// Cell index size in bytes. Guaranteed to be 4 at max.
282    pub fn ref_size(&self) -> usize {
283        self.ref_size
284    }
285
286    /// Slices of the unique cells.
287    pub fn cells(&self) -> &[&'a [u8]] {
288        &self.cells
289    }
290
291    /// Root indices.
292    pub fn roots(&self) -> &[u32] {
293        &self.roots
294    }
295}
296
297/// Array of processed cells.
298pub struct ProcessedCells(SmallVec<[Cell; CELLS_ON_STACK]>);
299
300impl ProcessedCells {
301    /// Returns a processed cell by index.
302    pub fn get(&self, index: u32) -> Option<Cell> {
303        self.0.get(self.0.len() - index as usize - 1).cloned()
304    }
305}
306
307/// Wrapper around indexed bytes slice access
308/// to eliminate bounds check.
309struct BocReader {
310    len: usize,
311    offset: usize,
312}
313
314impl BocReader {
315    #[inline(always)]
316    const fn new(len: usize) -> Self {
317        Self { len, offset: 0 }
318    }
319
320    #[inline(always)]
321    const fn require(&self, len: usize) -> bool {
322        self.offset + len <= self.len
323    }
324
325    #[inline(always)]
326    fn advance(&mut self, bytes: usize) {
327        self.offset += bytes;
328    }
329
330    #[inline(always)]
331    unsafe fn read_boc_tag(&self, data: &[u8]) -> Option<BocTag> {
332        unsafe { BocTag::from_bytes(*(data.as_ptr() as *const [u8; 4])) }
333    }
334
335    /// # Safety
336    ///
337    /// The following must be true:
338    /// - size must be in range 1..=4.
339    /// - data must be at least `self.offset + size` bytes long.
340    #[inline(always)]
341    unsafe fn read_next_be_uint_fast(&mut self, data: &[u8], size: usize) -> usize {
342        unsafe {
343            let res = read_be_u32_fast(data.as_ptr().add(self.offset), size) as usize;
344            self.advance(size);
345            res
346        }
347    }
348
349    /// # Safety
350    ///
351    /// The following must be true:
352    /// - size must be in range 1..=8.
353    /// - data must be at least `self.offset + size` bytes long.
354    #[inline(always)]
355    unsafe fn read_next_be_uint_full(&mut self, data: &[u8], size: usize) -> u64 {
356        unsafe {
357            let res = read_be_u64_fast(data.as_ptr().add(self.offset), size);
358            self.advance(size);
359            res
360        }
361    }
362
363    #[cfg(not(fuzzing))]
364    #[inline(always)]
365    unsafe fn check_crc(&self, data: &[u8]) -> bool {
366        unsafe {
367            let data_ptr = data.as_ptr();
368            let crc_start_ptr = data_ptr.add(self.offset);
369
370            let parsed_crc = u32::from_le_bytes(*(crc_start_ptr as *const [u8; 4]));
371            let real_crc = crc32c::crc32c(std::slice::from_raw_parts(data_ptr, self.offset));
372
373            parsed_crc == real_crc
374        }
375    }
376}
377
378impl Deref for BocReader {
379    type Target = usize;
380
381    #[inline(always)]
382    fn deref(&self) -> &Self::Target {
383        &self.offset
384    }
385}
386
387impl<'a> CellParts<'a> {
388    /// Reads cell parts from the raw cell slice.
389    ///
390    /// # Safety
391    ///
392    /// The following must be true:
393    /// - `bytes` must be a correct bytes representation of cell.
394    ///
395    /// NOTE: It is safe to use an unmodified output from `CellParts::read_raw_cell`.
396    pub unsafe fn from_raw_cell(
397        raw_cell: &'a [u8],
398        cells: &[Cell],
399        cell_count: u32,
400        ref_size: usize,
401    ) -> Result<Self, Error> {
402        unsafe {
403            let raw_cell_ptr = raw_cell.as_ptr();
404
405            let descriptor = CellDescriptor::new(*(raw_cell_ptr as *const [u8; 2]));
406            let data_len = descriptor.byte_len() as usize;
407
408            let mut data_ptr = raw_cell_ptr.add(2);
409            if unlikely(descriptor.store_hashes()) {
410                let level = descriptor.level_mask().level();
411                debug_assert!(!descriptor.cell_type().is_pruned_branch());
412                data_ptr = data_ptr.add((32 + 2) * (level as usize + 1));
413            }
414
415            let data = std::slice::from_raw_parts(data_ptr, data_len);
416            data_ptr = data_ptr.add(data_len);
417
418            let bit_len = if descriptor.is_aligned() {
419                (data_len * 8) as u16
420            } else if let Some(data) = data.last() {
421                data_len as u16 * 8 - data.trailing_zeros() as u16 - 1
422            } else {
423                0
424            };
425
426            let mut references = ArrayVec::<Cell, MAX_REF_COUNT>::default();
427            let mut children_mask = LevelMask::EMPTY;
428
429            for _ in 0..descriptor.reference_count() {
430                let child_index = read_be_u32_fast(data_ptr, ref_size);
431                if child_index >= cell_count {
432                    return Err(Error::InvalidRef);
433                }
434
435                let child = match cells.get((cell_count - child_index - 1) as usize) {
436                    Some(child) => child.clone(),
437                    None => return Err(Error::InvalidRefOrder),
438                };
439
440                children_mask |= child.descriptor().level_mask();
441
442                references.push(child);
443
444                data_ptr = data_ptr.add(ref_size);
445            }
446
447            Ok(CellParts {
448                bit_len,
449                descriptor,
450                children_mask,
451                references,
452                data,
453            })
454        }
455    }
456
457    /// Reads a raw cell from the specified slice.
458    /// The returned slice is guaranteed to be a correct bytes representation of cell.
459    pub fn read_raw_cell<'b>(bytes: &mut &'b [u8], ref_size: usize) -> Result<&'b [u8], Error> {
460        let total_len = ok!(Self::read_raw_cell_from_ptr(
461            bytes.as_ptr(),
462            bytes.len(),
463            ref_size
464        ));
465        let (cell, rest) = bytes.split_at(total_len);
466        *bytes = rest;
467        Ok(cell)
468    }
469
470    fn read_raw_cell_from_ptr(
471        bytes_ptr: *const u8,
472        bytes_len: usize,
473        ref_size: usize,
474    ) -> Result<usize, Error> {
475        const _: () = assert!(std::mem::size_of::<CellDescriptor>() == 2);
476
477        if unlikely(bytes_len < 2) {
478            return Err(Error::UnexpectedEof);
479        }
480
481        // SAFETY: we have already checked the reader has 2 bytes
482        // and the `CellDescriptor` layout is fixed by `repr(C)`
483        let descriptor = CellDescriptor::new(unsafe { *(bytes_ptr as *const [u8; 2]) });
484
485        if unlikely(descriptor.is_absent()) {
486            return Err(Error::AbsentCellsNotSupported);
487        }
488
489        // 0b11111111 -> 0b01111111 + 1 = 0b10000000 = byte len 128, max bit len = 1023
490        // 0b11111110 -> 0b01111111 = byte len 127, bit len = 1016
491        let data_len = descriptor.byte_len() as usize;
492        let ref_count = descriptor.reference_count() as usize;
493        if unlikely(ref_count > MAX_REF_COUNT) {
494            return Err(Error::InvalidRef);
495        }
496
497        let mut data_offset = 0;
498        if unlikely(descriptor.store_hashes()) {
499            let level = descriptor.level_mask().level();
500            if descriptor.is_exotic() && ref_count == 0 && level > 0 {
501                // Pruned branch with `store_hashes` is invalid
502                return Err(Error::UnnormalizedCell);
503            }
504            data_offset = (32 + 2) * (level as usize + 1);
505        }
506
507        let total_len = 2 + data_offset + data_len + ref_count * ref_size;
508        if unlikely(bytes_len < total_len) {
509            return Err(Error::UnexpectedEof);
510        }
511
512        if data_len > 0 && !descriptor.is_aligned() {
513            // SAFETY: we have already requested 2+{data_len} bytes
514            let byte_with_tag = unsafe { *bytes_ptr.add(2 + data_offset + data_len - 1) };
515            if unlikely(byte_with_tag & 0x7f == 0) {
516                return Err(Error::UnnormalizedCell);
517            }
518        }
519
520        Ok(total_len)
521    }
522}
523
524const CELLS_ON_STACK: usize = 16;
525const ROOTS_ON_STACK: usize = 2;
526
527const MAX_ROOTS: usize = 32;
528
529/// Error type for BOC decoding related errors.
530#[derive(Debug, Copy, Clone, thiserror::Error)]
531pub enum Error {
532    /// EOF encountered during another operation.
533    #[error("unexpected EOF")]
534    UnexpectedEof,
535    /// Invalid magic bytes.
536    #[error("unknown BOC tag")]
537    UnknownBocTag,
538    /// Invalid BOC header.
539    #[error("invalid header")]
540    InvalidHeader,
541    /// References size is greater than 4.
542    #[error("ref index does not fit in `u32` type")]
543    InvalidRefSize,
544    /// Offset size is greater than 8.
545    #[error("cell offset does not fit in `usize` type")]
546    InvalidOffsetSize,
547    /// Root cell not found.
548    #[error("root cell not found")]
549    RootCellNotFound,
550    /// Specified BOC tag doesn't support multiple roots.
551    #[error("unexpected multiple roots")]
552    UnexpectedMultipleRoots,
553    /// The number of roots in BOC is greater than expected.
554    #[error("too many root cells")]
555    TooManyRootCells,
556    /// Absent cells are legacy therefore not supported.
557    #[error("absent cells are not supported")]
558    AbsentCellsNotSupported,
559    /// The number of roots in BOC is less than expected.
560    #[error("too few root cells")]
561    TooFewRootCells,
562    /// Total cells size mismatch.
563    #[error("invalid total cells size")]
564    InvalidTotalSize,
565    /// Invalid root cell index.
566    #[error("root index out of bounds")]
567    RootOutOfBounds,
568    /// Invalid child reference.
569    #[error("cell ref count not in range 0..=4")]
570    InvalidRef,
571    /// Suboptimal cells are treated as error.
572    #[error("unnormalized cell")]
573    UnnormalizedCell,
574    /// Possible graph loop detected.
575    #[error("invalid children order")]
576    InvalidRefOrder,
577    /// Failed to parse cell.
578    #[error("invalid cell")]
579    InvalidCell,
580    /// Crc mismatch.
581    #[error("invalid checksum")]
582    InvalidChecksum,
583}