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
- Fix or replace the non-conformant peer/intermediary so it emits only RFC 6455 opcodes (0x0,0x1,0x2,0x8,0x9,0xA).
- Ensure any WebSocket extensions (e.g. permessage-deflate) are actually negotiated in the handshake before frames with extension-specific framing are sent.
- Verify TLS/transport integrity; a corrupting proxy or mis-framed stream can shift opcode bytes, so reset the connection and re-handshake.
- 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
- Only interoperate with RFC 6455-compliant WebSocket implementations
- Never use reserved opcodes for private signaling; use ping payloads or an application-level field
- Validate any custom proxy/intermediary preserves frame bytes exactly
- Log the opcode value on failure to identify the offending peer quickly
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
- invalid opcode ({})
- invalid control frame length ({})
- unknown continuation fragment: {}
- Invalid control frame length ({})
- Received a frame with non-zero reserved bits
AI-assisted analysis of actix/actix-web@c215607f4b (2026-09-09).
Data as JSON: /api/errors/58baf2ee482dbb82.
Report an issue: GitHub.