How to Open a Bidirectional Stream on an Iroh Connection

To open a bidirectional stream on an Iroh connection, call Connection::open_bi and await the resulting future, which resolves to a tuple containing a SendStream and RecvStream enabling simultaneous two-way data transfer.

Iroh provides a QUIC-based networking stack that enables fast, reliable peer-to-peer communication. Opening a bidirectional stream is the standard way to exchange data bidirectionally over a single Iroh connection. This guide explains how to use the open_bi method in the n0-computer/iroh repository to establish concurrent read-write streams.

Understanding Bidirectional Streams in Iroh

What is a Bidirectional Stream?

A bidirectional stream is a pair of linked stream objects that allow data to flow in both directions over the same stream identifier. According to the Iroh source code, this consists of a SendStream for writing data and a RecvStream for reading incoming data from the peer.

QUIC Foundation

Iroh’s networking layer is built on top of QUIC via the quinn crate. In iroh/src/endpoint/connection.rs, the Connection struct abstracts the underlying QUIC implementation to provide a safe, async API for stream management.

Using Connection::open_bi

The primary API for initiating bidirectional communication is the open_bi method defined on the Connection struct.

In iroh/src/endpoint/connection.rs at lines 822–834, the open_bi method is documented to return an OpenBi<'_> future. When awaited, this future resolves to a tuple (SendStream, RecvStream). The SendStream handles outgoing data transmission, while the RecvStream manages incoming data from the peer.

Critical Implementation Detail

A crucial behavior is documented at lines 845–848 of the same file: the initiating peer must write to the SendStream before the remote side can successfully complete accept_bi. If you await the RecvStream without writing anything first, the acceptance will block indefinitely.

Complete Implementation Example

Here is a practical example demonstrating how to open a bidirectional stream, send data, and receive a response:

use iroh::endpoint::{Endpoint, Connection};
use tokio::io::{AsyncWriteExt, AsyncReadExt};

#[tokio::main]
async fn main() -> anyhow::Result<()> {
    // Build an endpoint (client or server) – see the `Endpoint` docs.
    let endpoint = Endpoint::client().await?;          // or `Endpoint::server(..)` for a listener
    // Connect to a remote peer – replace with the peer's address.
    let conn: Connection = endpoint.connect(remote_addr).await?;

    // ---------- Open a bidirectional stream ----------
    // `open_bi` returns a future that resolves to (send, recv).
    let (mut send, mut recv) = conn.open_bi().await?;

    // Write some data to the peer.
    send.write_all(b"hello iroh!").await?;
    // It's important to flush / close the send side if you expect the peer
    // to read the data before you start reading.
    send.finish().await?;

    // Read the peer’s response.
    let mut peer_buf = Vec::new();
    recv.read_to_end(&mut peer_buf).await?;
    println!("Received: {}", String::from_utf8_lossy(&peer_buf));

    Ok(())
}

This pattern appears in the repository’s example programs. The echo example in iroh/examples/echo.rs opens a bidirectional stream, writes a payload, and reads the echoed data back, providing a fully working demonstration of this API.

Stream Limits and Configuration

The number of concurrent bidirectional streams is constrained by the QUIC transport configuration. Specifically, the limit is controlled by QuicTransportConfig::max_concurrent_bidirectional_streams. You can open multiple concurrent streams up to this configured limit, with each stream operating independently. The underlying stream definitions and QUIC-specific plumbing are located in iroh/src/endpoint/quic.rs.

Summary

  • Use Connection::open_bi: Call this method on an active Connection to initiate a bidirectional stream as implemented in iroh/src/endpoint/connection.rs.
  • Await the tuple: The method returns a future that resolves to (SendStream, RecvStream) for full-duplex communication.
  • Write before read: Always write data to the SendStream before awaiting the RecvStream to prevent blocking the remote peer's accept_bi call, as documented at lines 845–848.
  • Reference implementations: Study iroh/examples/echo.rs for working patterns and iroh/src/endpoint/quic.rs for stream type definitions.
  • Respect limits: Monitor max_concurrent_bidirectional_streams in your transport configuration to avoid exceeding connection limits.

Frequently Asked Questions

What is the difference between open_bi and accept_bi?

open_bi initiates a new bidirectional stream from the local peer to the remote peer, while accept_bi listens for incoming stream requests from the remote side. The initiator must write data to the SendStream before the receiver can successfully complete accept_bi and begin reading.

Why does my bidirectional stream block when reading?

According to the Iroh source code at lines 845–848 of iroh/src/endpoint/connection.rs, the receiving side of a bidirectional stream will block if the initiating peer has not written any data to its SendStream. Ensure you write to the stream before attempting to read from the corresponding RecvStream.

How many bidirectional streams can I open on one connection?

The number is limited by the QuicTransportConfig::max_concurrent_bidirectional_streams setting. You can create any number of concurrent bidirectional streams up to this configured limit, subject to the underlying QUIC transport constraints.

Where can I find working examples of bidirectional streams in Iroh?

The iroh/examples/echo.rs file in the n0-computer/iroh repository demonstrates a complete working implementation. It opens a bidirectional stream, transmits data, and processes the response, serving as the canonical reference for stream usage patterns.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →