EpicGames/lore · error · anyhow::Error

Failed to decode protocol

Error message

Failed to decode protocol: {e:?}

What it means

The negotiated ALPN protocol bytes from the TLS handshake must be valid UTF-8; quinn stores them as raw bytes. If they are not, String::from_utf8 fails and get_protocol wraps the error with this message.

Solutions

  1. Fix the client to send ASCII/UTF-8 ALPN values
  2. Use String::from_utf8_lossy or bytes::String where possible to tolerate invalid input
  3. Keep ALPN registrations to standard ASCII protocol names

Example fix

// before
.map(String::from_utf8)
.transpose()
.map_err(|e| anyhow!("Failed to decode protocol: {e:?}"))?
// after
.map(|b| String::from_utf8_lossy(&b).into_owned())
Defensive patterns

Strategy: validation

Validate before calling

// validate ALPN values at registration time
fn valid_alpn(p: &[u8]) -> bool { std::str::from_utf8(p).map(|s| s.is_ascii()).unwrap_or(false) }

Try / catch

match get_protocol(&connection) {
    Err(e) if e.to_string().contains("Failed to decode protocol") => { warn!("invalid UTF-8 ALPN from client"); return Ok(()); }
    r => r?,
}

Prevention

When it happens

Trigger: A client (or middlebox) sends an ALPN value containing invalid UTF-8 bytes; the protocol field decodes as Some(bytes) but from_utf8 returns Err.

Common situations: Malicious or buggy clients probing the server; custom ALPN strings with non-ASCII characters; corrupted handshake data.

Understand the failure class

Background: "Invalid ... format", "must be in format X", "does not look like a ..." — invalid argument format errors across CLI tools and libraries — this error's family across 17 libraries.

Related errors


AI-assisted analysis of EpicGames/lore@074eb0b0d1 (2026-09-13). Data as JSON: /api/errors/19b32abd6a8ea082. Report an issue: GitHub.

Appendix: source

Thrown at lore-server/src/quic/quinn/quinn_server.rs:261

                .in_current_span(),
            )
        );
    }

    Ok(())
}

fn get_protocol(connection: &quinn::Connection) -> Result<String, anyhow::Error> {
    let handshake_data = connection
        .handshake_data()
        .and_then(|h| h.downcast::<HandshakeData>().ok())
        .ok_or(anyhow!("No handshake data"))?;

    handshake_data
        .protocol
        .map(String::from_utf8)
        .transpose()
        .map_err(|e| anyhow!("Failed to decode protocol: {e:?}"))?
        .ok_or(anyhow!("No protocol found on request"))
}

#[tracing::instrument(
    name = "urc-quic",
    skip_all,
    fields(connection_id, protocol, correlation_id, repository_id)
)]
async fn handle_conn(
    conn: quinn::Incoming,
    monitor: TaskMonitor,
    connection_metrics_interval: Duration,
    stream_handler_factory: Arc<Box<dyn StreamHandlerFactory>>,
) -> anyhow::Result<()> {
    let connection = conn.await?;

    let protocol = get_protocol(&connection)?;

View on GitHub (pinned to 074eb0b0d1)