protected override void Decode(IChannelHandlerContext context, IByteBuffer input, List <object> output) { // Discard all data received if closing handshake was received before. if (this.receivedClosingHandshake) { _ = input.SkipBytes(this.ActualReadableBytes); return; } switch (this.state) { case State.ReadingFirst: if (!input.IsReadable()) { return; } this.framePayloadLength = 0; // FIN, RSV, OPCODE byte b = input.ReadByte(); this.frameFinalFlag = (b & 0x80) != 0; this.frameRsv = (b & 0x70) >> 4; this.frameOpcode = b & 0x0F; #if DEBUG if (Logger.TraceEnabled) { Logger.DecodingWebSocketFrameOpCode(this.frameOpcode); } #endif this.state = State.ReadingSecond; goto case State.ReadingSecond; case State.ReadingSecond: if (!input.IsReadable()) { return; } // MASK, PAYLOAD LEN 1 b = input.ReadByte(); this.frameMasked = (b & 0x80) != 0; this.framePayloadLen1 = b & 0x7F; if (this.frameRsv != 0 && !_config.AllowExtensions) { this.ProtocolViolation_RSVNoExtensionNegotiated(context, input, this.frameRsv); return; } if (!_config.AllowMaskMismatch && _config.ExpectMaskedFrames != this.frameMasked) { this.ProtocolViolation_RecAFrameThatIsNotMaskedAsExected(context, input); return; } // control frame (have MSB in opcode set) if (this.frameOpcode > 7) { // control frames MUST NOT be fragmented if (!this.frameFinalFlag) { this.ProtocolViolation_FragmentedControlFrame(context, input); return; } // control frames MUST have payload 125 octets or less if (this.framePayloadLen1 > 125) { this.ProtocolViolation_ControlFrameWithPayloadLength125Octets(context, input); return; } switch (this.frameOpcode) { // close frame : if there is a body, the first two bytes of the // body MUST be a 2-byte unsigned integer (in network byte // order) representing a getStatus code case OpcodeClose: if (this.framePayloadLen1 == 1) { this.ProtocolViolation_RecCloseControlFrame(context, input); return; } break; case OpcodePing: case OpcodePong: break; // check for reserved control frame opcodes default: this.ProtocolViolation_ControlFrameUsingReservedOpcode(context, input, this.frameOpcode); return; } } else // data frame { switch (this.frameOpcode) { case OpcodeCont: case OpcodeText: case OpcodeBinary: break; // check for reserved data frame opcodes default: this.ProtocolViolation_DataFrameUsingReservedOpcode(context, input, this.frameOpcode); return; } uint uFragmentedFramesCount = (uint)this.fragmentedFramesCount; // check opcode vs message fragmentation state 1/2 if (0u >= uFragmentedFramesCount && this.frameOpcode == OpcodeCont) { this.ProtocolViolation_RecContionuationDataFrame(context, input); return; } // check opcode vs message fragmentation state 2/2 if (uFragmentedFramesCount > 0u && this.frameOpcode != OpcodeCont && this.frameOpcode != OpcodePing) { this.ProtocolViolation_RecNonContionuationDataFrame(context, input); return; } } this.state = State.ReadingSize; goto case State.ReadingSize; case State.ReadingSize: // Read frame payload length switch (this.framePayloadLen1) { case 126: if (input.ReadableBytes < 2) { return; } this.framePayloadLength = input.ReadUnsignedShort(); if (this.framePayloadLength < 126) { this.ProtocolViolation_InvalidDataFrameLength(context, input); return; } break; case 127: if (input.ReadableBytes < 8) { return; } this.framePayloadLength = input.ReadLong(); // TODO: check if it's bigger than 0x7FFFFFFFFFFFFFFF, Maybe // just check if it's negative? if (this.framePayloadLength < 65536) { this.ProtocolViolation_InvalidDataFrameLength(context, input); return; } break; default: this.framePayloadLength = this.framePayloadLen1; break; } var maxFramePayloadLength = _config.MaxFramePayloadLength; if (this.framePayloadLength > maxFramePayloadLength) { this.ProtocolViolation_MaxFrameLengthHasBeenExceeded(context, input, maxFramePayloadLength); return; } #if DEBUG if (Logger.TraceEnabled) { Logger.DecodingWebSocketFrameLength(this.framePayloadLength); } #endif this.state = State.MaskingKey; goto case State.MaskingKey; case State.MaskingKey: if (this.frameMasked) { if (input.ReadableBytes < 4) { return; } if (this.maskingKey is null) { this.maskingKey = new byte[4]; } _ = input.ReadBytes(this.maskingKey); } this.state = State.Payload; goto case State.Payload; case State.Payload: if (input.ReadableBytes < this.framePayloadLength) { return; } IByteBuffer payloadBuffer = null; try { payloadBuffer = ReadBytes(context.Allocator, input, ToFrameLength(this.framePayloadLength)); // Now we have all the data, the next checkpoint must be the next // frame this.state = State.ReadingFirst; // Unmask data if needed if (this.frameMasked) { this.Unmask(payloadBuffer); } // Processing ping/pong/close frames because they cannot be // fragmented switch (this.frameOpcode) { case OpcodePing: output.Add(new PingWebSocketFrame(this.frameFinalFlag, this.frameRsv, payloadBuffer)); payloadBuffer = null; return; case OpcodePong: output.Add(new PongWebSocketFrame(this.frameFinalFlag, this.frameRsv, payloadBuffer)); payloadBuffer = null; return; case OpcodeClose: this.receivedClosingHandshake = true; this.CheckCloseFrameBody(context, payloadBuffer); output.Add(new CloseWebSocketFrame(this.frameFinalFlag, this.frameRsv, payloadBuffer)); payloadBuffer = null; return; } // Processing for possible fragmented messages for text and binary // frames if (this.frameFinalFlag) { // Final frame of the sequence. Apparently ping frames are // allowed in the middle of a fragmented message if (this.frameOpcode != OpcodePing) { this.fragmentedFramesCount = 0; } } else { // Increment counter this.fragmentedFramesCount++; } // Return the frame switch (this.frameOpcode) { case OpcodeText: output.Add(new TextWebSocketFrame(this.frameFinalFlag, this.frameRsv, payloadBuffer)); payloadBuffer = null; return; case OpcodeBinary: output.Add(new BinaryWebSocketFrame(this.frameFinalFlag, this.frameRsv, payloadBuffer)); payloadBuffer = null; return; case OpcodeCont: output.Add(new ContinuationWebSocketFrame(this.frameFinalFlag, this.frameRsv, payloadBuffer)); payloadBuffer = null; return; default: ThrowNotSupportedException(this.frameOpcode); return; } } finally { _ = (payloadBuffer?.Release()); } case State.Corrupt: if (input.IsReadable()) { // If we don't keep reading Netty will throw an exception saying // we can't return null if no bytes read and state not changed. _ = input.ReadByte(); } return; default: ThrowHelper.ThrowException_FrameDecoder(); break; } }