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
- Close the connection with a protocol-error close code (1002); actix's Dispatcher does this automatically on ProtocolError.
- If writing a client, ensure you only emit opcodes 0,1,2,8,9,10 via the Codec rather than raw bytes.
- Check for an upstream proxy/CDN corrupting or re-framing the WebSocket stream.
- 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
- Only send frames via the Codec/Message API.
- Confirm Sec-WebSocket-Version is 13 at handshake.
- Investigate reserved opcodes as a sign of stream desync or a hostile peer.
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
- invalid control frame length
- Invalid opcode ( )
- unknown continuation fragment
- Invalid chunk body CR
- Invalid chunk body LF
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)