use crate::block::Block;
use crate::chunking::{DagLink, DagNode};
use crate::cid::Cid;
use crate::error::{Error, Result};
use bytes::Bytes;
use futures::future::BoxFuture;
use std::collections::VecDeque;
use std::io::{self, Read};
use std::pin::Pin;
use std::task::{Context, Poll};
use tokio::io::{AsyncRead, ReadBuf};
pub trait BlockFetcher: Send + Sync {
fn fetch(&self, cid: Cid) -> BoxFuture<'_, Result<Block>>;
}
pub struct MemoryBlockFetcher {
blocks: std::collections::HashMap<Cid, Block>,
}
impl MemoryBlockFetcher {
pub fn new() -> Self {
Self {
blocks: std::collections::HashMap::new(),
}
}
pub fn add_block(&mut self, block: Block) {
self.blocks.insert(*block.cid(), block);
}
pub fn add_blocks(&mut self, blocks: impl IntoIterator<Item = Block>) {
for block in blocks {
self.add_block(block);
}
}
}
impl Default for MemoryBlockFetcher {
fn default() -> Self {
Self::new()
}
}
impl BlockFetcher for MemoryBlockFetcher {
fn fetch(&self, cid: Cid) -> BoxFuture<'_, Result<Block>> {
let result = self
.blocks
.get(&cid)
.cloned()
.ok_or_else(|| Error::BlockNotFound(cid.to_string()));
Box::pin(async move { result })
}
}
pub struct BlockReader {
data: Bytes,
position: usize,
}
impl BlockReader {
pub fn new(block: &Block) -> Self {
Self {
data: block.data().clone(),
position: 0,
}
}
pub fn from_bytes(data: Bytes) -> Self {
Self { data, position: 0 }
}
pub fn remaining(&self) -> usize {
self.data.len() - self.position
}
pub fn is_empty(&self) -> bool {
self.position >= self.data.len()
}
pub fn len(&self) -> usize {
self.data.len()
}
}
impl Read for BlockReader {
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
if self.position >= self.data.len() {
return Ok(0);
}
let remaining = &self.data[self.position..];
let to_read = std::cmp::min(buf.len(), remaining.len());
buf[..to_read].copy_from_slice(&remaining[..to_read]);
self.position += to_read;
Ok(to_read)
}
}
pub struct AsyncBlockReader {
data: Bytes,
position: usize,
}
impl AsyncBlockReader {
pub fn new(block: &Block) -> Self {
Self {
data: block.data().clone(),
position: 0,
}
}
pub fn from_bytes(data: Bytes) -> Self {
Self { data, position: 0 }
}
pub fn remaining(&self) -> usize {
self.data.len() - self.position
}
pub fn is_empty(&self) -> bool {
self.position >= self.data.len()
}
pub fn len(&self) -> usize {
self.data.len()
}
}
impl AsyncRead for AsyncBlockReader {
fn poll_read(
mut self: Pin<&mut Self>,
_cx: &mut Context<'_>,
buf: &mut ReadBuf<'_>,
) -> Poll<io::Result<()>> {
if self.position >= self.data.len() {
return Poll::Ready(Ok(()));
}
let remaining = &self.data[self.position..];
let to_read = std::cmp::min(buf.remaining(), remaining.len());
buf.put_slice(&remaining[..to_read]);
self.position += to_read;
Poll::Ready(Ok(()))
}
}
#[allow(dead_code)]
enum DagReaderState {
Reading { data: Bytes, position: usize },
FetchingNext,
Done,
}
#[allow(dead_code)]
pub struct DagStreamReader<F: BlockFetcher> {
fetcher: std::sync::Arc<F>,
state: DagReaderState,
pending_links: VecDeque<DagLink>,
total_read: u64,
}
impl<F: BlockFetcher> DagStreamReader<F> {
pub fn new(fetcher: std::sync::Arc<F>, root_links: Vec<DagLink>) -> Self {
Self {
fetcher,
state: DagReaderState::FetchingNext,
pending_links: root_links.into(),
total_read: 0,
}
}
pub fn from_node(fetcher: std::sync::Arc<F>, node: &DagNode) -> Self {
if let Some(data) = &node.data {
Self {
fetcher,
state: DagReaderState::Reading {
data: Bytes::from(data.clone()),
position: 0,
},
pending_links: VecDeque::new(),
total_read: 0,
}
} else {
Self::new(fetcher, node.links.clone())
}
}
pub fn bytes_read(&self) -> u64 {
self.total_read
}
}
pub struct DagChunkStream<F: BlockFetcher> {
fetcher: std::sync::Arc<F>,
pending_links: VecDeque<DagLink>,
}
impl<F: BlockFetcher> DagChunkStream<F> {
pub fn new(fetcher: std::sync::Arc<F>, links: Vec<DagLink>) -> Self {
Self {
fetcher,
pending_links: links.into(),
}
}
pub async fn next_chunk(&mut self) -> Option<Result<Bytes>> {
loop {
let link = self.pending_links.pop_front()?;
match self.fetcher.fetch(link.cid.0).await {
Ok(block) => {
if block.cid().codec() == 0x55 {
return Some(Ok(block.data().clone()));
}
match crate::ipld::Ipld::from_dag_cbor(block.data()) {
Ok(ipld) => {
if let crate::ipld::Ipld::Map(map) = ipld {
if let Some(crate::ipld::Ipld::List(links)) = map.get("links") {
let mut new_links = Vec::new();
for link_ipld in links {
if let crate::ipld::Ipld::Map(link_map) = link_ipld {
if let (
Some(crate::ipld::Ipld::Link(cid)),
Some(crate::ipld::Ipld::Integer(size)),
) = (link_map.get("cid"), link_map.get("size"))
{
new_links.push(DagLink::new(cid.0, *size as u64));
}
}
}
for new_link in new_links.into_iter().rev() {
self.pending_links.push_front(new_link);
}
}
if let Some(crate::ipld::Ipld::Bytes(data)) = map.get("data") {
return Some(Ok(Bytes::from(data.clone())));
}
}
}
Err(e) => return Some(Err(e)),
}
}
Err(e) => return Some(Err(e)),
}
}
}
pub fn has_more(&self) -> bool {
!self.pending_links.is_empty()
}
}
pub async fn read_chunked_file<F: BlockFetcher>(fetcher: &F, root_cid: &Cid) -> Result<Vec<u8>> {
let root_block = fetcher.fetch(*root_cid).await?;
if root_block.cid().codec() == 0x55 {
return Ok(root_block.data().to_vec());
}
let root_ipld = crate::ipld::Ipld::from_dag_cbor(root_block.data())?;
let mut result = Vec::new();
let mut queue: VecDeque<crate::ipld::Ipld> = VecDeque::new();
queue.push_back(root_ipld);
while let Some(ipld) = queue.pop_front() {
if let crate::ipld::Ipld::Map(map) = ipld {
if let Some(crate::ipld::Ipld::Bytes(data)) = map.get("data") {
result.extend_from_slice(data);
}
if let Some(crate::ipld::Ipld::List(links)) = map.get("links") {
for link_ipld in links {
if let crate::ipld::Ipld::Map(link_map) = link_ipld {
if let Some(crate::ipld::Ipld::Link(cid)) = link_map.get("cid") {
let block = fetcher.fetch(cid.0).await?;
if block.cid().codec() == 0x55 {
result.extend_from_slice(block.data());
} else {
let child_ipld = crate::ipld::Ipld::from_dag_cbor(block.data())?;
queue.push_back(child_ipld);
}
}
}
}
}
}
}
Ok(result)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::chunking::{Chunker, ChunkingConfig};
use std::io::Read;
#[test]
fn test_block_reader() {
let block = Block::new(Bytes::from_static(b"Hello, World!")).unwrap();
let mut reader = BlockReader::new(&block);
let mut buf = [0u8; 5];
let n = reader.read(&mut buf).unwrap();
assert_eq!(n, 5);
assert_eq!(&buf, b"Hello");
let n = reader.read(&mut buf).unwrap();
assert_eq!(n, 5);
assert_eq!(&buf, b", Wor");
let n = reader.read(&mut buf).unwrap();
assert_eq!(n, 3);
assert_eq!(&buf[..3], b"ld!");
}
#[tokio::test]
async fn test_async_block_reader() {
use tokio::io::AsyncReadExt;
let block = Block::new(Bytes::from_static(b"Hello, World!")).unwrap();
let mut reader = AsyncBlockReader::new(&block);
let mut buf = Vec::new();
reader.read_to_end(&mut buf).await.unwrap();
assert_eq!(buf, b"Hello, World!");
}
#[tokio::test]
async fn test_memory_block_fetcher() {
let block = Block::new(Bytes::from_static(b"test data")).unwrap();
let cid = *block.cid();
let mut fetcher = MemoryBlockFetcher::new();
fetcher.add_block(block.clone());
let fetched = fetcher.fetch(cid).await.unwrap();
assert_eq!(fetched.data(), block.data());
}
#[tokio::test]
async fn test_read_single_block_file() {
let data = b"Hello, IPFS!";
let block = Block::new(Bytes::from_static(data)).unwrap();
let cid = *block.cid();
let mut fetcher = MemoryBlockFetcher::new();
fetcher.add_block(block);
let result = read_chunked_file(&fetcher, &cid).await.unwrap();
assert_eq!(result, data);
}
#[tokio::test]
async fn test_read_chunked_file() {
let config = ChunkingConfig::with_chunk_size(1024).unwrap();
let chunker = Chunker::with_config(config);
let data: Vec<u8> = (0..3000).map(|i| (i % 256) as u8).collect();
let chunked = chunker.chunk(&data).unwrap();
let mut fetcher = MemoryBlockFetcher::new();
fetcher.add_blocks(chunked.blocks.clone());
let result = read_chunked_file(&fetcher, &chunked.root_cid)
.await
.unwrap();
assert_eq!(result, data);
}
#[tokio::test]
async fn test_dag_chunk_stream() {
let block1 = Block::new(Bytes::from_static(b"chunk1")).unwrap();
let block2 = Block::new(Bytes::from_static(b"chunk2")).unwrap();
let mut fetcher = MemoryBlockFetcher::new();
fetcher.add_block(block1.clone());
fetcher.add_block(block2.clone());
let links = vec![
DagLink::new(*block1.cid(), 6),
DagLink::new(*block2.cid(), 6),
];
let mut stream = DagChunkStream::new(std::sync::Arc::new(fetcher), links);
let chunk1 = stream.next_chunk().await.unwrap().unwrap();
assert_eq!(chunk1.as_ref(), b"chunk1");
let chunk2 = stream.next_chunk().await.unwrap().unwrap();
assert_eq!(chunk2.as_ref(), b"chunk2");
assert!(stream.next_chunk().await.is_none());
}
}