Skip to content

Commit

Permalink
CS144 Lab 3 assignment
Browse files Browse the repository at this point in the history
  • Loading branch information
keithw committed Oct 8, 2020
1 parent 81b077f commit 6552aa2
Show file tree
Hide file tree
Showing 16 changed files with 1,434 additions and 0 deletions.
35 changes: 35 additions & 0 deletions libsponge/tcp_helpers/tcp_config.hh
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
#ifndef SPONGE_LIBSPONGE_TCP_CONFIG_HH
#define SPONGE_LIBSPONGE_TCP_CONFIG_HH

#include "address.hh"
#include "wrapping_integers.hh"

#include <cstddef>
#include <cstdint>
#include <optional>

//! Config for TCP sender and receiver
class TCPConfig {
public:
static constexpr size_t DEFAULT_CAPACITY = 64000; //!< Default capacity
static constexpr size_t MAX_PAYLOAD_SIZE = 1452; //!< Max TCP payload that fits in either IPv4 or UDP datagram
static constexpr uint16_t TIMEOUT_DFLT = 1000; //!< Default re-transmit timeout is 1 second
static constexpr unsigned MAX_RETX_ATTEMPTS = 8; //!< Maximum re-transmit attempts before giving up

uint16_t rt_timeout = TIMEOUT_DFLT; //!< Initial value of the retransmission timeout, in milliseconds
size_t recv_capacity = DEFAULT_CAPACITY; //!< Receive capacity, in bytes
size_t send_capacity = DEFAULT_CAPACITY; //!< Sender capacity, in bytes
std::optional<WrappingInt32> fixed_isn{};
};

//! Config for classes derived from FdAdapter
class FdAdapterConfig {
public:
Address source{"0", 0}; //!< Source address and port
Address destination{"0", 0}; //!< Destination address and port

uint16_t loss_rate_dn = 0; //!< Downlink loss rate (for LossyFdAdapter)
uint16_t loss_rate_up = 0; //!< Uplink loss rate (for LossyFdAdapter)
};

#endif // SPONGE_LIBSPONGE_TCP_CONFIG_HH
18 changes: 18 additions & 0 deletions libsponge/tcp_helpers/tcp_state.cc
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,21 @@ string TCPState::state_summary(const TCPReceiver &receiver) {
return TCPReceiverStateSummary::SYN_RECV;
}
}

string TCPState::state_summary(const TCPSender &sender) {
if (sender.stream_in().error()) {
return TCPSenderStateSummary::ERROR;
} else if (sender.next_seqno_absolute() == 0) {
return TCPSenderStateSummary::CLOSED;
} else if (sender.next_seqno_absolute() == sender.bytes_in_flight()) {
return TCPSenderStateSummary::SYN_SENT;
} else if (not sender.stream_in().eof()) {
return TCPSenderStateSummary::SYN_ACKED;
} else if (sender.next_seqno_absolute() < sender.stream_in().bytes_written() + 2) {
return TCPSenderStateSummary::SYN_ACKED;
} else if (sender.bytes_in_flight()) {
return TCPSenderStateSummary::FIN_SENT;
} else {
return TCPSenderStateSummary::FIN_ACKED;
}
}
13 changes: 13 additions & 0 deletions libsponge/tcp_helpers/tcp_state.hh
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
#define SPONGE_LIBSPONGE_TCP_STATE

#include "tcp_receiver.hh"
#include "tcp_sender.hh"

#include <string>

Expand All @@ -23,6 +24,9 @@ class TCPState {
public:
//! \brief Summarize the state of a TCPReceiver in a string
static std::string state_summary(const TCPReceiver &receiver);

//! \brief Summarize the state of a TCPSender in a string
static std::string state_summary(const TCPSender &receiver);
};

namespace TCPReceiverStateSummary {
Expand All @@ -32,4 +36,13 @@ const std::string SYN_RECV = "SYN received (ackno exists), and input to stream h
const std::string FIN_RECV = "input to stream has ended";
} // namespace TCPReceiverStateSummary

namespace TCPSenderStateSummary {
const std::string ERROR = "error (connection was reset)";
const std::string CLOSED = "waiting for stream to begin (no SYN sent)";
const std::string SYN_SENT = "stream started but nothing acknowledged";
const std::string SYN_ACKED = "stream ongoing";
const std::string FIN_SENT = "stream finished (FIN sent) but not fully acknowledged";
const std::string FIN_ACKED = "stream finished and fully acknowledged";
} // namespace TCPSenderStateSummary

