atuinsh/atuin · error · std::io::Error

frame exceeds maximum length

Error message

frame exceeds maximum length

What it means

Returned by read_frame in the pty-proxy protocol crate. Every frame starts with a 5-byte header: one frame-type byte plus a u32 big-endian payload length. If that length exceeds MAX_FRAME_LEN (1 MiB, protocol.rs:70), read_frame returns io::ErrorKind::InvalidData with this message rather than allocating a hostile or corrupt buffer. It almost always means the byte stream is not actually frame-encoded data, or the two endpoints disagree on the protocol.

Source

Thrown at crates/atuin-pty-proxy/src/protocol.rs:152

/// Read one frame. Returns `Ok(None)` on a clean EOF at a frame boundary.
///
/// Unknown frame types are returned as-is: the transport layer does not
/// decide policy (the server closes on unknown client frames; clients skip
/// unknown server frames for forward compatibility).
///
/// # Errors
///
/// Fails on EOF mid-frame, on a length above [`MAX_FRAME_LEN`], or on any
/// underlying read error.
pub fn read_frame(reader: &mut impl Read) -> io::Result<Option<(u8, Vec<u8>)>> {
    let mut header = [0u8; 5];
    if !read_exact_or_eof(reader, &mut header)? {
        return Ok(None);
    }
    let frame_type = header[0];
    let len = u32::from_be_bytes([header[1], header[2], header[3], header[4]]) as usize;
    if len > MAX_FRAME_LEN {
        return Err(io::Error::new(
            io::ErrorKind::InvalidData,
            "frame exceeds maximum length",
        ));
    }
    let mut payload = vec![0u8; len];
    reader.read_exact(&mut payload)?;
    Ok(Some((frame_type, payload)))
}

/// Fill `buf` completely. Returns `Ok(false)` on EOF before the first byte,
/// `Ok(true)` when full; EOF partway through is an [`io::ErrorKind::UnexpectedEof`].
fn read_exact_or_eof(reader: &mut impl Read, buf: &mut [u8]) -> io::Result<bool> {
    let mut filled = 0;
    while filled < buf.len() {
        match reader.read(&mut buf[filled..]) {
            Ok(0) => {
                if filled == 0 {
                    return Ok(false);

View on GitHub (pinned to 202f6ad98e)

Solutions

  1. Verify both endpoints run the same atuin/pty-proxy version and speak the V2 frame protocol
  2. Confirm the reader is attached to the correct endpoint and that the peer sends the magic greeting before frames
  3. Treat the connection as poisoned: close it and reconnect rather than trying to resync mid-stream
  4. If writing a sender, ensure payloads are chunked under 1 MiB (encode_frame already asserts this on the write side)

Example fix

// before
let (frame_type, payload) = read_frame(&mut stream)?.expect("frame");

// after
match read_frame(&mut stream)? {
    Some((frame_type, payload)) => { /* handle */ }
    None => { /* clean EOF at frame boundary */ }
}
// and on write: assert payload.len() <= MAX_FRAME_LEN before encode_frame
Defensive patterns

Strategy: try-catch

Validate before calling

// Before reading frames, confirm the peer speaks V2 by sniffing the magic greeting
let mut first = [0u8; 4];
let n = read_with_timeout(&mut stream, &mut first)?;
if classify_greeting(&first[..n]) == Greeting::Legacy {
    // do not feed this stream to read_frame
}

Try / catch

match read_frame(&mut stream) {
    Ok(Some(frame)) => { /* handle */ }
    Ok(None) => { /* clean close */ }
    Err(e) if e.kind() == std::io::ErrorKind::InvalidData => {
        // length prefix > 1 MiB: stream is desynced or wrong protocol.
        // Do NOT continue reading; close and reconnect.
    }
    Err(e) => return Err(e.into()),
}

Prevention

When it happens

Trigger: Calling read_frame on a socket/stream carrying something other than pty-proxy frames (raw TLS, HTTP, shell output); a desync where a previous frame was misparsed so the reader is now interpreting payload bytes as a header; a version mismatch where one side emits a different header layout; a malicious peer writing a crafted length prefix.

Common situations: Connecting the proxy client to the wrong port or a TLS-enabled endpoint; a legacy peer (the post-accept greeting/classify_greeting distinguishes V2 from legacy) that never speaks frames; upgrading one side of the proxy pair and not the other; feeding a captured/truncated pcap replay into the parser.

Related errors


AI-assisted analysis of atuinsh/atuin@202f6ad98e (2026-08-16). Data as JSON: /api/errors/d92e9529f23ed268. Report an issue: GitHub.