| // Copyright 2024 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #ifndef NET_TEST_EMBEDDED_TEST_SERVER_WEBSOCKET_CONNECTION_H_ |
| #define NET_TEST_EMBEDDED_TEST_SERVER_WEBSOCKET_CONNECTION_H_ |
| |
| #include <stddef.h> |
| #include <stdint.h> |
| |
| #include <memory> |
| #include <optional> |
| #include <queue> |
| #include <string_view> |
| |
| #include "base/containers/span.h" |
| #include "base/memory/ref_counted.h" |
| #include "base/memory/scoped_refptr.h" |
| #include "base/sequence_checker.h" |
| #include "net/base/io_buffer.h" |
| #include "net/test/embedded_test_server/embedded_test_server.h" |
| #include "net/test/embedded_test_server/websocket_message_assembler.h" |
| #include "net/websockets/websocket_chunk_assembler.h" |
| #include "net/websockets/websocket_frame.h" |
| |
| namespace net { |
| |
| class StreamSocket; |
| |
| namespace test_server { |
| |
| class WebSocketHandler; |
| |
| class WebSocketConnection final : public base::RefCounted<WebSocketConnection> { |
| public: |
| WebSocketConnection(const WebSocketConnection&) = delete; |
| WebSocketConnection& operator=(const WebSocketConnection&) = delete; |
| |
| // Constructor initializes the WebSocket connection with a given socket and |
| // prepares for the WebSocket handshake by setting up necessary headers. |
| explicit WebSocketConnection(std::unique_ptr<StreamSocket> socket, |
| std::string_view sec_websocket_key, |
| EmbeddedTestServer* server); |
| |
| // Adds or replaces the response header with name `name`. Should only be |
| // called from WebSocketHandler::OnHandshake(). |
| void SetResponseHeader(std::string_view name, std::string_view value); |
| |
| // Send a text message. Can be called in OnHandshake(), in which case the |
| // message will be queued to be sent immediately after the response headers. |
| // Can be called at any time up until WebSocketHandler::OnClosingHandshake(), |
| // WebSocketConnection::StartClosingHandshake(), |
| // WebSocketConnection::DisconnectAfterAnyWritesDone() or |
| // WebSocketConnection::DisconnectImmediately() is called. |
| void SendTextMessage(std::string_view message); |
| |
| // Send a binary message. Can be called as with SendTextMessage(). |
| void SendBinaryMessage(base::span<const uint8_t> message); |
| |
| // Send a CLOSE frame with `code` and `message`. If `code` is std::nullopt |
| // then an empty CLOSE frame will be sent. Initiates a close handshake from |
| // the server side. |
| void StartClosingHandshake(std::optional<uint16_t> code, |
| std::string_view message); |
| |
| // Responds to a CLOSE frame received from the client. If `code` is |
| // std::nullopt then an empty CLOSE frame will be sent. |
| void RespondToCloseFrame(std::optional<uint16_t> code, |
| std::string_view message); |
| |
| // Send a PING frame. The payload is optional and can be omitted or included |
| // based on the application logic. |
| void SendPing(base::span<const uint8_t> payload); |
| |
| // Send a PONG frame. The payload is optional and can be omitted or included |
| // based on the application logic. |
| void SendPong(base::span<const uint8_t> payload); |
| |
| // Delete the handler, scheduling a disconnect after any pending writes are |
| // completed. |
| void DisconnectAfterAnyWritesDone(); |
| |
| // Sends `bytes` as-is directly on stream. Can be called from |
| // WebSocketHandler::OnHandshake() to send data before the normal |
| // response header. After OnHandshake() returns, can be used to send invalid |
| // WebSocket frames. |
| void SendRaw(base::span<const uint8_t> bytes); |
| |
| // Sends the handshake response after headers are set. |
| void SendHandshakeResponse(); |
| |
| // Set the WebSocketHandler instance for this connection. |
| void SetHandler(std::unique_ptr<WebSocketHandler> handler); |
| |
| private: |
| friend class base::RefCounted<WebSocketConnection>; |
| |
| // Enum to represent the current state of the WebSocket connection. |
| // For managing transitions between different phases of the WebSocket |
| // lifecycle. |
| enum class WebSocketState { |
| kHandshakeInProgress, |
| kOpen, |
| kWaitingForClientClose, |
| kDisconnectingSoon, |
| kClosed |
| }; |
| |
| ~WebSocketConnection(); |
| |
| // Internal function to immediately disconnect, deleting the handler and |
| // closing the socket. |
| void DisconnectImmediately(); |
| |
| // Internal function to reset the stream socket. |
| void ResetStreamSocket(); |
| |
| void PerformWrite() VALID_CONTEXT_REQUIRED(sequence_checker_); |
| void OnWriteComplete(int result) VALID_CONTEXT_REQUIRED(sequence_checker_); |
| void Read() VALID_CONTEXT_REQUIRED(sequence_checker_); |
| void OnReadComplete(int result) VALID_CONTEXT_REQUIRED(sequence_checker_); |
| |
| // Handles incoming WebSocket frames of different opcodes: text, binary, |
| // and continuation frames. Based on the frame's opcode and whether the |
| // frame is marked as final (`is_final`), the payload is processed and |
| // dispatched accordingly. `is_final` determines if the frame completes the |
| // current message. |
| void HandleFrame(WebSocketFrameHeader::OpCode opcode, |
| base::span<const char> payload, |
| bool is_final) VALID_CONTEXT_REQUIRED(sequence_checker_); |
| |
| // Internal function to handle sending buffers. |
| // `wait_for_handshake`: If true, the message will be queued until the |
| // handshake is complete. |
| void SendInternal(scoped_refptr<IOBufferWithSize> buffer, |
| bool wait_for_handshake); |
| |
| std::unique_ptr<StreamSocket> stream_socket_; |
| base::StringPairs response_headers_; |
| std::unique_ptr<WebSocketHandler> handler_; |
| |
| // Messages that are pending until the handshake is complete or until a |
| // previous write is completed. |
| std::queue<scoped_refptr<IOBufferWithSize>> pending_messages_; |
| |
| // Tracks pending bytes to be written, used for handling partial writes. |
| scoped_refptr<DrainableIOBuffer> pending_buffer_; |
| |
| scoped_refptr<IOBufferWithSize> read_buffer_; |
| |
| // The current state of the WebSocket connection, such as OPEN or CLOSED. |
| WebSocketState state_ = WebSocketState::kHandshakeInProgress; |
| |
| // Flag to indicate if a disconnect should be performed after write |
| // completion. |
| bool should_disconnect_after_write_ = false; |
| |
| // Assembles fragmented frames into full messages. |
| WebSocketMessageAssembler message_assembler_; |
| |
| // Handles assembling fragmented WebSocket frame chunks. |
| WebSocketChunkAssembler chunk_assembler_; |
| |
| // Subscription to the shutdown closure in EmbeddedTestServer. |
| base::CallbackListSubscription shutdown_subscription_; |
| |
| SEQUENCE_CHECKER(sequence_checker_); |
| }; |
| |
| // Methods to create specific WebSocket frames. |
| scoped_refptr<IOBufferWithSize> CreateTextFrame(std::string_view message); |
| scoped_refptr<IOBufferWithSize> CreateBinaryFrame( |
| base::span<const uint8_t> message); |
| scoped_refptr<IOBufferWithSize> CreateCloseFrame(std::optional<uint16_t> code, |
| std::string_view message); |
| scoped_refptr<IOBufferWithSize> CreatePingFrame( |
| base::span<const uint8_t> payload); |
| scoped_refptr<IOBufferWithSize> CreatePongFrame( |
| base::span<const uint8_t> payload); |
| |
| // Helper for building WebSocket frames (both data and control frames). |
| scoped_refptr<IOBufferWithSize> BuildWebSocketFrame( |
| base::span<const uint8_t> payload, |
| WebSocketFrameHeader::OpCode op_code); |
| |
| } // namespace test_server |
| |
| } // namespace net |
| |
| #endif // NET_TEST_EMBEDDED_TEST_SERVER_WEBSOCKET_CONNECTION_H_ |