actix/actix-web · error · ProtocolError

Invalid opcode ({})

Error message

Invalid opcode ({})

What it means

This error is `ws::ProtocolError::InvalidOpcode(u8)` from actix-http's WebSocket implementation. It means a WebSocket frame arrived whose opcode byte (the low 4 bits of the first frame byte) is not one of the opcodes defined by RFC 6455 (continuation 0x0, text 0x1, binary 0x2, close 0x8, ping 0x9, pong 0xA). actix-http's frame parser rejects such frames because it cannot interpret the payload correctly, so the connection is failed with a protocol error.

Source

Thrown at actix-http/src/ws/mod.rs:39

    dispatcher::Dispatcher,
    frame::Parser,
    proto::{hash_key, CloseCode, CloseReason, OpCode},
};

/// WebSocket protocol errors.
#[derive(Debug, Display, Error, From)]
pub enum ProtocolError {
    /// Received an unmasked frame from client.
    #[display("Received an unmasked frame from client")]
    UnmaskedFrame,

    /// Received a masked frame from server.
    #[display("Received a masked frame from server")]
    MaskedFrame,

    /// Encountered invalid opcode.
    #[display("Invalid opcode ({})", _0)]
    InvalidOpcode(#[error(not(source))] u8),

    // TODO(semver-major):
    // /// Received a frame with non-zero reserved bits.
    // #[display("Received a frame with non-zero reserved bits")]
    // InvalidReservedBits,
    //
    /// Invalid control frame length
    #[display("Invalid control frame length ({})", _0)]
    InvalidLength(#[error(not(source))] usize),

    // TODO(semver-major): use in Parser::try_parse_close_payload
    //
    // /// Invalid close status code.
    // #[display("Invalid close status code ({})", _0)]
    // InvalidCloseCode(#[error(not(source))] u16),
    //
    // /// Invalid UTF-8 close reason.
    // #[display("Invalid UTF-8 close reason")]

View on GitHub (pinned to c215607f4b)

Solutions

  1. Fix or replace the non-conformant peer/intermediary so it emits only RFC 6455 opcodes (0x0,0x1,0x2,0x8,0x9,0xA).
  2. Ensure any WebSocket extensions (e.g. permessage-deflate) are actually negotiated in the handshake before frames with extension-specific framing are sent.
  3. Verify TLS/transport integrity; a corrupting proxy or mis-framed stream can shift opcode bytes, so reset the connection and re-handshake.
  4. Log the offending opcode value from the error payload and compare against RFC 6455 Section 5.2 to identify which reserved opcode the peer emits.

Example fix

// before: treating every ProtocolError as a retryable bug
Err(ProtocolError::InvalidOpcode(op)) => panic!("bad opcode {}", op),
// after: it is a peer protocol violation; close the connection cleanly
Err(err @ ProtocolError::InvalidOpcode(_)) => {
    log::warn!("peer sent reserved opcode; closing: {err}");
    ws.close(Some(CloseReason::from(CloseCode::Protocol)))
}
Defensive patterns

Strategy: try-catch

Validate before calling

const VALID_OPCODES = new Set([0x0, 0x1, 0x2, 0x8, 0x9, 0xA]);
// before sending/forwarding a frame:
if (!VALID_OPCODES.has(frame.opcode)) throw new Error(`peer would send reserved opcode ${frame.opcode}`);

Type guard

fn is_valid_opcode(op: u8) -> bool {
    matches!(op, 0x0 | 0x1 | 0x2 | 0x8 | 0x9 | 0xA)
}

Try / catch

match result {
    Err(ProtocolError::InvalidOpcode(op)) => {
        log::warn!("reserved opcode {} from peer; closing", op);
        close_with(CloseCode::Protocol);
    }
    Err(e) => handle_protocol_error(e),
    Ok(msg) => process(msg),
}

Prevention

When it happens

Trigger: The peer sends a WebSocket frame whose 4-bit opcode is 0x3-0x7 (reserved non-control) or 0xB-0xF (reserved control), e.g. during Parser::parse of an incoming frame on a ws session.

Common situations: Talking to a non-conformant or hand-rolled WebSocket client/server, a broken intermediary/proxy corrupting frame headers, desynchronized framing after a payload-length parsing bug, or an implementation using private/reserved extensions without a negotiated extension.

Understand the failure class

Background: Invalid enum value errors: "Unknown type", "Invalid scope", "must be one of" — when a string is not on the library's allowed list — this error's family across 23 libraries.

Related errors


AI-assisted analysis of actix/actix-web@c215607f4b (2026-09-09). Data as JSON: /api/errors/58baf2ee482dbb82. Report an issue: GitHub.