blob: 345b6668096ce2720d2ea429d5d0d97281597d3b [file] [edit]
/*
* Copyright (C) 2019 Igalia, S.L.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public License
* along with this library; see the file COPYING.LIB. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#include "config.h"
#include "SocketConnection.h"
#include <cstring>
#include <gio/gio.h>
#include <wtf/ByteOrder.h>
#include <wtf/CheckedArithmetic.h>
#include <wtf/FastMalloc.h>
#include <wtf/Logging.h>
#include <wtf/RunLoop.h>
#include <wtf/glib/GSpanExtras.h>
#include <wtf/text/MakeString.h>
namespace WTF {
static const unsigned defaultBufferSize = 4096;
SocketConnection::SocketConnection(GRefPtr<GSocketConnection>&& connection, const MessageHandlers& messageHandlers, gpointer userData)
: m_connection(WTF::move(connection))
, m_messageHandlers(messageHandlers)
, m_userData(userData)
{
m_readBuffer.reserveInitialCapacity(defaultBufferSize);
m_writeBuffer.reserveInitialCapacity(defaultBufferSize);
auto* socket = g_socket_connection_get_socket(m_connection.get());
g_socket_set_blocking(socket, FALSE);
m_readMonitor.start(socket, G_IO_IN, RunLoop::currentSingleton(), nullptr, [this, protectedThis = Ref { *this }](GIOCondition condition) -> gboolean {
if (isClosed())
return G_SOURCE_REMOVE;
if (condition & G_IO_HUP || condition & G_IO_ERR || condition & G_IO_NVAL) {
didClose();
return G_SOURCE_REMOVE;
}
ASSERT(condition & G_IO_IN);
return read();
});
}
SocketConnection::~SocketConnection() = default;
bool SocketConnection::didReceiveInvalidMessage(const CString& message)
{
RELEASE_LOG_FAULT_WITH_PAYLOAD(Process, "Received invalid message (%s), closing SocketConnection", message);
close();
m_readBuffer.shrink(0);
return false;
}
bool SocketConnection::read()
{
while (true) {
size_t previousBufferSize = m_readBuffer.size();
if (m_readBuffer.capacity() - previousBufferSize <= 0)
m_readBuffer.reserveCapacity(m_readBuffer.capacity() + defaultBufferSize);
m_readBuffer.grow(m_readBuffer.capacity());
GUniqueOutPtr<GError> error;
auto bufferSpan = m_readBuffer.mutableSpan().subspan(previousBufferSize);
auto bytesRead = g_socket_receive(g_socket_connection_get_socket(m_connection.get()), bufferSpan.data(), bufferSpan.size(), nullptr, &error.outPtr());
if (bytesRead == -1) {
if (g_error_matches(error.get(), G_IO_ERROR, G_IO_ERROR_WOULD_BLOCK)) {
m_readBuffer.shrink(previousBufferSize);
break;
}
g_warning("Error reading from socket connection: %s\n", error->message);
didClose();
return G_SOURCE_REMOVE;
}
if (!bytesRead) {
didClose();
return G_SOURCE_REMOVE;
}
m_readBuffer.shrink(previousBufferSize + bytesRead);
while (readMessage()) { }
if (isClosed())
return G_SOURCE_REMOVE;
}
return G_SOURCE_CONTINUE;
}
enum {
ByteOrderLittleEndian = 1 << 0
};
typedef uint8_t MessageFlags;
// The smallest possible message has no parameters, one character for the message
// name (an empty name is invalid), and a null terminator at the end of the name.
static auto constexpr MinimumMessageBodySize = 2;
static auto constexpr MaximumMessageBodySize = 512 * MB;
static inline bool messageIsByteSwapped(MessageFlags flags)
{
#if G_BYTE_ORDER == G_LITTLE_ENDIAN
return !(flags & ByteOrderLittleEndian);
#else
return (flags & ByteOrderLittleEndian);
#endif
}
#define MESSAGE_CHECK(assertion, message) do { \
if (!(assertion)) [[unlikely]] \
return didReceiveInvalidMessage(message); \
} while (0)
bool SocketConnection::readMessage()
{
// Ensure we have enough data to read the message size.
if (m_readBuffer.size() < sizeof(uint32_t))
return false;
auto messageData = m_readBuffer.span();
const size_t bodySize = ntohl(consumeAndReinterpretCastTo<uint32_t>(messageData));
MESSAGE_CHECK(bodySize >= MinimumMessageBodySize, "message body too small");
MESSAGE_CHECK(bodySize <= MaximumMessageBodySize, "message body too big");
// Ensure the whole message has been read from the socket.
const size_t messageSize = sizeof(uint32_t) + sizeof(MessageFlags) + bodySize;
if (m_readBuffer.size() < messageSize) {
m_readBuffer.reserveCapacity(messageSize);
return false;
}
const auto flags = consumeAndReinterpretCastTo<MessageFlags>(messageData);
// Ensure that the span covers only the first message in the read buffer, and
// that parsing the message does not step onto the next one in the buffer.
messageData = messageData.first(bodySize);
const auto nullIndex = find(messageData, '\0');
MESSAGE_CHECK(nullIndex != notFound, "message name delimiter missing");
const CString messageName(consumeSpan(messageData, nullIndex));
ASSERT(messageData.front() == '\0');
skip(messageData, 1);
const auto it = m_messageHandlers.find(messageName);
if (it != m_messageHandlers.end()) {
GRefPtr<GVariant> parameters;
if (!it->value.first.isNull()) {
GUniquePtr<GVariantType> variantType(g_variant_type_new(it->value.first.data()));
parameters = g_variant_new_from_data(variantType.get(), messageData.data(), messageData.size(), FALSE, nullptr, nullptr);
if (messageIsByteSwapped(flags))
parameters = adoptGRef(g_variant_byteswap(parameters.get()));
}
it->value.second(*this, parameters.get(), m_userData);
if (isClosed())
return false;
}
if (m_readBuffer.size() > messageSize) {
memmoveSpan(m_readBuffer.mutableSpan(), m_readBuffer.subspan(messageSize));
m_readBuffer.shrink(m_readBuffer.size() - messageSize);
} else
m_readBuffer.shrink(0);
if (m_readBuffer.size() < defaultBufferSize)
m_readBuffer.shrinkCapacity(defaultBufferSize);
return true;
}
#undef MESSAGE_CHECK
void SocketConnection::sendMessage(const CString& messageName, GVariant* parameters)
{
ASSERT(!messageName.isEmpty());
GRefPtr<GVariant> adoptedParameters = parameters;
size_t parametersSize = parameters ? g_variant_get_size(parameters) : 0;
const auto messageNameAndTerminator = messageName.spanIncludingNullTerminator();
CheckedUint32 bodySize = messageNameAndTerminator.size();
bodySize += parametersSize;
if (bodySize.hasOverflowed() || bodySize > MaximumMessageBodySize) [[unlikely]] {
g_warning("Trying to send message '%s' with invalid too long body", messageName.data());
return;
}
ASSERT(bodySize >= MinimumMessageBodySize);
size_t previousBufferSize = m_writeBuffer.size();
m_writeBuffer.grow(previousBufferSize + sizeof(uint32_t) + sizeof(MessageFlags) + bodySize.value());
auto messageData = m_writeBuffer.mutableSpan().subspan(previousBufferSize);
consumeAndReinterpretCastTo<uint32_t>(messageData) = htonl(bodySize);
#if G_BYTE_ORDER == G_LITTLE_ENDIAN
consumeAndReinterpretCastTo<MessageFlags>(messageData) = ByteOrderLittleEndian;
#else
consumeAndReinterpretCastTo<MessageFlags>(messageData) = 0;
#endif
memcpySpan(consumeSpan(messageData, messageNameAndTerminator.size()), messageNameAndTerminator);
ASSERT(parametersSize == messageData.size());
if (parameters)
memcpySpan(messageData, span(parameters));
write();
}
void SocketConnection::write()
{
if (isClosed())
return;
GUniqueOutPtr<GError> error;
auto bytesWritten = g_socket_send(g_socket_connection_get_socket(m_connection.get()), m_writeBuffer.mutableSpan().data(), m_writeBuffer.size(), nullptr, &error.outPtr());
if (bytesWritten == -1) {
if (g_error_matches(error.get(), G_IO_ERROR, G_IO_ERROR_WOULD_BLOCK)) {
waitForSocketWritability();
return;
}
g_warning("Error sending message on socket connection: %s\n", error->message);
didClose();
return;
}
if (m_writeBuffer.size() > static_cast<size_t>(bytesWritten)) {
memmoveSpan(m_writeBuffer.mutableSpan(), m_writeBuffer.subspan(bytesWritten));
m_writeBuffer.shrink(m_writeBuffer.size() - bytesWritten);
} else
m_writeBuffer.shrink(0);
if (m_writeBuffer.size() < defaultBufferSize)
m_writeBuffer.shrinkCapacity(defaultBufferSize);
if (!m_writeBuffer.isEmpty())
waitForSocketWritability();
}
void SocketConnection::waitForSocketWritability()
{
if (m_writeMonitor.isActive())
return;
m_writeMonitor.start(g_socket_connection_get_socket(m_connection.get()), G_IO_OUT, RunLoop::currentSingleton(), nullptr, [this, protectedThis = Ref { *this }] (GIOCondition condition) -> gboolean {
if (condition & G_IO_OUT) {
// We can't stop the monitor from this lambda, because stop destroys the lambda.
RunLoop::currentSingleton().dispatch([this, protectedThis] {
m_writeMonitor.stop();
write();
});
}
return G_SOURCE_REMOVE;
});
}
void SocketConnection::close()
{
m_readMonitor.stop();
m_writeMonitor.stop();
m_connection = nullptr;
}
void SocketConnection::didClose()
{
if (isClosed())
return;
close();
ASSERT(m_messageHandlers.contains("DidClose"));
m_messageHandlers.get("DidClose").second(*this, nullptr, m_userData);
}
} // namespace WTF