actix/actix-web · error · ProtocolError

invalid opcode ( )

Error message

invalid opcode ({})

What it means

ProtocolError::InvalidOpcode(u8) is raised in frame.rs:44 when the low nibble of a WebSocket frame's first byte does not map to a valid opcode. OpCode::from (proto.rs:68) returns OpCode::Bad for any value outside {0,1,2,8,9,10}, and Parser::parse_metadata converts that into this error. Per RFC 6455 §11.8 opcodes 3-7 and 11-15 are reserved, so receiving one indicates a non-conformant or corrupt peer.

Solutions

  1. Close the connection with a protocol-error close code (1002); actix's Dispatcher does this automatically on ProtocolError.
  2. If writing a client, ensure you only emit opcodes 0,1,2,8,9,10 via the Codec rather than raw bytes.
  3. Check for an upstream proxy/CDN corrupting or re-framing the WebSocket stream.
  4. Verify the client and server agree on the WebSocket version (13) during the handshake.

Example fix

// before: hand-crafted frame with reserved opcode 0x03
let one = 0x03; // reserved opcode

// after: use the Codec / Message API which only emits valid opcodes
frame.write_message(Message::Text("hi".into()));
Defensive patterns

Strategy: try-catch

Try / catch

// When driving a WS session, treat ProtocolError as fatal and close
match frame_result {
    Err(actix_http::ws::ProtocolError::InvalidOpcode(b)) => {
        log::warn!("bad opcode {b:#x}, closing");
        close_with(CloseCode::Protocol);
    }
    Err(e) => close_on_protocol_error(e),
    Ok(frame) => handle(frame),
}

Prevention

When it happens

Trigger: A WebSocket peer sends a frame whose opcode nibble is 3-7 or 11-15 (reserved/future). Also possible if the byte stream is desynchronized so the parser reads a data byte as an opcode.

Common situations: Interoperating with a buggy or experimental WebSocket client/server; stream desync after an earlier masking/framing bug; a man-in-the-middle injecting garbage bytes; clients using reserved opcodes for custom extensions without negotiating them.

Related errors


AI-assisted analysis of actix/actix-web@4d435abc28 (2026-08-09). Data as JSON: /api/errors/7fd4a94463244c3c. Report an issue: GitHub.

Appendix: 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),

    /// Invalid control frame length
    #[display("invalid control frame length ({})", _0)]
    InvalidLength(#[error(not(source))] usize),

    /// Bad opcode.
    #[display("bad opcode")]
    BadOpCode,

    /// A payload reached size limit.
    #[display("payload reached size limit")]
    Overflow,

    /// Continuation has not started.
    #[display("continuation has not started")]
    ContinuationNotStarted,

    /// Received new continuation but it is already started.

View on GitHub (pinned to 4d435abc28)