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}