#endif // SPONGE_LIBSPONGE_TCP_STATE
38 changes: 38 additions & 0 deletions libsponge/tcp_sender.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
#include "tcp_sender.hh"

#include "tcp_config.hh"

#include <random>

// Dummy implementation of a TCP sender

// For Lab 3, please replace with a real implementation that passes the
// automated checks run by `make check_lab3`.

template <typename... Targs>
void DUMMY_CODE(Targs &&... /* unused */) {}

using namespace std;

//! \param[in] capacity the capacity of the outgoing byte stream
//! \param[in] retx_timeout the initial amount of time to wait before retransmitting the oldest outstanding segment
//! \param[in] fixed_isn the Initial Sequence Number to use, if set (otherwise uses a random ISN)
TCPSender::TCPSender(const size_t capacity, const uint16_t retx_timeout, const std::optional<WrappingInt32> fixed_isn)
: _isn(fixed_isn.value_or(WrappingInt32{random_device()()}))
, _initial_retransmission_timeout{retx_timeout}
, _stream(capacity) {}

uint64_t TCPSender::bytes_in_flight() const { return {}; }

void TCPSender::fill_window() {}

//! \param ackno The remote receiver's ackno (acknowledgment number)
//! \param window_size The remote receiver's advertised window size
void TCPSender::ack_received(const WrappingInt32 ackno, const uint16_t window_size) { DUMMY_CODE(ackno, window_size); }

//! \param[in] ms_since_last_tick the number of milliseconds since the last call to this method
void TCPSender::tick(const size_t ms_since_last_tick) { DUMMY_CODE(ms_since_last_tick); }

unsigned int TCPSender::consecutive_retransmissions() const { return {}; }

void TCPSender::send_empty_segment() {}
92 changes: 92 additions & 0 deletions libsponge/tcp_sender.hh
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
#ifndef SPONGE_LIBSPONGE_TCP_SENDER_HH
#define SPONGE_LIBSPONGE_TCP_SENDER_HH

#include "byte_stream.hh"
#include "tcp_config.hh"
#include "tcp_segment.hh"
#include "wrapping_integers.hh"

#include <functional>
#include <queue>

//! \brief The "sender" part of a TCP implementation.

//! Accepts a ByteStream, divides it up into segments and sends the
//! segments, keeps track of which segments are still in-flight,
//! maintains the Retransmission Timer, and retransmits in-flight
//! segments if the retransmission timer expires.
class TCPSender {
private:
//! our initial sequence number, the number for our SYN.
WrappingInt32 _isn;

//! outbound queue of segments that the TCPSender wants sent
std::queue<TCPSegment> _segments_out{};

//! retransmission timer for the connection
unsigned int _initial_retransmission_timeout;

//! outgoing stream of bytes that have not yet been sent
ByteStream _stream;

//! the (absolute) sequence number for the next byte to be sent
uint64_t _next_seqno{0};

public:
//! Initialize a TCPSender
TCPSender(const size_t capacity = TCPConfig::DEFAULT_CAPACITY,
const uint16_t retx_timeout = TCPConfig::TIMEOUT_DFLT,
const std::optional<WrappingInt32> fixed_isn = {});

//! \name "Input" interface for the writer
//!@{
ByteStream &stream_in() { return _stream; }
const ByteStream &stream_in() const { return _stream; }
//!@}

//! \name Methods that can cause the TCPSender to send a segment
//!@{

//! \brief A new acknowledgment was received
void ack_received(const WrappingInt32 ackno, const uint16_t window_size);

//! \brief Generate an empty-payload segment (useful for creating empty ACK segments)
void send_empty_segment();

//! \brief create and send segments to fill as much of the window as possible
void fill_window();

//! \brief Notifies the TCPSender of the passage of time
void tick(const size_t ms_since_last_tick);
//!@}

//! \name Accessors
//!@{

//! \brief How many sequence numbers are occupied by segments sent but not yet acknowledged?
//! \note count is in "sequence space," i.e. SYN and FIN each count for one byte
//! (see TCPSegment::length_in_sequence_space())
size_t bytes_in_flight() const;

//! \brief Number of consecutive retransmissions that have occurred in a row
unsigned int consecutive_retransmissions() const;

//! \brief TCPSegments that the TCPSender has enqueued for transmission.
//! \note These must be dequeued and sent by the TCPConnection,
//! which will need to fill in the fields that are set by the TCPReceiver
//! (ackno and window size) before sending.
std::queue<TCPSegment> &segments_out() { return _segments_out; }
//!@}

//! \name What is the next sequence number? (used for testing)
//!@{

//! \brief absolute seqno for the next byte to be sent
uint64_t next_seqno_absolute() const { return _next_seqno; }

//! \brief relative seqno for the next byte to be sent
WrappingInt32 next_seqno() const { return wrap(_next_seqno, _isn); }
//!@}
};

