Commit 32abbda9 authored by zhouqi's avatar zhouqi

init class 05

parents
---
Language: Cpp
BasedOnStyle: LLVM
AllowAllParametersOfDeclarationOnNextLine: false
AlwaysBreakTemplateDeclarations: true
BinPackArguments: false
BinPackParameters: false
BreakConstructorInitializers: BeforeComma
ColumnLimit: 120
CommentPragmas: '^(!|NOLINT)'
ConstructorInitializerAllOnOneLineOrOnePerLine: true
IncludeBlocks: Regroup
IncludeCategories:
- Regex: '^<.*'
Priority: 2
- Regex: '.*'
Priority: 1
IncludeIsMainRegex: '(_dt|_win)?$'
IndentCaseLabels: true
IndentWidth: 4
KeepEmptyLinesAtTheStartOfBlocks: false
PenaltyReturnTypeOnItsOwnLine: 200
SpacesBeforeTrailingComments: 2
TabWidth: 4
UseTab: Never
...
*.a
*.o
/build
CMakeCache.txt
CMakeFiles
Makefile
cmake_install.cmake
CTestTestfile.cmake
compile_commands.json
cmake_minimum_required (VERSION 2.8.5)
cmake_policy (SET CMP0054 NEW)
project (Sponge)
include (etc/build_defs.cmake)
include (etc/build_type.cmake)
include (etc/cflags.cmake)
include (etc/doxygen.cmake)
include (etc/clang_format.cmake)
include (etc/clang_tidy.cmake)
include (etc/cppcheck.cmake)
include_directories ("${PROJECT_SOURCE_DIR}/libsponge/util")
include_directories ("${PROJECT_SOURCE_DIR}/libsponge/tcp_helpers")
include_directories ("${PROJECT_SOURCE_DIR}/libsponge")
add_subdirectory ("${PROJECT_SOURCE_DIR}/libsponge")
add_subdirectory ("${PROJECT_SOURCE_DIR}/apps")
add_subdirectory ("${PROJECT_SOURCE_DIR}/tests")
add_subdirectory ("${PROJECT_SOURCE_DIR}/doctests")
include (etc/tests.cmake)
For build prereqs, see [the CS144 VM setup instructions](https://web.stanford.edu/class/cs144/vm_howto).
## Sponge quickstart
To set up your build directory:
$ mkdir -p <path/to/sponge>/build
$ cd <path/to/sponge>/build
$ cmake ..
**Note:** all further commands listed below should be run from the `build` dir.
To build:
$ make
You can use the `-j` switch to build in parallel, e.g.,
$ make -j$(nproc)
To test (after building; make sure you've got the [build prereqs](https://web.stanford.edu/class/cs144/vm_howto) installed!)
$ make check
The first time you run `make check`, it will run `sudo` to configure two
[TUN](https://www.kernel.org/doc/Documentation/networking/tuntap.txt) devices for use during
testing.
### build options
You can specify a different compiler when you run cmake:
$ CC=clang CXX=clang++ cmake ..
You can also specify `CLANG_TIDY=` or `CLANG_FORMAT=` (see "other useful targets", below).
Sponge's build system supports several different build targets. By default, cmake chooses the `Release`
target, which enables the usual optimizations. The `Debug` target enables debugging and reduces the
level of optimization. To choose the `Debug` target:
$ cmake .. -DCMAKE_BUILD_TYPE=Debug
The following targets are supported:
- `Release` - optimizations
- `Debug` - debug symbols and `-Og`
- `RelASan` - release build with [ASan](https://en.wikipedia.org/wiki/AddressSanitizer) and
[UBSan](https://developers.redhat.com/blog/2014/10/16/gcc-undefined-behavior-sanitizer-ubsan/)
- `RelTSan` - release build with
[ThreadSan](https://developer.mozilla.org/en-US/docs/Mozilla/Projects/Thread_Sanitizer)
- `DebugASan` - debug build with ASan and UBSan
- `DebugTSan` - debug build with ThreadSan
Of course, you can combine all of the above, e.g.,
$ CLANG_TIDY=clang-tidy-6.0 CXX=clang++-6.0 .. -DCMAKE_BUILD_TYPE=Debug
**Note:** if you want to change `CC`, `CXX`, `CLANG_TIDY`, or `CLANG_FORMAT`, you need to remove
`build/CMakeCache.txt` and re-run cmake. (This isn't necessary for `CMAKE_BUILD_TYPE`.)
### other useful targets
To generate documentation (you'll need `doxygen`; output will be in `build/doc/`):
$ make doc
To lint (you'll need `clang-tidy`):
$ make -j$(nproc) tidy
To run cppcheck (you'll need `cppcheck`):
$ make cppcheck
To format (you'll need `clang-format`):
$ make format
To see all available targets,
$ make help
add_library (stream_copy STATIC bidirectional_stream_copy.cc)
add_sponge_exec (udp_tcpdump ${LIBPCAP})
add_sponge_exec (tcp_native stream_copy)
add_sponge_exec (tun)
add_sponge_exec (tcp_udp stream_copy)
add_sponge_exec (tcp_ipv4 stream_copy)
add_sponge_exec (webget)
add_sponge_exec (tcp_benchmark)
#include "bidirectional_stream_copy.hh"
#include "byte_stream.hh"
#include "eventloop.hh"
#include <algorithm>
#include <iostream>
#include <unistd.h>
using namespace std;
void bidirectional_stream_copy(Socket &socket) {
constexpr size_t max_copy_length = 65536;
constexpr size_t buffer_size = 1048576;
EventLoop _eventloop{};
FileDescriptor _input{STDIN_FILENO};
FileDescriptor _output{STDOUT_FILENO};
ByteStream _outbound{buffer_size};
ByteStream _inbound{buffer_size};
bool _outbound_shutdown{false};
bool _inbound_shutdown{false};
socket.set_blocking(false);
_input.set_blocking(false);
_output.set_blocking(false);
// rule 1: read from stdin into outbound byte stream
_eventloop.add_rule(_input,
Direction::In,
[&] {
_outbound.write(_input.read(_outbound.remaining_capacity()));
if (_input.eof()) {
_outbound.end_input();
}
},
[&] { return (not _outbound.error()) and (_outbound.remaining_capacity() > 0); },
[&] { _outbound.end_input(); });
// rule 2: read from outbound byte stream into socket
_eventloop.add_rule(socket,
Direction::Out,
[&] {
const size_t bytes_to_write = min(max_copy_length, _outbound.buffer_size());
const size_t bytes_written = socket.write(_outbound.peek_output(bytes_to_write), false);
_outbound.pop_output(bytes_written);
if (_outbound.eof()) {
socket.shutdown(SHUT_WR);
_outbound_shutdown = true;
}
},
[&] { return (not _outbound.buffer_empty()) or (_outbound.eof() and not _outbound_shutdown); },
[&] { _outbound.set_error(); });
// rule 3: read from socket into inbound byte stream
_eventloop.add_rule(socket,
Direction::In,
[&] {
_inbound.write(socket.read(_inbound.remaining_capacity()));
if (socket.eof()) {
_inbound.end_input();
}
},
[&] { return (not _inbound.error()) and (_inbound.remaining_capacity() > 0); },
[&] { _inbound.end_input(); });
// rule 4: read from inbound byte stream into stdout
_eventloop.add_rule(_output,
Direction::Out,
[&] {
const size_t bytes_to_write = min(max_copy_length, _inbound.buffer_size());
const size_t bytes_written = _output.write(_inbound.peek_output(bytes_to_write), false);
_inbound.pop_output(bytes_written);
if (_inbound.eof()) {
_output.close();
_inbound_shutdown = true;
}
},
[&] { return (not _inbound.buffer_empty()) or (_inbound.eof() and not _inbound_shutdown); },
[&] { _inbound.set_error(); });
// loop until completion
while (true) {
if (EventLoop::Result::Exit == _eventloop.wait_next_event(-1)) {
return;
}
}
}
#ifndef SPONGE_APPS_BIDIRECTIONAL_STREAM_COPY_HH
#define SPONGE_APPS_BIDIRECTIONAL_STREAM_COPY_HH
#include "socket.hh"
//! Copy socket input/output to stdin/stdout until finished
void bidirectional_stream_copy(Socket &socket);
#endif // SPONGE_APPS_BIDIRECTIONAL_STREAM_COPY_HH
#include "tcp_connection.hh"
#include <chrono>
#include <cstdlib>
#include <iomanip>
#include <iostream>
#include <string>
using namespace std;
using namespace std::chrono;
constexpr size_t len = 100 * 1024 * 1024;
void move_segments(TCPConnection &x, TCPConnection &y, vector<TCPSegment> &segments, const bool reorder) {
while (not x.segments_out().empty()) {
segments.emplace_back(move(x.segments_out().front()));
x.segments_out().pop();
}
if (reorder) {
for (auto it = segments.rbegin(); it != segments.rend(); ++it) {
y.segment_received(move(*it));
}
} else {
for (auto it = segments.begin(); it != segments.end(); ++it) {
y.segment_received(move(*it));
}
}
segments.clear();
}
void main_loop(const bool reorder) {
TCPConfig config;
TCPConnection x{config}, y{config};
string string_to_send(len, 'x');
for (auto &ch : string_to_send) {
ch = rand();
}
Buffer bytes_to_send{string(string_to_send)};
x.connect();
y.end_input_stream();
bool x_closed = false;
string string_received;
string_received.reserve(len);
const auto first_time = high_resolution_clock::now();
auto loop = [&] {
// write input into x
while (bytes_to_send.size() and x.remaining_outbound_capacity()) {
const auto want = min(x.remaining_outbound_capacity(), bytes_to_send.size());
const auto written = x.write(string(bytes_to_send.str().substr(0, want)));
if (want != written) {
throw runtime_error("want = " + to_string(want) + ", written = " + to_string(written));
}
bytes_to_send.remove_prefix(written);
}
if (bytes_to_send.size() == 0 and not x_closed) {
x.end_input_stream();
x_closed = true;
}
// exchange segments between x and y but in reverse order
vector<TCPSegment> segments;
move_segments(x, y, segments, reorder);
move_segments(y, x, segments, false);
// read output from y
const auto available_output = y.inbound_stream().buffer_size();
if (available_output > 0) {
string_received.append(y.inbound_stream().read(available_output));
}
// time passes
x.tick(1000);
y.tick(1000);
};
while (not y.inbound_stream().eof()) {
loop();
}
if (string_received != string_to_send) {
throw runtime_error("strings sent vs. received don't match");
}
const auto final_time = high_resolution_clock::now();
const auto duration = duration_cast<nanoseconds>(final_time - first_time).count();
const auto gigabits_per_second = len * 8.0 / double(duration);
cout << fixed << setprecision(2);
cout << "CPU-limited throughput" << (reorder ? " with reordering: " : " : ") << gigabits_per_second
<< " Gbit/s\n";
while (x.active() or y.active()) {
loop();
}
}
int main() {
try {
main_loop(false);
main_loop(true);
} catch (const exception &e) {
cerr << e.what() << "\n";
return EXIT_FAILURE;
}
return EXIT_SUCCESS;
}
#include "bidirectional_stream_copy.hh"
#include "tcp_config.hh"
#include "tcp_sponge_socket.hh"
#include "tun.hh"
#include <cstdint>
#include <cstdlib>
#include <cstring>
#include <iostream>
#include <random>
#include <string>
#include <tuple>
using namespace std;
constexpr const char *TUN_DFLT = "tun144";
const string LOCAL_ADDRESS_DFLT = "169.254.144.9";
static void show_usage(const char *argv0, const char *msg) {
cout << "Usage: " << argv0 << " [options] <host> <port>\n\n"
<< " Option Default\n"
<< " -- --\n\n"
<< " -l Server (listen) mode. (client mode)\n"
<< " In server mode, <host>:<port> is the address to bind.\n\n"
<< " -a <addr> Set source address (client mode only) " << LOCAL_ADDRESS_DFLT << "\n"
<< " -s <port> Set source port (client mode only) (random)\n\n"
<< " -w <winsz> Use a window of <winsz> bytes " << TCPConfig::MAX_PAYLOAD_SIZE
<< "\n\n"
<< " -t <tmout> Set rt_timeout to tmout " << TCPConfig::TIMEOUT_DFLT << "\n\n"
<< " -d <tundev> Connect to tun <tundev> " << TUN_DFLT << "\n\n"
<< " -Lu <loss> Set uplink loss to <rate> (float in 0..1) (no loss)\n"
<< " -Ld <loss> Set downlink loss to <rate> (float in 0..1) (no loss)\n\n"
<< " -h Show this message.\n\n";
if (msg != nullptr) {
cout << msg;
}
cout << endl;
}
static void check_argc(int argc, char **argv, int curr, const char *err) {
if (curr + 3 >= argc) {
show_usage(argv[0], err);
exit(1);
}
}
static tuple<TCPConfig, FdAdapterConfig, bool, char *> get_config(int argc, char **argv) {
TCPConfig c_fsm{};
FdAdapterConfig c_filt{};
char *tundev = nullptr;
int curr = 1;
bool listen = false;
string source_address = "0";
string source_port = to_string(uint16_t(random_device()()));
while (argc - curr > 2) {
if (strncmp("-l", argv[curr], 3) == 0) {
listen = true;
curr += 1;
} else if (strncmp("-a", argv[curr], 3) == 0) {
check_argc(argc, argv, curr, "ERROR: -a requires one argument.");
source_address = argv[curr + 1];
curr += 2;
} else if (strncmp("-s", argv[curr], 3) == 0) {
check_argc(argc, argv, curr, "ERROR: -s requires one argument.");
source_port = argv[curr + 1];
curr += 2;
} else if (strncmp("-w", argv[curr], 3) == 0) {
check_argc(argc, argv, curr, "ERROR: -w requires one argument.");
c_fsm.recv_capacity = strtol(argv[curr + 1], nullptr, 0);
curr += 2;
} else if (strncmp("-t", argv[curr], 3) == 0) {
check_argc(argc, argv, curr, "ERROR: -t requires one argument.");
c_fsm.rt_timeout = strtol(argv[curr + 1], nullptr, 0);
curr += 2;
} else if (strncmp("-d", argv[curr], 3) == 0) {
check_argc(argc, argv, curr, "ERROR: -t requires one argument.");
tundev = argv[curr + 1];
curr += 2;
} else if (strncmp("-Lu", argv[curr], 3) == 0) {
check_argc(argc, argv, curr, "ERROR: -Lu requires one argument.");
float lossrate = strtof(argv[curr + 1], nullptr);
using LossRateUpT = decltype(c_filt.loss_rate_up);
c_filt.loss_rate_up =
static_cast<LossRateUpT>(static_cast<float>(numeric_limits<LossRateUpT>::max()) * lossrate);
curr += 2;
} else if (strncmp("-Ld", argv[curr], 3) == 0) {
check_argc(argc, argv, curr, "ERROR: -Lu requires one argument.");
float lossrate = strtof(argv[curr + 1], nullptr);
using LossRateDnT = decltype(c_filt.loss_rate_dn);
c_filt.loss_rate_dn =
static_cast<LossRateDnT>(static_cast<float>(numeric_limits<LossRateDnT>::max()) * lossrate);
curr += 2;
} else if (strncmp("-h", argv[curr], 3) == 0) {
show_usage(argv[0], nullptr);
exit(0);
} else {
show_usage(argv[0], string("ERROR: unrecognized option " + string(argv[curr])).c_str());
exit(1);
}
}
// parse positional command-line arguments
if (listen) {
c_filt.source = {"0", argv[curr + 1]};
if (c_filt.source.port() == 0) {
show_usage(argv[0], "ERROR: listen port cannot be zero in server mode.");
exit(1);
}
} else {
c_filt.destination = {argv[curr], argv[curr + 1]};
c_filt.source = {source_address, source_port};
}
return make_tuple(c_fsm, c_filt, listen, tundev);
}
int main(int argc, char **argv) {
try {
if (argc < 3) {
show_usage(argv[0], "ERROR: required arguments are missing.");
return EXIT_FAILURE;
}
auto [c_fsm, c_filt, listen, tun_dev_name] = get_config(argc, argv);
LossyTCPOverIPv4SpongeSocket tcp_socket(TunFD(tun_dev_name == nullptr ? TUN_DFLT : tun_dev_name));
deque<string> write_queue;
if (listen) {
tcp_socket.listen_and_accept(c_fsm, c_filt);
} else {
tcp_socket.connect(c_fsm, c_filt);
}
bidirectional_stream_copy(tcp_socket);
tcp_socket.wait_until_closed();
} catch (const exception &e) {
cerr << "Exception: " << e.what() << endl;
return EXIT_FAILURE;
}
return EXIT_SUCCESS;
}
#include "bidirectional_stream_copy.hh"
#include <cstdlib>
#include <cstring>
#include <iostream>
using namespace std;
void show_usage(const char *argv0) {
cerr << "Usage: " << argv0 << " [-l] <host> <port>\n\n"
<< " -l specifies listen mode; <host>:<port> is the listening address." << endl;
}
int main(int argc, char **argv) {
try {
bool server_mode = false;
if (argc < 3 || ((server_mode = (strncmp("-l", argv[1], 3) == 0)) && argc < 4)) {
show_usage(argv[0]);
return EXIT_FAILURE;
}
// in client mode, connect; in server mode, accept exactly one connection
auto socket = [&] {
if (server_mode) {
TCPSocket listening_socket; // create a TCP socket
listening_socket.set_reuseaddr(); // reuse the server's address as soon as the program quits
listening_socket.bind({argv[2], argv[3]}); // bind to specified address
listening_socket.listen(); // mark the socket as listening for incoming connections
return listening_socket.accept(); // accept exactly one connection
}
TCPSocket connecting_socket;
connecting_socket.connect({argv[1], argv[2]});
return connecting_socket;
}();
bidirectional_stream_copy(socket);
} catch (const exception &e) {
cerr << "Exception: " << e.what() << endl;
return EXIT_FAILURE;
}
return EXIT_SUCCESS;
}
#include "bidirectional_stream_copy.hh"
#include "tcp_config.hh"
#include "tcp_sponge_socket.hh"
#include <cstdlib>
#include <cstring>
#include <iostream>
#include <limits>
#include <random>
#include <string>
#include <tuple>
using namespace std;
constexpr uint16_t DPORT_DFLT = 1440;
static void show_usage(const char *argv0, const char *msg) {
cout << "Usage: " << argv0 << " [options] <host> <port>\n\n"
<< " Option Default\n"
<< " -- --\n\n"
<< " -l Server (listen) mode. (client mode)\n"
<< " In server mode, <host>:<port> is the address to bind.\n\n"
<< " -w <winsz> Use a window of <winsz> bytes " << TCPConfig::MAX_PAYLOAD_SIZE
<< "\n\n"
<< " -t <tmout> Set rt_timeout to tmout " << TCPConfig::TIMEOUT_DFLT << "\n\n"
<< " -Lu <loss> Set uplink loss to <rate> (float in 0..1) (no loss)\n"
<< " -Ld <loss> Set downlink loss to <rate> (float in 0..1) (no loss)\n\n"
<< " -h Show this message and quit.\n\n";
if (msg != nullptr) {
cout << msg;
}
cout << endl;
}
static void check_argc(int argc, char **argv, int curr, const char *err) {
if (curr + 3 >= argc) {
show_usage(argv[0], err);
exit(1);
}
}
static tuple<TCPConfig, FdAdapterConfig, bool> get_config(int argc, char **argv) {
TCPConfig c_fsm{};
FdAdapterConfig c_filt{};
int curr = 1;
bool listen = false;
while (argc - curr > 2) {
if (strncmp("-l", argv[curr], 3) == 0) {
listen = true;
curr += 1;
} else if (strncmp("-w", argv[curr], 3) == 0) {
check_argc(argc, argv, curr, "ERROR: -w requires one argument.");
c_fsm.recv_capacity = strtol(argv[curr + 1], nullptr, 0);
curr += 2;
} else if (strncmp("-t", argv[curr], 3) == 0) {
check_argc(argc, argv, curr, "ERROR: -t requires one argument.");
c_fsm.rt_timeout = strtol(argv[curr + 1], nullptr, 0);
curr += 2;
} else if (strncmp("-Lu", argv[curr], 3) == 0) {
check_argc(argc, argv, curr, "ERROR: -Lu requires one argument.");
float lossrate = strtof(argv[curr + 1], nullptr);
using LossRateUpT = decltype(c_filt.loss_rate_up);
c_filt.loss_rate_up =
static_cast<LossRateUpT>(static_cast<float>(numeric_limits<LossRateUpT>::max()) * lossrate);
curr += 2;
} else if (strncmp("-Ld", argv[curr], 3) == 0) {
check_argc(argc, argv, curr, "ERROR: -Lu requires one argument.");
float lossrate = strtof(argv[curr + 1], nullptr);
using LossRateDnT = decltype(c_filt.loss_rate_dn);
c_filt.loss_rate_dn =
static_cast<LossRateDnT>(static_cast<float>(numeric_limits<LossRateDnT>::max()) * lossrate);
curr += 2;
} else if (strncmp("-h", argv[curr], 3) == 0) {
show_usage(argv[0], nullptr);
exit(0);
} else {
show_usage(argv[0], std::string("ERROR: unrecognized option " + std::string(argv[curr])).c_str());
exit(1);
}
}
if (listen) {
c_filt.source = {"0", argv[argc - 1]};
} else {
c_filt.destination = {argv[argc - 2], argv[argc - 1]};
}
return make_tuple(c_fsm, c_filt, listen);
}
int main(int argc, char **argv) {
try {
if (argc < 3) {
show_usage(argv[0], "ERROR: required arguments are missing.");
exit(1);
}
// handle configuration and UDP setup from cmdline arguments
auto [c_fsm, c_filt, listen] = get_config(argc, argv);
// build a TCP FSM on top of the UDP socket
UDPSocket udp_sock;
if (listen) {
udp_sock.bind(c_filt.source);
}
LossyTCPOverUDPSpongeSocket tcp_socket(move(udp_sock));
if (listen) {
tcp_socket.listen_and_accept(c_fsm, c_filt);
} else {
tcp_socket.connect(c_fsm, c_filt);
}
bidirectional_stream_copy(tcp_socket);
tcp_socket.wait_until_closed();
} catch (const exception &e) {
cerr << "Exception: " << e.what() << endl;
return EXIT_FAILURE;
}
return EXIT_SUCCESS;
}
#include "tun.hh"
#include "ipv4_datagram.hh"
#include "parser.hh"
#include "tcp_segment.hh"
#include "util.hh"
#include <cstdlib>
#include <exception>
#include <iostream>
#include <vector>
using namespace std;
int main() {
try {
TunFD tun("tun144");
while (true) {
auto buffer = tun.read();
cout << "\n\n***\n*** Got packet:\n***\n";
hexdump(buffer.data(), buffer.size());
IPv4Datagram ip_dgram;
cout << "attempting to parse as ipv4 datagram... ";
if (ip_dgram.parse(move(buffer)) != ParseResult::NoError) {
cout << "failed.\n";
continue;
}
cout << "success! totlen=" << ip_dgram.header().len << ", IPv4 header contents:\n";
cout << ip_dgram.header().to_string();
if (ip_dgram.header().proto != IPv4Header::PROTO_TCP) {
cout << "\nNot TCP, skipping.\n";
continue;
}
cout << "\nAttempting to parse as a TCP segment... ";
TCPSegment tcp_seg;
if (tcp_seg.parse(ip_dgram.payload(), ip_dgram.header().pseudo_cksum()) != ParseResult::NoError) {
cout << "failed.\n";
continue;
}
cout << "success! payload len=" << tcp_seg.payload().size() << ", TCP header contents:\n";
cout << tcp_seg.header().to_string() << endl;
}
} catch (const exception &e) {
cout << "Exception: " << e.what() << endl;
return EXIT_FAILURE;
}
return EXIT_SUCCESS;
}
#include "parser.hh"
#include "tcp_header.hh"
#include "tcp_segment.hh"
#include "util.hh"
#include <arpa/inet.h>
#include <cstdint>
#include <cstdlib>
#include <cstring>
#include <iomanip>
#include <iostream>
#include <pcap/pcap.h>
#include <sstream>
#include <string>
#include <sys/socket.h>
#include <unistd.h>
#include <vector>
using namespace std;
static void show_usage(const char *arg0, const char *errmsg) {
cout << "Usage: " << arg0 << " [-i <intf>] [-F <file>] [-h|--help] <expression>\n\n"
<< " -i <intf> only capture packets from <intf> (default: all)\n\n"
<< " -F <file> reads in a filter expression from <file>\n"
<< " <expression> is ignored if -F is supplied.\n\n"
<< " -h, --help show this message\n\n"
<< " <expression> a filter expression in pcap-filter(7) syntax\n";
if (errmsg != nullptr) {
cout << '\n' << errmsg;
}
cout << endl;
}
static void check_arg(char *arg0, int argc, int curr, const char *errmsg) {
if (curr + 1 >= argc) {
show_usage(arg0, errmsg);
exit(1);
}
}
static int parse_arguments(int argc, char **argv, char **dev_ptr) {
int curr = 1;
while (curr < argc) {
if (strncmp("-i", argv[curr], 3) == 0) {
check_arg(argv[0], argc, curr, "ERROR: -i requires an argument");
*dev_ptr = argv[curr + 1];
curr += 2;
} else if ((strncmp("-h", argv[curr], 3) == 0) || (strncmp("--help", argv[curr], 7) == 0)) {
show_usage(argv[0], nullptr);
exit(0);
} else {
break;
}
}
return curr;
}
static string inet4_addr(const uint8_t *data) {
char addrbuf[128];
auto *addr = reinterpret_cast<const in_addr *>(data);
if (inet_ntop(AF_INET, addr, static_cast<char *>(addrbuf), 128) == nullptr) {
return "unknown";
}
return string(static_cast<char *>(addrbuf));
}
static string inet6_addr(const uint8_t *data) {
char addrbuf[128];
auto *addr = reinterpret_cast<const in6_addr *>(data);
if (inet_ntop(AF_INET6, addr, static_cast<char *>(addrbuf), 128) == nullptr) {
return "unknown";
}
return string(static_cast<char *>(addrbuf));
}
static int process_ipv4_ipv6(int len, const uint8_t *data, string &src_addr, string &dst_addr) {
// this is either an IPv4 or IPv6 packet, we hope
if (len < 1) {
return -1;
}
int data_offset = 0;
const uint8_t pt = data[0] & 0xf0;
if (pt == 0x40) {
// check packet length and proto
data_offset = (data[0] & 0x0f) * 4;
if (len < data_offset) {
return -1;
}
if (data[9] != 0x11) {
cerr << "Not UDP; ";
return -1;
}
src_addr = inet4_addr(data + 12);
dst_addr = inet4_addr(data + 16);
} else if (pt == 0x60) {
// check packet length
if (len < 42) {
return -1;
}
data_offset = 40;
uint8_t nxt = data[6];
while (nxt != 0x11) {
if (nxt != 0 && nxt != 43 && nxt != 60) {
cerr << "Not UDP or fragmented; ";
return -1;
}
nxt = data[data_offset];
data_offset += 8 * (1 + data[data_offset + 1]);
if (len < data_offset + 2) {
return -1;
}
}
src_addr = inet6_addr(data + 8);
dst_addr = inet6_addr(data + 24);
} else {
return -1;
}
return data_offset + 8; // skip UDP header
}
int main(int argc, char **argv) {
char *dev = nullptr;
const int exp_start = parse_arguments(argc, argv, &dev);
// create pcap handle
if (dev != nullptr) {
cout << "Capturing on interface " << dev;
} else {
cout << "Capturing on all interfaces";
}
pcap_t *p_hdl = nullptr;
const int dl_type = [&] {
char errbuf[PCAP_ERRBUF_SIZE] = {
0,
};
p_hdl = pcap_open_live(dev, 65535, 0, 100, static_cast<char *>(errbuf));
if (p_hdl == nullptr) {
cout << "\nError initiating capture: " << static_cast<char *>(errbuf) << endl;
exit(1);
}
int dlt = pcap_datalink(p_hdl);
// need to handle: DLT_RAW, DLT_NULL, DLT_EN10MB, DLT_LINUX_SLL
if (dlt != DLT_RAW && dlt != DLT_NULL && dlt != DLT_EN10MB && dlt != DLT_LINUX_SLL
#ifdef DLT_LINUX_SLL2
&& dlt != DLT_LINUX_SLL2
#endif
) {
cout << "\nError: unsupported datalink type " << pcap_datalink_val_to_description(dlt) << endl;
exit(1);
}
cout << " (type: " << pcap_datalink_val_to_description(dlt) << ")\n";
return dlt;
}();
// compile and set filter
{
struct bpf_program p_flt {};
stringstream f_stream;
for (int i = exp_start; i < argc; ++i) {
f_stream << argv[i] << ' ';
}
string filter_expression = f_stream.str();
cout << "Using filter expression: " << filter_expression << "\n";
if (pcap_compile(p_hdl, &p_flt, filter_expression.c_str(), 1, PCAP_NETMASK_UNKNOWN) != 0) {
cout << "Error compiling filter expression: " << pcap_geterr(p_hdl) << endl;
return EXIT_FAILURE;
}
if (pcap_setfilter(p_hdl, &p_flt) != 0) {
cout << "Error configuring packet filter: " << pcap_geterr(p_hdl) << endl;
return EXIT_FAILURE;
}
pcap_freecode(&p_flt);
}
int next_ret = 0;
struct pcap_pkthdr *pkt_hdr = nullptr;
const uint8_t *pkt_data = nullptr;
cout << setfill('0');
while ((next_ret = pcap_next_ex(p_hdl, &pkt_hdr, &pkt_data)) >= 0) {
if (next_ret == 0) {
// timeout; just listen again
continue;
}
size_t hdr_off = 0;
int start_off = 0;
// figure out where in the datagram to look based on link type
if (dl_type == DLT_NULL) {
hdr_off = 4;
if (pkt_hdr->caplen < hdr_off) {
cerr << "[INFO] Skipping malformed packet.\n";
continue;
}
const uint8_t pt = pkt_data[3];
if (pt != 2 && pt != 24 && pt != 28 && pt != 30) {
cerr << "[INFO] Skipping non-IP packet.\n";
continue;
}
} else if (dl_type == DLT_EN10MB) {
hdr_off = 14;
if (pkt_hdr->caplen < hdr_off) {
cerr << "[INFO] Skipping malformed packet.\n";
continue;
}
const uint16_t pt = (pkt_data[12] << 8) | pkt_data[13];
if (pt != 0x0800 && pt != 0x86dd) {
cerr << "[INFO] Skipping non-IP packet.\n";
continue;
}
} else if (dl_type == DLT_LINUX_SLL) {
hdr_off = 16;
if (pkt_hdr->caplen < hdr_off) {
cerr << "[INFO] Skipping malformed packet.\n";
continue;
}
const uint16_t pt = (pkt_data[14] << 8) | pkt_data[15];
if (pt != 0x0800 && pt != 0x86dd) {
cerr << "[INFO] Skipping non-IP packet.\n";
continue;
}
#ifdef DLT_LINUX_SLL2
} else if (dl_type == DLT_LINUX_SLL2) {
if (pkt_hdr->caplen < 20) {
cerr << "[INFO] Skipping malformed packet.\n";
continue;
}
const uint16_t pt = (pkt_data[0] << 8) | pkt_data[1];
hdr_off = 20;
if (pt != 0x0800 && pt != 0x86dd) {
cerr << "[INFO] Skipping non-IP packet.\n";
continue;
}
#endif
} else if (dl_type != DLT_RAW) {
cerr << "Mysterious datalink type. Giving up.";
return EXIT_FAILURE;
}
// now actually parse the packet
string src{}, dst{};
if ((start_off = process_ipv4_ipv6(pkt_hdr->caplen - hdr_off, pkt_data + hdr_off, src, dst)) < 0) {
cerr << "Error parsing IPv4/IPv6 packet. Skipping.\n";
continue;
}
// hdr_off + start_off is now the start of the UDP payload
const size_t payload_off = hdr_off + start_off;
const size_t payload_len = pkt_hdr->caplen - payload_off;
string_view payload{reinterpret_cast<const char *>(pkt_data) + payload_off, payload_len};
// try to parse UDP payload as TCP packet
auto seg = TCPSegment{};
if (const auto res = seg.parse(string(payload), 0); res > ParseResult::BadChecksum) {
cout << "(did not recognize TCP header) src: " << src << " dst: " << dst << '\n';
} else {
const TCPHeader &tcp_hdr = seg.header();
uint32_t seqlen = seg.length_in_sequence_space();
cout << src << ':' << tcp_hdr.sport << " > " << dst << ':' << tcp_hdr.dport << "\n Flags ["
<< (tcp_hdr.urg ? "U" : "") << (tcp_hdr.psh ? "P" : "") << (tcp_hdr.rst ? "R" : "")
<< (tcp_hdr.syn ? "S" : "") << (tcp_hdr.fin ? "F" : "") << (tcp_hdr.ack ? "." : "")
<< "] cksum 0x" << hex << setw(4) << tcp_hdr.cksum << dec
<< (res == ParseResult::NoError ? " (correct)" : " (incorrect!)")
<< " seq " << tcp_hdr.seqno;
if (seqlen > 0) {
cout << ':' << (tcp_hdr.seqno + seqlen);
}
cout << " ack " << tcp_hdr.ackno << " win " << tcp_hdr.win << " length " << payload_len << endl;
}
hexdump(payload.data(), payload.size(), 8);
}
pcap_close(p_hdl);
if (next_ret == -1) {
cout << "Error listening for packet: " << pcap_geterr(p_hdl) << endl;
return EXIT_FAILURE;
}
return EXIT_SUCCESS;
}
#include "socket.hh"
#include "util.hh"
#include <cstdlib>
#include <iostream>
using namespace std;
void get_URL(const string &host, const string &path) {
// Your code here.
// You will need to connect to the "http" service on
// the computer whose name is in the "host" string,
// then request the URL path given in the "path" string.
// Then you'll need to print out everything the server sends back,
// (not just one call to read() -- everything) until you reach
// the "eof" (end of file).
cerr << "Function called: get_URL(" << host << ", " << path << ").\n";
cerr << "Warning: get_URL() has not been implemented yet.\n";
}
int main(int argc, char *argv[]) {
try {
if (argc <= 0) {
abort(); // For sticklers: don't try to access argv[0] if argc <= 0.
}
// The program takes two command-line arguments: the hostname and "path" part of the URL.
// Print the usage message unless there are these two arguments (plus the program name
// itself, so arg count = 3 in total).
if (argc != 3) {
cerr << "Usage: " << argv[0] << " HOST PATH\n";
cerr << "\tExample: " << argv[0] << " stanford.edu /class/cs144\n";
return EXIT_FAILURE;
}
// Get the command-line arguments.
const string host = argv[1];
const string path = argv[2];
// Call the student-written function.
get_URL(host, path);
} catch (const exception &e) {
cerr << e.what() << "\n";
return EXIT_FAILURE;
}
return EXIT_SUCCESS;
}
add_sponge_exec (address_dt)
add_sponge_exec (parser_dt)
add_sponge_exec (socket_dt)
#include "address.hh"
#include <cstdlib>
#include <iostream>
#include <stdexcept>
int main() {
try {
#include "address_example_1.cc"
#include "address_example_2.cc"
#include "address_example_3.cc"
if ((google_webserver.port() != 443) || (a_dns_server_numeric != 0x12'47'00'97)) {
throw std::runtime_error("unexpected value");
}
} catch (const std::exception &e) {
std::cerr << "This test requires Internet access and working DNS.\n";
std::cerr << "Error: " << e.what() << "\n";
return EXIT_FAILURE;
}
return EXIT_SUCCESS;
}
const Address google_webserver("www.google.com", "https");
const Address a_dns_server("18.71.0.151", 53);
const uint32_t a_dns_server_numeric = a_dns_server.ipv4_numeric();
#include "parser.hh"
#include <cstdint>
#include <cstdlib>
#include <stdexcept>
#include <vector>
int main() {
try {
#include "parser_example.cc"
} catch (...) {
return EXIT_FAILURE;
}
return EXIT_SUCCESS;
}
const uint32_t val1 = 0xdeadbeef;
const uint16_t val2 = 0xc0c0;
const uint8_t val3 = 0xff;
const uint32_t val4 = 0x0c05fefe;
// first, let's serialize it
std::string buffer;
buffer.push_back(0x32); // manually added to beginning of string
{
NetUnparser p;
p.u32(buffer, val1);
p.u16(buffer, val2);
p.u8(buffer, val3);
p.u32(buffer, val4);
} // p goes out of scope, data is in buffer
// now let's deserialize it
uint8_t out0, out3;
uint32_t out1, out4;
uint16_t out2;
{
NetParser p{std::string(buffer)}; // NOTE: starting at offset 0
out0 = p.u8(); // buffer[0], which we manually set to 0x32 above
out1 = p.u32(); // parse out val1
out2 = p.u16(); // val2
out3 = p.u8(); // val3
out4 = p.u32(); // val4
} // p goes out of scope
if (out0 != 0x32 || out1 != val1 || out2 != val2 || out3 != val3 || out4 != val4) {
throw std::runtime_error("bad parse");
}
#include "socket.hh"
#include "address.hh"
#include "util.hh"
#include <array>
#include <cstdlib>
#include <random>
#include <stdexcept>
#include <sys/socket.h>
#include <vector>
int main() {
try {
{
#include "socket_example_1.cc"
} {
#include "socket_example_2.cc"
} {
#include "socket_example_3.cc"
}
} catch (...) {
return EXIT_FAILURE;
}
return EXIT_SUCCESS;
}
const uint16_t portnum = ((std::random_device()()) % 50000) + 1025;
// create a UDP socket and bind it to a local address
UDPSocket sock1{};
sock1.bind(Address("127.0.0.1", portnum));
// create another UDP socket and send a datagram to the first socket without connecting
UDPSocket sock2{};
sock2.sendto(Address("127.0.0.1", portnum), "hi there");
// receive sent datagram, connect the socket to the peer's address, and send a response
auto recvd = sock1.recv();
sock1.connect(recvd.source_address);
sock1.send("hi yourself");
auto recvd2 = sock2.recv();
if (recvd.payload != "hi there" || recvd2.payload != "hi yourself") {
throw std::runtime_error("wrong data received");
}
const uint16_t portnum = ((std::random_device()()) % 50000) + 1025;
// create a TCP socket, bind it to a local address, and listen
TCPSocket sock1{};
sock1.bind(Address("127.0.0.1", portnum));
sock1.listen(1);
// create another socket and connect to the first one
TCPSocket sock2{};
sock2.connect(Address("127.0.0.1", portnum));
// accept the connection
auto sock3 = sock1.accept();
sock3.write("hi there");
auto recvd = sock2.read();
sock2.write("hi yourself");
auto recvd2 = sock3.read();
sock1.close(); // don't need to accept any more connections
sock2.close(); // you can call close(2) on a socket
sock3.shutdown(SHUT_RDWR); // you can also shutdown(2) a socket
if (recvd != "hi there" || recvd2 != "hi yourself") {
throw std::runtime_error("wrong data received");
}
// create a pair of stream sockets
std::array<int, 2> fds{};
SystemCall("socketpair", ::socketpair(AF_UNIX, SOCK_STREAM, 0, fds.data()));
LocalStreamSocket pipe1{FileDescriptor(fds[0])}, pipe2{FileDescriptor(fds[1])};
pipe1.write("hi there");
auto recvd = pipe2.read();
pipe2.write("hi yourself");
auto recvd2 = pipe1.read();
if (recvd != "hi there" || recvd2 != "hi yourself") {
throw std::runtime_error("wrong data received");
}
# Doxyfile 1.8.14
DOXYFILE_ENCODING = UTF-8
PROJECT_NAME = "Sponge"
PROJECT_BRIEF = "CS144's user-space TCP library"
PROJECT_LOGO = "@PROJECT_SOURCE_DIR@/etc/sponge_small.png"
INPUT = @PROJECT_SOURCE_DIR@
RECURSIVE = YES
EXCLUDE = @PROJECT_SOURCE_DIR@/etc @PROJECT_SOURCE_DIR@/build @PROJECT_SOURCE_DIR@/tests @PROJECT_SOURCE_DIR@/writeups
OUTPUT_DIRECTORY = "@PROJECT_BINARY_DIR@/doc"
CASE_SENSE_NAMES = NO
SORT_BRIEF_DOCS = YES
SORT_MEMBERS_CTORS_1ST = YES
SHOW_NAMESPACES = NO
USE_MDFILE_AS_MAINPAGE = @PROJECT_SOURCE_DIR@/README.md
SOURCE_BROWSER = YES
EXT_LINKS_IN_WINDOW = YES
INCLUDE_PATH = @PROJECT_SOURCE_DIR@/libsponge
TAGFILES = "@PROJECT_SOURCE_DIR@/etc/cppreference-doxygen-web.tag.xml=https://en.cppreference.com/w/"
TAGFILES += "@PROJECT_SOURCE_DIR@/etc/linux-man-doxygen-web.tag.xml=http://man7.org/linux/man-pages/"
TAGFILES += "@PROJECT_SOURCE_DIR@/etc/rfc-doxygen-web.tag.xml=https://tools.ietf.org/html/"
HIDE_UNDOC_RELATIONS = NO
INLINE_GROUPED_CLASSES = YES
INLINE_SIMPLE_STRUCTS = YES
HTML_COLORSTYLE_HUE = 204
HTML_COLORSTYLE_SAT = 120
HTML_COLORSTYLE_GAMMA = 60
HTML_EXTRA_STYLESHEET = "@PROJECT_SOURCE_DIR@/etc/sponge_doxygen.css"
GENERATE_LATEX = NO
EXAMPLE_PATH = "@PROJECT_SOURCE_DIR@/doctests"
# cmake detects whether dot is available
HAVE_DOT = @DOXYGEN_DOT_FOUND@
CLASS_GRAPH = YES
TEMPLATE_RELATIONS = YES
DOT_IMAGE_FORMAT = png
INTERACTIVE_SVG = NO
COLLABORATION_GRAPH = NO
# ??? temporary
EXTRACT_ALL = YES
EXTRACT_PRIVATE = YES
EXTRACT_STATIC = YES
EXTRACT_ANON_NSPACES = YES
# do u liek eclips
GENERATE_ECLIPSEHELP = NO
ECLIPSE_DOC_ID = edu.stanford.cs144.sponge
find_library (LIBPCAP pcap)
find_library (LIBPTHREAD pthread)
macro (add_sponge_exec exec_name)
add_executable ("${exec_name}" "${exec_name}.cc")
target_link_libraries ("${exec_name}" ${ARGN} sponge ${LIBPTHREAD})
endmacro (add_sponge_exec)
set (default_build_type "Release")
if (NOT (CMAKE_BUILD_TYPE_SHADOW STREQUAL CMAKE_BUILD_TYPE))
if (NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES)
message (STATUS "Setting build type to '${default_build_type}'")
set (CMAKE_BUILD_TYPE "${default_build_type}" CACHE STRING "Choose the type of build." FORCE)
else ()
message (STATUS "Building in ${CMAKE_BUILD_TYPE} mode as requested.")
endif ()
set (CMAKE_BUILD_TYPE_SHADOW ${CMAKE_BUILD_TYPE} CACHE STRING "used to detect changes in build type" FORCE)
endif ()
message (STATUS " NOTE: You can choose a build type by calling cmake with one of:")
message (STATUS " -DCMAKE_BUILD_TYPE=Release -- full optimizations")
message (STATUS " -DCMAKE_BUILD_TYPE=Debug -- better debugging experience in gdb")
message (STATUS " -DCMAKE_BUILD_TYPE=RelASan -- full optimizations plus address and undefined-behavior sanitizers")
message (STATUS " -DCMAKE_BUILD_TYPE=DebugASan -- debug plus sanitizers")
set (CMAKE_CXX_STANDARD 17)
set (CMAKE_EXPORT_COMPILE_COMMANDS ON)
set (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++17 -g -pedantic -pedantic-errors -Werror -Wall -Wextra -Wshadow -Wpointer-arith -Wcast-qual -Wformat=2 -Weffc++ -Wold-style-cast")
# check for supported compiler versions
set (IS_GNU_COMPILER ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "GNU"))
set (IS_CLANG_COMPILER ("${CMAKE_CXX_COMPILER_ID}" MATCHES "[Cc][Ll][Aa][Nn][Gg]"))
set (CXX_VERSION_LT_6 ("${CMAKE_CXX_COMPILER_VERSION}" VERSION_LESS 6))
set (CXX_VERSION_LT_8 ("${CMAKE_CXX_COMPILER_VERSION}" VERSION_LESS 8))
if ((${IS_GNU_COMPILER} AND ${CXX_VERSION_LT_8}) OR (${IS_CLANG_COMPILER} AND ${CXX_VERSION_LT_6}))
message (FATAL_ERROR "You must compile this project with g++ >= 8 or clang >= 6.")
endif ()
if (${IS_CLANG_COMPILER})
set (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wloop-analysis")
endif ()
# add some flags for the Release, Debug, and DebugSan modes
set (CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -ggdb3 -Og")
set (CMAKE_CXX_FLAGS_DEBUGASAN "${CMAKE_CXX_FLAGS_DEBUG} -fsanitize=undefined -fsanitize=address")
set (CMAKE_CXX_FLAGS_RELASAN "${CMAKE_CXX_FLAGS_RELEASE} -fsanitize=undefined -fsanitize=address")
if (NOT CLANG_FORMAT)
if (DEFINED ENV{CLANG_FORMAT})
set (CLANG_FORMAT_TMP $ENV{CLANG_FORMAT})
else (NOT DEFINED ENV{CLANG_FORMAT})
set (CLANG_FORMAT_TMP clang-format-6.0)
endif (DEFINED ENV{CLANG_FORMAT})
# figure out which version of clang-format we're using
execute_process (COMMAND ${CLANG_FORMAT_TMP} --version RESULT_VARIABLE CLANG_FORMAT_RESULT OUTPUT_VARIABLE CLANG_FORMAT_VERSION)
if (${CLANG_FORMAT_RESULT} EQUAL 0)
string (REGEX MATCH "version [0-9]" CLANG_FORMAT_VERSION ${CLANG_FORMAT_VERSION})
message (STATUS "Found clang-format " ${CLANG_FORMAT_VERSION})
set(CLANG_FORMAT ${CLANG_FORMAT_TMP} CACHE STRING "clang-format executable name")
endif (${CLANG_FORMAT_RESULT} EQUAL 0)
endif (NOT CLANG_FORMAT)
if (DEFINED CLANG_FORMAT)
file (GLOB_RECURSE ALL_CC_FILES *.cc)
file (GLOB_RECURSE ALL_HH_FILES *.hh)
add_custom_target (format ${CLANG_FORMAT} -i ${ALL_CC_FILES} ${ALL_HH_FILES} COMMENT "Formatted all source files.")
else (NOT DEFINED CLANG_FORMAT)
add_custom_target (format echo "Could not find clang-format. Please install and re-run cmake")
endif (DEFINED CLANG_FORMAT)
if (NOT CLANG_TIDY)
if (DEFINED ENV{CLANG_TIDY})
set (CLANG_TIDY_TMP $ENV{CLANG_TIDY})
else (NOT DEFINED ENV{CLANG_TIDY})
set (CLANG_TIDY_TMP clang-tidy)
endif (DEFINED ENV{CLANG_TIDY})
# is clang-tidy available?
execute_process (COMMAND ${CLANG_TIDY_TMP} --version RESULT_VARIABLE CLANG_TIDY_RESULT OUTPUT_VARIABLE CLANG_TIDY_VERSION)
if (${CLANG_TIDY_RESULT} EQUAL 0)
string (REGEX MATCH "version [0-9]" CLANG_TIDY_VERSION ${CLANG_TIDY_VERSION})
message (STATUS "Found clang-tidy " ${CLANG_TIDY_VERSION})
set (CLANG_TIDY ${CLANG_TIDY_TMP} CACHE STRING "clang-tidy executable name")
endif (${CLANG_TIDY_RESULT} EQUAL 0)
endif (NOT CLANG_TIDY)
if (DEFINED CLANG_TIDY)
file (GLOB_RECURSE ALL_CC_FILES *.cc)
set (CLANG_TIDY_CHECKS "'*,-fuchsia-*,-hicpp-signed-bitwise,-google-build-using-namespace,-android*,-cppcoreguidelines-pro-bounds-pointer-arithmetic,-google-runtime-references,-readability-avoid-const-params-in-decls,-llvm-header-guard'")
foreach (tidy_target ${ALL_CC_FILES})
get_filename_component (basename ${tidy_target} NAME)
get_filename_component (dirname ${tidy_target} DIRECTORY)
get_filename_component (basedir ${dirname} NAME)
set (tidy_target_name "${basedir}__${basename}")
set (tidy_command ${CLANG_TIDY} -checks=${CLANG_TIDY_CHECKS} -header-filter=.* -p=${PROJECT_BINARY_DIR} ${tidy_target})
add_custom_target (tidy_quiet_${tidy_target_name} ${tidy_command} 2>/dev/null)
add_custom_target (tidy_${tidy_target_name} ${tidy_command})
list (APPEND ALL_TIDY_TARGETS tidy_quiet_${tidy_target_name})
list (APPEND ALL_TIDY_VERBOSE_TARGETS tidy_${tidy_target_name})
endforeach (tidy_target)
add_custom_target (tidy DEPENDS ${ALL_TIDY_TARGETS})
add_custom_target (tidy_verbose DEPENDS ${ALL_TIDY_VERBOSE_TARGETS})
endif (DEFINED CLANG_TIDY)
if (NOT CPPCHECK)
if (DEFINED ENV{CPPCHECK})
set (CPPCHECK_TMP $ENV{CPPCHECK})
else (NOT DEFINED ENV{CPPCHECK})
set (CPPCHECK_TMP cppcheck)
endif ()
# is cppcheck available?
execute_process (COMMAND ${CPPCHECK_TMP} --version RESULT_VARIABLE CPPCHECK_RESULT OUTPUT_VARIABLE CPPCHECK_OUTPUT)
if (${CPPCHECK_RESULT} EQUAL 0)
message (STATUS "Found cppcheck")
set (CPPCHECK ${CPPCHECK_TMP} CACHE STRING "cppcheck executable name")
endif()
endif (NOT CPPCHECK)
if (DEFINED CPPCHECK)
add_custom_target (cppcheck ${CPPCHECK} --enable=all --project="${PROJECT_BINARY_DIR}/compile_commands.json")
endif (DEFINED CPPCHECK)
This source diff could not be displayed because it is too large. You can view the blob instead.
find_package (Doxygen)
if (DOXYGEN_FOUND)
if (Doxygen_dot_FOUND)
set (DOXYGEN_DOT_FOUND YES)
else (NOT Doxygen_dot_FOUND)
set (DOXYGEN_DOT_FOUND NO)
endif (Doxygen_dot_FOUND)
configure_file ("${PROJECT_SOURCE_DIR}/etc/Doxyfile.in" "${PROJECT_BINARY_DIR}/Doxyfile" @ONLY)
add_custom_target (doc "${DOXYGEN_EXECUTABLE}" "${PROJECT_BINARY_DIR}/Doxyfile"
WORKING_DIRECTORY "${PROJECT_BINARY_DIR}"
COMMENT "Generate docs using Doxygen" VERBATIM)
endif ()
This source diff could not be displayed because it is too large. You can view the blob instead.
<?xml version="1.0" encoding="UTF-8" standalone="yes" ?>
<tagfile>
<compound kind="namespace"><name>rfc</name><filename></filename>
<member kind="function">
<type></type>
<name>rfc791</name>
<anchorfile>rfc791</anchorfile>
<anchor></anchor>
<arglist></arglist>
</member>
<member kind="function">
<type></type>
<name>rfc793</name>
<anchorfile>rfc793</anchorfile>
<anchor></anchor>
<arglist></arglist>
</member>
<member kind="function">
<type></type>
<name>rfc6298</name>
<anchorfile>rfc6298</anchorfile>
<anchor></anchor>
<arglist></arglist>
</member>
</compound>
</tagfile>
html, body { background-color: #F8F8F8; }
div.textblock>p,div.memdoc>p,dl.section.note>dd { max-width: 750px; }
div.line,pre.fragment { line-height: 1.5; }
div.contents {
padding: 12px;
margin-top: auto;
margin-bottom: auto;
margin-left: 3%;
margin-right: 6%;
border-radius: 8px;
}
enable_testing ()
set (LOSS_RATE 0.1)
add_test(NAME t_wrapping_ints_cmp COMMAND wrapping_integers_cmp)
add_test(NAME t_wrapping_ints_unwrap COMMAND wrapping_integers_unwrap)
add_test(NAME t_wrapping_ints_wrap COMMAND wrapping_integers_wrap)
add_test(NAME t_recv_connect COMMAND recv_connect)
add_test(NAME t_recv_transmit COMMAND recv_transmit)
add_test(NAME t_recv_window COMMAND recv_window)
add_test(NAME t_recv_reorder COMMAND recv_reorder)
add_test(NAME t_recv_close COMMAND recv_close)
add_test(NAME t_send_connect COMMAND send_connect)
add_test(NAME t_send_transmit COMMAND send_transmit)
add_test(NAME t_send_retx COMMAND send_retx)
add_test(NAME t_send_window COMMAND send_window)
add_test(NAME t_send_ack COMMAND send_ack)
add_test(NAME t_send_close COMMAND send_close)
add_test(NAME t_strm_reassem_cap COMMAND fsm_stream_reassembler_cap)
add_test(NAME t_strm_reassem_single COMMAND fsm_stream_reassembler_single)
add_test(NAME t_strm_reassem_seq COMMAND fsm_stream_reassembler_seq)
add_test(NAME t_strm_reassem_dup COMMAND fsm_stream_reassembler_dup)
add_test(NAME t_strm_reassem_holes COMMAND fsm_stream_reassembler_holes)
add_test(NAME t_strm_reassem_many COMMAND fsm_stream_reassembler_many)
add_test(NAME t_strm_reassem_overlapping COMMAND fsm_stream_reassembler_overlapping)
add_test(NAME t_strm_reassem_win COMMAND fsm_stream_reassembler_win)
add_test(NAME t_byte_stream_construction COMMAND byte_stream_construction)
add_test(NAME t_byte_stream_one_write COMMAND byte_stream_one_write)
add_test(NAME t_byte_stream_two_writes COMMAND byte_stream_two_writes)
add_test(NAME t_byte_stream_capacity COMMAND byte_stream_capacity)
add_test(NAME t_byte_stream_many_writes COMMAND byte_stream_many_writes)
add_test(NAME t_webget COMMAND "${PROJECT_SOURCE_DIR}/tests/webget_t.sh")
add_test(NAME t_tcp_parser COMMAND tcp_parser "${PROJECT_SOURCE_DIR}/tests/ipv4_parser.data")
add_test(NAME t_ipv4_parser COMMAND ipv4_parser "${PROJECT_SOURCE_DIR}/tests/ipv4_parser.data")
add_test(NAME t_active_close COMMAND fsm_active_close)
add_test(NAME t_passive_close COMMAND fsm_passive_close)
add_test(NAME ec_ack_rst COMMAND fsm_ack_rst)
add_test(NAME t_ack_rst COMMAND fsm_ack_rst_relaxed)
add_test(NAME ec_ack_rst_win COMMAND fsm_ack_rst_win)
add_test(NAME t_ack_rst_win COMMAND fsm_ack_rst_win_relaxed)
add_test(NAME ec_connect COMMAND fsm_connect)
add_test(NAME t_connect COMMAND fsm_connect_relaxed)
add_test(NAME ec_listen COMMAND fsm_listen)
add_test(NAME t_listen COMMAND fsm_listen_relaxed)
add_test(NAME t_winsize COMMAND fsm_winsize)
add_test(NAME ec_retx COMMAND fsm_retx)
add_test(NAME t_retx COMMAND fsm_retx_relaxed)
add_test(NAME t_retx_win COMMAND fsm_retx_win)
add_test(NAME t_loopback COMMAND fsm_loopback)
add_test(NAME t_loopback_win COMMAND fsm_loopback_win)
add_test(NAME t_reorder COMMAND fsm_reorder)
add_test(NAME t_address_dt COMMAND address_dt)
add_test(NAME t_parser_dt COMMAND parser_dt)
add_test(NAME t_socket_dt COMMAND socket_dt)
add_test(NAME t_udp_client_send COMMAND "${PROJECT_SOURCE_DIR}/txrx.sh" -ucS)
add_test(NAME t_udp_server_send COMMAND "${PROJECT_SOURCE_DIR}/txrx.sh" -usS)
add_test(NAME t_udp_client_recv COMMAND "${PROJECT_SOURCE_DIR}/txrx.sh" -ucR)
add_test(NAME t_udp_server_recv COMMAND "${PROJECT_SOURCE_DIR}/txrx.sh" -usR)
add_test(NAME t_udp_client_dupl COMMAND "${PROJECT_SOURCE_DIR}/txrx.sh" -ucD)
add_test(NAME t_udp_server_dupl COMMAND "${PROJECT_SOURCE_DIR}/txrx.sh" -usD)
add_test(NAME t_ucS_1M_32k COMMAND "${PROJECT_SOURCE_DIR}/txrx.sh" -ucSd 1M -w 32K)
add_test(NAME t_ucS_128K_8K COMMAND "${PROJECT_SOURCE_DIR}/txrx.sh" -ucSd 128K -w 8K)
add_test(NAME t_ucS_16_1 COMMAND "${PROJECT_SOURCE_DIR}/txrx.sh" -ucSd 16 -w 1)
add_test(NAME t_ucS_32K_d COMMAND "${PROJECT_SOURCE_DIR}/txrx.sh" -ucSd 32K)
add_test(NAME t_ucR_1M_32k COMMAND "${PROJECT_SOURCE_DIR}/txrx.sh" -ucRd 1M -w 32K)
add_test(NAME t_ucR_128K_8K COMMAND "${PROJECT_SOURCE_DIR}/txrx.sh" -ucRd 128K -w 8K)
add_test(NAME t_ucR_16_1 COMMAND "${PROJECT_SOURCE_DIR}/txrx.sh" -ucRd 16 -w 1)
add_test(NAME t_ucR_32K_d COMMAND "${PROJECT_SOURCE_DIR}/txrx.sh" -ucRd 32K)
add_test(NAME t_ucD_1M_32k COMMAND "${PROJECT_SOURCE_DIR}/txrx.sh" -ucDd 1M -w 32K)
add_test(NAME t_ucD_128K_8K COMMAND "${PROJECT_SOURCE_DIR}/txrx.sh" -ucDd 128K -w 8K)
add_test(NAME t_ucD_16_1 COMMAND "${PROJECT_SOURCE_DIR}/txrx.sh" -ucDd 16 -w 1)
add_test(NAME t_ucD_32K_d COMMAND "${PROJECT_SOURCE_DIR}/txrx.sh" -ucDd 32K)
add_test(NAME t_usS_1M_32k COMMAND "${PROJECT_SOURCE_DIR}/txrx.sh" -usSd 1M -w 32K)
add_test(NAME t_usS_128K_8K COMMAND "${PROJECT_SOURCE_DIR}/txrx.sh" -usSd 128K -w 8K)
add_test(NAME t_usS_16_1 COMMAND "${PROJECT_SOURCE_DIR}/txrx.sh" -usSd 16 -w 1)
add_test(NAME t_usS_32K_d COMMAND "${PROJECT_SOURCE_DIR}/txrx.sh" -usSd 32K)
add_test(NAME t_usR_1M_32k COMMAND "${PROJECT_SOURCE_DIR}/txrx.sh" -usRd 1M -w 32K)
add_test(NAME t_usR_128K_8K COMMAND "${PROJECT_SOURCE_DIR}/txrx.sh" -usRd 128K -w 8K)
add_test(NAME t_usR_16_1 COMMAND "${PROJECT_SOURCE_DIR}/txrx.sh" -usRd 16 -w 1)
add_test(NAME t_usR_32K_d COMMAND "${PROJECT_SOURCE_DIR}/txrx.sh" -usRd 32K)
add_test(NAME t_usD_1M_32k COMMAND "${PROJECT_SOURCE_DIR}/txrx.sh" -usDd 1M -w 32K)
add_test(NAME t_usD_128K_8K COMMAND "${PROJECT_SOURCE_DIR}/txrx.sh" -usDd 128K -w 8K)
add_test(NAME t_usD_16_1 COMMAND "${PROJECT_SOURCE_DIR}/txrx.sh" -usDd 16 -w 1)
add_test(NAME t_usD_32K_d COMMAND "${PROJECT_SOURCE_DIR}/txrx.sh" -usDd 32K)
add_test(NAME t_ucS_128K_8K_l COMMAND "${PROJECT_SOURCE_DIR}/txrx.sh" -ucSd 128K -w 8K -l ${LOSS_RATE})
add_test(NAME t_ucS_128K_8K_L COMMAND "${PROJECT_SOURCE_DIR}/txrx.sh" -ucSd 128K -w 8K -L ${LOSS_RATE})
add_test(NAME t_ucS_128K_8K_lL COMMAND "${PROJECT_SOURCE_DIR}/txrx.sh" -ucSd 128K -w 8K -l ${LOSS_RATE} -L ${LOSS_RATE})
add_test(NAME t_ucR_128K_8K_l COMMAND "${PROJECT_SOURCE_DIR}/txrx.sh" -ucRd 128K -w 8K -l ${LOSS_RATE})
add_test(NAME t_ucR_128K_8K_L COMMAND "${PROJECT_SOURCE_DIR}/txrx.sh" -ucRd 128K -w 8K -L ${LOSS_RATE})
add_test(NAME t_ucR_128K_8K_lL COMMAND "${PROJECT_SOURCE_DIR}/txrx.sh" -ucRd 128K -w 8K -l ${LOSS_RATE} -L ${LOSS_RATE})
add_test(NAME t_ucD_128K_8K_l COMMAND "${PROJECT_SOURCE_DIR}/txrx.sh" -ucDd 128K -w 8K -l ${LOSS_RATE})
add_test(NAME t_ucD_128K_8K_L COMMAND "${PROJECT_SOURCE_DIR}/txrx.sh" -ucDd 128K -w 8K -L ${LOSS_RATE})
add_test(NAME t_ucD_128K_8K_lL COMMAND "${PROJECT_SOURCE_DIR}/txrx.sh" -ucDd 128K -w 8K -l ${LOSS_RATE} -L ${LOSS_RATE})
add_test(NAME t_usS_128K_8K_l COMMAND "${PROJECT_SOURCE_DIR}/txrx.sh" -usSd 128K -w 8K -l ${LOSS_RATE})
add_test(NAME t_usS_128K_8K_L COMMAND "${PROJECT_SOURCE_DIR}/txrx.sh" -usSd 128K -w 8K -L ${LOSS_RATE})
add_test(NAME t_usS_128K_8K_lL COMMAND "${PROJECT_SOURCE_DIR}/txrx.sh" -usSd 128K -w 8K -l ${LOSS_RATE} -L ${LOSS_RATE})
add_test(NAME t_usR_128K_8K_l COMMAND "${PROJECT_SOURCE_DIR}/txrx.sh" -usRd 128K -w 8K -l ${LOSS_RATE})
add_test(NAME t_usR_128K_8K_L COMMAND "${PROJECT_SOURCE_DIR}/txrx.sh" -usRd 128K -w 8K -L ${LOSS_RATE})
add_test(NAME t_usR_128K_8K_lL COMMAND "${PROJECT_SOURCE_DIR}/txrx.sh" -usRd 128K -w 8K -l ${LOSS_RATE} -L ${LOSS_RATE})
add_test(NAME t_usD_128K_8K_l COMMAND "${PROJECT_SOURCE_DIR}/txrx.sh" -usDd 128K -w 8K -l ${LOSS_RATE})
add_test(NAME t_usD_128K_8K_L COMMAND "${PROJECT_SOURCE_DIR}/txrx.sh" -usDd 128K -w 8K -L ${LOSS_RATE})
add_test(NAME t_usD_128K_8K_lL COMMAND "${PROJECT_SOURCE_DIR}/txrx.sh" -usDd 128K -w 8K -l ${LOSS_RATE} -L ${LOSS_RATE})
add_test(NAME t_ipv4_client_send COMMAND "${PROJECT_SOURCE_DIR}/txrx.sh" -icS)
add_test(NAME t_ipv4_server_send COMMAND "${PROJECT_SOURCE_DIR}/txrx.sh" -isS)
add_test(NAME t_ipv4_client_recv COMMAND "${PROJECT_SOURCE_DIR}/txrx.sh" -icR)
add_test(NAME t_ipv4_server_recv COMMAND "${PROJECT_SOURCE_DIR}/txrx.sh" -isR)
add_test(NAME t_ipv4_client_dupl COMMAND "${PROJECT_SOURCE_DIR}/txrx.sh" -icD)
add_test(NAME t_ipv4_server_dupl COMMAND "${PROJECT_SOURCE_DIR}/txrx.sh" -isD)
add_test(NAME t_icS_1M_32k COMMAND "${PROJECT_SOURCE_DIR}/txrx.sh" -icSd 1M -w 32K)
add_test(NAME t_icS_128K_8K COMMAND "${PROJECT_SOURCE_DIR}/txrx.sh" -icSd 128K -w 8K)
add_test(NAME t_icS_16_1 COMMAND "${PROJECT_SOURCE_DIR}/txrx.sh" -icSd 16 -w 1)
add_test(NAME t_icS_32K_d COMMAND "${PROJECT_SOURCE_DIR}/txrx.sh" -icSd 32K)
add_test(NAME t_icR_1M_32k COMMAND "${PROJECT_SOURCE_DIR}/txrx.sh" -icRd 1M -w 32K)
add_test(NAME t_icR_128K_8K COMMAND "${PROJECT_SOURCE_DIR}/txrx.sh" -icRd 128K -w 8K)
add_test(NAME t_icR_16_1 COMMAND "${PROJECT_SOURCE_DIR}/txrx.sh" -icRd 16 -w 1)
add_test(NAME t_icR_32K_d COMMAND "${PROJECT_SOURCE_DIR}/txrx.sh" -icRd 32K)
add_test(NAME t_icD_1M_32k COMMAND "${PROJECT_SOURCE_DIR}/txrx.sh" -icDd 1M -w 32K)
add_test(NAME t_icD_128K_8K COMMAND "${PROJECT_SOURCE_DIR}/txrx.sh" -icDd 128K -w 8K)
add_test(NAME t_icD_16_1 COMMAND "${PROJECT_SOURCE_DIR}/txrx.sh" -icDd 16 -w 1)
add_test(NAME t_icD_32K_d COMMAND "${PROJECT_SOURCE_DIR}/txrx.sh" -icDd 32K)
add_test(NAME t_isS_1M_32k COMMAND "${PROJECT_SOURCE_DIR}/txrx.sh" -isSd 1M -w 32K)
add_test(NAME t_isS_128K_8K COMMAND "${PROJECT_SOURCE_DIR}/txrx.sh" -isSd 128K -w 8K)
add_test(NAME t_isS_16_1 COMMAND "${PROJECT_SOURCE_DIR}/txrx.sh" -isSd 16 -w 1)
add_test(NAME t_isS_32K_d COMMAND "${PROJECT_SOURCE_DIR}/txrx.sh" -isSd 32K)
add_test(NAME t_isR_1M_32k COMMAND "${PROJECT_SOURCE_DIR}/txrx.sh" -isRd 1M -w 32K)
add_test(NAME t_isR_128K_8K COMMAND "${PROJECT_SOURCE_DIR}/txrx.sh" -isRd 128K -w 8K)
add_test(NAME t_isR_16_1 COMMAND "${PROJECT_SOURCE_DIR}/txrx.sh" -isRd 16 -w 1)
add_test(NAME t_isR_32K_d COMMAND "${PROJECT_SOURCE_DIR}/txrx.sh" -isRd 32K)
add_test(NAME t_isD_1M_32k COMMAND "${PROJECT_SOURCE_DIR}/txrx.sh" -isDd 1M -w 32K)
add_test(NAME t_isD_128K_8K COMMAND "${PROJECT_SOURCE_DIR}/txrx.sh" -isDd 128K -w 8K)
add_test(NAME t_isD_16_1 COMMAND "${PROJECT_SOURCE_DIR}/txrx.sh" -isDd 16 -w 1)
add_test(NAME t_isD_32K_d COMMAND "${PROJECT_SOURCE_DIR}/txrx.sh" -isDd 32K)
add_test(NAME t_icS_128K_8K_l COMMAND "${PROJECT_SOURCE_DIR}/txrx.sh" -icSd 128K -w 8K -l ${LOSS_RATE})
add_test(NAME t_icS_128K_8K_L COMMAND "${PROJECT_SOURCE_DIR}/txrx.sh" -icSd 128K -w 8K -L ${LOSS_RATE})
add_test(NAME t_icS_128K_8K_lL COMMAND "${PROJECT_SOURCE_DIR}/txrx.sh" -icSd 128K -w 8K -l ${LOSS_RATE} -L ${LOSS_RATE})
add_test(NAME t_icR_128K_8K_l COMMAND "${PROJECT_SOURCE_DIR}/txrx.sh" -icRd 128K -w 8K -l ${LOSS_RATE})
add_test(NAME t_icR_128K_8K_L COMMAND "${PROJECT_SOURCE_DIR}/txrx.sh" -icRd 128K -w 8K -L ${LOSS_RATE})
add_test(NAME t_icR_128K_8K_lL COMMAND "${PROJECT_SOURCE_DIR}/txrx.sh" -icRd 128K -w 8K -l ${LOSS_RATE} -L ${LOSS_RATE})
add_test(NAME t_icD_128K_8K_l COMMAND "${PROJECT_SOURCE_DIR}/txrx.sh" -icDd 128K -w 8K -l ${LOSS_RATE})
add_test(NAME t_icD_128K_8K_L COMMAND "${PROJECT_SOURCE_DIR}/txrx.sh" -icDd 128K -w 8K -L ${LOSS_RATE})
add_test(NAME t_icD_128K_8K_lL COMMAND "${PROJECT_SOURCE_DIR}/txrx.sh" -icDd 128K -w 8K -l ${LOSS_RATE} -L ${LOSS_RATE})
add_test(NAME t_isS_128K_8K_l COMMAND "${PROJECT_SOURCE_DIR}/txrx.sh" -isSd 128K -w 8K -l ${LOSS_RATE})
add_test(NAME t_isS_128K_8K_L COMMAND "${PROJECT_SOURCE_DIR}/txrx.sh" -isSd 128K -w 8K -L ${LOSS_RATE})
add_test(NAME t_isS_128K_8K_lL COMMAND "${PROJECT_SOURCE_DIR}/txrx.sh" -isSd 128K -w 8K -l ${LOSS_RATE} -L ${LOSS_RATE})
add_test(NAME t_isR_128K_8K_l COMMAND "${PROJECT_SOURCE_DIR}/txrx.sh" -isRd 128K -w 8K -l ${LOSS_RATE})
add_test(NAME t_isR_128K_8K_L COMMAND "${PROJECT_SOURCE_DIR}/txrx.sh" -isRd 128K -w 8K -L ${LOSS_RATE})
add_test(NAME t_isR_128K_8K_lL COMMAND "${PROJECT_SOURCE_DIR}/txrx.sh" -isRd 128K -w 8K -l ${LOSS_RATE} -L ${LOSS_RATE})
add_test(NAME t_isD_128K_8K_l COMMAND "${PROJECT_SOURCE_DIR}/txrx.sh" -isDd 128K -w 8K -l ${LOSS_RATE})
add_test(NAME t_isD_128K_8K_L COMMAND "${PROJECT_SOURCE_DIR}/txrx.sh" -isDd 128K -w 8K -L ${LOSS_RATE})
add_test(NAME t_isD_128K_8K_lL COMMAND "${PROJECT_SOURCE_DIR}/txrx.sh" -isDd 128K -w 8K -l ${LOSS_RATE} -L ${LOSS_RATE})
add_test(NAME t_icnS_128K_8K_l COMMAND "${PROJECT_SOURCE_DIR}/txrx.sh" -icSnd 128K -w 8K -l ${LOSS_RATE})
add_test(NAME t_icnS_128K_8K_L COMMAND "${PROJECT_SOURCE_DIR}/txrx.sh" -icSnd 128K -w 8K -L ${LOSS_RATE})
add_test(NAME t_icnS_128K_8K_lL COMMAND "${PROJECT_SOURCE_DIR}/txrx.sh" -icSnd 128K -w 8K -l ${LOSS_RATE} -L ${LOSS_RATE})
add_test(NAME t_icnR_128K_8K_l COMMAND "${PROJECT_SOURCE_DIR}/txrx.sh" -icRnd 128K -w 8K -l ${LOSS_RATE})
add_test(NAME t_icnR_128K_8K_L COMMAND "${PROJECT_SOURCE_DIR}/txrx.sh" -icRnd 128K -w 8K -L ${LOSS_RATE})
add_test(NAME t_icnR_128K_8K_lL COMMAND "${PROJECT_SOURCE_DIR}/txrx.sh" -icRnd 128K -w 8K -l ${LOSS_RATE} -L ${LOSS_RATE})
add_test(NAME t_icnD_128K_8K_l COMMAND "${PROJECT_SOURCE_DIR}/txrx.sh" -icDnd 128K -w 8K -l ${LOSS_RATE})
add_test(NAME t_icnD_128K_8K_L COMMAND "${PROJECT_SOURCE_DIR}/txrx.sh" -icDnd 128K -w 8K -L ${LOSS_RATE})
add_test(NAME t_icnD_128K_8K_lL COMMAND "${PROJECT_SOURCE_DIR}/txrx.sh" -icDnd 128K -w 8K -l ${LOSS_RATE} -L ${LOSS_RATE})
add_test(NAME t_isnS_128K_8K_l COMMAND "${PROJECT_SOURCE_DIR}/txrx.sh" -isSnd 128K -w 8K -l ${LOSS_RATE})
add_test(NAME t_isnS_128K_8K_L COMMAND "${PROJECT_SOURCE_DIR}/txrx.sh" -isSnd 128K -w 8K -L ${LOSS_RATE})
add_test(NAME t_isnS_128K_8K_lL COMMAND "${PROJECT_SOURCE_DIR}/txrx.sh" -isSnd 128K -w 8K -l ${LOSS_RATE} -L ${LOSS_RATE})
add_test(NAME t_isnR_128K_8K_l COMMAND "${PROJECT_SOURCE_DIR}/txrx.sh" -isRnd 128K -w 8K -l ${LOSS_RATE})
add_test(NAME t_isnR_128K_8K_L COMMAND "${PROJECT_SOURCE_DIR}/txrx.sh" -isRnd 128K -w 8K -L ${LOSS_RATE})
add_test(NAME t_isnR_128K_8K_lL COMMAND "${PROJECT_SOURCE_DIR}/txrx.sh" -isRnd 128K -w 8K -l ${LOSS_RATE} -L ${LOSS_RATE})
add_test(NAME t_isnD_128K_8K_l COMMAND "${PROJECT_SOURCE_DIR}/txrx.sh" -isDnd 128K -w 8K -l ${LOSS_RATE})
add_test(NAME t_isnD_128K_8K_L COMMAND "${PROJECT_SOURCE_DIR}/txrx.sh" -isDnd 128K -w 8K -L ${LOSS_RATE})
add_test(NAME t_isnD_128K_8K_lL COMMAND "${PROJECT_SOURCE_DIR}/txrx.sh" -isDnd 128K -w 8K -l ${LOSS_RATE} -L ${LOSS_RATE})
#add_test(NAME t_icoS_128K_8K_l COMMAND "${PROJECT_SOURCE_DIR}/txrx.sh" -icSod 128K -w 8K -l ${LOSS_RATE})
#add_test(NAME t_icoS_128K_8K_L COMMAND "${PROJECT_SOURCE_DIR}/txrx.sh" -icSod 128K -w 8K -L ${LOSS_RATE})
#add_test(NAME t_icoS_128K_8K_lL COMMAND "${PROJECT_SOURCE_DIR}/txrx.sh" -icSod 128K -w 8K -l ${LOSS_RATE} -L ${LOSS_RATE})
#add_test(NAME t_icoR_128K_8K_l COMMAND "${PROJECT_SOURCE_DIR}/txrx.sh" -icRod 128K -w 8K -l ${LOSS_RATE})
#add_test(NAME t_icoR_128K_8K_L COMMAND "${PROJECT_SOURCE_DIR}/txrx.sh" -icRod 128K -w 8K -L ${LOSS_RATE})
#add_test(NAME t_icoR_128K_8K_lL COMMAND "${PROJECT_SOURCE_DIR}/txrx.sh" -icRod 128K -w 8K -l ${LOSS_RATE} -L ${LOSS_RATE})
#add_test(NAME t_icoD_128K_8K_l COMMAND "${PROJECT_SOURCE_DIR}/txrx.sh" -icDod 128K -w 8K -l ${LOSS_RATE})
#add_test(NAME t_icoD_128K_8K_L COMMAND "${PROJECT_SOURCE_DIR}/txrx.sh" -icDod 128K -w 8K -L ${LOSS_RATE})
#add_test(NAME t_icoD_128K_8K_lL COMMAND "${PROJECT_SOURCE_DIR}/txrx.sh" -icDod 128K -w 8K -l ${LOSS_RATE} -L ${LOSS_RATE})
#add_test(NAME t_isoS_128K_8K_l COMMAND "${PROJECT_SOURCE_DIR}/txrx.sh" -isSod 128K -w 8K -l ${LOSS_RATE})
#add_test(NAME t_isoS_128K_8K_L COMMAND "${PROJECT_SOURCE_DIR}/txrx.sh" -isSod 128K -w 8K -L ${LOSS_RATE})
#add_test(NAME t_isoS_128K_8K_lL COMMAND "${PROJECT_SOURCE_DIR}/txrx.sh" -isSod 128K -w 8K -l ${LOSS_RATE} -L ${LOSS_RATE})
#add_test(NAME t_isoR_128K_8K_l COMMAND "${PROJECT_SOURCE_DIR}/txrx.sh" -isRod 128K -w 8K -l ${LOSS_RATE})
#add_test(NAME t_isoR_128K_8K_L COMMAND "${PROJECT_SOURCE_DIR}/txrx.sh" -isRod 128K -w 8K -L ${LOSS_RATE})
#add_test(NAME t_isoR_128K_8K_lL COMMAND "${PROJECT_SOURCE_DIR}/txrx.sh" -isRod 128K -w 8K -l ${LOSS_RATE} -L ${LOSS_RATE})
#add_test(NAME t_isoD_128K_8K_l COMMAND "${PROJECT_SOURCE_DIR}/txrx.sh" -isDod 128K -w 8K -l ${LOSS_RATE})
#add_test(NAME t_isoD_128K_8K_L COMMAND "${PROJECT_SOURCE_DIR}/txrx.sh" -isDod 128K -w 8K -L ${LOSS_RATE})
#add_test(NAME t_isoD_128K_8K_lL COMMAND "${PROJECT_SOURCE_DIR}/txrx.sh" -isDod 128K -w 8K -l ${LOSS_RATE} -L ${LOSS_RATE})
add_custom_target (check_webget COMMAND ${CMAKE_CTEST_COMMAND} --output-on-failure --timeout 10 -R 't_webget'
COMMENT "Testing webget...")
add_custom_target (check_lab0 COMMAND ${CMAKE_CTEST_COMMAND} --output-on-failure --timeout 10 -R 't_webget|t_byte_stream|_dt'
COMMENT "Testing Lab 0...")
add_custom_target (check_lab1 COMMAND ${CMAKE_CTEST_COMMAND} --output-on-failure --timeout 10 -R 't_strm_reassem_|t_webget|t_byte_stream|_dt'
COMMENT "Testing the stream reassembler...")
add_custom_target (check_lab2 COMMAND ${CMAKE_CTEST_COMMAND} --output-on-failure --timeout 10 -R 't_recv_|t_wrapping_|t_strm_reassem_|t_webget|t_byte_stream|_dt'
COMMENT "Testing the TCP receiver...")
add_custom_target (check_lab3 COMMAND ${CMAKE_CTEST_COMMAND} --output-on-failure --timeout 10 -R 't_send_|t_recv_|t_wrapping_|t_strm_reassem_|t_webget|t_byte_stream|_dt'
COMMENT "Testing the TCP sender...")
add_custom_target (check_lab4 COMMAND "${PROJECT_SOURCE_DIR}/tun.sh" check 144 145
COMMAND ${CMAKE_CTEST_COMMAND} --output-on-failure --timeout 10 -R "^t_"
COMMENT "Testing libsponge...")
add_custom_target (check COMMAND "${PROJECT_SOURCE_DIR}/tun.sh" check 144 145
COMMAND ${CMAKE_CTEST_COMMAND} --output-on-failure --timeout 10 -R "^t_"
COMMENT "Testing libsponge...")
TUN_IP_PREFIX=169.254
file (GLOB LIB_SOURCES "*.cc" "util/*.cc" "tcp_helpers/*.cc")
add_library (sponge STATIC ${LIB_SOURCES})
#include "byte_stream.hh"
#include <algorithm>
#include <iterator>
#include <stdexcept>
// Dummy implementation of a flow-controlled in-memory byte stream.
// For Lab 0, please replace with a real implementation that passes the
// automated checks run by `make check_lab0`.
// You will need to add private members to the class declaration in `byte_stream.hh`
template <typename... Targs>
void DUMMY_CODE(Targs &&... /* unused */) {}
using namespace std;
ByteStream::ByteStream(const size_t capacity) { DUMMY_CODE(capacity); }
size_t ByteStream::write(const string &data) {
DUMMY_CODE(data);
return {};
}
//! \param[in] len bytes will be copied from the output side of the buffer
string ByteStream::peek_output(const size_t len) const {
DUMMY_CODE(len);
return {};
}
//! \param[in] len bytes will be removed from the output side of the buffer
void ByteStream::pop_output(const size_t len) { DUMMY_CODE(len); }
void ByteStream::end_input() {}
bool ByteStream::input_ended() const { return {}; }
size_t ByteStream::buffer_size() const { return {}; }
bool ByteStream::buffer_empty() const { return {}; }
bool ByteStream::eof() const { return false; }
size_t ByteStream::bytes_written() const { return {}; }
size_t ByteStream::bytes_read() const { return {}; }
size_t ByteStream::remaining_capacity() const { return {}; }
#ifndef SPONGE_LIBSPONGE_BYTE_STREAM_HH
#define SPONGE_LIBSPONGE_BYTE_STREAM_HH
#include <cstddef>
#include <cstdint>
#include <deque>
#include <list>
#include <string>
#include <utility>
//! \brief An in-order byte stream.
//! Bytes are written on the "input" side and read from the "output"
//! side. The byte stream is finite: the writer can end the input,
//! and then no more bytes can be written.
class ByteStream {
private:
// Your code here -- add private members as necessary.
bool _error{}; //!< Flag indicating that the stream suffered an error.
public:
//! Construct a stream with room for `capacity` bytes.
ByteStream(const size_t capacity);
//! \name "Input" interface for the writer
//!@{
//! Write a string of bytes into the stream. Write as many
//! as will fit, and return how many were written.
//! \returns the number of bytes accepted into the stream
size_t write(const std::string &data);
//! \returns the number of additional bytes that the stream has space for
size_t remaining_capacity() const;
//! Signal that the byte stream has reached its ending
void end_input();
//! Indicate that the stream suffered an error.
void set_error() { _error = true; }
//!@}
//! \name "Output" interface for the reader
//!@{
//! Peek at next "len" bytes of the stream
//! \returns a string
std::string peek_output(const size_t len) const;
//! Remove bytes from the buffer
void pop_output(const size_t len);
//! Read (i.e., copy and then pop) the next "len" bytes of the stream
//! \returns a vector of bytes read
std::string read(const size_t len) {
const auto ret = peek_output(len);
pop_output(len);
return ret;
}
//! \returns `true` if the stream input has ended
bool input_ended() const;
//! \returns `true` if the stream has suffered an error
bool error() const { return _error; }
//! \returns the maximum amount that can currently be read from the stream
size_t buffer_size() const;
//! \returns `true` if the buffer is empty
bool buffer_empty() const;
//! \returns `true` if the output has reached the ending
bool eof() const;
//!@}
//! \name General accounting
//!@{
//! Total number of bytes written
size_t bytes_written() const;
//! Total number of bytes popped
size_t bytes_read() const;
//!@}
};
#endif // SPONGE_LIBSPONGE_BYTE_STREAM_HH
#include "stream_reassembler.hh"
// Dummy implementation of a stream reassembler.
// For Lab 1, please replace with a real implementation that passes the
// automated checks run by `make check_lab1`.
// You will need to add private members to the class declaration in `stream_reassembler.hh`
template <typename... Targs>
void DUMMY_CODE(Targs &&... /* unused */) {}
using namespace std;
StreamReassembler::StreamReassembler(const size_t capacity) : _output(capacity), _capacity(capacity) {}
//! \details This function accepts a substring (aka a segment) of bytes,
//! possibly out-of-order, from the logical stream, and assembles any newly
//! contiguous substrings and writes them into the output stream in order.
void StreamReassembler::push_substring(const string &data, const size_t index, const bool eof) {
DUMMY_CODE(data, index, eof);
}
size_t StreamReassembler::unassembled_bytes() const { return {}; }
bool StreamReassembler::empty() const { return {}; }
#ifndef SPONGE_LIBSPONGE_STREAM_REASSEMBLER_HH
#define SPONGE_LIBSPONGE_STREAM_REASSEMBLER_HH
#include "byte_stream.hh"
#include <cstdint>
#include <string>
//! \brief A class that assembles a series of excerpts from a byte stream (possibly out of order,
//! possibly overlapping) into an in-order byte stream.
class StreamReassembler {
private:
// Your code here -- add private members as necessary.
ByteStream _output; //!< The reassembled in-order byte stream
size_t _capacity; //!< The maximum number of bytes
public:
//! \brief Construct a `StreamReassembler` that will store up to `capacity` bytes.
//! \note This capacity limits both the bytes that have been reassembled,
//! and those that have not yet been reassembled.
StreamReassembler(const size_t capacity);
//! \brief Receives a substring and writes any newly contiguous bytes into the stream.
//!
//! If accepting all the data would overflow the `capacity` of this
//! `StreamReassembler`, then only the part of the data that fits will be
//! accepted. If the substring is only partially accepted, then the `eof`
//! will be disregarded.
//!
//! \param data the string being added
//! \param index the index of the first byte in `data`
//! \param eof whether or not this segment ends with the end of the stream
void push_substring(const std::string &data, const uint64_t index, const bool eof);
//! \name Access the reassembled byte stream
//!@{
const ByteStream &stream_out() const { return _output; }
ByteStream &stream_out() { return _output; }
//!@}
//! The number of bytes in the substrings stored but not yet reassembled
//!
//! \note If the byte at a particular index has been submitted twice, it
//! should only be counted once for the purpose of this function.
size_t unassembled_bytes() const;
//! \brief Is the internal state empty (other than the output stream)?
//! \returns `true` if no substrings are waiting to be assembled
bool empty() const;
};
#endif // SPONGE_LIBSPONGE_STREAM_REASSEMBLER_HH
#include "tcp_connection.hh"
#include <iostream>
// Dummy implementation of a TCP connection
// For Lab 4, please replace with a real implementation that passes the
// automated checks run by `make check`.
template <typename... Targs>
void DUMMY_CODE(Targs &&... /* unused */) {}
using namespace std;
size_t TCPConnection::remaining_outbound_capacity() const { return {}; }
size_t TCPConnection::bytes_in_flight() const { return {}; }
size_t TCPConnection::unassembled_bytes() const { return {}; }
size_t TCPConnection::time_since_last_segment_received() const { return {}; }
void TCPConnection::segment_received(const TCPSegment &seg) { DUMMY_CODE(seg); }
bool TCPConnection::active() const { return {}; }
size_t TCPConnection::write(const string &data) {
DUMMY_CODE(data);
return {};
}
//! \param[in] ms_since_last_tick number of milliseconds since the last call to this method
void TCPConnection::tick(const size_t ms_since_last_tick) { DUMMY_CODE(ms_since_last_tick); }
void TCPConnection::end_input_stream() {}
void TCPConnection::connect() {}
TCPConnection::~TCPConnection() {
try {
if (active()) {
cerr << "Warning: Unclean shutdown of TCPConnection\n";
// Your code here: need to send a RST segment to the peer
}
} catch (const exception &e) {
std::cerr << "Exception destructing TCP FSM: " << e.what() << std::endl;
}
}
#ifndef SPONGE_LIBSPONGE_TCP_FACTORED_HH
#define SPONGE_LIBSPONGE_TCP_FACTORED_HH
#include "tcp_config.hh"
#include "tcp_receiver.hh"
#include "tcp_sender.hh"
#include "tcp_state.hh"
//! \brief A complete endpoint of a TCP connection
class TCPConnection {
private:
TCPConfig _cfg;
TCPReceiver _receiver{_cfg.recv_capacity};
TCPSender _sender{_cfg.send_capacity, _cfg.rt_timeout, _cfg.fixed_isn};
//! outbound queue of segments that the TCPConnection wants sent
std::queue<TCPSegment> _segments_out{};
//! Should the TCPConnection stay active (and keep ACKing)
//! for 10 * _cfg.rt_timeout milliseconds after both streams have ended,
//! in case the remote TCPConnection doesn't know we've received its whole stream?
bool _linger_after_streams_finish{true};
public:
//! \name "Input" interface for the writer
//!@{
//! \brief Initiate a connection by sending a SYN segment
void connect();
//! \brief Write data to the outbound byte stream, and send it over TCP if possible
//! \returns the number of bytes from `data` that were actually written.
size_t write(const std::string &data);
//! \returns the number of `bytes` that can be written right now.
size_t remaining_outbound_capacity() const;
//! \brief Shut down the outbound byte stream (still allows reading incoming data)
void end_input_stream();
//!@}
//! \name "Output" interface for the reader
//!@{
//! \brief The inbound byte stream received from the peer
ByteStream &inbound_stream() { return _receiver.stream_out(); }
//!@}
//! \name Accessors used for testing
//!@{
//! \brief number of bytes sent and not yet acknowledged, counting SYN/FIN each as one byte
size_t bytes_in_flight() const;
//! \brief number of bytes not yet reassembled
size_t unassembled_bytes() const;
//! \brief Number of milliseconds since the last segment was received
size_t time_since_last_segment_received() const;
//!< \brief summarize the state of the sender, receiver, and the connection
TCPState state() const { return {_sender, _receiver, active(), _linger_after_streams_finish}; };
//!@}
//! \name Methods for the owner or operating system to call
//!@{
//! Called when a new segment has been received from the network
void segment_received(const TCPSegment &seg);
//! Called periodically when time elapses
void tick(const size_t ms_since_last_tick);
//! \brief TCPSegments that the TCPConnection has enqueued for transmission.
//! \note The owner or operating system will dequeue these and
//! put each one into the payload of a lower-layer datagram (usually Internet datagrams (IP),
//! but could also be user datagrams (UDP) or any other kind).
std::queue<TCPSegment> &segments_out() { return _segments_out; }
//! \brief Is the connection still alive in any way?
//! \returns `true` if either stream is still running or if the TCPConnection is lingering
//! after both streams have finished (e.g. to ACK retransmissions from the peer)
bool active() const;
//!@}
//! Construct a new connection from a configuration
explicit TCPConnection(const TCPConfig &cfg) : _cfg{cfg} {}
//! \name construction and destruction
//! moving is allowed; copying is disallowed; default construction not possible
//!@{
~TCPConnection(); //!< destructor sends a RST if the connection is still open
TCPConnection() = delete;
TCPConnection(TCPConnection &&other) = default;
TCPConnection &operator=(TCPConnection &&other) = default;
TCPConnection(const TCPConnection &other) = delete;
TCPConnection &operator=(const TCPConnection &other) = delete;
//!@}
};
#endif // SPONGE_LIBSPONGE_TCP_FACTORED_HH
#include "fd_adapter.hh"
#include <iostream>
#include <stdexcept>
#include <utility>
using namespace std;
//! \details This function first attempts to parse a TCP segment from the next UDP
//! payload recv()d from the socket.
//!
//! If this succeeds, it then checks that the received segment is related to the
//! current connection. When a TCP connection has been established, this means
//! checking that the source and destination ports in the TCP header are correct.
//!
//! If the TCP FSM is listening (i.e., TCPOverUDPSocketAdapter::_listen is `true`)
//! and the TCP segment read from the wire includes a SYN, this function clears the
//! `_listen` flag and calls calls connect() on the underlying UDP socket, with
//! the result that future outgoing segments go to the sender of the SYN segment.
//! \returns a std::optional<TCPSegment> that is empty if the segment was invalid or unrelated
optional<TCPSegment> TCPOverUDPSocketAdapter::read() {
auto datagram = recv();
// is it for us?
if (not listening() and (datagram.source_address != config().destination)) {
return {};
}
// is the payload a valid TCP segment?
TCPSegment seg;
if (ParseResult::NoError != seg.parse(move(datagram.payload), 0)) {
return {};
}
// should we target this source in all future replies?
if (listening()) {
if (seg.header().syn and not seg.header().rst) {
config_mutable().destination = datagram.source_address;
set_listening(false);
} else {
return {};
}
}
return seg;
}
//! Serialize a TCP segment and send it as the payload of a UDP datagram.
//! \param[in] seg is the TCP segment to write
void TCPOverUDPSocketAdapter::write(TCPSegment &seg) {
seg.header().sport = config().source.port();
seg.header().dport = config().destination.port();
UDPSocket::sendto(config().destination, seg.serialize(0));
}
//! Specialize LossyFdAdapter to TCPOverUDPSocketAdapter
template class LossyFdAdapter<TCPOverUDPSocketAdapter>;
#ifndef SPONGE_LIBSPONGE_FD_ADAPTER_HH
#define SPONGE_LIBSPONGE_FD_ADAPTER_HH
#include "file_descriptor.hh"
#include "lossy_fd_adapter.hh"
#include "socket.hh"
#include "tcp_config.hh"
#include "tcp_header.hh"
#include "tcp_segment.hh"
#include <optional>
#include <utility>
//! \brief Basic functionality for file descriptor adaptors
//! \details See TCPOverUDPSocketAdapter and TCPOverIPv4OverTunFdAdapter for more information.
class FdAdapterBase {
private:
FdAdapterConfig _cfg{}; //!< Configuration values
bool _listen = false; //!< Is the connected TCP FSM in listen state?
protected:
FdAdapterConfig &config_mutable() { return _cfg; }
public:
//! \brief Set the listening flag
//! \param[in] l is the new value for the flag
void set_listening(const bool l) { _listen = l; }
//! \brief Get the listening flag
//! \returns whether the FdAdapter is listening for a new connection
bool listening() const { return _listen; }
//! \brief Get the current configuration
//! \returns a const reference
const FdAdapterConfig &config() const { return _cfg; }
//! \brief Get the current configuration (mutable)
//! \returns a mutable reference
FdAdapterConfig &config_mut() { return _cfg; }
};
//! \brief A FD adaptor that reads and writes TCP segments in UDP payloads
class TCPOverUDPSocketAdapter : public FdAdapterBase, public UDPSocket {
public:
//! Construct from a UDPSocket sliced into a FileDescriptor
explicit TCPOverUDPSocketAdapter(FileDescriptor &&fd) : UDPSocket(std::move(fd)) {}
//! Attempts to read and return a TCP segment related to the current connection from a UDP payload
std::optional<TCPSegment> read();
//! Writes a TCP segment into a UDP payload
void write(TCPSegment &seg);
};
//! Typedef for TCPOverUDPSocketAdapter
using LossyTCPOverUDPSocketAdapter = LossyFdAdapter<TCPOverUDPSocketAdapter>;
#endif // SPONGE_LIBSPONGE_FD_ADAPTER_HH
#include "ipv4_datagram.hh"
#include "parser.hh"
#include "util.hh"
#include <stdexcept>
#include <string>
using namespace std;
ParseResult IPv4Datagram::parse(const Buffer buffer) {
NetParser p{buffer};
_header.parse(p);
_payload = p.buffer();
if (_payload.size() != _header.payload_length()) {
return ParseResult::PacketTooShort;
}
return p.get_error();
}
BufferList IPv4Datagram::serialize() const {
if (_payload.size() != _header.payload_length()) {
throw runtime_error("IPv4Datagram::serialize: payload is wrong size");
}
IPv4Header header_out = _header;
header_out.cksum = 0;
const string header_zero_checksum = header_out.serialize();
// calculate checksum -- taken over header only
InternetChecksum check;
check.add(header_zero_checksum);
header_out.cksum = check.value();
BufferList ret;
ret.append(header_out.serialize());
ret.append(_payload);
return ret;
}
#ifndef SPONGE_LIBSPONGE_IPV4_DATAGRAM_HH
#define SPONGE_LIBSPONGE_IPV4_DATAGRAM_HH
#include "buffer.hh"
#include "ipv4_header.hh"
//! \brief [IPv4](\ref rfc::rfc791) Internet datagram
class IPv4Datagram {
private:
IPv4Header _header{};
BufferList _payload{};
public:
//! \brief Parse the segment from a string
ParseResult parse(const Buffer buffer);
//! \brief Serialize the segment to a string
BufferList serialize() const;
//! \name Accessors
//!@{
const IPv4Header &header() const { return _header; }
IPv4Header &header() { return _header; }
const BufferList &payload() const { return _payload; }
BufferList &payload() { return _payload; }
//!@}
};
using InternetDatagram = IPv4Datagram;
#endif // SPONGE_LIBSPONGE_IPV4_DATAGRAM_HH
#include "ipv4_header.hh"
#include "util.hh"
#include <iomanip>
#include <sstream>
using namespace std;
//! \param[in,out] p is a NetParser from which the IP fields will be extracted
//! \returns a ParseResult indicating success or the reason for failure
//! \details It is important to check for (at least) the following potential errors
//! (but note that NetParser inherently checks for certain errors;
//! use that fact to your advantage!):
//!
//! - data stream is too short to contain a header
//! - wrong IP version number
//! - the header's `hlen` field is shorter than the minimum allowed
//! - there is less data in the header than the `doff` field claims
//! - there is less data in the full datagram than the `len` field claims
//! - the checksum is bad
ParseResult IPv4Header::parse(NetParser &p) {
Buffer original_serialized_version = p.buffer();
const size_t data_size = p.buffer().size();
if (data_size < IPv4Header::LENGTH) {
return ParseResult::PacketTooShort;
}
const uint8_t first_byte = p.u8();
ver = first_byte >> 4; // version
hlen = first_byte & 0x0f; // header length
tos = p.u8(); // type of service
len = p.u16(); // length
id = p.u16(); // id
const uint16_t fo_val = p.u16();
df = static_cast<bool>(fo_val & 0x4000); // don't fragment
mf = static_cast<bool>(fo_val & 0x2000); // more fragments
offset = fo_val & 0x1fff; // offset
ttl = p.u8(); // ttl
proto = p.u8(); // proto
cksum = p.u16(); // checksum
src = p.u32(); // source address
dst = p.u32(); // destination address
if (data_size < 4 * hlen) {
return ParseResult::PacketTooShort;
}
if (ver != 4) {
return ParseResult::WrongIPVersion;
}
if (hlen < 5) {
return ParseResult::HeaderTooShort;
}
if (data_size != len) {
return ParseResult::TruncatedPacket;
}
p.remove_prefix(hlen * 4 - IPv4Header::LENGTH);
if (p.error()) {
return p.get_error();
}
InternetChecksum check;
check.add({original_serialized_version.str().data(), size_t(4 * hlen)});
if (check.value()) {
return ParseResult::BadChecksum;
}
return ParseResult::NoError;
}
//! Serialize the IPv4Header to a string (does not recompute the checksum)
string IPv4Header::serialize() const {
// sanity checks
if (ver != 4) {
throw runtime_error("wrong IP version");
}
if (4 * hlen < IPv4Header::LENGTH) {
throw runtime_error("IP header too short");
}
string ret;
ret.reserve(4 * hlen);
const uint8_t first_byte = (ver << 4) | (hlen & 0xf);
NetUnparser::u8(ret, first_byte); // version and header length
NetUnparser::u8(ret, tos); // type of service
NetUnparser::u16(ret, len); // length
NetUnparser::u16(ret, id); // id
const uint16_t fo_val = (df ? 0x4000 : 0) | (mf ? 0x2000 : 0) | (offset & 0x1fff);
NetUnparser::u16(ret, fo_val); // flags and offset
NetUnparser::u8(ret, ttl); // time to live
NetUnparser::u8(ret, proto); // protocol number
NetUnparser::u16(ret, cksum); // checksum
NetUnparser::u32(ret, src); // src address
NetUnparser::u32(ret, dst); // dst address
ret.resize(4 * hlen); // expand header to advertised size
return ret;
}
uint16_t IPv4Header::payload_length() const { return len - 4 * hlen; }
//! \details This value is needed when computing the checksum of an encapsulated TCP segment.
//! ~~~{.txt}
//! 0 7 8 15 16 23 24 31
//! +--------+--------+--------+--------+
//! | source address |
//! +--------+--------+--------+--------+
//! | destination address |
//! +--------+--------+--------+--------+
//! | zero |protocol| payload length |
//! +--------+--------+--------+--------+
//! ~~~
uint32_t IPv4Header::pseudo_cksum() const {
uint32_t pcksum = (src >> 16) + (src & 0xffff); // source addr
pcksum += (dst >> 16) + (dst & 0xffff); // dest addr
pcksum += proto; // protocol
pcksum += payload_length(); // payload length
return pcksum;
}
//! \returns A string with the header's contents
std::string IPv4Header::to_string() const {
stringstream ss{};
ss << hex << boolalpha << "IP version: " << +ver << '\n'
<< "IP hdr len: " << +hlen << '\n'
<< "IP tos: " << +tos << '\n'
<< "IP dgram len: " << +len << '\n'
<< "IP id: " << +id << '\n'
<< "Flags: df: " << df << " mf: " << mf << '\n'
<< "Offset: " << +offset << '\n'
<< "TTL: " << +ttl << '\n'
<< "Protocol: " << +proto << '\n'
<< "Checksum: " << +cksum << '\n'
<< "Src addr: " << +src << '\n'
<< "Dst addr: " << +dst << '\n';
return ss.str();
}
#ifndef SPONGE_LIBSPONGE_IPV4_HEADER_HH
#define SPONGE_LIBSPONGE_IPV4_HEADER_HH
#include "parser.hh"
//! \brief [IPv4](\ref rfc::rfc791) Internet datagram header
//! \note IP options are not supported
struct IPv4Header {
static constexpr size_t LENGTH = 20; //!< [IPv4](\ref rfc::rfc791) header length, not including options
static constexpr uint8_t DEFAULT_TTL = 128; //!< A reasonable default TTL value
static constexpr uint8_t PROTO_TCP = 6; //!< Protocol number for [tcp](\ref rfc::rfc793)
//! \struct IPv4Header
//! ~~~{.txt}
//! 0 1 2 3
//! 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
//! +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
//! |Version| IHL |Type of Service| Total Length |
//! +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
//! | Identification |Flags| Fragment Offset |
//! +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
//! | Time to Live | Protocol | Header Checksum |
//! +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
//! | Source Address |
//! +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
//! | Destination Address |
//! +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
//! | Options | Padding |
//! +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
//! ~~~
//! \name IPv4 Header fields
//!@{
uint8_t ver = 4; //!< IP version
uint8_t hlen = LENGTH / 4; //!< header length (multiples of 32 bits)
uint8_t tos = 0; //!< type of service
uint16_t len = 0; //!< total length of packet
uint16_t id = 0; //!< identification number
bool df = true; //!< don't fragment flag
bool mf = false; //!< more fragments flag
uint16_t offset = 0; //!< fragment offset field
uint8_t ttl = DEFAULT_TTL; //!< time to live field
uint8_t proto = PROTO_TCP; //!< protocol field
uint16_t cksum = 0; //!< checksum field
uint32_t src = 0; //!< src address
uint32_t dst = 0; //!< dst address
//!@}
//! Parse the IP fields from the provided NetParser
ParseResult parse(NetParser &p);
//! Serialize the IP fields
std::string serialize() const;
//! Length of the payload
uint16_t payload_length() const;
//! [pseudo-header's](\ref rfc::rfc793) contribution to the TCP checksum
uint32_t pseudo_cksum() const;
//! Return a string containing a header in human-readable format
std::string to_string() const;
};
//! \struct IPv4Header
//! This struct can be used to parse an existing IP header or to create a new one.
#endif // SPONGE_LIBSPONGE_IPV4_HEADER_HH
#ifndef SPONGE_LIBSPONGE_LOSSY_FD_ADAPTER_HH
#define SPONGE_LIBSPONGE_LOSSY_FD_ADAPTER_HH
#include "file_descriptor.hh"
#include "tcp_config.hh"
#include "tcp_segment.hh"
#include "util.hh"
#include <optional>
#include <random>
#include <utility>
//! An adapter class that adds random dropping behavior to an FD adapter
template <typename AdapterT>
class LossyFdAdapter {
private:
//! Fast RNG used by _should_drop()
std::mt19937 _rand{get_random_generator()};
//! The underlying FD adapter
AdapterT _adapter;
//! \brief Determine whether or not to drop a given read or write
//! \param[in] uplink is `true` to use the uplink loss probability, else use the downlink loss probability
//! \returns `true` if the segment should be dropped
bool _should_drop(bool uplink) {
const auto &cfg = _adapter.config();
const uint16_t loss = uplink ? cfg.loss_rate_up : cfg.loss_rate_dn;
return loss != 0 && uint16_t(_rand()) < loss;
}
public:
//! Conversion to a FileDescriptor by returning the underlying AdapterT
operator const FileDescriptor &() const { return _adapter; }
//! Construct from a FileDescriptor appropriate to the AdapterT constructor
explicit LossyFdAdapter(FileDescriptor &&fd) : _adapter(std::move(fd)) {}
//! \brief Read from the underlying AdapterT instance, potentially dropping the read datagram
//! \returns std::optional<TCPSegment> that is empty if the segment was dropped or if
//! the underlying AdapterT returned an empty value
std::optional<TCPSegment> read() {
auto ret = _adapter.read();
if (_should_drop(false)) {
return {};
}
return ret;
}
//! \brief Write to the underlying AdapterT instance, potentially dropping the datagram to be written
//! \param[in] seg is the packet to either write or drop
void write(TCPSegment &seg) {
if (_should_drop(true)) {
return;
}
return _adapter.write(seg);
}
//! \name
//! Passthrough functions to the underlying AdapterT instance
//!@{
void set_listening(const bool l) { _adapter.set_listening(l); } //!< FdAdapterBase::set_listening passthrough
const FdAdapterConfig &config() const { return _adapter.config(); } //!< FdAdapterBase::config passthrough
FdAdapterConfig &config_mut() { return _adapter.config_mut(); } //!< FdAdapterBase::config_mut passthrough
//!@}
};
#endif // SPONGE_LIBSPONGE_LOSSY_FD_ADAPTER_HH
#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
#include "tcp_header.hh"
#include <sstream>
using namespace std;
//! \param[in,out] p is a NetParser from which the TCP fields will be extracted
//! \returns a ParseResult indicating success or the reason for failure
//! \details It is important to check for (at least) the following potential errors
//! (but note that NetParser inherently checks for certain errors;
//! use that fact to your advantage!):
//!
//! - data stream inside the NetParser is too short to contain a header
//! - the header's `doff` field is shorter than the minimum allowed
//! - there is less data in the header than the `doff` field claims
//! - the checksum is bad
ParseResult TCPHeader::parse(NetParser &p) {
sport = p.u16(); // source port
dport = p.u16(); // destination port
seqno = WrappingInt32{p.u32()}; // sequence number
ackno = WrappingInt32{p.u32()}; // ack number
doff = p.u8() >> 4; // data offset
const uint8_t fl_b = p.u8(); // byte including flags
urg = static_cast<bool>(fl_b & 0b0010'0000); // binary literals and ' digit separator since C++14!!!
ack = static_cast<bool>(fl_b & 0b0001'0000);
psh = static_cast<bool>(fl_b & 0b0000'1000);
rst = static_cast<bool>(fl_b & 0b0000'0100);
syn = static_cast<bool>(fl_b & 0b0000'0010);
fin = static_cast<bool>(fl_b & 0b0000'0001);
win = p.u16(); // window size
cksum = p.u16(); // checksum
uptr = p.u16(); // urgent pointer
if (doff < 5) {
return ParseResult::HeaderTooShort;
}
// skip any options or anything extra in the header
p.remove_prefix(doff * 4 - TCPHeader::LENGTH);
if (p.error()) {
return p.get_error();
}
return ParseResult::NoError;
}
//! Serialize the TCPHeader to a string (does not recompute the checksum)
string TCPHeader::serialize() const {
// sanity check
if (doff < 5) {
throw runtime_error("TCP header too short");
}
string ret;
ret.reserve(4 * doff);
NetUnparser::u16(ret, sport); // source port
NetUnparser::u16(ret, dport); // destination port
NetUnparser::u32(ret, seqno.raw_value()); // sequence number
NetUnparser::u32(ret, ackno.raw_value()); // ack number
NetUnparser::u8(ret, doff << 4); // data offset
const uint8_t fl_b = (urg ? 0b0010'0000 : 0) | (ack ? 0b0001'0000 : 0) | (psh ? 0b0000'1000 : 0) |
(rst ? 0b0000'0100 : 0) | (syn ? 0b0000'0010 : 0) | (fin ? 0b0000'0001 : 0);
NetUnparser::u8(ret, fl_b); // flags
NetUnparser::u16(ret, win); // window size
NetUnparser::u16(ret, cksum); // checksum
NetUnparser::u16(ret, uptr); // urgent pointer
ret.resize(4 * doff); // expand header to advertised size
return ret;
}
//! \returns A string with the header's contents
string TCPHeader::to_string() const {
stringstream ss{};
ss << hex << boolalpha << "TCP source port: " << +sport << '\n'
<< "TCP dest port: " << +dport << '\n'
<< "TCP seqno: " << seqno << '\n'
<< "TCP ackno: " << ackno << '\n'
<< "TCP doff: " << +doff << '\n'
<< "Flags: urg: " << urg << " ack: " << ack << " psh: " << psh << " rst: " << rst << " syn: " << syn
<< " fin: " << fin << '\n'
<< "TCP winsize: " << +win << '\n'
<< "TCP cksum: " << +cksum << '\n'
<< "TCP uptr: " << +uptr << '\n';
return ss.str();
}
string TCPHeader::summary() const {
stringstream ss{};
ss << "Header(flags=" << (syn ? "S" : "") << (ack ? "A" : "") << (rst ? "R" : "") << (fin ? "F" : "")
<< ",seqno=" << seqno << ",ack=" << ackno << ",win=" << win << ")";
return ss.str();
}
bool TCPHeader::operator==(const TCPHeader &other) const {
// TODO(aozdemir) more complete check (right now we omit cksum, src, dst
return seqno == other.seqno && ackno == other.ackno && doff == other.doff && urg == other.urg && ack == other.ack &&
psh == other.psh && rst == other.rst && syn == other.syn && fin == other.fin && win == other.win &&
uptr == other.uptr;
}
#ifndef SPONGE_LIBSPONGE_TCP_HEADER_HH
#define SPONGE_LIBSPONGE_TCP_HEADER_HH
#include "parser.hh"
#include "wrapping_integers.hh"
//! \brief [TCP](\ref rfc::rfc793) segment header
//! \note TCP options are not supported
struct TCPHeader {
static constexpr size_t LENGTH = 20; //!< [TCP](\ref rfc::rfc793) header length, not including options
//! \struct TCPHeader
//! ~~~{.txt}
//! 0 1 2 3
//! 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
//! +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
//! | Source Port | Destination Port |
//! +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
//! | Sequence Number |
//! +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
//! | Acknowledgment Number |
//! +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
//! | Data | |U|A|P|R|S|F| |
//! | Offset| Reserved |R|C|S|S|Y|I| Window |
//! | | |G|K|H|T|N|N| |
//! +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
//! | Checksum | Urgent Pointer |
//! +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
//! | Options | Padding |
//! +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
//! | data |
//! +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
//! ~~~
//! \name TCP Header fields
//!@{
uint16_t sport = 0; //!< source port
uint16_t dport = 0; //!< destination port
WrappingInt32 seqno{0}; //!< sequence number
WrappingInt32 ackno{0}; //!< ack number
uint8_t doff = LENGTH / 4; //!< data offset
bool urg = false; //!< urgent flag
bool ack = false; //!< ack flag
bool psh = false; //!< push flag
bool rst = false; //!< rst flag
bool syn = false; //!< syn flag
bool fin = false; //!< fin flag
uint16_t win = 0; //!< window size
uint16_t cksum = 0; //!< checksum
uint16_t uptr = 0; //!< urgent pointer
//!@}
//! Parse the TCP fields from the provided NetParser
ParseResult parse(NetParser &p);
//! Serialize the TCP fields
std::string serialize() const;
//! Return a string containing a header in human-readable format
std::string to_string() const;
//! Return a string containing a human-readable summary of the header
std::string summary() const;
bool operator==(const TCPHeader &other) const;
};
#endif // SPONGE_LIBSPONGE_TCP_HEADER_HH
#include "tcp_segment.hh"
#include "parser.hh"
#include "util.hh"
#include <variant>
using namespace std;
//! \param[in] buffer string/Buffer to be parsed
//! \param[in] datagram_layer_checksum pseudo-checksum from the lower-layer protocol
ParseResult TCPSegment::parse(const Buffer buffer, const uint32_t datagram_layer_checksum) {
InternetChecksum check(datagram_layer_checksum);
check.add(buffer);
if (check.value()) {
return ParseResult::BadChecksum;
}
NetParser p{buffer};
_header.parse(p);
_payload = p.buffer();
return p.get_error();
}
size_t TCPSegment::length_in_sequence_space() const {
return payload().str().size() + (header().syn ? 1 : 0) + (header().fin ? 1 : 0);
}
//! \param[in] datagram_layer_checksum pseudo-checksum from the lower-layer protocol
BufferList TCPSegment::serialize(const uint32_t datagram_layer_checksum) const {
TCPHeader header_out = _header;
header_out.cksum = 0;
// calculate checksum -- taken over entire segment
InternetChecksum check(datagram_layer_checksum);
check.add(header_out.serialize());
check.add(_payload);
header_out.cksum = check.value();
BufferList ret;
ret.append(header_out.serialize());
ret.append(_payload);
return ret;
}
#ifndef SPONGE_LIBSPONGE_TCP_SEGMENT_HH
#define SPONGE_LIBSPONGE_TCP_SEGMENT_HH
#include "buffer.hh"
#include "tcp_header.hh"
#include <cstdint>
//! \brief [TCP](\ref rfc::rfc793) segment
class TCPSegment {
private:
TCPHeader _header{};
Buffer _payload{};
public:
//! \brief Parse the segment from a string
ParseResult parse(const Buffer buffer, const uint32_t datagram_layer_checksum = 0);
//! \brief Serialize the segment to a string
BufferList serialize(const uint32_t datagram_layer_checksum = 0) const;
//! \name Accessors
//!@{
const TCPHeader &header() const { return _header; }
TCPHeader &header() { return _header; }
const Buffer &payload() const { return _payload; }
Buffer &payload() { return _payload; }
//!@}
//! \brief Segment's length in sequence space
//! \note Equal to payload length plus one byte if SYN is set, plus one byte if FIN is set
size_t length_in_sequence_space() const;
};
#endif // SPONGE_LIBSPONGE_TCP_SEGMENT_HH
#include "tcp_sponge_socket.hh"
#include "parser.hh"
#include "tun.hh"
#include "util.hh"
#include <cstddef>
#include <exception>
#include <iostream>
#include <stdexcept>
#include <string>
#include <sys/socket.h>
#include <sys/syscall.h>
#include <sys/types.h>
#include <unistd.h>
#include <utility>
using namespace std;
static constexpr size_t TCP_TICK_MS = 10;
//! \param[in] condition is a function returning true if loop should continue
template <typename AdaptT>
void TCPSpongeSocket<AdaptT>::_tcp_loop(const function<bool()> &condition) {
auto base_time = timestamp_ms();
while (condition()) {
auto ret = _eventloop.wait_next_event(TCP_TICK_MS);
if (ret == EventLoop::Result::Exit or _abort) {
break;
}
if (_tcp.value().active()) {
const auto next_time = timestamp_ms();
_tcp.value().tick(next_time - base_time);
base_time = next_time;
}
}
}
//! \param[in] data_socket_pair is a pair of connected AF_UNIX SOCK_STREAM sockets
//! \param[in] dgramfd is the FileDescriptor for reading and writing datagrams
template <typename AdaptT>
TCPSpongeSocket<AdaptT>::TCPSpongeSocket(pair<FileDescriptor, FileDescriptor> data_socket_pair,
FileDescriptor &&dgramfd)
: LocalStreamSocket(move(data_socket_pair.first))
, _thread_data(move(data_socket_pair.second))
, _datagram_adapter(move(dgramfd)) {
_thread_data.set_blocking(false);
}
template <typename AdaptT>
void TCPSpongeSocket<AdaptT>::_initialize_TCP(const TCPConfig &config) {
_tcp.emplace(config);
// Set up the event loop
// There are four possible events to handle:
//
// 1) Incoming datagram received (needs to be given to
// TCPConnection::segment_received method)
//
// 2) Outbound bytes received from local application via a write()
// call (needs to be read from the local stream socket and
// given to TCPConnection::data_written method)
//
// 3) Incoming bytes reassembled by the TCPConnection
// (needs to be read from the inbound_stream and written
// to the local stream socket back to the application)
//
// 4) Outbound segment generated by TCP (needs to be
// given to underlying datagram socket)
// rule 1: read from filtered packet stream and dump into TCPConnection
_eventloop.add_rule(_datagram_adapter,
Direction::In,
[&] {
auto seg = _datagram_adapter.read();
if (seg) {
_tcp->segment_received(move(seg.value()));
}
// debugging output:
if (_thread_data.eof() and _tcp.value().bytes_in_flight() == 0 and not _fully_acked) {
cerr << "DEBUG: Outbound stream to "
<< _datagram_adapter.config().destination.to_string()
<< " has been fully acknowledged.\n";
_fully_acked = true;
}
},
[&] { return _tcp->active(); });
// rule 2: read from pipe into outbound buffer
_eventloop.add_rule(
_thread_data,
Direction::In,
[&] {
const auto data = _thread_data.read(_tcp->remaining_outbound_capacity());
const auto len = data.size();
const auto amount_written = _tcp->write(move(data));
if (amount_written != len) {
throw runtime_error("TCPConnection::write() accepted less than advertised length");
}
if (_thread_data.eof()) {
_tcp->end_input_stream();
_outbound_shutdown = true;
// debugging output:
cerr << "DEBUG: Outbound stream to " << _datagram_adapter.config().destination.to_string()
<< " finished (" << _tcp.value().bytes_in_flight() << " byte"
<< (_tcp.value().bytes_in_flight() == 1 ? "" : "s") << " still in flight).\n";
}
},
[&] { return (_tcp->active()) and (not _outbound_shutdown) and (_tcp->remaining_outbound_capacity() > 0); },
[&] {
_tcp->end_input_stream();
_outbound_shutdown = true;
});
// rule 3: read from inbound buffer into pipe
_eventloop.add_rule(
_thread_data,
Direction::Out,
[&] {
ByteStream &inbound = _tcp->inbound_stream();
// Write from the inbound_stream into
// the pipe, handling the possibility of a partial
// write (i.e., only pop what was actually written).
const size_t amount_to_write = min(size_t(65536), inbound.buffer_size());
const std::string buffer = inbound.peek_output(amount_to_write);
const auto bytes_written = _thread_data.write(move(buffer), false);
inbound.pop_output(bytes_written);
if (inbound.eof() or inbound.error()) {
_thread_data.shutdown(SHUT_WR);
_inbound_shutdown = true;
// debugging output:
cerr << "DEBUG: Inbound stream from " << _datagram_adapter.config().destination.to_string()
<< " finished " << (inbound.error() ? "with an error/reset.\n" : "cleanly.\n");
if (_tcp.value().state() == TCPState::State::TIME_WAIT) {
cerr << "DEBUG: Waiting for lingering segments (e.g. retransmissions of FIN) from peer...\n";
}
}
},
[&] {
return (not _tcp->inbound_stream().buffer_empty()) or
((_tcp->inbound_stream().eof() or _tcp->inbound_stream().error()) and not _inbound_shutdown);
});
// rule 4: read outbound segments from TCPConnection and send as datagrams
_eventloop.add_rule(_datagram_adapter,
Direction::Out,
[&] {
while (not _tcp->segments_out().empty()) {
_datagram_adapter.write(_tcp->segments_out().front());
_tcp->segments_out().pop();
}
},
[&] { return not _tcp->segments_out().empty(); });
}
//! \brief Call [socketpair](\ref man2::socketpair) and return connected Unix-domain sockets of specified type
//! \param[in] type is the type of AF_UNIX sockets to create (e.g., SOCK_SEQPACKET)
//! \returns a std::pair of connected sockets
static inline pair<FileDescriptor, FileDescriptor> socket_pair_helper(const int type) {
int fds[2];
SystemCall("socketpair", ::socketpair(AF_UNIX, type, 0, static_cast<int *>(fds)));
return {FileDescriptor(fds[0]), FileDescriptor(fds[1])};
}
//! \param[in] dgramfd is the FileDescriptor for reading and writing datagrams
template <typename AdaptT>
TCPSpongeSocket<AdaptT>::TCPSpongeSocket(FileDescriptor &&dgramfd)
: TCPSpongeSocket(socket_pair_helper(SOCK_STREAM), move(dgramfd)) {}
template <typename AdaptT>
TCPSpongeSocket<AdaptT>::~TCPSpongeSocket() {
try {
if (_tcp_thread.joinable()) {
cerr << "Warning: unclean shutdown of TCPSpongeSocket\n";
// force the other side to exit
_abort.store(true);
_tcp_thread.join();
}
} catch (const exception &e) {
cerr << "Exception destructing TCPSpongeSocket: " << e.what() << endl;
}
}
template <typename AdaptT>
void TCPSpongeSocket<AdaptT>::wait_until_closed() {
shutdown(SHUT_RDWR);
if (_tcp_thread.joinable()) {
cerr << "DEBUG: Waiting for clean shutdown... ";
_tcp_thread.join();
cerr << "done.\n";
}
}
//! \param[in] c_tcp is the TCPConfig for the TCPConnection
//! \param[in] c_ad is the FdAdapterConfig for the FdAdapter
template <typename AdaptT>
void TCPSpongeSocket<AdaptT>::connect(const TCPConfig &c_tcp, const FdAdapterConfig &c_ad) {
if (_tcp) {
throw runtime_error("connect() with TCPConnection already initialized");
}
_initialize_TCP(c_tcp);
_datagram_adapter.config_mut() = c_ad;
cerr << "DEBUG: Connecting to " << c_ad.destination.to_string() << "... ";
_tcp->connect();
const TCPState expected_state = TCPState::State::SYN_SENT;
if (_tcp->state() != expected_state) {
throw runtime_error("After TCPConnection::connect(), state was " + _tcp->state().name() + " but expected " +
expected_state.name());
}
_tcp_loop([&] { return _tcp->state() == TCPState::State::SYN_SENT; });
cerr << "done.\n";
_tcp_thread = thread(&TCPSpongeSocket::_tcp_main, this);
}
//! \param[in] c_tcp is the TCPConfig for the TCPConnection
//! \param[in] c_ad is the FdAdapterConfig for the FdAdapter
template <typename AdaptT>
void TCPSpongeSocket<AdaptT>::listen_and_accept(const TCPConfig &c_tcp, const FdAdapterConfig &c_ad) {
if (_tcp) {
throw runtime_error("listen_and_accept() with TCPConnection already initialized");
}
_initialize_TCP(c_tcp);
_datagram_adapter.config_mut() = c_ad;
_datagram_adapter.set_listening(true);
cerr << "DEBUG: Listening for incoming connection... ";
_tcp_loop([&] {
const auto s = _tcp->state();
return (s == TCPState::State::LISTEN or s == TCPState::State::SYN_RCVD or s == TCPState::State::SYN_SENT);
});
cerr << "new connection from " << _datagram_adapter.config().destination.to_string() << ".\n";
_tcp_thread = thread(&TCPSpongeSocket::_tcp_main, this);
}
template <typename AdaptT>
void TCPSpongeSocket<AdaptT>::_tcp_main() {
try {
if (not _tcp.has_value()) {
throw runtime_error("no TCP");
}
_tcp_loop([] { return true; });
shutdown(SHUT_RDWR);
if (not _tcp.value().active()) {
cerr << "DEBUG: TCP connection finished "
<< (_tcp.value().state() == TCPState::State::RESET ? "uncleanly" : "cleanly.\n");
}
_tcp.reset();
} catch (const exception &e) {
cerr << "Exception in TCPConnection runner thread: " << e.what() << "\n";
throw e;
}
}
//! Specialization of TCPSpongeSocket for TCPOverUDPSocketAdapter
template class TCPSpongeSocket<TCPOverUDPSocketAdapter>;
//! Specialization of TCPSpongeSocket for TCPOverIPv4OverTunFdAdapter
template class TCPSpongeSocket<TCPOverIPv4OverTunFdAdapter>;
//! Specialization of TCPSpongeSocket for LossyTCPOverUDPSocketAdapter
template class TCPSpongeSocket<LossyTCPOverUDPSocketAdapter>;
//! Specialization of TCPSpongeSocket for LossyTCPOverIPv4OverTunFdAdapter
template class TCPSpongeSocket<LossyTCPOverIPv4OverTunFdAdapter>;
CS144TCPSocket::CS144TCPSocket() : TCPOverIPv4SpongeSocket(TunFD("tun144")) {}
void CS144TCPSocket::connect(const Address &address) {
TCPConfig tcp_config;
tcp_config.rt_timeout = 100;
FdAdapterConfig multiplexer_config;
multiplexer_config.source = {"169.254.144.9", to_string(uint16_t(random_device()()))};
multiplexer_config.destination = address;
TCPOverIPv4SpongeSocket::connect(tcp_config, multiplexer_config);
}
#ifndef SPONGE_LIBSPONGE_TCP_SPONGE_SOCKET_HH
#define SPONGE_LIBSPONGE_TCP_SPONGE_SOCKET_HH
#include "byte_stream.hh"
#include "eventloop.hh"
#include "fd_adapter.hh"
#include "file_descriptor.hh"
#include "tcp_config.hh"
#include "tcp_connection.hh"
#include "tunfd_adapter.hh"
#include <atomic>
#include <cstdint>
#include <optional>
#include <thread>
#include <vector>
//! Multithreaded wrapper around TCPConnection that approximates the Unix sockets API
template <typename AdaptT>
class TCPSpongeSocket : public LocalStreamSocket {
private:
//! Stream socket for reads and writes between owner and TCP thread
LocalStreamSocket _thread_data;
//! Adapter to underlying datagram socket (e.g., UDP or IP)
AdaptT _datagram_adapter;
//! Set up the TCPConnection and the event loop
void _initialize_TCP(const TCPConfig &config);
//! TCP state machine
std::optional<TCPConnection> _tcp{};
//! eventloop that handles all the events (new inbound datagram, new outbound bytes, new inbound bytes)
EventLoop _eventloop{};
//! Process events while specified condition is true
void _tcp_loop(const std::function<bool()> &condition);
//! Main loop of TCPConnection thread
void _tcp_main();
//! Handle to the TCPConnection thread; owner thread calls join() in the destructor
std::thread _tcp_thread{};
//! Construct LocalStreamSocket fds from socket pair, initialize eventloop
TCPSpongeSocket(std::pair<FileDescriptor, FileDescriptor> data_socket_pair, FileDescriptor &&dgramfd);
std::atomic_bool _abort{false}; //!< Flag used by the owner to force the TCPConnection thread to shut down
bool _inbound_shutdown{false}; //!< Has TCPSpongeSocket shut down the incoming data to the owner?
bool _outbound_shutdown{false}; //!< Has the owner shut down the outbound data to the TCP connection?
bool _fully_acked{false}; //!< Has the outbound data been fully acknowledged by the peer?
public:
//! Construct from the FileDescriptor that the TCPConnection thread will use to read and write datagrams
explicit TCPSpongeSocket(FileDescriptor &&dgramfd);
//! Close socket, and wait for TCPConnection to finish
//! \note Calling this function is only advisable if the socket has reached EOF,
//! or else may wait foreever for remote peer to close the TCP connection.
void wait_until_closed();
//! Connect using the specified configurations; blocks until connect succeeds or fails
void connect(const TCPConfig &c_tcp, const FdAdapterConfig &c_ad);
//! Listen and accept using the specified configurations; blocks until accept succeeds or fails
void listen_and_accept(const TCPConfig &c_tcp, const FdAdapterConfig &c_ad);
//! When a connected socket is destructed, it will send a RST
~TCPSpongeSocket();
//! \name
//! This object cannot be safely moved or copied, since it is in use by two threads simultaneously
//!@{
TCPSpongeSocket(const TCPSpongeSocket &) = delete;
TCPSpongeSocket(TCPSpongeSocket &&) = delete;
TCPSpongeSocket &operator=(const TCPSpongeSocket &) = delete;
TCPSpongeSocket &operator=(TCPSpongeSocket &&) = delete;
//!@}
//! \name
//! Some methods of the parent Socket wouldn't work as expected on the TCP socket, so delete them
//!@{
void bind(const Address &address) = delete;
Address local_address() const = delete;
Address peer_address() const = delete;
void set_reuseaddr() = delete;
//!@}
};
using TCPOverUDPSpongeSocket = TCPSpongeSocket<TCPOverUDPSocketAdapter>;
using TCPOverIPv4SpongeSocket = TCPSpongeSocket<TCPOverIPv4OverTunFdAdapter>;
using LossyTCPOverUDPSpongeSocket = TCPSpongeSocket<LossyTCPOverUDPSocketAdapter>;
using LossyTCPOverIPv4SpongeSocket = TCPSpongeSocket<LossyTCPOverIPv4OverTunFdAdapter>;
//! \class TCPSpongeSocket
//! This class involves the simultaneous operation of two threads.
//!
//! One, the "owner" or foreground thread, interacts with this class in much the
//! same way as one would interact with a TCPSocket: it connects or listens, writes to
//! and reads from a reliable data stream, etc. Only the owner thread calls public
//! methods of this class.
//!
//! The other, the "TCPConnection" thread, takes care of the back-end tasks that the kernel would
//! perform for a TCPSocket: reading and parsing datagrams from the wire, filtering out
//! segments unrelated to the connection, etc.
//!
//! There are a few notable differences between the TCPSpongeSocket and TCPSocket interfaces:
//!
//! - a TCPSpongeSocket can only accept a single connection
//! - listen_and_accept() is a blocking function call that acts as both [listen(2)](\ref man2::listen)
//! and [accept(2)](\ref man2::accept)
//! - if TCPSpongeSocket is destructed while a TCP connection is open, the connection is
//! immediately terminated with a RST (call `wait_until_closed` to avoid this)
//! Helper class that makes a TCPOverIPv4SpongeSocket behave more like a (kernel) TCPSocket
class CS144TCPSocket : public TCPOverIPv4SpongeSocket {
public:
CS144TCPSocket();
void connect(const Address &address);
};
#endif // SPONGE_LIBSPONGE_TCP_SPONGE_SOCKET_HH
#include "tcp_state.hh"
using namespace std;
bool TCPState::operator==(const TCPState &other) const {
return _active == other._active and _linger_after_streams_finish == other._linger_after_streams_finish and
_sender == other._sender and _receiver == other._receiver;
}
bool TCPState::operator!=(const TCPState &other) const { return not operator==(other); }
string TCPState::name() const {
return "sender=`" + _sender + "`, receiver=`" + _receiver + "`, active=" + to_string(_active) +
", linger_after_streams_finish=" + to_string(_linger_after_streams_finish);
}
TCPState::TCPState(const TCPState::State state) {
switch (state) {
case TCPState::State::LISTEN:
_receiver = TCPReceiverStateSummary::LISTEN;
_sender = TCPSenderStateSummary::CLOSED;
break;
case TCPState::State::SYN_RCVD:
_receiver = TCPReceiverStateSummary::SYN_RECV;
_sender = TCPSenderStateSummary::SYN_SENT;
break;
case TCPState::State::SYN_SENT:
_receiver = TCPReceiverStateSummary::LISTEN;
_sender = TCPSenderStateSummary::SYN_SENT;
break;
case TCPState::State::ESTABLISHED:
_receiver = TCPReceiverStateSummary::SYN_RECV;
_sender = TCPSenderStateSummary::SYN_ACKED;
break;
case TCPState::State::CLOSE_WAIT:
_receiver = TCPReceiverStateSummary::FIN_RECV;
_sender = TCPSenderStateSummary::SYN_ACKED;
_linger_after_streams_finish = false;
break;
case TCPState::State::LAST_ACK:
_receiver = TCPReceiverStateSummary::FIN_RECV;
_sender = TCPSenderStateSummary::FIN_SENT;
_linger_after_streams_finish = false;
break;
case TCPState::State::CLOSING:
_receiver = TCPReceiverStateSummary::FIN_RECV;
_sender = TCPSenderStateSummary::FIN_SENT;
break;
case TCPState::State::FIN_WAIT_1:
_receiver = TCPReceiverStateSummary::SYN_RECV;
_sender = TCPSenderStateSummary::FIN_SENT;
break;
case TCPState::State::FIN_WAIT_2:
_receiver = TCPReceiverStateSummary::SYN_RECV;
_sender = TCPSenderStateSummary::FIN_ACKED;
break;
case TCPState::State::TIME_WAIT:
_receiver = TCPReceiverStateSummary::FIN_RECV;
_sender = TCPSenderStateSummary::FIN_ACKED;
break;
case TCPState::State::RESET:
_receiver = TCPReceiverStateSummary::ERROR;
_sender = TCPSenderStateSummary::ERROR;
_linger_after_streams_finish = false;
_active = false;
break;
case TCPState::State::CLOSED:
_receiver = TCPReceiverStateSummary::FIN_RECV;
_sender = TCPSenderStateSummary::FIN_ACKED;
_linger_after_streams_finish = false;
_active = false;
break;
}
}
TCPState::TCPState(const TCPSender &sender, const TCPReceiver &receiver, const bool active, const bool linger)
: _sender(state_summary(sender))
, _receiver(state_summary(receiver))
, _active(active)
, _linger_after_streams_finish(active ? linger : false) {}
string TCPState::state_summary(const TCPReceiver &receiver) {
if (receiver.stream_out().error()) {
return TCPReceiverStateSummary::ERROR;
} else if (not receiver.ackno().has_value()) {
return TCPReceiverStateSummary::LISTEN;
} else if (receiver.stream_out().input_ended()) {
return TCPReceiverStateSummary::FIN_RECV;
} else {
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;
}
}
#ifndef SPONGE_LIBSPONGE_TCP_STATE
#define SPONGE_LIBSPONGE_TCP_STATE
#include "tcp_receiver.hh"
#include "tcp_sender.hh"
#include <string>
//! \brief Summary of a TCPConnection's internal state
//!
//! Most TCP implementations have a global per-connection state
//! machine, as described in the [TCP](\ref rfc::rfc793)
//! specification. Sponge is a bit different: we have factored the
//! connection into two independent parts (the sender and the
//! receiver). The TCPSender and TCPReceiver maintain their interval
//! state variables independently (e.g. next_seqno, number of bytes in
//! flight, or whether each stream has ended). There is no notion of a
//! discrete state machine or much overarching state outside the
//! sender and receiver. To test that Sponge follows the TCP spec, we
//! use this class to compare the "official" states with Sponge's
//! sender/receiver states and two variables that belong to the
//! overarching TCPConnection object.
class TCPState {
private:
std::string _sender{};
std::string _receiver{};
bool _active{true};
bool _linger_after_streams_finish{true};
public:
bool operator==(const TCPState &other) const;
bool operator!=(const TCPState &other) const;
//! \brief Official state names from the [TCP](\ref rfc::rfc793) specification
enum class State {
LISTEN = 0, //!< Listening for a peer to connect
SYN_RCVD, //!< Got the peer's SYN
SYN_SENT, //!< Sent a SYN to initiate a connection
ESTABLISHED, //!< Three-way handshake complete
CLOSE_WAIT, //!< Remote side has sent a FIN, connection is half-open
LAST_ACK, //!< Local side sent a FIN from CLOSE_WAIT, waiting for ACK
FIN_WAIT_1, //!< Sent a FIN to the remote side, not yet ACK'd
FIN_WAIT_2, //!< Received an ACK for previously-sent FIN
CLOSING, //!< Received a FIN just after we sent one
TIME_WAIT, //!< Both sides have sent FIN and ACK'd, waiting for 2 MSL
CLOSED, //!< A connection that has terminated normally
RESET, //!< A connection that terminated abnormally
};
//! \brief Summarize the TCPState in a string
std::string name() const;
//! \brief Construct a TCPState given a sender, a receiver, and the TCPConnection's active and linger bits
TCPState(const TCPSender &sender, const TCPReceiver &receiver, const bool active, const bool linger);
//! \brief Construct a TCPState that corresponds to one of the "official" TCP state names
TCPState(const TCPState::State state);
//! \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 {
const std::string ERROR = "error (connection was reset)";
const std::string LISTEN = "waiting for stream to begin (listening for SYN)";
const std::string SYN_RECV = "stream started";
const std::string FIN_RECV = "stream finished";
} // 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
#include "tunfd_adapter.hh"
#include "ipv4_datagram.hh"
#include "ipv4_header.hh"
#include "parser.hh"
#include <arpa/inet.h>
#include <stdexcept>
#include <unistd.h>
#include <utility>
using namespace std;
//! \details This function first attempts to parse an IP header from the next
//! payload read from the TUN device, then attempts to parse a TCP segment from
//! the IP datagram's payload.
//!
//! If this succeeds, it then checks that the received segment is related to the
//! current connection. When a TCP connection has been established, this means
//! checking that the source and destination ports in the TCP header are correct.
//!
//! If the TCP FSM is listening (i.e., TCPOverIPv4OverTunFdAdapter::_listen is `true`)
//! and the TCP segment read from the wire includes a SYN, this function clears the
//! `_listen` flag and records the source and destination addresses and port numbers
//! from the TCP header; it uses this information to filter future reads.
//! \returns a std::optional<TCPSegment> that is empty if the segment was invalid or unrelated
optional<TCPSegment> TCPOverIPv4OverTunFdAdapter::read() {
// is the packet a valid IPv4 datagram?
auto ip_dgram = IPv4Datagram{};
if (ParseResult::NoError != ip_dgram.parse(FileDescriptor::read())) {
return {};
}
// is the IPv4 datagram for us?
// Note: it's valid to bind to address "0" (INADDR_ANY) and reply from actual address contacted
if (not listening() and (ip_dgram.header().dst != config().source.ipv4_numeric())) {
return {};
}
// is the IPv4 datagram from our peer?
if (not listening() and (ip_dgram.header().src != config().destination.ipv4_numeric())) {
return {};
}
// does the IPv4 datagram claim that its payload is a TCP segment?
if (ip_dgram.header().proto != IPv4Header::PROTO_TCP) {
return {};
}
// is the payload a valid TCP segment?
TCPSegment tcp_seg;
if (ParseResult::NoError != tcp_seg.parse(ip_dgram.payload(), ip_dgram.header().pseudo_cksum())) {
return {};
}
// is the TCP segment for us?
if (tcp_seg.header().dport != config().source.port()) {
return {};
}
// should we target this source addr/port (and use its destination addr as our source) in reply?
if (listening()) {
if (tcp_seg.header().syn and not tcp_seg.header().rst) {
config_mutable().source = {inet_ntoa({htobe32(ip_dgram.header().dst)}), config().source.port()};
config_mutable().destination = {inet_ntoa({htobe32(ip_dgram.header().src)}), tcp_seg.header().sport};
set_listening(false);
} else {
return {};
}
}
// is the TCP segment from our peer?
if (tcp_seg.header().sport != config().destination.port()) {
return {};
}
return tcp_seg;
}
//! Serializes a TCP segment into an IPv4 datagram, serialize the IPv4 datagram, and send it to the TUN device.
//! \param[in] seg is the TCP segment to write
void TCPOverIPv4OverTunFdAdapter::write(TCPSegment &seg) {
// set the port numbers in the TCP segment
seg.header().sport = config().source.port();
seg.header().dport = config().destination.port();
// create an Internet Datagram and set its addresses and length
IPv4Datagram ip_dgram;
ip_dgram.header().src = config().source.ipv4_numeric();
ip_dgram.header().dst = config().destination.ipv4_numeric();
ip_dgram.header().len = ip_dgram.header().hlen * 4 + seg.header().doff * 4 + seg.payload().size();
// set payload, calculating TCP checksum using information from IP header
ip_dgram.payload() = seg.serialize(ip_dgram.header().pseudo_cksum());
// send
FileDescriptor::write(ip_dgram.serialize());
}
//! Specialize LossyFdAdapter to TCPOverIPv4OverTunFdAdapter
template class LossyFdAdapter<TCPOverIPv4OverTunFdAdapter>;
#ifndef SPONGE_LIBSPONGE_TUNFD_ADAPTER_HH
#define SPONGE_LIBSPONGE_TUNFD_ADAPTER_HH
#include "fd_adapter.hh"
#include "file_descriptor.hh"
#include "lossy_fd_adapter.hh"
#include "tcp_segment.hh"
#include <optional>
#include <utility>
//! \brief A FD adapter for IPv4 datagrams read from and written to a TUN device
class TCPOverIPv4OverTunFdAdapter : public FdAdapterBase, public FileDescriptor {
public:
//! Construct from a TunFD sliced into a FileDescriptor
explicit TCPOverIPv4OverTunFdAdapter(FileDescriptor &&fd) : FileDescriptor(std::move(fd)) {}
//! Attempts to read and parse an IPv4 datagram containing a TCP segment related to the current connection
std::optional<TCPSegment> read();
//! Creates an IPv4 datagram from a TCP segment and writes it to the TUN device
void write(TCPSegment &seg);
};
//! Typedef for TCPOverIPv4OverTunFdAdapter
using LossyTCPOverIPv4OverTunFdAdapter = LossyFdAdapter<TCPOverIPv4OverTunFdAdapter>;
#endif // SPONGE_LIBSPONGE_TUNFD_ADAPTER_HH
#include "tcp_receiver.hh"
// Dummy implementation of a TCP receiver
// For Lab 2, please replace with a real implementation that passes the
// automated checks run by `make check_lab2`.
template <typename... Targs>
void DUMMY_CODE(Targs &&... /* unused */) {}
using namespace std;
bool TCPReceiver::segment_received(const TCPSegment &seg) {
DUMMY_CODE(seg);
return {};
}
optional<WrappingInt32> TCPReceiver::ackno() const { return {}; }
size_t TCPReceiver::window_size() const { return {}; }
#ifndef SPONGE_LIBSPONGE_TCP_RECEIVER_HH
#define SPONGE_LIBSPONGE_TCP_RECEIVER_HH
#include "byte_stream.hh"
#include "stream_reassembler.hh"
#include "tcp_segment.hh"
#include "wrapping_integers.hh"
#include <optional>
//! \brief The "receiver" part of a TCP implementation.
//! Receives and reassembles segments into a ByteStream, and computes
//! the acknowledgment number and window size to advertise back to the
//! remote TCPSender.
class TCPReceiver {
//! Our data structure for re-assembling bytes.
StreamReassembler _reassembler;
//! The maximum number of bytes we'll store.
size_t _capacity;
public:
//! \brief Construct a TCP receiver
//!
//! \param capacity the maximum number of bytes that the receiver will
//! store in its buffers at any give time.
TCPReceiver(const size_t capacity) : _reassembler(capacity), _capacity(capacity) {}
//! \name Accessors to provide feedback to the remote TCPSender
//!@{
//! \brief The ackno that should be sent to the peer
//! \returns empty if no SYN has been received
//!
//! This is the beginning of the receiver's window, or in other words, the sequence number
//! of the first byte in the stream that the receiver hasn't received.
std::optional<WrappingInt32> ackno() const;
//! \brief The window size that should be sent to the peer
//!
//! Operationally: the capacity minus the number of bytes that the
//! TCPReceiver is holding in its byte stream (those that have been
//! reassembled, but not consumed).
//!
//! Formally: the difference between (a) the sequence number of
//! the first byte that falls after the window (and will not be
//! accepted by the receiver) and (b) the sequence number of the
//! beginning of the window (the ackno).
size_t window_size() const;
//!@}
//! \brief number of bytes stored but not yet reassembled
size_t unassembled_bytes() const { return _reassembler.unassembled_bytes(); }
//! \brief handle an inbound segment
//! \returns `true` if any part of the segment was inside the window
bool segment_received(const TCPSegment &seg);
//! \name "Output" interface for the reader
//!@{
ByteStream &stream_out() { return _reassembler.stream_out(); }
const ByteStream &stream_out() const { return _reassembler.stream_out(); }
//!@}
};
#endif // SPONGE_LIBSPONGE_TCP_RECEIVER_HH
#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
//! \returns `false` if the ackno appears invalid (acknowledges something the TCPSender hasn't sent yet)
bool TCPSender::ack_received(const WrappingInt32 ackno, const uint16_t window_size) {
DUMMY_CODE(ackno, window_size);
return {};
}
//! \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() {}
#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
bool 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
#include "address.hh"
#include "util.hh"
#include <arpa/inet.h>
#include <cstring>
#include <memory>
#include <netdb.h>
#include <stdexcept>
#include <system_error>
using namespace std;
//! Converts Raw to `sockaddr *`.
Address::Raw::operator sockaddr *() { return reinterpret_cast<sockaddr *>(&storage); }
//! Converts Raw to `const sockaddr *`.
Address::Raw::operator const sockaddr *() const { return reinterpret_cast<const sockaddr *>(&storage); }
//! \param[in] addr points to a raw socket address
//! \param[in] size is `addr`'s length
Address::Address(const sockaddr *addr, const size_t size) : _size(size) {
// make sure proposed sockaddr can fit
if (size > sizeof(_address.storage)) {
throw runtime_error("invalid sockaddr size");
}
memcpy(&_address.storage, addr, size);
}
//! Error category for getaddrinfo and getnameinfo failures.
class gai_error_category : public error_category {
public:
//! The name of the wrapped error
const char *name() const noexcept override { return "gai_error_category"; }
//! \brief An error message
//! \param[in] return_value the error return value from [getaddrinfo(3)](\ref man3::getaddrinfo)
//! or [getnameinfo(3)](\ref man3::getnameinfo)
string message(const int return_value) const noexcept override { return gai_strerror(return_value); }
};
//! \param[in] node is the hostname or dotted-quad address
//! \param[in] service is the service name or numeric string
//! \param[in] hints are criteria for resolving the supplied name
Address::Address(const string &node, const string &service, const addrinfo &hints) : _size() {
// prepare for the answer
addrinfo *resolved_address = nullptr;
// look up the name or names
const int gai_ret = getaddrinfo(node.c_str(), service.c_str(), &hints, &resolved_address);
if (gai_ret != 0) {
throw tagged_error(gai_error_category(), "getaddrinfo(" + node + ", " + service + ")", gai_ret);
}
// if success, should always have at least one entry
if (resolved_address == nullptr) {
throw runtime_error("getaddrinfo returned successfully but with no results");
}
// put resolved_address in a wrapper so it will get freed if we have to throw an exception
auto addrinfo_deleter = [](addrinfo *const x) { freeaddrinfo(x); };
unique_ptr<addrinfo, decltype(addrinfo_deleter)> wrapped_address(resolved_address, move(addrinfo_deleter));
// assign to our private members (making sure size fits)
*this = Address(wrapped_address->ai_addr, wrapped_address->ai_addrlen);
}
//! \brief Build a `struct addrinfo` containing hints for [getaddrinfo(3)](\ref man3::getaddrinfo)
//! \param[in] ai_flags is the value of the `ai_flags` field in the [struct addrinfo](\ref man3::getaddrinfo)
//! \param[in] ai_family is the value of the `ai_family` field in the [struct addrinfo](\ref man3::getaddrinfo)
static inline addrinfo make_hints(const int ai_flags, const int ai_family) {
addrinfo hints{}; // value initialized to all zeros
hints.ai_flags = ai_flags;
hints.ai_family = ai_family;
return hints;
}
//! \param[in] hostname to resolve
//! \param[in] service name (from `/etc/services`, e.g., "http" is port 80)
Address::Address(const string &hostname, const string &service)
: Address(hostname, service, make_hints(AI_ALL, AF_INET)) {}
//! \param[in] ip address as a dotted quad ("1.1.1.1")
//! \param[in] port number
Address::Address(const string &ip, const uint16_t port)
// tell getaddrinfo that we don't want to resolve anything
: Address(ip, ::to_string(port), make_hints(AI_NUMERICHOST | AI_NUMERICSERV, AF_INET)) {}
// accessors
pair<string, uint16_t> Address::ip_port() const {
array<char, NI_MAXHOST> ip{};
array<char, NI_MAXSERV> port{};
const int gni_ret =
getnameinfo(_address, _size, ip.data(), ip.size(), port.data(), port.size(), NI_NUMERICHOST | NI_NUMERICSERV);
if (gni_ret != 0) {
throw tagged_error(gai_error_category(), "getnameinfo", gni_ret);
}
return {ip.data(), stoi(port.data())};
}
string Address::to_string() const {
const auto ip_and_port = ip_port();
return ip_and_port.first + ":" + ::to_string(ip_and_port.second);
}
uint32_t Address::ipv4_numeric() const {
if (_address.storage.ss_family != AF_INET or _size != sizeof(sockaddr_in)) {
throw runtime_error("ipv4_numeric called on non-IPV4 address");
}
sockaddr_in ipv4_addr{};
memcpy(&ipv4_addr, &_address.storage, _size);
return be32toh(ipv4_addr.sin_addr.s_addr);
}
// equality
bool Address::operator==(const Address &other) const {
if (_size != other._size) {
return false;
}
return 0 == memcmp(&_address, &other._address, _size);
}
#ifndef SPONGE_LIBSPONGE_ADDRESS_HH
#define SPONGE_LIBSPONGE_ADDRESS_HH
#include <cstddef>
#include <cstdint>
#include <netdb.h>
#include <netinet/in.h>
#include <string>
#include <sys/socket.h>
#include <utility>
//! Wrapper around [IPv4 addresses](@ref man7::ip) and DNS operations.
class Address {
public:
//! \brief Wrapper around [sockaddr_storage](@ref man7::socket).
//! \details A `sockaddr_storage` is enough space to store any socket address (IPv4 or IPv6).
class Raw {
public:
sockaddr_storage storage{}; //!< The wrapped struct itself.
operator sockaddr *();
operator const sockaddr *() const;
};
private:
socklen_t _size; //!< Size of the wrapped address.
Raw _address{}; //!< A wrapped [sockaddr_storage](@ref man7::socket) containing the address.
//! Constructor from ip/host, service/port, and hints to the resolver.
Address(const std::string &node, const std::string &service, const addrinfo &hints);
public:
//! Construct by resolving a hostname and servicename.
Address(const std::string &hostname, const std::string &service);
//! Construct from dotted-quad string ("18.243.0.1") and numeric port.
Address(const std::string &ip, const std::uint16_t port);
//! Construct from a [sockaddr *](@ref man7::socket).
Address(const sockaddr *addr, const std::size_t size);
//! Equality comparison.
bool operator==(const Address &other) const;
bool operator!=(const Address &other) const { return not operator==(other); }
//! \name Conversions
//!@{
//! Dotted-quad IP address string ("18.243.0.1") and numeric port.
std::pair<std::string, uint16_t> ip_port() const;
//! Dotted-quad IP address string ("18.243.0.1").
std::string ip() const { return ip_port().first; }
//! Numeric port (host byte order).
uint16_t port() const { return ip_port().second; }
//! Numeric IP address as an integer (i.e., in [host byte order](\ref man3::byteorder)).
uint32_t ipv4_numeric() const;
//! Human-readable string, e.g., "8.8.8.8:53".
std::string to_string() const;
//!@}
//! \name Low-level operations
//!@{
//! Size of the underlying address storage.
socklen_t size() const { return _size; }
//! Const pointer to the underlying socket address storage.
operator const sockaddr *() const { return _address; }
//!@}
};
//! \class Address
//! For example, you can do DNS lookups:
//!
//! \include address_example_1.cc
//!
//! or you can specify an IP address and port number:
//!
//! \include address_example_2.cc
//!
//! Once you have an address, you can convert it to other useful representations, e.g.,
//!
//! \include address_example_3.cc
#endif // SPONGE_LIBSPONGE_ADDRESS_HH
#include "buffer.hh"
using namespace std;
void Buffer::remove_prefix(const size_t n) {
if (n > str().size()) {
throw out_of_range("Buffer::remove_prefix");
}
_starting_offset += n;
if (_storage and _starting_offset == _storage->size()) {
_storage.reset();
}
}
void BufferList::append(const BufferList &other) {
for (const auto &buf : other._buffers) {
_buffers.push_back(buf);
}
}
BufferList::operator Buffer() const {
switch (_buffers.size()) {
case 0:
return {};
case 1:
return _buffers[0];
default: {
throw runtime_error(
"BufferList: please use concatenate() to combine a multi-Buffer BufferList into one Buffer");
}
}
}
string BufferList::concatenate() const {
std::string ret;
ret.reserve(size());
for (const auto &buf : _buffers) {
ret.append(buf);
}
return ret;
}
size_t BufferList::size() const {
size_t ret = 0;
for (const auto &buf : _buffers) {
ret += buf.size();
}
return ret;
}
void BufferList::remove_prefix(size_t n) {
while (n > 0) {
if (_buffers.empty()) {
throw std::out_of_range("BufferList::remove_prefix");
}
if (n < _buffers.front().str().size()) {
_buffers.front().remove_prefix(n);
n = 0;
} else {
n -= _buffers.front().str().size();
_buffers.pop_front();
}
}
}
BufferViewList::BufferViewList(const BufferList &buffers) {
for (const auto &x : buffers.buffers()) {
_views.push_back(x);
}
}
void BufferViewList::remove_prefix(size_t n) {
while (n > 0) {
if (_views.empty()) {
throw std::out_of_range("BufferListView::remove_prefix");
}
if (n < _views.front().size()) {
_views.front().remove_prefix(n);
n = 0;
} else {
n -= _views.front().size();
_views.pop_front();
}
}
}
size_t BufferViewList::size() const {
size_t ret = 0;
for (const auto &buf : _views) {
ret += buf.size();
}
return ret;
}
vector<iovec> BufferViewList::as_iovecs() const {
vector<iovec> ret;
ret.reserve(_views.size());
for (const auto &x : _views) {
ret.push_back({const_cast<char *>(x.data()), x.size()});
}
return ret;
}
#ifndef SPONGE_LIBSPONGE_BUFFER_HH
#define SPONGE_LIBSPONGE_BUFFER_HH
#include <algorithm>
#include <deque>
#include <memory>
#include <numeric>
#include <string>
#include <string_view>
#include <sys/uio.h>
#include <vector>
//! \brief A reference-counted read-only string that can discard bytes from the front
class Buffer {
private:
std::shared_ptr<std::string> _storage{};
size_t _starting_offset{};
public:
Buffer() = default;
//! \brief Construct by taking ownership of a string
Buffer(std::string &&str) noexcept : _storage(std::make_shared<std::string>(std::move(str))) {}
//! \name Expose contents as a std::string_view
//!@{
std::string_view str() const {
if (not _storage) {
return {};
}
return {_storage->data() + _starting_offset, _storage->size() - _starting_offset};
}
operator std::string_view() const { return str(); }
//!@}
//! \brief Get character at location `n`
uint8_t at(const size_t n) const { return str().at(n); }
//! \brief Size of the string
size_t size() const { return str().size(); }
//! \brief Make a copy to a new std::string
std::string copy() const { return std::string(str()); }
//! \brief Discard the first `n` bytes of the string (does not require a copy or move)
//! \note Doesn't free any memory until the whole string has been discarded in all copies of the Buffer.
void remove_prefix(const size_t n);
};
//! \brief A reference-counted discontiguous string that can discard bytes from the front
//! \note Used to model packets that contain multiple sets of headers
//! + a payload. This allows us to prepend headers (e.g., to
//! encapsulate a TCP payload in a TCPSegment, and then encapsulate
//! the TCPSegment in an IPv4Datagram) without copying the payload.
class BufferList {
private:
std::deque<Buffer> _buffers{};
public:
//! \name Constructors
//!@{
BufferList() = default;
//! \brief Construct from a Buffer
BufferList(Buffer buffer) : _buffers{buffer} {}
//! \brief Construct by taking ownership of a std::string
BufferList(std::string &&str) noexcept {
Buffer buf{std::move(str)};
append(buf);
}
//!@}
//! \brief Access the underlying queue of Buffers
const std::deque<Buffer> &buffers() const { return _buffers; }
//! \brief Append a BufferList
void append(const BufferList &other);
//! \brief Transform to a Buffer
//! \note Throws an exception unless BufferList is contiguous
operator Buffer() const;
//! \brief Discard the first `n` bytes of the string (does not require a copy or move)
void remove_prefix(size_t n);
//! \brief Size of the string
size_t size() const;
//! \brief Make a copy to a new std::string
std::string concatenate() const;
};
//! \brief A non-owning temporary view (similar to std::string_view) of a discontiguous string
class BufferViewList {
std::deque<std::string_view> _views{};
public:
//! \name Constructors
//!@{
//! \brief Construct from a std::string
BufferViewList(const std::string &str) : BufferViewList(std::string_view(str)) {}
//! \brief Construct from a C string (must be NULL-terminated)
BufferViewList(const char *s) : BufferViewList(std::string_view(s)) {}
//! \brief Construct from a BufferList
BufferViewList(const BufferList &buffers);
//! \brief Construct from a std::string_view
BufferViewList(std::string_view str) { _views.push_back({const_cast<char *>(str.data()), str.size()}); }
//!@}
//! \brief Discard the first `n` bytes of the string (does not require a copy or move)
void remove_prefix(size_t n);
//! \brief Size of the string
size_t size() const;
//! \brief Convert to a vector of `iovec` structures
//! \note used for system calls that write discontiguous buffers,
//! e.g. [writev(2)](\ref man2::writev) and [sendmsg(2)](\ref man2::sendmsg)
std::vector<iovec> as_iovecs() const;
};
#endif // SPONGE_LIBSPONGE_BUFFER_HH
#include "eventloop.hh"
#include "util.hh"
#include <cerrno>
#include <stdexcept>
#include <system_error>
#include <utility>
#include <vector>
using namespace std;
unsigned int EventLoop::Rule::service_count() const {
return direction == Direction::In ? fd.read_count() : fd.write_count();
}
//! \param[in] fd is the FileDescriptor to be polled
//! \param[in] direction indicates whether to poll for reading (Direction::In) or writing (Direction::Out)
//! \param[in] callback is called when `fd` is ready.
//! \param[in] interest is called by EventLoop::wait_next_event. If it returns `true`, `fd` will
//! be polled, otherwise `fd` will be ignored only for this execution of `wait_next_event.
//! \param[in] cancel is called when the rule is cancelled (e.g. on hangup, EOF, or closure).
void EventLoop::add_rule(const FileDescriptor &fd,
const Direction direction,
const CallbackT &callback,
const InterestT &interest,
const CallbackT &cancel) {
_rules.push_back({fd.duplicate(), direction, callback, interest, cancel});
}
//! \param[in] timeout_ms is the timeout value passed to [poll(2)](\ref man2::poll); `wait_next_event`
//! returns Result::Timeout if no fd is ready after the timeout expires.
//! \returns Eventloop::Result indicating success, timeout, or no more Rule objects to poll.
//!
//! For each Rule, this function first calls Rule::interest; if `true`, Rule::fd is added to the
//! list of file descriptors to be polled for readability (if Rule::direction == Direction::In) or
//! writability (if Rule::direction == Direction::Out) unless Rule::fd has reached EOF, in which case
//! the Rule is canceled (i.e., deleted from EventLoop::_rules).
//!
//! Next, this function calls [poll(2)](\ref man2::poll) with timeout value `timeout_ms`.
//!
//! Then, for each ready file descriptor, this function calls Rule::callback. If fd reaches EOF or
//! if the Rule was registered using EventLoop::add_cancelable_rule and Rule::callback returns true,
//! this Rule is canceled.
//!
//! If an error occurs during polling, this function throws a std::runtime_error.
//!
//! If a [signal(7)](\ref man7::signal) was caught during polling or if EventLoop::_rules becomes empty,
//! this function returns Result::Exit.
//!
//! If a timeout occurred while polling (i.e., no fd became ready), this function returns Result::Timeout.
//!
//! Otherwise, this function returns Result::Success.
//!
//! \b IMPORTANT: every call to Rule::callback must read from or write to Rule::fd, or the `interest`
//! callback must stop returning true after the callback completes.
//! If none of these conditions occur, EventLoop::wait_next_event will throw std::runtime_error. This is
//! because [poll(2)](\ref man2::poll) is level triggered, so failing to act on a ready file descriptor
//! will result in a busy loop (poll returns on a ready file descriptor; file descriptor is not read or
//! written, so it is still ready; the next call to poll will immediately return).
EventLoop::Result EventLoop::wait_next_event(const int timeout_ms) {
vector<pollfd> pollfds{};
pollfds.reserve(_rules.size());
bool something_to_poll = false;
// set up the pollfd for each rule
for (auto it = _rules.cbegin(); it != _rules.cend();) { // NOTE: it gets erased or incremented in loop body
const auto &this_rule = *it;
if (this_rule.direction == Direction::In && this_rule.fd.eof()) {
// no more reading on this rule, it's reached eof
this_rule.cancel();
it = _rules.erase(it);
continue;
}
if (this_rule.fd.closed()) {
this_rule.cancel();
it = _rules.erase(it);
continue;
}
if (this_rule.interest()) {
pollfds.push_back({this_rule.fd.fd_num(), static_cast<short>(this_rule.direction), 0});
something_to_poll = true;
} else {
pollfds.push_back({this_rule.fd.fd_num(), 0, 0}); // placeholder --- we still want errors
}
++it;
}
// quit if there is nothing left to poll
if (not something_to_poll) {
return Result::Exit;
}
// call poll -- wait until one of the fds satisfies one of the rules (writeable/readable)
try {
if (0 == SystemCall("poll", ::poll(pollfds.data(), pollfds.size(), timeout_ms))) {
return Result::Timeout;
}
} catch (unix_error const &e) {
if (e.code().value() == EINTR) {
return Result::Exit;
}
}
// go through the poll results
for (auto [it, idx] = make_pair(_rules.begin(), size_t(0)); it != _rules.end(); ++idx) {
const auto &this_pollfd = pollfds[idx];
const auto poll_error = static_cast<bool>(this_pollfd.revents & (POLLERR | POLLNVAL));
if (poll_error) {
throw runtime_error("EventLoop: error on polled file descriptor");
}
const auto &this_rule = *it;
const auto poll_ready = static_cast<bool>(this_pollfd.revents & this_pollfd.events);
const auto poll_hup = static_cast<bool>(this_pollfd.revents & POLLHUP);
if (poll_hup && this_pollfd.events && !poll_ready) {
// if we asked for the status, and the _only_ condition was a hangup, this FD is defunct:
// - if it was POLLIN and nothing is readable, no more will ever be readable
// - if it was POLLOUT, it will not be writable again
this_rule.cancel();
it = _rules.erase(it);
continue;
}
if (poll_ready) {
// we only want to call callback if revents includes the event we asked for
const auto count_before = this_rule.service_count();
this_rule.callback();
// only check for busy wait if we're not canceling or exiting
if (count_before == this_rule.service_count() and this_rule.interest()) {
throw runtime_error(
"EventLoop: busy wait detected: callback did not read/write fd and is still interested");
}
}
++it; // if we got here, it means we didn't call _rules.erase()
}
return Result::Success;
}
#ifndef SPONGE_LIBSPONGE_EVENTLOOP_HH
#define SPONGE_LIBSPONGE_EVENTLOOP_HH
#include "file_descriptor.hh"
#include <cstdlib>
#include <functional>
#include <list>
#include <poll.h>
//! Waits for events on file descriptors and executes corresponding callbacks.
class EventLoop {
public:
//! Indicates interest in reading (In) or writing (Out) a polled fd.
enum class Direction : short {
In = POLLIN, //!< Callback will be triggered when Rule::fd is readable.
Out = POLLOUT //!< Callback will be triggered when Rule::fd is writable.
};
private:
using CallbackT = std::function<void(void)>; //!< Callback for ready Rule::fd
using InterestT = std::function<bool(void)>; //!< `true` return indicates Rule::fd should be polled.
//! \brief Specifies a condition and callback that an EventLoop should handle.
//! \details Created by calling EventLoop::add_rule() or EventLoop::add_cancelable_rule().
class Rule {
public:
FileDescriptor fd; //!< FileDescriptor to monitor for activity.
Direction direction; //!< Direction::In for reading from fd, Direction::Out for writing to fd.
CallbackT callback; //!< A callback that reads or writes fd.
InterestT interest; //!< A callback that returns `true` whenever fd should be polled.
CallbackT cancel; //!< A callback that is called when the rule is cancelled (e.g. on hangup)
//! Returns the number of times fd has been read or written, depending on the value of Rule::direction.
//! \details This function is used internally by EventLoop; you will not need to call it
unsigned int service_count() const;
};
std::list<Rule> _rules{}; //!< All rules that have been added and not canceled.
public:
//! Returned by each call to EventLoop::wait_next_event.
enum class Result {
Success, //!< At least one Rule was triggered.
Timeout, //!< No rules were triggered before timeout.
Exit //!< All rules have been canceled or were uninterested; make no further calls to EventLoop::wait_next_event.
};
//! Add a rule whose callback will be called when `fd` is ready in the specified Direction.
void add_rule(const FileDescriptor &fd,
const Direction direction,
const CallbackT &callback,
const InterestT &interest = [] { return true; },
const CallbackT &cancel = [] {});
//! Calls [poll(2)](\ref man2::poll) and then executes callback for each ready fd.
Result wait_next_event(const int timeout_ms);
};
using Direction = EventLoop::Direction;
//! \class EventLoop
//!
//! An EventLoop holds a std::list of Rule objects. Each time EventLoop::wait_next_event is
//! executed, the EventLoop uses the Rule objects to construct a call to [poll(2)](\ref man2::poll).
//!
//! When a Rule is installed using EventLoop::add_rule, it will be polled for the specified Rule::direction
//! whenver the Rule::interest callback returns `true`, until Rule::fd is no longer readable
//! (for Rule::direction == Direction::In) or writable (for Rule::direction == Direction::Out).
//! Once this occurs, the Rule is canceled, i.e., the EventLoop deletes it.
//!
//! A Rule installed using EventLoop::add_cancelable_rule will be polled and canceled under the
//! same conditions, with the additional condition that if Rule::callback returns `true`, the
//! Rule will be canceled.
#endif // SPONGE_LIBSPONGE_EVENTLOOP_HH
#include "file_descriptor.hh"
#include "util.hh"
#include <algorithm>
#include <fcntl.h>
#include <iostream>
#include <stdexcept>
#include <sys/uio.h>
#include <unistd.h>
using namespace std;
//! \param[in] fd is the file descriptor number returned by [open(2)](\ref man2::open) or similar
FileDescriptor::FDWrapper::FDWrapper(const int fd) : _fd(fd) {
if (fd < 0) {
throw runtime_error("invalid fd number:" + to_string(fd));
}
}
void FileDescriptor::FDWrapper::close() {
SystemCall("close", ::close(_fd));
_eof = _closed = true;
}
FileDescriptor::FDWrapper::~FDWrapper() {
try {
if (_closed) {
return;
}
close();
} catch (const exception &e) {
// don't throw an exception from the destructor
std::cerr << "Exception destructing FDWrapper: " << e.what() << std::endl;
}
}
//! \param[in] fd is the file descriptor number returned by [open(2)](\ref man2::open) or similar
FileDescriptor::FileDescriptor(const int fd) : _internal_fd(make_shared<FDWrapper>(fd)) {}
//! Private constructor used by duplicate()
FileDescriptor::FileDescriptor(shared_ptr<FDWrapper> other_shared_ptr) : _internal_fd(move(other_shared_ptr)) {}
//! \returns a copy of this FileDescriptor
FileDescriptor FileDescriptor::duplicate() const { return FileDescriptor(_internal_fd); }
//! \param[in] limit is the maximum number of bytes to read; fewer bytes may be returned
//! \param[out] str is the string to be read
void FileDescriptor::read(std::string &str, const size_t limit) {
constexpr size_t BUFFER_SIZE = 1024 * 1024; // maximum size of a read
const size_t size_to_read = min(BUFFER_SIZE, limit);
str.resize(size_to_read);
ssize_t bytes_read = SystemCall("read", ::read(fd_num(), str.data(), size_to_read));
if (limit > 0 && bytes_read == 0) {
_internal_fd->_eof = true;
}
if (bytes_read > static_cast<ssize_t>(size_to_read)) {
throw runtime_error("read() read more than requested");
}
str.resize(bytes_read);
register_read();
}
//! \param[in] limit is the maximum number of bytes to read; fewer bytes may be returned
//! \returns a vector of bytes read
string FileDescriptor::read(const size_t limit) {
string ret;
read(ret, limit);
return ret;
}
size_t FileDescriptor::write(BufferViewList buffer, const bool write_all) {
size_t total_bytes_written = 0;
do {
auto iovecs = buffer.as_iovecs();
const ssize_t bytes_written = SystemCall("writev", ::writev(fd_num(), iovecs.data(), iovecs.size()));
if (bytes_written == 0 and buffer.size() != 0) {
throw runtime_error("write returned 0 given non-empty input buffer");
}
if (bytes_written > ssize_t(buffer.size())) {
throw runtime_error("write wrote more than length of input buffer");
}
register_write();
buffer.remove_prefix(bytes_written);
total_bytes_written += bytes_written;
} while (write_all and buffer.size());
return total_bytes_written;
}
void FileDescriptor::set_blocking(const bool blocking_state) {
int flags = SystemCall("fcntl", fcntl(fd_num(), F_GETFL));
if (blocking_state) {
flags ^= (flags & O_NONBLOCK);
} else {
flags |= O_NONBLOCK;
}
SystemCall("fcntl", fcntl(fd_num(), F_SETFL, flags));
}
#ifndef SPONGE_LIBSPONGE_FILE_DESCRIPTOR_HH
#define SPONGE_LIBSPONGE_FILE_DESCRIPTOR_HH
#include "buffer.hh"
#include <array>
#include <cstddef>
#include <limits>
#include <memory>
//! A reference-counted handle to a file descriptor
class FileDescriptor {
//! \brief A handle on a kernel file descriptor.
//! \details FileDescriptor objects contain a std::shared_ptr to a FDWrapper.
class FDWrapper {
public:
int _fd; //!< The file descriptor number returned by the kernel
bool _eof = false; //!< Flag indicating whether FDWrapper::_fd is at EOF
bool _closed = false; //!< Flag indicating whether FDWrapper::_fd has been closed
unsigned _read_count = 0; //!< The number of times FDWrapper::_fd has been read
unsigned _write_count = 0; //!< The numberof times FDWrapper::_fd has been written
//! Construct from a file descriptor number returned by the kernel
explicit FDWrapper(const int fd);
//! Closes the file descriptor upon destruction
~FDWrapper();
//! Calls [close(2)](\ref man2::close) on FDWrapper::_fd
void close();
//! \name
//! An FDWrapper cannot be copied or moved
//!@{
FDWrapper(const FDWrapper &other) = delete;
FDWrapper &operator=(const FDWrapper &other) = delete;
FDWrapper(FDWrapper &&other) = delete;
FDWrapper &operator=(FDWrapper &&other) = delete;
//!@}
};
//! A reference-counted handle to a shared FDWrapper
std::shared_ptr<FDWrapper> _internal_fd;
// private constructor used to duplicate the FileDescriptor (increase the reference count)
explicit FileDescriptor(std::shared_ptr<FDWrapper> other_shared_ptr);
protected:
void register_read() { ++_internal_fd->_read_count; } //!< increment read count
void register_write() { ++_internal_fd->_write_count; } //!< increment write count
public:
//! Construct from a file descriptor number returned by the kernel
explicit FileDescriptor(const int fd);
//! Free the std::shared_ptr; the FDWrapper destructor calls close() when the refcount goes to zero.
~FileDescriptor() = default;
//! Read up to `limit` bytes
std::string read(const size_t limit = std::numeric_limits<size_t>::max());
//! Read up to `limit` bytes into `str` (caller can allocate storage)
void read(std::string &str, const size_t limit = std::numeric_limits<size_t>::max());
//! Write a string, possibly blocking until all is written
size_t write(const char *str, const bool write_all = true) { return write(BufferViewList(str), write_all); }
//! Write a string, possibly blocking until all is written
size_t write(const std::string &str, const bool write_all = true) { return write(BufferViewList(str), write_all); }
//! Write a buffer (or list of buffers), possibly blocking until all is written
size_t write(BufferViewList buffer, const bool write_all = true);
//! Close the underlying file descriptor
void close() { _internal_fd->close(); }
//! Copy a FileDescriptor explicitly, increasing the FDWrapper refcount
FileDescriptor duplicate() const;
//! Set blocking(true) or non-blocking(false)
void set_blocking(const bool blocking_state);
//! \name FDWrapper accessors
//!@{
int fd_num() const { return _internal_fd->_fd; } //!< \brief underlying descriptor number
bool eof() const { return _internal_fd->_eof; } //!< \brief EOF flag state
bool closed() const { return _internal_fd->_closed; } //!< \brief closed flag state
unsigned int read_count() const { return _internal_fd->_read_count; } //!< \brief number of reads
unsigned int write_count() const { return _internal_fd->_write_count; } //!< \brief number of writes
//!@}
//! \name Copy/move constructor/assignment operators
//! FileDescriptor can be moved, but cannot be copied (but see duplicate())
//!@{
FileDescriptor(const FileDescriptor &other) = delete; //!< \brief copy construction is forbidden
FileDescriptor &operator=(const FileDescriptor &other) = delete; //!< \brief copy assignment is forbidden
FileDescriptor(FileDescriptor &&other) = default; //!< \brief move construction is allowed
FileDescriptor &operator=(FileDescriptor &&other) = default; //!< \brief move assignment is allowed
//!@}
};
//! \class FileDescriptor
//! In addition, FileDescriptor tracks EOF state and calls to FileDescriptor::read and
//! FileDescriptor::write, which EventLoop uses to detect busy loop conditions.
//!
//! For an example of FileDescriptor use, see the EventLoop class documentation.
#endif // SPONGE_LIBSPONGE_FILE_DESCRIPTOR_HH
#include "parser.hh"
using namespace std;
//! \param[in] r is the ParseResult to show
//! \returns a string representation of the ParseResult
string as_string(const ParseResult r) {
static constexpr const char *_names[] = {
"NoError",
"BadChecksum",
"PacketTooShort",
"WrongIPVersion",
"HeaderTooShort",
"TruncatedPacket",
};
return _names[static_cast<size_t>(r)];
}
void NetParser::_check_size(const size_t size) {
if (size > _buffer.size()) {
set_error(ParseResult::PacketTooShort);
}
}
template <typename T>
T NetParser::_parse_int() {
constexpr size_t len = sizeof(T);
_check_size(len);
if (error()) {
return 0;
}
T ret = 0;
for (size_t i = 0; i < len; i++) {
ret <<= 8;
ret += uint8_t(_buffer.at(i));
}
_buffer.remove_prefix(len);
return ret;
}
void NetParser::remove_prefix(const size_t n) {
_check_size(n);
if (error()) {
return;
}
_buffer.remove_prefix(n);
}
template <typename T>
void NetUnparser::_unparse_int(string &s, T val) {
constexpr size_t len = sizeof(T);
for (size_t i = 0; i < len; ++i) {
const uint8_t the_byte = (val >> ((len - i - 1) * 8)) & 0xff;
s.push_back(the_byte);
}
}
uint32_t NetParser::u32() { return _parse_int<uint32_t>(); }
uint16_t NetParser::u16() { return _parse_int<uint16_t>(); }
uint8_t NetParser::u8() { return _parse_int<uint8_t>(); }
void NetUnparser::u32(string &s, const uint32_t val) { return _unparse_int<uint32_t>(s, val); }
void NetUnparser::u16(string &s, const uint16_t val) { return _unparse_int<uint16_t>(s, val); }
void NetUnparser::u8(string &s, const uint8_t val) { return _unparse_int<uint8_t>(s, val); }
#ifndef SPONGE_LIBSPONGE_PARSER_HH
#define SPONGE_LIBSPONGE_PARSER_HH
#include "buffer.hh"
#include <cstdint>
#include <cstdlib>
#include <string>
#include <utility>
//! The result of parsing or unparsing an IP datagram or TCP segment
enum class ParseResult {
NoError = 0, //!< Success
BadChecksum, //!< Bad checksum
PacketTooShort, //!< Not enough data to finish parsing
WrongIPVersion, //!< Got a version of IP other than 4
HeaderTooShort, //!< Header length is shorter than minimum required
TruncatedPacket, //!< Packet length is shorter than header claims
};
//! Output a string representation of a ParseResult
std::string as_string(const ParseResult r);
class NetParser {
private:
Buffer _buffer;
ParseResult _error = ParseResult::NoError; //!< Result of parsing so far
//! Check that there is sufficient data to parse the next token
void _check_size(const size_t size);
//! Generic integer parsing method (used by u32, u16, u8)
template <typename T>
T _parse_int();
public:
NetParser(Buffer buffer) : _buffer(buffer) {}
Buffer buffer() const { return _buffer; }
//! Get the current value stored in BaseParser::_error
ParseResult get_error() const { return _error; }
//! \brief Set BaseParser::_error
//! \param[in] res is the value to store in BaseParser::_error
void set_error(ParseResult res) { _error = res; }
//! Returns `true` if there has been an error
bool error() const { return get_error() != ParseResult::NoError; }
//! Parse a 32-bit integer in network byte order from the data stream
uint32_t u32();
//! Parse a 16-bit integer in network byte order from the data stream
uint16_t u16();
//! Parse an 8-bit integer in network byte order from the data stream
uint8_t u8();
//! Remove n bytes from the buffer
void remove_prefix(const size_t n);
};
struct NetUnparser {
template <typename T>
static void _unparse_int(std::string &s, T val);
//! Write a 32-bit integer into the data stream in network byte order
static void u32(std::string &s, const uint32_t val);
//! Write a 16-bit integer into the data stream in network byte order
static void u16(std::string &s, const uint16_t val);
//! Write an 8-bit integer into the data stream in network byte order
static void u8(std::string &s, const uint8_t val);
};
#endif // SPONGE_LIBSPONGE_PARSER_HH
#include "socket.hh"
#include "util.hh"
#include <cstddef>
#include <stdexcept>
#include <unistd.h>
using namespace std;
// default constructor for socket of (subclassed) domain and type
//! \param[in] domain is as described in [socket(7)](\ref man7::socket), probably `AF_INET` or `AF_UNIX`
//! \param[in] type is as described in [socket(7)](\ref man7::socket)
Socket::Socket(const int domain, const int type) : FileDescriptor(SystemCall("socket", socket(domain, type, 0))) {}
// construct from file descriptor
//! \param[in] fd is the FileDescriptor from which to construct
//! \param[in] domain is `fd`'s domain; throws std::runtime_error if wrong value is supplied
//! \param[in] type is `fd`'s type; throws std::runtime_error if wrong value is supplied
Socket::Socket(FileDescriptor &&fd, const int domain, const int type) : FileDescriptor(move(fd)) {
int actual_value;
socklen_t len;
// verify domain
len = sizeof(actual_value);
SystemCall("getsockopt", getsockopt(fd_num(), SOL_SOCKET, SO_DOMAIN, &actual_value, &len));
if ((len != sizeof(actual_value)) or (actual_value != domain)) {
throw runtime_error("socket domain mismatch");
}
// verify type
len = sizeof(actual_value);
SystemCall("getsockopt", getsockopt(fd_num(), SOL_SOCKET, SO_TYPE, &actual_value, &len));
if ((len != sizeof(actual_value)) or (actual_value != type)) {
throw runtime_error("socket type mismatch");
}
}
// get the local or peer address the socket is connected to
//! \param[in] name_of_function is the function to call (string passed to SystemCall())
//! \param[in] function is a pointer to the function
//! \returns the requested Address
Address Socket::get_address(const string &name_of_function,
const function<int(int, sockaddr *, socklen_t *)> &function) const {
Address::Raw address;
socklen_t size = sizeof(address);
SystemCall(name_of_function, function(fd_num(), address, &size));
return {address, size};
}
//! \returns the local Address of the socket
Address Socket::local_address() const { return get_address("getsockname", getsockname); }
//! \returns the socket's peer's Address
Address Socket::peer_address() const { return get_address("getpeername", getpeername); }
// bind socket to a specified local address (usually to listen/accept)
//! \param[in] address is a local Address to bind
void Socket::bind(const Address &address) { SystemCall("bind", ::bind(fd_num(), address, address.size())); }
// connect socket to a specified peer address
//! \param[in] address is the peer's Address
void Socket::connect(const Address &address) { SystemCall("connect", ::connect(fd_num(), address, address.size())); }
// shut down a socket in the specified way
//! \param[in] how can be `SHDN_RD`, `SHDN_WR`, or `SHDN_RDWR`; see [shutdown(2)](\ref man2::shutdown)
void Socket::shutdown(const int how) {
SystemCall("shutdown", ::shutdown(fd_num(), how));
switch (how) {
case SHUT_RD:
register_read();
break;
case SHUT_WR:
register_write();
break;
case SHUT_RDWR:
register_read();
register_write();
break;
default:
throw runtime_error("Socket::shutdown() called with invalid `how`");
}
}
//! \note If `mtu` is too small to hold the received datagram, this method throws a std::runtime_error
void UDPSocket::recv(received_datagram &datagram, const size_t mtu) {
// receive source address and payload
Address::Raw datagram_source_address;
datagram.payload.resize(mtu);
socklen_t fromlen = sizeof(datagram_source_address);
const ssize_t recv_len = SystemCall(
"recvfrom",
::recvfrom(
fd_num(), datagram.payload.data(), datagram.payload.size(), MSG_TRUNC, datagram_source_address, &fromlen));
if (recv_len > ssize_t(mtu)) {
throw runtime_error("recvfrom (oversized datagram)");
}
register_read();
datagram.source_address = {datagram_source_address, fromlen};
datagram.payload.resize(recv_len);
}
UDPSocket::received_datagram UDPSocket::recv(const size_t mtu) {
received_datagram ret{{nullptr, 0}, ""};
recv(ret, mtu);
return ret;
}
void sendmsg_helper(const int fd_num,
const sockaddr *destination_address,
const socklen_t destination_address_len,
const BufferViewList &payload) {
auto iovecs = payload.as_iovecs();
msghdr message{};
message.msg_name = const_cast<sockaddr *>(destination_address);
message.msg_namelen = destination_address_len;
message.msg_iov = iovecs.data();
message.msg_iovlen = iovecs.size();
const ssize_t bytes_sent = SystemCall("sendmsg", ::sendmsg(fd_num, &message, 0));
if (size_t(bytes_sent) != payload.size()) {
throw runtime_error("datagram payload too big for sendmsg()");
}
}
void UDPSocket::sendto(const Address &destination, const BufferViewList &payload) {
sendmsg_helper(fd_num(), destination, destination.size(), payload);
register_write();
}
void UDPSocket::send(const BufferViewList &payload) {
sendmsg_helper(fd_num(), nullptr, 0, payload);
register_write();
}
// mark the socket as listening for incoming connections
//! \param[in] backlog is the number of waiting connections to queue (see [listen(2)](\ref man2::listen))
void TCPSocket::listen(const int backlog) { SystemCall("listen", ::listen(fd_num(), backlog)); }
// accept a new incoming connection
//! \returns a new TCPSocket connected to the peer.
//! \note This function blocks until a new connection is available
TCPSocket TCPSocket::accept() {
register_read();
return TCPSocket(FileDescriptor(SystemCall("accept", ::accept(fd_num(), nullptr, nullptr))));
}
// set socket option
//! \param[in] level The protocol level at which the argument resides
//! \param[in] option A single option to set
//! \param[in] option_value The value to set
//! \details See [setsockopt(2)](\ref man2::setsockopt) for details.
template <typename option_type>
void Socket::setsockopt(const int level, const int option, const option_type &option_value) {
SystemCall("setsockopt", ::setsockopt(fd_num(), level, option, &option_value, sizeof(option_value)));
}
// allow local address to be reused sooner, at the cost of some robustness
//! \note Using `SO_REUSEADDR` may reduce the robustness of your application
void Socket::set_reuseaddr() { setsockopt(SOL_SOCKET, SO_REUSEADDR, int(true)); }
#ifndef SPONGE_LIBSPONGE_SOCKET_HH
#define SPONGE_LIBSPONGE_SOCKET_HH
#include "address.hh"
#include "file_descriptor.hh"
#include <cstdint>
#include <functional>
#include <string>
#include <sys/socket.h>
//! \brief Base class for network sockets (TCP, UDP, etc.)
//! \details Socket is generally used via a subclass. See TCPSocket and UDPSocket for usage examples.
class Socket : public FileDescriptor {
private:
//! Get the local or peer address the socket is connected to
Address get_address(const std::string &name_of_function,
const std::function<int(int, sockaddr *, socklen_t *)> &function) const;
protected:
//! Construct via [socket(2)](\ref man2::socket)
Socket(const int domain, const int type);
//! Construct from a file descriptor.
Socket(FileDescriptor &&fd, const int domain, const int type);
//! Wrapper around [setsockopt(2)](\ref man2::setsockopt)
template <typename option_type>
void setsockopt(const int level, const int option, const option_type &option_value);
public:
//! Bind a socket to a specified address with [bind(2)](\ref man2::bind), usually for listen/accept
void bind(const Address &address);
//! Connect a socket to a specified peer address with [connect(2)](\ref man2::connect)
void connect(const Address &address);
//! Shut down a socket via [shutdown(2)](\ref man2::shutdown)
void shutdown(const int how);
//! Get local address of socket with [getsockname(2)](\ref man2::getsockname)
Address local_address() const;
//! Get peer address of socket with [getpeername(2)](\ref man2::getpeername)
Address peer_address() const;
//! Allow local address to be reused sooner via [SO_REUSEADDR](\ref man7::socket)
void set_reuseaddr();
};
//! A wrapper around [UDP sockets](\ref man7::udp)
class UDPSocket : public Socket {
protected:
//! \brief Construct from FileDescriptor (used by TCPOverUDPSocketAdapter)
//! \param[in] fd is the FileDescriptor from which to construct
explicit UDPSocket(FileDescriptor &&fd) : Socket(std::move(fd), AF_INET, SOCK_DGRAM) {}
public:
//! Default: construct an unbound, unconnected UDP socket
UDPSocket() : Socket(AF_INET, SOCK_DGRAM) {}
//! Returned by UDPSocket::recv; carries received data and information about the sender
struct received_datagram {
Address source_address; //!< Address from which this datagram was received
std::string payload; //!< UDP datagram payload
};
//! Receive a datagram and the Address of its sender
received_datagram recv(const size_t mtu = 65536);
//! Receive a datagram and the Address of its sender (caller can allocate storage)
void recv(received_datagram &datagram, const size_t mtu = 65536);
//! Send a datagram to specified Address
void sendto(const Address &destination, const BufferViewList &payload);
//! Send datagram to the socket's connected address (must call connect() first)
void send(const BufferViewList &payload);
};
//! \class UDPSocket
//! Functions in this class are essentially wrappers over their POSIX eponyms.
//!
//! Example:
//!
//! \include socket_example_1.cc
//! A wrapper around [TCP sockets](\ref man7::tcp)
class TCPSocket : public Socket {
private:
//! \brief Construct from FileDescriptor (used by accept())
//! \param[in] fd is the FileDescriptor from which to construct
explicit TCPSocket(FileDescriptor &&fd) : Socket(std::move(fd), AF_INET, SOCK_STREAM) {}
public:
//! Default: construct an unbound, unconnected TCP socket
TCPSocket() : Socket(AF_INET, SOCK_STREAM) {}
//! Mark a socket as listening for incoming connections
void listen(const int backlog = 16);
//! Accept a new incoming connection
TCPSocket accept();
};
//! \class TCPSocket
//! Functions in this class are essentially wrappers over their POSIX eponyms.
//!
//! Example:
//!
//! \include socket_example_2.cc
//! A wrapper around [Unix-domain stream sockets](\ref man7::unix)
class LocalStreamSocket : public Socket {
public:
//! Construct from a file descriptor
explicit LocalStreamSocket(FileDescriptor &&fd) : Socket(std::move(fd), AF_UNIX, SOCK_STREAM) {}
};
//! \class LocalStreamSocket
//! Example:
//!
//! \include socket_example_3.cc
#endif // SPONGE_LIBSPONGE_SOCKET_HH
#include "tun.hh"
#include "util.hh"
#include <cstring>
#include <fcntl.h>
#include <linux/if.h>
#include <linux/if_tun.h>
#include <sys/ioctl.h>
static constexpr const char *CLONEDEV = "/dev/net/tun";
using namespace std;
//! \param[in] devname is the name of the TUN device, specified at its creation.
//!
//! To create a TUN device, you should already have run
//!
//! ip tuntap add mode tun user `username` name `devname`
//!
//! as root before calling this function.
TunFD::TunFD(const string &devname) : FileDescriptor(SystemCall("open", open(CLONEDEV, O_RDWR))) {
struct ifreq tun_req {};
tun_req.ifr_flags = IFF_TUN | IFF_NO_PI; // tun device with no packetinfo
// copy devname to ifr_name, making sure to null terminate
strncpy(static_cast<char *>(tun_req.ifr_name), devname.data(), IFNAMSIZ - 1);
tun_req.ifr_name[IFNAMSIZ - 1] = '\0';
SystemCall("ioctl", ioctl(fd_num(), TUNSETIFF, static_cast<void *>(&tun_req)));
}
#ifndef SPONGE_LIBSPONGE_TUN_HH
#define SPONGE_LIBSPONGE_TUN_HH
#include "file_descriptor.hh"
#include <string>
//! A FileDescriptor to a [Linux TUN](https://www.kernel.org/doc/Documentation/networking/tuntap.txt) device
class TunFD : public FileDescriptor {
public:
//! Open an existing persistent [TUN device](https://www.kernel.org/doc/Documentation/networking/tuntap.txt).
explicit TunFD(const std::string &devname);
};
#endif // SPONGE_LIBSPONGE_TUN_HH
#include "util.hh"
#include <array>
#include <cctype>
#include <chrono>
#include <iomanip>
#include <iostream>
#include <sstream>
#include <sys/socket.h>
using namespace std;
//! \returns the number of milliseconds since the program started
uint64_t timestamp_ms() {
using time_point = std::chrono::steady_clock::time_point;
static const time_point program_start = std::chrono::steady_clock::now();
const time_point now = std::chrono::steady_clock::now();
return std::chrono::duration_cast<std::chrono::milliseconds>(now - program_start).count();
}
//! \param[in] attempt is the name of the syscall to try (for error reporting)
//! \param[in] return_value is the return value of the syscall
//! \param[in] errno_mask is any errno value that is acceptable, e.g., `EAGAIN` when reading a non-blocking fd
//! \details This function works for any syscall that returns less than 0 on error and sets errno:
//!
//! For example, to wrap a call to [open(2)](\ref man2::open), you might say:
//!
//! ~~~{.cc}
//! const int foo_fd = SystemCall("open", ::open("/tmp/foo", O_RDWR));
//! ~~~
//!
//! If you don't have permission to open the file, SystemCall will throw a std::runtime_error.
//! If you don't want to throw in that case, you can pass `EACCESS` in `errno_mask`:
//!
//! ~~~{.cc}
//! // open a file, or print an error if permission was denied
//! const int foo_fd = SystemCall("open", ::open("/tmp/foo", O_RDWR), EACCESS);
//! if (foo_fd < 0) {
//! std::cerr << "Access to /tmp/foo was denied." << std::endl;
//! }
//! ~~~
int SystemCall(const char *attempt, const int return_value, const int errno_mask) {
if (return_value >= 0 || errno == errno_mask) {
return return_value;
}
throw unix_error(attempt);
}
//! \param[in] attempt is the name of the syscall to try (for error reporting)
//! \param[in] return_value is the return value of the syscall
//! \param[in] errno_mask is any errno value that is acceptable, e.g., `EAGAIN` when reading a non-blocking fd
//! \details see the other SystemCall() documentation for more details
int SystemCall(const string &attempt, const int return_value, const int errno_mask) {
return SystemCall(attempt.c_str(), return_value, errno_mask);
}
//! \details A properly seeded mt19937 generator takes a lot of entropy!
//!
//! This code borrows from the following:
//!
//! - https://kristerw.blogspot.com/2017/05/seeding-stdmt19937-random-number-engine.html
//! - http://www.pcg-random.org/posts/cpps-random_device.html
mt19937 get_random_generator() {
auto rd = random_device();
array<uint32_t, mt19937::state_size> seed_data{};
generate(seed_data.begin(), seed_data.end(), [&] { return rd(); });
seed_seq seed(seed_data.begin(), seed_data.end());
return mt19937(seed);
}
//! \note This class returns the checksum in host byte order.
//! See https://commandcenter.blogspot.com/2012/04/byte-order-fallacy.html for rationale
//! \details This class can be used to either check or compute an Internet checksum
//! (e.g., for an IP datagram header or a TCP segment).
//!
//! The Internet checksum is defined such that evaluating inet_cksum() on a TCP segment (IP datagram, etc)
//! containing a correct checksum header will return zero. In other words, if you read a correct TCP segment
//! off the wire and pass it untouched to inet_cksum(), the return value will be 0.
//!
//! Meanwhile, to compute the checksum for an outgoing TCP segment (IP datagram, etc.), you must first set
//! the checksum header to zero, then call inet_cksum(), and finally set the checksum header to the return
//! value.
//!
//! For more information, see the [Wikipedia page](https://en.wikipedia.org/wiki/IPv4_header_checksum)
//! on the Internet checksum, and consult the [IP](\ref rfc::rfc791) and [TCP](\ref rfc::rfc793) RFCs.
InternetChecksum::InternetChecksum(const uint32_t initial_sum) : _sum(initial_sum) {}
void InternetChecksum::add(std::string_view data) {
for (size_t i = 0; i < data.size(); i++) {
uint16_t val = uint8_t(data[i]);
if (not _parity) {
val <<= 8;
}
_sum += val;
_parity = !_parity;
}
}
uint16_t InternetChecksum::value() const {
uint32_t ret = _sum;
while (ret > 0xffff) {
ret = (ret >> 16) + (ret & 0xffff);
}
return ~ret;
}
//! \param[in] data is a pointer to the bytes to show
//! \param[in] len is the number of bytes to show
//! \param[in] indent is the number of spaces to indent
void hexdump(const uint8_t *data, const size_t len, const size_t indent) {
const auto flags(cout.flags());
const string indent_string(indent, ' ');
int printed = 0;
stringstream pchars(" ");
cout << hex << setfill('0');
for (unsigned i = 0; i < len; ++i) {
if ((printed & 0xf) == 0) {
if (printed != 0) {
cout << " " << pchars.str() << "\n";
pchars.str(" ");
}
cout << indent_string << setw(8) << printed << ": ";
} else if ((printed & 1) == 0) {
cout << ' ';
}
cout << setw(2) << +data[i];
pchars << (static_cast<bool>(isprint(data[i])) ? static_cast<char>(data[i]) : '.');
printed += 1;
}
const int print_rem = (16 - (printed & 0xf)) % 16; // extra spacing before final chars
cout << string(2 * print_rem + print_rem / 2 + 4, ' ') << pchars.str();
cout << '\n' << endl;
cout.flags(flags);
}
//! \param[in] data is a pointer to the bytes to show
//! \param[in] len is the number of bytes to show
//! \param[in] indent is the number of spaces to indent
void hexdump(const char *data, const size_t len, const size_t indent) {
hexdump(reinterpret_cast<const uint8_t *>(data), len, indent);
}
#ifndef SPONGE_LIBSPONGE_UTIL_HH
#define SPONGE_LIBSPONGE_UTIL_HH
#include <algorithm>
#include <cerrno>
#include <cstddef>
#include <cstdint>
#include <iterator>
#include <ostream>
#include <random>
#include <string>
#include <system_error>
#include <vector>
//! std::system_error plus the name of what was being attempted
class tagged_error : public std::system_error {
private:
std::string _attempt_and_error; //!< What was attempted, and what happened
public:
//! \brief Construct from a category, an attempt, and an error code
//! \param[in] category is the category of error
//! \param[in] attempt is what was supposed to happen
//! \param[in] error_code is the resulting error
tagged_error(const std::error_category &category, const std::string &attempt, const int error_code)
: system_error(error_code, category), _attempt_and_error(attempt + ": " + std::system_error::what()) {}
//! Returns a C string describing the error
const char *what() const noexcept override { return _attempt_and_error.c_str(); }
};
//! a tagged_error for syscalls
class unix_error : public tagged_error {
public:
//! brief Construct from a syscall name and the resulting errno
//! \param[in] attempt is the name of the syscall attempted
//! \param[in] error is the [errno(3)](\ref man3::errno) that resulted
explicit unix_error(const std::string &attempt, const int error = errno)
: tagged_error(std::system_category(), attempt, error) {}
};
//! Error-checking wrapper for most syscalls
int SystemCall(const char *attempt, const int return_value, const int errno_mask = 0);
//! Version of SystemCall that takes a C++ std::string
int SystemCall(const std::string &attempt, const int return_value, const int errno_mask = 0);
//! Seed a fast random generator
std::mt19937 get_random_generator();
//! Get the time in milliseconds since the program began.
uint64_t timestamp_ms();
//! The internet checksum algorithm
class InternetChecksum {
private:
uint32_t _sum;
bool _parity{};
public:
InternetChecksum(const uint32_t initial_sum = 0);
void add(std::string_view data);
uint16_t value() const;
};
//! Hexdump the contents of a packet (or any other sequence of bytes)
void hexdump(const char *data, const size_t len, const size_t indent = 0);
//! Hexdump the contents of a packet (or any other sequence of bytes)
void hexdump(const uint8_t *data, const size_t len, const size_t indent = 0);
#endif // SPONGE_LIBSPONGE_UTIL_HH
#include "wrapping_integers.hh"
// Dummy implementation of a 32-bit wrapping integer
// For Lab 2, please replace with a real implementation that passes the
// automated checks run by `make check_lab2`.
template <typename... Targs>
void DUMMY_CODE(Targs &&... /* unused */) {}
using namespace std;
//! Transform an "absolute" 64-bit sequence number (zero-indexed) into a WrappingInt32
//! \param n The input absolute 64-bit sequence number
//! \param isn The initial sequence number
WrappingInt32 wrap(uint64_t n, WrappingInt32 isn) {
DUMMY_CODE(n, isn);
return WrappingInt32{0};
}
//! Transform a WrappingInt32 into an "absolute" 64-bit sequence number (zero-indexed)
//! \param n The relative sequence number
//! \param isn The initial sequence number
//! \param checkpoint A recent absolute 64-bit sequence number
//! \returns the 64-bit sequence number that wraps to `n` and is closest to `checkpoint`
//!
//! \note Each of the two streams of the TCP connection has its own ISN. One stream
//! runs from the local TCPSender to the remote TCPReceiver and has one ISN,
//! and the other stream runs from the remote TCPSender to the local TCPReceiver and
//! has a different ISN.
uint64_t unwrap(WrappingInt32 n, WrappingInt32 isn, uint64_t checkpoint) {
DUMMY_CODE(n, isn, checkpoint);
return {};
}
#ifndef SPONGE_LIBSPONGE_WRAPPING_INTEGERS_HH
#define SPONGE_LIBSPONGE_WRAPPING_INTEGERS_HH
#include <cstdint>
#include <ostream>
//! \brief A 32-bit integer, expressed relative to an arbitrary initial sequence number (ISN)
//! \note This is used to express TCP sequence numbers (seqno) and acknowledgment numbers (ackno)
class WrappingInt32 {
private:
uint32_t _raw_value; //!< The raw 32-bit stored integer
public:
//! Construct from a raw 32-bit unsigned integer
explicit WrappingInt32(uint32_t raw_value) : _raw_value(raw_value) {}
uint32_t raw_value() const { return _raw_value; } //!< Access raw stored value
};
//! Transform a 64-bit absolute sequence number (zero-indexed) into a 32-bit relative sequence number
//! \param n the absolute sequence number
//! \param isn the initial sequence number
//! \returns the relative sequence number
WrappingInt32 wrap(uint64_t n, WrappingInt32 isn);
//! Transform a 32-bit relative sequence number into a 64-bit absolute sequence number (zero-indexed)
//! \param n The relative sequence number
//! \param isn The initial sequence number
//! \param checkpoint A recent absolute sequence number
//! \returns the absolute sequence number that wraps to `n` and is closest to `checkpoint`
//!
//! \note Each of the two streams of the TCP connection has its own ISN. One stream
//! runs from the local TCPSender to the remote TCPReceiver and has one ISN,
//! and the other stream runs from the remote TCPSender to the local TCPReceiver and
//! has a different ISN.
uint64_t unwrap(WrappingInt32 n, WrappingInt32 isn, uint64_t checkpoint);
//! \name Helper functions
//!@{
//! \brief The offset of `a` relative to `b`
//! \param b the starting point
//! \param a the ending point
//! \returns the number of increments needed to get from `b` to `a`,
//! negative if the number of decrements needed is less than or equal to
//! the number of increments
inline int32_t operator-(WrappingInt32 a, WrappingInt32 b) { return a.raw_value() - b.raw_value(); }
//! \brief Whether the two integers are equal.
inline bool operator==(WrappingInt32 a, WrappingInt32 b) { return a.raw_value() == b.raw_value(); }
//! \brief Whether the two integers are not equal.
inline bool operator!=(WrappingInt32 a, WrappingInt32 b) { return !(a == b); }
//! \brief Serializes the wrapping integer, `a`.
inline std::ostream &operator<<(std::ostream &os, WrappingInt32 a) { return os << a.raw_value(); }
//! \brief The point `b` steps past `a`.
inline WrappingInt32 operator+(WrappingInt32 a, uint32_t b) { return WrappingInt32{a.raw_value() + b}; }
//! \brief The point `b` steps before `a`.
inline WrappingInt32 operator-(WrappingInt32 a, uint32_t b) { return a + -b; }
//!@}
#endif // SPONGE_LIBSPONGE_WRAPPING_INTEGERS_HH
add_library (spongechecks STATIC send_equivalence_checker.cc tcp_fsm_test_harness.cc byte_stream_test_harness.cc)
macro (add_test_exec exec_name)
add_executable ("${exec_name}" "${exec_name}.cc")
target_link_libraries ("${exec_name}" spongechecks ${ARGN})
target_link_libraries ("${exec_name}" sponge ${ARGN})
endmacro (add_test_exec)
add_test_exec (tcp_parser ${LIBPCAP})
add_test_exec (ipv4_parser ${LIBPCAP})
add_test_exec (fsm_active_close)
add_test_exec (fsm_passive_close)
add_test_exec (fsm_ack_rst)
add_test_exec (fsm_ack_rst_relaxed)
add_test_exec (fsm_ack_rst_win)
add_test_exec (fsm_ack_rst_win_relaxed)
add_test_exec (fsm_stream_reassembler_cap)
add_test_exec (fsm_stream_reassembler_single)
add_test_exec (fsm_stream_reassembler_seq)
add_test_exec (fsm_stream_reassembler_dup)
add_test_exec (fsm_stream_reassembler_holes)
add_test_exec (fsm_stream_reassembler_many)
add_test_exec (fsm_stream_reassembler_overlapping)
add_test_exec (fsm_stream_reassembler_win)
add_test_exec (fsm_connect)
add_test_exec (fsm_connect_relaxed)
add_test_exec (fsm_listen)
add_test_exec (fsm_listen_relaxed)
add_test_exec (fsm_reorder)
add_test_exec (fsm_loopback)
add_test_exec (fsm_loopback_win)
add_test_exec (fsm_retx)
add_test_exec (fsm_retx_relaxed)
add_test_exec (fsm_retx_win)
add_test_exec (fsm_winsize)
add_test_exec (wrapping_integers_cmp)
add_test_exec (wrapping_integers_unwrap)
add_test_exec (wrapping_integers_wrap)
add_test_exec (byte_stream_construction)
add_test_exec (byte_stream_one_write)
add_test_exec (byte_stream_two_writes)
add_test_exec (byte_stream_capacity)
add_test_exec (byte_stream_many_writes)
add_test_exec (recv_connect)
add_test_exec (recv_transmit)
add_test_exec (recv_window)
add_test_exec (recv_reorder)
add_test_exec (recv_close)
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)
#include "byte_stream.hh"
#include "byte_stream_test_harness.hh"
#include <exception>
#include <iostream>
using namespace std;
int main() {
try {
{
ByteStreamTestHarness test{"overwrite", 2};
test.execute(Write{"cat"}.with_bytes_written(2));
test.execute(InputEnded{false});
test.execute(BufferEmpty{false});
test.execute(Eof{false});
test.execute(BytesRead{0});
test.execute(BytesWritten{2});
test.execute(RemainingCapacity{0});
test.execute(BufferSize{2});
test.execute(Peek{"ca"});
test.execute(Write{"t"}.with_bytes_written(0));
test.execute(InputEnded{false});
test.execute(BufferEmpty{false});
test.execute(Eof{false});
test.execute(BytesRead{0});
test.execute(BytesWritten{2});
test.execute(RemainingCapacity{0});
test.execute(BufferSize{2});
test.execute(Peek{"ca"});
}
{
ByteStreamTestHarness test{"overwrite-clear-overwrite", 2};
test.execute(Write{"cat"}.with_bytes_written(2));
test.execute(Pop{2});
test.execute(Write{"tac"}.with_bytes_written(2));
test.execute(InputEnded{false});
test.execute(BufferEmpty{false});
test.execute(Eof{false});
test.execute(BytesRead{2});
test.execute(BytesWritten{4});
test.execute(RemainingCapacity{0});
test.execute(BufferSize{2});
test.execute(Peek{"ta"});
}
{
ByteStreamTestHarness test{"overwrite-pop-overwrite", 2};
test.execute(Write{"cat"}.with_bytes_written(2));
test.execute(Pop{1});
test.execute(Write{"tac"}.with_bytes_written(1));
test.execute(InputEnded{false});
test.execute(BufferEmpty{false});
test.execute(Eof{false});
test.execute(BytesRead{1});
test.execute(BytesWritten{3});
test.execute(RemainingCapacity{0});
test.execute(BufferSize{2});
test.execute(Peek{"at"});
}
} catch (const exception &e) {
cerr << "Exception: " << e.what() << endl;
return EXIT_FAILURE;
}
return EXIT_SUCCESS;
}
#include "byte_stream.hh"
#include "byte_stream_test_harness.hh"
#include <exception>
#include <iostream>
using namespace std;
int main() {
try {
{
ByteStreamTestHarness test{"construction", 15};
test.execute(InputEnded{false});
test.execute(BufferEmpty{true});
test.execute(Eof{false});
test.execute(BytesRead{0});
test.execute(BytesWritten{0});
test.execute(RemainingCapacity{15});
test.execute(BufferSize{0});
}
{
ByteStreamTestHarness test{"construction-end", 15};
test.execute(EndInput{});
test.execute(InputEnded{true});
test.execute(BufferEmpty{true});
test.execute(Eof{true});
test.execute(BytesRead{0});
test.execute(BytesWritten{0});
test.execute(RemainingCapacity{15});
test.execute(BufferSize{0});
}
} catch (const exception &e) {
cerr << "Exception: " << e.what() << endl;
return EXIT_FAILURE;
}
return EXIT_SUCCESS;
}
#include "byte_stream.hh"
#include "byte_stream_test_harness.hh"
#include "util.hh"
#include <exception>
#include <iostream>
using namespace std;
int main() {
try {
auto rd = get_random_generator();
const size_t NREPS = 1000;
const size_t MIN_WRITE = 10;
const size_t MAX_WRITE = 200;
const size_t CAPACITY = MAX_WRITE * NREPS;
{
ByteStreamTestHarness test{"many writes", CAPACITY};
size_t acc = 0;
for (size_t i = 0; i < NREPS; ++i) {
const size_t size = MIN_WRITE + (rd() % (MAX_WRITE - MIN_WRITE));
string d(size, 0);
generate(d.begin(), d.end(), [&] { return 'a' + (rd() % 26); });
test.execute(Write{d}.with_bytes_written(size));
acc += size;
test.execute(InputEnded{false});
test.execute(BufferEmpty{false});
test.execute(Eof{false});
test.execute(BytesRead{0});
test.execute(BytesWritten{acc});
test.execute(RemainingCapacity{CAPACITY - acc});
test.execute(BufferSize{acc});
}
}
} catch (const exception &e) {
cerr << "Exception: " << e.what() << endl;
return EXIT_FAILURE;
}
return EXIT_SUCCESS;
}
#include "byte_stream.hh"
#include "byte_stream_test_harness.hh"
#include <exception>
#include <iostream>
using namespace std;
int main() {
try {
{
ByteStreamTestHarness test{"write-end-pop", 15};
test.execute(Write{"cat"});
test.execute(InputEnded{false});
test.execute(BufferEmpty{false});
test.execute(Eof{false});
test.execute(BytesRead{0});
test.execute(BytesWritten{3});
test.execute(RemainingCapacity{12});
test.execute(BufferSize{3});
test.execute(Peek{"cat"});
test.execute(EndInput{});
test.execute(InputEnded{true});
test.execute(BufferEmpty{false});
test.execute(Eof{false});
test.execute(BytesRead{0});
test.execute(BytesWritten{3});
test.execute(RemainingCapacity{12});
test.execute(BufferSize{3});
test.execute(Peek{"cat"});
test.execute(Pop{3});
test.execute(InputEnded{true});
test.execute(BufferEmpty{true});
test.execute(Eof{true});
test.execute(BytesRead{3});
test.execute(BytesWritten{3});
test.execute(RemainingCapacity{15});
test.execute(BufferSize{0});
}
{
ByteStreamTestHarness test{"write-pop-end", 15};
test.execute(Write{"cat"});
test.execute(InputEnded{false});
test.execute(BufferEmpty{false});
test.execute(Eof{false});
test.execute(BytesRead{0});
test.execute(BytesWritten{3});
test.execute(RemainingCapacity{12});
test.execute(BufferSize{3});
test.execute(Peek{"cat"});
test.execute(Pop{3});
test.execute(InputEnded{false});
test.execute(BufferEmpty{true});
test.execute(Eof{false});
test.execute(BytesRead{3});
test.execute(BytesWritten{3});
test.execute(RemainingCapacity{15});
test.execute(BufferSize{0});
test.execute(EndInput{});
test.execute(InputEnded{true});
test.execute(BufferEmpty{true});
test.execute(Eof{true});
test.execute(BytesRead{3});
test.execute(BytesWritten{3});
test.execute(RemainingCapacity{15});
test.execute(BufferSize{0});
}
{
ByteStreamTestHarness test{"write-pop2-end", 15};
test.execute(Write{"cat"});
test.execute(InputEnded{false});
test.execute(BufferEmpty{false});
test.execute(Eof{false});
test.execute(BytesRead{0});
test.execute(BytesWritten{3});
test.execute(RemainingCapacity{12});
test.execute(BufferSize{3});
test.execute(Peek{"cat"});
test.execute(Pop{1});
test.execute(InputEnded{false});
test.execute(BufferEmpty{false});
test.execute(Eof{false});
test.execute(BytesRead{1});
test.execute(BytesWritten{3});
test.execute(RemainingCapacity{13});
test.execute(BufferSize{2});
test.execute(Peek{"at"});
test.execute(Pop{2});
test.execute(InputEnded{false});
test.execute(BufferEmpty{true});
test.execute(Eof{false});
test.execute(BytesRead{3});
test.execute(BytesWritten{3});
test.execute(RemainingCapacity{15});
test.execute(BufferSize{0});
test.execute(EndInput{});
test.execute(InputEnded{true});
test.execute(BufferEmpty{true});
test.execute(Eof{true});
test.execute(BytesRead{3});
test.execute(BytesWritten{3});
test.execute(RemainingCapacity{15});
test.execute(BufferSize{0});
}
} catch (const exception &e) {
cerr << "Exception: " << e.what() << endl;
return EXIT_FAILURE;
}
return EXIT_SUCCESS;
}
#include "byte_stream_test_harness.hh"
#include <memory>
#include <sstream>
using namespace std;
// ByteStreamTestStep
ByteStreamTestStep::operator std::string() const { return "ByteStreamTestStep"; }
void ByteStreamTestStep::execute(ByteStream &) const {}
ByteStreamTestStep::~ByteStreamTestStep() {}
// ByteStreamExpectationViolation
ByteStreamExpectationViolation::ByteStreamExpectationViolation(const std::string &msg) : std::runtime_error(msg) {}
template <typename T>
ByteStreamExpectationViolation ByteStreamExpectationViolation::property(const std::string &property_name,
const T &expected,
const T &actual) {
return ByteStreamExpectationViolation("The ByteStream should have had " + property_name + " equal to " +
to_string(expected) + " but instead it was " + to_string(actual));
}
// ByteStreamExpectation
ByteStreamExpectation::operator std::string() const { return "Expectation: " + description(); }
std::string ByteStreamExpectation::description() const { return "description missing"; }
void ByteStreamExpectation::execute(ByteStream &) const {}
ByteStreamExpectation::~ByteStreamExpectation() {}
// ByteStreamAction
ByteStreamAction::operator std::string() const { return " Action: " + description(); }
std::string ByteStreamAction::description() const { return "description missing"; }
void ByteStreamAction::execute(ByteStream &) const {}
ByteStreamAction::~ByteStreamAction() {}
ByteStreamTestHarness::ByteStreamTestHarness(const std::string &test_name, const size_t capacity)
: _test_name(test_name), _byte_stream(capacity) {
std::ostringstream ss;
ss << "Initialized with ("
<< "capacity=" << capacity << ")";
_steps_executed.emplace_back(ss.str());
}
void ByteStreamTestHarness::execute(const ByteStreamTestStep &step) {
try {
step.execute(_byte_stream);
_steps_executed.emplace_back(step);
} catch (const ByteStreamExpectationViolation &e) {
std::cerr << "Test Failure on expectation:\n\t" << std::string(step);
std::cerr << "\n\nFailure message:\n\t" << e.what();
std::cerr << "\n\nList of steps that executed successfully:";
for (const std::string &s : _steps_executed) {
std::cerr << "\n\t" << s;
}
std::cerr << std::endl << std::endl;
throw ByteStreamExpectationViolation("The test \"" + _test_name + "\" failed");
} catch (const exception &e) {
std::cerr << "Test Failure on expectation:\n\t" << std::string(step);
std::cerr << "\n\nException:\n\t" << e.what();
std::cerr << "\n\nList of steps that executed successfully:";
for (const std::string &s : _steps_executed) {
std::cerr << "\n\t" << s;
}
std::cerr << std::endl << std::endl;
throw ByteStreamExpectationViolation("The test \"" + _test_name +
"\" caused your implementation to throw an exception!");
}
}
// EndInput
std::string EndInput::description() const { return "end input"; }
void EndInput::execute(ByteStream &bs) const { bs.end_input(); }
// Write
Write::Write(const std::string &data) : _data(data) {}
Write &Write::with_bytes_written(const size_t bytes_written) {
_bytes_written = bytes_written;
return *this;
}
std::string Write::description() const { return "write \"" + _data + "\" to the stream"; }
void Write::execute(ByteStream &bs) const {
auto bytes_written = bs.write(_data);
if (_bytes_written and bytes_written != _bytes_written.value()) {
throw ByteStreamExpectationViolation::property("bytes_written", _bytes_written.value(), bytes_written);
}
}
// Pop
Pop::Pop(const size_t len) : _len(len) {}
std::string Pop::description() const { return "pop " + to_string(_len); }
void Pop::execute(ByteStream &bs) const { bs.pop_output(_len); }
// InputEnded
InputEnded::InputEnded(const bool input_ended) : _input_ended(input_ended) {}
std::string InputEnded::description() const { return "input_ended: " + to_string(_input_ended); }
void InputEnded::execute(ByteStream &bs) const {
auto input_ended = bs.input_ended();
if (input_ended != _input_ended) {
throw ByteStreamExpectationViolation::property("input_ended", _input_ended, input_ended);
}
}
// BufferEmpty
BufferEmpty::BufferEmpty(const bool buffer_empty) : _buffer_empty(buffer_empty) {}
std::string BufferEmpty::description() const { return "buffer_empty: " + to_string(_buffer_empty); }
void BufferEmpty::execute(ByteStream &bs) const {
auto buffer_empty = bs.buffer_empty();
if (buffer_empty != _buffer_empty) {
throw ByteStreamExpectationViolation::property("buffer_empty", _buffer_empty, buffer_empty);
}
}
// Eof
Eof::Eof(const bool eof) : _eof(eof) {}
std::string Eof::description() const { return "eof: " + to_string(_eof); }
void Eof::execute(ByteStream &bs) const {
auto eof = bs.eof();
if (eof != _eof) {
throw ByteStreamExpectationViolation::property("eof", _eof, eof);
}
}
// BufferSize
BufferSize::BufferSize(const size_t buffer_size) : _buffer_size(buffer_size) {}
std::string BufferSize::description() const { return "buffer_size: " + to_string(_buffer_size); }
void BufferSize::execute(ByteStream &bs) const {
auto buffer_size = bs.buffer_size();
if (buffer_size != _buffer_size) {
throw ByteStreamExpectationViolation::property("buffer_size", _buffer_size, buffer_size);
}
}
// RemainingCapacity
RemainingCapacity::RemainingCapacity(const size_t remaining_capacity) : _remaining_capacity(remaining_capacity) {}
std::string RemainingCapacity::description() const { return "remaining_capacity: " + to_string(_remaining_capacity); }
void RemainingCapacity::execute(ByteStream &bs) const {
auto remaining_capacity = bs.remaining_capacity();
if (remaining_capacity != _remaining_capacity) {
throw ByteStreamExpectationViolation::property("remaining_capacity", _remaining_capacity, remaining_capacity);
}
}
// BytesWritten
BytesWritten::BytesWritten(const size_t bytes_written) : _bytes_written(bytes_written) {}
std::string BytesWritten::description() const { return "bytes_written: " + to_string(_bytes_written); }
void BytesWritten::execute(ByteStream &bs) const {
auto bytes_written = bs.bytes_written();
if (bytes_written != _bytes_written) {
throw ByteStreamExpectationViolation::property("bytes_written", _bytes_written, bytes_written);
}
}
// BytesRead
BytesRead::BytesRead(const size_t bytes_read) : _bytes_read(bytes_read) {}
std::string BytesRead::description() const { return "bytes_read: " + to_string(_bytes_read); }
void BytesRead::execute(ByteStream &bs) const {
auto bytes_read = bs.bytes_read();
if (bytes_read != _bytes_read) {
throw ByteStreamExpectationViolation::property("bytes_read", _bytes_read, bytes_read);
}
}
// Peek
Peek::Peek(const std::string &output) : _output(output) {}
std::string Peek::description() const { return "\"" + _output + "\" at the front of the stream"; }
void Peek::execute(ByteStream &bs) const {
auto output = bs.peek_output(_output.size());
if (output != _output) {
throw ByteStreamExpectationViolation("Expected \"" + _output + "\" at the front of the stream, but found \"" +
output + "\"");
}
}
#ifndef SPONGE_BYTE_STREAM_HARNESS_HH
#define SPONGE_BYTE_STREAM_HARNESS_HH
#include "byte_stream.hh"
#include "util.hh"
#include <exception>
#include <initializer_list>
#include <iostream>
#include <memory>
#include <optional>
#include <string>
struct ByteStreamTestStep {
virtual operator std::string() const;
virtual void execute(ByteStream &) const;
virtual ~ByteStreamTestStep();
};
class ByteStreamExpectationViolation : public std::runtime_error {
public:
ByteStreamExpectationViolation(const std::string &msg);
template <typename T>
static ByteStreamExpectationViolation property(const std::string &property_name,
const T &expected,
const T &actual);
};
struct ByteStreamExpectation : public ByteStreamTestStep {
operator std::string() const override;
virtual std::string description() const;
virtual void execute(ByteStream &) const override;
virtual ~ByteStreamExpectation() override;
};
struct ByteStreamAction : public ByteStreamTestStep {
operator std::string() const override;
virtual std::string description() const;
virtual void execute(ByteStream &) const override;
virtual ~ByteStreamAction() override;
};
struct EndInput : public ByteStreamAction {
std::string description() const override;
void execute(ByteStream &) const override;
};
struct Write : public ByteStreamAction {
std::string _data;
std::optional<size_t> _bytes_written{};
Write(const std::string &data);
Write &with_bytes_written(const size_t bytes_written);
std::string description() const override;
void execute(ByteStream &) const override;
};
struct Pop : public ByteStreamAction {
size_t _len;
Pop(const size_t len);
std::string description() const override;
void execute(ByteStream &) const override;
};
struct InputEnded : public ByteStreamExpectation {
bool _input_ended;
InputEnded(const bool input_ended);
std::string description() const override;
void execute(ByteStream &) const override;
};
struct BufferEmpty : public ByteStreamExpectation {
bool _buffer_empty;
BufferEmpty(const bool buffer_empty);
std::string description() const override;
void execute(ByteStream &) const override;
};
struct Eof : public ByteStreamExpectation {
bool _eof;
Eof(const bool eof);
std::string description() const override;
void execute(ByteStream &) const override;
};
struct BufferSize : public ByteStreamExpectation {
size_t _buffer_size;
BufferSize(const size_t buffer_size);
std::string description() const override;
void execute(ByteStream &) const override;
};
struct BytesWritten : public ByteStreamExpectation {
size_t _bytes_written;
BytesWritten(const size_t bytes_written);
std::string description() const override;
void execute(ByteStream &) const override;
};
struct BytesRead : public ByteStreamExpectation {
size_t _bytes_read;
BytesRead(const size_t bytes_read);
std::string description() const override;
void execute(ByteStream &) const override;
};
struct RemainingCapacity : public ByteStreamExpectation {
size_t _remaining_capacity;
RemainingCapacity(const size_t remaining_capacity);
std::string description() const override;
void execute(ByteStream &) const override;
};
struct Peek : public ByteStreamExpectation {
std::string _output;
Peek(const std::string &output);
std::string description() const override;
void execute(ByteStream &) const override;
};
class ByteStreamTestHarness {
std::string _test_name;
ByteStream _byte_stream;
std::vector<std::string> _steps_executed{};
public:
ByteStreamTestHarness(const std::string &test_name, const size_t capacity);
void execute(const ByteStreamTestStep &step);
};
#endif // SPONGE_BYTE_STREAM_HARNESS_HH
#include "byte_stream.hh"
#include "byte_stream_test_harness.hh"
#include <exception>
#include <iostream>
using namespace std;
int main() {
try {
{
ByteStreamTestHarness test{"write-write-end-pop-pop", 15};
test.execute(Write{"cat"});
test.execute(InputEnded{false});
test.execute(BufferEmpty{false});
test.execute(Eof{false});
test.execute(BytesRead{0});
test.execute(BytesWritten{3});
test.execute(RemainingCapacity{12});
test.execute(BufferSize{3});
test.execute(Peek{"cat"});
test.execute(Write{"tac"});
test.execute(InputEnded{false});
test.execute(BufferEmpty{false});
test.execute(Eof{false});
test.execute(BytesRead{0});
test.execute(BytesWritten{6});
test.execute(RemainingCapacity{9});
test.execute(BufferSize{6});
test.execute(Peek{"cattac"});
test.execute(EndInput{});
test.execute(InputEnded{true});
test.execute(BufferEmpty{false});
test.execute(Eof{false});
test.execute(BytesRead{0});
test.execute(BytesWritten{6});
test.execute(RemainingCapacity{9});
test.execute(BufferSize{6});
test.execute(Peek{"cattac"});
test.execute(Pop{2});
test.execute(InputEnded{true});
test.execute(BufferEmpty{false});
test.execute(Eof{false});
test.execute(BytesRead{2});
test.execute(BytesWritten{6});
test.execute(RemainingCapacity{11});
test.execute(BufferSize{4});
test.execute(Peek{"ttac"});
test.execute(Pop{4});
test.execute(InputEnded{true});
test.execute(BufferEmpty{true});
test.execute(Eof{true});
test.execute(BytesRead{6});
test.execute(BytesWritten{6});
test.execute(RemainingCapacity{15});
test.execute(BufferSize{0});
}
{
ByteStreamTestHarness test{"write-pop-write-end-pop", 15};
test.execute(Write{"cat"});
test.execute(InputEnded{false});
test.execute(BufferEmpty{false});
test.execute(Eof{false});
test.execute(BytesRead{0});
test.execute(BytesWritten{3});
test.execute(RemainingCapacity{12});
test.execute(BufferSize{3});
test.execute(Peek{"cat"});
test.execute(Pop{2});
test.execute(InputEnded{false});
test.execute(BufferEmpty{false});
test.execute(Eof{false});
test.execute(BytesRead{2});
test.execute(BytesWritten{3});
test.execute(RemainingCapacity{14});
test.execute(BufferSize{1});
test.execute(Peek{"t"});
test.execute(Write{"tac"});
test.execute(InputEnded{false});
test.execute(BufferEmpty{false});
test.execute(Eof{false});
test.execute(BytesRead{2});
test.execute(BytesWritten{6});
test.execute(RemainingCapacity{11});
test.execute(BufferSize{4});
test.execute(Peek{"ttac"});
test.execute(EndInput{});
test.execute(InputEnded{true});
test.execute(BufferEmpty{false});
test.execute(Eof{false});
test.execute(BytesRead{2});
test.execute(BytesWritten{6});
test.execute(RemainingCapacity{11});
test.execute(BufferSize{4});
test.execute(Peek{"ttac"});
test.execute(Pop{4});
test.execute(InputEnded{true});
test.execute(BufferEmpty{true});
test.execute(Eof{true});
test.execute(BytesRead{6});
test.execute(BytesWritten{6});
test.execute(RemainingCapacity{15});
test.execute(BufferSize{0});
}
} catch (const exception &e) {
cerr << "Exception: " << e.what() << endl;
return EXIT_FAILURE;
}
return EXIT_SUCCESS;
}
#include "tcp_config.hh"
#include "tcp_expectation.hh"
#include "tcp_fsm_test_harness.hh"
#include "tcp_header.hh"
#include "tcp_segment.hh"
#include <cstdint>
#include <cstdlib>
#include <exception>
#include <iostream>
using namespace std;
using State = TCPTestHarness::State;
// in LISTEN, send ACK
static void ack_listen_test(const TCPConfig &cfg,
const WrappingInt32 seqno,
const WrappingInt32 ackno,
const int lineno) {
try {
TCPTestHarness test = TCPTestHarness::in_listen(cfg);
// any ACK should result in a RST
test.send_ack(seqno, ackno);
test.execute(ExpectState{State::RESET});
test.execute(ExpectOneSegment{}.with_rst(true), "test 3 failed: no RST after ACK in LISTEN");
} catch (const exception &e) {
throw runtime_error(string(e.what()) + " (ack_listen_test called from line " + to_string(lineno) + ")");
}
}
// in SYN_SENT, send ACK and maybe RST
static void ack_rst_syn_sent_test(const TCPConfig &cfg,
const WrappingInt32 base_seq,
const WrappingInt32 seqno,
const WrappingInt32 ackno,
const bool send_rst,
const int lineno) {
try {
TCPTestHarness test = TCPTestHarness::in_syn_sent(cfg, base_seq);
// unacceptable ACKs should elicit a RST
if (send_rst) {
test.send_rst(seqno, ackno);
} else {
test.send_ack(seqno, ackno);
}
if (send_rst) {
test.execute(ExpectNotInState{State::RESET});
test.execute(ExpectNoSegment{}, "test 5 failed: bad ACK w/ RST should have been ignored in SYN_SENT");
} else {
test.execute(ExpectState{State::RESET});
test.execute(ExpectOneSegment{}.with_rst(true).with_seqno(ackno),
"test 5 failed: problem with RST segment for bad ACK");
}
} catch (const exception &e) {
throw runtime_error(string(e.what()) + " (ack_rst_syn_sent_test called from line " + to_string(lineno) + ")");
}
}
int main() {
try {
TCPConfig cfg{};
const WrappingInt32 base_seq(1 << 31);
// test #1: in ESTABLISHED, send unacceptable segments and ACKs
{
cerr << "Test 1" << endl;
TCPTestHarness test_1 = TCPTestHarness::in_established(cfg, base_seq - 1, base_seq - 1);
// acceptable ack---no response
test_1.send_ack(base_seq, base_seq);
test_1.execute(ExpectNoSegment{}, "test 1 failed: ACK after acceptable ACK");
// ack in the past---no response
test_1.send_ack(base_seq, base_seq - 1);
test_1.execute(ExpectNoSegment{}, "test 1 failed: ACK after past ACK");
// ack in the future---should get ACK back
test_1.send_ack(base_seq, base_seq + 1);
test_1.execute(ExpectOneSegment{}.with_ack(true).with_ackno(base_seq), "test 1 failed: bad ACK");
// segment out of the window---should get an ACK
test_1.send_byte(base_seq - 1, base_seq, 1);
test_1.execute(ExpectUnassembledBytes{0}, "test 1 failed: seg queued on early seqno");
test_1.execute(ExpectOneSegment{}.with_ack(true).with_ackno(base_seq), "test 1 failed: bad ACK");
// segment out of the window---should get an ACK
test_1.send_byte(base_seq + cfg.recv_capacity, base_seq, 1);
test_1.execute(ExpectUnassembledBytes{0}, "test 1 failed: seg queued on late seqno");
test_1.execute(ExpectOneSegment{}.with_ack(true).with_ackno(base_seq),
"test 1 failed: bad ACK on late seqno");
// packet next byte in the window - ack should advance and data should be readable
test_1.send_byte(base_seq, base_seq, 1);
test_1.execute(ExpectData{}, "test 1 failed: pkt not processed on next seqno");
test_1.execute(ExpectOneSegment{}.with_ack(true).with_ackno(base_seq + 1), "test 1 failed: bad ACK");
// segment not in window with RST set --- should get nothing back
test_1.send_rst(base_seq);
test_1.send_rst(base_seq + cfg.recv_capacity + 1);
test_1.execute(ExpectNoSegment{}, "test 1 failed: got a response to an out-of-window RST");
test_1.send_rst(base_seq + 1);
test_1.execute(ExpectState{State::RESET});
}
// test #2: in LISTEN, send RSTs
{
cerr << "Test 2" << endl;
TCPTestHarness test_2 = TCPTestHarness::in_listen(cfg);
// all RSTs should be ignored in LISTEN
test_2.send_rst(base_seq);
test_2.send_rst(base_seq - 1);
test_2.send_rst(base_seq + cfg.recv_capacity);
test_2.execute(ExpectNoSegment{}, "test 2 failed: RST was not ignored in LISTEN");
}
// test 3: ACKs in LISTEN
cerr << "Test 3" << endl;
ack_listen_test(cfg, base_seq, base_seq, __LINE__);
ack_listen_test(cfg, base_seq - 1, base_seq, __LINE__);
ack_listen_test(cfg, base_seq, base_seq - 1, __LINE__);
ack_listen_test(cfg, base_seq - 1, base_seq, __LINE__);
ack_listen_test(cfg, base_seq - 1, base_seq - 1, __LINE__);
ack_listen_test(cfg, base_seq + cfg.recv_capacity, base_seq, __LINE__);
ack_listen_test(cfg, base_seq, base_seq + cfg.recv_capacity, __LINE__);
ack_listen_test(cfg, base_seq + cfg.recv_capacity, base_seq, __LINE__);
ack_listen_test(cfg, base_seq + cfg.recv_capacity, base_seq + cfg.recv_capacity, __LINE__);
// test 4: ACK and RST in SYN_SENT
{
cerr << "Test 4" << endl;
TCPTestHarness test_4 = TCPTestHarness::in_syn_sent(cfg, base_seq);
// data segments with acceptable ACKs should be ignored
test_4.send_byte(base_seq, base_seq + 1, 1);
test_4.execute(ExpectState{State::SYN_SENT});
test_4.execute(ExpectNoSegment{}, "test 4 failed: bytes were not ignored in SYN_SENT");
// RST segments without ACKs should be ignored
test_4.send_rst(base_seq);
test_4.send_rst(base_seq + 1);
test_4.send_rst(base_seq - 1);
test_4.execute(ExpectState{State::SYN_SENT});
test_4.execute(ExpectNoSegment{}, "test 4 failed: RST without ACK was not ignored in SYN_SENT");
// good ACK with RST should result in a RESET but no RST segment sent
test_4.send_rst(base_seq, base_seq + 1);
test_4.execute(ExpectState{State::RESET});
test_4.execute(ExpectNoSegment{}, "test 4 failed: RST with good ackno should RESET the connection");
}
// test 5: ack/rst in SYN_SENT
cerr << "Test 5" << endl;
ack_rst_syn_sent_test(cfg, base_seq, base_seq, base_seq, false, __LINE__);
ack_rst_syn_sent_test(cfg, base_seq, base_seq, base_seq + 2, false, __LINE__);
ack_rst_syn_sent_test(cfg, base_seq, base_seq, base_seq, true, __LINE__);
ack_rst_syn_sent_test(cfg, base_seq, base_seq, base_seq + 2, true, __LINE__);
} catch (const exception &e) {
cerr << e.what() << endl;
return EXIT_FAILURE;
}
return EXIT_SUCCESS;
}
#include "tcp_config.hh"
#include "tcp_expectation.hh"
#include "tcp_fsm_test_harness.hh"
#include "tcp_header.hh"
#include "tcp_segment.hh"
#include <cstdint>
#include <cstdlib>
#include <exception>
#include <iostream>
using namespace std;
using State = TCPTestHarness::State;
// in LISTEN, send ACK
static void ack_listen_test(const TCPConfig &cfg,
const WrappingInt32 seqno,
const WrappingInt32 ackno,
const int lineno) {
try {
TCPTestHarness test = TCPTestHarness::in_listen(cfg);
// any ACK should result in a RST
test.send_ack(seqno, ackno);
test.execute(ExpectState{State::LISTEN});
test.execute(ExpectNoSegment{}, "test 3 failed: ACKs in LISTEN should be ignored");
} catch (const exception &e) {
throw runtime_error(string(e.what()) + " (ack_listen_test called from line " + to_string(lineno) + ")");
}
}
// in SYN_SENT, send ACK and maybe RST
static void ack_rst_syn_sent_test(const TCPConfig &cfg,
const WrappingInt32 base_seq,
const WrappingInt32 seqno,
const WrappingInt32 ackno,
const int lineno) {
try {
TCPTestHarness test = TCPTestHarness::in_syn_sent(cfg, base_seq);
// unacceptable ACKs should be ignored
test.send_ack(seqno, ackno);
test.execute(ExpectState{State::SYN_SENT});
test.execute(ExpectNoSegment{}, "test 3 failed: bad ACKs in SYN_SENT should be ignored");
} catch (const exception &e) {
throw runtime_error(string(e.what()) + " (ack_rst_syn_sent_test called from line " + to_string(lineno) + ")");
}
}
int main() {
try {
TCPConfig cfg{};
const WrappingInt32 base_seq(1 << 31);
// test #1: in ESTABLISHED, send unacceptable segments and ACKs
{
cerr << "Test 1" << endl;
TCPTestHarness test_1 = TCPTestHarness::in_established(cfg, base_seq - 1, base_seq - 1);
// acceptable ack---no response
test_1.send_ack(base_seq, base_seq);
test_1.execute(ExpectNoSegment{}, "test 1 failed: ACK after acceptable ACK");
// ack in the past---no response
test_1.send_ack(base_seq, base_seq - 1);
test_1.execute(ExpectNoSegment{}, "test 1 failed: ACK after past ACK");
// ack in the future---should get ACK back
test_1.send_ack(base_seq, base_seq + 1);
test_1.execute(ExpectOneSegment{}.with_ack(true).with_ackno(base_seq), "test 1 failed: bad ACK");
// segment out of the window---should get an ACK
test_1.send_byte(base_seq - 1, base_seq, 1);
test_1.execute(ExpectUnassembledBytes{0}, "test 1 failed: seg queued on early seqno");
test_1.execute(ExpectOneSegment{}.with_ack(true).with_ackno(base_seq), "test 1 failed: bad ACK");
// segment out of the window---should get an ACK
test_1.send_byte(base_seq + cfg.recv_capacity, base_seq, 1);
test_1.execute(ExpectUnassembledBytes{0}, "test 1 failed: seg queued on late seqno");
test_1.execute(ExpectOneSegment{}.with_ack(true).with_ackno(base_seq),
"test 1 failed: bad ACK on late seqno");
// packet next byte in the window - ack should advance and data should be readable
test_1.send_byte(base_seq, base_seq, 1);
test_1.execute(ExpectData{}, "test 1 failed: pkt not processed on next seqno");
test_1.execute(ExpectOneSegment{}.with_ack(true).with_ackno(base_seq + 1), "test 1 failed: bad ACK");
test_1.send_rst(base_seq + 1);
test_1.execute(ExpectState{State::RESET});
}
// test #2: in LISTEN, send RSTs
{
cerr << "Test 2" << endl;
TCPTestHarness test_2 = TCPTestHarness::in_listen(cfg);
// all RSTs should be ignored in LISTEN
test_2.send_rst(base_seq);
test_2.send_rst(base_seq - 1);
test_2.send_rst(base_seq + cfg.recv_capacity);
test_2.execute(ExpectNoSegment{}, "test 2 failed: RST was not ignored in LISTEN");
}
// test 3: ACKs in LISTEN
cerr << "Test 3" << endl;
ack_listen_test(cfg, base_seq, base_seq, __LINE__);
ack_listen_test(cfg, base_seq - 1, base_seq, __LINE__);
ack_listen_test(cfg, base_seq, base_seq - 1, __LINE__);
ack_listen_test(cfg, base_seq - 1, base_seq, __LINE__);
ack_listen_test(cfg, base_seq - 1, base_seq - 1, __LINE__);
ack_listen_test(cfg, base_seq + cfg.recv_capacity, base_seq, __LINE__);
ack_listen_test(cfg, base_seq, base_seq + cfg.recv_capacity, __LINE__);
ack_listen_test(cfg, base_seq + cfg.recv_capacity, base_seq, __LINE__);
ack_listen_test(cfg, base_seq + cfg.recv_capacity, base_seq + cfg.recv_capacity, __LINE__);
// test 4: ACK and RST in SYN_SENT
{
cerr << "Test 4" << endl;
TCPTestHarness test_4 = TCPTestHarness::in_syn_sent(cfg, base_seq);
// good ACK with RST should result in a RESET but no RST segment sent
test_4.send_rst(base_seq, base_seq + 1);
test_4.execute(ExpectState{State::RESET});
test_4.execute(ExpectNoSegment{}, "test 4 failed: RST with good ackno should RESET the connection");
}
// test 5: ack/rst in SYN_SENT
cerr << "Test 5" << endl;
ack_rst_syn_sent_test(cfg, base_seq, base_seq, base_seq, __LINE__);
ack_rst_syn_sent_test(cfg, base_seq, base_seq, base_seq + 2, __LINE__);
} catch (const exception &e) {
cerr << e.what() << endl;
return EXIT_FAILURE;
}
return EXIT_SUCCESS;
}
#include "tcp_config.hh"
#include "tcp_expectation.hh"
#include "tcp_fsm_test_harness.hh"
#include "tcp_header.hh"
#include "tcp_segment.hh"
#include <cstdint>
#include <cstdlib>
#include <exception>
#include <iostream>
using namespace std;
using State = TCPTestHarness::State;
int main() {
try {
TCPConfig cfg{};
const WrappingInt32 base_seq(1 << 31);
// test #1: in ESTABLISHED, send unacceptable segments and ACKs
{
TCPTestHarness test_1 = TCPTestHarness::in_established(cfg, base_seq - 1, base_seq - 1);
// acceptable ack---no response
test_1.send_ack(base_seq, base_seq);
test_1.execute(ExpectNoSegment{}, "test 1 failed: ACK after acceptable ACK");
// ack in the past---no response
test_1.send_ack(base_seq, base_seq - 1);
test_1.execute(ExpectNoSegment{}, "test 1 failed: ACK after past ACK");
// ack in the future---should get ACK back
test_1.send_ack(base_seq, base_seq + 1);
test_1.execute(ExpectOneSegment{}.with_ackno(base_seq), "test 1 failed: bad ACK after future ACK");
// segment out of the window---should get an ACK
test_1.send_byte(base_seq - 1, base_seq, 1);
test_1.execute(ExpectUnassembledBytes{0}, "test 1 failed: seg queued on early seqno");
test_1.execute(ExpectOneSegment{}.with_ackno(base_seq), "test 1 failed: no ack on early seqno");
// segment out of the window---should get an ACK
test_1.send_byte(base_seq + cfg.recv_capacity, base_seq, 1);
test_1.execute(ExpectUnassembledBytes{0}, "test 1 failed: seg queued on late seqno");
test_1.execute(ExpectOneSegment{}.with_ackno(base_seq), "test 1 failed: no ack on late seqno");
// segment in the window but late---should get an ACK and seg should be queued
test_1.send_byte(base_seq + cfg.recv_capacity - 1, base_seq, 1);
test_1.execute(ExpectUnassembledBytes{1}, "seg not queued on end-of-window seqno");
test_1.execute(ExpectOneSegment{}.with_ackno(base_seq), "test 1 failed: no ack on end-of-window seqno");
test_1.execute(ExpectNoData{}, "test 1 failed: no ack on end-of-window seqno");
// segment next byte in the window - ack should advance and data should be readable
test_1.send_byte(base_seq, base_seq, 1);
test_1.execute(ExpectUnassembledBytes{1}, "seg not processed on next seqno");
test_1.execute(ExpectOneSegment{}.with_ackno(base_seq + 1), "test 1 failed: no ack on next seqno");
test_1.execute(ExpectData{}, "test 1 failed: no ack on next seqno");
// segment not in window with RST set --- should get nothing back
test_1.send_rst(base_seq);
test_1.send_rst(base_seq + cfg.recv_capacity + 1);
test_1.execute(ExpectNoSegment{}, "test 1 failed: got a response to an out-of-window RST");
test_1.send_rst(base_seq + 1);
test_1.execute(ExpectState{State::RESET});
}
} catch (const exception &e) {
cerr << e.what() << endl;
return EXIT_FAILURE;
}
return EXIT_SUCCESS;
}
#include "tcp_config.hh"
#include "tcp_expectation.hh"
#include "tcp_fsm_test_harness.hh"
#include "tcp_header.hh"
#include "tcp_segment.hh"
#include <cstdint>
#include <cstdlib>
#include <exception>
#include <iostream>
using namespace std;
using State = TCPTestHarness::State;
int main() {
try {
TCPConfig cfg{};
const WrappingInt32 base_seq(1 << 31);
// test #1: in ESTABLISHED, send unacceptable segments and ACKs
{
TCPTestHarness test_1 = TCPTestHarness::in_established(cfg, base_seq - 1, base_seq - 1);
// acceptable ack---no response
test_1.send_ack(base_seq, base_seq);
test_1.execute(ExpectNoSegment{}, "test 1 failed: ACK after acceptable ACK");
// ack in the past---no response
test_1.send_ack(base_seq, base_seq - 1);
test_1.execute(ExpectNoSegment{}, "test 1 failed: ACK after past ACK");
// ack in the future---should get ACK back
test_1.send_ack(base_seq, base_seq + 1);
test_1.execute(ExpectOneSegment{}.with_ackno(base_seq), "test 1 failed: bad ACK after future ACK");
// segment out of the window---should get an ACK
test_1.send_byte(base_seq - 1, base_seq, 1);
test_1.execute(ExpectUnassembledBytes{0}, "test 1 failed: seg queued on early seqno");
test_1.execute(ExpectOneSegment{}.with_ackno(base_seq), "test 1 failed: no ack on early seqno");
// segment out of the window---should get an ACK
test_1.send_byte(base_seq + cfg.recv_capacity, base_seq, 1);
test_1.execute(ExpectUnassembledBytes{0}, "test 1 failed: seg queued on late seqno");
test_1.execute(ExpectOneSegment{}.with_ackno(base_seq), "test 1 failed: no ack on late seqno");
// segment in the window but late---should get an ACK and seg should be queued
test_1.send_byte(base_seq + cfg.recv_capacity - 1, base_seq, 1);
test_1.execute(ExpectUnassembledBytes{1}, "seg not queued on end-of-window seqno");
test_1.execute(ExpectOneSegment{}.with_ackno(base_seq), "test 1 failed: no ack on end-of-window seqno");
test_1.execute(ExpectNoData{}, "test 1 failed: no ack on end-of-window seqno");
// segment next byte in the window - ack should advance and data should be readable
test_1.send_byte(base_seq, base_seq, 1);
test_1.execute(ExpectUnassembledBytes{1}, "seg not processed on next seqno");
test_1.execute(ExpectOneSegment{}.with_ackno(base_seq + 1), "test 1 failed: no ack on next seqno");
test_1.execute(ExpectData{}, "test 1 failed: no ack on next seqno");
test_1.send_rst(base_seq + 1);
test_1.execute(ExpectState{State::RESET});
}
} catch (const exception &e) {
cerr << e.what() << endl;
return EXIT_FAILURE;
}
return EXIT_SUCCESS;
}
#include "tcp_config.hh"
#include "tcp_expectation.hh"
#include "tcp_fsm_test_harness.hh"
#include "tcp_header.hh"
#include "tcp_segment.hh"
#include <cstdlib>
#include <exception>
#include <iostream>
#include <stdexcept>
#include <string>
using namespace std;
using State = TCPTestHarness::State;
int main() {
try {
TCPConfig cfg{};
// test #1: start in TIME_WAIT, timeout
{
TCPTestHarness test_1 = TCPTestHarness::in_time_wait(cfg);
test_1.execute(Tick(10 * cfg.rt_timeout - 1));
test_1.execute(ExpectState{State::TIME_WAIT});
test_1.execute(Tick(1));
test_1.execute(ExpectNotInState{State::TIME_WAIT});
test_1.execute(Tick(10 * cfg.rt_timeout));
test_1.execute(ExpectState{State::CLOSED});
}
// test #2: start in CLOSING, send ack, time out
{
TCPTestHarness test_2 = TCPTestHarness::in_closing(cfg);
test_2.execute(Tick(4 * cfg.rt_timeout));
test_2.execute(ExpectOneSegment{}.with_fin(true));
test_2.execute(ExpectState{State::CLOSING});
test_2.send_ack(WrappingInt32{2}, WrappingInt32{2});
test_2.execute(ExpectNoSegment{});
test_2.execute(ExpectState{State::TIME_WAIT});
test_2.execute(Tick(10 * cfg.rt_timeout - 1));
test_2.execute(ExpectState{State::TIME_WAIT});
test_2.execute(Tick(2));
test_2.execute(ExpectState{State::CLOSED});
}
// test #3: start in FIN_WAIT_2, send FIN, time out
{
TCPTestHarness test_3 = TCPTestHarness::in_fin_wait_2(cfg);
test_3.execute(Tick(4 * cfg.rt_timeout));
test_3.execute(ExpectState{State::FIN_WAIT_2});
const WrappingInt32 rx_seqno{1};
test_3.send_fin(rx_seqno, WrappingInt32{2});
const auto ack_expect = rx_seqno + 1;
test_3.execute(Tick(1));
test_3.execute(ExpectOneSegment{}.with_ack(true).with_ackno(ack_expect),
"test 3 failed: wrong ACK for FIN");
test_3.execute(ExpectState{State::TIME_WAIT});
test_3.execute(Tick(10 * cfg.rt_timeout));
test_3.execute(ExpectState{State::CLOSED});
}
// test #4: start in FIN_WAIT_1, ack, FIN, time out
{
TCPTestHarness test_4 = TCPTestHarness::in_fin_wait_1(cfg);
// Expect retransmission of FIN
test_4.execute(Tick(4 * cfg.rt_timeout));
test_4.execute(ExpectOneSegment{}.with_fin(true));
// ACK the FIN
const WrappingInt32 rx_seqno{1};
test_4.send_ack(rx_seqno, WrappingInt32{2});
test_4.execute(Tick(5));
// Send our own FIN
test_4.send_fin(rx_seqno, WrappingInt32{2});
const auto ack_expect = rx_seqno + 1;
test_4.execute(Tick(1));
test_4.execute(ExpectOneSegment{}.with_no_flags().with_ack(true).with_ackno(ack_expect));
test_4.execute(Tick(10 * cfg.rt_timeout));
test_4.execute(ExpectState{State::CLOSED});
}
// test 5: start in FIN_WAIT_1, ack, FIN, FIN again, time out
{
TCPTestHarness test_5 = TCPTestHarness::in_fin_wait_1(cfg);
// ACK the FIN
const WrappingInt32 rx_seqno{1};
test_5.send_ack(rx_seqno, WrappingInt32{2});
test_5.execute(ExpectState{State::FIN_WAIT_2});
test_5.execute(Tick(5));
test_5.send_fin(rx_seqno, WrappingInt32{2});
test_5.execute(ExpectState{State::TIME_WAIT});
test_5.execute(ExpectLingerTimer{0ul});
const auto ack_expect = rx_seqno + 1;
test_5.execute(Tick(1));
test_5.execute(ExpectLingerTimer{1ul});
test_5.execute(ExpectOneSegment{}.with_no_flags().with_ack(true).with_ackno(ack_expect));
test_5.execute(Tick(10 * cfg.rt_timeout - 10));
test_5.execute(ExpectLingerTimer{uint64_t(10 * cfg.rt_timeout - 9)});
test_5.send_fin(rx_seqno, WrappingInt32{2});
test_5.execute(ExpectLingerTimer{0ul});
test_5.execute(Tick(1));
test_5.execute(ExpectOneSegment{}.with_ack(true).with_ackno(ack_expect),
"test 5 failed: no ACK for 2nd FIN");
test_5.execute(ExpectState{State::TIME_WAIT});
// tick the timer and see what happens---a 2nd FIN in TIME_WAIT should reset the wait timer!
// (this is an edge case of "throw it away and send another ack" for out-of-window segs)
test_5.execute(Tick(10 * cfg.rt_timeout - 10));
test_5.execute(ExpectLingerTimer{uint64_t(10 * cfg.rt_timeout - 9)},
"test 5 failed: time_since_last_segment_received() should reset after 2nd FIN");
test_5.execute(ExpectNoSegment{});
test_5.execute(Tick(10));
test_5.execute(ExpectState{State::CLOSED});
}
// test 6: start in ESTABLISHED, get FIN, get FIN re-tx, send FIN, get ACK, send ACK, time out
{
TCPTestHarness test_6 = TCPTestHarness::in_established(cfg);
test_6.execute(Close{});
test_6.execute(Tick(1));
TCPSegment seg1 = test_6.expect_seg(ExpectOneSegment{}.with_fin(true),
"test 6 failed: bad FIN after close()");
auto &seg1_hdr = seg1.header();
test_6.execute(Tick(cfg.rt_timeout - 2));
test_6.execute(ExpectNoSegment{}, "test 6 failed: FIN re-tx was too fast");
test_6.execute(Tick(2));
TCPSegment seg2 = test_6.expect_seg(ExpectOneSegment{}.with_fin(true).with_seqno(seg1_hdr.seqno),
"test 6 failed: bad re-tx FIN");
auto &seg2_hdr = seg2.header();
const WrappingInt32 rx_seqno{1};
test_6.send_fin(rx_seqno, WrappingInt32{0});
const auto ack_expect = rx_seqno + 1;
test_6.execute(Tick(1));
test_6.execute(ExpectState{State::CLOSING});
test_6.execute(ExpectOneSegment{}.with_ack(true).with_ackno(ack_expect), "test 6 failed: bad ACK for FIN");
test_6.send_ack(ack_expect, seg2_hdr.seqno + 1);
test_6.execute(Tick(1));
test_6.execute(ExpectState{State::TIME_WAIT});
test_6.execute(Tick(10 * cfg.rt_timeout));
test_6.execute(ExpectState{State::CLOSED});
}
} catch (const exception &e) {
cerr << e.what() << endl;
return EXIT_FAILURE;
}
return EXIT_SUCCESS;
}
#include "tcp_config.hh"
#include "tcp_expectation.hh"
#include "tcp_fsm_test_harness.hh"
#include "tcp_header.hh"
#include "tcp_segment.hh"
#include "util.hh"
#include <cstdint>
#include <cstdlib>
#include <exception>
#include <iostream>
#include <stdexcept>
#include <string>
using namespace std;
using State = TCPTestHarness::State;
int main() {
try {
TCPConfig cfg{};
auto rd = get_random_generator();
// NB: In Riad's version of this test, the ACK was not ignored, but per
// pg 68 of the RFC, I think that it should be.
//
// test #1: START -> SYN_SENT -> ACK (ignored) -> SYN -> SYN_RECV
{
TCPTestHarness test_1(cfg);
// tell the FSM to connect, make sure we get a SYN
test_1.execute(Connect{});
test_1.execute(Tick(1));
TCPSegment seg1 = test_1.expect_seg(ExpectOneSegment{}.with_syn(true).with_ack(false),
"test 1 failed: could not parse SYN segment or invalid flags");
test_1.execute(ExpectState{State::SYN_SENT});
// send ACK only (no SYN yet)
test_1.send_ack(WrappingInt32{0}, seg1.header().seqno + 1);
test_1.execute(Tick(1));
test_1.execute(ExpectState{State::SYN_SENT});
// now send SYN
const WrappingInt32 isn(rd());
test_1.send_syn(isn);
test_1.execute(Tick(1));
test_1.execute(ExpectState{State::SYN_RCVD});
test_1.execute(ExpectOneSegment{}.with_ack(true).with_syn(false).with_ackno(isn + 1));
test_1.execute(ExpectBytesInFlight{1UL});
}
// test #2: START -> SYN_SENT -> SYN -> ACK -> ESTABLISHED
{
TCPTestHarness test_2(cfg);
test_2.execute(Connect{});
test_2.execute(Tick(1));
TCPSegment seg = test_2.expect_seg(ExpectOneSegment{}.with_syn(true).with_ack(false),
"test 2 failed: could not parse SYN segment or invalid flags");
auto &seg_hdr = seg.header();
test_2.execute(ExpectState{State::SYN_SENT});
// send SYN (no ACK yet)
const WrappingInt32 isn(rd());
test_2.send_syn(isn);
test_2.execute(Tick(1));
test_2.expect_seg(ExpectOneSegment{}.with_syn(false).with_ack(true).with_ackno(isn + 1),
"test 2 failed: bad ACK for SYN");
test_2.execute(ExpectState{State::SYN_RCVD});
// now send ACK
test_2.send_ack(isn + 1, seg_hdr.seqno + 1);
test_2.execute(Tick(1));
test_2.execute(ExpectNoSegment{}, "test 2 failed: got spurious ACK after ACKing SYN");
test_2.execute(ExpectState{State::ESTABLISHED});
}
// test #3: START -> SYN_SENT -> SYN/ACK -> ESTABLISHED
{
TCPTestHarness test_3(cfg);
test_3.execute(Connect{});
test_3.execute(Tick(1));
TCPSegment seg = test_3.expect_seg(ExpectOneSegment{}.with_syn(true).with_ack(false),
"test 3 failed: could not parse SYN segment or invalid flags");
auto &seg_hdr = seg.header();
test_3.execute(ExpectState{State::SYN_SENT});
// send SYN (no ACK yet)
const WrappingInt32 isn(rd());
test_3.send_syn(isn, seg_hdr.seqno + 1);
test_3.execute(Tick(1));
test_3.execute(ExpectOneSegment{}.with_ack(true).with_ackno(isn + 1).with_syn(false),
"test 3 failed: bad ACK for SYN");
test_3.execute(ExpectState{State::ESTABLISHED});
}
} catch (const exception &e) {
cerr << e.what() << endl;
return 1;
}
return EXIT_SUCCESS;
}
#include "tcp_config.hh"
#include "tcp_expectation.hh"
#include "tcp_fsm_test_harness.hh"
#include "tcp_header.hh"
#include "tcp_segment.hh"
#include "util.hh"
#include <cstdint>
#include <cstdlib>
#include <exception>
#include <iostream>
#include <stdexcept>
#include <string>
using namespace std;
using State = TCPTestHarness::State;
int main() {
try {
TCPConfig cfg{};
auto rd = get_random_generator();
// test #1: START -> SYN_SENT -> SYN/ACK -> ACK
{
TCPTestHarness test_1(cfg);
// tell the FSM to connect, make sure we get a SYN
test_1.execute(Connect{});
test_1.execute(Tick(1));
TCPSegment seg1 = test_1.expect_seg(ExpectOneSegment{}.with_syn(true).with_ack(false),
"test 1 failed: could not parse SYN segment or invalid flags");
test_1.execute(ExpectState{State::SYN_SENT});
// now send SYN/ACK
const WrappingInt32 isn(rd());
test_1.send_syn(isn, seg1.header().seqno + 1);
test_1.execute(Tick(1));
test_1.execute(ExpectState{State::ESTABLISHED});
test_1.execute(ExpectOneSegment{}.with_ack(true).with_syn(false).with_ackno(isn + 1));
test_1.execute(ExpectBytesInFlight{0UL});
}
// test #2: START -> SYN_SENT -> SYN -> ACK -> ESTABLISHED
{
TCPTestHarness test_2(cfg);
test_2.execute(Connect{});
test_2.execute(Tick(1));
TCPSegment seg = test_2.expect_seg(ExpectOneSegment{}.with_syn(true).with_ack(false),
"test 2 failed: could not parse SYN segment or invalid flags");
auto &seg_hdr = seg.header();
test_2.execute(ExpectState{State::SYN_SENT});
// send SYN (no ACK yet)
const WrappingInt32 isn(rd());
test_2.send_syn(isn);
test_2.execute(Tick(1));
test_2.expect_seg(ExpectOneSegment{}.with_syn(false).with_ack(true).with_ackno(isn + 1),
"test 2 failed: bad ACK for SYN");
test_2.execute(ExpectState{State::SYN_RCVD});
// now send ACK
test_2.send_ack(isn + 1, seg_hdr.seqno + 1);
test_2.execute(Tick(1));
test_2.execute(ExpectNoSegment{}, "test 2 failed: got spurious ACK after ACKing SYN");
test_2.execute(ExpectState{State::ESTABLISHED});
}
// test #3: START -> SYN_SENT -> SYN/ACK -> ESTABLISHED
{
TCPTestHarness test_3(cfg);
test_3.execute(Connect{});
test_3.execute(Tick(1));
TCPSegment seg = test_3.expect_seg(ExpectOneSegment{}.with_syn(true).with_ack(false),
"test 3 failed: could not parse SYN segment or invalid flags");
auto &seg_hdr = seg.header();
test_3.execute(ExpectState{State::SYN_SENT});
// send SYN (no ACK yet)
const WrappingInt32 isn(rd());
test_3.send_syn(isn, seg_hdr.seqno + 1);
test_3.execute(Tick(1));
test_3.execute(ExpectOneSegment{}.with_ack(true).with_ackno(isn + 1).with_syn(false),
"test 3 failed: bad ACK for SYN");
test_3.execute(ExpectState{State::ESTABLISHED});
}
} catch (const exception &e) {
cerr << e.what() << endl;
return 1;
}
return EXIT_SUCCESS;
}
#include "tcp_config.hh"
#include "tcp_expectation.hh"
#include "tcp_fsm_test_harness.hh"
#include "tcp_header.hh"
#include "tcp_segment.hh"
#include <cstdint>
#include <cstdlib>
#include <exception>
#include <iostream>
#include <stdexcept>
#include <string>
using namespace std;
using State = TCPTestHarness::State;
int main() {
try {
TCPConfig cfg{};
// test #1: START -> LISTEN -> data without ACK or SYN (dropped) -> SYN -> SYN/ACK -> ACK
{
TCPTestHarness test_1(cfg);
// tell the FSM to connect, make sure we get a SYN
test_1.execute(Listen{});
test_1.execute(Tick(1));
test_1.send_byte(WrappingInt32{0}, {}, 0);
test_1.send_fin(WrappingInt32{0}, {});
test_1.execute(Tick(1));
test_1.execute(ExpectState{State::LISTEN});
test_1.execute(ExpectNoSegment{}, "test 1 failed: non-ACK data segment should be ignored");
test_1.send_syn(WrappingInt32{0}, {});
test_1.execute(Tick(1));
TCPSegment seg = test_1.expect_seg(ExpectOneSegment{}.with_syn(true).with_ack(true).with_ackno(1),
"test 1 failed: no SYN/ACK in response to SYN");
test_1.execute(ExpectState{State::SYN_RCVD});
// wrong seqno! should get ACK back but not transition
const WrappingInt32 syn_seqno = seg.header().seqno;
test_1.send_ack(WrappingInt32{0}, syn_seqno + 1);
test_1.execute(Tick(1));
test_1.execute(
ExpectOneSegment{}.with_no_flags().with_ack(true).with_ackno(1).with_seqno(seg.header().seqno + 1),
"test 1 failed: wrong response to old seqno");
test_1.send_ack(WrappingInt32(cfg.recv_capacity + 1), syn_seqno + 1);
test_1.execute(Tick(1));
test_1.execute(
ExpectOneSegment{}.with_no_flags().with_ack(true).with_ackno(1).with_seqno(seg.header().seqno + 1));
test_1.send_ack(WrappingInt32{1}, seg.header().seqno + 1);
test_1.execute(Tick(1));
test_1.execute(ExpectNoSegment{}, "test 1 failed: no need to ACK an ACK");
test_1.execute(ExpectState{State::ESTABLISHED});
}
} catch (const exception &e) {
cerr << e.what() << endl;
return 1;
}
return EXIT_SUCCESS;
}
#include "tcp_config.hh"
#include "tcp_expectation.hh"
#include "tcp_fsm_test_harness.hh"
#include "tcp_header.hh"
#include "tcp_segment.hh"
#include <cstdint>
#include <cstdlib>
#include <exception>
#include <iostream>
#include <stdexcept>
#include <string>
using namespace std;
using State = TCPTestHarness::State;
int main() {
try {
TCPConfig cfg{};
// test #1: START -> LISTEN -> SYN -> SYN/ACK -> ACK
{
TCPTestHarness test_1(cfg);
// tell the FSM to connect, make sure we get a SYN
test_1.execute(Listen{});
test_1.execute(ExpectState{State::LISTEN});
test_1.execute(Tick(1));
test_1.execute(ExpectState{State::LISTEN});
test_1.send_syn(WrappingInt32{0}, {});
test_1.execute(Tick(1));
TCPSegment seg = test_1.expect_seg(ExpectOneSegment{}.with_syn(true).with_ack(true).with_ackno(1),
"test 1 failed: no SYN/ACK in response to SYN");
test_1.execute(ExpectState{State::SYN_RCVD});
// wrong seqno! should get ACK back but not transition
const WrappingInt32 syn_seqno = seg.header().seqno;
test_1.send_ack(WrappingInt32{0}, syn_seqno + 1);
test_1.execute(Tick(1));
test_1.execute(
ExpectOneSegment{}.with_no_flags().with_ack(true).with_ackno(1).with_seqno(seg.header().seqno + 1),
"test 1 failed: wrong response to old seqno");
test_1.send_ack(WrappingInt32(cfg.recv_capacity + 1), syn_seqno + 1);
test_1.execute(Tick(1));
test_1.execute(
ExpectOneSegment{}.with_no_flags().with_ack(true).with_ackno(1).with_seqno(seg.header().seqno + 1));
test_1.send_ack(WrappingInt32{1}, seg.header().seqno + 1);
test_1.execute(Tick(1));
test_1.execute(ExpectNoSegment{}, "test 1 failed: no need to ACK an ACK");
test_1.execute(ExpectState{State::ESTABLISHED});
}
} catch (const exception &e) {
cerr << e.what() << endl;
return 1;
}
return EXIT_SUCCESS;
}
#include "tcp_config.hh"
#include "tcp_expectation.hh"
#include "tcp_fsm_test_harness.hh"
#include "tcp_header.hh"
#include "tcp_segment.hh"
#include "util.hh"
#include <algorithm>
#include <cstdint>
#include <cstdlib>
#include <exception>
#include <iostream>
#include <stdexcept>
#include <string>
#include <tuple>
#include <vector>
using namespace std;
using State = TCPTestHarness::State;
static constexpr unsigned NREPS = 64;
int main() {
try {
TCPConfig cfg{};
cfg.recv_capacity = 65000;
auto rd = get_random_generator();
// loop segments back into the same FSM
for (unsigned rep_no = 0; rep_no < NREPS; ++rep_no) {
const WrappingInt32 rx_offset(rd());
TCPTestHarness test_1 = TCPTestHarness::in_established(cfg, rx_offset - 1, rx_offset - 1);
test_1.send_ack(rx_offset, rx_offset, 65000);
string d(cfg.recv_capacity, 0);
generate(d.begin(), d.end(), [&] { return rd(); });
size_t sendoff = 0;
while (sendoff < d.size()) {
const size_t len = min(d.size() - sendoff, static_cast<size_t>(rd()) % 8192);
if (len == 0) {
continue;
}
test_1.execute(Write{d.substr(sendoff, len)});
test_1.execute(Tick(1));
test_1.execute(ExpectBytesInFlight{len});
test_1.execute(ExpectSegmentAvailable{}, "test 1 failed: cannot read after write()");
size_t n_segments = ceil(double(len) / TCPConfig::MAX_PAYLOAD_SIZE);
size_t bytes_remaining = len;
// Transfer the data segments
for (size_t i = 0; i < n_segments; ++i) {
size_t expected_size = min(bytes_remaining, TCPConfig::MAX_PAYLOAD_SIZE);
auto seg = test_1.expect_seg(ExpectSegment{}.with_payload_size(expected_size));
bytes_remaining -= expected_size;
test_1.execute(SendSegment{move(seg)});
test_1.execute(Tick(1));
}
// Transfer the (bare) ack segments
for (size_t i = 0; i < n_segments; ++i) {
auto seg = test_1.expect_seg(ExpectSegment{}.with_ack(true).with_payload_size(0));
test_1.execute(SendSegment{move(seg)});
test_1.execute(Tick(1));
}
test_1.execute(ExpectNoSegment{});
test_1.execute(ExpectBytesInFlight{0});
sendoff += len;
}
test_1.execute(ExpectData{}.with_data(d), "test 1 falied: got back the wrong data");
}
} catch (const exception &e) {
cerr << e.what() << endl;
return EXIT_FAILURE;
}
return EXIT_SUCCESS;
}
#include "tcp_config.hh"
#include "tcp_expectation.hh"
#include "tcp_fsm_test_harness.hh"
#include "tcp_header.hh"
#include "tcp_segment.hh"
#include "util.hh"
#include <algorithm>
#include <cstdint>
#include <cstdlib>
#include <exception>
#include <iostream>
#include <stdexcept>
#include <string>
#include <tuple>
#include <vector>
using namespace std;
using State = TCPTestHarness::State;
static constexpr unsigned NREPS = 32;
int main() {
try {
TCPConfig cfg{};
cfg.recv_capacity = 65000;
auto rd = get_random_generator();
// loop segments back in a different order
for (unsigned rep_no = 0; rep_no < NREPS; ++rep_no) {
const WrappingInt32 rx_offset(rd());
TCPTestHarness test_2 = TCPTestHarness::in_established(cfg, rx_offset - 1, rx_offset - 1);
test_2.send_ack(rx_offset, rx_offset, 65000);
string d(cfg.recv_capacity, 0);
generate(d.begin(), d.end(), [&] { return rd(); });
vector<TCPSegment> segs;
size_t sendoff = 0;
while (sendoff < d.size()) {
const size_t len = min(d.size() - sendoff, static_cast<size_t>(rd()) % 8192);
if (len == 0) {
continue;
}
test_2.execute(Write{d.substr(sendoff, len)});
test_2.execute(Tick(1));
test_2.execute(ExpectSegmentAvailable{}, "test 2 failed: cannot read after write()");
while (test_2.can_read()) {
segs.emplace_back(test_2.expect_seg(ExpectSegment{}));
}
sendoff += len;
}
// resend them in shuffled order
vector<size_t> seg_idx(segs.size(), 0);
iota(seg_idx.begin(), seg_idx.end(), 0);
shuffle(seg_idx.begin(), seg_idx.end(), rd);
vector<TCPSegment> acks;
for (auto idx : seg_idx) {
test_2.execute(SendSegment{std::move(segs[idx])});
test_2.execute(Tick(1));
TCPSegment s = test_2.expect_seg(ExpectOneSegment{}.with_ack(true), "test 2 failed: no ACK after rcvd");
acks.emplace_back(std::move(s));
test_2.execute(ExpectNoSegment{}, "test 2 failed: double ACK?");
}
// send just the final ack
test_2.execute(SendSegment{std::move(acks.back())});
test_2.execute(ExpectNoSegment{}, "test 2 failed: ACK for ACK?");
test_2.execute(ExpectData{}.with_data(d), "test 2 failed: wrong data after loopback");
}
} catch (const exception &e) {
cerr << e.what() << endl;
return EXIT_FAILURE;
}
return EXIT_SUCCESS;
}
#include "tcp_config.hh"
#include "tcp_expectation.hh"
#include "tcp_fsm_test_harness.hh"
#include "tcp_header.hh"
#include "tcp_segment.hh"
#include <cstdlib>
#include <exception>
#include <iostream>
#include <stdexcept>
#include <string>
using namespace std;
using State = TCPTestHarness::State;
int main() {
try {
TCPConfig cfg{};
// test #1: start in LAST_ACK, ack
{
TCPTestHarness test_1 = TCPTestHarness::in_last_ack(cfg);
test_1.execute(Tick(4 * cfg.rt_timeout));
test_1.execute(ExpectState{State::LAST_ACK});
test_1.send_ack(WrappingInt32{2}, WrappingInt32{2});
test_1.execute(Tick(1));
test_1.execute(ExpectState{State::CLOSED});
}
// test #2: start in CLOSE_WAIT, close(), throw away first FIN, ack re-tx FIN
{
TCPTestHarness test_2 = TCPTestHarness::in_close_wait(cfg);
test_2.execute(Tick(4 * cfg.rt_timeout));
test_2.execute(ExpectState{State::CLOSE_WAIT});
test_2.execute(Close{});
test_2.execute(Tick(1));
test_2.execute(ExpectState{State::LAST_ACK});
TCPSegment seg1 = test_2.expect_seg(ExpectOneSegment{}.with_fin(true), "test 2 falied: bad seg or no FIN");
test_2.execute(Tick(cfg.rt_timeout - 2));
test_2.execute(ExpectNoSegment{}, "test 2 failed: FIN re-tx was too fast");
test_2.execute(Tick(2));
TCPSegment seg2 = test_2.expect_seg(ExpectOneSegment{}.with_fin(true).with_seqno(seg1.header().seqno),
"test 2 failed: bad re-tx FIN");
const WrappingInt32 rx_seqno{2};
const auto ack_expect = rx_seqno;
test_2.send_ack(ack_expect, seg2.header().seqno - 1); // wrong ackno!
test_2.execute(Tick(1));
test_2.execute(ExpectState{State::LAST_ACK});
test_2.send_ack(ack_expect, seg2.header().seqno + 1);
test_2.execute(Tick(1));
test_2.execute(ExpectState{State::CLOSED});
}
// test #3: start in ESTABLSHED, send FIN, recv ACK, check for CLOSE_WAIT
{
TCPTestHarness test_3 = TCPTestHarness::in_established(cfg);
test_3.execute(Tick(4 * cfg.rt_timeout));
test_3.execute(ExpectState{State::ESTABLISHED});
const WrappingInt32 rx_seqno{1};
const auto ack_expect = rx_seqno + 1;
test_3.send_fin(rx_seqno, WrappingInt32{0});
test_3.execute(Tick(1));
test_3.execute(ExpectOneSegment{}.with_ack(true).with_ackno(ack_expect),
"test 3 failed: bad seg, no ACK, or wrong ackno");
test_3.execute(ExpectState{State::CLOSE_WAIT});
test_3.send_fin(rx_seqno, WrappingInt32{0});
test_3.execute(Tick(1));
test_3.execute(ExpectOneSegment{}.with_ack(true).with_ackno(ack_expect),
"test 3 falied: bad response to 2nd FIN");
test_3.execute(ExpectState{State::CLOSE_WAIT});
test_3.execute(Tick(1));
test_3.execute(Close{});
test_3.execute(Tick(1));
test_3.execute(ExpectState{State::LAST_ACK});
TCPSegment seg3 = test_3.expect_seg(ExpectOneSegment{}.with_fin(true), "test 3 failed: bad seg or no FIN");
test_3.send_ack(ack_expect, seg3.header().seqno + 1);
test_3.execute(Tick(1));
test_3.execute(ExpectState{State::CLOSED});
}
} catch (const exception &e) {
cerr << e.what() << endl;
return 1;
}
return EXIT_SUCCESS;
}
#include "tcp_config.hh"
#include "tcp_expectation.hh"
#include "tcp_fsm_test_harness.hh"
#include "tcp_header.hh"
#include "tcp_segment.hh"
#include "test_err_if.hh"
#include "util.hh"
#include <algorithm>
#include <cstdint>
#include <cstdlib>
#include <exception>
#include <iostream>
#include <stdexcept>
#include <string>
#include <tuple>
#include <vector>
using namespace std;
using State = TCPTestHarness::State;
static constexpr unsigned NREPS = 32;
bool wrapping_lt(WrappingInt32 a, WrappingInt32 b) { return static_cast<int32_t>(b.raw_value() - a.raw_value()) > 0; }
bool wrapping_gt(WrappingInt32 a, WrappingInt32 b) { return wrapping_lt(b, a); }
int main() {
try {
TCPConfig cfg{};
cfg.recv_capacity = 65000;
auto rd = get_random_generator();
// non-overlapping out-of-order segments
for (unsigned rep_no = 0; rep_no < NREPS; ++rep_no) {
const WrappingInt32 rx_isn(rd());
const WrappingInt32 tx_isn(rd());
TCPTestHarness test_1 = TCPTestHarness::in_established(cfg, tx_isn, rx_isn);
vector<tuple<size_t, size_t>> seq_size;
size_t datalen = 0;
while (datalen < cfg.recv_capacity) {
const size_t size = min(1 + (static_cast<size_t>(rd()) % (TCPConfig::MAX_PAYLOAD_SIZE - 1)),
cfg.recv_capacity - datalen);
seq_size.emplace_back(datalen, size);
datalen += size;
}
shuffle(seq_size.begin(), seq_size.end(), rd);
string d(datalen, 0);
generate(d.begin(), d.end(), [&] { return rd(); });
WrappingInt32 min_expect_ackno = rx_isn + 1;
WrappingInt32 max_expect_ackno = rx_isn + 1;
for (auto [off, sz] : seq_size) {
test_1.send_data(rx_isn + 1 + off, tx_isn + 1, d.cbegin() + off, d.cbegin() + off + sz);
if (off == min_expect_ackno.raw_value()) {
min_expect_ackno = min_expect_ackno + sz;
}
max_expect_ackno = max_expect_ackno + sz;
TCPSegment seg =
test_1.expect_seg(ExpectSegment{}.with_ack(true), "test 1 failed: no ACK for datagram");
auto &seg_hdr = seg.header();
test_err_if(wrapping_lt(seg_hdr.ackno, min_expect_ackno) ||
wrapping_gt(seg_hdr.ackno, max_expect_ackno),
"test 1 failed: no ack or out of expected range");
}
test_1.execute(Tick(1));
test_1.execute(ExpectData{}.with_data(d), "test 1 failed: got back the wrong data");
}
// overlapping out-of-order segments
for (unsigned rep_no = 0; rep_no < NREPS; ++rep_no) {
WrappingInt32 rx_isn(rd());
WrappingInt32 tx_isn(rd());
TCPTestHarness test_2 = TCPTestHarness::in_established(cfg, tx_isn, rx_isn);
vector<tuple<size_t, size_t>> seq_size;
size_t datalen = 0;
while (datalen < cfg.recv_capacity) {
const size_t size = min(1 + (static_cast<size_t>(rd()) % (TCPConfig::MAX_PAYLOAD_SIZE - 1)),
cfg.recv_capacity - datalen);
const size_t rem = TCPConfig::MAX_PAYLOAD_SIZE - size;
size_t offs;
if (rem == 0) {
offs = 0;
} else if (rem == 1) {
offs = min(size_t(1), datalen);
} else {
offs = min(min(datalen, rem), 1 + (static_cast<size_t>(rd()) % (rem - 1)));
}
test_err_if(size + offs > TCPConfig::MAX_PAYLOAD_SIZE, "test 2 internal error: bad payload size");
seq_size.emplace_back(datalen - offs, size + offs);
datalen += size;
}
test_err_if(datalen > cfg.recv_capacity, "test 2 internal error: bad offset sequence");
shuffle(seq_size.begin(), seq_size.end(), rd);
string d(datalen, 0);
generate(d.begin(), d.end(), [&] { return rd(); });
WrappingInt32 min_expect_ackno = rx_isn + 1;
WrappingInt32 max_expect_ackno = rx_isn + 1;
for (auto [off, sz] : seq_size) {
test_2.send_data(rx_isn + 1 + off, tx_isn + 1, d.cbegin() + off, d.cbegin() + off + sz);
if (off <= min_expect_ackno.raw_value() && off + sz > min_expect_ackno.raw_value()) {
min_expect_ackno = WrappingInt32(sz + off);
}
max_expect_ackno = max_expect_ackno + sz; // really loose because of overlap
TCPSegment seg =
test_2.expect_seg(ExpectSegment{}.with_ack(true), "test 2 failed: no ACK for datagram");
auto &seg_hdr = seg.header();
test_err_if(wrapping_lt(seg_hdr.ackno, min_expect_ackno) ||
wrapping_gt(seg_hdr.ackno, max_expect_ackno),
"test 2 failed: no ack or out of expected range");
}
test_2.execute(Tick(1));
test_2.execute(ExpectData{}.with_data(d), "test 1 failed: got back the wrong data");
}
} catch (const exception &e) {
cerr << e.what() << endl;
return err_num;
}
return EXIT_SUCCESS;
}
#include "fsm_retx.hh"
#include "tcp_config.hh"
#include "tcp_expectation.hh"
#include "util.hh"
#include <algorithm>
#include <cstdlib>
#include <iostream>
using namespace std;
using State = TCPTestHarness::State;
int main() {
try {
TCPConfig cfg{};
cfg.recv_capacity = 65000;
auto rd = get_random_generator();
// NOTE: the timeouts in this test are carefully adjusted to work whether the tcp_state_machine sends
// immediately upon write() or only after the next tick(). If you edit this test, make sure that
// it passes both ways (i.e., with and without calling _try_send() in TCPConnection::write())
// NOTE 2: ACK -- I think I was successful, although given unrelated
// refactor to template code it will be more challenging now
// to wait until tick() to send an outgoing segment.
// single segment re-transmit
{
const WrappingInt32 tx_ackno(rd());
TCPTestHarness test_1 = TCPTestHarness::in_established(cfg, tx_ackno - 1, tx_ackno - 1);
string data = "asdf";
test_1.execute(Write{data});
test_1.execute(Tick(1));
check_segment(test_1, data, false, __LINE__);
test_1.execute(Tick(cfg.rt_timeout - 2));
test_1.execute(ExpectNoSegment{}, "test 1 failed: re-tx too fast");
test_1.execute(Tick(2));
check_segment(test_1, data, false, __LINE__);
test_1.execute(Tick(10 * cfg.rt_timeout + 100));
check_segment(test_1, data, false, __LINE__);
for (unsigned i = 2; i < TCPConfig::MAX_RETX_ATTEMPTS; ++i) {
test_1.execute(Tick((cfg.rt_timeout << i) - i)); // exponentially increasing delay length
test_1.execute(ExpectNoSegment{}, "test 1 failed: re-tx too fast after timeout");
test_1.execute(Tick(i));
check_segment(test_1, data, false, __LINE__);
}
test_1.execute(ExpectState{State::ESTABLISHED});
test_1.execute(Tick(1 + (cfg.rt_timeout << TCPConfig::MAX_RETX_ATTEMPTS)));
test_1.execute(ExpectState{State::RESET});
test_1.execute(ExpectOneSegment{}.with_rst(true).with_ack(false).with_seqno(tx_ackno),
"test 1 failed: RST on re-tx failure was malformed");
}
} catch (const exception &e) {
cerr << e.what() << endl;
return 1;
}
return EXIT_SUCCESS;
}
#ifndef SPONGE_TESTS_FSM_RETX_HH
#define SPONGE_TESTS_FSM_RETX_HH
#include "tcp_expectation.hh"
#include "tcp_fsm_test_harness.hh"
#include "tcp_header.hh"
#include "tcp_segment.hh"
#include <exception>
#include <iostream>
#include <stdexcept>
#include <string>
#include <vector>
static void check_segment(TCPTestHarness &test, const std::string &data, const bool multiple, const int lineno) {
try {
std::cerr << " check_segment" << std::endl;
test.execute(ExpectSegment{}.with_ack(true).with_payload_size(data.size()).with_data(data));
if (!multiple) {
test.execute(ExpectNoSegment{}, "test failed: multiple re-tx?");
}
} catch (const std::exception &e) {
throw std::runtime_error(std::string(e.what()) + " (in check_segment called from line " +
std::to_string(lineno) + ")");
}
}
#endif // SPONGE_TESTS_FSM_RETX_HH
#include "fsm_retx.hh"
#include "tcp_config.hh"
#include "tcp_expectation.hh"
#include "util.hh"
#include <algorithm>
#include <cstdlib>
#include <iostream>
using namespace std;
using State = TCPTestHarness::State;
int main() {
try {
TCPConfig cfg{};
cfg.recv_capacity = 65000;
auto rd = get_random_generator();
// NOTE: the timeouts in this test are carefully adjusted to work whether the tcp_state_machine sends
// immediately upon write() or only after the next tick(). If you edit this test, make sure that
// it passes both ways (i.e., with and without calling _try_send() in TCPConnection::write())
// NOTE 2: ACK -- I think I was successful, although given unrelated
// refactor to template code it will be more challenging now
// to wait until tick() to send an outgoing segment.
// single segment re-transmit
{
WrappingInt32 tx_ackno(rd());
TCPTestHarness test_1 = TCPTestHarness::in_established(cfg, tx_ackno - 1, tx_ackno - 1);
string data = "asdf";
test_1.execute(Write{data});
test_1.execute(Tick(1));
check_segment(test_1, data, false, __LINE__);
test_1.execute(Tick(cfg.rt_timeout - 2));
test_1.execute(ExpectNoSegment{}, "test 1 failed: re-tx too fast");
test_1.execute(Tick(2));
check_segment(test_1, data, false, __LINE__);
test_1.execute(Tick(10 * cfg.rt_timeout + 100));
check_segment(test_1, data, false, __LINE__);
for (unsigned i = 2; i < TCPConfig::MAX_RETX_ATTEMPTS; ++i) {
test_1.execute(Tick((cfg.rt_timeout << i) - i)); // exponentially increasing delay length
test_1.execute(ExpectNoSegment{}, "test 1 failed: re-tx too fast after timeout");
test_1.execute(Tick(i));
check_segment(test_1, data, false, __LINE__);
}
test_1.execute(ExpectState{State::ESTABLISHED});
test_1.execute(Tick(1 + (cfg.rt_timeout << TCPConfig::MAX_RETX_ATTEMPTS)));
test_1.execute(ExpectState{State::RESET});
test_1.execute(ExpectOneSegment{}.with_rst(true), "test 1 failed: RST on re-tx failure was malformed");
}
} catch (const exception &e) {
cerr << e.what() << endl;
return 1;
}
return EXIT_SUCCESS;
}
#include "fsm_retx.hh"
#include "tcp_config.hh"
#include "util.hh"
#include <algorithm>
#include <cstdlib>
#include <iostream>
using namespace std;
using State = TCPTestHarness::State;
int main() {
try {
TCPConfig cfg{};
cfg.recv_capacity = 65000;
auto rd = get_random_generator();
// multiple segments with intervening ack
{
WrappingInt32 tx_ackno(rd());
TCPTestHarness test_2 = TCPTestHarness::in_established(cfg, tx_ackno - 1, tx_ackno - 1);
string d1 = "asdf";
string d2 = "qwer";
test_2.execute(Write{d1});
test_2.execute(Tick(1));
test_2.execute(Tick(20));
test_2.execute(Write{d2});
test_2.execute(Tick(1));
test_2.execute(ExpectSegmentAvailable{}, "test 2 failed: cannot read after write()s");
check_segment(test_2, d1, true, __LINE__);
check_segment(test_2, d2, false, __LINE__);
test_2.execute(Tick(cfg.rt_timeout - 23));
test_2.execute(ExpectNoSegment{}, "test 2 failed: re-tx too fast");
test_2.execute(Tick(4));
check_segment(test_2, d1, false, __LINE__);
test_2.execute(Tick(2 * cfg.rt_timeout - 2));
test_2.execute(ExpectNoSegment{}, "test 2 failed: re-tx too fast");
test_2.send_ack(tx_ackno, tx_ackno + 4); // make sure RTO timer restarts on successful ACK
test_2.execute(Tick(cfg.rt_timeout - 2));
test_2.execute(ExpectNoSegment{}, "test 2 failed: re-tx of 2nd seg after ack for 1st seg too fast");
test_2.execute(Tick(3));
check_segment(test_2, d2, false, __LINE__);
}
// multiple segments without intervening ack
{
WrappingInt32 tx_ackno(rd());
TCPTestHarness test_3 = TCPTestHarness::in_established(cfg, tx_ackno - 1, tx_ackno - 1);
string d1 = "asdf";
string d2 = "qwer";
test_3.execute(Write{d1});
test_3.execute(Tick(1));
test_3.execute(Tick(20));
test_3.execute(Write{d2});
test_3.execute(Tick(1));
test_3.execute(ExpectSegmentAvailable{}, "test 3 failed: cannot read after write()s");
check_segment(test_3, d1, true, __LINE__);
check_segment(test_3, d2, false, __LINE__);
test_3.execute(Tick(cfg.rt_timeout - 23));
test_3.execute(ExpectNoSegment{}, "test 3 failed: re-tx too fast");
test_3.execute(Tick(4));
check_segment(test_3, d1, false, __LINE__);
test_3.execute(Tick(2 * cfg.rt_timeout - 2));
test_3.execute(ExpectNoSegment{}, "test 3 failed: re-tx of 2nd seg too fast");
test_3.execute(Tick(3));
check_segment(test_3, d1, false, __LINE__);
}
// check that ACK of new data resets exponential backoff and restarts timer
auto backoff_test = [&](const unsigned int num_backoffs) {
WrappingInt32 tx_ackno(rd());
TCPTestHarness test_4 = TCPTestHarness::in_established(cfg, tx_ackno - 1, tx_ackno - 1);
string d1 = "asdf";
string d2 = "qwer";
test_4.execute(Write{d1});
test_4.execute(Tick(1));
test_4.execute(Tick(20));
test_4.execute(Write{d2});
test_4.execute(Tick(1));
test_4.execute(ExpectSegmentAvailable{}, "test 4 failed: cannot read after write()s");
check_segment(test_4, d1, true, __LINE__);
check_segment(test_4, d2, false, __LINE__);
test_4.execute(Tick(cfg.rt_timeout - 23));
test_4.execute(ExpectNoSegment{}, "test 4 failed: re-tx too fast");
test_4.execute(Tick(4));
check_segment(test_4, d1, false, __LINE__);
for (unsigned i = 1; i < num_backoffs; ++i) {
test_4.execute(Tick((cfg.rt_timeout << i) - i)); // exponentially increasing delay length
test_4.execute(ExpectNoSegment{}, "test 4 failed: re-tx too fast after timeout");
test_4.execute(Tick(i));
check_segment(test_4, d1, false, __LINE__);
}
test_4.send_ack(tx_ackno, tx_ackno + 4); // make sure RTO timer restarts on successful ACK
test_4.execute(Tick(cfg.rt_timeout - 2));
test_4.execute(ExpectNoSegment{},
"test 4 failed: re-tx of 2nd seg after ack for 1st seg too fast after " +
to_string(num_backoffs) + " backoffs");
test_4.execute(Tick(3));
check_segment(test_4, d2, false, __LINE__);
};
for (unsigned int i = 0; i < TCPConfig::MAX_RETX_ATTEMPTS; ++i) {
backoff_test(i);
}
} catch (const exception &e) {
cerr << e.what() << endl;
return 1;
}
return EXIT_SUCCESS;
}
#include "byte_stream.hh"
#include "fsm_stream_reassembler_harness.hh"
#include "stream_reassembler.hh"
#include "util.hh"
#include <exception>
#include <iostream>
using namespace std;
int main() {
try {
{
ReassemblerTestHarness test{2};
test.execute(SubmitSegment{"ab", 0});
test.execute(BytesAssembled(2));
test.execute(BytesAvailable("ab"));
test.execute(SubmitSegment{"cd", 2});
test.execute(BytesAssembled(4));
test.execute(BytesAvailable("cd"));
test.execute(SubmitSegment{"ef", 4});
test.execute(BytesAssembled(6));
test.execute(BytesAvailable("ef"));
}
{
ReassemblerTestHarness test{2};
test.execute(SubmitSegment{"ab", 0});
test.execute(BytesAssembled(2));
test.execute(SubmitSegment{"cd", 2});
test.execute(BytesAssembled(2));
test.execute(BytesAvailable("ab"));
test.execute(BytesAssembled(2));
test.execute(SubmitSegment{"cd", 2});
test.execute(BytesAssembled(4));
test.execute(BytesAvailable("cd"));
}
{
ReassemblerTestHarness test{1};
test.execute(SubmitSegment{"ab", 0});
test.execute(BytesAssembled(1));
test.execute(SubmitSegment{"ab", 0});
test.execute(BytesAssembled(1));
test.execute(BytesAvailable("a"));
test.execute(BytesAssembled(1));
test.execute(SubmitSegment{"abc", 0});
test.execute(BytesAssembled(2));
test.execute(BytesAvailable("b"));
test.execute(BytesAssembled(2));
}
} catch (const exception &e) {
cerr << "Exception: " << e.what() << endl;
return EXIT_FAILURE;
}
return EXIT_SUCCESS;
}
#include "byte_stream.hh"
#include "fsm_stream_reassembler_harness.hh"
#include "stream_reassembler.hh"
#include "util.hh"
#include <exception>
#include <iostream>
using namespace std;
int main() {
try {
auto rd = get_random_generator();
{
ReassemblerTestHarness test{65000};
test.execute(SubmitSegment{"abcd", 0});
test.execute(BytesAssembled(4));
test.execute(BytesAvailable("abcd"));
test.execute(NotAtEof{});
test.execute(SubmitSegment{"abcd", 0});
test.execute(BytesAssembled(4));
test.execute(BytesAvailable(""));
test.execute(NotAtEof{});
}
{
ReassemblerTestHarness test{65000};
test.execute(SubmitSegment{"abcd", 0});
test.execute(BytesAssembled(4));
test.execute(BytesAvailable("abcd"));
test.execute(NotAtEof{});
test.execute(SubmitSegment{"abcd", 4});
test.execute(BytesAssembled(8));
test.execute(BytesAvailable("abcd"));
test.execute(NotAtEof{});
test.execute(SubmitSegment{"abcd", 0});
test.execute(BytesAssembled(8));
test.execute(BytesAvailable(""));
test.execute(NotAtEof{});
test.execute(SubmitSegment{"abcd", 4});
test.execute(BytesAssembled(8));
test.execute(BytesAvailable(""));
test.execute(NotAtEof{});
}
{
ReassemblerTestHarness test{65000};
test.execute(SubmitSegment{"abcdefgh", 0});
test.execute(BytesAssembled(8));
test.execute(BytesAvailable("abcdefgh"));
test.execute(NotAtEof{});
string data = "abcdefgh";
for (size_t i = 0; i < 1000; ++i) {
size_t start_i = uniform_int_distribution<size_t>{0, 8}(rd);
auto start = data.begin();
std::advance(start, start_i);
size_t end_i = uniform_int_distribution<size_t>{start_i, 8}(rd);
auto end = data.begin();
std::advance(end, end_i);
test.execute(SubmitSegment{string{start, end}, start_i});
test.execute(BytesAssembled(8));
test.execute(BytesAvailable(""));
test.execute(NotAtEof{});
}
}
{
ReassemblerTestHarness test{65000};
test.execute(SubmitSegment{"abcd", 0});
test.execute(BytesAssembled(4));
test.execute(BytesAvailable("abcd"));
test.execute(NotAtEof{});
test.execute(SubmitSegment{"abcdef", 0});
test.execute(BytesAssembled(6));
test.execute(BytesAvailable("ef"));
test.execute(NotAtEof{});
}
} catch (const exception &e) {
cerr << "Exception: " << e.what() << endl;
return EXIT_FAILURE;
}
return EXIT_SUCCESS;
}
#ifndef SPONGE_FSM_STREAM_REASSEMBLER_HARNESS_HH
#define SPONGE_FSM_STREAM_REASSEMBLER_HARNESS_HH
#include "byte_stream.hh"
#include "stream_reassembler.hh"
#include "util.hh"
#include <exception>
#include <iostream>
#include <sstream>
#include <string>
#include <utility>
class ReassemblerExpectationViolation : public std::runtime_error {
public:
ReassemblerExpectationViolation(const std::string msg) : std::runtime_error(msg) {}
};
struct ReassemblerTestStep {
virtual std::string to_string() const { return "ReassemblerTestStep"; }
virtual void execute(StreamReassembler &) const {}
virtual ~ReassemblerTestStep() {}
};
struct ReassemblerAction : public ReassemblerTestStep {
std::string to_string() const { return "Action: " + description(); }
virtual std::string description() const { return "description missing"; }
virtual void execute(StreamReassembler &) const {}
virtual ~ReassemblerAction() {}
};
struct ReassemblerExpectation : public ReassemblerTestStep {
std::string to_string() const { return "Expectation: " + description(); }
virtual std::string description() const { return "description missing"; }
virtual void execute(StreamReassembler &) const {}
virtual ~ReassemblerExpectation() {}
};
struct BytesAvailable : public ReassemblerExpectation {
std::string _bytes;
BytesAvailable(std::string &&bytes) : _bytes(std::move(bytes)) {}
std::string description() const {
std::ostringstream ss;
ss << "stream_out().buffer_size() returned " << _bytes.size() << ", and stream_out().read(" << _bytes.size()
<< ") returned the string \"" << _bytes << "\"";
return ss.str();
}
void execute(StreamReassembler &reassembler) const {
if (reassembler.stream_out().buffer_size() != _bytes.size()) {
std::ostringstream ss;
ss << "The reassembler was expected to have `" << _bytes.size() << "` bytes available, but there were `"
<< reassembler.stream_out().buffer_size() << "`";
throw ReassemblerExpectationViolation(ss.str());
}
std::string data = reassembler.stream_out().read(_bytes.size());
if (data != _bytes) {
std::ostringstream ss;
ss << "The reassembler was expected to have bytes \"" << _bytes << "\", but there were \"" << data << "\"";
throw ReassemblerExpectationViolation(ss.str());
}
}
};
struct BytesAssembled : public ReassemblerExpectation {
size_t _bytes;
BytesAssembled(size_t bytes) : _bytes(bytes) {}
std::string description() const {
std::ostringstream ss;
ss << "net bytes assembled = " << _bytes;
return ss.str();
}
void execute(StreamReassembler &reassembler) const {
if (reassembler.stream_out().bytes_written() != _bytes) {
std::ostringstream ss;
ss << "The reassembler was expected to have `" << _bytes << "` total bytes assembled, but there were `"
<< reassembler.stream_out().bytes_written() << "`";
throw ReassemblerExpectationViolation(ss.str());
}
}
};
struct UnassembledBytes : public ReassemblerExpectation {
size_t _bytes;
UnassembledBytes(size_t bytes) : _bytes(bytes) {}
std::string description() const {
std::ostringstream ss;
ss << "bytes not assembled = " << _bytes;
return ss.str();
}
void execute(StreamReassembler &reassembler) const {
if (reassembler.unassembled_bytes() != _bytes) {
std::ostringstream ss;
ss << "The reassembler was expected to have `" << _bytes << "` bytes not assembled, but there were `"
<< reassembler.unassembled_bytes() << "`";
throw ReassemblerExpectationViolation(ss.str());
}
}
};
struct AtEof : public ReassemblerExpectation {
AtEof() {}
std::string description() const {
std::ostringstream ss;
ss << "at EOF";
return ss.str();
}
void execute(StreamReassembler &reassembler) const {
if (not reassembler.stream_out().eof()) {
std::ostringstream ss;
ss << "The reassembler was expected to be at EOF, but was not";
throw ReassemblerExpectationViolation(ss.str());
}
}
};
struct NotAtEof : public ReassemblerExpectation {
NotAtEof() {}
std::string description() const {
std::ostringstream ss;
ss << "not at EOF";
return ss.str();
}
void execute(StreamReassembler &reassembler) const {
if (reassembler.stream_out().eof()) {
std::ostringstream ss;
ss << "The reassembler was expected to **not** be at EOF, but was";
throw ReassemblerExpectationViolation(ss.str());
}
}
};
struct SubmitSegment : public ReassemblerAction {
std::string _data;
size_t _index;
bool _eof{false};
SubmitSegment(std::string data, size_t index) : _data(data), _index(index) {}
SubmitSegment &with_eof(bool eof) {
_eof = eof;
return *this;
}
std::string description() const {
std::ostringstream ss;
ss << "substring submitted with data \"" << _data << "\", index `" << _index << "`, eof `"
<< std::to_string(_eof) << "`";
return ss.str();
}
void execute(StreamReassembler &reassembler) const { reassembler.push_substring(_data, _index, _eof); }
};
class ReassemblerTestHarness {
StreamReassembler reassembler;
std::vector<std::string> steps_executed;
public:
ReassemblerTestHarness(const size_t capacity) : reassembler(capacity), steps_executed() {
steps_executed.emplace_back("Initialized (capacity = " + std::to_string(capacity) + ")");
}
void execute(const ReassemblerTestStep &step) {
try {
step.execute(reassembler);
steps_executed.emplace_back(step.to_string());
} catch (const ReassemblerExpectationViolation &e) {
std::cerr << "Test Failure on expectation:\n\t" << step.to_string();
std::cerr << "\n\nFailure message:\n\t" << e.what();
std::cerr << "\n\nList of steps that executed successfully:";
for (const std::string &s : steps_executed) {
std::cerr << "\n\t" << s;
}
std::cerr << std::endl << std::endl;
throw e;
} catch (const std::exception &e) {
std::cerr << "Test Failure on expectation:\n\t" << step.to_string();
std::cerr << "\n\nFailure message:\n\t" << e.what();
std::cerr << "\n\nList of steps that executed successfully:";
for (const std::string &s : steps_executed) {
std::cerr << "\n\t" << s;
}
std::cerr << std::endl << std::endl;
throw ReassemblerExpectationViolation("The test caused your implementation to throw an exception!");
}
}
};
#endif // SPONGE_FSM_STREAM_REASSEMBLER_HARNESS_HH
#include "byte_stream.hh"
#include "fsm_stream_reassembler_harness.hh"
#include "stream_reassembler.hh"
#include "util.hh"
#include <exception>
#include <iostream>
using namespace std;
int main() {
try {
{
ReassemblerTestHarness test{65000};
test.execute(SubmitSegment{"b", 1});
test.execute(BytesAssembled(0));
test.execute(BytesAvailable(""));
test.execute(NotAtEof{});
}
{
ReassemblerTestHarness test{65000};
test.execute(SubmitSegment{"b", 1});
test.execute(SubmitSegment{"a", 0});
test.execute(BytesAssembled(2));
test.execute(BytesAvailable("ab"));
test.execute(NotAtEof{});
}
{
ReassemblerTestHarness test{65000};
test.execute(SubmitSegment{"b", 1}.with_eof(true));
test.execute(BytesAssembled(0));
test.execute(BytesAvailable(""));
test.execute(NotAtEof{});
test.execute(SubmitSegment{"a", 0});
test.execute(BytesAssembled(2));
test.execute(BytesAvailable("ab"));
test.execute(AtEof{});
}
{
ReassemblerTestHarness test{65000};
test.execute(SubmitSegment{"b", 1});
test.execute(SubmitSegment{"ab", 0});
test.execute(BytesAssembled(2));
test.execute(BytesAvailable("ab"));
test.execute(NotAtEof{});
}
{
ReassemblerTestHarness test{65000};
test.execute(SubmitSegment{"b", 1});
test.execute(BytesAssembled(0));
test.execute(BytesAvailable(""));
test.execute(NotAtEof{});
test.execute(SubmitSegment{"d", 3});
test.execute(BytesAssembled(0));
test.execute(BytesAvailable(""));
test.execute(NotAtEof{});
test.execute(SubmitSegment{"c", 2});
test.execute(BytesAssembled(0));
test.execute(BytesAvailable(""));
test.execute(NotAtEof{});
test.execute(SubmitSegment{"a", 0});
test.execute(BytesAssembled(4));
test.execute(BytesAvailable("abcd"));
test.execute(NotAtEof{});
}
{
ReassemblerTestHarness test{65000};
test.execute(SubmitSegment{"b", 1});
test.execute(BytesAssembled(0));
test.execute(BytesAvailable(""));
test.execute(NotAtEof{});
test.execute(SubmitSegment{"d", 3});
test.execute(BytesAssembled(0));
test.execute(BytesAvailable(""));
test.execute(NotAtEof{});
test.execute(SubmitSegment{"abc", 0});
test.execute(BytesAssembled(4));
test.execute(BytesAvailable("abcd"));
test.execute(NotAtEof{});
}
{
ReassemblerTestHarness test{65000};
test.execute(SubmitSegment{"b", 1});
test.execute(BytesAssembled(0));
test.execute(BytesAvailable(""));
test.execute(NotAtEof{});
test.execute(SubmitSegment{"d", 3});
test.execute(BytesAssembled(0));
test.execute(BytesAvailable(""));
test.execute(NotAtEof{});
test.execute(SubmitSegment{"a", 0});
test.execute(BytesAssembled(2));
test.execute(BytesAvailable("ab"));
test.execute(NotAtEof{});
test.execute(SubmitSegment{"c", 2});
test.execute(BytesAssembled(4));
test.execute(BytesAvailable("cd"));
test.execute(NotAtEof{});
test.execute(SubmitSegment{"", 4}.with_eof(true));
test.execute(BytesAssembled(4));
test.execute(BytesAvailable(""));
test.execute(AtEof{});
}
} catch (const exception &e) {
cerr << "Exception: " << e.what() << endl;
return EXIT_FAILURE;
}
return EXIT_SUCCESS;
}
#include "byte_stream.hh"
#include "stream_reassembler.hh"
#include "util.hh"
#include <algorithm>
#include <cstdint>
#include <cstdlib>
#include <exception>
#include <iostream>
#include <stdexcept>
#include <tuple>
#include <utility>
#include <vector>
using namespace std;
static constexpr unsigned NREPS = 32;
static constexpr unsigned NSEGS = 128;
static constexpr unsigned MAX_SEG_LEN = 2048;
string read(StreamReassembler &reassembler) {
return reassembler.stream_out().read(reassembler.stream_out().buffer_size());
}
int main() {
try {
auto rd = get_random_generator();
// buffer a bunch of bytes, make sure we can empty and re-fill before calling close()
for (unsigned rep_no = 0; rep_no < NREPS; ++rep_no) {
StreamReassembler buf{MAX_SEG_LEN * NSEGS};
vector<tuple<size_t, size_t>> seq_size;
size_t offset = 0;
for (unsigned i = 0; i < NSEGS; ++i) {
const size_t size = 1 + (rd() % (MAX_SEG_LEN - 1));
seq_size.emplace_back(offset, size);
offset += size;
}
shuffle(seq_size.begin(), seq_size.end(), rd);
string d(offset, 0);
generate(d.begin(), d.end(), [&] { return rd(); });
for (auto [off, sz] : seq_size) {
string dd(d.cbegin() + off, d.cbegin() + off + sz);
buf.push_substring(move(dd), off, off + sz == offset);
}
auto result = read(buf);
if (buf.stream_out().bytes_written() != offset) { // read bytes
throw runtime_error("test 1 - number of bytes RX is incorrect");
}
if (!equal(result.cbegin(), result.cend(), d.cbegin())) {
throw runtime_error("test 1 - content of RX bytes is incorrect");
}
}
// insert EOF into a hole in the buffer
for (unsigned rep_no = 0; rep_no < NREPS; ++rep_no) {
StreamReassembler buf{65'000};
const size_t size = 1024;
string d(size, 0);
generate(d.begin(), d.end(), [&] { return rd(); });
buf.push_substring(d, 0, false);
buf.push_substring(d.substr(10), size + 10, false);
auto res1 = read(buf);
if (buf.stream_out().bytes_written() != size) {
throw runtime_error("test 3 - number of RX bytes is incorrect");
}
if (!equal(res1.cbegin(), res1.cend(), d.cbegin())) {
throw runtime_error("test 3 - content of RX bytes is incorrect");
}
buf.push_substring(string(d.cbegin(), d.cbegin() + 7), size, false);
buf.push_substring(string(d.cbegin() + 7, d.cbegin() + 8), size + 7, true);
auto res2 = read(buf);
if (buf.stream_out().bytes_written() != size + 8) { // rx bytes
throw runtime_error("test 3 - number of RX bytes is incorrect after 2nd read");
}
if (!equal(res2.cbegin(), res2.cend(), d.cbegin())) {
throw runtime_error("test 3 - content of RX bytes is incorrect after 2nd read");
}
}
// insert EOF over previously queued data, require one of two possible correct actions
for (unsigned rep_no = 0; rep_no < NREPS; ++rep_no) {
StreamReassembler buf{65'000};
const size_t size = 1024;
string d(size, 0);
generate(d.begin(), d.end(), [&] { return rd(); });
buf.push_substring(d, 0, false);
buf.push_substring(d.substr(10), size + 10, false);
auto res1 = read(buf);
if (buf.stream_out().bytes_written() != size) {
throw runtime_error("test 4 - number of RX bytes is incorrect");
}
if (!equal(res1.cbegin(), res1.cend(), d.cbegin())) {
throw runtime_error("test 4 - content of RX bytes is incorrect");
}
buf.push_substring(string(d.cbegin(), d.cbegin() + 15), size, true);
auto res2 = read(buf);
if (buf.stream_out().bytes_written() != 2 * size && buf.stream_out().bytes_written() != size + 15) {
throw runtime_error("test 4 - number of RX bytes is incorrect after 2nd read");
}
if (!equal(res2.cbegin(), res2.cend(), d.cbegin())) {
throw runtime_error("test 4 - content of RX bytes is incorrect after 2nd read");
}
}
} catch (const exception &e) {
cerr << "Exception: " << e.what() << endl;
return EXIT_FAILURE;
}
return EXIT_SUCCESS;
}
#include "byte_stream.hh"
#include "fsm_stream_reassembler_harness.hh"
#include "stream_reassembler.hh"
#include "util.hh"
#include <exception>
#include <iostream>
using namespace std;
int main() {
try {
{
// Overlapping assembled (unread) section
const size_t cap = {1000};
ReassemblerTestHarness test{cap};
test.execute(SubmitSegment{"a", 0});
test.execute(SubmitSegment{"ab", 0});
test.execute(BytesAssembled(2));
test.execute(BytesAvailable("ab"));
}
{
// Overlapping assembled (read) section
const size_t cap = {1000};
ReassemblerTestHarness test{cap};
test.execute(SubmitSegment{"a", 0});
test.execute(BytesAvailable("a"));
test.execute(SubmitSegment{"ab", 0});
test.execute(BytesAvailable("b"));
test.execute(BytesAssembled(2));
}
{
// Overlapping unassembled section, resulting in assembly
const size_t cap = {1000};
ReassemblerTestHarness test{cap};
test.execute(SubmitSegment{"b", 1});
test.execute(BytesAvailable(""));
test.execute(SubmitSegment{"ab", 0});
test.execute(BytesAvailable("ab"));
test.execute(UnassembledBytes{0});
test.execute(BytesAssembled(2));
}
{
// Overlapping unassembled section, not resulting in assembly
const size_t cap = {1000};
ReassemblerTestHarness test{cap};
test.execute(SubmitSegment{"b", 1});
test.execute(BytesAvailable(""));
test.execute(SubmitSegment{"bc", 1});
test.execute(BytesAvailable(""));
test.execute(UnassembledBytes{2});
test.execute(BytesAssembled(0));
}
{
// Overlapping unassembled section, not resulting in assembly
const size_t cap = {1000};
ReassemblerTestHarness test{cap};
test.execute(SubmitSegment{"c", 2});
test.execute(BytesAvailable(""));
test.execute(SubmitSegment{"bcd", 1});
test.execute(BytesAvailable(""));
test.execute(UnassembledBytes{3});
test.execute(BytesAssembled(0));
}
{
// Overlapping multiple unassembled sections
const size_t cap = {1000};
ReassemblerTestHarness test{cap};
test.execute(SubmitSegment{"b", 1});
test.execute(SubmitSegment{"d", 3});
test.execute(BytesAvailable(""));
test.execute(SubmitSegment{"bcde", 1});
test.execute(BytesAvailable(""));
test.execute(BytesAssembled(0));
test.execute(UnassembledBytes(4));
}
{
// Submission over existing
const size_t cap = {1000};
ReassemblerTestHarness test{cap};
test.execute(SubmitSegment{"c", 2});
test.execute(SubmitSegment{"bcd", 1});
test.execute(BytesAvailable(""));
test.execute(BytesAssembled(0));
test.execute(UnassembledBytes(3));
test.execute(SubmitSegment{"a", 0});
test.execute(BytesAvailable("abcd"));
test.execute(BytesAssembled(4));
test.execute(UnassembledBytes(0));
}
{
// Submission within existing
const size_t cap = {1000};
ReassemblerTestHarness test{cap};
test.execute(SubmitSegment{"bcd", 1});
test.execute(SubmitSegment{"c", 2});
test.execute(BytesAvailable(""));
test.execute(BytesAssembled(0));
test.execute(UnassembledBytes(3));
test.execute(SubmitSegment{"a", 0});
test.execute(BytesAvailable("abcd"));
test.execute(BytesAssembled(4));
test.execute(UnassembledBytes(0));
}
} catch (const exception &e) {
cerr << "Exception: " << e.what() << endl;
return EXIT_FAILURE;
}
return EXIT_SUCCESS;
}
#include "byte_stream.hh"
#include "fsm_stream_reassembler_harness.hh"
#include "stream_reassembler.hh"
#include "util.hh"
#include <exception>
#include <iostream>
using namespace std;
int main() {
try {
{
ReassemblerTestHarness test{65000};
test.execute(SubmitSegment{"abcd", 0});
test.execute(BytesAssembled(4));
test.execute(BytesAvailable("abcd"));
test.execute(NotAtEof{});
test.execute(SubmitSegment{"efgh", 4});
test.execute(BytesAssembled(8));
test.execute(BytesAvailable("efgh"));
test.execute(NotAtEof{});
}
{
ReassemblerTestHarness test{65000};
test.execute(SubmitSegment{"abcd", 0});
test.execute(BytesAssembled(4));
test.execute(NotAtEof{});
test.execute(SubmitSegment{"efgh", 4});
test.execute(BytesAssembled(8));
test.execute(BytesAvailable("abcdefgh"));
test.execute(NotAtEof{});
}
{
ReassemblerTestHarness test{65000};
std::ostringstream ss;
for (size_t i = 0; i < 100; ++i) {
test.execute(BytesAssembled(4 * i));
test.execute(SubmitSegment{"abcd", 4 * i});
test.execute(NotAtEof{});
ss << "abcd";
}
test.execute(BytesAvailable(ss.str()));
test.execute(NotAtEof{});
}
{
ReassemblerTestHarness test{65000};
std::ostringstream ss;
for (size_t i = 0; i < 100; ++i) {
test.execute(BytesAssembled(4 * i));
test.execute(SubmitSegment{"abcd", 4 * i});
test.execute(NotAtEof{});
test.execute(BytesAvailable("abcd"));
}
}
} catch (const exception &e) {
cerr << "Exception: " << e.what() << endl;
return EXIT_FAILURE;
}
return EXIT_SUCCESS;
}
#include "byte_stream.hh"
#include "fsm_stream_reassembler_harness.hh"
#include "stream_reassembler.hh"
#include "util.hh"
#include <exception>
#include <iostream>
using namespace std;
int main() {
try {
{
ReassemblerTestHarness test{65000};
test.execute(BytesAssembled(0));
test.execute(BytesAvailable(""));
test.execute(NotAtEof{});
}
{
ReassemblerTestHarness test{65000};
test.execute(SubmitSegment{"a", 0});
test.execute(BytesAssembled(1));
test.execute(BytesAvailable("a"));
test.execute(NotAtEof{});
}
{
ReassemblerTestHarness test{65000};
test.execute(SubmitSegment{"a", 0}.with_eof(true));
test.execute(BytesAssembled(1));
test.execute(BytesAvailable("a"));
test.execute(AtEof{});
}
{
ReassemblerTestHarness test{65000};
test.execute(SubmitSegment{"", 0}.with_eof(true));
test.execute(BytesAssembled(0));
test.execute(BytesAvailable(""));
test.execute(AtEof{});
}
{
ReassemblerTestHarness test{65000};
test.execute(SubmitSegment{"b", 0}.with_eof(true));
test.execute(BytesAssembled(1));
test.execute(BytesAvailable("b"));
test.execute(AtEof{});
}
{
ReassemblerTestHarness test{65000};
test.execute(SubmitSegment{"", 0});
test.execute(BytesAssembled(0));
test.execute(BytesAvailable(""));
test.execute(NotAtEof{});
}
} catch (const exception &e) {
cerr << "Exception: " << e.what() << endl;
return EXIT_FAILURE;
}
return EXIT_SUCCESS;
}
#include "byte_stream.hh"
#include "stream_reassembler.hh"
#include "util.hh"
#include <algorithm>
#include <cstdint>
#include <cstdlib>
#include <exception>
#include <iostream>
#include <stdexcept>
#include <tuple>
#include <utility>
#include <vector>
using namespace std;
static constexpr unsigned NREPS = 32;
static constexpr unsigned NSEGS = 128;
static constexpr unsigned MAX_SEG_LEN = 2048;
string read(StreamReassembler &reassembler) {
return reassembler.stream_out().read(reassembler.stream_out().buffer_size());
}
int main() {
try {
auto rd = get_random_generator();
// overlapping segments
for (unsigned rep_no = 0; rep_no < NREPS; ++rep_no) {
StreamReassembler buf{NSEGS * MAX_SEG_LEN};
vector<tuple<size_t, size_t>> seq_size;
size_t offset = 0;
for (unsigned i = 0; i < NSEGS; ++i) {
const size_t size = 1 + (rd() % (MAX_SEG_LEN - 1));
const size_t offs = min(offset, 1 + (static_cast<size_t>(rd()) % 1023));
seq_size.emplace_back(offset - offs, size + offs);
offset += size;
}
shuffle(seq_size.begin(), seq_size.end(), rd);
string d(offset, 0);
generate(d.begin(), d.end(), [&] { return rd(); });
for (auto [off, sz] : seq_size) {
string dd(d.cbegin() + off, d.cbegin() + off + sz);
buf.push_substring(move(dd), off, off + sz == offset);
}
auto result = read(buf);
if (buf.stream_out().bytes_written() != offset) { // read bytes
throw runtime_error("test 2 - number of RX bytes is incorrect");
}
if (!equal(result.cbegin(), result.cend(), d.cbegin())) {
throw runtime_error("test 2 - content of RX bytes is incorrect");
}
}
} catch (const exception &e) {
cerr << "Exception: " << e.what() << endl;
return EXIT_FAILURE;
}
return EXIT_SUCCESS;
}
#include "tcp_config.hh"
#include "tcp_expectation.hh"
#include "tcp_fsm_test_harness.hh"
#include "tcp_header.hh"
#include "tcp_segment.hh"
#include "test_err_if.hh"
#include "util.hh"
#include <algorithm>
#include <cstdint>
#include <cstdlib>
#include <exception>
#include <iostream>
#include <stdexcept>
#include <string>
#include <vector>
using namespace std;
using State = TCPTestHarness::State;
static constexpr unsigned NREPS = 32;
static constexpr unsigned MIN_SWIN = 2048;
static constexpr unsigned MAX_SWIN = 34816;
static constexpr unsigned MIN_SWIN_MUL = 2;
static constexpr unsigned MAX_SWIN_MUL = 6;
int main() {
try {
auto rd = get_random_generator();
TCPConfig cfg{};
cfg.send_capacity = MAX_SWIN * MAX_SWIN_MUL;
// test 1: listen -> established -> check advertised winsize -> check sent bytes before ACK
for (unsigned rep_no = 0; rep_no < NREPS; ++rep_no) {
cfg.recv_capacity = 2048 + (rd() % 32768);
const WrappingInt32 seq_base(rd());
TCPTestHarness test_1(cfg);
// connect
test_1.execute(Listen{});
test_1.send_syn(seq_base);
TCPSegment seg = test_1.expect_seg(
ExpectOneSegment{}.with_ack(true).with_ackno(seq_base + 1).with_win(cfg.recv_capacity),
"test 1 failed: SYN/ACK invalid");
auto &seg_hdr = seg.header();
const WrappingInt32 ack_base = seg_hdr.seqno;
// ack
const uint16_t swin = MIN_SWIN + (rd() % (MAX_SWIN - MIN_SWIN));
test_1.send_ack(seq_base + 1, ack_base + 1, swin); // adjust send window here
test_1.execute(ExpectNoSegment{}, "test 1 failed: ACK after acceptable ACK");
test_1.execute(ExpectState{State::ESTABLISHED});
// write swin_mul * swin, make sure swin gets sent
const unsigned swin_mul = MIN_SWIN_MUL + (rd() % (MAX_SWIN_MUL - MIN_SWIN_MUL));
string d(swin_mul * swin, 0);
generate(d.begin(), d.end(), [&] { return rd(); });
test_1.execute(Write{d}.with_bytes_written(swin_mul * swin));
test_1.execute(Tick(1));
string d_out(swin_mul * swin, 0);
size_t bytes_total = 0;
while (bytes_total < swin_mul * swin) {
test_1.execute(ExpectSegmentAvailable{}, "test 1 failed: nothing sent after write()");
size_t bytes_read = 0;
while (test_1.can_read()) {
TCPSegment seg2 = test_1.expect_seg(
ExpectSegment{}.with_ack(true).with_ackno(seq_base + 1).with_win(cfg.recv_capacity),
"test 1 failed: invalid datagrams carrying write() data");
auto &seg2_hdr = seg2.header();
bytes_read += seg2.payload().size();
size_t seg2_first = seg2_hdr.seqno - ack_base - 1;
copy(seg2.payload().str().cbegin(), seg2.payload().str().cend(), d_out.begin() + seg2_first);
}
test_err_if( // correct number of bytes sent
bytes_read + TCPConfig::MAX_PAYLOAD_SIZE < swin,
"test 1 failed: sender did not fill window");
test_1.execute(ExpectBytesInFlight{bytes_read}, "test 1 failed: sender wrong bytes_in_flight");
bytes_total += bytes_read;
// NOTE that we don't override send window here because cfg should have been updated
test_1.send_ack(seq_base + 1, ack_base + 1 + bytes_total, swin);
test_1.execute(Tick(1));
}
test_1.execute(ExpectBytesInFlight{0}, "test 1 failed: after acking, bytes still in flight?");
test_err_if(!equal(d.cbegin(), d.cend(), d_out.cbegin()), "test 1 failed: data mismatch");
}
} catch (const exception &e) {
cerr << e.what() << endl;
return err_num;
}
return EXIT_SUCCESS;
}
#include "ipv4_datagram.hh"
#include "ipv4_header.hh"
#include "tcp_header.hh"
#include "tcp_segment.hh"
#include "test_utils.hh"
#include "test_utils_ipv4.hh"
#include "util.hh"
#include <algorithm>
#include <cstdint>
#include <cstdlib>
#include <exception>
#include <iostream>
#include <pcap/pcap.h>
#include <string>
#include <vector>
using namespace std;
constexpr unsigned NREPS = 32;
uint16_t inet_cksum(const uint8_t *data, const size_t len) {
InternetChecksum check;
check.add({reinterpret_cast<const char *>(data), len});
return check.value();
}
int main(int argc, char **argv) {
try {
// first, make sure the parser gets the correct values and catches errors
auto rd = get_random_generator();
for (unsigned i = 0; i < NREPS; ++i) {
const uint16_t totlen = 30 + rd() % 1024;
vector<uint8_t> test_header(totlen, 0);
generate(test_header.begin(), test_header.end(), [&] { return rd(); });
test_header[0] = 0x45; // IPv4, length 20
test_header[2] = totlen >> 8;
test_header[3] = totlen & 0xff;
test_header[14] = test_header[15] = 0;
const uint16_t cksum = inet_cksum(test_header.data(), 20);
test_header[14] = cksum >> 8;
test_header[15] = cksum & 0xff;
IPv4Header test_hdr{};
{
NetParser p{string(test_header.begin(), test_header.end())};
if (auto ret = test_hdr.parse(p); ret != ParseResult::NoError) {
throw runtime_error("Parse error: " + as_string(ret));
}
}
if (test_hdr.tos != test_header[1]) {
throw runtime_error("Parse error: tos field is wrong");
}
if (test_hdr.len != totlen) {
throw runtime_error("Parse error: len field is wrong");
}
if (uint16_t tval = (test_header[4] << 8) | test_header[5]; test_hdr.id != tval) {
throw runtime_error("Parse error: id field is wrong");
}
if (uint16_t tval = ((test_header[6] & 0x7f) << 8) | test_header[7];
tval != ((test_hdr.df ? 0x4000 : 0) | (test_hdr.mf ? 0x2000 : 0) | test_hdr.offset)) {
throw runtime_error("Parse error: flags or fragment offset is wrong");
}
if (test_header[8] != test_hdr.ttl) {
throw runtime_error("Parse error: ttl is wrong");
}
if (test_header[9] != test_hdr.proto) {
throw runtime_error("Parse error: proto is wrong");
}
if (uint16_t tval = (test_header[10] << 8) | test_header[11]; test_hdr.cksum != tval) {
throw runtime_error("Parse error: cksum is wrong");
}
if (uint32_t tval =
(test_header[12] << 24) | (test_header[13] << 16) | (test_header[14] << 8) | test_header[15];
test_hdr.src != tval) {
throw runtime_error("Parse error: src addr is wrong");
}
if (uint32_t tval =
(test_header[16] << 24) | (test_header[17] << 16) | (test_header[18] << 8) | test_header[19];
test_hdr.dst != tval) {
throw runtime_error("Parse error: dst addr is wrong");
}
test_header[0] = 0x55;
{
const uint16_t new_cksum = inet_cksum(test_header.data(), 20);
test_header[14] = new_cksum >> 8;
test_header[15] = new_cksum & 0xff;
NetParser p{string(test_header.begin(), test_header.end())};
if (auto ret = test_hdr.parse(p); ret != ParseResult::WrongIPVersion) {
throw runtime_error("Parse error: failed to detect wrong IP version.");
}
}
test_header[0] = 0x44;
{
const uint16_t new_cksum = inet_cksum(test_header.data(), 20);
test_header[14] = new_cksum >> 8;
test_header[15] = new_cksum & 0xff;
NetParser p{string(test_header.begin(), test_header.end())};
if (auto ret = test_hdr.parse(p); ret != ParseResult::HeaderTooShort) {
throw runtime_error("Parse error: failed to detect header too short");
}
}
test_header[0] = 0x45;
test_header[14] = (cksum >> 8) + max(int(rd()), 1);
test_header[15] = (cksum & 0xff) + max(int(rd()), 1);
{
NetParser p{string(test_header.begin(), test_header.end())};
if (auto ret = test_hdr.parse(p); ret != ParseResult::BadChecksum) {
throw runtime_error("Parse error: failed to detect incorrect checksum");
}
}
test_header.resize(totlen - 10);
{
NetParser p{string(test_header.begin(), test_header.end())};
if (auto ret = test_hdr.parse(p); ret != ParseResult::TruncatedPacket) {
throw runtime_error("Parse error: failed to detect truncated packet");
}
}
test_header[0] = 0x46;
test_header.resize(20);
{
const uint16_t new_cksum = inet_cksum(test_header.data(), 20);
test_header[14] = new_cksum >> 8;
test_header[15] = new_cksum & 0xff;
NetParser p{string(test_header.begin(), test_header.end())};
if (auto ret = test_hdr.parse(p); ret != ParseResult::PacketTooShort) {
throw runtime_error("Parse error: failed to detect packet too short to parse");
}
}
test_header[0] = 0x45;
test_header[14] = (cksum >> 8) + max(int(rd()), 1);
test_header[15] = (cksum & 0xff) + max(int(rd()), 1);
test_header.resize(16);
{
NetParser p{string(test_header.begin(), test_header.end())};
if (auto ret = test_hdr.parse(p); ret != ParseResult::PacketTooShort) {
throw runtime_error("Parse error: failed to detect packet too short to parse");
}
}
}
if (argc < 2) {
cout << "USAGE: " << argv[0] << " <filename>" << endl;
return EXIT_FAILURE;
}
char errbuf[PCAP_ERRBUF_SIZE];
pcap_t *pcap = pcap_open_offline(argv[1], static_cast<char *>(errbuf));
if (pcap == nullptr) {
cout << "ERROR opening " << argv[1] << ": " << static_cast<char *>(errbuf) << endl;
return EXIT_FAILURE;
}
if (pcap_datalink(pcap) != 1) {
cout << "ERROR expected ethernet linktype in capture file" << endl;
return EXIT_FAILURE;
}
bool ok = true;
const uint8_t *pkt;
struct pcap_pkthdr hdr;
while ((pkt = pcap_next(pcap, &hdr)) != nullptr) {
if (hdr.caplen < 14) {
cout << "ERROR frame too short to contain Ethernet header\n";
ok = false;
continue;
}
const bool expect_fail = (pkt[12] != 0x08) || (pkt[13] != 0x00);
IPv4Datagram ip_dgram;
if (auto res = ip_dgram.parse(string(pkt + 14, pkt + hdr.caplen)); res != ParseResult::NoError) {
// parse failed
if (expect_fail) {
continue;
}
auto ip_parse_result = as_string(res);
cout << "ERROR got unexpected IP parse failure " << ip_parse_result << " for this datagram:\n";
show_ethernet_frame(pkt, hdr);
hexdump(pkt + 14, hdr.caplen - 14);
ok = false;
continue;
}
TCPSegment tcp_seg;
if (auto res = tcp_seg.parse(ip_dgram.payload(), ip_dgram.header().pseudo_cksum());
res != ParseResult::NoError) {
// parse failed
if (expect_fail) {
continue;
}
auto tcp_parse_result = as_string(res);
cout << "ERROR got unexpected TCP parse failure " << tcp_parse_result << " for this segment:\n";
show_ethernet_frame(pkt, hdr);
hexdump(pkt + 14, hdr.caplen - 14);
ok = false;
continue;
}
if (expect_fail) {
cout << "ERROR: expected parse failure but got success. Something is wrong.\n";
show_ethernet_frame(pkt, hdr);
hexdump(pkt + 14, hdr.caplen - 14);
ok = false;
continue;
}
// parse succeeded. Create a new packet and rebuild the header by unparsing.
cout << dec;
IPv4Datagram ip_dgram_copy;
TCPSegment tcp_seg_copy;
tcp_seg_copy.payload() = tcp_seg.payload();
// set headers in new packets, and fix up to remove extensions
{
const IPv4Header &ipv4_hdr_orig = ip_dgram.header();
IPv4Header &ipv4_hdr_copy = ip_dgram_copy.header();
ipv4_hdr_copy = ipv4_hdr_orig;
const TCPHeader &tcp_hdr_orig = tcp_seg.header();
TCPHeader &tcp_hdr_copy = tcp_seg_copy.header();
tcp_hdr_copy = tcp_hdr_orig;
// fix up packets to remove IPv4 and TCP header extensions
ipv4_hdr_copy.len -= 4 * ipv4_hdr_orig.hlen - IPv4Header::LENGTH;
ipv4_hdr_copy.hlen = 5;
ipv4_hdr_copy.len -= 4 * tcp_hdr_orig.doff - TCPHeader::LENGTH;
tcp_hdr_copy.doff = 5;
} // ipv4_hdr_{orig,copy}, tcp_hdr_{orig,copy} go out of scope
if (!compare_ip_headers_nolen(ip_dgram.header(), ip_dgram_copy.header())) {
cout << "ERROR: after unparsing, IP headers (other than length) don't match.\n";
}
if (!compare_tcp_headers_nolen(tcp_seg.header(), tcp_seg_copy.header())) {
cout << "ERROR: after unparsing, TCP headers (other than length) don't match.\n";
}
// create a new datagram from the serialized IP and TCP headers + payload
IPv4Datagram ip_dgram_copy2;
string concat;
concat.append(ip_dgram_copy.header().serialize());
concat.append(tcp_seg_copy.serialize(ip_dgram_copy.header().pseudo_cksum()).concatenate());
if (auto res = ip_dgram_copy2.parse(string(concat)); res != ParseResult::NoError) {
auto ip_parse_result = as_string(res);
cout << "ERROR got IP parse failure " << ip_parse_result << " for this datagram (copy2):\n";
cout << ip_dgram_copy.header().to_string();
hexdump(concat.data(), concat.size());
cout << endl;
hexdump(pkt + 14, hdr.caplen - 14);
ok = false;
continue;
}
TCPSegment tcp_seg_copy2;
if (auto res = tcp_seg_copy2.parse(ip_dgram_copy2.payload(), ip_dgram_copy2.header().pseudo_cksum());
res != ParseResult::NoError) {
auto tcp_parse_result = as_string(res);
cout << "ERROR got TCP parse failure " << tcp_parse_result << " for this segment (copy2):\n";
cout << tcp_seg_copy.header().to_string();
ok = false;
continue;
}
if (!compare_ip_headers_nolen(ip_dgram.header(), ip_dgram_copy2.header())) {
cout << "ERROR: after re-parsing, IP headers don't match (0<->2).\n";
ok = false;
continue;
}
if (!compare_ip_headers(ip_dgram_copy.header(), ip_dgram_copy2.header())) {
cout << "ERROR: after re-parsing, IP headers don't match (1<->2).\n";
ok = false;
continue;
}
if (!compare_tcp_headers_nolen(tcp_seg.header(), tcp_seg_copy2.header())) {
cout << "ERROR: after re-parsing, TCP headers don't match (0<->2).\n";
ok = false;
continue;
}
if (!compare_tcp_headers(tcp_seg_copy.header(), tcp_seg_copy2.header())) {
cout << "ERROR: after re-parsing, TCP headers don't match (1<->2).\n";
ok = false;
continue;
}
if (tcp_seg_copy.payload().str() != tcp_seg_copy2.payload().str()) {
cout << "ERROR: after re-parsing, TCP payloads don't match.\n";
hexdump(tcp_seg_copy2.payload().str().data(), tcp_seg_copy2.payload().str().size());
cout << endl;
hexdump(tcp_seg_copy.payload().str().data(), tcp_seg_copy.payload().str().size());
ok = false;
continue;
}
}
pcap_close(pcap);
if (!ok) {
return EXIT_FAILURE;
}
} catch (const exception &e) {
cout << "Exception: " << e.what() << endl;
return EXIT_FAILURE;
}
return EXIT_SUCCESS;
}
#ifndef SPONGE_RECEIVER_HARNESS_HH
#define SPONGE_RECEIVER_HARNESS_HH
#include "byte_stream.hh"
#include "tcp_receiver.hh"
#include "tcp_state.hh"
#include "util.hh"
#include "wrapping_integers.hh"
#include <algorithm>
#include <exception>
#include <iostream>
#include <optional>
#include <sstream>
#include <string>
struct ReceiverTestStep {
virtual std::string to_string() const { return "ReceiverTestStep"; }
virtual void execute(TCPReceiver &) const {}
virtual ~ReceiverTestStep() {}
};
class ReceiverExpectationViolation : public std::runtime_error {
public:
ReceiverExpectationViolation(const std::string msg) : std::runtime_error(msg) {}
};
struct ReceiverExpectation : public ReceiverTestStep {
std::string to_string() const { return "Expectation: " + description(); }
virtual std::string description() const { return "description missing"; }
virtual void execute(TCPReceiver &) const {}
virtual ~ReceiverExpectation() {}
};
struct ExpectState : public ReceiverExpectation {
std::string _state;
ExpectState(const std::string &state) : _state(state) {}
std::string description() const { return "in state `" + _state + "`"; }
void execute(TCPReceiver &receiver) const {
if (TCPState::state_summary(receiver) != _state) {
throw ReceiverExpectationViolation("The TCPReceiver was in state `" + TCPState::state_summary(receiver) +
"`, but it was expected to be in state `" + _state + "`");
}
}
};
struct ExpectAckno : public ReceiverExpectation {
std::optional<WrappingInt32> _ackno;
ExpectAckno(std::optional<WrappingInt32> ackno) : _ackno(ackno) {}
std::string description() const {
if (_ackno.has_value()) {
return "ackno " + std::to_string(_ackno.value().raw_value());
} else {
return "no ackno available";
}
}
void execute(TCPReceiver &receiver) const {
if (receiver.ackno() != _ackno) {
std::string reported =
receiver.ackno().has_value() ? std::to_string(receiver.ackno().value().raw_value()) : "none";
std::string expected = _ackno.has_value() ? std::to_string(_ackno.value().raw_value()) : "none";
throw ReceiverExpectationViolation("The TCPReceiver reported ackno `" + reported +
"`, but it was expected to be `" + expected + "`");
}
}
};
struct ExpectWindow : public ReceiverExpectation {
size_t _window;
ExpectWindow(const size_t window) : _window(window) {}
std::string description() const { return "window " + std::to_string(_window); }
void execute(TCPReceiver &receiver) const {
if (receiver.window_size() != _window) {
std::string reported = std::to_string(receiver.window_size());
std::string expected = std::to_string(_window);
throw ReceiverExpectationViolation("The TCPReceiver reported window `" + reported +
"`, but it was expected to be `" + expected + "`");
}
}
};
struct ExpectUnassembledBytes : public ReceiverExpectation {
size_t _n_bytes;
ExpectUnassembledBytes(size_t n_bytes) : _n_bytes(n_bytes) {}
std::string description() const { return std::to_string(_n_bytes) + " unassembled bytes"; }
void execute(TCPReceiver &receiver) const {
if (receiver.unassembled_bytes() != _n_bytes) {
std::ostringstream ss;
ss << "The TCPReceiver reported `" << receiver.unassembled_bytes()
<< "` unassembled bytes, but there was expected to be `" << _n_bytes << "` unassembled bytes";
throw ReceiverExpectationViolation(ss.str());
}
}
};
struct ExpectTotalAssembledBytes : public ReceiverExpectation {
size_t _n_bytes;
ExpectTotalAssembledBytes(size_t n_bytes) : _n_bytes(n_bytes) {}
std::string description() const { return std::to_string(_n_bytes) + " assembled bytes, in total"; }
void execute(TCPReceiver &receiver) const {
if (receiver.stream_out().bytes_written() != _n_bytes) {
std::ostringstream ss;
ss << "The TCPReceiver stream reported `" << receiver.stream_out().bytes_written()
<< "` bytes written, but there was expected to be `" << _n_bytes << "` bytes written (in total)";
throw ReceiverExpectationViolation(ss.str());
}
}
};
struct ExpectBytes : public ReceiverExpectation {
std::string _bytes;
ExpectBytes(std::string &&bytes) : _bytes(std::move(bytes)) {}
std::string description() const {
std::ostringstream ss;
ss << "bytes available: \"" << _bytes << "\"";
return ss.str();
}
void execute(TCPReceiver &receiver) const {
ByteStream &stream = receiver.stream_out();
if (stream.buffer_size() != _bytes.size()) {
std::ostringstream ss;
ss << "The TCPReceiver reported `" << stream.buffer_size()
<< "` bytes available, but there were expected to be `" << _bytes.size() << "` bytes available";
throw ReceiverExpectationViolation(ss.str());
}
std::string bytes = stream.read(_bytes.size());
if (not std::equal(bytes.begin(), bytes.end(), _bytes.begin(), _bytes.end())) {
std::ostringstream ss;
ss << "the TCPReceiver assembled \"" << bytes << "\", but was expected to assemble \"" << _bytes << "\".";
throw ReceiverExpectationViolation(ss.str());
}
}
};
struct ReceiverAction : public ReceiverTestStep {
std::string to_string() const { return "Action: " + description(); }
virtual std::string description() const { return "description missing"; }
virtual void execute(TCPReceiver &) const {}
virtual ~ReceiverAction() {}
};
struct SegmentArrives : public ReceiverAction {
enum class Result { NOT_SYN, OUT_OF_WINDOW, OK };
static std::string result_name(Result res) {
switch (res) {
case Result::NOT_SYN:
return "(no SYN received)";
case Result::OUT_OF_WINDOW:
return "false (segment does not overlap the window)";
case Result::OK:
return "true (segment is at least partly in the window)";
default:
return "unknown";
}
}
bool ack{};
bool rst{};
bool syn{};
bool fin{};
WrappingInt32 seqno{0};
WrappingInt32 ackno{0};
uint16_t win{};
std::string data{};
std::optional<Result> result{};
SegmentArrives &with_ack(WrappingInt32 ackno_) {
ack = true;
ackno = ackno_;
return *this;
}
SegmentArrives &with_ack(uint32_t ackno_) { return with_ack(WrappingInt32{ackno_}); }
SegmentArrives &with_rst() {
rst = true;
return *this;
}
SegmentArrives &with_syn() {
syn = true;
return *this;
}
SegmentArrives &with_fin() {
fin = true;
return *this;
}
SegmentArrives &with_seqno(WrappingInt32 seqno_) {
seqno = seqno_;
return *this;
}
SegmentArrives &with_seqno(uint32_t seqno_) { return with_seqno(WrappingInt32{seqno_}); }
SegmentArrives &with_win(uint16_t win_) {
win = win_;
return *this;
}
SegmentArrives &with_data(std::string data_) {
data = data_;
return *this;
}
SegmentArrives &with_result(Result result_) {
result = result_;
return *this;
}
TCPSegment build_segment() const {
TCPSegment seg;
seg.payload() = std::string(data);
seg.header().ack = ack;
seg.header().fin = fin;
seg.header().syn = syn;
seg.header().rst = rst;
seg.header().ackno = ackno;
seg.header().seqno = seqno;
seg.header().win = win;
return seg;
}
std::string description() const override {
TCPSegment seg = build_segment();
std::ostringstream o;
o << "segment arrives ";
o << seg.header().summary();
if (data.size() > 0) {
o << " with data \"" << data << "\"";
}
return o.str();
}
void execute(TCPReceiver &receiver) const override {
TCPSegment seg = build_segment();
std::ostringstream o;
o << seg.header().summary();
if (data.size() > 0) {
o << " with data \"" << data << "\"";
}
const bool return_value = receiver.segment_received(std::move(seg));
Result res;
if (not receiver.ackno().has_value()) {
res = Result::NOT_SYN;
} else if (return_value) {
res = Result::OK;
} else {
res = Result::OUT_OF_WINDOW;
}
if (result.has_value() and result.value() != res) {
throw ReceiverExpectationViolation("TCPReceiver::segment_received() reported `" + result_name(res) +
"` in response to `" + o.str() + "`, but it was expected to report `" +
result_name(result.value()) + "`");
}
}
};
class TCPReceiverTestHarness {
TCPReceiver receiver;
std::vector<std::string> steps_executed;
public:
TCPReceiverTestHarness(size_t capacity) : receiver(capacity), steps_executed() {
std::ostringstream ss;
ss << "Initialized with ("
<< "capacity=" << capacity << ")";
steps_executed.emplace_back(ss.str());
}
void execute(const ReceiverTestStep &step) {
try {
step.execute(receiver);
steps_executed.emplace_back(step.to_string());
} catch (const ReceiverExpectationViolation &e) {
std::cerr << "Test Failure on expectation:\n\t" << step.to_string();
std::cerr << "\n\nFailure message:\n\t" << e.what();
std::cerr << "\n\nList of steps that executed successfully:";
for (const std::string &s : steps_executed) {
std::cerr << "\n\t" << s;
}
std::cerr << std::endl << std::endl;
throw e;
}
}
};
#endif // SPONGE_RECEIVER_HARNESS_HH
#include "receiver_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();
{
uint32_t isn = uniform_int_distribution<uint32_t>{0, UINT32_MAX}(rd);
TCPReceiverTestHarness test{4000};
test.execute(ExpectState{TCPReceiverStateSummary::LISTEN});
test.execute(SegmentArrives{}.with_syn().with_seqno(isn + 0).with_result(SegmentArrives::Result::OK));
test.execute(ExpectState{TCPReceiverStateSummary::SYN_RECV});
test.execute(SegmentArrives{}.with_fin().with_seqno(isn + 1).with_result(SegmentArrives::Result::OK));
test.execute(ExpectAckno{WrappingInt32{isn + 2}});
test.execute(ExpectUnassembledBytes{0});
test.execute(ExpectBytes{""});
test.execute(ExpectTotalAssembledBytes{0});
test.execute(ExpectState{TCPReceiverStateSummary::FIN_RECV});
}
{
uint32_t isn = uniform_int_distribution<uint32_t>{0, UINT32_MAX}(rd);
TCPReceiverTestHarness test{4000};
test.execute(ExpectState{TCPReceiverStateSummary::LISTEN});
test.execute(SegmentArrives{}.with_syn().with_seqno(isn + 0).with_result(SegmentArrives::Result::OK));
test.execute(ExpectState{TCPReceiverStateSummary::SYN_RECV});
test.execute(
SegmentArrives{}.with_fin().with_seqno(isn + 1).with_data("a").with_result(SegmentArrives::Result::OK));
test.execute(ExpectState{TCPReceiverStateSummary::FIN_RECV});
test.execute(ExpectAckno{WrappingInt32{isn + 3}});
test.execute(ExpectUnassembledBytes{0});
test.execute(ExpectBytes{"a"});
test.execute(ExpectTotalAssembledBytes{1});
test.execute(ExpectState{TCPReceiverStateSummary::FIN_RECV});
}
} catch (const exception &e) {
cerr << e.what() << endl;
return 1;
}
return EXIT_SUCCESS;
}
#include "receiver_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();
{
TCPReceiverTestHarness test{4000};
test.execute(ExpectWindow{4000});
test.execute(ExpectAckno{std::optional<WrappingInt32>{}});
test.execute(ExpectUnassembledBytes{0});
test.execute(ExpectTotalAssembledBytes{0});
test.execute(SegmentArrives{}.with_syn().with_seqno(0).with_result(SegmentArrives::Result::OK));
test.execute(ExpectAckno{WrappingInt32{1}});
test.execute(ExpectUnassembledBytes{0});
test.execute(ExpectTotalAssembledBytes{0});
}
{
TCPReceiverTestHarness test{5435};
test.execute(ExpectAckno{std::optional<WrappingInt32>{}});
test.execute(ExpectUnassembledBytes{0});
test.execute(ExpectTotalAssembledBytes{0});
test.execute(SegmentArrives{}.with_syn().with_seqno(89347598).with_result(SegmentArrives::Result::OK));
test.execute(ExpectAckno{WrappingInt32{89347599}});
test.execute(ExpectUnassembledBytes{0});
test.execute(ExpectTotalAssembledBytes{0});
}
{
TCPReceiverTestHarness test{5435};
test.execute(ExpectAckno{std::optional<WrappingInt32>{}});
test.execute(ExpectUnassembledBytes{0});
test.execute(ExpectTotalAssembledBytes{0});
test.execute(SegmentArrives{}.with_seqno(893475).with_result(SegmentArrives::Result::NOT_SYN));
test.execute(ExpectAckno{std::optional<WrappingInt32>{}});
test.execute(ExpectUnassembledBytes{0});
test.execute(ExpectTotalAssembledBytes{0});
}
{
TCPReceiverTestHarness test{5435};
test.execute(ExpectAckno{std::optional<WrappingInt32>{}});
test.execute(ExpectUnassembledBytes{0});
test.execute(ExpectTotalAssembledBytes{0});
test.execute(SegmentArrives{}.with_ack(0).with_fin().with_seqno(893475).with_result(
SegmentArrives::Result::NOT_SYN));
test.execute(ExpectAckno{std::optional<WrappingInt32>{}});
test.execute(ExpectUnassembledBytes{0});
test.execute(ExpectTotalAssembledBytes{0});
}
{
TCPReceiverTestHarness test{5435};
test.execute(ExpectAckno{std::optional<WrappingInt32>{}});
test.execute(ExpectUnassembledBytes{0});
test.execute(ExpectTotalAssembledBytes{0});
test.execute(SegmentArrives{}.with_ack(0).with_fin().with_seqno(893475).with_result(
SegmentArrives::Result::NOT_SYN));
test.execute(ExpectAckno{std::optional<WrappingInt32>{}});
test.execute(ExpectUnassembledBytes{0});
test.execute(ExpectTotalAssembledBytes{0});
test.execute(SegmentArrives{}.with_syn().with_seqno(89347598).with_result(SegmentArrives::Result::OK));
test.execute(ExpectAckno{WrappingInt32{89347599}});
test.execute(ExpectUnassembledBytes{0});
test.execute(ExpectTotalAssembledBytes{0});
}
{
TCPReceiverTestHarness test{4000};
test.execute(SegmentArrives{}.with_syn().with_seqno(5).with_fin().with_result(SegmentArrives::Result::OK));
test.execute(ExpectState{TCPReceiverStateSummary::FIN_RECV});
test.execute(ExpectAckno{WrappingInt32{7}});
test.execute(ExpectUnassembledBytes{0});
test.execute(ExpectTotalAssembledBytes{0});
}
{
// Window overflow
size_t cap = static_cast<size_t>(UINT16_MAX) + 5;
TCPReceiverTestHarness test{cap};
test.execute(ExpectWindow{cap});
}
} catch (const exception &e) {
cerr << e.what() << endl;
return 1;
}
return EXIT_SUCCESS;
}
#include "receiver_harness.hh"
#include "util.hh"
#include "wrapping_integers.hh"
#include <cstdint>
#include <cstdlib>
#include <exception>
#include <iostream>
#include <optional>
#include <random>
#include <stdexcept>
#include <string>
using namespace std;
int main() {
try {
auto rd = get_random_generator();
// An in-window, but later segment
{
uint32_t isn = uniform_int_distribution<uint32_t>{0, UINT32_MAX}(rd);
TCPReceiverTestHarness test{2358};
test.execute(SegmentArrives{}.with_syn().with_seqno(isn).with_result(SegmentArrives::Result::OK));
test.execute(ExpectAckno{WrappingInt32{isn + 1}});
test.execute(
SegmentArrives{}.with_seqno(isn + 10).with_data("abcd").with_result(SegmentArrives::Result::OK));
test.execute(ExpectAckno{WrappingInt32{isn + 1}});
test.execute(ExpectBytes{""});
test.execute(ExpectUnassembledBytes{4});
test.execute(ExpectTotalAssembledBytes{0});
}
// An in-window, but later segment, then the hole is filled
{
uint32_t isn = uniform_int_distribution<uint32_t>{0, UINT32_MAX}(rd);
TCPReceiverTestHarness test{2358};
test.execute(SegmentArrives{}.with_syn().with_seqno(isn).with_result(SegmentArrives::Result::OK));
test.execute(ExpectAckno{WrappingInt32{isn + 1}});
test.execute(
SegmentArrives{}.with_seqno(isn + 5).with_data("efgh").with_result(SegmentArrives::Result::OK));
test.execute(ExpectAckno{WrappingInt32{isn + 1}});
test.execute(ExpectBytes{""});
test.execute(ExpectUnassembledBytes{4});
test.execute(ExpectTotalAssembledBytes{0});
test.execute(
SegmentArrives{}.with_seqno(isn + 1).with_data("abcd").with_result(SegmentArrives::Result::OK));
test.execute(ExpectAckno{WrappingInt32{isn + 9}});
test.execute(ExpectBytes{"abcdefgh"});
test.execute(ExpectUnassembledBytes{0});
test.execute(ExpectTotalAssembledBytes{8});
}
// An in-window, but later segment, then the hole is filled, bit by bit
{
uint32_t isn = uniform_int_distribution<uint32_t>{0, UINT32_MAX}(rd);
TCPReceiverTestHarness test{2358};
test.execute(SegmentArrives{}.with_syn().with_seqno(isn).with_result(SegmentArrives::Result::OK));
test.execute(ExpectAckno{WrappingInt32{isn + 1}});
test.execute(
SegmentArrives{}.with_seqno(isn + 5).with_data("efgh").with_result(SegmentArrives::Result::OK));
test.execute(ExpectAckno{WrappingInt32{isn + 1}});
test.execute(ExpectBytes{""});
test.execute(ExpectUnassembledBytes{4});
test.execute(ExpectTotalAssembledBytes{0});
test.execute(SegmentArrives{}.with_seqno(isn + 1).with_data("ab").with_result(SegmentArrives::Result::OK));
test.execute(ExpectAckno{WrappingInt32{isn + 3}});
test.execute(ExpectBytes{"ab"});
test.execute(ExpectUnassembledBytes{4});
test.execute(ExpectTotalAssembledBytes{2});
test.execute(SegmentArrives{}.with_seqno(isn + 3).with_data("cd").with_result(SegmentArrives::Result::OK));
test.execute(ExpectAckno{WrappingInt32{isn + 9}});
test.execute(ExpectBytes{"cdefgh"});
test.execute(ExpectUnassembledBytes{0});
test.execute(ExpectTotalAssembledBytes{8});
}
// Many gaps, then filled bit by bit.
{
uint32_t isn = uniform_int_distribution<uint32_t>{0, UINT32_MAX}(rd);
TCPReceiverTestHarness test{2358};
test.execute(SegmentArrives{}.with_syn().with_seqno(isn).with_result(SegmentArrives::Result::OK));
test.execute(ExpectAckno{WrappingInt32{isn + 1}});
test.execute(SegmentArrives{}.with_seqno(isn + 5).with_data("e").with_result(SegmentArrives::Result::OK));
test.execute(ExpectAckno{WrappingInt32{isn + 1}});
test.execute(ExpectBytes{""});
test.execute(ExpectUnassembledBytes{1});
test.execute(ExpectTotalAssembledBytes{0});
test.execute(SegmentArrives{}.with_seqno(isn + 7).with_data("g").with_result(SegmentArrives::Result::OK));
test.execute(ExpectAckno{WrappingInt32{isn + 1}});
test.execute(ExpectBytes{""});
test.execute(ExpectUnassembledBytes{2});
test.execute(ExpectTotalAssembledBytes{0});
test.execute(SegmentArrives{}.with_seqno(isn + 3).with_data("c").with_result(SegmentArrives::Result::OK));
test.execute(ExpectAckno{WrappingInt32{isn + 1}});
test.execute(ExpectBytes{""});
test.execute(ExpectUnassembledBytes{3});
test.execute(ExpectTotalAssembledBytes{0});
test.execute(SegmentArrives{}.with_seqno(isn + 1).with_data("ab").with_result(SegmentArrives::Result::OK));
test.execute(ExpectAckno{WrappingInt32{isn + 4}});
test.execute(ExpectBytes{"abc"});
test.execute(ExpectUnassembledBytes{2});
test.execute(ExpectTotalAssembledBytes{3});
test.execute(SegmentArrives{}.with_seqno(isn + 6).with_data("f").with_result(SegmentArrives::Result::OK));
test.execute(ExpectUnassembledBytes{3});
test.execute(ExpectTotalAssembledBytes{3});
test.execute(ExpectBytes{""});
test.execute(SegmentArrives{}.with_seqno(isn + 4).with_data("d").with_result(SegmentArrives::Result::OK));
test.execute(ExpectUnassembledBytes{0});
test.execute(ExpectTotalAssembledBytes{7});
test.execute(ExpectBytes{"defg"});
}
// Many gaps, then subsumed
{
uint32_t isn = uniform_int_distribution<uint32_t>{0, UINT32_MAX}(rd);
TCPReceiverTestHarness test{2358};
test.execute(SegmentArrives{}.with_syn().with_seqno(isn).with_result(SegmentArrives::Result::OK));
test.execute(ExpectAckno{WrappingInt32{isn + 1}});
test.execute(SegmentArrives{}.with_seqno(isn + 5).with_data("e").with_result(SegmentArrives::Result::OK));
test.execute(ExpectAckno{WrappingInt32{isn + 1}});
test.execute(ExpectBytes{""});
test.execute(ExpectUnassembledBytes{1});
test.execute(ExpectTotalAssembledBytes{0});
test.execute(SegmentArrives{}.with_seqno(isn + 7).with_data("g").with_result(SegmentArrives::Result::OK));
test.execute(ExpectAckno{WrappingInt32{isn + 1}});
test.execute(ExpectBytes{""});
test.execute(ExpectUnassembledBytes{2});
test.execute(ExpectTotalAssembledBytes{0});
test.execute(SegmentArrives{}.with_seqno(isn + 3).with_data("c").with_result(SegmentArrives::Result::OK));
test.execute(ExpectAckno{WrappingInt32{isn + 1}});
test.execute(ExpectBytes{""});
test.execute(ExpectUnassembledBytes{3});
test.execute(ExpectTotalAssembledBytes{0});
test.execute(
SegmentArrives{}.with_seqno(isn + 1).with_data("abcdefgh").with_result(SegmentArrives::Result::OK));
test.execute(ExpectAckno{WrappingInt32{isn + 9}});
test.execute(ExpectBytes{"abcdefgh"});
test.execute(ExpectUnassembledBytes{0});
test.execute(ExpectTotalAssembledBytes{8});
}
} catch (const exception &e) {
cerr << e.what() << endl;
return 1;
}
return EXIT_SUCCESS;
}
#include "receiver_harness.hh"
#include "util.hh"
#include "wrapping_integers.hh"
#include <cstdint>
#include <cstdlib>
#include <exception>
#include <iostream>
#include <optional>
#include <random>
#include <stdexcept>
#include <string>
using namespace std;
int main() {
try {
auto rd = get_random_generator();
{
TCPReceiverTestHarness test{4000};
test.execute(SegmentArrives{}.with_syn().with_seqno(0).with_result(SegmentArrives::Result::OK));
test.execute(SegmentArrives{}.with_seqno(1).with_data("abcd").with_result(SegmentArrives::Result::OK));
test.execute(ExpectAckno{WrappingInt32{5}});
test.execute(ExpectBytes{"abcd"});
test.execute(ExpectUnassembledBytes{0});
test.execute(ExpectTotalAssembledBytes{4});
}
{
uint32_t isn = 384678;
TCPReceiverTestHarness test{4000};
test.execute(SegmentArrives{}.with_syn().with_seqno(isn).with_result(SegmentArrives::Result::OK));
test.execute(
SegmentArrives{}.with_seqno(isn + 1).with_data("abcd").with_result(SegmentArrives::Result::OK));
test.execute(ExpectAckno{WrappingInt32{isn + 5}});
test.execute(ExpectUnassembledBytes{0});
test.execute(ExpectTotalAssembledBytes{4});
test.execute(ExpectBytes{"abcd"});
test.execute(
SegmentArrives{}.with_seqno(isn + 5).with_data("efgh").with_result(SegmentArrives::Result::OK));
test.execute(ExpectAckno{WrappingInt32{isn + 9}});
test.execute(ExpectUnassembledBytes{0});
test.execute(ExpectTotalAssembledBytes{8});
test.execute(ExpectBytes{"efgh"});
}
{
uint32_t isn = 5;
TCPReceiverTestHarness test{4000};
test.execute(SegmentArrives{}.with_syn().with_seqno(isn).with_result(SegmentArrives::Result::OK));
test.execute(
SegmentArrives{}.with_seqno(isn + 1).with_data("abcd").with_result(SegmentArrives::Result::OK));
test.execute(ExpectAckno{WrappingInt32{isn + 5}});
test.execute(ExpectUnassembledBytes{0});
test.execute(ExpectTotalAssembledBytes{4});
test.execute(
SegmentArrives{}.with_seqno(isn + 5).with_data("efgh").with_result(SegmentArrives::Result::OK));
test.execute(ExpectAckno{WrappingInt32{isn + 9}});
test.execute(ExpectUnassembledBytes{0});
test.execute(ExpectTotalAssembledBytes{8});
test.execute(ExpectBytes{"abcdefgh"});
}
{
TCPReceiverTestHarness test{4000};
test.execute(SegmentArrives{}.with_syn().with_seqno(0).with_result(SegmentArrives::Result::OK));
test.execute(SegmentArrives{}.with_seqno(1).with_data("abcd").with_result(SegmentArrives::Result::OK));
test.execute(
SegmentArrives{}.with_seqno(1).with_data("efgh").with_result(SegmentArrives::Result::OUT_OF_WINDOW));
test.execute(
SegmentArrives{}.with_seqno(4005).with_data("efgh").with_result(SegmentArrives::Result::OUT_OF_WINDOW));
}
// Many (arrive/read)s
{
TCPReceiverTestHarness test{4000};
uint32_t max_block_size = 10;
uint32_t n_rounds = 10000;
uint32_t isn = 893472;
size_t bytes_sent = 0;
test.execute(SegmentArrives{}.with_syn().with_seqno(isn).with_result(SegmentArrives::Result::OK));
for (uint32_t i = 0; i < n_rounds; ++i) {
string data;
uint32_t block_size = uniform_int_distribution<uint32_t>{1, max_block_size}(rd);
for (uint8_t j = 0; j < block_size; ++j) {
uint8_t c = 'a' + ((i + j) % 26);
data.push_back(c);
}
test.execute(ExpectAckno{WrappingInt32{isn + uint32_t(bytes_sent) + 1}});
test.execute(ExpectTotalAssembledBytes{bytes_sent});
test.execute(SegmentArrives{}
.with_seqno(isn + bytes_sent + 1)
.with_data(data)
.with_result(SegmentArrives::Result::OK));
bytes_sent += block_size;
test.execute(ExpectBytes{std::move(data)});
}
}
// Many arrives, one read
{
uint32_t max_block_size = 10;
uint32_t n_rounds = 100;
TCPReceiverTestHarness test{uint16_t(max_block_size * n_rounds)};
uint32_t isn = 238;
size_t bytes_sent = 0;
test.execute(SegmentArrives{}.with_syn().with_seqno(isn).with_result(SegmentArrives::Result::OK));
string all_data;
for (uint32_t i = 0; i < n_rounds; ++i) {
string data;
uint32_t block_size = uniform_int_distribution<uint32_t>{1, max_block_size}(rd);
for (uint8_t j = 0; j < block_size; ++j) {
uint8_t c = 'a' + ((i + j) % 26);
data.push_back(c);
all_data.push_back(c);
}
test.execute(ExpectAckno{WrappingInt32{isn + uint32_t(bytes_sent) + 1}});
test.execute(ExpectTotalAssembledBytes{bytes_sent});
test.execute(SegmentArrives{}
.with_seqno(isn + bytes_sent + 1)
.with_data(data)
.with_result(SegmentArrives::Result::OK));
bytes_sent += block_size;
}
test.execute(ExpectBytes{std::move(all_data)});
}
} catch (const exception &e) {
cerr << e.what() << endl;
return 1;
}
return EXIT_SUCCESS;
}
#include "receiver_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();
{
// Window size decreases appropriately
size_t cap = 4000;
uint32_t isn = 23452;
TCPReceiverTestHarness test{cap};
test.execute(SegmentArrives{}.with_syn().with_seqno(isn).with_result(SegmentArrives::Result::OK));
test.execute(ExpectAckno{WrappingInt32{isn + 1}});
test.execute(ExpectWindow{cap});
test.execute(
SegmentArrives{}.with_seqno(isn + 1).with_data("abcd").with_result(SegmentArrives::Result::OK));
test.execute(ExpectAckno{WrappingInt32{isn + 5}});
test.execute(ExpectWindow{cap - 4});
test.execute(
SegmentArrives{}.with_seqno(isn + 9).with_data("ijkl").with_result(SegmentArrives::Result::OK));
test.execute(ExpectAckno{WrappingInt32{isn + 5}});
test.execute(ExpectWindow{cap - 4});
test.execute(
SegmentArrives{}.with_seqno(isn + 5).with_data("efgh").with_result(SegmentArrives::Result::OK));
test.execute(ExpectAckno{WrappingInt32{isn + 13}});
test.execute(ExpectWindow{cap - 12});
}
{
// Window size expands upon read
size_t cap = 4000;
uint32_t isn = 23452;
TCPReceiverTestHarness test{cap};
test.execute(SegmentArrives{}.with_syn().with_seqno(isn).with_result(SegmentArrives::Result::OK));
test.execute(ExpectAckno{WrappingInt32{isn + 1}});
test.execute(ExpectWindow{cap});
test.execute(
SegmentArrives{}.with_seqno(isn + 1).with_data("abcd").with_result(SegmentArrives::Result::OK));
test.execute(ExpectAckno{WrappingInt32{isn + 5}});
test.execute(ExpectWindow{cap - 4});
test.execute(ExpectBytes{"abcd"});
test.execute(ExpectAckno{WrappingInt32{isn + 5}});
test.execute(ExpectWindow{cap});
}
{
// high-seqno segment is rejected
size_t cap = 2;
uint32_t isn = 23452;
TCPReceiverTestHarness test{cap};
test.execute(SegmentArrives{}.with_syn().with_seqno(isn).with_result(SegmentArrives::Result::OK));
test.execute(ExpectAckno{WrappingInt32{isn + 1}});
test.execute(ExpectWindow{cap});
test.execute(ExpectTotalAssembledBytes{0});
test.execute(SegmentArrives{}.with_seqno(isn + 3).with_data("cd").with_result(
SegmentArrives::Result::OUT_OF_WINDOW));
test.execute(ExpectAckno{WrappingInt32{isn + 1}});
test.execute(ExpectWindow{cap});
test.execute(ExpectTotalAssembledBytes{0});
}
{
// almost-high-seqno segment is accepted, but only some bytes are kept
size_t cap = 2;
uint32_t isn = 23452;
TCPReceiverTestHarness test{cap};
test.execute(SegmentArrives{}.with_syn().with_seqno(isn).with_result(SegmentArrives::Result::OK));
test.execute(SegmentArrives{}.with_seqno(isn + 2).with_data("bc").with_result(SegmentArrives::Result::OK));
test.execute(ExpectTotalAssembledBytes{0});
test.execute(SegmentArrives{}.with_seqno(isn + 1).with_data("a").with_result(SegmentArrives::Result::OK));
test.execute(ExpectAckno{WrappingInt32{isn + 3}});
test.execute(ExpectWindow{0});
test.execute(ExpectTotalAssembledBytes{2});
test.execute(ExpectBytes{"ab"});
test.execute(ExpectWindow{2});
}
{
// low-seqno segment is rejected
size_t cap = 4;
uint32_t isn = 294058;
TCPReceiverTestHarness test{cap};
test.execute(SegmentArrives{}.with_syn().with_seqno(isn).with_result(SegmentArrives::Result::OK));
test.execute(SegmentArrives{}.with_data("ab").with_seqno(isn + 1).with_result(SegmentArrives::Result::OK));
test.execute(ExpectTotalAssembledBytes{2});
test.execute(ExpectWindow{cap - 2});
test.execute(SegmentArrives{}.with_data("ab").with_seqno(isn + 1).with_result(
SegmentArrives::Result::OUT_OF_WINDOW));
test.execute(ExpectTotalAssembledBytes{2});
}
{
// almost-low-seqno segment is accepted
size_t cap = 4;
uint32_t isn = 294058;
TCPReceiverTestHarness test{cap};
test.execute(SegmentArrives{}.with_syn().with_seqno(isn).with_result(SegmentArrives::Result::OK));
test.execute(SegmentArrives{}.with_data("ab").with_seqno(isn + 1).with_result(SegmentArrives::Result::OK));
test.execute(ExpectTotalAssembledBytes{2});
test.execute(ExpectWindow{cap - 2});
test.execute(SegmentArrives{}.with_data("abc").with_seqno(isn + 1).with_result(SegmentArrives::Result::OK));
test.execute(ExpectTotalAssembledBytes{3});
test.execute(ExpectWindow{cap - 3});
}
{
// second SYN is rejected
size_t cap = 2;
uint32_t isn = 23452;
TCPReceiverTestHarness test{cap};
test.execute(SegmentArrives{}.with_syn().with_seqno(isn).with_result(SegmentArrives::Result::OK));
test.execute(
SegmentArrives{}.with_syn().with_seqno(isn).with_result(SegmentArrives::Result::OUT_OF_WINDOW));
test.execute(ExpectWindow{cap});
test.execute(ExpectTotalAssembledBytes{0});
}
{
// second FIN is rejected
size_t cap = 2;
uint32_t isn = 23452;
TCPReceiverTestHarness test{cap};
test.execute(SegmentArrives{}.with_syn().with_seqno(isn).with_result(SegmentArrives::Result::OK));
test.execute(SegmentArrives{}.with_fin().with_seqno(isn + 1).with_result(SegmentArrives::Result::OK));
test.execute(
SegmentArrives{}.with_fin().with_seqno(isn + 1).with_result(SegmentArrives::Result::OUT_OF_WINDOW));
test.execute(ExpectWindow{cap});
test.execute(ExpectTotalAssembledBytes{0});
}
{
// Segment overflowing the window on left side is acceptable.
size_t cap = 4;
uint32_t isn = 23452;
TCPReceiverTestHarness test{cap};
test.execute(SegmentArrives{}.with_syn().with_seqno(isn).with_result(SegmentArrives::Result::OK));
test.execute(SegmentArrives{}.with_seqno(isn + 1).with_data("ab").with_result(SegmentArrives::Result::OK));
test.execute(
SegmentArrives{}.with_seqno(isn + 3).with_data("cdef").with_result(SegmentArrives::Result::OK));
}
{
// Segment matching the window is acceptable.
size_t cap = 4;
uint32_t isn = 23452;
TCPReceiverTestHarness test{cap};
test.execute(SegmentArrives{}.with_syn().with_seqno(isn).with_result(SegmentArrives::Result::OK));
test.execute(SegmentArrives{}.with_seqno(isn + 1).with_data("ab").with_result(SegmentArrives::Result::OK));
test.execute(SegmentArrives{}.with_seqno(isn + 3).with_data("cd").with_result(SegmentArrives::Result::OK));
}
} catch (const exception &e) {
cerr << e.what() << endl;
return 1;
}
return EXIT_SUCCESS;
}
#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{});
}
{
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;
}
#include "sender_harness.hh"
#include "tcp_config.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{"FIN sent test", cfg};
test.execute(ExpectSegment{}.with_no_flags().with_syn(true).with_payload_size(0).with_seqno(isn));
test.execute(AckReceived{WrappingInt32{isn + 1}});
test.execute(ExpectState{TCPSenderStateSummary::SYN_ACKED});
test.execute(Close{});
test.execute(ExpectState{TCPSenderStateSummary::FIN_SENT});
test.execute(ExpectBytesInFlight{1});
test.execute(ExpectSegment{}.with_fin(true).with_seqno(isn + 1));
test.execute(ExpectNoSegment{});
}
{
TCPConfig cfg;
WrappingInt32 isn(rd());
cfg.fixed_isn = isn;
TCPSenderTestHarness test{"FIN acked test", cfg};
test.execute(ExpectSegment{}.with_no_flags().with_syn(true).with_payload_size(0).with_seqno(isn));
test.execute(AckReceived{WrappingInt32{isn + 1}});
test.execute(ExpectState{TCPSenderStateSummary::SYN_ACKED});
test.execute(Close{});
test.execute(ExpectState{TCPSenderStateSummary::FIN_SENT});
test.execute(ExpectSegment{}.with_fin(true).with_seqno(isn + 1));
test.execute(AckReceived{WrappingInt32{isn + 2}});
test.execute(ExpectState{TCPSenderStateSummary::FIN_ACKED});
test.execute(ExpectBytesInFlight{0});
test.execute(ExpectNoSegment{});
}
{
TCPConfig cfg;
WrappingInt32 isn(rd());
cfg.fixed_isn = isn;
TCPSenderTestHarness test{"FIN not acked test", cfg};
test.execute(ExpectSegment{}.with_no_flags().with_syn(true).with_payload_size(0).with_seqno(isn));
test.execute(AckReceived{WrappingInt32{isn + 1}});
test.execute(ExpectState{TCPSenderStateSummary::SYN_ACKED});
test.execute(Close{});
test.execute(ExpectState{TCPSenderStateSummary::FIN_SENT});
test.execute(ExpectSegment{}.with_fin(true).with_seqno(isn + 1));
test.execute(AckReceived{WrappingInt32{isn + 1}});
test.execute(ExpectState{TCPSenderStateSummary::FIN_SENT});
test.execute(ExpectBytesInFlight{1});
test.execute(ExpectNoSegment{});
}
{
TCPConfig cfg;
WrappingInt32 isn(rd());
cfg.fixed_isn = isn;
TCPSenderTestHarness test{"FIN retx test", cfg};
test.execute(ExpectSegment{}.with_no_flags().with_syn(true).with_payload_size(0).with_seqno(isn));
test.execute(AckReceived{WrappingInt32{isn + 1}});
test.execute(ExpectState{TCPSenderStateSummary::SYN_ACKED});
test.execute(Close{});
test.execute(ExpectState{TCPSenderStateSummary::FIN_SENT});
test.execute(ExpectSegment{}.with_fin(true).with_seqno(isn + 1));
test.execute(AckReceived{WrappingInt32{isn + 1}});
test.execute(ExpectState{TCPSenderStateSummary::FIN_SENT});
test.execute(ExpectBytesInFlight{1});
test.execute(ExpectNoSegment{});
test.execute(Tick{TCPConfig::TIMEOUT_DFLT - 1});
test.execute(ExpectState{TCPSenderStateSummary::FIN_SENT});
test.execute(ExpectBytesInFlight{1});
test.execute(ExpectNoSegment{});
test.execute(Tick{1});
test.execute(ExpectState{TCPSenderStateSummary::FIN_SENT});
test.execute(ExpectBytesInFlight{1});
test.execute(ExpectSegment{}.with_fin(true).with_seqno(isn + 1));
test.execute(ExpectNoSegment{});
test.execute(Tick{1});
test.execute(ExpectState{TCPSenderStateSummary::FIN_SENT});
test.execute(ExpectBytesInFlight{1});
test.execute(ExpectNoSegment{});
test.execute(AckReceived{WrappingInt32{isn + 2}});
test.execute(ExpectState{TCPSenderStateSummary::FIN_ACKED});
test.execute(ExpectBytesInFlight{0});
test.execute(ExpectNoSegment{});
}
} catch (const exception &e) {
cerr << e.what() << endl;
return 1;
}
return EXIT_SUCCESS;
}
#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{"SYN sent test", cfg};
test.execute(ExpectState{TCPSenderStateSummary::SYN_SENT});
test.execute(ExpectSegment{}.with_no_flags().with_syn(true).with_payload_size(0).with_seqno(isn));
test.execute(ExpectBytesInFlight{1});
}
{
TCPConfig cfg;
WrappingInt32 isn(rd());
cfg.fixed_isn = isn;
TCPSenderTestHarness test{"SYN acked test", cfg};
test.execute(ExpectState{TCPSenderStateSummary::SYN_SENT});
test.execute(ExpectSegment{}.with_no_flags().with_syn(true).with_payload_size(0).with_seqno(isn));
test.execute(ExpectBytesInFlight{1});
test.execute(AckReceived{WrappingInt32{isn + 1}});
test.execute(ExpectState{TCPSenderStateSummary::SYN_ACKED});
test.execute(ExpectNoSegment{});
test.execute(ExpectBytesInFlight{0});
}
{
TCPConfig cfg;
WrappingInt32 isn(rd());
cfg.fixed_isn = isn;
TCPSenderTestHarness test{"SYN -> wrong ack test", cfg};
test.execute(ExpectState{TCPSenderStateSummary::SYN_SENT});
test.execute(ExpectSegment{}.with_no_flags().with_syn(true).with_payload_size(0).with_seqno(isn));
test.execute(ExpectBytesInFlight{1});
test.execute(AckReceived{WrappingInt32{isn}});
test.execute(ExpectState{TCPSenderStateSummary::SYN_SENT});
test.execute(ExpectNoSegment{});
test.execute(ExpectBytesInFlight{1});
}
{
TCPConfig cfg;
WrappingInt32 isn(rd());
cfg.fixed_isn = isn;
TCPSenderTestHarness test{"SYN acked, data", cfg};
test.execute(ExpectState{TCPSenderStateSummary::SYN_SENT});
test.execute(ExpectSegment{}.with_no_flags().with_syn(true).with_payload_size(0).with_seqno(isn));
test.execute(ExpectBytesInFlight{1});
test.execute(AckReceived{WrappingInt32{isn + 1}});
test.execute(ExpectState{TCPSenderStateSummary::SYN_ACKED});
test.execute(ExpectNoSegment{});
test.execute(ExpectBytesInFlight{0});
test.execute(WriteBytes{"abcdefgh"});
test.execute(Tick{1});
test.execute(ExpectState{TCPSenderStateSummary::SYN_ACKED});
test.execute(ExpectSegment{}.with_seqno(isn + 1).with_data("abcdefgh"));
test.execute(ExpectBytesInFlight{8});
test.execute(AckReceived{WrappingInt32{isn + 9}});
test.execute(ExpectState{TCPSenderStateSummary::SYN_ACKED});
test.execute(ExpectNoSegment{});
test.execute(ExpectBytesInFlight{0});
test.execute(ExpectSeqno{WrappingInt32{isn + 9}});
}
} catch (const exception &e) {
cerr << e.what() << endl;
return 1;
}
return EXIT_SUCCESS;
}
#include "send_equivalence_checker.hh"
#include "tcp_segment.hh"
#include <algorithm>
#include <exception>
#include <iostream>
#include <sstream>
using namespace std;
void check_equivalent_segments(const TCPSegment &a, const TCPSegment &b) {
if (not(a.header() == b.header())) {
cerr << a.header().to_string() << endl;
cerr << b.header().to_string() << endl;
stringstream s{};
s << a.header().summary() << " with " << a.payload().size() << " bytes != " << b.header().summary() << " with "
<< b.payload().size() << " bytes";
throw runtime_error(s.str());
}
if (a.payload().str() != b.payload().str()) {
stringstream s{};
s << a.header().summary() << " with " << a.payload().size() << " bytes != " << b.header().summary() << " with "
<< b.payload().size() << " bytes";
throw runtime_error("unequal payloads: " + s.str());
}
}
void SendEquivalenceChecker::submit_a(TCPSegment &seg) {
if (not bs.empty()) {
check_equivalent_segments(seg, bs.front());
bs.pop_front();
} else {
TCPSegment cp;
cp.parse(seg.serialize());
as.emplace_back(move(cp));
}
}
void SendEquivalenceChecker::submit_b(TCPSegment &seg) {
if (not as.empty()) {
check_equivalent_segments(as.front(), seg);
as.pop_front();
} else {
TCPSegment cp;
cp.parse(seg.serialize());
bs.emplace_back(move(cp));
}
}
void SendEquivalenceChecker::check_empty() const {
if (not as.empty()) {
stringstream s{};
s << as.size() << " unmatched packets from standard" << endl;
for (const TCPSegment &a : as) {
s << " - " << a.header().summary() << " with " << a.payload().size() << " bytes\n";
}
throw runtime_error(s.str());
}
if (not bs.empty()) {
stringstream s{};
s << bs.size() << " unmatched packets from factored" << endl;
for (const TCPSegment &a : bs) {
s << " - " << a.header().summary() << " with " << a.payload().size() << " bytes\n";
}
throw runtime_error(s.str());
}
}
#ifndef SPONGE_LIBSPONGE_SEND_EQUIVALENCE_CHECKER_HH
#define SPONGE_LIBSPONGE_SEND_EQUIVALENCE_CHECKER_HH
#include "tcp_segment.hh"
#include <deque>
#include <optional>
class SendEquivalenceChecker {
std::deque<TCPSegment> as{};
std::deque<TCPSegment> bs{};
public:
void submit_a(TCPSegment &seg);
void submit_b(TCPSegment &seg);
void check_empty() const;
};
#endif // SPONGE_LIBSPONGE_SEND_EQUIVALENCE_CHECKER_HH
#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());
uint16_t retx_timeout = uniform_int_distribution<uint16_t>{10, 10000}(rd);
cfg.fixed_isn = isn;
cfg.rt_timeout = retx_timeout;
TCPSenderTestHarness test{"Retx SYN twice at the right times, then ack", cfg};
test.execute(ExpectSegment{}.with_no_flags().with_syn(true).with_payload_size(0).with_seqno(isn));
test.execute(ExpectNoSegment{});
test.execute(ExpectState{TCPSenderStateSummary::SYN_SENT});
test.execute(Tick{retx_timeout - 1u});
test.execute(ExpectNoSegment{});
test.execute(Tick{1});
test.execute(ExpectSegment{}.with_no_flags().with_syn(true).with_payload_size(0).with_seqno(isn));
test.execute(ExpectState{TCPSenderStateSummary::SYN_SENT});
test.execute(ExpectBytesInFlight{1});
// Wait twice as long b/c exponential back-off
test.execute(Tick{2 * retx_timeout - 1u});
test.execute(ExpectNoSegment{});
test.execute(Tick{1});
test.execute(ExpectSegment{}.with_no_flags().with_syn(true).with_payload_size(0).with_seqno(isn));
test.execute(ExpectState{TCPSenderStateSummary::SYN_SENT});
test.execute(ExpectBytesInFlight{1});
test.execute(AckReceived{WrappingInt32{isn + 1}});
test.execute(ExpectState{TCPSenderStateSummary::SYN_ACKED});
test.execute(ExpectBytesInFlight{0});
}
{
TCPConfig cfg;
WrappingInt32 isn(rd());
uint16_t retx_timeout = uniform_int_distribution<uint16_t>{10, 10000}(rd);
cfg.fixed_isn = isn;
cfg.rt_timeout = retx_timeout;
TCPSenderTestHarness test{"Retx SYN until too many retransmissions", cfg};
test.execute(ExpectSegment{}.with_no_flags().with_syn(true).with_payload_size(0).with_seqno(isn));
test.execute(ExpectNoSegment{});
test.execute(ExpectState{TCPSenderStateSummary::SYN_SENT});
for (size_t attempt_no = 0; attempt_no < TCPConfig::MAX_RETX_ATTEMPTS; attempt_no++) {
test.execute(Tick{(retx_timeout << attempt_no) - 1u}.with_max_retx_exceeded(false));
test.execute(ExpectNoSegment{});
test.execute(Tick{1}.with_max_retx_exceeded(false));
test.execute(ExpectSegment{}.with_no_flags().with_syn(true).with_payload_size(0).with_seqno(isn));
test.execute(ExpectState{TCPSenderStateSummary::SYN_SENT});
test.execute(ExpectBytesInFlight{1});
}
test.execute(Tick{(retx_timeout << TCPConfig::MAX_RETX_ATTEMPTS) - 1u}.with_max_retx_exceeded(false));
test.execute(Tick{1}.with_max_retx_exceeded(true));
}
{
TCPConfig cfg;
WrappingInt32 isn(rd());
uint16_t retx_timeout = uniform_int_distribution<uint16_t>{10, 10000}(rd);
cfg.fixed_isn = isn;
cfg.rt_timeout = retx_timeout;
TCPSenderTestHarness test{"Send some data, the retx and succeed, then retx till limit", 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{"abcd"});
test.execute(ExpectSegment{}.with_payload_size(4));
test.execute(ExpectNoSegment{});
test.execute(AckReceived{WrappingInt32{isn + 5}});
test.execute(ExpectBytesInFlight{0});
test.execute(WriteBytes{"efgh"});
test.execute(ExpectSegment{}.with_payload_size(4));
test.execute(ExpectNoSegment{});
test.execute(Tick{retx_timeout}.with_max_retx_exceeded(false));
test.execute(ExpectSegment{}.with_payload_size(4));
test.execute(ExpectNoSegment{});
test.execute(AckReceived{WrappingInt32{isn + 9}});
test.execute(ExpectBytesInFlight{0});
test.execute(WriteBytes{"ijkl"});
test.execute(ExpectSegment{}.with_payload_size(4).with_seqno(isn + 9));
for (size_t attempt_no = 0; attempt_no < TCPConfig::MAX_RETX_ATTEMPTS; attempt_no++) {
test.execute(Tick{(retx_timeout << attempt_no) - 1u}.with_max_retx_exceeded(false));
test.execute(ExpectNoSegment{});
test.execute(Tick{1}.with_max_retx_exceeded(false));
test.execute(ExpectSegment{}.with_payload_size(4).with_seqno(isn + 9));
test.execute(ExpectState{TCPSenderStateSummary::SYN_ACKED});
test.execute(ExpectBytesInFlight{4});
}
test.execute(Tick{(retx_timeout << TCPConfig::MAX_RETX_ATTEMPTS) - 1u}.with_max_retx_exceeded(false));
test.execute(Tick{1}.with_max_retx_exceeded(true));
}
} catch (const exception &e) {
cerr << e.what() << endl;
return 1;
}
return EXIT_SUCCESS;
}
#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{"Three short writes", cfg};
test.execute(ExpectSegment{}.with_no_flags().with_syn(true).with_payload_size(0).with_seqno(isn));
test.execute(AckReceived{WrappingInt32{isn + 1}});
test.execute(ExpectState{TCPSenderStateSummary::SYN_ACKED});
test.execute(WriteBytes{"ab"});
test.execute(ExpectSegment{}.with_data("ab").with_seqno(isn + 1));
test.execute(WriteBytes{"cd"});
test.execute(ExpectSegment{}.with_data("cd").with_seqno(isn + 3));
test.execute(WriteBytes{"abcd"});
test.execute(ExpectSegment{}.with_data("abcd").with_seqno(isn + 5));
test.execute(ExpectSeqno{WrappingInt32{isn + 9}});
test.execute(ExpectBytesInFlight{8});
test.execute(ExpectState{TCPSenderStateSummary::SYN_ACKED});
}
{
TCPConfig cfg;
WrappingInt32 isn(rd());
cfg.fixed_isn = isn;
TCPSenderTestHarness test{"Many short writes, continuous acks", cfg};
test.execute(ExpectSegment{}.with_no_flags().with_syn(true).with_payload_size(0).with_seqno(isn));
test.execute(AckReceived{WrappingInt32{isn + 1}});
test.execute(ExpectState{TCPSenderStateSummary::SYN_ACKED});
uint32_t max_block_size = 10;
uint32_t n_rounds = 10000;
size_t bytes_sent = 0;
for (uint32_t i = 0; i < n_rounds; ++i) {
string data;
uint32_t block_size = uniform_int_distribution<uint32_t>{1, max_block_size}(rd);
for (uint8_t j = 0; j < block_size; ++j) {
uint8_t c = 'a' + ((i + j) % 26);
data.push_back(c);
}
test.execute(ExpectSeqno{WrappingInt32{isn + uint32_t(bytes_sent) + 1}});
test.execute(WriteBytes(string(data)));
bytes_sent += block_size;
test.execute(ExpectBytesInFlight{block_size});
test.execute(
ExpectSegment{}.with_seqno(isn + 1 + uint32_t(bytes_sent - block_size)).with_data(move(data)));
test.execute(ExpectNoSegment{});
test.execute(AckReceived{WrappingInt32{isn + 1 + uint32_t(bytes_sent)}});
}
}
{
TCPConfig cfg;
WrappingInt32 isn(rd());
cfg.fixed_isn = isn;
TCPSenderTestHarness test{"Many short writes, ack at end", cfg};
test.execute(ExpectSegment{}.with_no_flags().with_syn(true).with_payload_size(0).with_seqno(isn));
test.execute(AckReceived{WrappingInt32{isn + 1}}.with_win(65000));
test.execute(ExpectState{TCPSenderStateSummary::SYN_ACKED});
uint32_t max_block_size = 10;
uint32_t n_rounds = 1000;
size_t bytes_sent = 0;
for (uint32_t i = 0; i < n_rounds; ++i) {
string data;
uint32_t block_size = uniform_int_distribution<uint32_t>{1, max_block_size}(rd);
for (uint8_t j = 0; j < block_size; ++j) {
uint8_t c = 'a' + ((i + j) % 26);
data.push_back(c);
}
test.execute(ExpectSeqno{WrappingInt32{isn + uint32_t(bytes_sent) + 1}});
test.execute(WriteBytes(string(data)));
bytes_sent += block_size;
test.execute(ExpectBytesInFlight{bytes_sent});
test.execute(
ExpectSegment{}.with_seqno(isn + 1 + uint32_t(bytes_sent - block_size)).with_data(move(data)));
test.execute(ExpectNoSegment{});
}
test.execute(ExpectBytesInFlight{bytes_sent});
test.execute(AckReceived{WrappingInt32{isn + 1 + uint32_t(bytes_sent)}});
test.execute(ExpectBytesInFlight{0});
}
{
TCPConfig cfg;
WrappingInt32 isn(rd());
cfg.fixed_isn = isn;
TCPSenderTestHarness test{"Window filling", cfg};
test.execute(ExpectSegment{}.with_no_flags().with_syn(true).with_payload_size(0).with_seqno(isn));
test.execute(AckReceived{WrappingInt32{isn + 1}}.with_win(3));
test.execute(ExpectState{TCPSenderStateSummary::SYN_ACKED});
test.execute(WriteBytes("01234567"));
test.execute(ExpectBytesInFlight{3});
test.execute(ExpectSegment{}.with_data("012"));
test.execute(ExpectNoSegment{});
test.execute(ExpectSeqno{WrappingInt32{isn + 1 + 3}});
test.execute(AckReceived{WrappingInt32{isn + 1 + 3}}.with_win(3));
test.execute(ExpectBytesInFlight{3});
test.execute(ExpectSegment{}.with_data("345"));
test.execute(ExpectNoSegment{});
test.execute(ExpectSeqno{WrappingInt32{isn + 1 + 6}});
test.execute(AckReceived{WrappingInt32{isn + 1 + 6}}.with_win(3));
test.execute(ExpectBytesInFlight{2});
test.execute(ExpectSegment{}.with_data("67"));
test.execute(ExpectNoSegment{});
test.execute(ExpectSeqno{WrappingInt32{isn + 1 + 8}});
test.execute(AckReceived{WrappingInt32{isn + 1 + 8}}.with_win(3));
test.execute(ExpectBytesInFlight{0});
test.execute(ExpectNoSegment{});
}
{
TCPConfig cfg;
WrappingInt32 isn(rd());
cfg.fixed_isn = isn;
TCPSenderTestHarness test{"Immediate writes respect the window", cfg};
test.execute(ExpectSegment{}.with_no_flags().with_syn(true).with_payload_size(0).with_seqno(isn));
test.execute(AckReceived{WrappingInt32{isn + 1}}.with_win(3));
test.execute(ExpectState{TCPSenderStateSummary::SYN_ACKED});
test.execute(WriteBytes("01"));
test.execute(ExpectBytesInFlight{2});
test.execute(ExpectSegment{}.with_data("01"));
test.execute(ExpectNoSegment{});
test.execute(ExpectSeqno{WrappingInt32{isn + 1 + 2}});
test.execute(WriteBytes("23"));
test.execute(ExpectBytesInFlight{3});
test.execute(ExpectSegment{}.with_data("2"));
test.execute(ExpectNoSegment{});
test.execute(ExpectSeqno{WrappingInt32{isn + 1 + 3}});
}
} catch (const exception &e) {
cerr << e.what() << endl;
return 1;
}
return EXIT_SUCCESS;
}
#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{"Initial receiver advertised window is respected", cfg};
test.execute(ExpectSegment{}.with_no_flags().with_syn(true).with_payload_size(0).with_seqno(isn));
test.execute(AckReceived{WrappingInt32{isn + 1}}.with_win(4));
test.execute(ExpectNoSegment{});
test.execute(WriteBytes{"abcdefg"});
test.execute(ExpectSegment{}.with_no_flags().with_data("abcd"));
test.execute(ExpectNoSegment{});
}
{
TCPConfig cfg;
WrappingInt32 isn(rd());
cfg.fixed_isn = isn;
TCPSenderTestHarness test{"Immediate window is respected", cfg};
test.execute(ExpectSegment{}.with_no_flags().with_syn(true).with_payload_size(0).with_seqno(isn));
test.execute(AckReceived{WrappingInt32{isn + 1}}.with_win(6));
test.execute(ExpectNoSegment{});
test.execute(WriteBytes{"abcdefg"});
test.execute(ExpectSegment{}.with_no_flags().with_data("abcdef"));
test.execute(ExpectNoSegment{});
}
{
const size_t MIN_WIN = 5;
const size_t MAX_WIN = 100;
const size_t N_REPS = 1000;
for (size_t i = 0; i < N_REPS; ++i) {
size_t len = MIN_WIN + rd() % (MAX_WIN - MIN_WIN);
TCPConfig cfg;
WrappingInt32 isn(rd());
cfg.fixed_isn = isn;
TCPSenderTestHarness test{"Window " + to_string(i), cfg};
test.execute(ExpectSegment{}.with_no_flags().with_syn(true).with_payload_size(0).with_seqno(isn));
test.execute(AckReceived{WrappingInt32{isn + 1}}.with_win(len));
test.execute(ExpectNoSegment{});
test.execute(WriteBytes{string(2 * N_REPS, 'a')});
test.execute(ExpectSegment{}.with_no_flags().with_payload_size(len));
test.execute(ExpectNoSegment{});
}
}
{
TCPConfig cfg;
WrappingInt32 isn(rd());
cfg.fixed_isn = isn;
TCPSenderTestHarness test{"Window growth is exploited", cfg};
test.execute(ExpectSegment{}.with_no_flags().with_syn(true).with_payload_size(0).with_seqno(isn));
test.execute(AckReceived{WrappingInt32{isn + 1}}.with_win(4));
test.execute(ExpectNoSegment{});
test.execute(WriteBytes{"0123456789"});
test.execute(ExpectSegment{}.with_no_flags().with_data("0123"));
test.execute(AckReceived{WrappingInt32{isn + 5}}.with_win(5));
test.execute(ExpectSegment{}.with_no_flags().with_data("45678"));
test.execute(ExpectNoSegment{});
}
{
TCPConfig cfg;
WrappingInt32 isn(rd());
cfg.fixed_isn = isn;
TCPSenderTestHarness test{"FIN flag occupies space in window", cfg};
test.execute(ExpectSegment{}.with_no_flags().with_syn(true).with_payload_size(0).with_seqno(isn));
test.execute(AckReceived{WrappingInt32{isn + 1}}.with_win(7));
test.execute(ExpectNoSegment{});
test.execute(WriteBytes{"1234567"});
test.execute(Close{});
test.execute(ExpectSegment{}.with_no_flags().with_data("1234567"));
test.execute(ExpectNoSegment{}); // window is full
test.execute(AckReceived{WrappingInt32{isn + 8}}.with_win(1));
test.execute(ExpectSegment{}.with_fin(true).with_data(""));
test.execute(ExpectNoSegment{});
}
{
TCPConfig cfg;
WrappingInt32 isn(rd());
cfg.fixed_isn = isn;
TCPSenderTestHarness test{"FIN flag occupies space in window (part II)", cfg};
test.execute(ExpectSegment{}.with_no_flags().with_syn(true).with_payload_size(0).with_seqno(isn));
test.execute(AckReceived{WrappingInt32{isn + 1}}.with_win(7));
test.execute(ExpectNoSegment{});
test.execute(WriteBytes{"1234567"});
test.execute(Close{});
test.execute(ExpectSegment{}.with_no_flags().with_data("1234567"));
test.execute(ExpectNoSegment{}); // window is full
test.execute(AckReceived{WrappingInt32{isn + 1}}.with_win(8));
test.execute(ExpectSegment{}.with_fin(true).with_data(""));
test.execute(ExpectNoSegment{});
}
{
TCPConfig cfg;
WrappingInt32 isn(rd());
cfg.fixed_isn = isn;
TCPSenderTestHarness test{"Piggyback FIN in segment when space is available", cfg};
test.execute(ExpectSegment{}.with_no_flags().with_syn(true).with_payload_size(0).with_seqno(isn));
test.execute(AckReceived{WrappingInt32{isn + 1}}.with_win(3));
test.execute(ExpectNoSegment{});
test.execute(WriteBytes{"1234567"});
test.execute(Close{});
test.execute(ExpectSegment{}.with_no_flags().with_data("123"));
test.execute(ExpectNoSegment{}); // window is full
test.execute(AckReceived{WrappingInt32{isn + 1}}.with_win(8));
test.execute(ExpectSegment{}.with_fin(true).with_data("4567"));
test.execute(ExpectNoSegment{});
}
} catch (const exception &e) {
cerr << e.what() << endl;
return 1;
}
return EXIT_SUCCESS;
}
#ifndef SPONGE_SENDER_HARNESS_HH
#define SPONGE_SENDER_HARNESS_HH
#include "byte_stream.hh"
#include "string_conversions.hh"
#include "tcp_sender.hh"
#include "tcp_state.hh"
#include "util.hh"
#include "wrapping_integers.hh"
#include <algorithm>
#include <deque>
#include <exception>
#include <iostream>
#include <optional>
#include <sstream>
#include <string>
const unsigned int DEFAULT_TEST_WINDOW = 137;
struct SenderTestStep {
virtual operator std::string() const { return "SenderTestStep"; }
virtual void execute(TCPSender &, std::deque<TCPSegment> &) const {}
virtual ~SenderTestStep() {}
};
class SenderExpectationViolation : public std::runtime_error {
public:
SenderExpectationViolation(const std::string msg) : std::runtime_error(msg) {}
};
class SegmentExpectationViolation : public SenderExpectationViolation {
public:
SegmentExpectationViolation(const std::string &msg) : SenderExpectationViolation(msg) {}
static SegmentExpectationViolation violated_verb(const std::string &msg) {
return SegmentExpectationViolation{"The Sender should have produced a segment that " + msg +
", but it did not"};
}
template <typename T>
static SegmentExpectationViolation violated_field(const std::string &field_name,
const T &expected_value,
const T &actual_value) {
std::stringstream ss{};
ss << "The Sender produced a segment with `" << field_name << " = " << actual_value << "`, but " << field_name
<< " was expected to be `" << expected_value << "`";
return SegmentExpectationViolation{ss.str()};
}
};
struct SenderExpectation : public SenderTestStep {
operator std::string() const { return "Expectation: " + description(); }
virtual std::string description() const { return "description missing"; }
virtual void execute(TCPSender &, std::deque<TCPSegment> &) const {}
virtual ~SenderExpectation() {}
};
struct ExpectState : public SenderExpectation {
std::string _state;
ExpectState(const std::string &state) : _state(state) {}
std::string description() const { return "in state `" + _state + "`"; }
void execute(TCPSender &sender, std::deque<TCPSegment> &) const {
if (TCPState::state_summary(sender) != _state) {
throw SenderExpectationViolation("The TCPSender was in state `" + TCPState::state_summary(sender) +
"`, but it was expected to be in state `" + _state + "`");
}
}
};
struct ExpectSeqno : public SenderExpectation {
WrappingInt32 _seqno;
ExpectSeqno(WrappingInt32 seqno) : _seqno(seqno) {}
std::string description() const { return "next seqno " + std::to_string(_seqno.raw_value()); }
void execute(TCPSender &sender, std::deque<TCPSegment> &) const {
if (sender.next_seqno() != _seqno) {
std::string reported = std::to_string(sender.next_seqno().raw_value());
std::string expected = to_string(_seqno);
throw SenderExpectationViolation("The TCPSender reported that the next seqno is `" + reported +
"`, but it was expected to be `" + expected + "`");
}
}
};
struct ExpectBytesInFlight : public SenderExpectation {
size_t _n_bytes;
ExpectBytesInFlight(size_t n_bytes) : _n_bytes(n_bytes) {}
std::string description() const { return std::to_string(_n_bytes) + " bytes in flight"; }
void execute(TCPSender &sender, std::deque<TCPSegment> &) const {
if (sender.bytes_in_flight() != _n_bytes) {
std::ostringstream ss;
ss << "The TCPSender reported " << sender.bytes_in_flight()
<< " bytes in flight, but there was expected to be " << _n_bytes << " bytes in flight";
throw SenderExpectationViolation(ss.str());
}
}
};
struct ExpectNoSegment : public SenderExpectation {
ExpectNoSegment() {}
std::string description() const { return "no (more) segments"; }
void execute(TCPSender &, std::deque<TCPSegment> &segments) const {
if (not segments.empty()) {
std::ostringstream ss;
ss << "The TCPSender sent a segment, but should not have. Segment info:\n\t";
TCPSegment &seg = segments.back();
ss << seg.header().summary();
ss << " with " << seg.payload().size() << " bytes";
throw SenderExpectationViolation(ss.str());
}
}
};
struct SenderAction : public SenderTestStep {
operator std::string() const { return "Action: " + description(); }
virtual std::string description() const { return "description missing"; }
virtual void execute(TCPSender &, std::deque<TCPSegment> &) const {}
virtual ~SenderAction() {}
};
struct WriteBytes : public SenderAction {
std::string _bytes;
WriteBytes(std::string &&bytes) : _bytes(std::move(bytes)) {}
std::string description() const {
std::ostringstream ss;
ss << "write bytes: \"" << _bytes << "\"";
return ss.str();
}
void execute(TCPSender &sender, std::deque<TCPSegment> &) const {
sender.stream_in().write(std::move(_bytes));
sender.fill_window();
}
};
struct Tick : public SenderAction {
size_t _ms;
std::optional<bool> max_retx_exceeded{};
Tick(size_t ms) : _ms(ms) {}
Tick &with_max_retx_exceeded(bool max_retx_exceeded_) {
max_retx_exceeded = max_retx_exceeded_;
return *this;
}
std::string description() const {
std::ostringstream ss;
ss << _ms << " ms pass";
if (max_retx_exceeded.has_value()) {
ss << " with max_retx_exceeded = " << max_retx_exceeded.value();
}
return ss.str();
}
void execute(TCPSender &sender, std::deque<TCPSegment> &) const {
sender.tick(_ms);
if (max_retx_exceeded.has_value() and
max_retx_exceeded != (sender.consecutive_retransmissions() > TCPConfig::MAX_RETX_ATTEMPTS)) {
std::ostringstream ss;
ss << "after " << _ms << "ms passed the TCP Sender reported\n\tconsecutive_retransmissions = "
<< sender.consecutive_retransmissions() << "\nbut it should have been\n\t";
if (max_retx_exceeded.value()) {
ss << "greater than ";
} else {
ss << "less than or equal to ";
}
ss << TCPConfig::MAX_RETX_ATTEMPTS << std::endl;
throw SenderExpectationViolation(ss.str());
}
}
};
struct AckReceived : public SenderAction {
WrappingInt32 _ackno;
std::optional<uint16_t> _window_advertisement{};
AckReceived(WrappingInt32 ackno) : _ackno(ackno) {}
std::string description() const {
std::ostringstream ss;
ss << "ack " << _ackno.raw_value();
return ss.str();
}
AckReceived &with_win(uint16_t win) {
_window_advertisement.emplace(win);
return *this;
}
void execute(TCPSender &sender, std::deque<TCPSegment> &) const {
if (not sender.ack_received(_ackno, _window_advertisement.value_or(DEFAULT_TEST_WINDOW))) {
sender.send_empty_segment();
}
sender.fill_window();
}
};
struct Close : public SenderAction {
Close() {}
std::string description() const { return "close"; }
void execute(TCPSender &sender, std::deque<TCPSegment> &) const {
sender.stream_in().end_input();
sender.fill_window();
}
};
struct ExpectSegment : public SenderExpectation {
std::optional<bool> ack{};
std::optional<bool> rst{};
std::optional<bool> syn{};
std::optional<bool> fin{};
std::optional<WrappingInt32> seqno{};
std::optional<WrappingInt32> ackno{};
std::optional<uint16_t> win{};
std::optional<size_t> payload_size{};
std::optional<std::string> data{};
ExpectSegment &with_ack(bool ack_) {
ack = ack_;
return *this;
}
ExpectSegment &with_rst(bool rst_) {
rst = rst_;
return *this;
}
ExpectSegment &with_syn(bool syn_) {
syn = syn_;
return *this;
}
ExpectSegment &with_fin(bool fin_) {
fin = fin_;
return *this;
}
ExpectSegment &with_no_flags() {
ack = false;
rst = false;
syn = false;
fin = false;
return *this;
}
ExpectSegment &with_seqno(WrappingInt32 seqno_) {
seqno = seqno_;
return *this;
}
ExpectSegment &with_seqno(uint32_t seqno_) { return with_seqno(WrappingInt32{seqno_}); }
ExpectSegment &with_ackno(WrappingInt32 ackno_) {
ackno = ackno_;
return *this;
}
ExpectSegment &with_ackno(uint32_t ackno_) { return with_ackno(WrappingInt32{ackno_}); }
ExpectSegment &with_win(uint16_t win_) {
win = win_;
return *this;
}
ExpectSegment &with_payload_size(size_t payload_size_) {
payload_size = payload_size_;
return *this;
}
ExpectSegment &with_data(std::string data_) {
data = data_;
return *this;
}
std::string segment_description() const {
std::ostringstream o;
o << "(";
if (ack.has_value()) {
o << (ack.value() ? "A=1," : "A=0,");
}
if (rst.has_value()) {
o << (rst.value() ? "R=1," : "R=0,");
}
if (syn.has_value()) {
o << (syn.value() ? "S=1," : "S=0,");
}
if (fin.has_value()) {
o << (fin.value() ? "F=1," : "F=0,");
}
if (ackno.has_value()) {
o << "ackno=" << ackno.value() << ",";
}
if (win.has_value()) {
o << "win=" << win.value() << ",";
}
if (seqno.has_value()) {
o << "seqno=" << seqno.value() << ",";
}
if (payload_size.has_value()) {
o << "payload_size=" << payload_size.value() << ",";
}
if (data.has_value()) {
o << "\"";
for (unsigned int i = 0; i < std::min(size_t(16), data.value().size()); i++) {
const char x = data.value().at(i);
if (isprint(x)) {
o << data.value().at(i);
} else {
o << "<" << std::to_string(uint8_t(x)) << ">";
}
}
if (data.value().size() > 16) {
o << "...";
}
o << "\",";
}
o << "...)";
return o.str();
}
virtual std::string description() const { return "segment sent with " + segment_description(); }
void execute(TCPSender &, std::deque<TCPSegment> &segments) const {
if (segments.empty()) {
throw SegmentExpectationViolation::violated_verb("existed");
}
TCPSegment seg = std::move(segments.back());
segments.pop_back();
if (ack.has_value() and seg.header().ack != ack.value()) {
throw SegmentExpectationViolation::violated_field("ack", ack.value(), seg.header().ack);
}
if (rst.has_value() and seg.header().rst != rst.value()) {
throw SegmentExpectationViolation::violated_field("rst", rst.value(), seg.header().rst);
}
if (syn.has_value() and seg.header().syn != syn.value()) {
throw SegmentExpectationViolation::violated_field("syn", syn.value(), seg.header().syn);
}
if (fin.has_value() and seg.header().fin != fin.value()) {
throw SegmentExpectationViolation::violated_field("fin", fin.value(), seg.header().fin);
}
if (seqno.has_value() and seg.header().seqno != seqno.value()) {
throw SegmentExpectationViolation::violated_field("seqno", seqno.value(), seg.header().seqno);
}
if (ackno.has_value() and seg.header().ackno != ackno.value()) {
throw SegmentExpectationViolation::violated_field("ackno", ackno.value(), seg.header().ackno);
}
if (win.has_value() and seg.header().win != win.value()) {
throw SegmentExpectationViolation::violated_field("win", win.value(), seg.header().win);
}
if (payload_size.has_value() and seg.payload().size() != payload_size.value()) {
throw SegmentExpectationViolation::violated_field(
"payload_size", payload_size.value(), seg.payload().size());
}
if (seg.length_in_sequence_space() > TCPConfig::MAX_PAYLOAD_SIZE) {
throw SegmentExpectationViolation("packet has length_including_flags (" +
std::to_string(seg.length_in_sequence_space()) +
") greater than the maximum");
}
if (data.has_value() and seg.payload().str() != data.value()) {
throw SegmentExpectationViolation("payloads differ. expected \"" + data.value() + "\" but found \"" +
std::string(seg.payload().str()) + "\"");
}
}
};
class TCPSenderTestHarness {
std::deque<TCPSegment> outbound_segments;
TCPSender sender;
std::vector<std::string> steps_executed;
std::string name;
void collect_output() {
while (not sender.segments_out().empty()) {
outbound_segments.push_back(std::move(sender.segments_out().front()));
sender.segments_out().pop();
}
}
public:
TCPSenderTestHarness(const std::string &name_, TCPConfig config)
: outbound_segments()
, sender(config.send_capacity, config.rt_timeout, config.fixed_isn)
, steps_executed()
, name(name_) {
sender.fill_window();
collect_output();
std::ostringstream ss;
ss << "Initialized with ("
<< "retx-timeout=" << config.rt_timeout << ") and called fill_window()";
steps_executed.emplace_back(ss.str());
}
void execute(const SenderTestStep &step) {
try {
step.execute(sender, outbound_segments);
collect_output();
steps_executed.emplace_back(step);
} catch (const SenderExpectationViolation &e) {
std::cerr << "Test Failure on expectation:\n\t" << std::string(step);
std::cerr << "\n\nFailure message:\n\t" << e.what();
std::cerr << "\n\nList of steps that executed successfully:";
for (const std::string &s : steps_executed) {
std::cerr << "\n\t" << s;
}
std::cerr << std::endl << std::endl;
throw SenderExpectationViolation("The test \"" + name + "\" failed");
} catch (const std::exception &e) {
std::cerr << "Test Failure on expectation:\n\t" << std::string(step);
std::cerr << "\n\nFailure message:\n\t" << e.what();
std::cerr << "\n\nList of steps that executed successfully:";
for (const std::string &s : steps_executed) {
std::cerr << "\n\t" << s;
}
std::cerr << std::endl << std::endl;
throw SenderExpectationViolation("The test \"" + name +
"\" caused your implementation to throw an exception!");
}
}
};
#endif // SPONGE_SENDER_HARNESS_HH
#ifndef SPONGE_STRING_CONVERSIONS_HH
#define SPONGE_STRING_CONVERSIONS_HH
#include "wrapping_integers.hh"
#include <optional>
#include <string>
#include <utility>
// https://stackoverflow.com/questions/33399594/making-a-user-defined-class-stdto-stringable
namespace sponge_conversions {
using std::to_string;
std::string to_string(WrappingInt32 i) { return std::to_string(i.raw_value()); }
template <typename T>
std::string to_string(const std::optional<T> &v) {
if (v.has_value()) {
return "Some(" + to_string(v.value()) + ")";
} else {
return "None";
}
}
template <typename T>
std::string as_string(T &&t) {
return to_string(std::forward<T>(t));
}
} // namespace sponge_conversions
template <typename T>
std::string to_string(T &&t) {
return sponge_conversions::as_string(std::forward<T>(t));
}
#endif // SPONGE_STRING_CONVERSIONS_HH
#ifndef SPONGE_LIBSPONGE_TCP_EXPECTATION_HH
#define SPONGE_LIBSPONGE_TCP_EXPECTATION_HH
#include "tcp_config.hh"
#include "tcp_expectation_forward.hh"
#include "tcp_fsm_test_harness.hh"
#include "tcp_state.hh"
#include <algorithm>
#include <cstdint>
#include <exception>
#include <optional>
#include <sstream>
struct TCPExpectation : public TCPTestStep {
virtual ~TCPExpectation() {}
std::string to_string() const { return "Expectation: " + description(); }
virtual std::string description() const { return "description missing"; }
virtual void execute(TCPTestHarness &) const {}
};
struct TCPAction : public TCPTestStep {
virtual ~TCPAction() {}
std::string to_string() const { return "Action: " + description(); }
virtual std::string description() const { return "description missing"; }
virtual void execute(TCPTestHarness &) const {}
};
struct ExpectNoData : public TCPExpectation {
std::string description() const { return "no (more) data available"; }
void execute(TCPTestHarness &harness) const {
if (harness.can_read()) {
throw SegmentExpectationViolation("The TCP produced data when it should not have");
}
}
};
static void append_data(std::ostream &os, std::string_view data) {
os << "\"";
for (unsigned int i = 0; i < std::min(size_t(16), data.size()); i++) {
const char x = data.at(i);
if (isprint(x)) {
os << data.at(i);
} else {
os << "<" << std::to_string(uint8_t(x)) << ">";
}
}
if (data.size() > 16) {
os << "...";
}
os << "\"";
}
struct ExpectData : public TCPExpectation {
std::optional<std::string> data{};
ExpectData() {}
ExpectData &with_data(const std::string &data_) {
data = data_;
return *this;
}
std::string description() const {
std::ostringstream msg;
msg << "data available";
if (data.has_value()) {
msg << " (" << data.value().size() << " bytes starting with ";
append_data(msg, data.value());
msg << ")";
}
return msg.str();
}
void execute(TCPTestHarness &harness) const {
if (harness._fsm.inbound_stream().buffer_empty()) {
throw SegmentExpectationViolation("The TCP should have data for the user, but does not");
}
const size_t bytes_avail = harness._fsm.inbound_stream().buffer_size();
std::string actual_data = harness._fsm.inbound_stream().read(bytes_avail);
if (data.has_value()) {
if (actual_data.size() != data->size()) {
std::ostringstream msg{"The TCP produced "};
msg << actual_data.size() << " bytes, but should have produced " << data->size() << " bytes";
throw TCPExpectationViolation(msg.str());
}
if (not std::equal(actual_data.begin(), actual_data.end(), data->begin())) {
std::ostringstream msg{"The TCP produced data "};
msg << actual_data << ", but should have produced " << data.value() << " bytes";
throw TCPExpectationViolation(msg.str());
}
}
}
};
struct ExpectSegmentAvailable : public TCPExpectation {
ExpectSegmentAvailable() {}
std::string description() const { return "segment sent"; }
void execute(TCPTestHarness &harness) const {
if (not harness.can_read()) {
throw SegmentExpectationViolation("The TCP should have produces a segment, but did not");
}
}
};
struct ExpectNoSegment : public TCPExpectation {
std::string description() const { return "no (more) segments sent"; }
void execute(TCPTestHarness &harness) const {
if (harness.can_read()) {
throw SegmentExpectationViolation("The TCP produced a segment when it should not have");
}
}
};
struct ExpectSegment : public TCPExpectation {
std::optional<bool> ack{};
std::optional<bool> rst{};
std::optional<bool> syn{};
std::optional<bool> fin{};
std::optional<WrappingInt32> seqno{};
std::optional<WrappingInt32> ackno{};
std::optional<uint16_t> win{};
std::optional<size_t> payload_size{};
std::optional<std::string> data{};
ExpectSegment &with_ack(bool ack_) {
ack = ack_;
return *this;
}
ExpectSegment &with_rst(bool rst_) {
rst = rst_;
return *this;
}
ExpectSegment &with_syn(bool syn_) {
syn = syn_;
return *this;
}
ExpectSegment &with_fin(bool fin_) {
fin = fin_;
return *this;
}
ExpectSegment &with_no_flags() {
ack = false;
rst = false;
syn = false;
fin = false;
return *this;
}
ExpectSegment &with_seqno(WrappingInt32 seqno_) {
seqno = seqno_;
return *this;
}
ExpectSegment &with_seqno(uint32_t seqno_) { return with_seqno(WrappingInt32{seqno_}); }
ExpectSegment &with_ackno(WrappingInt32 ackno_) {
ackno = ackno_;
return *this;
}
ExpectSegment &with_ackno(uint32_t ackno_) { return with_ackno(WrappingInt32{ackno_}); }
ExpectSegment &with_win(uint16_t win_) {
win = win_;
return *this;
}
ExpectSegment &with_payload_size(size_t payload_size_) {
payload_size = payload_size_;
return *this;
}
ExpectSegment &with_data(std::string data_) {
data = data_;
return *this;
}
std::string segment_description() const {
std::ostringstream o;
o << "(";
if (ack.has_value()) {
o << (ack.value() ? "A=1," : "A=0,");
}
if (rst.has_value()) {
o << (rst.value() ? "R=1," : "R=0,");
}
if (syn.has_value()) {
o << (syn.value() ? "S=1," : "S=0,");
}
if (fin.has_value()) {
o << (fin.value() ? "F=1," : "F=0,");
}
if (ackno.has_value()) {
o << "ackno=" << ackno.value() << ",";
}
if (win.has_value()) {
o << "win=" << win.value() << ",";
}
if (seqno.has_value()) {
o << "seqno=" << seqno.value() << ",";
}
if (payload_size.has_value()) {
o << "payload_size=" << payload_size.value() << ",";
}
if (data.has_value()) {
o << "data=";
append_data(o, data.value());
o << ",";
}
o << ")";
return o.str();
}
virtual std::string description() const { return "segment sent with " + segment_description(); }
virtual TCPSegment expect_seg(TCPTestHarness &harness) const {
if (not harness.can_read()) {
throw SegmentExpectationViolation::violated_verb("existed");
}
TCPSegment seg;
if (ParseResult::NoError != seg.parse(harness._flt.read())) {
throw SegmentExpectationViolation::violated_verb("was parsable");
}
if (ack.has_value() and seg.header().ack != ack.value()) {
throw SegmentExpectationViolation::violated_field("ack", ack.value(), seg.header().ack);
}
if (rst.has_value() and seg.header().rst != rst.value()) {
throw SegmentExpectationViolation::violated_field("rst", rst.value(), seg.header().rst);
}
if (syn.has_value() and seg.header().syn != syn.value()) {
throw SegmentExpectationViolation::violated_field("syn", syn.value(), seg.header().syn);
}
if (fin.has_value() and seg.header().fin != fin.value()) {
throw SegmentExpectationViolation::violated_field("fin", fin.value(), seg.header().fin);
}
if (seqno.has_value() and seg.header().seqno != seqno.value()) {
throw SegmentExpectationViolation::violated_field("seqno", seqno.value(), seg.header().seqno);
}
if (ackno.has_value() and seg.header().ackno != ackno.value()) {
throw SegmentExpectationViolation::violated_field("ackno", ackno.value(), seg.header().ackno);
}
if (win.has_value() and seg.header().win != win.value()) {
throw SegmentExpectationViolation::violated_field("win", win.value(), seg.header().win);
}
if (payload_size.has_value() and seg.payload().size() != payload_size.value()) {
throw SegmentExpectationViolation::violated_field(
"payload_size", payload_size.value(), seg.payload().size());
}
if (seg.length_in_sequence_space() > TCPConfig::MAX_PAYLOAD_SIZE) {
throw SegmentExpectationViolation("packet has length_including_flags (" +
std::to_string(seg.length_in_sequence_space()) +
") greater than the maximum");
}
if (data.has_value() and seg.payload().str() != *data) {
throw SegmentExpectationViolation("payloads differ");
}
return seg;
}
virtual void execute(TCPTestHarness &harness) const { expect_seg(harness); }
};
struct ExpectOneSegment : public ExpectSegment {
std::string description() const { return "exactly one segment sent with " + segment_description(); }
TCPSegment expect_seg(TCPTestHarness &harness) const {
TCPSegment seg = ExpectSegment::expect_seg(harness);
if (harness.can_read()) {
throw SegmentExpectationViolation("The TCP an extra segment when it should not have");
}
return seg;
}
void execute(TCPTestHarness &harness) const {
ExpectSegment::execute(harness);
if (harness.can_read()) {
throw SegmentExpectationViolation("The TCP an extra segment when it should not have");
}
}
};
struct ExpectState : public TCPExpectation {
TCPState state;
ExpectState(TCPState stat) : state(stat) {}
std::string description() const {
std::ostringstream o;
o << "TCP in state ";
o << state.name();
return o.str();
}
void execute(TCPTestHarness &harness) const {
TCPState actual_state = harness._fsm.state();
if (actual_state != state) {
throw StateExpectationViolation{state, actual_state};
}
}
};
struct ExpectNotInState : public TCPExpectation {
TCPState state;
ExpectNotInState(TCPState stat) : state(stat) {}
std::string description() const {
std::ostringstream o;
o << "TCP **not** in state ";
o << state.name();
return o.str();
}
void execute(TCPTestHarness &harness) const {
TCPState actual_state = harness._fsm.state();
if (actual_state == state) {
throw TCPPropertyViolation::make_not("state", state.name());
}
}
};
struct ExpectBytesInFlight : public TCPExpectation {
uint64_t bytes;
ExpectBytesInFlight(uint64_t bytes_) : bytes(bytes_) {}
std::string description() const {
std::ostringstream o;
o << "TCP has " << bytes << " bytes in flight";
return o.str();
}
void execute(TCPTestHarness &harness) const {
uint64_t actual_bytes = harness._fsm.bytes_in_flight();
if (actual_bytes != bytes) {
throw TCPPropertyViolation::make("bytes_in_flight", bytes, actual_bytes);
}
}
};
struct ExpectUnassembledBytes : public TCPExpectation {
uint64_t bytes;
ExpectUnassembledBytes(uint64_t bytes_) : bytes(bytes_) {}
std::string description() const {
std::ostringstream o;
o << "TCP has " << bytes << " unassembled bytes";
return o.str();
}
void execute(TCPTestHarness &harness) const {
uint64_t actual_unassembled_bytes = harness._fsm.unassembled_bytes();
if (actual_unassembled_bytes != bytes) {
throw TCPPropertyViolation::make("unassembled_bytes", bytes, actual_unassembled_bytes);
}
}
};
struct ExpectLingerTimer : public TCPExpectation {
uint64_t ms;
ExpectLingerTimer(uint64_t ms_) : ms(ms_) {}
std::string description() const {
std::ostringstream o;
o << "Most recent incoming segment was " << ms << " ms ago";
return o.str();
}
void execute(TCPTestHarness &harness) const {
uint64_t actual_ms = harness._fsm.time_since_last_segment_received();
if (actual_ms != ms) {
throw TCPPropertyViolation::make("time_since_last_segment_received", ms, actual_ms);
}
}
};
struct SendSegment : public TCPAction {
bool ack{false};
bool rst{false};
bool syn{false};
bool fin{false};
WrappingInt32 seqno{0};
WrappingInt32 ackno{0};
uint16_t win{0};
size_t payload_size{0};
std::string data{};
SendSegment() {}
SendSegment(TCPSegment seg) {
ack = seg.header().ack;
rst = seg.header().rst;
syn = seg.header().syn;
fin = seg.header().fin;
seqno = seg.header().seqno;
ackno = seg.header().ackno;
win = seg.header().win;
data = seg.payload();
}
SendSegment &with_ack(bool ack_) {
ack = ack_;
return *this;
}
SendSegment &with_rst(bool rst_) {
rst = rst_;
return *this;
}
SendSegment &with_syn(bool syn_) {
syn = syn_;
return *this;
}
SendSegment &with_fin(bool fin_) {
fin = fin_;
return *this;
}
SendSegment &with_seqno(WrappingInt32 seqno_) {
seqno = seqno_;
return *this;
}
SendSegment &with_seqno(uint32_t seqno_) { return with_seqno(WrappingInt32{seqno_}); }
SendSegment &with_ackno(WrappingInt32 ackno_) {
ackno = ackno_;
return *this;
}
SendSegment &with_ackno(uint32_t ackno_) { return with_ackno(WrappingInt32{ackno_}); }
SendSegment &with_win(uint16_t win_) {
win = win_;
return *this;
}
SendSegment &with_payload_size(size_t payload_size_) {
payload_size = payload_size_;
return *this;
}
SendSegment &with_data(std::string &&data_) {
data = data_;
return *this;
}
TCPSegment get_segment() const {
TCPSegment data_seg;
data_seg.payload() = std::string(data);
auto &data_hdr = data_seg.header();
data_hdr.ack = ack;
data_hdr.rst = rst;
data_hdr.syn = syn;
data_hdr.fin = fin;
data_hdr.ackno = ackno;
data_hdr.seqno = seqno;
data_hdr.win = win;
return data_seg;
}
virtual std::string description() const {
TCPSegment seg = get_segment();
std::ostringstream o;
o << "packet arrives: ";
o << seg.header().summary();
if (seg.payload().size() > 0) {
o << " with " << seg.payload().size() << " data bytes ";
append_data(o, seg.payload());
} else {
o << " with no payload";
}
return o.str();
}
virtual void execute(TCPTestHarness &harness) const {
TCPSegment seg = get_segment();
harness._fsm.segment_received(std::move(seg));
}
};
struct Write : public TCPAction {
std::string data;
std::optional<size_t> _bytes_written{};
Write(const std::string &data_) : data(data_) {}
TCPAction &with_bytes_written(size_t bytes_written) {
_bytes_written = bytes_written;
return *this;
}
std::string description() const {
std::ostringstream o;
o << "write (" << data.size() << " bytes";
if (_bytes_written.has_value()) {
o << " with " << _bytes_written.value() << " accepted";
}
o << ") [";
bool first = true;
for (auto u : data.substr(0, 16)) {
if (first) {
first = false;
} else {
o << ", ";
}
o << std::hex << uint16_t(uint8_t(u));
}
if (data.size() > 16) {
o << ", ...";
}
o << "]";
return o.str();
}
void execute(TCPTestHarness &harness) const {
size_t bytes_written = harness._fsm.write(data);
if (_bytes_written.has_value() and bytes_written != _bytes_written.value()) {
throw TCPExpectationViolation(std::to_string(_bytes_written.value()) +
" bytes should have been written but " + std::to_string(bytes_written) +
" were");
}
}
};
struct Tick : public TCPAction {
size_t ms_since_last_tick;
Tick(size_t ms_) : ms_since_last_tick(ms_) {}
std::string description() const {
std::ostringstream o;
o << ms_since_last_tick << "ms pass";
return o.str();
}
void execute(TCPTestHarness &harness) const { harness._fsm.tick(ms_since_last_tick); }
};
struct Connect : public TCPAction {
std::string description() const { return "connect"; }
void execute(TCPTestHarness &harness) const { harness._fsm.connect(); }
};
struct Listen : public TCPAction {
std::string description() const { return "listen"; }
void execute(TCPTestHarness &) const {}
};
struct Close : public TCPAction {
std::string description() const { return "close"; }
void execute(TCPTestHarness &harness) const { harness._fsm.end_input_stream(); }
};
#endif // SPONGE_LIBSPONGE_TCP_EXPECTATION_HH
#ifndef SPONGE_LIBSPONGE_TCP_EXPECTATION_FORWARD_HH
#define SPONGE_LIBSPONGE_TCP_EXPECTATION_FORWARD_HH
#include "tcp_segment.hh"
#include "tcp_state.hh"
#include <cstdint>
#include <exception>
#include <optional>
#include <sstream>
class TCPTestHarness;
struct TCPTestStep {
public:
virtual ~TCPTestStep() {}
virtual std::string to_string() const { return "TestStep"; }
virtual void execute(TCPTestHarness &) const {}
virtual TCPSegment expect_seg(TCPTestHarness &) const { throw std::runtime_error("unimplemented"); }
};
struct TCPExpectation;
struct ExpectNoSegment;
struct ExpectState;
struct ExpectNotInState;
struct ExpectOneSegment;
struct ExpectSegment;
struct ExpectData;
struct ExpectNoData;
struct ExpectSegmentAvailable;
struct ExpectBytesInFlight;
struct ExpectUnassembledBytes;
struct ExpectWaitTimer;
struct SendSegment;
struct Write;
struct Tick;
struct Connect;
struct Listen;
struct Close;
class TCPExpectationViolation : public std::runtime_error {
public:
TCPExpectationViolation(const std::string msg) : std::runtime_error(msg) {}
};
class TCPPropertyViolation : public TCPExpectationViolation {
public:
TCPPropertyViolation(const std::string &msg) : TCPExpectationViolation(msg) {}
template <typename T>
static TCPPropertyViolation make(const std::string &property_name, const T &expected_value, const T &actual_value) {
std::stringstream ss{};
ss << "The TCP has `" << property_name << " = " << actual_value << "`, but " << property_name
<< " was expected to be `" << expected_value << "`";
return TCPPropertyViolation{ss.str()};
}
template <typename T>
static TCPPropertyViolation make_not(const std::string &property_name, const T &expected_non_value) {
std::stringstream ss{};
ss << "The TCP has `" << property_name << " = " << expected_non_value << "`, but " << property_name
<< " was expected to **not** be `" << expected_non_value << "`";
return TCPPropertyViolation{ss.str()};
}
};
class SegmentExpectationViolation : public TCPExpectationViolation {
public:
SegmentExpectationViolation(const std::string &msg) : TCPExpectationViolation(msg) {}
static SegmentExpectationViolation violated_verb(const std::string &msg) {
return SegmentExpectationViolation{"The TCP should have produced a segment that " + msg + ", but it did not"};
}
template <typename T>
static SegmentExpectationViolation violated_field(const std::string &field_name,
const T &expected_value,
const T &actual_value) {
std::stringstream ss{};
ss << "The TCP produced a segment with `" << field_name << " = " << actual_value << "`, but " << field_name
<< " was expected to be `" << expected_value << "`";
return SegmentExpectationViolation{ss.str()};
}
};
class StateExpectationViolation : public TCPExpectationViolation {
public:
StateExpectationViolation(const std::string &msg) : TCPExpectationViolation(msg) {}
StateExpectationViolation(const TCPState &expected_state, const TCPState &actual_state)
: TCPExpectationViolation("The TCP was in state `" + actual_state.name() +
"`, but it was expected to be in state `" + expected_state.name() + "`") {}
};
#endif // SPONGE_LIBSPONGE_TCP_EXPECTATION_FORWARD_HH
#include "tcp_fsm_test_harness.hh"
#include "tcp_expectation.hh"
#include "tcp_header.hh"
#include "util.hh"
#include <cerrno>
#include <iostream>
#include <stdexcept>
#include <string_view>
#include <sys/socket.h>
#include <utility>
const unsigned int DEFAULT_TEST_WINDOW = 137;
using namespace std;
TestFD::TestFD()
: TestFD([&]() {
int fds[2];
SystemCall("socketpair", socketpair(AF_UNIX, SOCK_SEQPACKET, 0, &fds[0]));
return make_pair(FileDescriptor(fds[0]), TestRFD(fds[1]));
}()) {}
TestFD::TestFD(pair<FileDescriptor, TestRFD> fd_pair)
: FileDescriptor(move(fd_pair.first)), _recv_fd(move(fd_pair.second)) {}
//! \param[in] buffer is the content to write to the TestFD
void TestFD::write(const BufferViewList &buffer) {
auto iovecs = buffer.as_iovecs();
msghdr message{};
message.msg_iov = iovecs.data();
message.msg_iovlen = iovecs.size();
SystemCall("sendmsg", ::sendmsg(fd_num(), &message, MSG_EOR));
}
//! \returns `true` if there is a packet available for reading from TestRFD
bool TestFD::TestRFD::can_read() const {
uint8_t tmp;
return SystemCall("recv", ::recv(fd_num(), &tmp, 1, MSG_PEEK | MSG_DONTWAIT), EAGAIN) >= 0;
}
//! \returns a vector of bytes received on the TestRFD
string TestFD::TestRFD::read() {
string ret(MAX_RECV, 0);
ssize_t ret_read = SystemCall("recv", ::recv(fd_num(), ret.data(), ret.size(), MSG_TRUNC));
if (ret_read > static_cast<ssize_t>(ret.size())) {
throw runtime_error("TestFD unexpectedly got truncated packet.");
}
ret.resize(ret_read);
register_read();
return ret;
}
//! \param[in] seg is the TCPSegment to configure
void TestFdAdapter::config_segment(TCPSegment &seg) {
const auto &cfg = config();
auto &tcp_hdr = seg.header();
tcp_hdr.sport = cfg.source.port();
tcp_hdr.dport = cfg.destination.port();
}
//! \param[in] seg is the TCPSegment to write
void TestFdAdapter::write(TCPSegment &seg) {
config_segment(seg);
TestFD::write(seg.serialize());
}
//! \param[in] seqno is the sequence number of the segment
//! \param[in] ackno is the optional acknowledgment number of the segment; if no value, ACK flag is not set
void TCPTestHarness::send_fin(const WrappingInt32 seqno, const optional<WrappingInt32> ackno) {
SendSegment step{};
if (ackno.has_value()) {
step.with_ack(true).with_ackno(ackno.value());
}
step.with_fin(true).with_seqno(seqno).with_win(DEFAULT_TEST_WINDOW);
execute(step);
}
//! \param[in] seqno is the sequence number of the segment
//! \param[in] ackno is the acknowledgment number of the segment
//! \param[in] swin is the optional window size for the segment; if no value, uses default value (137 bytes)
void TCPTestHarness::send_ack(const WrappingInt32 seqno, const WrappingInt32 ackno, const optional<uint16_t> swin) {
uint16_t win = DEFAULT_TEST_WINDOW;
if (swin.has_value()) {
win = swin.value();
}
execute(SendSegment{}.with_ack(true).with_ackno(ackno).with_seqno(seqno).with_win(win));
}
//! \param[in] seqno is the sequence number of the segment
//! \param[in] ackno is the optional acknowledgment number of the segment; if no value, ACK flag is not set
void TCPTestHarness::send_rst(const WrappingInt32 seqno, const optional<WrappingInt32> ackno) {
SendSegment step{};
if (ackno.has_value()) {
step.with_ack(true).with_ackno(ackno.value());
}
step.with_rst(true).with_seqno(seqno).with_win(DEFAULT_TEST_WINDOW);
execute(step);
}
//! \param[in] seqno is the sequence number of the segment
//! \param[in] ackno is the optional acknowledgment number of the segment; if no value, ACK flag is not set
void TCPTestHarness::send_syn(const WrappingInt32 seqno, const optional<WrappingInt32> ackno) {
SendSegment step{};
if (ackno.has_value()) {
step.with_ack(true).with_ackno(ackno.value());
}
step.with_syn(true).with_seqno(seqno).with_win(DEFAULT_TEST_WINDOW);
execute(step);
}
//! \param[in] seqno is the sequence number of the segment
//! \param[in] ackno is the optional acknowledgment number of the segment; if no value, ACK flag is not set
//! \param[in] val is the value of the one-byte payload
void TCPTestHarness::send_byte(const WrappingInt32 seqno, const optional<WrappingInt32> ackno, const uint8_t val) {
SendSegment step{};
if (ackno.has_value()) {
step.with_ack(true).with_ackno(ackno.value());
}
step.with_payload_size(1).with_data(string{&val, &val + 1}).with_seqno(seqno).with_win(DEFAULT_TEST_WINDOW);
execute(step);
}
//! \param[in] seqno is the sequence number of the segment
//! \param[in] ackno is the acknowledgment number of the segment
//! \param[in] begin is an iterator to the start of the payload
//! \param[in] end is an iterator to the end of the payload
void TCPTestHarness::send_data(const WrappingInt32 seqno, const WrappingInt32 ackno, VecIterT begin, VecIterT end) {
SendSegment step{};
execute(SendSegment{}
.with_ack(true)
.with_ackno(ackno)
.with_payload_size(1)
.with_data(string{begin, end})
.with_seqno(seqno)
.with_win(DEFAULT_TEST_WINDOW));
}
void TCPTestHarness::execute(const TCPTestStep &step, std::string note) {
try {
step.execute(*this);
while (not _fsm.segments_out().empty()) {
_flt.write(_fsm.segments_out().front());
_fsm.segments_out().pop();
}
_steps_executed.emplace_back(step.to_string());
} catch (const TCPExpectationViolation &e) {
cerr << "Test Failure on expectation:\n\t" << step.to_string();
cerr << "\n\nFailure message:\n\t" << e.what();
cerr << "\n\nList of steps that executed successfully:";
for (const string &s : _steps_executed) {
cerr << "\n\t" << s;
}
cerr << endl << endl;
if (note.size() > 0) {
cerr << "Note:\n\t" << note << endl << endl;
}
throw e;
}
}
TCPSegment TCPTestHarness::expect_seg(const ExpectSegment &expectation, std::string note) {
try {
return expectation.expect_seg(*this);
_steps_executed.emplace_back(expectation.to_string());
} catch (const TCPExpectationViolation &e) {
cerr << "Test Failure on expectation:\n\t" << expectation.description() << "\nFailure message:\n\t" << e.what()
<< endl;
if (note.size() > 0) {
cerr << "Note:\n\t" << note << endl << endl;
}
throw e;
}
}
//! Create a FSM in the "LISTEN" state.
TCPTestHarness TCPTestHarness::in_listen(const TCPConfig &cfg) {
TCPTestHarness h{cfg};
h.execute(Listen{});
return h;
}
//! \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.
TCPTestHarness TCPTestHarness::in_syn_sent(const TCPConfig &cfg, const WrappingInt32 tx_isn) {
TCPConfig c{cfg};
c.fixed_isn = tx_isn;
TCPTestHarness h{c};
h.execute(Connect{});
h.execute(ExpectOneSegment{}.with_no_flags().with_syn(true).with_seqno(tx_isn).with_payload_size(0));
return h;
}
//! \brief Create an FSM with an established connection
//! \details The mahine has sent and received a SYN, and both SYNs have been ACK'd
//! \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.
TCPTestHarness TCPTestHarness::in_established(const TCPConfig &cfg,
const WrappingInt32 tx_isn,
const WrappingInt32 rx_isn) {
TCPTestHarness h = in_syn_sent(cfg, tx_isn);
// It has sent a SYN with nothing else, and that SYN has been consumed
// We reply with ACK and SYN.
h.send_syn(rx_isn, tx_isn + 1);
h.execute(ExpectOneSegment{}.with_no_flags().with_ack(true).with_ackno(rx_isn + 1).with_payload_size(0));
return h;
}
//! \brief Create an FSM in CLOSE_WAIT
//! \details SYNs have been traded, and then the machine received and ACK'd FIN.
//! \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.
TCPTestHarness TCPTestHarness::in_close_wait(const TCPConfig &cfg,
const WrappingInt32 tx_isn,
const WrappingInt32 rx_isn) {
TCPTestHarness h = in_established(cfg, tx_isn, rx_isn);
h.send_fin(rx_isn + 1, tx_isn + 1);
h.execute(ExpectOneSegment{}.with_no_flags().with_ack(true).with_ackno(rx_isn + 2));
return h;
}
//! \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.
//! \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.
TCPTestHarness TCPTestHarness::in_last_ack(const TCPConfig &cfg,
const WrappingInt32 tx_isn,
const WrappingInt32 rx_isn) {
TCPTestHarness h = in_close_wait(cfg, tx_isn, rx_isn);
h.execute(Close{});
h.execute(
ExpectOneSegment{}.with_no_flags().with_fin(true).with_ack(true).with_seqno(tx_isn + 1).with_ackno(rx_isn + 2));
return h;
}
//! \brief Create an FSM in FIN_WAIT
//! \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.
TCPTestHarness TCPTestHarness::in_fin_wait_1(const TCPConfig &cfg,
const WrappingInt32 tx_isn,
const WrappingInt32 rx_isn) {
TCPTestHarness h = in_established(cfg, tx_isn, rx_isn);
h.execute(Close{});
h.execute(
ExpectOneSegment{}.with_no_flags().with_fin(true).with_ack(true).with_ackno(rx_isn + 1).with_seqno(tx_isn + 1));
return h;
}
//! \brief Create an FSM in FIN_WAIT_2
//! \details SYNs have been traded, then the TCP sent FIN, which was 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.
TCPTestHarness TCPTestHarness::in_fin_wait_2(const TCPConfig &cfg,
const WrappingInt32 tx_isn,
const WrappingInt32 rx_isn) {
TCPTestHarness h = in_fin_wait_1(cfg, tx_isn, rx_isn);
h.send_ack(rx_isn + 1, tx_isn + 2);
return h;
}
//! \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.
TCPTestHarness TCPTestHarness::in_closing(const TCPConfig &cfg,
const WrappingInt32 tx_isn,
const WrappingInt32 rx_isn) {
TCPTestHarness h = in_fin_wait_1(cfg, tx_isn, rx_isn);
h.send_fin(rx_isn + 1, tx_isn + 1);
h.execute(ExpectOneSegment{}.with_no_flags().with_ack(true).with_ackno(rx_isn + 2));
return h;
}
//! \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.
TCPTestHarness TCPTestHarness::in_time_wait(const TCPConfig &cfg,
const WrappingInt32 tx_isn,
const WrappingInt32 rx_isn) {
TCPTestHarness h = in_fin_wait_1(cfg, tx_isn, rx_isn);
h.send_fin(rx_isn + 1, tx_isn + 2);
h.execute(ExpectOneSegment{}.with_no_flags().with_ack(true).with_ackno(rx_isn + 2));
return h;
}
#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
#include "parser.hh"
#include "tcp_header.hh"
#include "tcp_segment.hh"
#include "test_utils.hh"
#include "util.hh"
#include <algorithm>
#include <cstdint>
#include <cstdlib>
#include <exception>
#include <iostream>
#include <pcap/pcap.h>
#include <string>
#include <vector>
using namespace std;
constexpr unsigned NREPS = 32;
uint16_t inet_cksum(const uint8_t *data, const size_t len) {
InternetChecksum check;
check.add({reinterpret_cast<const char *>(data), len});
return check.value();
}
int main(int argc, char **argv) {
try {
// first, make sure the parser gets the correct values and catches errors
auto rd = get_random_generator();
for (unsigned i = 0; i < NREPS; ++i) {
vector<uint8_t> test_header(20, 0);
generate(test_header.begin(), test_header.end(), [&] { return rd(); });
test_header[12] = 0x50; // make sure doff is correct
test_header[16] = test_header[17] = 0; // zero out cksum
const auto checksum = inet_cksum(test_header.data(), test_header.size());
test_header[16] = checksum >> 8;
test_header[17] = checksum & 0xff;
TCPHeader test_1{};
{
NetParser p{string(test_header.begin(), test_header.end())};
if (const auto res = test_1.parse(p); res != ParseResult::NoError) {
throw runtime_error("header parse failed: " + as_string(res));
}
}
if (const uint16_t tval = (test_header[0] << 8) | test_header[1]; test_1.sport != tval) {
throw runtime_error("bad parse: wrong source port");
}
if (const uint16_t tval = (test_header[2] << 8) | test_header[3]; test_1.dport != tval) {
throw runtime_error("bad parse: wrong destination port");
}
if (const uint32_t tval =
(test_header[4] << 24) | (test_header[5] << 16) | (test_header[6] << 8) | test_header[7];
test_1.seqno.raw_value() != tval) {
throw runtime_error("bad parse: wrong seqno");
}
if (const uint32_t tval =
(test_header[8] << 24) | (test_header[9] << 16) | (test_header[10] << 8) | test_header[11];
test_1.ackno.raw_value() != tval) {
throw runtime_error("bad parse: wrong ackno");
}
if (const uint8_t tval = (test_1.urg ? 0x20 : 0) | (test_1.ack ? 0x10 : 0) | (test_1.psh ? 0x08 : 0) |
(test_1.rst ? 0x04 : 0) | (test_1.syn ? 0x02 : 0) | (test_1.fin ? 0x01 : 0);
tval != (test_header[13] & 0x3f)) {
throw runtime_error("bad parse: bad flags");
}
if (const uint16_t tval = (test_header[14] << 8) | test_header[15]; test_1.win != tval) {
throw runtime_error("bad parse: wrong window value");
}
if (test_1.cksum != checksum) {
throw runtime_error("bad parse: wrong checksum");
}
if (const uint16_t tval = (test_header[18] << 8) | test_header[19]; test_1.uptr != tval) {
throw runtime_error("bad parse: wrong urgent pointer");
}
test_header[12] = 0x40;
{
const auto new_cksum = inet_cksum(test_header.data(), test_header.size());
test_header[16] = new_cksum >> 8;
test_header[17] = new_cksum & 0xff;
NetParser p{string(test_header.begin(), test_header.end())};
if (const auto res = test_1.parse(p); res != ParseResult::HeaderTooShort) {
throw runtime_error("bad parse: got wrong error for header with bad doff value");
}
}
test_header[12] = 0x50;
test_header[16] = (checksum >> 8) + max(int(rd()), 1);
test_header[17] = checksum + max(int(rd()), 1);
/* // checksum is taken over whole segment, so only TCPSegment parser checks the checksum
{
NetParser p{string(test_header.begin(), test_header.end())};
if (const auto res = test_1.parse(p); res != ParseResult::BadChecksum) {
throw runtime_error("bad parse: got wrong error for incorrect checksum: " + as_string(res));
}
}
*/
test_header[12] = 0x60;
test_header[16] = checksum >> 8;
test_header[17] = checksum & 0xff;
{
NetParser p{string(test_header.begin(), test_header.end())};
if (const auto res = test_1.parse(p); res != ParseResult::PacketTooShort) {
throw runtime_error("bad parse: got wrong error for segment shorter than 4 * doff: " +
as_string(res));
}
}
test_header[12] = 0x50;
test_header.resize(16);
{
NetParser p{string(test_header.begin(), test_header.end())};
if (const auto res = test_1.parse(p); res != ParseResult::PacketTooShort) {
throw runtime_error("bad parse: got wrong error for segment shorter than 20 bytes");
}
}
}
// now process some segments off the wire for correctness of parser and unparser
if (argc < 2) {
cout << "USAGE: " << argv[0] << " <filename>" << endl;
return EXIT_FAILURE;
}
char errbuf[PCAP_ERRBUF_SIZE];
pcap_t *pcap = pcap_open_offline(argv[1], static_cast<char *>(errbuf));
if (pcap == nullptr) {
cout << "ERROR opening " << argv[1] << ": " << static_cast<char *>(errbuf) << endl;
return EXIT_FAILURE;
}
if (pcap_datalink(pcap) != 1) {
cout << "ERROR expected ethernet linktype in capture file" << endl;
return EXIT_FAILURE;
}
bool ok = true;
const uint8_t *pkt;
struct pcap_pkthdr hdr;
while ((pkt = pcap_next(pcap, &hdr)) != nullptr) {
if (hdr.caplen < 14) {
cout << "ERROR frame too short to contain Ethernet header\n";
ok = false;
continue;
}
if (pkt[12] != 0x08 || pkt[13] != 0x00) {
continue;
}
uint8_t hdrlen = (pkt[14] & 0x0f) << 2;
uint16_t tlen = (pkt[16] << 8) | pkt[17];
if (hdr.caplen - 14 != tlen) {
continue; // weird! truncated segment
}
const uint8_t *const tcp_seg_data = pkt + 14 + hdrlen;
const auto tcp_seg_len = hdr.caplen - 14 - hdrlen;
auto [tcp_seg, result] = [&] {
vector<uint8_t> tcp_data(tcp_seg_data, tcp_seg_data + tcp_seg_len);
// fix up checksum to remove contribution from IPv4 pseudo-header
uint32_t cksum_fixup = ((pkt[26] << 8) | pkt[27]) + ((pkt[28] << 8) | pkt[29]); // src addr
cksum_fixup += ((pkt[30] << 8) | pkt[31]) + ((pkt[32] << 8) | pkt[33]); // dst addr
cksum_fixup += pkt[23]; // proto
cksum_fixup += tcp_seg_len; // len
cksum_fixup += (tcp_data[16] << 8) | tcp_data[17]; // original cksum
while (cksum_fixup > 0xffff) { // carry bits
cksum_fixup = (cksum_fixup >> 16) + (cksum_fixup & 0xffff);
}
tcp_data[16] = cksum_fixup >> 8;
tcp_data[17] = cksum_fixup & 0xff;
TCPSegment tcp_seg_ret;
const auto parse_result = tcp_seg_ret.parse(string(tcp_data.begin(), tcp_data.end()), 0);
return make_tuple(tcp_seg_ret, parse_result);
}();
if (result != ParseResult::NoError) {
auto tcp_parse_result = as_string(result);
cout << "ERROR got unexpected parse failure " << tcp_parse_result << " for this segment:\n";
hexdump(tcp_seg_data, tcp_seg_len);
ok = false;
continue;
}
// parse succeeded. Create a new segment and rebuild the header by unparsing.
cout << dec;
TCPSegment tcp_seg_copy;
tcp_seg_copy.payload() = tcp_seg.payload();
// set headers in new segment, and fix up to remove extensions
{
auto &tcp_hdr_orig = tcp_seg.header();
TCPHeader &tcp_hdr_copy = tcp_seg_copy.header();
tcp_hdr_copy = tcp_hdr_orig;
// fix up segment to remove IPv4 and TCP header extensions
tcp_hdr_copy.doff = 5;
} // tcp_hdr_{orig,copy} go out of scope
if (!compare_tcp_headers_nolen(tcp_seg.header(), tcp_seg_copy.header())) {
cout << "ERROR: after unparsing, TCP headers (other than length) don't match.\n";
}
TCPSegment tcp_seg_copy2;
if (const auto res = tcp_seg_copy2.parse(tcp_seg_copy.serialize().concatenate());
res != ParseResult::NoError) {
auto tcp_parse_result = as_string(res);
cout << "ERROR got parse failure " << tcp_parse_result << " for this segment:\n";
cout << endl << "original:\n";
hexdump(tcp_seg_data, tcp_seg_len);
ok = false;
continue;
}
if (!compare_tcp_headers_nolen(tcp_seg.header(), tcp_seg_copy2.header())) {
cout << "ERROR: after re-parsing, TCP headers don't match.\n";
ok = false;
continue;
}
if (!compare_tcp_headers(tcp_seg_copy.header(), tcp_seg_copy2.header())) {
cout << "ERROR: after re-parsing, TCP headers don't match.\n";
ok = false;
continue;
}
if (tcp_seg_copy.payload().str() != tcp_seg_copy2.payload().str()) {
cout << "ERROR: after re-parsing, TCP payloads don't match.\n";
ok = false;
continue;
}
}
pcap_close(pcap);
if (!ok) {
return EXIT_FAILURE;
}
} catch (const exception &e) {
cout << "Exception: " << e.what() << endl;
return EXIT_FAILURE;
}
return EXIT_SUCCESS;
}
#ifndef SPONGE_TESTS_TEST_ERR_IF_HH
#define SPONGE_TESTS_TEST_ERR_IF_HH
#include <stdexcept>
#include <string>
static int err_num = 1;
#define test_err_if(c, s) _test_err_if(c, s, __LINE__)
static void _test_err_if(const bool err_condition, const std::string &err_string, const int lineno) {
if (err_condition) {
throw std::runtime_error(err_string + " (at line " + std::to_string(lineno) + ")");
}
++err_num;
}
#endif // SPONGE_TESTS_TEST_ERR_IF_HH
#ifndef SPONGE_TESTS_TEST_SHOULD_BE_HH
#define SPONGE_TESTS_TEST_SHOULD_BE_HH
#include "string_conversions.hh"
#include <optional>
#include <sstream>
#include <stdexcept>
#include <string>
#define test_should_be(act, exp) _test_should_be(act, exp, #act, #exp, __LINE__)
template <typename T>
static void _test_should_be(const T &actual,
const T &expected,
const char *actual_s,
const char *expected_s,
const int lineno) {
if (actual != expected) {
std::ostringstream ss;
ss << "`" << actual_s << "` should have been `" << expected_s << "`, but the former is\n\t" << to_string(actual)
<< "\nand the latter is\n\t" << to_string(expected) << "\n"
<< " (at line " << lineno << ")\n";
throw std::runtime_error(ss.str());
}
}
#endif // SPONGE_TESTS_TEST_SHOULD_BE_HH
#ifndef SPONGE_TESTS_TEST_UTILS_HH
#define SPONGE_TESTS_TEST_UTILS_HH
#include "tcp_header.hh"
#include "util.hh"
#include <cstdint>
#include <iomanip>
#include <iostream>
#include <pcap/pcap.h>
inline void show_ethernet_frame(const uint8_t *pkt, const struct pcap_pkthdr &hdr) {
const auto flags(std::cout.flags());
std::cout << "source MAC: ";
std::cout << std::hex << std::setfill('0');
for (unsigned i = 0; i < 6; ++i) {
std::cout << std::setw(2) << +pkt[i] << (i == 5 ? ' ' : ':');
}
std::cout << " dest MAC: ";
for (unsigned i = 0; i < 6; ++i) {
std::cout << std::setw(2) << +pkt[i + 6] << (i == 5 ? ' ' : ':');
}
std::cout << " ethertype: " << std::setw(2) << +pkt[12] << std::setw(2) << +pkt[13] << '\n';
std::cout << std::dec << "length: " << hdr.len << " captured: " << hdr.caplen << '\n';
std::cout.flags(flags);
}
inline bool compare_tcp_headers_nolen(const TCPHeader &h1, const TCPHeader &h2) {
return h1.sport == h2.sport && h1.dport == h2.dport && h1.seqno == h2.seqno && h1.ackno == h2.ackno &&
h1.urg == h2.urg && h1.ack == h2.ack && h1.psh == h2.psh && h1.rst == h2.rst && h1.syn == h2.syn &&
h1.fin == h2.fin && h1.win == h2.win && h1.uptr == h2.uptr;
}
inline bool compare_tcp_headers(const TCPHeader &h1, const TCPHeader &h2) {
return compare_tcp_headers_nolen(h1, h2) && h1.doff == h2.doff;
}
#endif // SPONGE_TESTS_TEST_UTILS_HH
#ifndef SPONGE_TESTS_TEST_UTILS_IPV4_HH
#define SPONGE_TESTS_TEST_UTILS_IPV4_HH
#include "ipv4_header.hh"
inline bool compare_ip_headers_nolen(const IPv4Header &h1, const IPv4Header &h2) {
return h1.proto == h2.proto && h1.src == h2.src && h1.dst == h2.dst && h1.ver == h2.ver && h1.tos == h2.tos &&
h1.id == h2.id && h1.df == h2.df && h1.mf == h2.mf && h1.offset == h2.offset && h1.ttl == h2.ttl;
}
inline bool compare_ip_headers(const IPv4Header &h1, const IPv4Header &h2) {
return compare_ip_headers_nolen(h1, h2) && h1.hlen == h2.hlen && h1.len == h2.len && h1.cksum == h2.cksum;
}
#endif // SPONGE_TESTS_TEST_UTILS_IPV4_HH
#!/bin/bash
WEB_HASH=`./apps/webget cs144.keithw.org /hasher/xyzzy | tee /dev/stderr | tail -n 1`
CORRECT_HASH="QWx0NhMPkoM/bJr/ohvHXlviFhOyYrYb+qqdOnwLYo4"
if [ "${WEB_HASH}" != "${CORRECT_HASH}" ]; then
echo ERROR: webget returned output that did not match the test\'s expectations
exit 1
fi
exit 0
#include "test_should_be.hh"
#include "util.hh"
#include "wrapping_integers.hh"
#include <cstdint>
#include <exception>
#include <iostream>
#include <optional>
#include <stdexcept>
#include <string>
using namespace std;
int main() {
try {
// Comparing low-number adjacent seqnos
test_should_be(WrappingInt32(3) != WrappingInt32(1), true);
test_should_be(WrappingInt32(3) == WrappingInt32(1), false);
size_t N_REPS = 4096;
auto rd = get_random_generator();
for (size_t i = 0; i < N_REPS; i++) {
uint32_t n = rd();
uint8_t diff = rd();
uint32_t m = n + diff;
test_should_be(WrappingInt32(n) == WrappingInt32(m), n == m);
test_should_be(WrappingInt32(n) != WrappingInt32(m), n != m);
}
} catch (const exception &e) {
cerr << e.what() << endl;
return 1;
}
return EXIT_SUCCESS;
}
#include "test_should_be.hh"
#include "wrapping_integers.hh"
#include <cstdint>
#include <exception>
#include <iostream>
#include <stdexcept>
#include <string>
using namespace std;
int main() {
try {
// Unwrap the first byte after ISN
test_should_be(unwrap(WrappingInt32(1), WrappingInt32(0), 0), 1ul);
// Unwrap the first byte after the first wrap
test_should_be(unwrap(WrappingInt32(1), WrappingInt32(0), UINT32_MAX), (1ul << 32) + 1);
// Unwrap the last byte before the third wrap
test_should_be(unwrap(WrappingInt32(UINT32_MAX - 1), WrappingInt32(0), 3 * (1ul << 32)), 3 * (1ul << 32) - 2);
// Unwrap the 10th from last byte before the third wrap
test_should_be(unwrap(WrappingInt32(UINT32_MAX - 10), WrappingInt32(0), 3 * (1ul << 32)), 3 * (1ul << 32) - 11);
// Non-zero ISN
test_should_be(unwrap(WrappingInt32(UINT32_MAX), WrappingInt32(10), 3 * (1ul << 32)), 3 * (1ul << 32) - 11);
// Big unwrap
test_should_be(unwrap(WrappingInt32(UINT32_MAX), WrappingInt32(0), 0), static_cast<uint64_t>(UINT32_MAX));
// Unwrap a non-zero ISN
test_should_be(unwrap(WrappingInt32(16), WrappingInt32(16), 0), 0ul);
// Big unwrap with non-zero ISN
test_should_be(unwrap(WrappingInt32(15), WrappingInt32(16), 0), static_cast<uint64_t>(UINT32_MAX));
// Big unwrap with non-zero ISN
test_should_be(unwrap(WrappingInt32(0), WrappingInt32(INT32_MAX), 0), static_cast<uint64_t>(INT32_MAX) + 2);
// Barely big unwrap with non-zero ISN
test_should_be(unwrap(WrappingInt32(UINT32_MAX), WrappingInt32(INT32_MAX), 0), static_cast<uint64_t>(1) << 31);
// Nearly big unwrap with non-zero ISN
test_should_be(unwrap(WrappingInt32(UINT32_MAX), WrappingInt32(1ul << 31), 0),
static_cast<uint64_t>(UINT32_MAX) >> 1);
} catch (const exception &e) {
cerr << e.what() << endl;
return 1;
}
return EXIT_SUCCESS;
}
#include "test_should_be.hh"
#include "wrapping_integers.hh"
#include <cstdint>
#include <exception>
#include <iostream>
#include <optional>
#include <stdexcept>
#include <string>
using namespace std;
int main() {
try {
test_should_be(wrap(3 * (1ll << 32), WrappingInt32(0)), WrappingInt32(0));
test_should_be(wrap(3 * (1ll << 32) + 17, WrappingInt32(15)), WrappingInt32(32));
test_should_be(wrap(7 * (1ll << 32) - 2, WrappingInt32(15)), WrappingInt32(13));
} catch (const exception &e) {
cerr << e.what() << endl;
return 1;
}
return EXIT_SUCCESS;
}
#!/bin/bash
show_usage () {
echo "Usage: $0 <start | stop | restart | check> [tunnum ...]"
exit 1
}
start_tun () {
local TUNNUM="$1" TUNDEV="tun$1"
ip tuntap add mode tun user "${SUDO_USER}" name "${TUNDEV}"
ip addr add "${TUN_IP_PREFIX}.${TUNNUM}.1/24" dev "${TUNDEV}"
ip link set dev "${TUNDEV}" up
ip route change "${TUN_IP_PREFIX}.${TUNNUM}.0/24" dev "${TUNDEV}" rto_min 10ms
# Apply NAT (masquerading) only to traffic from CS144's network devices
iptables -t nat -A PREROUTING -s ${TUN_IP_PREFIX}.${TUNNUM}.0/24 -j CONNMARK --set-mark ${TUNNUM}
iptables -t nat -A POSTROUTING -j MASQUERADE -m connmark --mark ${TUNNUM}
echo 1 > /proc/sys/net/ipv4/ip_forward
}
stop_tun () {
local TUNDEV="tun$1"
iptables -t nat -D PREROUTING -s ${TUN_IP_PREFIX}.${1}.0/24 -j CONNMARK --set-mark ${1}
iptables -t nat -D POSTROUTING -j MASQUERADE -m connmark --mark ${1}
ip tuntap del mode tun name "$TUNDEV"
}
start_all () {
while [ ! -z "$1" ]; do
local INTF="$1"; shift
start_tun "$INTF"
done
}
stop_all () {
while [ ! -z "$1" ]; do
local INTF="$1"; shift
stop_tun "$INTF"
done
}
restart_all() {
stop_all "$@"
start_all "$@"
}
check_tun () {
[ "$#" != 1 ] && { echo "bad params in check_tun"; exit 1; }
local TUNDEV="tun${1}"
# make sure tun is healthy: device is up, ip_forward is set, and iptables is configured
ip link show ${TUNDEV} &>/dev/null || return 1
[ "$(cat /proc/sys/net/ipv4/ip_forward)" = "1" ] || return 2
}
check_sudo () {
if [ "$SUDO_USER" = "root" ]; then
echo "please execute this script as a regular user, not as root"
exit 1
fi
if [ -z "$SUDO_USER" ]; then
# if the user didn't call us with sudo, re-execute
exec sudo $0 "$MODE" "$@"
fi
}
# check arguments
if [ -z "$1" ] || ([ "$1" != "start" ] && [ "$1" != "stop" ] && [ "$1" != "restart" ] && [ "$1" != "check" ]); then
show_usage
fi
MODE=$1; shift
# set default argument
if [ "$#" = "0" ]; then
set -- 144 145
fi
# execute 'check' before trying to sudo
# - like start, but exit successfully if everything is OK
if [ "$MODE" = "check" ]; then
declare -a INTFS
MODE="start"
while [ ! -z "$1" ]; do
INTF="$1"; shift
check_tun ${INTF}
RET=$?
if [ "$RET" = "0" ]; then
continue
fi
if [ "$((RET > 1))" = "1" ]; then
MODE="restart"
fi
INTFS+=($INTF)
done
# address only the interfaces that need it
set -- "${INTFS[@]}"
if [ "$#" = "0" ]; then
exit 0
fi
echo -e "[$0] Bringing up tunnels ${INTFS[@]}:"
fi
# sudo if necessary
check_sudo "$@"
# get configuration
. "$(dirname "$0")"/etc/tunconfig
# start, stop, or restart all intfs
eval "${MODE}_all" "$@"
#!/bin/bash
show_usage() {
echo "Usage: $0 <-i|-u> <-c|-s> <-R|-S|-D> [-n|-o]"
echo " [-t <rtto>] [-d <size>] [-w <size>] [-l <rate>] [-L <rate>]"
echo
echo " Option Default"
echo " -- --"
echo " -i or -u Select IP or UDP mode (must specify)"
echo " -c or -s Select client or server mode (must specify)"
echo " -R, -S, -D Receive test, Send test, or Duplex test (must specify)"
echo
echo " -t <rtto> Set rtto to <rtto> ms 12"
echo " -d <size> Set total transfer size to <size> 32"
echo " -w <size> Set window size to <size> 1452"
echo
echo " -l <rate> Set downlink loss to <rate> (float in 0..1) 0"
echo " -L <rate> Set uplink loss to <rate> (float in 0..1) 0"
echo
echo " -n In IP mode, use tcp_native rather tcp_ipv4_ref False"
echo " -o In IP mode, use socat rather than tcp_ipv4_ref False"
[ ! -z "$1" ] && { echo; echo ERROR: "$1"; }
exit 1
}
get_cmdline_options () {
# prepare to use getopts
local OPT= OPTIND=1 OPTARG=
CSMODE= RSDMODE= DATASIZE=32 WINSIZE= IUMODE= USE_IPV4= RTTO="-t 12" LOSS_UP= LOSS_DN=
while getopts "t:oniucsRSDd:w:p:l:L:" OPT; do
case "$OPT" in
i|u)
[ ! -z "$IUMODE" ] && show_usage "Only one of -i and -u is allowed."
IUMODE=$OPT
;;
c|s)
[ ! -z "$CSMODE" ] && show_usage "Only one of -c and -s is allowed."
CSMODE=$OPT
;;
R|S|D)
[ ! -z "$RSDMODE" ] && show_usage "Only one of -R, -S, and -D is allowed."
RSDMODE=$OPT
;;
d)
DATASIZE="$OPTARG"
;;
w)
expand_num "$OPTARG" || show_usage "Bad numeric arg \"$OPTARG\" to -w."
WINSIZE="-w ${NUM_EXPANDED}"
;;
l)
LOSS_DN="$OPTARG"
;;
L)
LOSS_UP="$OPTARG"
;;
n|o)
[ ! -z "$USE_IPV4" ] && show_usage "Only one of -n and -o is allowed."
USE_IPV4=$OPT
;;
t)
expand_num "$OPTARG" || show_usage "Bad numeric arg \"$OPTARG\" to -t."
RTTO="-t ${NUM_EXPANDED}"
;;
*)
show_usage "Unknown option $OPT"
;;
esac
done
if [ "$OPTIND" != $(($# + 1)) ]; then
show_usage "Extraneous arguments detected."
fi
if [ -z "$CSMODE" ] || [ -z "$RSDMODE" ] || [ -z "$IUMODE" ]; then
show_usage "You must specify either -i or -u, either -c or -s, and one of -R, -S, and -D."
fi
if [ ! -z "$USE_IPV4" ] && [ "$IUMODE" != "i" ]; then
show_usage "-n and -o may only be specified in IP mode (-i)."
fi
# loss param args depend on whether we're applying to ref or test program (test uplink == loss downlink)
{ [ ! -z "$USE_IPV4" ] && local LD_SWITCH="-Ld" LU_SWITCH="-Lu"; } || local LD_SWITCH="-Lu" LU_SWITCH="-Ld"
[ ! -z "$LOSS_DN" ] && LOSS_DN="${LD_SWITCH} ${LOSS_DN}"
[ ! -z "$LOSS_UP" ] && LOSS_UP="${LU_SWITCH} ${LOSS_UP}"
}
expand_num () {
[ "$#" != "1" ] && { echo "bad args"; exit 1; }
NUM_EXPANDED=$(numfmt --from=iec "$1" 2>/dev/null)
return $?
}
_socat_listen () {
coproc socat tcp4-listen:${SERVER_PORT},reuseaddr,reuseport,linger=2 stdio >"$1" <"$2" && sleep 0.1
set +u
[ -z "$COPROC_PID" ] && { echo "Error in _socat_listen"; exit 1; }
set -u
}
_socat_connect () {
socat tcp4-connect:${TEST_HOST}:${SERVER_PORT},reuseaddr,reuseport,linger=2 stdio >"$1" <"$2" ||
{ echo "Error in _socat_connect"; exit 1; }
}
_rt_listen () {
coproc $3 -l $4 ${SERVER_PORT} >"$1" <"$2" && sleep 0.1
set +u
[ -z "$COPROC_PID" ] && { echo "Error in _rt_listen"; exit 1; }
set -u
}
_rt_connect () {
$3 $4 ${SERVER_PORT} >"$1" <"$2" || { echo "Error in _rt_connect"; exit 1; }
}
test_listen () {
[ "$#" != 2 ] && { echo "bad args"; exit 1; }
_rt_listen "$1" "$2" "${TEST_PROG}" "${TEST_HOST}"
}
test_connect () {
[ "$#" != 2 ] && { echo "bad args"; exit 1; }
_rt_connect "$1" "$2" "${TEST_PROG}" "${REF_HOST}"
}
ref_listen () {
[ "$#" != 2 ] && { echo "bad args"; exit 1; }
if [ "$IUMODE" = "u" ] || [ -z "$USE_IPV4" ] || [ "$USE_IPV4" = "n" ]; then
_rt_listen "$1" "$2" "${REF_PROG}" "${REF_HOST}"
else
_socat_listen "$1" "$2"
fi
}
ref_connect () {
[ "$#" != 2 ] && { echo "bad args"; exit 1; }
if [ "$IUMODE" = "u" ] || [ -z "$USE_IPV4" ] || [ "$USE_IPV4" = "n" ]; then
_rt_connect "$1" "$2" "${REF_PROG}" "${TEST_HOST}"
else
_socat_connect "$1" "$2"
fi
}
hash_file () {
[ "$#" != "1" ] && { echo "bad args"; exit 1; }
sha256sum "$1" | cut -d \ -f 1
}
make_test_file () {
if expand_num "$2"; then
dd status=none if=/dev/urandom of="$1" bs="${NUM_EXPANDED}" count=1 || { echo "Failed to make test file."; exit 1; }
else
# can't interpret as a number, so interpret as literal data to send
echo -en "$2" >"$1"
fi
}
exit_cleanup () {
set +u
rm -f "${TEST_IN_FILE}" "${TEST_OUT_FILE}" "${TEST_OUT2_FILE}"
[ ! -z "$COPROC_PID" ] && kill ${COPROC_PID}
}
# make sure tun device is running
ip link show tun144 &>/dev/null || { echo "please enable tun144 and re-run"; exit 1; }
ip link show tun145 &>/dev/null || { echo "please enable tun145 and re-run"; exit 1; }
set -u
trap exit_cleanup EXIT
get_cmdline_options "$@"
. "$(dirname "$0")"/etc/tunconfig
REF_HOST=${TUN_IP_PREFIX}.144.1
TEST_HOST=${TUN_IP_PREFIX}.144.1
SERVER_PORT=$(($((RANDOM % 50000)) + 1025))
if [ "$IUMODE" = "i" ]; then
# IPv4 mode
TEST_HOST=${TUN_IP_PREFIX}.144.9
if [ -z "$USE_IPV4" ]; then
REF_HOST=${TUN_IP_PREFIX}.145.9
REF_PROG="./apps/tcp_ipv4 ${RTTO} ${WINSIZE} ${LOSS_UP} ${LOSS_DN} -d tun145 -a ${REF_HOST}"
TEST_PROG="./apps/tcp_ipv4 ${RTTO} ${WINSIZE} -d tun144 -a ${TEST_HOST}"
else
REF_PROG="./apps/tcp_native"
TEST_PROG="./apps/tcp_ipv4 ${RTTO} ${WINSIZE} ${LOSS_UP} ${LOSS_DN} -d tun144 -a ${TEST_HOST}"
fi
else
# UDP mode
REF_PROG="./apps/tcp_udp ${RTTO} ${WINSIZE} ${LOSS_UP} ${LOSS_DN}"
TEST_PROG="./apps/tcp_udp ${RTTO} ${WINSIZE}"
fi
TEST_OUT_FILE=$(mktemp)
TEST_IN_FILE=$(mktemp)
make_test_file "${TEST_IN_FILE}" "${DATASIZE}"
HASH_IN=$(sha256sum ${TEST_IN_FILE} | cut -d \ -f 1)
HASH_OUT2=
case "$RSDMODE" in
S) # test sending
if [ "$CSMODE" = "c" ]; then
ref_listen "${TEST_OUT_FILE}" /dev/null
test_connect /dev/null "${TEST_IN_FILE}"
else
test_listen /dev/null "${TEST_IN_FILE}"
ref_connect "${TEST_OUT_FILE}" /dev/null
fi
;;
R) # test receiving
if [ "$CSMODE" = "c" ]; then
ref_listen /dev/null "${TEST_IN_FILE}"
test_connect "${TEST_OUT_FILE}" /dev/null
else
test_listen "${TEST_OUT_FILE}" /dev/null
ref_connect /dev/null "${TEST_IN_FILE}"
fi
;;
D) # test full-duplex
TEST_OUT2_FILE=$(mktemp)
if [ "$CSMODE" = "c" ]; then
ref_listen "${TEST_OUT_FILE}" "${TEST_IN_FILE}"
test_connect "${TEST_OUT2_FILE}" "${TEST_IN_FILE}"
else
test_listen "${TEST_OUT_FILE}" "${TEST_IN_FILE}"
ref_connect "${TEST_OUT2_FILE}" "${TEST_IN_FILE}"
fi
HASH_OUT2=$(hash_file "${TEST_OUT2_FILE}")
;;
esac
if ! wait; then
echo ERROR: subprocess failed
exit 1
fi
HASH_OUT=$(hash_file ${TEST_OUT_FILE})
if [ ! -z "${HASH_OUT2}" ] && [ "${HASH_OUT}" != "${HASH_OUT2}" ] || [ "${HASH_IN}" != "${HASH_OUT}" ]; then
echo ERROR: "$HASH_IN" neq "$HASH_OUT" or "$HASH_OUT2"
exit 1
fi
exit 0
Lab 0 Writeup
=============
My name: [your name here]
My SUNet ID: [your sunetid here]
This lab took me about [n] hours to do.
My secret code from section 2.1 was: [code here]
- Optional: I had unexpected difficulty with: [describe]
- Optional: I think you could make this lab better by: [describe]
- Optional: I was surprised by: [describe]
- Optional: I'm not sure about: [describe]
Lab 1 Writeup
=============
My name: [your name here]
My SUNet ID: [your sunetid here]
This lab took me about [n] hours to do.
Program Structure and Design of the StreamReassembler:
[]
Implementation Challenges:
[]
Remaining Bugs:
[]
- Optional: I had unexpected difficulty with: [describe]
- Optional: I think you could make this lab better by: [describe]
- Optional: I was surprised by: [describe]
- Optional: I'm not sure about: [describe]
Lab 2 Writeup
=============
My name: [your name here]
My SUNet ID: [your sunetid here]
This lab took me about [n] hours to do.
Program Structure and Design of the TCPReceiver:
[]
Implementation Challenges:
[]
Remaining Bugs:
[]
- Optional: I had unexpected difficulty with: [describe]
- Optional: I think you could make this lab better by: [describe]
- Optional: I was surprised by: [describe]
- Optional: I'm not sure about: [describe]
Lab 3 Writeup
=============
My name: [your name here]
My SUNet ID: [your sunetid here]
This lab took me about [n] hours to do.
Program Structure and Design of the TCPSender:
[]
Implementation Challenges:
[]
Remaining Bugs:
[]
- Optional: I had unexpected difficulty with: [describe]
- Optional: I think you could make this lab better by: [describe]
- Optional: I was surprised by: [describe]
- Optional: I'm not sure about: [describe]
Lab 4 Writeup
=============
My name: [your name here]
My SUNet ID: [your sunetid here]
This lab took me about [n] hours to do. I [did/did not] attend the lab session.
Program Structure and Design of the TCPConnection:
[]
Implementation Challenges:
[]
Remaining Bugs:
[]
- Optional: I had unexpected difficulty with: [describe]
- Optional: I think you could make this lab better by: [describe]
- Optional: I was surprised by: [describe]
- Optional: I'm not sure about: [describe]
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment