blob: e4b315ff6e02200753e73136e9a1f157c6c8b498 [file] [log] [blame]
// 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_QUIC_QUIC_SOCKET_DATA_PROVIDER_H_
#define NET_QUIC_QUIC_SOCKET_DATA_PROVIDER_H_
#include <map>
#include <memory>
#include <optional>
#include <set>
#include <string>
#include "base/memory/weak_ptr.h"
#include "base/run_loop.h"
#include "net/quic/quic_test_packet_printer.h"
#include "net/socket/socket_test_util.h"
#include "net/third_party/quiche/src/quiche/quic/core/crypto/quic_compressed_certs_cache.h"
#include "net/third_party/quiche/src/quiche/quic/core/crypto/quic_crypto_server_config.h"
#include "net/third_party/quiche/src/quiche/quic/core/quic_config.h"
#include "net/third_party/quiche/src/quiche/quic/core/quic_crypto_server_stream_base.h"
#include "net/third_party/quiche/src/quiche/quic/core/quic_packets.h"
#include "net/third_party/quiche/src/quiche/quic/core/quic_session.h"
#include "net/third_party/quiche/src/quiche/quic/core/quic_types.h"
#include "net/third_party/quiche/src/quiche/quic/test_tools/mock_quic_session_visitor.h"
#include "net/third_party/quiche/src/quiche/quic/test_tools/quic_test_utils.h"
#include "net/third_party/quiche/src/quiche/quic/tools/quic_memory_cache_backend.h"
#include "net/third_party/quiche/src/quiche/quic/tools/quic_simple_server_session.h"
namespace net::test {
// A `QuicSimpleServerSession` that decodes the HTTP frame received from the
// QuicSocketDataProvider.
class QuicSimpleServerSessionForTest : public quic::QuicSimpleServerSession {
public:
using quic::QuicSimpleServerSession::QuicSimpleServerSession;
~QuicSimpleServerSessionForTest() override;
bool IsEncryptionEstablished() const override;
};
// A `SocketDataProvider` specifically designed to handle QUIC's packet-based
// nature, and to give useful errors when things do not go as planned. This
// fills the same purpose as `MockQuicData` and it should be straightforward to
// "upgrade" a use of `MockQuicData` to this class when adding or modifying
// tests.
//
// To use: create a new `QuicSocketDataProvider`, then add expected reads and
// writes to it using the `AddRead` and `AddWrite` methods. Each read or write
// must have a short, unique name that will appear in logs and error messages.
// Once the provider is populated, add it to a `MockClientSocketFactory` with
// `AddSocketDataProvider`.
//
// Each `Add` method creates an "expectation" that some event will occur on the
// socket. A write expectation signals that the system under test will call
// `Write` with a packet matching the given data. A read expectation signals
// that the SUT will call `Read`, and the data in the expectation will be
// returned.
//
// Expectations can be adjusted when they are created by chaining method calls,
// such as setting the mode. Expectations are consumed in a partial order: each
// expectation specifies the expectations which must be consumed before it can
// be consumed. By default, each expectation must come after the previously
// added expectation, but the `After` method can be used to adjust this ordering
// for cases where the order is unimportant or might vary. For example, an ACK
// might be written before or after a read of stream data.
//
// To decode HTTP packets, two server sessions are created, one for the
// expected packets and one for the actual packets. If VerifyWriteData() failed
// the accumulated logs from both sessions will be printed.
//
// When a Write expectation is not met, such as write data not matching the
// expected packet, the Write call will result in `ERR_UNEXPECTED`.
//
// Use `--vmodule=quic_socket_data_provider*=1` in the test command-line to see
// additional logging from this module.
class QuicSocketDataProvider : public SocketDataProvider {
public:
class Expectation {
public:
enum class Type { READ, WRITE, PAUSE };
Expectation(Expectation&) = delete;
Expectation& operator=(Expectation&) = delete;
Expectation(Expectation&&);
Expectation& operator=(Expectation&&);
~Expectation();
// Set the mode for this expectation, where the default is ASYNC. If a `Read
// or `Write` call occurs for a sync expectation when its preconditions have
// not been met, the test will fail.
Expectation& Mode(IoMode mode) {
mode_ = mode;
return *this;
}
Expectation& Sync() {
Mode(SYNCHRONOUS);
return *this;
}
// Indicate that this expectation cannot be consumed until the named
// expectation has been consumed.
Expectation& After(std::string name);
// Set the TOS byte for this expectation.
Expectation& TosByte(uint8_t tos_byte) {
tos_byte_ = tos_byte;
return *this;
}
const std::string& name() const { return name_; }
Type type() const { return type_; }
bool consumed() const { return consumed_; }
const std::set<std::string>& after() const { return after_; }
int rv() const { return rv_; }
const std::unique_ptr<quic::QuicEncryptedPacket>& packet() const {
return packet_;
}
IoMode mode() const { return mode_; }
uint8_t tos_byte() const { return tos_byte_; }
static std::string TypeToString(Type type);
protected:
friend class QuicSocketDataProvider;
Expectation(std::string name,
Type type,
int rv,
std::unique_ptr<quic::QuicEncryptedPacket> packet);
void set_name(std::string name) { name_ = name; }
void Consume();
private:
// Name for this packet, used in sequencing and logging.
std::string name_;
// Type of expectation.
Type type_;
// True when this expectation has been consumed; that is, it has been
// matched with a call to Read or Write and that call has returned or its
// callback has been called.
bool consumed_ = false;
// Expectations which must be consumed before this one, by name.
std::set<std::string> after_;
int rv_;
std::unique_ptr<quic::QuicEncryptedPacket> packet_;
IoMode mode_ = ASYNC;
uint8_t tos_byte_ = 0;
};
// A PausePoint is just the index into the array of expectations.
using PausePoint = size_t;
explicit QuicSocketDataProvider(quic::ParsedQuicVersion version);
~QuicSocketDataProvider() override;
// Adds a read which will result in `packet`. A reference to the provided
// expectation is returned, which can be used to update the settings for that
// expectation. The more-specific version taking `QuicReceivedPacket` also
// sets the TOS byte based on the packet's ECN codepoint.
Expectation& AddRead(std::string name,
std::unique_ptr<quic::QuicReceivedPacket> packet);
Expectation& AddRead(std::string name,
std::unique_ptr<quic::QuicEncryptedPacket> packet);
// Adds a read error return. A reference to the provided expectation is
// returned, which can be used to update the settings for that expectation.
Expectation& AddReadError(std::string name, int rv);
// Adds a write which will expect the given packet and return the given
// result. A reference to the provided packet is returned, which can be used
// to update the settings for the packet.
Expectation& AddWrite(std::string name,
std::unique_ptr<quic::QuicEncryptedPacket> packet,
int rv = OK);
// Adds a write error return. A reference to the provided expectation is
// returned, which can be used to update the settings for that expectation.
Expectation& AddWriteError(std::string name, int rv);
// Adds a Pause point, returning a handle that can be used later to wait for
// and resume execution. Any expectations that come "after" the pause point
// will not be consumed until the pause is reached and execution is resumed.
//
// Note that this is not compatible with
// `SequencedSocketData::RunUntilPaused()`.
PausePoint AddPause(std::string name);
// Checks if all data has been consumed.
bool AllDataConsumed() const;
// Run the main loop until the given pause point is reached. If a different
// pause point is reached, this will fail. Note that the results of any
// `Read` or `Write` calls before the pause point might not be complete, if
// those results were delivered asynchronously.
void RunUntilPause(PausePoint pause_point);
// Resumes I/O after it is paused.
void Resume();
// Run the main loop until all expectations have been consumed. Note that the
// results of any `Read` or `Write` calls might not be complete, if those
// results were delivered asynchronously.
void RunUntilAllConsumed();
// SocketDataProvider implementation.
MockRead OnRead() override;
MockWriteResult OnWrite(const std::string& data) override;
bool AllReadDataConsumed() const override;
bool AllWriteDataConsumed() const override;
void CancelPendingRead() override;
void Reset() override;
private:
// Find indexes of expectations of the given type that are ready to consume.
std::optional<size_t> FindReadyExpectations(Expectation::Type type);
// Find a single ready operation, if any. Fails if multiple expectations of
// the given type are ready. The corresponding expectation is marked as
// consumed, and a task is scheduled to consume any expectations that become
// ready as a result.
std::optional<MockRead> ConsumeNextRead();
std::optional<MockWriteResult> ConsumeNextWrite();
// Consume any expectations that have become ready after a change to another
// expectation. This is called in a task automatically after one or more calls
// to `ExepctationsConsumed`.
void MaybeConsumeExpectations();
// Update state after an expectation has been consumed.
void ExpectationConsumed();
// Verify that the packet matches `write_pending_`.
bool VerifyWriteData(QuicSocketDataProvider::Expectation& expectation);
// Generate a comma-separated list of expectation names.
std::string ExpectationList(const std::vector<size_t>& indices);
// Generate a `QuicSimpleServerSession` for decrypting HTTP packets.
std::unique_ptr<quic::QuicSimpleServerSession> GenSimpleServerSession();
// Helper to print packet data with `QuicSimpleServerSession`.
std::string PrintWithQuicSession(quic::QuicSimpleServerSession* session,
std::string data);
std::vector<Expectation> expectations_;
bool pending_maybe_consume_expectations_ = false;
std::map<size_t, std::set<size_t>> dependencies_;
bool read_pending_ = false;
std::optional<std::string> write_pending_ = std::nullopt;
QuicPacketPrinter printer_;
std::optional<size_t> paused_at_;
std::unique_ptr<base::RunLoop> run_until_run_loop_;
quic::test::MockQuicSessionVisitor owner_;
quic::test::MockQuicCryptoServerStreamHelper stream_helper_;
quic::QuicConfig config_;
quic::test::MockQuicConnectionHelper helper_;
quic::test::MockAlarmFactory alarm_factory_;
quic::QuicCryptoServerConfig crypto_config_;
quic::QuicCompressedCertsCache compressed_certs_cache_;
quic::QuicMemoryCacheBackend memory_cache_backend_;
std::unique_ptr<quic::QuicSimpleServerSession> session_for_actual_;
std::unique_ptr<quic::QuicSimpleServerSession> session_for_expected_;
std::string actual_log_;
std::string expected_log_;
base::WeakPtrFactory<QuicSocketDataProvider> weak_factory_{this};
};
} // namespace net::test
#endif // NET_QUIC_QUIC_SOCKET_DATA_PROVIDER_H_