#endif // SPONGE_LIBSPONGE_TCP_SENDER_HH
6 changes: 6 additions & 0 deletions tests/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -30,3 +30,9 @@ add_test_exec (recv_window)
add_test_exec (recv_reorder)
add_test_exec (recv_close)
add_test_exec (recv_special)
add_test_exec (send_connect)
add_test_exec (send_transmit)
add_test_exec (send_retx)
add_test_exec (send_ack)
add_test_exec (send_window)
add_test_exec (send_close)
80 changes: 80 additions & 0 deletions tests/send_ack.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
#include "sender_harness.hh"
#include "wrapping_integers.hh"

#include <cstdint>
#include <cstdlib>
#include <exception>
#include <iostream>
#include <optional>
#include <stdexcept>
#include <string>

using namespace std;

int main() {
try {
auto rd = get_random_generator();

{
TCPConfig cfg;
WrappingInt32 isn(rd());
cfg.fixed_isn = isn;

TCPSenderTestHarness test{"Repeat ACK is ignored", cfg};
test.execute(ExpectSegment{}.with_no_flags().with_syn(true).with_payload_size(0).with_seqno(isn));
test.execute(ExpectNoSegment{});
test.execute(AckReceived{WrappingInt32{isn + 1}});
test.execute(WriteBytes{"a"});
test.execute(ExpectSegment{}.with_no_flags().with_data("a"));
test.execute(ExpectNoSegment{});
test.execute(AckReceived{WrappingInt32{isn + 1}});
test.execute(ExpectNoSegment{});
}

{
TCPConfig cfg;
WrappingInt32 isn(rd());
cfg.fixed_isn = isn;

TCPSenderTestHarness test{"Old ACK is ignored", cfg};
test.execute(ExpectSegment{}.with_no_flags().with_syn(true).with_payload_size(0).with_seqno(isn));
test.execute(ExpectNoSegment{});
test.execute(AckReceived{WrappingInt32{isn + 1}});
test.execute(WriteBytes{"a"});
test.execute(ExpectSegment{}.with_no_flags().with_data("a"));
test.execute(ExpectNoSegment{});
test.execute(AckReceived{WrappingInt32{isn + 2}});
test.execute(ExpectNoSegment{});
test.execute(WriteBytes{"b"});
test.execute(ExpectSegment{}.with_no_flags().with_data("b"));
test.execute(ExpectNoSegment{});
test.execute(AckReceived{WrappingInt32{isn + 1}});
test.execute(ExpectNoSegment{});
}

/* remove requirement to send corrective ACK for bad ACK
{
TCPConfig cfg;
WrappingInt32 isn(rd());
cfg.fixed_isn = isn;
TCPSenderTestHarness test{"Early ACK results in bare ACK", cfg};
test.execute(ExpectSegment{}.with_no_flags().with_syn(true).with_payload_size(0).with_seqno(isn));
test.execute(ExpectNoSegment{});
test.execute(AckReceived{WrappingInt32{isn + 1}});
test.execute(WriteBytes{"a"});
test.execute(ExpectSegment{}.with_no_flags().with_data("a"));
test.execute(ExpectNoSegment{});
test.execute(AckReceived{WrappingInt32{isn + 17}});
test.execute(ExpectSegment{}.with_seqno(isn + 2));
test.execute(ExpectNoSegment{});
}
*/

} catch (const exception &e) {
cerr << e.what() << endl;
return 1;
}

return EXIT_SUCCESS;
}
Loading

0 comments on commit 6552aa2

Please sign in to comment.