n0-computer/iroh · error · Error

InvalidFrame

InvalidFrame

Error message

Invalid frame encoding

What it means

Error::InvalidFrame from ServerToClientUrlOrEndpoint (health/status) frame decoding: from_bytes requires at least one byte to read the discriminant. An empty buffer cannot encode any health frame, so decoding fails immediately.

Solutions

  1. Check bytes.is_empty() before calling from_bytes and treat empty input as end-of-stream rather than a frame.
  2. Verify the framing layer reads the declared frame length fully before decoding.
  3. Inspect relay connectivity — repeated empty frames suggest a broken or closed connection.

Example fix

// before
let frame = HealthFrame::from_bytes(bytes)?;

// after
if bytes.is_empty() {
    return Ok(None); // end of stream / no frame
}
let frame = HealthFrame::from_bytes(bytes)?;
Defensive patterns

Strategy: validation

Validate before calling

fn decode_health(bytes: &Bytes) -> Option<HealthFrame> {
    if bytes.is_empty() { return None; }
    Some(HealthFrame::from_bytes(bytes.clone()).ok()?)
}

Type guard

fn has_frame(bytes: &Bytes) -> bool { !bytes.is_empty() }

Prevention

When it happens

Trigger: Decoding an empty Bytes buffer as a relay health/status frame — e.g. a zero-length message received from the relay or a caller slicing out an empty payload.

Common situations: Relay connection closing and delivering a truncated/empty final message; slicing bugs that pass an empty byte slice to from_bytes.

Understand the failure class

Background: "cannot parse invalid wire-format data", "cannot unmarshal", "failed unmarshalling": protobuf unmarshal errors explained — this error's family across 10 libraries.

Related errors


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

Appendix: source

Thrown at iroh-relay/src/protos/relay.rs:163

impl Status {
    #[cfg(feature = "server")]
    fn write_to<O: BufMut>(&self, mut dst: O) -> O {
        match self {
            Status::Healthy => dst.put_u8(0),
            Status::SameEndpointIdConnected => dst.put_u8(1),
            Status::RateLimited => dst.put_u8(2),
            Status::Unknown(discriminant) => dst.put_u8(*discriminant),
        }
        dst
    }

    #[cfg(feature = "server")]
    fn encoded_len(&self) -> usize {
        1
    }

    fn from_bytes(mut bytes: Bytes) -> Result<Self, Error> {
        ensure!(!bytes.is_empty(), Error::InvalidFrame);
        let discriminant = bytes.get_u8();
        match discriminant {
            0 => Ok(Self::Healthy),
            1 => Ok(Self::SameEndpointIdConnected),
            2 => Ok(Self::RateLimited),
            n => Ok(Self::Unknown(n)),
        }
    }
}

/// Messages that clients send to relays.
#[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub enum ClientToRelayMsg {
    /// Request from the client to the server to reply to the
    /// other side with a [`RelayToClientMsg::Pong`] with the given payload.
    Ping([u8; 8]),
    /// Reply to a [`RelayToClientMsg::Ping`] from a server

View on GitHub (pinned to 2b4de030ce)