tycho_types/boc/
mod.rs

1//! BOC (Bag Of Cells) implementation.
2
3#[cfg(feature = "serde")]
4pub use self::serde::SerdeBoc;
5use crate::cell::{Cell, CellBuilder, CellContext, CellFamily, DynCell, HashBytes, Load, Store};
6
7/// BOC decoder implementation.
8pub mod de;
9/// BOC encoder implementation.
10pub mod ser;
11
12#[cfg(feature = "serde")]
13mod serde;
14
15#[cfg(test)]
16mod tests;
17
18/// BOC file magic number.
19#[derive(Default, Copy, Clone, Eq, PartialEq)]
20pub enum BocTag {
21    /// Single root, cells index, no CRC32.
22    Indexed,
23    /// Single root, cells index, with CRC32.
24    IndexedCrc32,
25    /// Multiple roots, optional cells index, optional CRC32.
26    #[default]
27    Generic,
28}
29
30impl BocTag {
31    const INDEXED: [u8; 4] = [0x68, 0xff, 0x65, 0xf3];
32    const INDEXED_CRC32: [u8; 4] = [0xac, 0xc3, 0xa7, 0x28];
33    const GENERIC: [u8; 4] = [0xb5, 0xee, 0x9c, 0x72];
34
35    /// Tries to match bytes with BOC tag.
36    pub const fn from_bytes(data: [u8; 4]) -> Option<Self> {
37        match data {
38            Self::GENERIC => Some(Self::Generic),
39            Self::INDEXED_CRC32 => Some(Self::IndexedCrc32),
40            Self::INDEXED => Some(Self::Indexed),
41            _ => None,
42        }
43    }
44
45    /// Converts BOC tag to bytes.
46    pub const fn to_bytes(self) -> [u8; 4] {
47        match self {
48            Self::Indexed => Self::INDEXED,
49            Self::IndexedCrc32 => Self::INDEXED_CRC32,
50            Self::Generic => Self::GENERIC,
51        }
52    }
53}
54
55/// BOC (Bag Of Cells) helper.
56pub struct Boc;
57
58impl Boc {
59    /// Computes a simple SHA256 hash of the data.
60    #[inline]
61    pub fn file_hash(data: impl AsRef<[u8]>) -> HashBytes {
62        use sha2::Digest;
63
64        sha2::Sha256::digest(data).into()
65    }
66
67    /// Computes a Blake3 hash of the data.
68    #[cfg(feature = "blake3")]
69    #[inline]
70    pub fn file_hash_blake(data: impl AsRef<[u8]>) -> HashBytes {
71        #[cfg(not(feature = "rayon"))]
72        {
73            blake3::hash(data.as_ref()).into()
74        }
75
76        #[cfg(feature = "rayon")]
77        {
78            // Use Rayon for parallel hashing if data is larger than 256 KB.
79            const RAYON_THRESHOLD: usize = 256 * 1024;
80
81            let data = data.as_ref();
82            if data.len() < RAYON_THRESHOLD {
83                blake3::hash(data)
84            } else {
85                blake3::Hasher::new().update_rayon(data).finalize()
86            }
87            .into()
88        }
89    }
90
91    /// Encodes the specified cell tree as BOC and
92    /// returns the `hex` encoded bytes as a string.
93    pub fn encode_hex<T>(cell: T) -> String
94    where
95        T: AsRef<DynCell>,
96    {
97        hex::encode(Self::encode(cell))
98    }
99
100    /// Encodes the specified cell tree as BOC and
101    /// returns the `base64` encoded bytes as a string.
102    #[cfg(any(feature = "base64", test))]
103    pub fn encode_base64<T>(cell: T) -> String
104    where
105        T: AsRef<DynCell>,
106    {
107        crate::util::encode_base64(Self::encode(cell))
108    }
109
110    /// Encodes the specified cell tree as BOC and
111    /// returns the `hex` encoded bytes as a string.
112    ///
113    /// Uses `rayon` under the hood to parallelize encoding.
114    #[cfg(feature = "rayon")]
115    pub fn encode_hex_rayon<T>(cell: T) -> String
116    where
117        T: AsRef<DynCell>,
118    {
119        hex::encode(Self::encode_rayon(cell))
120    }
121
122    /// Encodes the specified cell tree as BOC and
123    /// returns the `base64` encoded bytes as a string.
124    ///
125    /// Uses `rayon` under the hood to parallelize encoding.
126    #[cfg(all(any(feature = "base64", test), feature = "rayon"))]
127    pub fn encode_base64_rayon<T>(cell: T) -> String
128    where
129        T: AsRef<DynCell>,
130    {
131        crate::util::encode_base64(Self::encode_rayon(cell))
132    }
133
134    /// Encodes the specified cell tree as BOC.
135    pub fn encode<T>(cell: T) -> Vec<u8>
136    where
137        T: AsRef<DynCell>,
138    {
139        fn encode_impl(cell: &DynCell) -> Vec<u8> {
140            let mut result = Vec::new();
141            ser::BocHeader::<ahash::RandomState>::with_root(cell).encode(&mut result);
142            result
143        }
144        encode_impl(cell.as_ref())
145    }
146
147    /// Encodes the specified cell tree as BOC using preallocated revs cache.
148    pub fn encode_with_cache<T>(
149        cell: T,
150        cache: &mut ser::BocHeaderCache<ahash::RandomState>,
151    ) -> Vec<u8>
152    where
153        T: AsRef<DynCell>,
154    {
155        fn encode_impl(
156            cell: &DynCell,
157            cache: &mut ser::BocHeaderCache<ahash::RandomState>,
158        ) -> Vec<u8> {
159            let mut result = Vec::new();
160            let header = ser::BocHeader::<ahash::RandomState>::with_root_and_cache(
161                cell,
162                std::mem::take(cache),
163            );
164            header.encode(&mut result);
165            *cache = header.into_cache();
166
167            result
168        }
169        encode_impl(cell.as_ref(), cache)
170    }
171
172    /// Encodes the specified cell tree as BOC.
173    ///
174    /// Uses `rayon` under the hood to parallelize encoding.
175    #[cfg(feature = "rayon")]
176    pub fn encode_rayon<T>(cell: T) -> Vec<u8>
177    where
178        T: AsRef<DynCell>,
179    {
180        fn encode_impl(cell: &DynCell) -> Vec<u8> {
181            let mut result = Vec::new();
182            ser::BocHeader::<ahash::RandomState>::with_root(cell).encode_rayon(&mut result);
183            result
184        }
185        encode_impl(cell.as_ref())
186    }
187
188    /// Encodes a pair of cell trees as BOC.
189    pub fn encode_pair<T1, T2>((cell1, cell2): (T1, T2)) -> Vec<u8>
190    where
191        T1: AsRef<DynCell>,
192        T2: AsRef<DynCell>,
193    {
194        fn encode_pair_impl(cell1: &DynCell, cell2: &DynCell) -> Vec<u8> {
195            let mut result = Vec::new();
196            let mut encoder = ser::BocHeader::<ahash::RandomState>::with_root(cell1);
197            encoder.add_root(cell2);
198            encoder.encode(&mut result);
199            result
200        }
201        encode_pair_impl(cell1.as_ref(), cell2.as_ref())
202    }
203
204    /// Decodes a `hex` encoded BOC into a cell tree
205    /// using an empty cell context.
206    pub fn decode_hex<T: AsRef<[u8]>>(data: T) -> Result<Cell, de::Error> {
207        fn decode_hex_impl(data: &[u8]) -> Result<Cell, de::Error> {
208            match hex::decode(data) {
209                Ok(data) => Boc::decode_ext(data.as_slice(), Cell::empty_context()),
210                Err(_) => Err(de::Error::UnknownBocTag),
211            }
212        }
213        decode_hex_impl(data.as_ref())
214    }
215
216    /// Decodes a `base64` encoded BOC into a cell tree
217    /// using an empty cell context.
218    #[cfg(any(feature = "base64", test))]
219    #[inline]
220    pub fn decode_base64<T: AsRef<[u8]>>(data: T) -> Result<Cell, de::Error> {
221        fn decode_base64_impl(data: &[u8]) -> Result<Cell, de::Error> {
222            match crate::util::decode_base64(data) {
223                Ok(data) => Boc::decode_ext(data.as_slice(), Cell::empty_context()),
224                Err(_) => Err(de::Error::UnknownBocTag),
225            }
226        }
227        decode_base64_impl(data.as_ref())
228    }
229
230    /// Decodes a cell tree using an empty cell context.
231    #[inline]
232    pub fn decode<T>(data: T) -> Result<Cell, de::Error>
233    where
234        T: AsRef<[u8]>,
235    {
236        fn decode_impl(data: &[u8]) -> Result<Cell, de::Error> {
237            Boc::decode_ext(data, Cell::empty_context())
238        }
239        decode_impl(data.as_ref())
240    }
241
242    /// Decodes a pair of cell trees using an empty cell context.
243    #[inline]
244    pub fn decode_pair<T>(data: T) -> Result<(Cell, Cell), de::Error>
245    where
246        T: AsRef<[u8]>,
247    {
248        fn decode_pair_impl(data: &[u8]) -> Result<(Cell, Cell), de::Error> {
249            Boc::decode_pair_ext(data, Cell::empty_context())
250        }
251        decode_pair_impl(data.as_ref())
252    }
253
254    /// Decodes a cell tree using the specified cell context.
255    pub fn decode_ext(data: &[u8], context: &dyn CellContext) -> Result<Cell, de::Error> {
256        use self::de::*;
257
258        let header = ok!(de::BocHeader::decode(data, &Options {
259            max_roots: Some(1),
260            min_roots: Some(1),
261        },));
262
263        if let Some(&root) = header.roots().first() {
264            let cells = ok!(header.finalize(context));
265            if let Some(root) = cells.get(root) {
266                return Ok(root);
267            }
268        }
269
270        Err(de::Error::RootCellNotFound)
271    }
272
273    /// Decodes a pair of cell trees using the specified cell context.
274    pub fn decode_pair_ext(
275        data: &[u8],
276        context: &dyn CellContext,
277    ) -> Result<(Cell, Cell), de::Error> {
278        use self::de::*;
279
280        let header = ok!(de::BocHeader::decode(data, &Options {
281            max_roots: Some(2),
282            min_roots: Some(2),
283        },));
284
285        let mut roots = header.roots().iter();
286        if let (Some(&root1), Some(&root2)) = (roots.next(), roots.next()) {
287            let cells = ok!(header.finalize(context));
288            if let (Some(root1), Some(root2)) = (cells.get(root1), cells.get(root2)) {
289                return Ok((root1, root2));
290            }
291        }
292
293        Err(de::Error::RootCellNotFound)
294    }
295
296    /// Serializes cell into an encoded BOC (as base64 for human readable serializers).
297    #[cfg(feature = "serde")]
298    pub fn serialize<T, S>(value: T, serializer: S) -> Result<S::Ok, S::Error>
299    where
300        SerdeBoc<T>: ::serde::Serialize,
301        S: ::serde::Serializer,
302    {
303        use ::serde::Serialize;
304
305        SerdeBoc::from(value).serialize(serializer)
306    }
307
308    /// Deserializes cell from an encoded BOC (from base64 for human readable deserializers).
309    #[cfg(feature = "serde")]
310    pub fn deserialize<'de, T, D>(deserializer: D) -> Result<T, D::Error>
311    where
312        SerdeBoc<T>: ::serde::Deserialize<'de>,
313        D: ::serde::Deserializer<'de>,
314    {
315        use ::serde::Deserialize;
316
317        SerdeBoc::<T>::deserialize(deserializer).map(SerdeBoc::into_inner)
318    }
319}
320
321/// BOC representation helper.
322pub struct BocRepr;
323
324impl BocRepr {
325    /// Encodes the specified cell tree as BOC using an empty cell context and
326    /// returns the `hex` encoded bytes as a string.
327    pub fn encode_hex<T>(data: T) -> Result<String, crate::error::Error>
328    where
329        T: Store,
330    {
331        let boc = ok!(Self::encode_ext(data, Cell::empty_context()));
332        Ok(hex::encode(boc))
333    }
334
335    /// Encodes the specified cell tree as BOC using an empty cell context and
336    /// returns the `base64` encoded bytes as a string.
337    #[cfg(any(feature = "base64", test))]
338    pub fn encode_base64<T>(data: T) -> Result<String, crate::error::Error>
339    where
340        T: Store,
341    {
342        let boc = ok!(Self::encode_ext(data, Cell::empty_context()));
343        Ok(crate::util::encode_base64(boc))
344    }
345
346    /// Encodes the specified cell tree as BOC using an empty cell context and
347    /// returns the `hex` encoded bytes as a string.
348    ///
349    /// Uses `rayon` under the hood to parallelize encoding.
350    #[cfg(feature = "rayon")]
351    pub fn encode_hex_rayon<T>(data: T) -> Result<String, crate::error::Error>
352    where
353        T: Store,
354    {
355        let boc = ok!(Self::encode_rayon_ext(data, Cell::empty_context()));
356        Ok(hex::encode(boc))
357    }
358
359    /// Encodes the specified cell tree as BOC using an empty cell context and
360    /// returns the `base64` encoded bytes as a string.
361    ///
362    /// Uses `rayon` under the hood to parallelize encoding.
363    #[cfg(all(any(feature = "base64", test), feature = "rayon"))]
364    pub fn encode_base64_rayon<T>(data: T) -> Result<String, crate::error::Error>
365    where
366        T: Store,
367    {
368        let boc = ok!(Self::encode_rayon_ext(data, Cell::empty_context()));
369        Ok(crate::util::encode_base64(boc))
370    }
371
372    /// Encodes the specified cell tree as BOC using an empty cell context.
373    pub fn encode<T>(data: T) -> Result<Vec<u8>, crate::error::Error>
374    where
375        T: Store,
376    {
377        Self::encode_ext(data, Cell::empty_context())
378    }
379
380    /// Encodes the specified cell tree as BOC using an empty cell context.
381    ///
382    /// Uses `rayon` under the hood to parallelize encoding.
383    #[cfg(feature = "rayon")]
384    pub fn encode_rayon<T>(data: T) -> Result<Vec<u8>, crate::error::Error>
385    where
386        T: Store,
387    {
388        Self::encode_rayon_ext(data, Cell::empty_context())
389    }
390
391    /// Decodes a `hex` encoded BOC into an object
392    /// using an empty cell context.
393    #[inline]
394    pub fn decode_hex<T, D>(data: D) -> Result<T, BocReprError>
395    where
396        for<'a> T: Load<'a>,
397        D: AsRef<[u8]>,
398    {
399        fn decode_hex_impl<T>(data: &[u8]) -> Result<T, BocReprError>
400        where
401            for<'a> T: Load<'a>,
402        {
403            match hex::decode(data) {
404                Ok(data) => BocRepr::decode_ext(data.as_slice(), Cell::empty_context()),
405                Err(_) => Err(BocReprError::InvalidBoc(de::Error::UnknownBocTag)),
406            }
407        }
408        decode_hex_impl::<T>(data.as_ref())
409    }
410
411    /// Decodes a `base64` encoded BOC into an object
412    /// using an empty cell context.
413    #[cfg(any(feature = "base64", test))]
414    #[inline]
415    pub fn decode_base64<T, D>(data: D) -> Result<T, BocReprError>
416    where
417        for<'a> T: Load<'a>,
418        D: AsRef<[u8]>,
419    {
420        fn decode_base64_impl<T>(data: &[u8]) -> Result<T, BocReprError>
421        where
422            for<'a> T: Load<'a>,
423        {
424            match crate::util::decode_base64(data) {
425                Ok(data) => BocRepr::decode_ext(data.as_slice(), Cell::empty_context()),
426                Err(_) => Err(BocReprError::InvalidBoc(de::Error::UnknownBocTag)),
427            }
428        }
429        decode_base64_impl::<T>(data.as_ref())
430    }
431
432    /// Decodes an object using an empty cell context.
433    #[inline]
434    pub fn decode<T, D>(data: D) -> Result<T, BocReprError>
435    where
436        for<'a> T: Load<'a>,
437        D: AsRef<[u8]>,
438    {
439        fn decode_impl<T>(data: &[u8]) -> Result<T, BocReprError>
440        where
441            for<'a> T: Load<'a>,
442        {
443            BocRepr::decode_ext(data, Cell::empty_context())
444        }
445        decode_impl::<T>(data.as_ref())
446    }
447
448    /// Encodes the specified object as BOC.
449    pub fn encode_ext<T>(data: T, context: &dyn CellContext) -> Result<Vec<u8>, crate::error::Error>
450    where
451        T: Store,
452    {
453        fn encode_ext_impl(
454            data: &dyn Store,
455            context: &dyn CellContext,
456        ) -> Result<Vec<u8>, crate::error::Error> {
457            let mut builder = CellBuilder::new();
458            ok!(data.store_into(&mut builder, context));
459            let cell = ok!(builder.build_ext(context));
460            Ok(Boc::encode(cell))
461        }
462        encode_ext_impl(&data, context)
463    }
464
465    /// Encodes the specified object as BOC.
466    ///
467    /// Uses `rayon` under the hood to parallelize encoding.
468    #[cfg(feature = "rayon")]
469    pub fn encode_rayon_ext<T>(
470        data: T,
471        context: &dyn CellContext,
472    ) -> Result<Vec<u8>, crate::error::Error>
473    where
474        T: Store,
475    {
476        fn encode_ext_impl(
477            data: &dyn Store,
478            context: &dyn CellContext,
479        ) -> Result<Vec<u8>, crate::error::Error> {
480            let mut builder = CellBuilder::new();
481            ok!(data.store_into(&mut builder, context));
482            let cell = ok!(builder.build_ext(context));
483            Ok(Boc::encode_rayon(cell))
484        }
485        encode_ext_impl(&data, context)
486    }
487
488    /// Decodes object from BOC using the specified cell context.
489    pub fn decode_ext<T>(data: &[u8], context: &dyn CellContext) -> Result<T, BocReprError>
490    where
491        for<'a> T: Load<'a>,
492    {
493        let cell = match Boc::decode_ext(data, context) {
494            Ok(cell) => cell,
495            Err(e) => return Err(BocReprError::InvalidBoc(e)),
496        };
497
498        match cell.as_ref().parse::<T>() {
499            Ok(data) => Ok(data),
500            Err(e) => Err(BocReprError::InvalidData(e)),
501        }
502    }
503
504    /// Serializes the type into an encoded BOC using an empty cell context
505    /// (as base64 for human readable serializers).
506    #[cfg(feature = "serde")]
507    pub fn serialize<S, T>(data: &T, serializer: S) -> Result<S::Ok, S::Error>
508    where
509        S: ::serde::Serializer,
510        T: Store,
511    {
512        use ::serde::ser::{Error, Serialize};
513
514        let context = Cell::empty_context();
515
516        let mut builder = CellBuilder::new();
517        if data.store_into(&mut builder, context).is_err() {
518            return Err(Error::custom("cell overflow"));
519        }
520
521        let cell = match builder.build_ext(context) {
522            Ok(cell) => cell,
523            Err(_) => return Err(Error::custom("failed to store into builder")),
524        };
525
526        cell.as_ref().serialize(serializer)
527    }
528
529    /// Deserializes the type from an encoded BOC using an empty cell context
530    /// (from base64 for human readable serializers).
531    #[cfg(feature = "serde")]
532    pub fn deserialize<'de, D, T>(deserializer: D) -> Result<T, D::Error>
533    where
534        D: ::serde::Deserializer<'de>,
535        for<'a> T: Load<'a>,
536    {
537        use ::serde::de::Error;
538
539        let cell = ok!(Boc::deserialize::<Cell, _>(deserializer));
540        match cell.as_ref().parse::<T>() {
541            Ok(data) => Ok(data),
542            Err(_) => Err(Error::custom("failed to decode object from cells")),
543        }
544    }
545}
546
547/// Error type for BOC repr decoding related errors.
548#[derive(Debug, thiserror::Error)]
549pub enum BocReprError {
550    /// Failed to decode BOC.
551    #[error("invalid BOC")]
552    InvalidBoc(#[source] de::Error),
553    /// Failed to decode data from cells.
554    #[error("failed to decode object from cells")]
555    InvalidData(#[source] crate::error::Error),
556}