n0-computer/iroh · error · Error

UnexpectedFrameType

UnexpectedFrameType

Error message

Unexpected tag, got {frame_type:?}, but expected one of {expected_types:?}

What it means

Error::UnexpectedFrameType is raised by read_frame when the frame tag read from the wire is not among the expected_types the caller passed. Both client-side and server-side handshake readers use this to enforce which frame types are valid at a given protocol stage. It usually indicates protocol desync, corruption, or talking to a non-iroh/incompatible peer.

Solutions

  1. Ensure client and relay use compatible protocol versions (same frame-type encoding).
  2. Check that no proxy/middlebox is corrupting or buffering the websocket stream.
  3. Confirm the peer is actually an iroh relay speaking the expected protocol on that port.
  4. Log the received frame_type versus expected_types to identify the desync point.
Defensive patterns

Strategy: try-catch

Try / catch

// Rust
match read_frame(&mut stream, &[FrameType::Ping, FrameType::Pong]).await {
    Err(err @ Error::UnexpectedFrameType { frame_type, expected_types }) => {
        tracing::error!(?frame_type, ?expected_types, "protocol desync — reconnecting");
        reconnect().await;
    }
    other => other?,
}

Prevention

When it happens

Trigger: Handshake read_frame called with expected frame types (e.g. [Ping, Pong]) but the stream delivers another frame type, such as the client sending data before handshake completion or receiving a server frame out of order.

Common situations: Version-mismatched client and relay speaking different frame layouts; corrupted or interleaved streams; websocket framing gone wrong through a proxy; buggy custom code writing frames out of order.

Related errors


AI-assisted analysis of n0-computer/iroh@2b4de030ce (2026-09-08). Data as JSON: /api/errors/4c0cc301c9e4f794. Report an issue: GitHub.

Appendix: source

Thrown at iroh-relay/src/protos/handshake.rs:586

    io.flush()
        .await
        .map_err(|err| e!(Error::Websocket, anyerr!(err)))?;
    Ok(())
}

async fn read_frame(
    io: &mut impl BytesStreamSink,
    expected_types: &[FrameType],
) -> Result<(FrameType, Bytes), Error> {
    let mut payload = io
        .try_next()
        .await
        .map_err(|err| e!(Error::Websocket, anyerr!(err)))?
        .ok_or_else(|| e!(Error::UnexpectedEnd))?;

    let frame_type = FrameType::from_bytes(&mut payload)?;
    trace!(?frame_type, "Reading frame");
    ensure!(
        expected_types.contains(&frame_type),
        Error::UnexpectedFrameType {
            frame_type,
            expected_types: expected_types.to_vec()
        }
    );

    Ok((frame_type, payload))
}

fn deserialize_frame<F: Frame + serde::de::DeserializeOwned>(frame: Bytes) -> Result<F, Error> {
    postcard::from_bytes(&frame).map_err(|err| {
        e!(Error::DeserializationError {
            frame_type: F::TAG,
            source: anyerr!(err)
        })
    })
}

View on GitHub (pinned to 2b4de030ce)