ipfrs_core/
block.rs

1//! Content-addressed data blocks.
2//!
3//! This module provides the [`Block`] type, which represents a piece of data
4//! along with its content identifier ([`Cid`]). Blocks are the fundamental
5//! storage unit in IPFS-style systems.
6//!
7//! # Example
8//!
9//! ```rust
10//! use ipfrs_core::Block;
11//! use bytes::Bytes;
12//!
13//! // Create a block from data
14//! let block = Block::new(Bytes::from_static(b"Hello!")).unwrap();
15//!
16//! // Access the CID and data
17//! println!("CID: {}", block.cid());
18//! assert_eq!(block.data().as_ref(), b"Hello!");
19//!
20//! // Verify the block's integrity
21//! assert!(block.verify().unwrap());
22//! ```
23//!
24//! # Size Limits
25//!
26//! Blocks have size constraints for network efficiency:
27//! - Minimum: 1 byte ([`MIN_BLOCK_SIZE`])
28//! - Maximum: 2 MiB ([`MAX_BLOCK_SIZE`])
29
30use crate::cid::{Cid, CidBuilder, HashAlgorithm};
31use crate::error::{Error, Result};
32use crate::types::BlockSize;
33use bytes::Bytes;
34use serde::{Deserialize, Serialize};
35
36/// Maximum block size in bytes (2 MiB, same as IPFS default)
37pub const MAX_BLOCK_SIZE: usize = 2 * 1024 * 1024;
38
39/// Minimum block size in bytes
40pub const MIN_BLOCK_SIZE: usize = 1;
41
42/// A content-addressed data block.
43///
44/// A `Block` consists of raw data and its content identifier (CID).
45/// The CID is cryptographically derived from the data, ensuring
46/// that blocks can be verified independently.
47///
48/// # Creating Blocks
49///
50/// Use [`Block::new`] for simple block creation with default settings,
51/// or [`Block::builder`] for custom hash algorithms or codecs.
52///
53/// # Thread Safety
54///
55/// `Block` is `Clone` and can be safely shared across threads.
56/// The underlying data uses reference counting ([`Bytes`]).
57#[derive(Debug, Clone)]
58pub struct Block {
59    /// Content identifier
60    cid: Cid,
61    /// Raw data
62    data: Bytes,
63}
64
65impl Block {
66    /// Create a new block with computed CID using default settings
67    ///
68    /// # Errors
69    /// Returns an error if the block size is outside the valid range
70    pub fn new(data: Bytes) -> Result<Self> {
71        Self::validate_size(data.len())?;
72        let cid = CidBuilder::new().build(&data)?;
73        Ok(Self { cid, data })
74    }
75
76    /// Validate block size
77    fn validate_size(size: usize) -> Result<()> {
78        if size < MIN_BLOCK_SIZE {
79            return Err(Error::InvalidData(format!(
80                "Block size {} is below minimum {}",
81                size, MIN_BLOCK_SIZE
82            )));
83        }
84        if size > MAX_BLOCK_SIZE {
85            return Err(Error::InvalidData(format!(
86                "Block size {} exceeds maximum {} (2 MiB)",
87                size, MAX_BLOCK_SIZE
88            )));
89        }
90        Ok(())
91    }
92
93    /// Create a block from existing CID and data (without verification)
94    pub fn from_parts(cid: Cid, data: Bytes) -> Self {
95        Self { cid, data }
96    }
97
98    /// Get the CID of this block
99    #[inline]
100    pub fn cid(&self) -> &Cid {
101        &self.cid
102    }
103
104    /// Get the data of this block
105    #[inline]
106    pub fn data(&self) -> &Bytes {
107        &self.data
108    }
109
110    /// Get the size of this block in bytes
111    #[inline]
112    pub fn size(&self) -> BlockSize {
113        self.data.len() as BlockSize
114    }
115
116    /// Verify that the CID matches the data
117    pub fn verify(&self) -> Result<bool> {
118        let computed_cid = CidBuilder::new().build(&self.data)?;
119        Ok(computed_cid == self.cid)
120    }
121
122    /// Consume the block and return its parts
123    #[inline]
124    pub fn into_parts(self) -> (Cid, Bytes) {
125        (self.cid, self.data)
126    }
127
128    /// Get a zero-copy slice of the block data
129    ///
130    /// This creates a new `Bytes` instance that references the same
131    /// underlying data without copying. The range must be within bounds.
132    ///
133    /// # Arguments
134    ///
135    /// * `range` - The byte range to slice (e.g., `0..100` or `10..`)
136    ///
137    /// # Panics
138    ///
139    /// Panics if the range is out of bounds.
140    ///
141    /// # Example
142    ///
143    /// ```rust
144    /// use ipfrs_core::Block;
145    /// use bytes::Bytes;
146    ///
147    /// let block = Block::new(Bytes::from_static(b"Hello, World!")).unwrap();
148    /// let slice = block.slice(0..5);  // "Hello"
149    /// assert_eq!(slice.as_ref(), b"Hello");
150    /// ```
151    pub fn slice(&self, range: impl std::ops::RangeBounds<usize>) -> Bytes {
152        use std::ops::Bound;
153
154        let len = self.data.len();
155        let start = match range.start_bound() {
156            Bound::Included(&n) => n,
157            Bound::Excluded(&n) => n + 1,
158            Bound::Unbounded => 0,
159        };
160        let end = match range.end_bound() {
161            Bound::Included(&n) => n + 1,
162            Bound::Excluded(&n) => n,
163            Bound::Unbounded => len,
164        };
165
166        self.data.slice(start..end)
167    }
168
169    /// Get a zero-copy view of the entire data as a byte slice
170    ///
171    /// This provides direct access to the underlying bytes without
172    /// any allocation or copying.
173    ///
174    /// # Example
175    ///
176    /// ```rust
177    /// use ipfrs_core::Block;
178    /// use bytes::Bytes;
179    ///
180    /// let block = Block::new(Bytes::from_static(b"data")).unwrap();
181    /// let view = block.as_bytes();
182    /// assert_eq!(view, b"data");
183    /// ```
184    #[inline]
185    pub fn as_bytes(&self) -> &[u8] {
186        &self.data
187    }
188
189    /// Clone the underlying `Bytes` reference (zero-copy)
190    ///
191    /// Since `Bytes` uses reference counting, cloning is cheap and
192    /// does not copy the underlying data.
193    ///
194    /// # Example
195    ///
196    /// ```rust
197    /// use ipfrs_core::Block;
198    /// use bytes::Bytes;
199    ///
200    /// let block = Block::new(Bytes::from_static(b"data")).unwrap();
201    /// let data_clone = block.clone_data();
202    /// // data_clone shares the same underlying memory
203    /// ```
204    #[inline]
205    pub fn clone_data(&self) -> Bytes {
206        self.data.clone()
207    }
208
209    /// Check if two blocks share the same underlying data buffer
210    ///
211    /// Returns `true` if both blocks reference the same memory,
212    /// even if they have different CIDs (which shouldn't happen
213    /// in normal usage).
214    #[inline]
215    #[must_use]
216    pub fn shares_data(&self, other: &Block) -> bool {
217        self.data.as_ptr() == other.data.as_ptr()
218    }
219
220    /// Get metadata about the block
221    pub fn metadata(&self) -> BlockMetadata {
222        BlockMetadata::new(self.cid, self.size())
223    }
224
225    /// Create a builder for constructing blocks with custom settings
226    pub fn builder() -> BlockBuilder {
227        BlockBuilder::new()
228    }
229
230    /// Get the length of the block data in bytes
231    ///
232    /// This is an alias for [`Block::size`] for better Rust convention compliance.
233    ///
234    /// # Example
235    ///
236    /// ```rust
237    /// use ipfrs_core::Block;
238    /// use bytes::Bytes;
239    ///
240    /// let block = Block::new(Bytes::from_static(b"data")).unwrap();
241    /// assert_eq!(block.len(), 4);
242    /// ```
243    #[inline]
244    pub fn len(&self) -> usize {
245        self.data.len()
246    }
247
248    /// Check if the block is empty
249    ///
250    /// Note: Due to the MIN_BLOCK_SIZE constraint, blocks created through
251    /// [`Block::new`] can never be empty. This method is provided for
252    /// completeness and for blocks created through other means.
253    ///
254    /// # Example
255    ///
256    /// ```rust
257    /// use ipfrs_core::Block;
258    /// use bytes::Bytes;
259    ///
260    /// let block = Block::new(Bytes::from_static(b"data")).unwrap();
261    /// assert!(!block.is_empty());
262    /// ```
263    #[inline]
264    pub fn is_empty(&self) -> bool {
265        self.data.is_empty()
266    }
267
268    /// Create a block from a byte vector with default settings
269    ///
270    /// This is a convenience method that takes ownership of the vector.
271    ///
272    /// # Errors
273    ///
274    /// Returns an error if the size is outside the valid range.
275    ///
276    /// # Example
277    ///
278    /// ```rust
279    /// use ipfrs_core::Block;
280    ///
281    /// let data = vec![1, 2, 3, 4];
282    /// let block = Block::from_vec(data).unwrap();
283    /// assert_eq!(block.len(), 4);
284    /// ```
285    pub fn from_vec(vec: Vec<u8>) -> Result<Self> {
286        Self::new(Bytes::from(vec))
287    }
288
289    /// Create a block from a byte slice with default settings
290    ///
291    /// This copies the slice data.
292    ///
293    /// # Errors
294    ///
295    /// Returns an error if the size is outside the valid range.
296    ///
297    /// # Example
298    ///
299    /// ```rust
300    /// use ipfrs_core::Block;
301    ///
302    /// let block = Block::from_slice(b"hello").unwrap();
303    /// assert_eq!(block.as_bytes(), b"hello");
304    /// ```
305    pub fn from_slice(slice: &[u8]) -> Result<Self> {
306        Self::new(Bytes::copy_from_slice(slice))
307    }
308
309    /// Create a block from a static byte slice (zero-copy)
310    ///
311    /// This is a zero-copy operation for static data.
312    ///
313    /// # Errors
314    ///
315    /// Returns an error if the size is outside the valid range.
316    ///
317    /// # Example
318    ///
319    /// ```rust
320    /// use ipfrs_core::Block;
321    ///
322    /// let block = Block::from_static(b"static data").unwrap();
323    /// assert_eq!(block.len(), 11);
324    /// ```
325    pub fn from_static(slice: &'static [u8]) -> Result<Self> {
326        Self::new(Bytes::from_static(slice))
327    }
328}
329
330/// Builder for creating blocks with custom CID settings.
331///
332/// Use this when you need to specify a custom hash algorithm,
333/// CID version, or codec.
334///
335/// # Example
336///
337/// ```rust
338/// use ipfrs_core::{Block, HashAlgorithm, cid::codec};
339/// use bytes::Bytes;
340///
341/// let block = Block::builder()
342///     .hash_algorithm(HashAlgorithm::Sha3_256)
343///     .codec(codec::DAG_CBOR)
344///     .build(Bytes::from_static(b"data"))
345///     .unwrap();
346/// ```
347#[derive(Debug, Clone)]
348pub struct BlockBuilder {
349    cid_builder: CidBuilder,
350}
351
352impl Default for BlockBuilder {
353    fn default() -> Self {
354        Self {
355            cid_builder: CidBuilder::new(),
356        }
357    }
358}
359
360impl BlockBuilder {
361    /// Creates a new `BlockBuilder` with default settings.
362    ///
363    /// Uses SHA2-256 hash algorithm and CIDv1 by default.
364    pub fn new() -> Self {
365        Self::default()
366    }
367
368    /// Set the hash algorithm to use
369    pub fn hash_algorithm(mut self, algorithm: HashAlgorithm) -> Self {
370        self.cid_builder = self.cid_builder.hash_algorithm(algorithm);
371        self
372    }
373
374    /// Set the CID version
375    pub fn cid_version(mut self, version: cid::Version) -> Self {
376        self.cid_builder = self.cid_builder.version(version);
377        self
378    }
379
380    /// Set the codec
381    pub fn codec(mut self, codec: u64) -> Self {
382        self.cid_builder = self.cid_builder.codec(codec);
383        self
384    }
385
386    /// Build a block from data
387    pub fn build(self, data: Bytes) -> Result<Block> {
388        Block::validate_size(data.len())?;
389        let cid = self.cid_builder.build(&data)?;
390        Ok(Block { cid, data })
391    }
392
393    /// Build a block from data slice (convenience method)
394    pub fn build_from_slice(self, data: &[u8]) -> Result<Block> {
395        self.build(Bytes::copy_from_slice(data))
396    }
397}
398
399impl From<&Block> for Cid {
400    fn from(block: &Block) -> Self {
401        block.cid
402    }
403}
404
405impl AsRef<[u8]> for Block {
406    fn as_ref(&self) -> &[u8] {
407        self.as_bytes()
408    }
409}
410
411impl std::ops::Deref for Block {
412    type Target = [u8];
413
414    fn deref(&self) -> &Self::Target {
415        self.as_bytes()
416    }
417}
418
419impl TryFrom<Vec<u8>> for Block {
420    type Error = Error;
421
422    fn try_from(vec: Vec<u8>) -> Result<Self> {
423        Block::from_vec(vec)
424    }
425}
426
427impl TryFrom<&[u8]> for Block {
428    type Error = Error;
429
430    fn try_from(slice: &[u8]) -> Result<Self> {
431        Block::from_slice(slice)
432    }
433}
434
435impl TryFrom<Bytes> for Block {
436    type Error = Error;
437
438    fn try_from(bytes: Bytes) -> Result<Self> {
439        Block::new(bytes)
440    }
441}
442
443impl PartialEq for Block {
444    /// Two blocks are equal if they have the same CID.
445    ///
446    /// Since CIDs are derived from data, this also implies equal data
447    /// (assuming no hash collisions).
448    fn eq(&self, other: &Self) -> bool {
449        self.cid == other.cid
450    }
451}
452
453impl Eq for Block {}
454
455impl std::hash::Hash for Block {
456    /// Hash based on the CID for use in HashMaps and HashSets.
457    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
458        self.cid.to_bytes().hash(state);
459    }
460}
461
462impl PartialOrd for Block {
463    /// Compare blocks by their CID.
464    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
465        Some(self.cmp(other))
466    }
467}
468
469impl Ord for Block {
470    /// Compare blocks by their CID for use in BTreeMaps and BTreeSets.
471    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
472        self.cid.to_bytes().cmp(&other.cid.to_bytes())
473    }
474}
475
476/// Metadata about a block for indexing and queries.
477///
478/// This lightweight structure contains summary information about a block
479/// without holding the actual data. Useful for block indices and caches.
480#[derive(Debug, Clone, Serialize, Deserialize)]
481pub struct BlockMetadata {
482    /// Content identifier of the block
483    pub cid: crate::cid::SerializableCid,
484    /// Size of the block data in bytes
485    pub size: BlockSize,
486    /// Number of CID links to other blocks (for DAG nodes)
487    pub links: usize,
488}
489
490impl BlockMetadata {
491    /// Creates new block metadata with the given CID and size.
492    ///
493    /// The number of links is initialized to 0.
494    pub fn new(cid: Cid, size: BlockSize) -> Self {
495        Self {
496            cid: crate::cid::SerializableCid(cid),
497            size,
498            links: 0,
499        }
500    }
501
502    /// Sets the number of CID links in this block.
503    ///
504    /// This is useful for DAG nodes that reference other blocks.
505    pub fn with_links(mut self, links: usize) -> Self {
506        self.links = links;
507        self
508    }
509}