# How to Open a Unidirectional QUIC Stream with Iroh

> Open a unidirectional QUIC stream with Iroh by calling conn.open_uni().await?. Get your QuicClient ready with the correct ALPN for one-way data transmission.

- Repository: [number zero/iroh](https://github.com/n0-computer/iroh)
- Tags: how-to-guide
- Published: 2026-06-25

---

**Call `conn.open_uni().await?` on a `noq::Connection`—obtained via Iroh's `QuicClient` configured with the `/iroh-qad/0` ALPN—to create a send-only stream for one-way data transmission.**

Iroh implements its QUIC transport on top of the **`noq`** library, supporting both bidirectional and unidirectional stream types. While bidirectional streams allow simultaneous read and write operations, unidirectional streams provide optimized one-way channels where the opener can only send data and the peer receives via `accept_uni()`.

## Establish the QUIC Connection

Before opening any stream, you must create a QUIC endpoint and negotiate a connection. According to the Iroh source code in **[`iroh-relay/src/quic.rs`](https://github.com/n0-computer/iroh/blob/main/iroh-relay/src/quic.rs)**, this involves three main components:

- **`QuicClient`** struct (lines 60-66): Holds the QUIC endpoint and TLS client configuration.
- **`create_conn`** method (lines 53-63): Constructs a `noq::Connection` to the remote address.
- **TLS Configuration**: Uses `make_dangerous_client_config()` with the Iroh-specific ALPN (`/iroh-qad/0`).

The connection setup is identical whether you intend to use bidirectional or unidirectional streams:

```rust
use std::net::SocketAddr;
use iroh_relay::quic::{QuicClient, ALPN_QUIC_ADDR_DISC};

// 1. Create a client endpoint bound to an OS-assigned port
let client_ep = noq::Endpoint::client("0.0.0.0:0".parse()?)?;

// 2. Configure TLS with Iroh's ALPN
let client_cfg = iroh_relay::tls::make_dangerous_client_config();

// 3. Wrap in QuicClient
let quic_client = QuicClient::new(client_ep, client_cfg);

// 4. Connect to remote server
let server_addr: SocketAddr = "127.0.0.1:4433".parse()?;
let conn = quic_client.create_conn(server_addr, "localhost").await?;

```

## Open a Unidirectional Send Stream

Once you have a valid `noq::Connection`, call **`open_uni()`** (as opposed to `open_bi()` used for bidirectional streams) to obtain a send-only stream. This method returns a stream that implements `AsyncWrite` but not `AsyncRead`, enforcing the one-way constraint at the type level.

Following the connection pattern shown in the `quic_endpoint_basic` test (lines 84-106), unidirectional streams are created as follows:

```rust
// Open a unidirectional stream (send-only for the opener)
let mut send_stream = conn.open_uni().await?;

// Write data - unidirectional streams do not support reading from the opener's side
send_stream.write_all(b"Telemetry data").await?;

// Explicitly signal the end of data to the peer
send_stream.finish().await?;

```

Unlike bidirectional streams where the same handle manages both directions, unidirectional streams require the peer to explicitly accept the incoming stream using **`accept_uni()`** on their connection object.

## Handle Incoming Unidirectional Streams

On the server side, within the connection handler referenced in `handle_connection` (lines 26-38 of **[`iroh-relay/src/quic.rs`](https://github.com/n0-computer/iroh/blob/main/iroh-relay/src/quic.rs)**), you accept unidirectional streams using the counterpart method:

```rust
// Server-side: accept a unidirectional stream from the client
let mut recv_stream = conn.accept_uni().await?;

// Read the incoming data
let mut buf = vec![0u8; 1024];
let n = recv_stream.read(&mut buf).await?;
println!("Received: {}", String::from_utf8_lossy(&buf[..n]));

```

The server treats stream closure as a normal shutdown when the client calls `finish()` or drops the stream, consistent with the graceful shutdown handling shown in the `handle_connection` implementation.

## Key Implementation Files

| File | Purpose |
|------|---------|
| **[`iroh-relay/src/quic.rs`](https://github.com/n0-computer/iroh/blob/main/iroh-relay/src/quic.rs)** | Contains `QuicClient`, connection creation logic, and the `quic_endpoint_basic` test demonstrating connection establishment. |
| **[`iroh-relay/src/quic.rs`](https://github.com/n0-computer/iroh/blob/main/iroh-relay/src/quic.rs) (lines 26-38)** | `handle_connection` function showing connection lifecycle management applicable to both stream types. |
| **[`iroh-relay/src/tls.rs`](https://github.com/n0-computer/iroh/blob/main/iroh-relay/src/tls.rs)** | Provides `make_dangerous_client_config()` for TLS setup required before stream creation. |

## Summary

- **Unidirectional streams** in Iroh are created via `conn.open_uni().await?` on a `noq::Connection` obtained through `QuicClient::create_conn()`.
- **Connection setup** requires proper TLS configuration using the `/iroh-qad/0` ALPN, as implemented in [`iroh-relay/src/quic.rs`](https://github.com/n0-computer/iroh/blob/main/iroh-relay/src/quic.rs) (lines 53-66).
- **One-way constraint**: The opener gets a write-only stream (`AsyncWrite`); the peer must use `accept_uni()` to receive a read-only stream (`AsyncRead`).
- **Cleanup**: Call `finish().await?` on the send stream to signal end-of-stream, then close the connection with `QUIC_ADDR_DISC_CLOSE_CODE` and `QUIC_ADDR_DISC_CLOSE_REASON`.

## Frequently Asked Questions

### What is the difference between `open_uni()` and `open_bi()` in Iroh?

**`open_uni()` creates a unidirectional stream** that allows the opener to only send data, while `open_bi()` creates a bidirectional stream supporting both send and receive operations on the same handle. Both methods are called on a `noq::Connection` object obtained through `QuicClient::create_conn()`.

### How does the server receive a unidirectional stream?

The server calls **`accept_uni().await?`** on the `noq::Connection` object to accept incoming unidirectional streams. This returns a receive-only stream that implements `AsyncRead`, allowing the server to consume data sent by the client until the stream is finished.

### Can I reuse a unidirectional stream for multiple messages?

Yes, you can call **`write_all()`** multiple times on the send stream before calling `finish()`. However, once `finish()` is called or the stream is dropped, the stream ID is consumed and cannot be reused. For subsequent communications, you must open a new stream via `open_uni()`.

### What ALPN does Iroh use for QUIC connections?

Iroh uses **`/iroh-qad/0`** (QUIC Address Discovery) as the Application-Layer Protocol Negotiation identifier, defined in the `ALPN_QUIC_ADDR_DISC` constant within the `iroh-relay` crate. This must be configured in the TLS client and server configs before calling `create_conn()`.