{"record":{"id":"7fd4a94463244c3c","repo":"actix/actix-web","slug":"invalid-opcode","errorCode":null,"errorMessage":"invalid opcode ({})","messagePattern":"invalid opcode \\((.+?)\\)","errorType":"exception","errorClass":"ProtocolError","httpStatus":null,"severity":"error","filePath":"actix-http/src/ws/mod.rs","lineNumber":39,"sourceCode":"    dispatcher::Dispatcher,\n    frame::Parser,\n    proto::{hash_key, CloseCode, CloseReason, OpCode},\n};\n\n/// WebSocket protocol errors.\n#[derive(Debug, Display, Error, From)]\npub enum ProtocolError {\n    /// Received an unmasked frame from client.\n    #[display(\"received an unmasked frame from client\")]\n    UnmaskedFrame,\n\n    /// Received a masked frame from server.\n    #[display(\"received a masked frame from server\")]\n    MaskedFrame,\n\n    /// Encountered invalid opcode.\n    #[display(\"invalid opcode ({})\", _0)]\n    InvalidOpcode(#[error(not(source))] u8),\n\n    /// Invalid control frame length\n    #[display(\"invalid control frame length ({})\", _0)]\n    InvalidLength(#[error(not(source))] usize),\n\n    /// Bad opcode.\n    #[display(\"bad opcode\")]\n    BadOpCode,\n\n    /// A payload reached size limit.\n    #[display(\"payload reached size limit\")]\n    Overflow,\n\n    /// Continuation has not started.\n    #[display(\"continuation has not started\")]\n    ContinuationNotStarted,\n\n    /// Received new continuation but it is already started.","sourceCodeStart":21,"sourceCodeEnd":57,"githubUrl":"https://github.com/actix/actix-web/blob/4d435abc281842f3cbee165b6cde739e001d3a25/actix-http/src/ws/mod.rs#L21-L57","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before: hand-crafted frame with reserved opcode 0x03\nlet one = 0x03; // reserved opcode\n\n// after: use the Codec / Message API which only emits valid opcodes\nframe.write_message(Message::Text(\"hi\".into()));","handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"// When driving a WS session, treat ProtocolError as fatal and close\nmatch frame_result {\n    Err(actix_http::ws::ProtocolError::InvalidOpcode(b)) => {\n        log::warn!(\"bad opcode {b:#x}, closing\");\n        close_with(CloseCode::Protocol);\n    }\n    Err(e) => close_on_protocol_error(e),\n    Ok(frame) => handle(frame),\n}","preventionTips":["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."],"tags":["websocket","protocol","actix-http","framing"],"backgroundTag":null,"analyzedSha":"4d435abc281842f3cbee165b6cde739e001d3a25","analyzedAt":"2026-08-09T01:01:40.926Z","contentChangedAt":"2026-08-09T01:01:40.926Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}