/* * Copyright (c) 2025, Tim Flynn * * SPDX-License-Identifier: BSD-2-Clause */ #include #include #include #include #include namespace DevTools { NonnullRefPtr Connection::create(NonnullOwnPtr socket) { return adopt_ref(*new Connection(move(socket))); } Connection::Connection(NonnullOwnPtr socket) : m_socket(move(socket)) { m_socket->on_ready_to_read = [this]() { if (auto result = on_ready_to_read(); result.is_error()) { if (on_connection_closed) on_connection_closed(); } }; } Connection::~Connection() = default; // https://firefox-source-docs.mozilla.org/devtools/backend/protocol.html#packets void Connection::send_message(JsonValue const& message) { auto serialized = message.serialized(); if constexpr (DEVTOOLS_DEBUG) { if (message.is_object() && message.as_object().get("error"sv).has_value()) dbgln("\x1b[1;31m<<\x1b[0m {}", serialized); else dbgln("\x1b[1;32m<<\x1b[0m {}", serialized); } if (m_socket->write_formatted("{}:{}", serialized.byte_count(), serialized).is_error()) { if (on_connection_closed) on_connection_closed(); } } // https://firefox-source-docs.mozilla.org/devtools/backend/protocol.html#packets ErrorOr Connection::read_message() { ByteBuffer length_buffer; // FIXME: `read_until(':')` would be nicer here, but that seems to return immediately without receiving any data. while (true) { auto byte = TRY(m_socket->read_value()); if (byte == ':') { break; } length_buffer.append(byte); } auto length = StringView { length_buffer }.to_number(); if (!length.has_value()) return Error::from_string_literal("Could not read message length from DevTools client"); ByteBuffer message_buffer; message_buffer.resize(*length); TRY(m_socket->read_until_filled(message_buffer)); auto message = TRY(JsonValue::from_string(message_buffer)); dbgln_if(DEVTOOLS_DEBUG, "\x1b[1;33m>>\x1b[0m {}", message); return message; } ErrorOr Connection::on_ready_to_read() { // https://firefox-source-docs.mozilla.org/devtools/backend/protocol.html#the-request-reply-pattern // Note that it is correct for a client to send several requests to a request/reply actor without waiting for a // reply to each request before sending the next; requests can be pipelined. while (TRY(m_socket->can_read_without_blocking())) { auto message = TRY(read_message()); if (!message.is_object()) continue; Core::deferred_invoke([this, message = move(message)]() mutable { if (on_message_received) on_message_received(move(message.as_object())); }); } return {}; } }