CS144Lab/tests/tcp_fsm_test_harness.hh
2021-09-28 17:03:04 -07:00

196 lines
9.3 KiB
C++

#ifndef SPONGE_LIBSPONGE_TCP_FSM_TEST_HARNESS_HH
#define SPONGE_LIBSPONGE_TCP_FSM_TEST_HARNESS_HH
#include "fd_adapter.hh"
#include "file_descriptor.hh"
#include "tcp_config.hh"
#include "tcp_connection.hh"
#include "tcp_expectation_forward.hh"
#include "tcp_segment.hh"
#include <cstddef>
#include <cstdint>
#include <exception>
#include <optional>
#include <vector>
//! \brief A wrapper class for a SOCK_SEQPACKET [Unix-domain socket](\ref man7::unix), for use by TCPTestHarness
//! \details A TestFD comprises the two endpoints of a Unix-domain socket connection.
//! The contents of a write() to this object can subsequently be read() from it.
class TestFD : public FileDescriptor {
private:
//! A wrapper for one end of a SOCK_SEQPACKET socket pair
class TestRFD : public FileDescriptor {
public:
//! Construction uses the base class constructor
using FileDescriptor::FileDescriptor;
bool can_read() const; //!< Is this descriptor readable?
std::string read(); //!< Read a from this socket
};
TestRFD _recv_fd; //!< The end of a SOCK_SEQPACKET socket pair from which TCPTestHarness reads
//! Max-sized segment plus some margin
static constexpr size_t MAX_RECV = TCPConfig::MAX_PAYLOAD_SIZE + TCPHeader::LENGTH + 16;
//! Construct from a pair of sockets
explicit TestFD(std::pair<FileDescriptor, TestRFD> fd_pair);
public:
TestFD(); //!< Default constructor invokes TestFD(std::pair<FileDescriptor, TestRFD> fd_pair)
void write(const BufferViewList &buffer); //!< Write a buffer
bool can_read() const { return _recv_fd.can_read(); } //!< Is TestFD::_recv_fd readable?
std::string read() { return _recv_fd.read(); } //!< Read from TestFD::_recv_fd
};
//! An FdAdapterBase that writes to a TestFD. Does not (need to) support reading.
class TestFdAdapter : public FdAdapterBase, public TestFD {
public:
void write(TCPSegment &seg); //!< Write a TCPSegment to the underlying TestFD
void config_segment(TCPSegment &seg); //!< Copy information from FdAdapterConfig into a TCPSegment
};
//! Test adapter for TCPConnection
class TCPTestHarness {
public:
TestFdAdapter _flt{}; //!< FdAdapter mockup
TCPConnection _fsm; //!< The TCPConnection under test
//! A list of test steps that passed
std::vector<std::string> _steps_executed{};
using State = TCPState::State; //!< TCP state names
using VecIterT = std::string::const_iterator; //!< Alias for a const iterator to a vector of bytes
//! Construct a test harness, optionally passing a configuration to the TCPConnection under test
explicit TCPTestHarness(const TCPConfig &c_fsm = {}) : _fsm(c_fsm) {}
//! construct a FIN segment and inject it into TCPConnection
void send_fin(const WrappingInt32 seqno, const std::optional<WrappingInt32> ackno = {});
//! construct an ACK segment and inject it into TCPConnection
void send_ack(const WrappingInt32 seqno, const WrappingInt32 ackno, const std::optional<uint16_t> swin = {});
//! construct a RST segment and inject it into TCPConnection
void send_rst(const WrappingInt32 seqno, const std::optional<WrappingInt32> ackno = {});
//! construct a SYN segment and inject it into TCPConnection
void send_syn(const WrappingInt32 seqno, const std::optional<WrappingInt32> ackno = {});
//! construct a segment containing one byte and inject it into TCPConnection
void send_byte(const WrappingInt32 seqno, const std::optional<WrappingInt32> ackno, const uint8_t val);
//! construct a segment containing the specified payload and inject it into TCPConnection
void send_data(const WrappingInt32 seqno, const WrappingInt32 ackno, VecIterT begin, VecIterT end);
//! is it possible to read from the TestFdAdapter (i.e., read a segment TCPConnection previously wrote)?
bool can_read() const { return _flt.can_read(); }
//! \brief execute one step in a test.
//! \param step is a representation of the step. i.e. an action or
//! expectation
//! \param note is an (optional) string describing the significance of this
//! step
void execute(const TCPTestStep &step, std::string note = "");
//! \brief expect and read a segment
//! \param expectaion is a representation of the properties of the expected
//! segment.
//! \param note is an (optional) string describing the significance of this
//! step
TCPSegment expect_seg(const ExpectSegment &expectation, std::string note = "");
//! Create an FSM in the "LISTEN" state.
static TCPTestHarness in_listen(const TCPConfig &cfg);
//! \brief Create an FSM which has sent a SYN.
//! \details The SYN has been consumed, but not ACK'd by the test harness
//! \param[in] tx_isn is the ISN of the FSM's outbound sequence. i.e. the
//! seqno for the SYN.
static TCPTestHarness in_syn_sent(const TCPConfig &cfg, const WrappingInt32 tx_isn = WrappingInt32{0});
//! \brief Create an FSM with an established connection
//! \details The mahine has sent and received a SYN, and both SYNs have been ACK'd
//! No payload was exchanged.
//! \param[in] tx_isn is the ISN of the FSM's outbound sequence. i.e. the
//! seqno for the SYN.
//! \param[in] rx_isn is the ISN of the FSM's inbound sequence. i.e. the
//! seqno for the SYN.
static TCPTestHarness in_established(const TCPConfig &cfg,
const WrappingInt32 tx_isn = WrappingInt32{0},
const WrappingInt32 rx_isn = WrappingInt32{0});
//! \brief Create an FSM in CLOSE_WAIT
//! \details SYNs have been traded, and then the machine received and ACK'd FIN.
//! No payload was exchanged.
//! \param[in] tx_isn is the ISN of the FSM's outbound sequence. i.e. the
//! seqno for the SYN.
//! \param[in] rx_isn is the ISN of the FSM's inbound sequence. i.e. the
//! seqno for the SYN.
static TCPTestHarness in_close_wait(const TCPConfig &cfg,
const WrappingInt32 tx_isn = WrappingInt32{0},
const WrappingInt32 rx_isn = WrappingInt32{0});
//! \brief Create an FSM in LAST_ACK
//! \details SYNs have been traded, then the machine received and ACK'd FIN, and then it sent its own FIN.
//! No payload was exchanged.
//! \param[in] tx_isn is the ISN of the FSM's outbound sequence. i.e. the
//! seqno for the SYN.
//! \param[in] rx_isn is the ISN of the FSM's inbound sequence. i.e. the
//! seqno for the SYN.
static TCPTestHarness in_last_ack(const TCPConfig &cfg,
const WrappingInt32 tx_isn = WrappingInt32{0},
const WrappingInt32 rx_isn = WrappingInt32{0});
//! \brief Create an FSM in FIN_WAIT_1
//! \details SYNs have been traded, then the TCP sent FIN.
//! No payload was exchanged.
//! \param[in] tx_isn is the ISN of the FSM's outbound sequence. i.e. the
//! seqno for the SYN.
//! \param[in] rx_isn is the ISN of the FSM's inbound sequence. i.e. the
//! seqno for the SYN.
static TCPTestHarness in_fin_wait_1(const TCPConfig &cfg,
const WrappingInt32 tx_isn = WrappingInt32{0},
const WrappingInt32 rx_isn = WrappingInt32{0});
//! \brief Create an FSM in FIN_WAIT_2
//! \details SYNs have been traded, then the TCP sent FIN.
//! No payload was exchanged.
//! \param[in] tx_isn is the ISN of the FSM's outbound sequence. i.e. the
//! seqno for the SYN.
//! \param[in] rx_isn is the ISN of the FSM's inbound sequence. i.e. the
//! seqno for the SYN.
static TCPTestHarness in_fin_wait_2(const TCPConfig &cfg,
const WrappingInt32 tx_isn = WrappingInt32{0},
const WrappingInt32 rx_isn = WrappingInt32{0});
//! \brief Create an FSM in CLOSING
//! \details SYNs have been traded, then the TCP sent FIN, then received FIN
//! No payload was exchanged.
//! \param[in] tx_isn is the ISN of the FSM's outbound sequence. i.e. the
//! seqno for the SYN.
//! \param[in] rx_isn is the ISN of the FSM's inbound sequence. i.e. the
//! seqno for the SYN.
static TCPTestHarness in_closing(const TCPConfig &cfg,
const WrappingInt32 tx_isn = WrappingInt32{0},
const WrappingInt32 rx_isn = WrappingInt32{0});
//! \brief Create an FSM in TIME_WAIT
//! \details SYNs have been traded, then the TCP sent FIN, then received FIN/ACK, and ACK'd.
//! No payload was exchanged.
//! \param[in] tx_isn is the ISN of the FSM's outbound sequence. i.e. the
//! seqno for the SYN.
//! \param[in] rx_isn is the ISN of the FSM's inbound sequence. i.e. the
//! seqno for the SYN.
static TCPTestHarness in_time_wait(const TCPConfig &cfg,
const WrappingInt32 tx_isn = WrappingInt32{0},
const WrappingInt32 rx_isn = WrappingInt32{0});
};
#endif // SPONGE_LIBSPONGE_TCP_FSM_TEST_HARNESS_HH