Skip to content

Example using iroh Sender and Receiver #79

Description

@frankPairs

Hey everyone,

I am looking for using bao-tree in a project that I am developing using Iroh.

The project at the moment is quite simple: just sending files from one node to another. At the moment, I have a piece of code working, mainly using irpc, but I am missing the hashing part. I was checking iroh-blobs, and I saw that they are using this library, but for me it's not very clear how to implement it.

I was playing a bit with blake3 for doing the hashing, but after reading some of your docs, I think I am doing it wrong. I mainly have two functions:

pub async fn send_file(file: &mut File, tx: Sender<FileChunk>) -> Result<()> {
    let mut file_reader = BufReader::new(file);
    let mut buf = vec![0; FILE_CHUNK_SIZE];

    loop {
        tokio::select! {
           result = file_reader.read(&mut buf) => {
               match result {
                   Ok(bytes_read_len) => {
                        if bytes_read_len == 0 {
                            break;
                        }
                        let hash = blake3::hash(bytes_read);


                        tx.send(FileChunk {
                            data: bytes_read.to_vec(),
                            hash: hash.to_string()
                        }).await?;
                   },
                   Err(err) => {
                       bail!(err);
                   }
               }
           },
           _ = tx.closed() => {
               break;
           }
        }
    }

    Ok(())
}

send_file just sents a file over the network using a Sender object. As you can see, I am using blake3:hash to create a hash of every chunk, and sending it to the other node, but as far as I understood there is a better way to do it using bao_tree.

Same think with the receive_file function:

pub async fn receive_file(file: &mut File, mut rx: Receiver<FileChunk>) -> Result<()> {
    let mut file_writer = BufWriter::new(file);
    let mut hasher = Hasher::new();

    while let Ok(Some(chunk)) = rx.recv().await {
        let hash = blake3::hash(&chunk.data);

        if hash.to_string() != chunk.hash {
            let err_message = "UploadFile Error: Invalid hash";

            bail!(err_message);
        }

        file_writer
            .write(chunk.data.as_slice())
            .await
            .context("UploadFile Error: Failed to write data into file")?;

        hasher.update(&chunk.data);
    }

    file_writer
        .flush()
        .await
        .context("UploadFile Error: Failed to flush {:?} file")?;

    Ok(())
}

Here I am just generating a hash with the chunk of data sent by the client, and checking if it's the same. I suppose that here it also would be easier to use bao_tree.

Do you have any example or idea how it could be implement it? I would appreciate any guidance here.

Thanks a lot in advance.

Activity

  1. moved this to 🚑 Needs Triage in irohon Aug 23, 2026
  2. frankPairs commented on Aug 31, 2026

    @frankPairs
    Author

    Good evening! I found a implementation that works. This is the implementation for sending a file using irpc and bao_tree.

    From one side, I have a client node, which has a send_file function:

    pub async fn send_file(
        mut file: iroh_io::File,
        mut ob: PreOrderOutboard<BytesMut>,
        tx: Sender<FileChunk>,
    ) -> Result<()> {
        let mut start_range = 0;
        let size = ob.tree.size();
        let block_size_len = ob.tree.block_size().bytes() as u64;
    
        while start_range < size {
            let end_range = start_range + block_size_len;
            let ranges = ByteRanges::from(start_range..end_range);
    
            let ranges = round_up_to_chunks(&ranges);
    
            let mut buf: Vec<u8> = Vec::new();
    
            encode_ranges_validated(&mut file, &mut ob, &ranges, &mut buf).await?;
    
            tx.send(FileChunk {
                data: buf.clone(),
                ranges: ChunkRange {
                    start: start_range,
                    end: end_range,
                },
            })
            .await?;
    
            start_range += block_size_len
        }
    
        Ok(())
    }
    

    I made some changes based on the example from the official docs. As you can see, I encode the chunks and send them as soon as they are encoded.

    At the server node side, I implemented a receive_file function:

    pub async fn receive_file(
        mut file: iroh_io::File,
        msg: UploadFileMessage,
        mut rx: Receiver<FileChunk>,
    ) -> Result<String> {
        let mut ob = PreOrderOutboard {
            tree: BaoTree::new(
                msg.size,
                BlockSize::from_bytes(msg.block_size).unwrap_or(BlockSize::ZERO),
            ),
            root: blake3::Hash::from_hex(msg.hash.as_bytes())
                .context("Failed when decoding file hash")?,
            data: BytesMut::new(),
        };
    
        while let Ok(Some(chunk)) = rx.recv().await {
            let ranges = ByteRanges::from(chunk.ranges.start..chunk.ranges.end);
            let ranges = round_up_to_chunks(&ranges);
            let from_server = io::Cursor::new(chunk.data.as_slice());
    
            decode_ranges(from_server, ranges, &mut file, &mut ob).await?;
        }
    
        let hash = ob.root.to_string();
    
        Ok(hash)
    }
    

    Here we create a PreOrderOutbard object based on information sent by the client, which includes size and block size.

    Does it make sense, or it looks very hackish?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    • Status
      🚑 Needs Triage

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions