From ad985f322796d25ca85a0c1ab02d664c8a8ad57f Mon Sep 17 00:00:00 2001 From: Andrew Kaster Date: Thu, 20 Feb 2025 04:16:23 -0700 Subject: [PATCH] LibWebSocket: Only call send() once on WebSocketImpl The previous implementation would call send a half-dozen times when sending each frame of WebSocket data. This is excessive, especially since we need to allocate a new buffer for the payload in order to mask it anyway. Let's just allocate one buffer up front, and send all the completed data at the end of the method --- Libraries/LibWebSocket/WebSocket.cpp | 37 +++++++++++++++++----------- 1 file changed, 23 insertions(+), 14 deletions(-) diff --git a/Libraries/LibWebSocket/WebSocket.cpp b/Libraries/LibWebSocket/WebSocket.cpp index f1f484057d7..6d59aaf1a5e 100644 --- a/Libraries/LibWebSocket/WebSocket.cpp +++ b/Libraries/LibWebSocket/WebSocket.cpp @@ -534,8 +534,13 @@ void WebSocket::send_frame(WebSocket::OpCode op_code, ReadonlyBytes payload, boo { VERIFY(m_impl); VERIFY(m_state == WebSocket::InternalState::Open); + + ByteBuffer buf = MUST(ByteBuffer::create_uninitialized(1 + 9 + 4 + payload.size())); + size_t offset = 0; + u8 frame_head[1] = { (u8)((is_final ? 0x80 : 0x00) | ((u8)(op_code) & 0xf)) }; - m_impl->send(ReadonlyBytes(frame_head, 1)); + buf.overwrite(offset, frame_head, 1); + offset += 1; // Section 5.1 : a client MUST mask all frames that it sends to the server bool has_mask = true; // FIXME: If the payload has a size > size_t max on a 32-bit platform, we could @@ -555,7 +560,8 @@ void WebSocket::send_frame(WebSocket::OpCode op_code, ReadonlyBytes payload, boo (u8)((payload.size() >> 8) & 0xff), (u8)((payload.size() >> 0) & 0xff), }; - m_impl->send(ReadonlyBytes(payload_length, 9)); + buf.overwrite(offset, payload_length, 9); + offset += 9; } else { u8 payload_length[9] = { (u8)((has_mask ? 0x80 : 0x00) | 127), @@ -568,7 +574,8 @@ void WebSocket::send_frame(WebSocket::OpCode op_code, ReadonlyBytes payload, boo (u8)((payload.size() >> 8) & 0xff), (u8)((payload.size() >> 0) & 0xff), }; - m_impl->send(ReadonlyBytes(payload_length, 9)); + buf.overwrite(offset, payload_length, 9); + offset += 9; } } else if (payload.size() >= 126) { // Send (the 'mask' flag + 126) + the 2-byte payload length @@ -577,13 +584,15 @@ void WebSocket::send_frame(WebSocket::OpCode op_code, ReadonlyBytes payload, boo (u8)((payload.size() >> 8) & 0xff), (u8)((payload.size() >> 0) & 0xff), }; - m_impl->send(ReadonlyBytes(payload_length, 3)); + buf.overwrite(offset, payload_length, 3); + offset += 3; } else { // Send the mask flag + the payload in a single byte u8 payload_length[1] = { (u8)((has_mask ? 0x80 : 0x00) | (u8)(payload.size() & 0x7f)), }; - m_impl->send(ReadonlyBytes(payload_length, 1)); + buf.overwrite(offset, payload_length, 1); + offset += 1; } if (has_mask) { // Section 10.3 : @@ -591,22 +600,22 @@ void WebSocket::send_frame(WebSocket::OpCode op_code, ReadonlyBytes payload, boo // > that cannot be predicted by end applications that provide data u8 masking_key[4]; Crypto::fill_with_secure_random(masking_key); - m_impl->send(ReadonlyBytes(masking_key, 4)); + buf.overwrite(offset, masking_key, 4); + offset += 4; // don't try to send empty payload if (payload.size() == 0) return; // Mask the payload - auto buffer_result = ByteBuffer::create_uninitialized(payload.size()); - if (!buffer_result.is_error()) { - auto& masked_payload = buffer_result.value(); - for (size_t i = 0; i < payload.size(); ++i) { - masked_payload[i] = payload[i] ^ (masking_key[i % 4]); - } - m_impl->send(masked_payload); + auto masked_payload = buf.span().slice(offset, payload.size()); + for (size_t i = 0; i < payload.size(); ++i) { + masked_payload[i] = payload[i] ^ (masking_key[i % 4]); } + offset += payload.size(); } else if (payload.size() > 0) { - m_impl->send(payload); + buf.overwrite(offset, payload.data(), payload.size()); + offset += payload.size(); } + m_impl->send(buf.span().slice(0, offset)); } void WebSocket::fatal_error(WebSocket::Error error)