Pumpkin-MC/Pumpkin · warning

Invalid UTF-8 sequence

Error message

Invalid UTF-8 sequence

What it means

Thrown by `Self::from_utf8` in the `PacketRead for String` impl when the bytes read for the string are not valid UTF-8. The protocol mandates UTF-8 strings, so undecodable bytes cause a hard error. This typically indicates data corruption or a stream desync rather than a normal runtime condition.

Solutions

  1. Check the stream for desync before this point (all prior fields parsed correctly?)
  2. Validate packet bytes with a UTF-8 validator or std::str::from_utf8 in a debug path to locate the offset
  3. Drop or reject the offending packet; resynchronize or reconnect
  4. Confirm the peer encodes strings as UTF-8 per the protocol version

Example fix

// before
let s: String = reader.read()?;
// after
if let Err(e) = String::read(&mut reader) {
    log::warn!("non-UTF-8 string in packet: {e}");
    return Err(PacketError::Malformed);
}
Defensive patterns

Strategy: try-catch

Validate before calling

// validate UTF-8 in debug builds at decode boundaries
debug_assert!(std::str::from_utf8(buf).is_ok(), "non-UTF-8 payload");

Type guard

fn is_utf8(b: &[u8]) -> bool { std::str::from_utf8(b).is_ok() }

Try / catch

match String::read(&mut reader) {
    Ok(s) => s,
    Err(e) => { log::warn!("utf8 decode failed: {e}"); return Err(PacketError::Malformed); }
}

Prevention

When it happens

Trigger: read() reads `len` bytes after a valid VarUInt prefix, but those bytes fail String::from_utf8 — corrupted stream, wrong length prefix, or non-UTF-8 encoding from the peer.

Common situations: Desynced packet stream reading binary payload bytes as a string; malicious packets with arbitrary bytes; reading a legacy/compressed payload incorrectly.

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 Pumpkin-MC/Pumpkin@8d4639e25a (2026-09-09). Data as JSON: /api/errors/9171f341ed096e8e. Report an issue: GitHub.

Appendix: source

Thrown at crates/pumpkin-protocol/src/serial/deserializer.rs:169

impl PacketRead for String {
    fn read<R: Read>(reader: &mut R) -> Result<Self, Error> {
        const MAX_STRING_LENGTH: usize = 32767;

        let len = VarUInt::read(reader)?.0 as usize;

        if len > MAX_STRING_LENGTH {
            return Err(Error::new(
                ErrorKind::InvalidData,
                format!("String length {len} exceeds maximum of {MAX_STRING_LENGTH}"),
            ));
        }

        let mut buf = vec![0u8; len];
        reader.read_exact(&mut buf)?;

        Self::from_utf8(buf)
            .map_err(|_| Error::new(ErrorKind::InvalidData, "Invalid UTF-8 sequence"))
    }
}

impl<T: PacketRead> PacketRead for Vec<T> {
    fn read<R: Read>(reader: &mut R) -> Result<Self, Error> {
        let len = VarUInt::read(reader)?.0 as usize;
        if len > 65536 {
            return Err(Error::new(
                ErrorKind::InvalidData,
                format!("Vector length {len} exceeds limit of 65536"),
            ));
        }
        let mut buf = Self::with_capacity(len.min(1024));
        for _ in 0..len {
            buf.push(T::read(reader)?);
        }
        Ok(buf)
    }

View on GitHub (pinned to 8d4639e25a)