├── etc ├── tunconfig ├── sponge_small.png ├── build_defs.cmake ├── sponge_doxygen.css ├── doxygen.cmake ├── cppcheck.cmake ├── rfc-doxygen-web.tag.xml ├── build_type.cmake ├── cflags.cmake ├── clang_format.cmake ├── clang_tidy.cmake └── Doxyfile.in ├── writeups ├── lab4.md ├── lab0.md ├── lab3.md ├── lab2.md └── lab1.md ├── doctests ├── address_example_2.cc ├── address_example_1.cc ├── address_example_3.cc ├── CMakeLists.txt ├── parser_dt.cc ├── socket_example_3.cc ├── socket_dt.cc ├── address_dt.cc ├── socket_example_1.cc ├── socket_example_2.cc └── parser_example.cc ├── tests ├── ipv4_parser.data ├── webget_t.sh ├── send_equivalence_checker.hh ├── test_err_if.hh ├── test_utils_ipv4.hh ├── wrapping_integers_wrap.cc ├── fsm_retx.hh ├── string_conversions.hh ├── test_should_be.hh ├── wrapping_integers_cmp.cc ├── byte_stream_construction.cc ├── byte_stream_many_writes.cc ├── test_utils.hh ├── CMakeLists.txt ├── fsm_stream_reassembler_single.cc ├── wrapping_integers_unwrap.cc ├── fsm_stream_reassembler_cap.cc ├── fsm_stream_reassembler_win.cc ├── fsm_stream_reassembler_seq.cc ├── recv_close.cc ├── fsm_listen_relaxed.cc ├── send_equivalence_checker.cc ├── fsm_listen.cc ├── byte_stream_capacity.cc ├── fsm_retx_relaxed.cc ├── fsm_retx.cc ├── fsm_loopback_win.cc ├── send_ack.cc ├── fsm_loopback.cc ├── fsm_ack_rst_win_relaxed.cc ├── fsm_stream_reassembler_dup.cc ├── fsm_ack_rst_win.cc ├── send_connect.cc ├── tcp_expectation_forward.hh ├── fsm_connect_relaxed.cc ├── fsm_passive_close.cc ├── fsm_winsize.cc ├── fsm_connect.cc ├── recv_connect.cc └── byte_stream_test_harness.hh ├── .vscode ├── settings.json ├── c_cpp_properties.json ├── tasks.json └── launch.json ├── libsponge ├── CMakeLists.txt ├── util │ ├── tun.hh │ ├── tun.cc │ ├── parser.cc │ ├── parser.hh │ ├── util.hh │ ├── buffer.cc │ ├── address.hh │ ├── eventloop.hh │ └── file_descriptor.cc ├── tcp_helpers │ ├── ipv4_datagram.hh │ ├── tunfd_adapter.hh │ ├── ipv4_datagram.cc │ ├── tcp_segment.hh │ ├── tcp_segment.cc │ ├── tcp_config.hh │ ├── fd_adapter.hh │ ├── fd_adapter.cc │ ├── lossy_fd_adapter.hh │ ├── tcp_header.hh │ ├── ipv4_header.hh │ ├── tcp_state.hh │ ├── tunfd_adapter.cc │ └── tcp_header.cc ├── tcp_receiver.cc ├── wrapping_integers.cc ├── byte_stream.cc ├── byte_stream.hh ├── stream_reassembler.hh ├── wrapping_integers.hh ├── tcp_receiver.hh └── tcp_connection.hh ├── apps ├── bidirectional_stream_copy.hh ├── CMakeLists.txt ├── tcp_native.cc ├── tun.cc ├── webget.cc ├── tcp_benchmark.cc └── bidirectional_stream_copy.cc ├── .gitignore ├── CMakeLists.txt └── tun.sh /etc/tunconfig: -------------------------------------------------------------------------------- 1 | TUN_IP_PREFIX=169.254 2 | -------------------------------------------------------------------------------- /writeups/lab4.md: -------------------------------------------------------------------------------- 1 | Lab 4 Writeup 2 | ============= 3 | 4 | 12 -------------------------------------------------------------------------------- /doctests/address_example_2.cc: -------------------------------------------------------------------------------- 1 | const Address a_dns_server("18.71.0.151", 53); 2 | -------------------------------------------------------------------------------- /doctests/address_example_1.cc: -------------------------------------------------------------------------------- 1 | const Address google_webserver("www.google.com", "https"); 2 | -------------------------------------------------------------------------------- /etc/sponge_small.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sunhuiquan/CS144_lab/HEAD/etc/sponge_small.png -------------------------------------------------------------------------------- /doctests/address_example_3.cc: -------------------------------------------------------------------------------- 1 | const uint32_t a_dns_server_numeric = a_dns_server.ipv4_numeric(); 2 | -------------------------------------------------------------------------------- /tests/ipv4_parser.data: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sunhuiquan/CS144_lab/HEAD/tests/ipv4_parser.data -------------------------------------------------------------------------------- /doctests/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_sponge_exec (address_dt) 2 | add_sponge_exec (parser_dt) 3 | add_sponge_exec (socket_dt) 4 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "files.associations": { 3 | "optional": "cpp", 4 | "iostream": "cpp" 5 | } 6 | } -------------------------------------------------------------------------------- /libsponge/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | file (GLOB LIB_SOURCES "*.cc" "util/*.cc" "tcp_helpers/*.cc") 2 | add_library (sponge STATIC ${LIB_SOURCES}) 3 | -------------------------------------------------------------------------------- /etc/build_defs.cmake: -------------------------------------------------------------------------------- 1 | find_library (LIBPCAP pcap) 2 | find_library (LIBPTHREAD pthread) 3 | macro (add_sponge_exec exec_name) 4 | add_executable ("${exec_name}" "${exec_name}.cc") 5 | target_link_libraries ("${exec_name}" ${ARGN} sponge ${LIBPTHREAD}) 6 | endmacro (add_sponge_exec) 7 | -------------------------------------------------------------------------------- /doctests/parser_dt.cc: -------------------------------------------------------------------------------- 1 | #include "parser.hh" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | int main() { 9 | try { 10 | #include "parser_example.cc" 11 | } catch (...) { 12 | return EXIT_FAILURE; 13 | } 14 | return EXIT_SUCCESS; 15 | } 16 | -------------------------------------------------------------------------------- /apps/bidirectional_stream_copy.hh: -------------------------------------------------------------------------------- 1 | #ifndef SPONGE_APPS_BIDIRECTIONAL_STREAM_COPY_HH 2 | #define SPONGE_APPS_BIDIRECTIONAL_STREAM_COPY_HH 3 | 4 | #include "socket.hh" 5 | 6 | //! Copy socket input/output to stdin/stdout until finished 7 | void bidirectional_stream_copy(Socket &socket); 8 | 9 | #endif // SPONGE_APPS_BIDIRECTIONAL_STREAM_COPY_HH 10 | -------------------------------------------------------------------------------- /apps/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_library (stream_copy STATIC bidirectional_stream_copy.cc) 2 | 3 | add_sponge_exec (udp_tcpdump ${LIBPCAP}) 4 | add_sponge_exec (tcp_native stream_copy) 5 | add_sponge_exec (tun) 6 | add_sponge_exec (tcp_udp stream_copy) 7 | add_sponge_exec (tcp_ipv4 stream_copy) 8 | add_sponge_exec (webget) 9 | add_sponge_exec (tcp_benchmark) 10 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | build 2 | 3 | #先决条件 4 | * .d 5 | 6 | #编译的目标文件 7 | * .slo 8 | * .lo 9 | * .o 10 | * .obj 11 | 12 | #预编译头 13 | * .gch 14 | * .pch 15 | 16 | #编译的动态库 17 | * .so 18 | * .dylib 19 | * .dll 20 | 21 | # Fortran的模块文件 22 | * .mod 23 | * .smod 24 | 25 | #编译的静态库 26 | * .lai 27 | * .la 28 | * .a 29 | * .lib 30 | 31 | #可执行文件 32 | * .exe 33 | * .out 34 | * .app -------------------------------------------------------------------------------- /tests/webget_t.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | WEB_HASH=`./apps/webget cs144.keithw.org /hasher/xyzzy | tee /dev/stderr | tail -n 1` 4 | CORRECT_HASH="QWx0NhMPkoM/bJr/ohvHXlviFhOyYrYb+qqdOnwLYo4" 5 | 6 | if [ "${WEB_HASH}" != "${CORRECT_HASH}" ]; then 7 | echo ERROR: webget returned output that did not match the test\'s expectations 8 | exit 1 9 | fi 10 | exit 0 11 | -------------------------------------------------------------------------------- /etc/sponge_doxygen.css: -------------------------------------------------------------------------------- 1 | html, body { background-color: #F8F8F8; } 2 | div.textblock>p,div.memdoc>p,dl.section.note>dd { max-width: 750px; } 3 | div.line,pre.fragment { line-height: 1.5; } 4 | div.contents { 5 | padding: 12px; 6 | margin-top: auto; 7 | margin-bottom: auto; 8 | margin-left: 3%; 9 | margin-right: 6%; 10 | border-radius: 8px; 11 | } 12 | -------------------------------------------------------------------------------- /writeups/lab0.md: -------------------------------------------------------------------------------- 1 | Lab 0 Writeup 2 | ============= 3 | 4 | My name: [your name here] 5 | 6 | My SUNet ID: [your sunetid here] 7 | 8 | This lab took me about [n] hours to do. 9 | 10 | My secret code from section 2.1 was: [code here] 11 | 12 | - Optional: I had unexpected difficulty with: [describe] 13 | 14 | - Optional: I think you could make this lab better by: [describe] 15 | 16 | - Optional: I was surprised by: [describe] 17 | 18 | - Optional: I'm not sure about: [describe] 19 | -------------------------------------------------------------------------------- /doctests/socket_example_3.cc: -------------------------------------------------------------------------------- 1 | // create a pair of stream sockets 2 | std::array fds{}; 3 | SystemCall("socketpair", ::socketpair(AF_UNIX, SOCK_STREAM, 0, fds.data())); 4 | LocalStreamSocket pipe1{FileDescriptor(fds[0])}, pipe2{FileDescriptor(fds[1])}; 5 | 6 | pipe1.write("hi there"); 7 | auto recvd = pipe2.read(); 8 | 9 | pipe2.write("hi yourself"); 10 | auto recvd2 = pipe1.read(); 11 | 12 | if (recvd != "hi there" || recvd2 != "hi yourself") { 13 | throw std::runtime_error("wrong data received"); 14 | } 15 | -------------------------------------------------------------------------------- /.vscode/c_cpp_properties.json: -------------------------------------------------------------------------------- 1 | { 2 | "configurations": [ 3 | { 4 | "name": "Linux", 5 | "includePath": [ 6 | "${workspaceFolder}/**" 7 | ], 8 | "defines": [], 9 | "compilerPath": "/usr/bin/gcc", 10 | "cStandard": "gnu17", 11 | "cppStandard": "gnu++17", 12 | "intelliSenseMode": "linux-gcc-x64", 13 | "compileCommands": "${workspaceFolder}/build/compile_commands.json" 14 | } 15 | ], 16 | "version": 4 17 | } -------------------------------------------------------------------------------- /libsponge/util/tun.hh: -------------------------------------------------------------------------------- 1 | #ifndef SPONGE_LIBSPONGE_TUN_HH 2 | #define SPONGE_LIBSPONGE_TUN_HH 3 | 4 | #include "file_descriptor.hh" 5 | 6 | #include 7 | 8 | //! A FileDescriptor to a [Linux TUN](https://www.kernel.org/doc/Documentation/networking/tuntap.txt) device 9 | class TunFD : public FileDescriptor { 10 | public: 11 | //! Open an existing persistent [TUN device](https://www.kernel.org/doc/Documentation/networking/tuntap.txt). 12 | explicit TunFD(const std::string &devname); 13 | }; 14 | 15 | #endif // SPONGE_LIBSPONGE_TUN_HH 16 | -------------------------------------------------------------------------------- /tests/send_equivalence_checker.hh: -------------------------------------------------------------------------------- 1 | #ifndef SPONGE_LIBSPONGE_SEND_EQUIVALENCE_CHECKER_HH 2 | #define SPONGE_LIBSPONGE_SEND_EQUIVALENCE_CHECKER_HH 3 | 4 | #include "tcp_segment.hh" 5 | 6 | #include 7 | #include 8 | 9 | class SendEquivalenceChecker { 10 | std::deque as{}; 11 | std::deque bs{}; 12 | 13 | public: 14 | void submit_a(TCPSegment &seg); 15 | void submit_b(TCPSegment &seg); 16 | void check_empty() const; 17 | }; 18 | 19 | #endif // SPONGE_LIBSPONGE_SEND_EQUIVALENCE_CHECKER_HH 20 | -------------------------------------------------------------------------------- /doctests/socket_dt.cc: -------------------------------------------------------------------------------- 1 | #include "socket.hh" 2 | 3 | #include "address.hh" 4 | #include "util.hh" 5 | 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | int main() { 14 | try { 15 | { 16 | #include "socket_example_1.cc" 17 | } { 18 | #include "socket_example_2.cc" 19 | } { 20 | #include "socket_example_3.cc" 21 | } 22 | } catch (...) { 23 | return EXIT_FAILURE; 24 | } 25 | return EXIT_SUCCESS; 26 | } 27 | -------------------------------------------------------------------------------- /tests/test_err_if.hh: -------------------------------------------------------------------------------- 1 | #ifndef SPONGE_TESTS_TEST_ERR_IF_HH 2 | #define SPONGE_TESTS_TEST_ERR_IF_HH 3 | 4 | #include 5 | #include 6 | 7 | static int err_num = 1; 8 | 9 | #define test_err_if(c, s) _test_err_if(c, s, __LINE__) 10 | 11 | static void _test_err_if(const bool err_condition, const std::string &err_string, const int lineno) { 12 | if (err_condition) { 13 | throw std::runtime_error(err_string + " (at line " + std::to_string(lineno) + ")"); 14 | } 15 | ++err_num; 16 | } 17 | 18 | #endif // SPONGE_TESTS_TEST_ERR_IF_HH 19 | -------------------------------------------------------------------------------- /writeups/lab3.md: -------------------------------------------------------------------------------- 1 | Lab 3 Writeup 2 | ============= 3 | 4 | My name: [your name here] 5 | 6 | My SUNet ID: [your sunetid here] 7 | 8 | This lab took me about [n] hours to do. 9 | 10 | Program Structure and Design of the TCPSender: 11 | [] 12 | 13 | Implementation Challenges: 14 | [] 15 | 16 | Remaining Bugs: 17 | [] 18 | 19 | - Optional: I had unexpected difficulty with: [describe] 20 | 21 | - Optional: I think you could make this lab better by: [describe] 22 | 23 | - Optional: I was surprised by: [describe] 24 | 25 | - Optional: I'm not sure about: [describe] 26 | -------------------------------------------------------------------------------- /writeups/lab2.md: -------------------------------------------------------------------------------- 1 | Lab 2 Writeup 2 | ============= 3 | 4 | My name: [your name here] 5 | 6 | My SUNet ID: [your sunetid here] 7 | 8 | This lab took me about [n] hours to do. 9 | 10 | Program Structure and Design of the TCPReceiver: 11 | [] 12 | 13 | Implementation Challenges: 14 | [] 15 | 16 | Remaining Bugs: 17 | [] 18 | 19 | - Optional: I had unexpected difficulty with: [describe] 20 | 21 | - Optional: I think you could make this lab better by: [describe] 22 | 23 | - Optional: I was surprised by: [describe] 24 | 25 | - Optional: I'm not sure about: [describe] 26 | -------------------------------------------------------------------------------- /writeups/lab1.md: -------------------------------------------------------------------------------- 1 | Lab 1 Writeup 2 | ============= 3 | 4 | My name: [your name here] 5 | 6 | My SUNet ID: [your sunetid here] 7 | 8 | This lab took me about [n] hours to do. 9 | 10 | Program Structure and Design of the StreamReassembler: 11 | [] 12 | 13 | Implementation Challenges: 14 | [] 15 | 16 | Remaining Bugs: 17 | [] 18 | 19 | - Optional: I had unexpected difficulty with: [describe] 20 | 21 | - Optional: I think you could make this lab better by: [describe] 22 | 23 | - Optional: I was surprised by: [describe] 24 | 25 | - Optional: I'm not sure about: [describe] 26 | -------------------------------------------------------------------------------- /etc/doxygen.cmake: -------------------------------------------------------------------------------- 1 | find_package (Doxygen) 2 | if (DOXYGEN_FOUND) 3 | if (Doxygen_dot_FOUND) 4 | set (DOXYGEN_DOT_FOUND YES) 5 | else (NOT Doxygen_dot_FOUND) 6 | set (DOXYGEN_DOT_FOUND NO) 7 | endif (Doxygen_dot_FOUND) 8 | configure_file ("${PROJECT_SOURCE_DIR}/etc/Doxyfile.in" "${PROJECT_BINARY_DIR}/Doxyfile" @ONLY) 9 | add_custom_target (doc "${DOXYGEN_EXECUTABLE}" "${PROJECT_BINARY_DIR}/Doxyfile" 10 | WORKING_DIRECTORY "${PROJECT_BINARY_DIR}" 11 | COMMENT "Generate docs using Doxygen" VERBATIM) 12 | endif () 13 | -------------------------------------------------------------------------------- /doctests/address_dt.cc: -------------------------------------------------------------------------------- 1 | #include "address.hh" 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | int main() { 8 | try { 9 | #include "address_example_1.cc" 10 | #include "address_example_2.cc" 11 | #include "address_example_3.cc" 12 | if ((google_webserver.port() != 443) || (a_dns_server_numeric != 0x12'47'00'97)) { 13 | throw std::runtime_error("unexpected value"); 14 | } 15 | } catch (const std::exception &e) { 16 | std::cerr << "This test requires Internet access and working DNS.\n"; 17 | std::cerr << "Error: " << e.what() << "\n"; 18 | return EXIT_FAILURE; 19 | } 20 | return EXIT_SUCCESS; 21 | } 22 | -------------------------------------------------------------------------------- /tests/test_utils_ipv4.hh: -------------------------------------------------------------------------------- 1 | #ifndef SPONGE_TESTS_TEST_UTILS_IPV4_HH 2 | #define SPONGE_TESTS_TEST_UTILS_IPV4_HH 3 | 4 | #include "ipv4_header.hh" 5 | 6 | inline bool compare_ip_headers_nolen(const IPv4Header &h1, const IPv4Header &h2) { 7 | return h1.proto == h2.proto && h1.src == h2.src && h1.dst == h2.dst && h1.ver == h2.ver && h1.tos == h2.tos && 8 | h1.id == h2.id && h1.df == h2.df && h1.mf == h2.mf && h1.offset == h2.offset && h1.ttl == h2.ttl; 9 | } 10 | 11 | inline bool compare_ip_headers(const IPv4Header &h1, const IPv4Header &h2) { 12 | return compare_ip_headers_nolen(h1, h2) && h1.hlen == h2.hlen && h1.len == h2.len && h1.cksum == h2.cksum; 13 | } 14 | 15 | #endif // SPONGE_TESTS_TEST_UTILS_IPV4_HH 16 | -------------------------------------------------------------------------------- /tests/wrapping_integers_wrap.cc: -------------------------------------------------------------------------------- 1 | #include "test_should_be.hh" 2 | #include "wrapping_integers.hh" 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | using namespace std; 12 | 13 | int main() { 14 | try { 15 | test_should_be(wrap(3 * (1ll << 32), WrappingInt32(0)), WrappingInt32(0)); 16 | test_should_be(wrap(3 * (1ll << 32) + 17, WrappingInt32(15)), WrappingInt32(32)); 17 | test_should_be(wrap(7 * (1ll << 32) - 2, WrappingInt32(15)), WrappingInt32(13)); 18 | } catch (const exception &e) { 19 | cerr << e.what() << endl; 20 | return 1; 21 | } 22 | 23 | return EXIT_SUCCESS; 24 | } 25 | -------------------------------------------------------------------------------- /etc/cppcheck.cmake: -------------------------------------------------------------------------------- 1 | if (NOT CPPCHECK) 2 | if (DEFINED ENV{CPPCHECK}) 3 | set (CPPCHECK_TMP $ENV{CPPCHECK}) 4 | else (NOT DEFINED ENV{CPPCHECK}) 5 | set (CPPCHECK_TMP cppcheck) 6 | endif () 7 | 8 | # is cppcheck available? 9 | execute_process (COMMAND ${CPPCHECK_TMP} --version RESULT_VARIABLE CPPCHECK_RESULT OUTPUT_VARIABLE CPPCHECK_OUTPUT) 10 | if (${CPPCHECK_RESULT} EQUAL 0) 11 | message (STATUS "Found cppcheck") 12 | set (CPPCHECK ${CPPCHECK_TMP} CACHE STRING "cppcheck executable name") 13 | endif() 14 | endif (NOT CPPCHECK) 15 | 16 | if (DEFINED CPPCHECK) 17 | add_custom_target (cppcheck ${CPPCHECK} --enable=all --project="${PROJECT_BINARY_DIR}/compile_commands.json") 18 | endif (DEFINED CPPCHECK) 19 | -------------------------------------------------------------------------------- /etc/rfc-doxygen-web.tag.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | rfc 4 | 5 | 6 | rfc791 7 | rfc791 8 | 9 | 10 | 11 | 12 | 13 | rfc793 14 | rfc793 15 | 16 | 17 | 18 | 19 | 20 | rfc6298 21 | rfc6298 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /doctests/socket_example_1.cc: -------------------------------------------------------------------------------- 1 | const uint16_t portnum = ((std::random_device()()) % 50000) + 1025; 2 | 3 | // create a UDP socket and bind it to a local address 4 | UDPSocket sock1{}; 5 | sock1.bind(Address("127.0.0.1", portnum)); 6 | 7 | // create another UDP socket and send a datagram to the first socket without connecting 8 | UDPSocket sock2{}; 9 | sock2.sendto(Address("127.0.0.1", portnum), "hi there"); 10 | 11 | // receive sent datagram, connect the socket to the peer's address, and send a response 12 | auto recvd = sock1.recv(); 13 | sock1.connect(recvd.source_address); 14 | sock1.send("hi yourself"); 15 | 16 | auto recvd2 = sock2.recv(); 17 | 18 | if (recvd.payload != "hi there" || recvd2.payload != "hi yourself") { 19 | throw std::runtime_error("wrong data received"); 20 | } 21 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required (VERSION 2.8.5) 2 | cmake_policy (SET CMP0054 NEW) 3 | project (Sponge) 4 | 5 | include (etc/build_defs.cmake) 6 | include (etc/build_type.cmake) 7 | include (etc/cflags.cmake) 8 | 9 | include (etc/doxygen.cmake) 10 | 11 | include (etc/clang_format.cmake) 12 | include (etc/clang_tidy.cmake) 13 | include (etc/cppcheck.cmake) 14 | 15 | include_directories ("${PROJECT_SOURCE_DIR}/libsponge/util") 16 | include_directories ("${PROJECT_SOURCE_DIR}/libsponge/tcp_helpers") 17 | include_directories ("${PROJECT_SOURCE_DIR}/libsponge") 18 | 19 | add_subdirectory ("${PROJECT_SOURCE_DIR}/libsponge") 20 | 21 | add_subdirectory ("${PROJECT_SOURCE_DIR}/apps") 22 | 23 | add_subdirectory ("${PROJECT_SOURCE_DIR}/tests") 24 | 25 | add_subdirectory ("${PROJECT_SOURCE_DIR}/doctests") 26 | 27 | include (etc/tests.cmake) 28 | -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "2.0.0", 3 | "options": { 4 | "cwd": "${workspaceFolder}/build" 5 | }, 6 | "tasks": [ 7 | { 8 | "type": "shell", 9 | "label": "cmake", 10 | "command": "cmake", 11 | "args": [ 12 | "..", 13 | "-DCMAKE_BUILD_TYPE=Debug" 14 | ] 15 | }, 16 | { 17 | "label": "make", 18 | "group": { 19 | "kind": "build", 20 | "isDefault": true 21 | }, 22 | "command": "make", 23 | "args": [ 24 | "-j8" 25 | ] 26 | }, 27 | { 28 | "label": "Build", 29 | "dependsOrder": "sequence", 30 | "dependsOn": [ 31 | "cmake", 32 | "make" 33 | ] 34 | } 35 | ] 36 | } -------------------------------------------------------------------------------- /doctests/socket_example_2.cc: -------------------------------------------------------------------------------- 1 | const uint16_t portnum = ((std::random_device()()) % 50000) + 1025; 2 | 3 | // create a TCP socket, bind it to a local address, and listen 4 | TCPSocket sock1{}; 5 | sock1.bind(Address("127.0.0.1", portnum)); 6 | sock1.listen(1); 7 | 8 | // create another socket and connect to the first one 9 | TCPSocket sock2{}; 10 | sock2.connect(Address("127.0.0.1", portnum)); 11 | 12 | // accept the connection 13 | auto sock3 = sock1.accept(); 14 | sock3.write("hi there"); 15 | 16 | auto recvd = sock2.read(); 17 | sock2.write("hi yourself"); 18 | 19 | auto recvd2 = sock3.read(); 20 | 21 | sock1.close(); // don't need to accept any more connections 22 | sock2.close(); // you can call close(2) on a socket 23 | sock3.shutdown(SHUT_RDWR); // you can also shutdown(2) a socket 24 | if (recvd != "hi there" || recvd2 != "hi yourself") { 25 | throw std::runtime_error("wrong data received"); 26 | } 27 | -------------------------------------------------------------------------------- /libsponge/tcp_helpers/ipv4_datagram.hh: -------------------------------------------------------------------------------- 1 | #ifndef SPONGE_LIBSPONGE_IPV4_DATAGRAM_HH 2 | #define SPONGE_LIBSPONGE_IPV4_DATAGRAM_HH 3 | 4 | #include "buffer.hh" 5 | #include "ipv4_header.hh" 6 | 7 | //! \brief [IPv4](\ref rfc::rfc791) Internet datagram 8 | class IPv4Datagram { 9 | private: 10 | IPv4Header _header{}; 11 | BufferList _payload{}; 12 | 13 | public: 14 | //! \brief Parse the segment from a string 15 | ParseResult parse(const Buffer buffer); 16 | 17 | //! \brief Serialize the segment to a string 18 | BufferList serialize() const; 19 | 20 | //! \name Accessors 21 | //!@{ 22 | const IPv4Header &header() const { return _header; } 23 | IPv4Header &header() { return _header; } 24 | 25 | const BufferList &payload() const { return _payload; } 26 | BufferList &payload() { return _payload; } 27 | //!@} 28 | }; 29 | 30 | using InternetDatagram = IPv4Datagram; 31 | 32 | #endif // SPONGE_LIBSPONGE_IPV4_DATAGRAM_HH 33 | -------------------------------------------------------------------------------- /tests/fsm_retx.hh: -------------------------------------------------------------------------------- 1 | #ifndef SPONGE_TESTS_FSM_RETX_HH 2 | #define SPONGE_TESTS_FSM_RETX_HH 3 | 4 | #include "tcp_expectation.hh" 5 | #include "tcp_fsm_test_harness.hh" 6 | #include "tcp_header.hh" 7 | #include "tcp_segment.hh" 8 | 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | 15 | static void check_segment(TCPTestHarness &test, const std::string &data, const bool multiple, const int lineno) { 16 | try { 17 | std::cerr << " check_segment" << std::endl; 18 | test.execute(ExpectSegment{}.with_ack(true).with_payload_size(data.size()).with_data(data)); 19 | if (!multiple) { 20 | test.execute(ExpectNoSegment{}, "test failed: multiple re-tx?"); 21 | } 22 | } catch (const std::exception &e) { 23 | throw std::runtime_error(std::string(e.what()) + " (in check_segment called from line " + 24 | std::to_string(lineno) + ")"); 25 | } 26 | } 27 | 28 | #endif // SPONGE_TESTS_FSM_RETX_HH 29 | -------------------------------------------------------------------------------- /tests/string_conversions.hh: -------------------------------------------------------------------------------- 1 | #ifndef SPONGE_STRING_CONVERSIONS_HH 2 | #define SPONGE_STRING_CONVERSIONS_HH 3 | 4 | #include "wrapping_integers.hh" 5 | 6 | #include 7 | #include 8 | #include 9 | 10 | // https://stackoverflow.com/questions/33399594/making-a-user-defined-class-stdto-stringable 11 | 12 | namespace sponge_conversions { 13 | using std::to_string; 14 | 15 | std::string to_string(WrappingInt32 i) { return std::to_string(i.raw_value()); } 16 | 17 | template 18 | std::string to_string(const std::optional &v) { 19 | if (v.has_value()) { 20 | return "Some(" + to_string(v.value()) + ")"; 21 | } else { 22 | return "None"; 23 | } 24 | } 25 | 26 | template 27 | std::string as_string(T &&t) { 28 | return to_string(std::forward(t)); 29 | } 30 | } // namespace sponge_conversions 31 | 32 | template 33 | std::string to_string(T &&t) { 34 | return sponge_conversions::as_string(std::forward(t)); 35 | } 36 | 37 | #endif // SPONGE_STRING_CONVERSIONS_HH 38 | -------------------------------------------------------------------------------- /tests/test_should_be.hh: -------------------------------------------------------------------------------- 1 | #ifndef SPONGE_TESTS_TEST_SHOULD_BE_HH 2 | #define SPONGE_TESTS_TEST_SHOULD_BE_HH 3 | 4 | #include "string_conversions.hh" 5 | 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | #define test_should_be(act, exp) _test_should_be(act, exp, #act, #exp, __LINE__) 12 | 13 | template 14 | static void _test_should_be(const T &actual, 15 | const T &expected, 16 | const char *actual_s, 17 | const char *expected_s, 18 | const int lineno) { 19 | if (actual != expected) { 20 | std::ostringstream ss; 21 | ss << "`" << actual_s << "` should have been `" << expected_s << "`, but the former is\n\t" << to_string(actual) 22 | << "\nand the latter is\n\t" << to_string(expected) << "\n" 23 | << " (at line " << lineno << ")\n"; 24 | throw std::runtime_error(ss.str()); 25 | } 26 | } 27 | 28 | #endif // SPONGE_TESTS_TEST_SHOULD_BE_HH 29 | -------------------------------------------------------------------------------- /etc/build_type.cmake: -------------------------------------------------------------------------------- 1 | set (default_build_type "Release") 2 | if (NOT (CMAKE_BUILD_TYPE_SHADOW STREQUAL CMAKE_BUILD_TYPE)) 3 | if (NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) 4 | message (STATUS "Setting build type to '${default_build_type}'") 5 | set (CMAKE_BUILD_TYPE "${default_build_type}" CACHE STRING "Choose the type of build." FORCE) 6 | else () 7 | message (STATUS "Building in ${CMAKE_BUILD_TYPE} mode as requested.") 8 | endif () 9 | set (CMAKE_BUILD_TYPE_SHADOW ${CMAKE_BUILD_TYPE} CACHE STRING "used to detect changes in build type" FORCE) 10 | endif () 11 | 12 | message (STATUS " NOTE: You can choose a build type by calling cmake with one of:") 13 | message (STATUS " -DCMAKE_BUILD_TYPE=Release -- full optimizations") 14 | message (STATUS " -DCMAKE_BUILD_TYPE=Debug -- better debugging experience in gdb") 15 | message (STATUS " -DCMAKE_BUILD_TYPE=RelASan -- full optimizations plus address and undefined-behavior sanitizers") 16 | message (STATUS " -DCMAKE_BUILD_TYPE=DebugASan -- debug plus sanitizers") 17 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // 使用 IntelliSense 了解相关属性。 3 | // 悬停以查看现有属性的描述。 4 | // 欲了解更多信息,请访问: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "name": "g++ Debug", 9 | "type": "cppdbg", 10 | "request": "launch", 11 | // "program": "${workspaceFolder}/build/apps/webget", 12 | "program": "${workspaceFolder}/build/tests/fsm_ack_rst_relaxed", 13 | "args": [], 14 | "stopAtEntry": false, 15 | "cwd": "${workspaceFolder}", 16 | "environment": [], 17 | "externalConsole": false, 18 | "MIMode": "gdb", 19 | "setupCommands": [ 20 | { 21 | "description": "为 gdb 启用整齐打印", 22 | "text": "-enable-pretty-printing", 23 | "ignoreFailures": true 24 | } 25 | ], 26 | "preLaunchTask": "Build", 27 | "miDebuggerPath": "/usr/bin/gdb" 28 | } 29 | ] 30 | } -------------------------------------------------------------------------------- /tests/wrapping_integers_cmp.cc: -------------------------------------------------------------------------------- 1 | #include "test_should_be.hh" 2 | #include "util.hh" 3 | #include "wrapping_integers.hh" 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | using namespace std; 13 | 14 | int main() { 15 | try { 16 | // Comparing low-number adjacent seqnos 17 | test_should_be(WrappingInt32(3) != WrappingInt32(1), true); 18 | test_should_be(WrappingInt32(3) == WrappingInt32(1), false); 19 | 20 | size_t N_REPS = 4096; 21 | 22 | auto rd = get_random_generator(); 23 | 24 | for (size_t i = 0; i < N_REPS; i++) { 25 | uint32_t n = rd(); 26 | uint8_t diff = rd(); 27 | uint32_t m = n + diff; 28 | test_should_be(WrappingInt32(n) == WrappingInt32(m), n == m); 29 | test_should_be(WrappingInt32(n) != WrappingInt32(m), n != m); 30 | } 31 | 32 | } catch (const exception &e) { 33 | cerr << e.what() << endl; 34 | return 1; 35 | } 36 | 37 | return EXIT_SUCCESS; 38 | } 39 | -------------------------------------------------------------------------------- /libsponge/util/tun.cc: -------------------------------------------------------------------------------- 1 | #include "tun.hh" 2 | 3 | #include "util.hh" 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | static constexpr const char *CLONEDEV = "/dev/net/tun"; 12 | 13 | using namespace std; 14 | 15 | //! \param[in] devname is the name of the TUN device, specified at its creation. 16 | //! 17 | //! To create a TUN device, you should already have run 18 | //! 19 | //! ip tuntap add mode tun user `username` name `devname` 20 | //! 21 | //! as root before calling this function. 22 | 23 | TunFD::TunFD(const string &devname) : FileDescriptor(SystemCall("open", open(CLONEDEV, O_RDWR))) { 24 | struct ifreq tun_req {}; 25 | 26 | tun_req.ifr_flags = IFF_TUN | IFF_NO_PI; // tun device with no packetinfo 27 | 28 | // copy devname to ifr_name, making sure to null terminate 29 | 30 | strncpy(static_cast(tun_req.ifr_name), devname.data(), IFNAMSIZ - 1); 31 | tun_req.ifr_name[IFNAMSIZ - 1] = '\0'; 32 | 33 | SystemCall("ioctl", ioctl(fd_num(), TUNSETIFF, static_cast(&tun_req))); 34 | } 35 | -------------------------------------------------------------------------------- /doctests/parser_example.cc: -------------------------------------------------------------------------------- 1 | const uint32_t val1 = 0xdeadbeef; 2 | const uint16_t val2 = 0xc0c0; 3 | const uint8_t val3 = 0xff; 4 | const uint32_t val4 = 0x0c05fefe; 5 | 6 | // first, let's serialize it 7 | std::string buffer; 8 | buffer.push_back(0x32); // manually added to beginning of string 9 | { 10 | NetUnparser p; 11 | p.u32(buffer, val1); 12 | p.u16(buffer, val2); 13 | p.u8(buffer, val3); 14 | p.u32(buffer, val4); 15 | } // p goes out of scope, data is in buffer 16 | 17 | // now let's deserialize it 18 | uint8_t out0, out3; 19 | uint32_t out1, out4; 20 | uint16_t out2; 21 | { 22 | NetParser p{std::string(buffer)}; // NOTE: starting at offset 0 23 | out0 = p.u8(); // buffer[0], which we manually set to 0x32 above 24 | out1 = p.u32(); // parse out val1 25 | out2 = p.u16(); // val2 26 | out3 = p.u8(); // val3 27 | out4 = p.u32(); // val4 28 | } // p goes out of scope 29 | 30 | if (out0 != 0x32 || out1 != val1 || out2 != val2 || out3 != val3 || out4 != val4) { 31 | throw std::runtime_error("bad parse"); 32 | } 33 | -------------------------------------------------------------------------------- /libsponge/tcp_helpers/tunfd_adapter.hh: -------------------------------------------------------------------------------- 1 | #ifndef SPONGE_LIBSPONGE_TUNFD_ADAPTER_HH 2 | #define SPONGE_LIBSPONGE_TUNFD_ADAPTER_HH 3 | 4 | #include "fd_adapter.hh" 5 | #include "file_descriptor.hh" 6 | #include "lossy_fd_adapter.hh" 7 | #include "tcp_segment.hh" 8 | 9 | #include 10 | #include 11 | 12 | //! \brief A FD adapter for IPv4 datagrams read from and written to a TUN device 13 | class TCPOverIPv4OverTunFdAdapter : public FdAdapterBase, public FileDescriptor { 14 | public: 15 | //! Construct from a TunFD sliced into a FileDescriptor 16 | explicit TCPOverIPv4OverTunFdAdapter(FileDescriptor &&fd) : FileDescriptor(std::move(fd)) {} 17 | 18 | //! Attempts to read and parse an IPv4 datagram containing a TCP segment related to the current connection 19 | std::optional read(); 20 | 21 | //! Creates an IPv4 datagram from a TCP segment and writes it to the TUN device 22 | void write(TCPSegment &seg); 23 | }; 24 | 25 | //! Typedef for TCPOverIPv4OverTunFdAdapter 26 | using LossyTCPOverIPv4OverTunFdAdapter = LossyFdAdapter; 27 | 28 | #endif // SPONGE_LIBSPONGE_TUNFD_ADAPTER_HH 29 | -------------------------------------------------------------------------------- /libsponge/tcp_helpers/ipv4_datagram.cc: -------------------------------------------------------------------------------- 1 | #include "ipv4_datagram.hh" 2 | 3 | #include "parser.hh" 4 | #include "util.hh" 5 | 6 | #include 7 | #include 8 | 9 | using namespace std; 10 | 11 | ParseResult IPv4Datagram::parse(const Buffer buffer) { 12 | NetParser p{buffer}; 13 | _header.parse(p); 14 | _payload = p.buffer(); 15 | 16 | if (_payload.size() != _header.payload_length()) { 17 | return ParseResult::PacketTooShort; 18 | } 19 | 20 | return p.get_error(); 21 | } 22 | 23 | BufferList IPv4Datagram::serialize() const { 24 | if (_payload.size() != _header.payload_length()) { 25 | throw runtime_error("IPv4Datagram::serialize: payload is wrong size"); 26 | } 27 | 28 | IPv4Header header_out = _header; 29 | header_out.cksum = 0; 30 | const string header_zero_checksum = header_out.serialize(); 31 | 32 | // calculate checksum -- taken over header only 33 | InternetChecksum check; 34 | check.add(header_zero_checksum); 35 | header_out.cksum = check.value(); 36 | 37 | BufferList ret; 38 | ret.append(header_out.serialize()); 39 | ret.append(_payload); 40 | return ret; 41 | } 42 | -------------------------------------------------------------------------------- /libsponge/tcp_helpers/tcp_segment.hh: -------------------------------------------------------------------------------- 1 | #ifndef SPONGE_LIBSPONGE_TCP_SEGMENT_HH 2 | #define SPONGE_LIBSPONGE_TCP_SEGMENT_HH 3 | 4 | #include "buffer.hh" 5 | #include "tcp_header.hh" 6 | 7 | #include 8 | 9 | //! \brief [TCP](\ref rfc::rfc793) segment 10 | class TCPSegment { 11 | private: 12 | TCPHeader _header{}; 13 | Buffer _payload{}; 14 | 15 | public: 16 | //! \brief Parse the segment from a string 17 | ParseResult parse(const Buffer buffer, const uint32_t datagram_layer_checksum = 0); 18 | 19 | //! \brief Serialize the segment to a string 20 | BufferList serialize(const uint32_t datagram_layer_checksum = 0) const; 21 | 22 | //! \name Accessors 23 | //!@{ 24 | const TCPHeader &header() const { return _header; } 25 | TCPHeader &header() { return _header; } 26 | 27 | const Buffer &payload() const { return _payload; } 28 | Buffer &payload() { return _payload; } 29 | //!@} 30 | 31 | //! \brief Segment's length in sequence space 32 | //! \note Equal to payload length plus one byte if SYN is set, plus one byte if FIN is set 33 | size_t length_in_sequence_space() const; 34 | }; 35 | 36 | #endif // SPONGE_LIBSPONGE_TCP_SEGMENT_HH 37 | -------------------------------------------------------------------------------- /etc/cflags.cmake: -------------------------------------------------------------------------------- 1 | set (CMAKE_CXX_STANDARD 17) 2 | set (CMAKE_EXPORT_COMPILE_COMMANDS ON) 3 | 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") 4 | 5 | # check for supported compiler versions 6 | set (IS_GNU_COMPILER ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "GNU")) 7 | set (IS_CLANG_COMPILER ("${CMAKE_CXX_COMPILER_ID}" MATCHES "[Cc][Ll][Aa][Nn][Gg]")) 8 | set (CXX_VERSION_LT_6 ("${CMAKE_CXX_COMPILER_VERSION}" VERSION_LESS 6)) 9 | set (CXX_VERSION_LT_8 ("${CMAKE_CXX_COMPILER_VERSION}" VERSION_LESS 8)) 10 | if ((${IS_GNU_COMPILER} AND ${CXX_VERSION_LT_8}) OR (${IS_CLANG_COMPILER} AND ${CXX_VERSION_LT_6})) 11 | message (FATAL_ERROR "You must compile this project with g++ >= 8 or clang >= 6.") 12 | endif () 13 | if (${IS_CLANG_COMPILER}) 14 | set (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wloop-analysis") 15 | endif () 16 | 17 | # add some flags for the Release, Debug, and DebugSan modes 18 | set (CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -ggdb3 -Og") 19 | set (CMAKE_CXX_FLAGS_DEBUGASAN "${CMAKE_CXX_FLAGS_DEBUG} -fsanitize=undefined -fsanitize=address") 20 | set (CMAKE_CXX_FLAGS_RELASAN "${CMAKE_CXX_FLAGS_RELEASE} -fsanitize=undefined -fsanitize=address") 21 | -------------------------------------------------------------------------------- /etc/clang_format.cmake: -------------------------------------------------------------------------------- 1 | if (NOT CLANG_FORMAT) 2 | if (DEFINED ENV{CLANG_FORMAT}) 3 | set (CLANG_FORMAT_TMP $ENV{CLANG_FORMAT}) 4 | else (NOT DEFINED ENV{CLANG_FORMAT}) 5 | set (CLANG_FORMAT_TMP clang-format-6.0) 6 | endif (DEFINED ENV{CLANG_FORMAT}) 7 | 8 | # figure out which version of clang-format we're using 9 | execute_process (COMMAND ${CLANG_FORMAT_TMP} --version RESULT_VARIABLE CLANG_FORMAT_RESULT OUTPUT_VARIABLE CLANG_FORMAT_VERSION) 10 | if (${CLANG_FORMAT_RESULT} EQUAL 0) 11 | string (REGEX MATCH "version [0-9]" CLANG_FORMAT_VERSION ${CLANG_FORMAT_VERSION}) 12 | message (STATUS "Found clang-format " ${CLANG_FORMAT_VERSION}) 13 | set(CLANG_FORMAT ${CLANG_FORMAT_TMP} CACHE STRING "clang-format executable name") 14 | endif (${CLANG_FORMAT_RESULT} EQUAL 0) 15 | endif (NOT CLANG_FORMAT) 16 | 17 | if (DEFINED CLANG_FORMAT) 18 | file (GLOB_RECURSE ALL_CC_FILES *.cc) 19 | file (GLOB_RECURSE ALL_HH_FILES *.hh) 20 | add_custom_target (format ${CLANG_FORMAT} -i ${ALL_CC_FILES} ${ALL_HH_FILES} COMMENT "Formatted all source files.") 21 | else (NOT DEFINED CLANG_FORMAT) 22 | add_custom_target (format echo "Could not find clang-format. Please install and re-run cmake") 23 | endif (DEFINED CLANG_FORMAT) 24 | -------------------------------------------------------------------------------- /tests/byte_stream_construction.cc: -------------------------------------------------------------------------------- 1 | #include "byte_stream.hh" 2 | #include "byte_stream_test_harness.hh" 3 | 4 | #include 5 | #include 6 | 7 | using namespace std; 8 | 9 | int main() { 10 | try { 11 | { 12 | ByteStreamTestHarness test{"construction", 15}; 13 | test.execute(InputEnded{false}); 14 | test.execute(BufferEmpty{true}); 15 | test.execute(Eof{false}); 16 | test.execute(BytesRead{0}); 17 | test.execute(BytesWritten{0}); 18 | test.execute(RemainingCapacity{15}); 19 | test.execute(BufferSize{0}); 20 | } 21 | 22 | { 23 | ByteStreamTestHarness test{"construction-end", 15}; 24 | test.execute(EndInput{}); 25 | test.execute(InputEnded{true}); 26 | test.execute(BufferEmpty{true}); 27 | test.execute(Eof{true}); 28 | test.execute(BytesRead{0}); 29 | test.execute(BytesWritten{0}); 30 | test.execute(RemainingCapacity{15}); 31 | test.execute(BufferSize{0}); 32 | } 33 | 34 | } catch (const exception &e) { 35 | cerr << "Exception: " << e.what() << endl; 36 | return EXIT_FAILURE; 37 | } 38 | 39 | return EXIT_SUCCESS; 40 | } 41 | -------------------------------------------------------------------------------- /libsponge/tcp_helpers/tcp_segment.cc: -------------------------------------------------------------------------------- 1 | #include "tcp_segment.hh" 2 | 3 | #include "parser.hh" 4 | #include "util.hh" 5 | 6 | #include 7 | 8 | using namespace std; 9 | 10 | //! \param[in] buffer string/Buffer to be parsed 11 | //! \param[in] datagram_layer_checksum pseudo-checksum from the lower-layer protocol 12 | ParseResult TCPSegment::parse(const Buffer buffer, const uint32_t datagram_layer_checksum) { 13 | InternetChecksum check(datagram_layer_checksum); 14 | check.add(buffer); 15 | if (check.value()) { 16 | return ParseResult::BadChecksum; 17 | } 18 | 19 | NetParser p{buffer}; 20 | _header.parse(p); 21 | _payload = p.buffer(); 22 | return p.get_error(); 23 | } 24 | 25 | size_t TCPSegment::length_in_sequence_space() const { 26 | return payload().str().size() + (header().syn ? 1 : 0) + (header().fin ? 1 : 0); 27 | } 28 | 29 | //! \param[in] datagram_layer_checksum pseudo-checksum from the lower-layer protocol 30 | BufferList TCPSegment::serialize(const uint32_t datagram_layer_checksum) const { 31 | TCPHeader header_out = _header; 32 | header_out.cksum = 0; 33 | 34 | // calculate checksum -- taken over entire segment 35 | InternetChecksum check(datagram_layer_checksum); 36 | check.add(header_out.serialize()); 37 | check.add(_payload); 38 | header_out.cksum = check.value(); 39 | 40 | BufferList ret; 41 | ret.append(header_out.serialize()); 42 | ret.append(_payload); 43 | 44 | return ret; 45 | } 46 | -------------------------------------------------------------------------------- /libsponge/tcp_helpers/tcp_config.hh: -------------------------------------------------------------------------------- 1 | #ifndef SPONGE_LIBSPONGE_TCP_CONFIG_HH 2 | #define SPONGE_LIBSPONGE_TCP_CONFIG_HH 3 | 4 | #include "address.hh" 5 | #include "wrapping_integers.hh" 6 | 7 | #include 8 | #include 9 | #include 10 | 11 | //! Config for TCP sender and receiver 12 | class TCPConfig { 13 | public: 14 | static constexpr size_t DEFAULT_CAPACITY = 64000; //!< Default capacity 15 | static constexpr size_t MAX_PAYLOAD_SIZE = 1452; //!< Max TCP payload that fits in either IPv4 or UDP datagram 16 | static constexpr uint16_t TIMEOUT_DFLT = 1000; //!< Default re-transmit timeout is 1 second 17 | static constexpr unsigned MAX_RETX_ATTEMPTS = 8; //!< Maximum re-transmit attempts before giving up 18 | 19 | uint16_t rt_timeout = TIMEOUT_DFLT; //!< Initial value of the retransmission timeout, in milliseconds 20 | size_t recv_capacity = DEFAULT_CAPACITY; //!< Receive capacity, in bytes 21 | size_t send_capacity = DEFAULT_CAPACITY; //!< Sender capacity, in bytes 22 | std::optional fixed_isn{}; 23 | }; 24 | 25 | //! Config for classes derived from FdAdapter 26 | class FdAdapterConfig { 27 | public: 28 | Address source{"0", 0}; //!< Source address and port 29 | Address destination{"0", 0}; //!< Destination address and port 30 | 31 | uint16_t loss_rate_dn = 0; //!< Downlink loss rate (for LossyFdAdapter) 32 | uint16_t loss_rate_up = 0; //!< Uplink loss rate (for LossyFdAdapter) 33 | }; 34 | 35 | #endif // SPONGE_LIBSPONGE_TCP_CONFIG_HH 36 | -------------------------------------------------------------------------------- /tests/byte_stream_many_writes.cc: -------------------------------------------------------------------------------- 1 | #include "byte_stream.hh" 2 | #include "byte_stream_test_harness.hh" 3 | #include "util.hh" 4 | 5 | #include 6 | #include 7 | 8 | using namespace std; 9 | 10 | int main() { 11 | try { 12 | auto rd = get_random_generator(); 13 | const size_t NREPS = 1000; 14 | const size_t MIN_WRITE = 10; 15 | const size_t MAX_WRITE = 200; 16 | const size_t CAPACITY = MAX_WRITE * NREPS; 17 | 18 | { 19 | ByteStreamTestHarness test{"many writes", CAPACITY}; 20 | 21 | size_t acc = 0; 22 | for (size_t i = 0; i < NREPS; ++i) { 23 | const size_t size = MIN_WRITE + (rd() % (MAX_WRITE - MIN_WRITE)); 24 | string d(size, 0); 25 | generate(d.begin(), d.end(), [&] { return 'a' + (rd() % 26); }); 26 | 27 | test.execute(Write{d}.with_bytes_written(size)); 28 | acc += size; 29 | 30 | test.execute(InputEnded{false}); 31 | test.execute(BufferEmpty{false}); 32 | test.execute(Eof{false}); 33 | test.execute(BytesRead{0}); 34 | test.execute(BytesWritten{acc}); 35 | test.execute(RemainingCapacity{CAPACITY - acc}); 36 | test.execute(BufferSize{acc}); 37 | } 38 | } 39 | 40 | } catch (const exception &e) { 41 | cerr << "Exception: " << e.what() << endl; 42 | return EXIT_FAILURE; 43 | } 44 | 45 | return EXIT_SUCCESS; 46 | } 47 | -------------------------------------------------------------------------------- /tests/test_utils.hh: -------------------------------------------------------------------------------- 1 | #ifndef SPONGE_TESTS_TEST_UTILS_HH 2 | #define SPONGE_TESTS_TEST_UTILS_HH 3 | 4 | #include "tcp_header.hh" 5 | #include "util.hh" 6 | 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | inline void show_ethernet_frame(const uint8_t *pkt, const struct pcap_pkthdr &hdr) { 13 | const auto flags(std::cout.flags()); 14 | 15 | std::cout << "source MAC: "; 16 | std::cout << std::hex << std::setfill('0'); 17 | for (unsigned i = 0; i < 6; ++i) { 18 | std::cout << std::setw(2) << +pkt[i] << (i == 5 ? ' ' : ':'); 19 | } 20 | 21 | std::cout << " dest MAC: "; 22 | for (unsigned i = 0; i < 6; ++i) { 23 | std::cout << std::setw(2) << +pkt[i + 6] << (i == 5 ? ' ' : ':'); 24 | } 25 | 26 | std::cout << " ethertype: " << std::setw(2) << +pkt[12] << std::setw(2) << +pkt[13] << '\n'; 27 | std::cout << std::dec << "length: " << hdr.len << " captured: " << hdr.caplen << '\n'; 28 | 29 | std::cout.flags(flags); 30 | } 31 | 32 | inline bool compare_tcp_headers_nolen(const TCPHeader &h1, const TCPHeader &h2) { 33 | return h1.sport == h2.sport && h1.dport == h2.dport && h1.seqno == h2.seqno && h1.ackno == h2.ackno && 34 | h1.urg == h2.urg && h1.ack == h2.ack && h1.psh == h2.psh && h1.rst == h2.rst && h1.syn == h2.syn && 35 | h1.fin == h2.fin && h1.win == h2.win && h1.uptr == h2.uptr; 36 | } 37 | 38 | inline bool compare_tcp_headers(const TCPHeader &h1, const TCPHeader &h2) { 39 | return compare_tcp_headers_nolen(h1, h2) && h1.doff == h2.doff; 40 | } 41 | 42 | #endif // SPONGE_TESTS_TEST_UTILS_HH 43 | -------------------------------------------------------------------------------- /apps/tcp_native.cc: -------------------------------------------------------------------------------- 1 | #include "bidirectional_stream_copy.hh" 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | using namespace std; 8 | 9 | void show_usage(const char *argv0) { 10 | cerr << "Usage: " << argv0 << " [-l] \n\n" 11 | << " -l specifies listen mode; : is the listening address." << endl; 12 | } 13 | 14 | int main(int argc, char **argv) { 15 | try { 16 | bool server_mode = false; 17 | if (argc < 3 || ((server_mode = (strncmp("-l", argv[1], 3) == 0)) && argc < 4)) { 18 | show_usage(argv[0]); 19 | return EXIT_FAILURE; 20 | } 21 | 22 | // in client mode, connect; in server mode, accept exactly one connection 23 | auto socket = [&] { 24 | if (server_mode) { 25 | TCPSocket listening_socket; // create a TCP socket 26 | listening_socket.set_reuseaddr(); // reuse the server's address as soon as the program quits 27 | listening_socket.bind({argv[2], argv[3]}); // bind to specified address 28 | listening_socket.listen(); // mark the socket as listening for incoming connections 29 | return listening_socket.accept(); // accept exactly one connection 30 | } 31 | TCPSocket connecting_socket; 32 | connecting_socket.connect({argv[1], argv[2]}); 33 | return connecting_socket; 34 | }(); 35 | 36 | bidirectional_stream_copy(socket); 37 | } catch (const exception &e) { 38 | cerr << "Exception: " << e.what() << endl; 39 | return EXIT_FAILURE; 40 | } 41 | 42 | return EXIT_SUCCESS; 43 | } 44 | -------------------------------------------------------------------------------- /apps/tun.cc: -------------------------------------------------------------------------------- 1 | #include "tun.hh" 2 | 3 | #include "ipv4_datagram.hh" 4 | #include "parser.hh" 5 | #include "tcp_segment.hh" 6 | #include "util.hh" 7 | 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | using namespace std; 14 | 15 | int main() { 16 | try { 17 | TunFD tun("tun144"); 18 | while (true) { 19 | auto buffer = tun.read(); 20 | cout << "\n\n***\n*** Got packet:\n***\n"; 21 | hexdump(buffer.data(), buffer.size()); 22 | 23 | IPv4Datagram ip_dgram; 24 | 25 | cout << "attempting to parse as ipv4 datagram... "; 26 | if (ip_dgram.parse(move(buffer)) != ParseResult::NoError) { 27 | cout << "failed.\n"; 28 | continue; 29 | } 30 | 31 | cout << "success! totlen=" << ip_dgram.header().len << ", IPv4 header contents:\n"; 32 | cout << ip_dgram.header().to_string(); 33 | 34 | if (ip_dgram.header().proto != IPv4Header::PROTO_TCP) { 35 | cout << "\nNot TCP, skipping.\n"; 36 | continue; 37 | } 38 | 39 | cout << "\nAttempting to parse as a TCP segment... "; 40 | 41 | TCPSegment tcp_seg; 42 | 43 | if (tcp_seg.parse(ip_dgram.payload(), ip_dgram.header().pseudo_cksum()) != ParseResult::NoError) { 44 | cout << "failed.\n"; 45 | continue; 46 | } 47 | 48 | cout << "success! payload len=" << tcp_seg.payload().size() << ", TCP header contents:\n"; 49 | cout << tcp_seg.header().to_string() << endl; 50 | } 51 | } catch (const exception &e) { 52 | cout << "Exception: " << e.what() << endl; 53 | return EXIT_FAILURE; 54 | } 55 | 56 | return EXIT_SUCCESS; 57 | } 58 | -------------------------------------------------------------------------------- /etc/clang_tidy.cmake: -------------------------------------------------------------------------------- 1 | if (NOT CLANG_TIDY) 2 | if (DEFINED ENV{CLANG_TIDY}) 3 | set (CLANG_TIDY_TMP $ENV{CLANG_TIDY}) 4 | else (NOT DEFINED ENV{CLANG_TIDY}) 5 | set (CLANG_TIDY_TMP clang-tidy) 6 | endif (DEFINED ENV{CLANG_TIDY}) 7 | 8 | # is clang-tidy available? 9 | execute_process (COMMAND ${CLANG_TIDY_TMP} --version RESULT_VARIABLE CLANG_TIDY_RESULT OUTPUT_VARIABLE CLANG_TIDY_VERSION) 10 | if (${CLANG_TIDY_RESULT} EQUAL 0) 11 | string (REGEX MATCH "version [0-9]" CLANG_TIDY_VERSION ${CLANG_TIDY_VERSION}) 12 | message (STATUS "Found clang-tidy " ${CLANG_TIDY_VERSION}) 13 | set (CLANG_TIDY ${CLANG_TIDY_TMP} CACHE STRING "clang-tidy executable name") 14 | endif (${CLANG_TIDY_RESULT} EQUAL 0) 15 | endif (NOT CLANG_TIDY) 16 | 17 | if (DEFINED CLANG_TIDY) 18 | file (GLOB_RECURSE ALL_CC_FILES *.cc) 19 | 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'") 20 | foreach (tidy_target ${ALL_CC_FILES}) 21 | get_filename_component (basename ${tidy_target} NAME) 22 | get_filename_component (dirname ${tidy_target} DIRECTORY) 23 | get_filename_component (basedir ${dirname} NAME) 24 | set (tidy_target_name "${basedir}__${basename}") 25 | set (tidy_command ${CLANG_TIDY} -checks=${CLANG_TIDY_CHECKS} -header-filter=.* -p=${PROJECT_BINARY_DIR} ${tidy_target}) 26 | add_custom_target (tidy_quiet_${tidy_target_name} ${tidy_command} 2>/dev/null) 27 | add_custom_target (tidy_${tidy_target_name} ${tidy_command}) 28 | list (APPEND ALL_TIDY_TARGETS tidy_quiet_${tidy_target_name}) 29 | list (APPEND ALL_TIDY_VERBOSE_TARGETS tidy_${tidy_target_name}) 30 | endforeach (tidy_target) 31 | add_custom_target (tidy DEPENDS ${ALL_TIDY_TARGETS}) 32 | add_custom_target (tidy_verbose DEPENDS ${ALL_TIDY_VERBOSE_TARGETS}) 33 | endif (DEFINED CLANG_TIDY) 34 | -------------------------------------------------------------------------------- /etc/Doxyfile.in: -------------------------------------------------------------------------------- 1 | # Doxyfile 1.8.14 2 | 3 | DOXYFILE_ENCODING = UTF-8 4 | PROJECT_NAME = "Sponge" 5 | PROJECT_BRIEF = "CS144's user-space TCP library" 6 | PROJECT_LOGO = "@PROJECT_SOURCE_DIR@/etc/sponge_small.png" 7 | INPUT = @PROJECT_SOURCE_DIR@ 8 | RECURSIVE = YES 9 | EXCLUDE = @PROJECT_SOURCE_DIR@/etc @PROJECT_SOURCE_DIR@/build @PROJECT_SOURCE_DIR@/tests @PROJECT_SOURCE_DIR@/writeups 10 | OUTPUT_DIRECTORY = "@PROJECT_BINARY_DIR@/doc" 11 | CASE_SENSE_NAMES = NO 12 | SORT_BRIEF_DOCS = YES 13 | SORT_MEMBERS_CTORS_1ST = YES 14 | SHOW_NAMESPACES = NO 15 | USE_MDFILE_AS_MAINPAGE = @PROJECT_SOURCE_DIR@/README.md 16 | SOURCE_BROWSER = YES 17 | EXT_LINKS_IN_WINDOW = YES 18 | INCLUDE_PATH = @PROJECT_SOURCE_DIR@/libsponge 19 | TAGFILES = "@PROJECT_SOURCE_DIR@/etc/cppreference-doxygen-web.tag.xml=https://en.cppreference.com/w/" 20 | TAGFILES += "@PROJECT_SOURCE_DIR@/etc/linux-man-doxygen-web.tag.xml=http://man7.org/linux/man-pages/" 21 | TAGFILES += "@PROJECT_SOURCE_DIR@/etc/rfc-doxygen-web.tag.xml=https://tools.ietf.org/html/" 22 | HIDE_UNDOC_RELATIONS = NO 23 | INLINE_GROUPED_CLASSES = YES 24 | INLINE_SIMPLE_STRUCTS = YES 25 | HTML_COLORSTYLE_HUE = 204 26 | HTML_COLORSTYLE_SAT = 120 27 | HTML_COLORSTYLE_GAMMA = 60 28 | HTML_EXTRA_STYLESHEET = "@PROJECT_SOURCE_DIR@/etc/sponge_doxygen.css" 29 | GENERATE_LATEX = NO 30 | EXAMPLE_PATH = "@PROJECT_SOURCE_DIR@/doctests" 31 | 32 | # cmake detects whether dot is available 33 | HAVE_DOT = @DOXYGEN_DOT_FOUND@ 34 | CLASS_GRAPH = YES 35 | TEMPLATE_RELATIONS = YES 36 | DOT_IMAGE_FORMAT = png 37 | INTERACTIVE_SVG = NO 38 | COLLABORATION_GRAPH = NO 39 | 40 | # ??? temporary 41 | EXTRACT_ALL = YES 42 | EXTRACT_PRIVATE = YES 43 | EXTRACT_STATIC = YES 44 | EXTRACT_ANON_NSPACES = YES 45 | 46 | # do u liek eclips 47 | GENERATE_ECLIPSEHELP = NO 48 | ECLIPSE_DOC_ID = edu.stanford.cs144.sponge 49 | -------------------------------------------------------------------------------- /libsponge/util/parser.cc: -------------------------------------------------------------------------------- 1 | #include "parser.hh" 2 | 3 | using namespace std; 4 | 5 | //! \param[in] r is the ParseResult to show 6 | //! \returns a string representation of the ParseResult 7 | string as_string(const ParseResult r) { 8 | static constexpr const char *_names[] = { 9 | "NoError", 10 | "BadChecksum", 11 | "PacketTooShort", 12 | "WrongIPVersion", 13 | "HeaderTooShort", 14 | "TruncatedPacket", 15 | }; 16 | 17 | return _names[static_cast(r)]; 18 | } 19 | 20 | void NetParser::_check_size(const size_t size) { 21 | if (size > _buffer.size()) { 22 | set_error(ParseResult::PacketTooShort); 23 | } 24 | } 25 | 26 | template 27 | T NetParser::_parse_int() { 28 | constexpr size_t len = sizeof(T); 29 | _check_size(len); 30 | if (error()) { 31 | return 0; 32 | } 33 | 34 | T ret = 0; 35 | for (size_t i = 0; i < len; i++) { 36 | ret <<= 8; 37 | ret += uint8_t(_buffer.at(i)); 38 | } 39 | 40 | _buffer.remove_prefix(len); 41 | 42 | return ret; 43 | } 44 | 45 | void NetParser::remove_prefix(const size_t n) { 46 | _check_size(n); 47 | if (error()) { 48 | return; 49 | } 50 | _buffer.remove_prefix(n); 51 | } 52 | 53 | template 54 | void NetUnparser::_unparse_int(string &s, T val) { 55 | constexpr size_t len = sizeof(T); 56 | for (size_t i = 0; i < len; ++i) { 57 | const uint8_t the_byte = (val >> ((len - i - 1) * 8)) & 0xff; 58 | s.push_back(the_byte); 59 | } 60 | } 61 | 62 | uint32_t NetParser::u32() { return _parse_int(); } 63 | 64 | uint16_t NetParser::u16() { return _parse_int(); } 65 | 66 | uint8_t NetParser::u8() { return _parse_int(); } 67 | 68 | void NetUnparser::u32(string &s, const uint32_t val) { return _unparse_int(s, val); } 69 | 70 | void NetUnparser::u16(string &s, const uint16_t val) { return _unparse_int(s, val); } 71 | 72 | void NetUnparser::u8(string &s, const uint8_t val) { return _unparse_int(s, val); } 73 | -------------------------------------------------------------------------------- /apps/webget.cc: -------------------------------------------------------------------------------- 1 | // #include "socket.hh" 2 | #include "tcp_sponge_socket.hh" 3 | #include "util.hh" 4 | 5 | #include 6 | #include 7 | 8 | using namespace std; 9 | 10 | void get_URL(const string &host, const string &path) 11 | { 12 | CS144TCPSocket client_socket{}; 13 | // uint16_t port_num = 80; // This is a HTTP request, so it's 80. 14 | // client_socket.connect(Address(host, port_num)); 15 | client_socket.connect(Address(host, "http")); 16 | 17 | client_socket.write("GET " + path + " HTTP/1.1\r\n"); 18 | client_socket.write("Host:" + host + "\r\n"); 19 | client_socket.write("\r\n"); // end the request 20 | 21 | client_socket.shutdown(SHUT_WR); 22 | 23 | while (!client_socket.eof()) 24 | { 25 | cout << client_socket.read(); 26 | } 27 | 28 | client_socket.wait_until_closed(); 29 | } 30 | 31 | int main(int argc, char *argv[]) 32 | { 33 | try 34 | { 35 | if (argc <= 0) 36 | { 37 | abort(); // For sticklers: don't try to access argv[0] if argc <= 0. 38 | } 39 | 40 | // The program takes two command-line arguments: the hostname and "path" part of the URL. 41 | // Print the usage message unless there are these two arguments (plus the program name 42 | // itself, so arg count = 3 in total). 43 | if (argc != 3) 44 | { 45 | cerr << "Usage: " << argv[0] << " HOST PATH\n"; 46 | cerr << "\tExample: " << argv[0] << " stanford.edu /class/cs144\n"; 47 | return EXIT_FAILURE; 48 | } 49 | 50 | // cout << argv[0] << "\n" 51 | // << argv[1] << "\n" 52 | // << argv[2] << endl; 53 | 54 | // Get the command-line arguments. 55 | const string host = argv[1]; 56 | const string path = argv[2]; 57 | 58 | // Call the student-written function. 59 | get_URL(host, path); 60 | } 61 | catch (const exception &e) 62 | { 63 | cerr << e.what() << "\n"; 64 | return EXIT_FAILURE; 65 | } 66 | 67 | return EXIT_SUCCESS; 68 | } 69 | -------------------------------------------------------------------------------- /tests/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_library (spongechecks STATIC send_equivalence_checker.cc tcp_fsm_test_harness.cc byte_stream_test_harness.cc) 2 | 3 | macro (add_test_exec exec_name) 4 | add_executable ("${exec_name}" "${exec_name}.cc") 5 | target_link_libraries ("${exec_name}" spongechecks ${ARGN}) 6 | target_link_libraries ("${exec_name}" sponge ${ARGN}) 7 | endmacro (add_test_exec) 8 | 9 | add_test_exec (tcp_parser ${LIBPCAP}) 10 | add_test_exec (ipv4_parser ${LIBPCAP}) 11 | add_test_exec (fsm_active_close) 12 | add_test_exec (fsm_passive_close) 13 | add_test_exec (fsm_ack_rst) 14 | add_test_exec (fsm_ack_rst_relaxed) 15 | add_test_exec (fsm_ack_rst_win) 16 | add_test_exec (fsm_ack_rst_win_relaxed) 17 | add_test_exec (fsm_stream_reassembler_cap) 18 | add_test_exec (fsm_stream_reassembler_single) 19 | add_test_exec (fsm_stream_reassembler_seq) 20 | add_test_exec (fsm_stream_reassembler_dup) 21 | add_test_exec (fsm_stream_reassembler_holes) 22 | add_test_exec (fsm_stream_reassembler_many) 23 | add_test_exec (fsm_stream_reassembler_overlapping) 24 | add_test_exec (fsm_stream_reassembler_win) 25 | add_test_exec (fsm_connect) 26 | add_test_exec (fsm_connect_relaxed) 27 | add_test_exec (fsm_listen) 28 | add_test_exec (fsm_listen_relaxed) 29 | add_test_exec (fsm_reorder) 30 | add_test_exec (fsm_loopback) 31 | add_test_exec (fsm_loopback_win) 32 | add_test_exec (fsm_retx) 33 | add_test_exec (fsm_retx_relaxed) 34 | add_test_exec (fsm_retx_win) 35 | add_test_exec (fsm_winsize) 36 | add_test_exec (wrapping_integers_cmp) 37 | add_test_exec (wrapping_integers_unwrap) 38 | add_test_exec (wrapping_integers_wrap) 39 | add_test_exec (byte_stream_construction) 40 | add_test_exec (byte_stream_one_write) 41 | add_test_exec (byte_stream_two_writes) 42 | add_test_exec (byte_stream_capacity) 43 | add_test_exec (byte_stream_many_writes) 44 | add_test_exec (recv_connect) 45 | add_test_exec (recv_transmit) 46 | add_test_exec (recv_window) 47 | add_test_exec (recv_reorder) 48 | add_test_exec (recv_close) 49 | add_test_exec (send_connect) 50 | add_test_exec (send_transmit) 51 | add_test_exec (send_retx) 52 | add_test_exec (send_ack) 53 | add_test_exec (send_window) 54 | add_test_exec (send_close) 55 | -------------------------------------------------------------------------------- /tests/fsm_stream_reassembler_single.cc: -------------------------------------------------------------------------------- 1 | #include "byte_stream.hh" 2 | #include "fsm_stream_reassembler_harness.hh" 3 | #include "stream_reassembler.hh" 4 | #include "util.hh" 5 | 6 | #include 7 | #include 8 | 9 | using namespace std; 10 | 11 | int main() { 12 | try { 13 | { 14 | ReassemblerTestHarness test{65000}; 15 | 16 | test.execute(BytesAssembled(0)); 17 | test.execute(BytesAvailable("")); 18 | test.execute(NotAtEof{}); 19 | } 20 | 21 | { 22 | ReassemblerTestHarness test{65000}; 23 | 24 | test.execute(SubmitSegment{"a", 0}); 25 | 26 | test.execute(BytesAssembled(1)); 27 | test.execute(BytesAvailable("a")); 28 | test.execute(NotAtEof{}); 29 | } 30 | 31 | { 32 | ReassemblerTestHarness test{65000}; 33 | 34 | test.execute(SubmitSegment{"a", 0}.with_eof(true)); 35 | 36 | test.execute(BytesAssembled(1)); 37 | test.execute(BytesAvailable("a")); 38 | test.execute(AtEof{}); 39 | } 40 | 41 | { 42 | ReassemblerTestHarness test{65000}; 43 | 44 | test.execute(SubmitSegment{"", 0}.with_eof(true)); 45 | 46 | test.execute(BytesAssembled(0)); 47 | test.execute(BytesAvailable("")); 48 | test.execute(AtEof{}); 49 | } 50 | 51 | { 52 | ReassemblerTestHarness test{65000}; 53 | 54 | test.execute(SubmitSegment{"b", 0}.with_eof(true)); 55 | 56 | test.execute(BytesAssembled(1)); 57 | test.execute(BytesAvailable("b")); 58 | test.execute(AtEof{}); 59 | } 60 | 61 | { 62 | ReassemblerTestHarness test{65000}; 63 | 64 | test.execute(SubmitSegment{"", 0}); 65 | 66 | test.execute(BytesAssembled(0)); 67 | test.execute(BytesAvailable("")); 68 | test.execute(NotAtEof{}); 69 | } 70 | } catch (const exception &e) { 71 | cerr << "Exception: " << e.what() << endl; 72 | return EXIT_FAILURE; 73 | } 74 | 75 | return EXIT_SUCCESS; 76 | } 77 | -------------------------------------------------------------------------------- /tests/wrapping_integers_unwrap.cc: -------------------------------------------------------------------------------- 1 | #include "test_should_be.hh" 2 | #include "wrapping_integers.hh" 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | using namespace std; 11 | 12 | int main() { 13 | try { 14 | // Unwrap the first byte after ISN 15 | test_should_be(unwrap(WrappingInt32(1), WrappingInt32(0), 0), 1ul); 16 | // Unwrap the first byte after the first wrap 17 | test_should_be(unwrap(WrappingInt32(1), WrappingInt32(0), UINT32_MAX), (1ul << 32) + 1); 18 | // Unwrap the last byte before the third wrap 19 | test_should_be(unwrap(WrappingInt32(UINT32_MAX - 1), WrappingInt32(0), 3 * (1ul << 32)), 3 * (1ul << 32) - 2); 20 | // Unwrap the 10th from last byte before the third wrap 21 | test_should_be(unwrap(WrappingInt32(UINT32_MAX - 10), WrappingInt32(0), 3 * (1ul << 32)), 3 * (1ul << 32) - 11); 22 | // Non-zero ISN 23 | test_should_be(unwrap(WrappingInt32(UINT32_MAX), WrappingInt32(10), 3 * (1ul << 32)), 3 * (1ul << 32) - 11); 24 | // Big unwrap 25 | test_should_be(unwrap(WrappingInt32(UINT32_MAX), WrappingInt32(0), 0), static_cast(UINT32_MAX)); 26 | // Unwrap a non-zero ISN 27 | test_should_be(unwrap(WrappingInt32(16), WrappingInt32(16), 0), 0ul); 28 | 29 | // Big unwrap with non-zero ISN 30 | test_should_be(unwrap(WrappingInt32(15), WrappingInt32(16), 0), static_cast(UINT32_MAX)); 31 | // Big unwrap with non-zero ISN 32 | test_should_be(unwrap(WrappingInt32(0), WrappingInt32(INT32_MAX), 0), static_cast(INT32_MAX) + 2); 33 | // Barely big unwrap with non-zero ISN 34 | test_should_be(unwrap(WrappingInt32(UINT32_MAX), WrappingInt32(INT32_MAX), 0), static_cast(1) << 31); 35 | // Nearly big unwrap with non-zero ISN 36 | test_should_be(unwrap(WrappingInt32(UINT32_MAX), WrappingInt32(1ul << 31), 0), 37 | static_cast(UINT32_MAX) >> 1); 38 | } catch (const exception &e) { 39 | cerr << e.what() << endl; 40 | return 1; 41 | } 42 | 43 | return EXIT_SUCCESS; 44 | } 45 | -------------------------------------------------------------------------------- /libsponge/tcp_helpers/fd_adapter.hh: -------------------------------------------------------------------------------- 1 | #ifndef SPONGE_LIBSPONGE_FD_ADAPTER_HH 2 | #define SPONGE_LIBSPONGE_FD_ADAPTER_HH 3 | 4 | #include "file_descriptor.hh" 5 | #include "lossy_fd_adapter.hh" 6 | #include "socket.hh" 7 | #include "tcp_config.hh" 8 | #include "tcp_header.hh" 9 | #include "tcp_segment.hh" 10 | 11 | #include 12 | #include 13 | 14 | //! \brief Basic functionality for file descriptor adaptors 15 | //! \details See TCPOverUDPSocketAdapter and TCPOverIPv4OverTunFdAdapter for more information. 16 | class FdAdapterBase { 17 | private: 18 | FdAdapterConfig _cfg{}; //!< Configuration values 19 | bool _listen = false; //!< Is the connected TCP FSM in listen state? 20 | 21 | protected: 22 | FdAdapterConfig &config_mutable() { return _cfg; } 23 | 24 | public: 25 | //! \brief Set the listening flag 26 | //! \param[in] l is the new value for the flag 27 | void set_listening(const bool l) { _listen = l; } 28 | 29 | //! \brief Get the listening flag 30 | //! \returns whether the FdAdapter is listening for a new connection 31 | bool listening() const { return _listen; } 32 | 33 | //! \brief Get the current configuration 34 | //! \returns a const reference 35 | const FdAdapterConfig &config() const { return _cfg; } 36 | 37 | //! \brief Get the current configuration (mutable) 38 | //! \returns a mutable reference 39 | FdAdapterConfig &config_mut() { return _cfg; } 40 | }; 41 | 42 | //! \brief A FD adaptor that reads and writes TCP segments in UDP payloads 43 | class TCPOverUDPSocketAdapter : public FdAdapterBase, public UDPSocket { 44 | public: 45 | //! Construct from a UDPSocket sliced into a FileDescriptor 46 | explicit TCPOverUDPSocketAdapter(FileDescriptor &&fd) : UDPSocket(std::move(fd)) {} 47 | 48 | //! Attempts to read and return a TCP segment related to the current connection from a UDP payload 49 | std::optional read(); 50 | 51 | //! Writes a TCP segment into a UDP payload 52 | void write(TCPSegment &seg); 53 | }; 54 | 55 | //! Typedef for TCPOverUDPSocketAdapter 56 | using LossyTCPOverUDPSocketAdapter = LossyFdAdapter; 57 | 58 | #endif // SPONGE_LIBSPONGE_FD_ADAPTER_HH 59 | -------------------------------------------------------------------------------- /tests/fsm_stream_reassembler_cap.cc: -------------------------------------------------------------------------------- 1 | #include "byte_stream.hh" 2 | #include "fsm_stream_reassembler_harness.hh" 3 | #include "stream_reassembler.hh" 4 | #include "util.hh" 5 | 6 | #include 7 | #include 8 | 9 | using namespace std; 10 | 11 | int main() { 12 | try { 13 | { 14 | ReassemblerTestHarness test{2}; 15 | 16 | test.execute(SubmitSegment{"ab", 0}); 17 | test.execute(BytesAssembled(2)); 18 | test.execute(BytesAvailable("ab")); 19 | 20 | test.execute(SubmitSegment{"cd", 2}); 21 | test.execute(BytesAssembled(4)); 22 | test.execute(BytesAvailable("cd")); 23 | 24 | test.execute(SubmitSegment{"ef", 4}); 25 | test.execute(BytesAssembled(6)); 26 | test.execute(BytesAvailable("ef")); 27 | } 28 | 29 | { 30 | ReassemblerTestHarness test{2}; 31 | 32 | test.execute(SubmitSegment{"ab", 0}); 33 | test.execute(BytesAssembled(2)); 34 | 35 | test.execute(SubmitSegment{"cd", 2}); 36 | test.execute(BytesAssembled(2)); 37 | 38 | test.execute(BytesAvailable("ab")); 39 | test.execute(BytesAssembled(2)); 40 | 41 | test.execute(SubmitSegment{"cd", 2}); 42 | test.execute(BytesAssembled(4)); 43 | 44 | test.execute(BytesAvailable("cd")); 45 | } 46 | 47 | { 48 | ReassemblerTestHarness test{1}; 49 | 50 | test.execute(SubmitSegment{"ab", 0}); 51 | test.execute(BytesAssembled(1)); 52 | 53 | test.execute(SubmitSegment{"ab", 0}); 54 | test.execute(BytesAssembled(1)); 55 | 56 | test.execute(BytesAvailable("a")); 57 | test.execute(BytesAssembled(1)); 58 | 59 | test.execute(SubmitSegment{"abc", 0}); 60 | test.execute(BytesAssembled(2)); 61 | 62 | test.execute(BytesAvailable("b")); 63 | test.execute(BytesAssembled(2)); 64 | } 65 | 66 | } catch (const exception &e) { 67 | cerr << "Exception: " << e.what() << endl; 68 | return EXIT_FAILURE; 69 | } 70 | 71 | return EXIT_SUCCESS; 72 | } 73 | -------------------------------------------------------------------------------- /libsponge/tcp_helpers/fd_adapter.cc: -------------------------------------------------------------------------------- 1 | #include "fd_adapter.hh" 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | using namespace std; 8 | 9 | //! \details This function first attempts to parse a TCP segment from the next UDP 10 | //! payload recv()d from the socket. 11 | //! 12 | //! If this succeeds, it then checks that the received segment is related to the 13 | //! current connection. When a TCP connection has been established, this means 14 | //! checking that the source and destination ports in the TCP header are correct. 15 | //! 16 | //! If the TCP FSM is listening (i.e., TCPOverUDPSocketAdapter::_listen is `true`) 17 | //! and the TCP segment read from the wire includes a SYN, this function clears the 18 | //! `_listen` flag and calls calls connect() on the underlying UDP socket, with 19 | //! the result that future outgoing segments go to the sender of the SYN segment. 20 | //! \returns a std::optional that is empty if the segment was invalid or unrelated 21 | optional TCPOverUDPSocketAdapter::read() { 22 | auto datagram = recv(); 23 | 24 | // is it for us? 25 | if (not listening() and (datagram.source_address != config().destination)) { 26 | return {}; 27 | } 28 | 29 | // is the payload a valid TCP segment? 30 | TCPSegment seg; 31 | if (ParseResult::NoError != seg.parse(move(datagram.payload), 0)) { 32 | return {}; 33 | } 34 | 35 | // should we target this source in all future replies? 36 | if (listening()) { 37 | if (seg.header().syn and not seg.header().rst) { 38 | config_mutable().destination = datagram.source_address; 39 | set_listening(false); 40 | } else { 41 | return {}; 42 | } 43 | } 44 | 45 | return seg; 46 | } 47 | 48 | //! Serialize a TCP segment and send it as the payload of a UDP datagram. 49 | //! \param[in] seg is the TCP segment to write 50 | void TCPOverUDPSocketAdapter::write(TCPSegment &seg) { 51 | seg.header().sport = config().source.port(); 52 | seg.header().dport = config().destination.port(); 53 | UDPSocket::sendto(config().destination, seg.serialize(0)); 54 | } 55 | 56 | //! Specialize LossyFdAdapter to TCPOverUDPSocketAdapter 57 | template class LossyFdAdapter; 58 | -------------------------------------------------------------------------------- /tests/fsm_stream_reassembler_win.cc: -------------------------------------------------------------------------------- 1 | #include "byte_stream.hh" 2 | #include "stream_reassembler.hh" 3 | #include "util.hh" 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | 15 | using namespace std; 16 | 17 | static constexpr unsigned NREPS = 32; 18 | static constexpr unsigned NSEGS = 128; 19 | static constexpr unsigned MAX_SEG_LEN = 2048; 20 | 21 | string read(StreamReassembler &reassembler) { 22 | return reassembler.stream_out().read(reassembler.stream_out().buffer_size()); 23 | } 24 | 25 | int main() { 26 | try { 27 | auto rd = get_random_generator(); 28 | 29 | // overlapping segments 30 | for (unsigned rep_no = 0; rep_no < NREPS; ++rep_no) { 31 | StreamReassembler buf{NSEGS * MAX_SEG_LEN}; 32 | 33 | vector> seq_size; 34 | size_t offset = 0; 35 | for (unsigned i = 0; i < NSEGS; ++i) { 36 | const size_t size = 1 + (rd() % (MAX_SEG_LEN - 1)); 37 | const size_t offs = min(offset, 1 + (static_cast(rd()) % 1023)); 38 | seq_size.emplace_back(offset - offs, size + offs); 39 | offset += size; 40 | } 41 | shuffle(seq_size.begin(), seq_size.end(), rd); 42 | 43 | string d(offset, 0); 44 | generate(d.begin(), d.end(), [&] { return rd(); }); 45 | 46 | for (auto [off, sz] : seq_size) { 47 | string dd(d.cbegin() + off, d.cbegin() + off + sz); 48 | buf.push_substring(move(dd), off, off + sz == offset); 49 | } 50 | 51 | auto result = read(buf); 52 | if (buf.stream_out().bytes_written() != offset) { // read bytes 53 | throw runtime_error("test 2 - number of RX bytes is incorrect"); 54 | } 55 | if (!equal(result.cbegin(), result.cend(), d.cbegin())) { 56 | throw runtime_error("test 2 - content of RX bytes is incorrect"); 57 | } 58 | } 59 | } catch (const exception &e) { 60 | cerr << "Exception: " << e.what() << endl; 61 | return EXIT_FAILURE; 62 | } 63 | 64 | return EXIT_SUCCESS; 65 | } 66 | -------------------------------------------------------------------------------- /libsponge/tcp_receiver.cc: -------------------------------------------------------------------------------- 1 | #include "tcp_receiver.hh" 2 | 3 | // Dummy implementation of a TCP receiver 4 | 5 | // For Lab 2, please replace with a real implementation that passes the 6 | // automated checks run by `make check_lab2`. 7 | 8 | using namespace std; 9 | 10 | bool TCPReceiver::segment_received(const TCPSegment &seg) 11 | { 12 | const TCPHeader &header = seg.header(); 13 | // It's a SYN, but if _syn has been set to true, you can't set it again. 14 | // And if you haven't set it, just set it to true. 15 | if (header.syn) 16 | { 17 | if (_syn) 18 | return false; 19 | _syn = true; 20 | _isn = header.seqno.raw_value(); 21 | } 22 | 23 | if (_syn) 24 | { 25 | bool eof = false; 26 | if (header.fin) 27 | { 28 | if (_fin) 29 | return false; 30 | 31 | _fin = true; 32 | eof = true; 33 | } 34 | 35 | uint64_t seqno = unwrap(header.seqno, WrappingInt32(_isn), _check_point); 36 | // Convert the absolute sequence number to the stream number 37 | if (header.syn) 38 | { 39 | seqno = 1; 40 | } 41 | 42 | // The seqno minus 1 is the index of byte stream. 43 | if (seqno - 1 >= _reassembler.unacceptable_byte()) 44 | { 45 | _seqno_out_of_window = true; 46 | return false; 47 | } 48 | 49 | if (seqno - 1 + seg.length_in_sequence_space() <= _reassembler.first_unreceived_byte()) 50 | { 51 | return false; 52 | } 53 | 54 | _reassembler.push_substring(string(seg.payload().str()), seqno - 1, eof); 55 | _check_point = seqno; 56 | return true; 57 | } 58 | 59 | // _syn == false 60 | return false; 61 | } 62 | 63 | optional TCPReceiver::ackno() const 64 | { 65 | uint64_t offset = 1; 66 | if (_fin && _reassembler.unassembled_bytes() == 0) 67 | { 68 | offset = 2; 69 | } 70 | 71 | if (_syn == true) 72 | { 73 | return wrap(_reassembler.first_unreceived_byte() + offset, WrappingInt32(_isn)); 74 | } 75 | return {}; 76 | } 77 | 78 | size_t TCPReceiver::window_size() const 79 | { 80 | // size_t here(64-bit system) is uint64_t 81 | return _capacity - _reassembler.stream_out().buffer_size(); 82 | } 83 | -------------------------------------------------------------------------------- /tests/fsm_stream_reassembler_seq.cc: -------------------------------------------------------------------------------- 1 | #include "byte_stream.hh" 2 | #include "fsm_stream_reassembler_harness.hh" 3 | #include "stream_reassembler.hh" 4 | #include "util.hh" 5 | 6 | #include 7 | #include 8 | 9 | using namespace std; 10 | 11 | int main() { 12 | try { 13 | { 14 | ReassemblerTestHarness test{65000}; 15 | 16 | test.execute(SubmitSegment{"abcd", 0}); 17 | test.execute(BytesAssembled(4)); 18 | test.execute(BytesAvailable("abcd")); 19 | test.execute(NotAtEof{}); 20 | 21 | test.execute(SubmitSegment{"efgh", 4}); 22 | test.execute(BytesAssembled(8)); 23 | test.execute(BytesAvailable("efgh")); 24 | test.execute(NotAtEof{}); 25 | } 26 | 27 | { 28 | ReassemblerTestHarness test{65000}; 29 | 30 | test.execute(SubmitSegment{"abcd", 0}); 31 | test.execute(BytesAssembled(4)); 32 | test.execute(NotAtEof{}); 33 | test.execute(SubmitSegment{"efgh", 4}); 34 | test.execute(BytesAssembled(8)); 35 | 36 | test.execute(BytesAvailable("abcdefgh")); 37 | test.execute(NotAtEof{}); 38 | } 39 | 40 | { 41 | ReassemblerTestHarness test{65000}; 42 | std::ostringstream ss; 43 | 44 | for (size_t i = 0; i < 100; ++i) { 45 | test.execute(BytesAssembled(4 * i)); 46 | test.execute(SubmitSegment{"abcd", 4 * i}); 47 | test.execute(NotAtEof{}); 48 | 49 | ss << "abcd"; 50 | } 51 | 52 | test.execute(BytesAvailable(ss.str())); 53 | test.execute(NotAtEof{}); 54 | } 55 | 56 | { 57 | ReassemblerTestHarness test{65000}; 58 | std::ostringstream ss; 59 | 60 | for (size_t i = 0; i < 100; ++i) { 61 | test.execute(BytesAssembled(4 * i)); 62 | test.execute(SubmitSegment{"abcd", 4 * i}); 63 | test.execute(NotAtEof{}); 64 | 65 | test.execute(BytesAvailable("abcd")); 66 | } 67 | } 68 | 69 | } catch (const exception &e) { 70 | cerr << "Exception: " << e.what() << endl; 71 | return EXIT_FAILURE; 72 | } 73 | 74 | return EXIT_SUCCESS; 75 | } 76 | -------------------------------------------------------------------------------- /tests/recv_close.cc: -------------------------------------------------------------------------------- 1 | #include "receiver_harness.hh" 2 | #include "wrapping_integers.hh" 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | using namespace std; 13 | 14 | int main() { 15 | try { 16 | auto rd = get_random_generator(); 17 | 18 | { 19 | uint32_t isn = uniform_int_distribution{0, UINT32_MAX}(rd); 20 | TCPReceiverTestHarness test{4000}; 21 | test.execute(ExpectState{TCPReceiverStateSummary::LISTEN}); 22 | test.execute(SegmentArrives{}.with_syn().with_seqno(isn + 0).with_result(SegmentArrives::Result::OK)); 23 | test.execute(ExpectState{TCPReceiverStateSummary::SYN_RECV}); 24 | test.execute(SegmentArrives{}.with_fin().with_seqno(isn + 1).with_result(SegmentArrives::Result::OK)); 25 | test.execute(ExpectAckno{WrappingInt32{isn + 2}}); 26 | test.execute(ExpectUnassembledBytes{0}); 27 | test.execute(ExpectBytes{""}); 28 | test.execute(ExpectTotalAssembledBytes{0}); 29 | test.execute(ExpectState{TCPReceiverStateSummary::FIN_RECV}); 30 | } 31 | 32 | { 33 | uint32_t isn = uniform_int_distribution{0, UINT32_MAX}(rd); 34 | TCPReceiverTestHarness test{4000}; 35 | test.execute(ExpectState{TCPReceiverStateSummary::LISTEN}); 36 | test.execute(SegmentArrives{}.with_syn().with_seqno(isn + 0).with_result(SegmentArrives::Result::OK)); 37 | test.execute(ExpectState{TCPReceiverStateSummary::SYN_RECV}); 38 | test.execute( 39 | SegmentArrives{}.with_fin().with_seqno(isn + 1).with_data("a").with_result(SegmentArrives::Result::OK)); 40 | test.execute(ExpectState{TCPReceiverStateSummary::FIN_RECV}); 41 | test.execute(ExpectAckno{WrappingInt32{isn + 3}}); 42 | test.execute(ExpectUnassembledBytes{0}); 43 | test.execute(ExpectBytes{"a"}); 44 | test.execute(ExpectTotalAssembledBytes{1}); 45 | test.execute(ExpectState{TCPReceiverStateSummary::FIN_RECV}); 46 | } 47 | 48 | } catch (const exception &e) { 49 | cerr << e.what() << endl; 50 | return 1; 51 | } 52 | 53 | return EXIT_SUCCESS; 54 | } 55 | -------------------------------------------------------------------------------- /tests/fsm_listen_relaxed.cc: -------------------------------------------------------------------------------- 1 | #include "tcp_config.hh" 2 | #include "tcp_expectation.hh" 3 | #include "tcp_fsm_test_harness.hh" 4 | #include "tcp_header.hh" 5 | #include "tcp_segment.hh" 6 | 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | using namespace std; 15 | using State = TCPTestHarness::State; 16 | 17 | int main() { 18 | try { 19 | TCPConfig cfg{}; 20 | 21 | // test #1: START -> LISTEN -> SYN -> SYN/ACK -> ACK 22 | { 23 | TCPTestHarness test_1(cfg); 24 | 25 | // tell the FSM to connect, make sure we get a SYN 26 | test_1.execute(Listen{}); 27 | test_1.execute(ExpectState{State::LISTEN}); 28 | test_1.execute(Tick(1)); 29 | test_1.execute(ExpectState{State::LISTEN}); 30 | 31 | test_1.send_syn(WrappingInt32{0}, {}); 32 | test_1.execute(Tick(1)); 33 | 34 | TCPSegment seg = test_1.expect_seg(ExpectOneSegment{}.with_syn(true).with_ack(true).with_ackno(1), 35 | "test 1 failed: no SYN/ACK in response to SYN"); 36 | test_1.execute(ExpectState{State::SYN_RCVD}); 37 | 38 | // wrong seqno! should get ACK back but not transition 39 | const WrappingInt32 syn_seqno = seg.header().seqno; 40 | test_1.send_ack(WrappingInt32{0}, syn_seqno + 1); 41 | test_1.execute(Tick(1)); 42 | 43 | test_1.execute( 44 | ExpectOneSegment{}.with_no_flags().with_ack(true).with_ackno(1).with_seqno(seg.header().seqno + 1), 45 | "test 1 failed: wrong response to old seqno"); 46 | 47 | test_1.send_ack(WrappingInt32(cfg.recv_capacity + 1), syn_seqno + 1); 48 | test_1.execute(Tick(1)); 49 | 50 | test_1.execute( 51 | ExpectOneSegment{}.with_no_flags().with_ack(true).with_ackno(1).with_seqno(seg.header().seqno + 1)); 52 | 53 | test_1.send_ack(WrappingInt32{1}, seg.header().seqno + 1); 54 | test_1.execute(Tick(1)); 55 | test_1.execute(ExpectNoSegment{}, "test 1 failed: no need to ACK an ACK"); 56 | test_1.execute(ExpectState{State::ESTABLISHED}); 57 | } 58 | } catch (const exception &e) { 59 | cerr << e.what() << endl; 60 | return 1; 61 | } 62 | 63 | return EXIT_SUCCESS; 64 | } 65 | -------------------------------------------------------------------------------- /tests/send_equivalence_checker.cc: -------------------------------------------------------------------------------- 1 | #include "send_equivalence_checker.hh" 2 | 3 | #include "tcp_segment.hh" 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | using namespace std; 11 | 12 | void check_equivalent_segments(const TCPSegment &a, const TCPSegment &b) { 13 | if (not(a.header() == b.header())) { 14 | cerr << a.header().to_string() << endl; 15 | cerr << b.header().to_string() << endl; 16 | stringstream s{}; 17 | s << a.header().summary() << " with " << a.payload().size() << " bytes != " << b.header().summary() << " with " 18 | << b.payload().size() << " bytes"; 19 | throw runtime_error(s.str()); 20 | } 21 | if (a.payload().str() != b.payload().str()) { 22 | stringstream s{}; 23 | s << a.header().summary() << " with " << a.payload().size() << " bytes != " << b.header().summary() << " with " 24 | << b.payload().size() << " bytes"; 25 | throw runtime_error("unequal payloads: " + s.str()); 26 | } 27 | } 28 | 29 | void SendEquivalenceChecker::submit_a(TCPSegment &seg) { 30 | if (not bs.empty()) { 31 | check_equivalent_segments(seg, bs.front()); 32 | bs.pop_front(); 33 | } else { 34 | TCPSegment cp; 35 | cp.parse(seg.serialize()); 36 | as.emplace_back(move(cp)); 37 | } 38 | } 39 | 40 | void SendEquivalenceChecker::submit_b(TCPSegment &seg) { 41 | if (not as.empty()) { 42 | check_equivalent_segments(as.front(), seg); 43 | as.pop_front(); 44 | } else { 45 | TCPSegment cp; 46 | cp.parse(seg.serialize()); 47 | bs.emplace_back(move(cp)); 48 | } 49 | } 50 | 51 | void SendEquivalenceChecker::check_empty() const { 52 | if (not as.empty()) { 53 | stringstream s{}; 54 | s << as.size() << " unmatched packets from standard" << endl; 55 | for (const TCPSegment &a : as) { 56 | s << " - " << a.header().summary() << " with " << a.payload().size() << " bytes\n"; 57 | } 58 | throw runtime_error(s.str()); 59 | } 60 | if (not bs.empty()) { 61 | stringstream s{}; 62 | s << bs.size() << " unmatched packets from factored" << endl; 63 | for (const TCPSegment &a : bs) { 64 | s << " - " << a.header().summary() << " with " << a.payload().size() << " bytes\n"; 65 | } 66 | throw runtime_error(s.str()); 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /libsponge/wrapping_integers.cc: -------------------------------------------------------------------------------- 1 | #include "wrapping_integers.hh" 2 | 3 | // Dummy implementation of a 32-bit wrapping integer 4 | 5 | // For Lab 2, please replace with a real implementation that passes the 6 | // automated checks run by `make check_lab2`. 7 | 8 | using namespace std; 9 | 10 | //! Transform an "absolute" 64-bit sequence number (zero-indexed) into a WrappingInt32 11 | //! \param n The input absolute 64-bit sequence number 12 | //! \param isn The initial sequence number 13 | WrappingInt32 wrap(uint64_t n, WrappingInt32 isn) 14 | { 15 | return isn + n; 16 | } 17 | 18 | //! Transform a WrappingInt32 into an "absolute" 64-bit sequence number (zero-indexed) 19 | //! \param n The relative sequence number 20 | //! \param isn The initial sequence number 21 | //! \param checkpoint A recent absolute 64-bit sequence number 22 | //! \returns the 64-bit sequence number that wraps to `n` and is closest to `checkpoint` 23 | //! 24 | //! \note Each of the two streams of the TCP connection has its own ISN. One stream 25 | //! runs from the local TCPSender to the remote TCPReceiver and has one ISN, 26 | //! and the other stream runs from the remote TCPSender to the local TCPReceiver and 27 | //! has a different ISN. 28 | 29 | // #include 30 | // using std::cout; 31 | // using std::endl; 32 | 33 | uint64_t unwrap(WrappingInt32 n, WrappingInt32 isn, uint64_t checkpoint) 34 | { 35 | uint64_t absolute_no = 0; 36 | if (n.raw_value() >= isn.raw_value()) 37 | { 38 | absolute_no = n.raw_value() - isn.raw_value(); 39 | } 40 | else 41 | { 42 | absolute_no = 1 + (uint64_t)UINT32_MAX - isn.raw_value() + n.raw_value(); 43 | // minus first to avoid overflow 44 | } 45 | 46 | uint64_t curr_diff = UINT64_MAX, old_diff = UINT64_MAX; 47 | uint64_t old_absolute_no = absolute_no; 48 | while (1) 49 | { 50 | uint64_t temp; 51 | if (checkpoint >= absolute_no) 52 | { 53 | temp = checkpoint - absolute_no; 54 | } 55 | else 56 | { 57 | temp = absolute_no - checkpoint; 58 | } 59 | 60 | old_diff = curr_diff; 61 | curr_diff = temp; 62 | 63 | if (curr_diff > old_diff) 64 | { 65 | break; 66 | } 67 | 68 | if (absolute_no + UINT32_MAX < absolute_no) 69 | { 70 | break; 71 | } 72 | old_absolute_no = absolute_no; 73 | absolute_no += (uint64_t)UINT32_MAX + 1; 74 | } 75 | // Using these for testing. 76 | // cout << "a" << endl; 77 | // cout << old_absolute_no << endl; 78 | return old_absolute_no; 79 | } 80 | -------------------------------------------------------------------------------- /tests/fsm_listen.cc: -------------------------------------------------------------------------------- 1 | #include "tcp_config.hh" 2 | #include "tcp_expectation.hh" 3 | #include "tcp_fsm_test_harness.hh" 4 | #include "tcp_header.hh" 5 | #include "tcp_segment.hh" 6 | 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | using namespace std; 15 | using State = TCPTestHarness::State; 16 | 17 | int main() { 18 | try { 19 | TCPConfig cfg{}; 20 | 21 | // test #1: START -> LISTEN -> data without ACK or SYN (dropped) -> SYN -> SYN/ACK -> ACK 22 | { 23 | TCPTestHarness test_1(cfg); 24 | 25 | // tell the FSM to connect, make sure we get a SYN 26 | test_1.execute(Listen{}); 27 | test_1.execute(Tick(1)); 28 | 29 | test_1.send_byte(WrappingInt32{0}, {}, 0); 30 | test_1.send_fin(WrappingInt32{0}, {}); 31 | test_1.execute(Tick(1)); 32 | test_1.execute(ExpectState{State::LISTEN}); 33 | test_1.execute(ExpectNoSegment{}, "test 1 failed: non-ACK data segment should be ignored"); 34 | 35 | test_1.send_syn(WrappingInt32{0}, {}); 36 | test_1.execute(Tick(1)); 37 | 38 | TCPSegment seg = test_1.expect_seg(ExpectOneSegment{}.with_syn(true).with_ack(true).with_ackno(1), 39 | "test 1 failed: no SYN/ACK in response to SYN"); 40 | test_1.execute(ExpectState{State::SYN_RCVD}); 41 | 42 | // wrong seqno! should get ACK back but not transition 43 | const WrappingInt32 syn_seqno = seg.header().seqno; 44 | test_1.send_ack(WrappingInt32{0}, syn_seqno + 1); 45 | test_1.execute(Tick(1)); 46 | 47 | test_1.execute( 48 | ExpectOneSegment{}.with_no_flags().with_ack(true).with_ackno(1).with_seqno(seg.header().seqno + 1), 49 | "test 1 failed: wrong response to old seqno"); 50 | 51 | test_1.send_ack(WrappingInt32(cfg.recv_capacity + 1), syn_seqno + 1); 52 | test_1.execute(Tick(1)); 53 | 54 | test_1.execute( 55 | ExpectOneSegment{}.with_no_flags().with_ack(true).with_ackno(1).with_seqno(seg.header().seqno + 1)); 56 | 57 | test_1.send_ack(WrappingInt32{1}, seg.header().seqno + 1); 58 | test_1.execute(Tick(1)); 59 | test_1.execute(ExpectNoSegment{}, "test 1 failed: no need to ACK an ACK"); 60 | test_1.execute(ExpectState{State::ESTABLISHED}); 61 | } 62 | } catch (const exception &e) { 63 | cerr << e.what() << endl; 64 | return 1; 65 | } 66 | 67 | return EXIT_SUCCESS; 68 | } 69 | -------------------------------------------------------------------------------- /tests/byte_stream_capacity.cc: -------------------------------------------------------------------------------- 1 | #include "byte_stream.hh" 2 | #include "byte_stream_test_harness.hh" 3 | 4 | #include 5 | #include 6 | 7 | using namespace std; 8 | 9 | int main() { 10 | try { 11 | { 12 | ByteStreamTestHarness test{"overwrite", 2}; 13 | 14 | test.execute(Write{"cat"}.with_bytes_written(2)); 15 | 16 | test.execute(InputEnded{false}); 17 | test.execute(BufferEmpty{false}); 18 | test.execute(Eof{false}); 19 | test.execute(BytesRead{0}); 20 | test.execute(BytesWritten{2}); 21 | test.execute(RemainingCapacity{0}); 22 | test.execute(BufferSize{2}); 23 | test.execute(Peek{"ca"}); 24 | 25 | test.execute(Write{"t"}.with_bytes_written(0)); 26 | 27 | test.execute(InputEnded{false}); 28 | test.execute(BufferEmpty{false}); 29 | test.execute(Eof{false}); 30 | test.execute(BytesRead{0}); 31 | test.execute(BytesWritten{2}); 32 | test.execute(RemainingCapacity{0}); 33 | test.execute(BufferSize{2}); 34 | test.execute(Peek{"ca"}); 35 | } 36 | 37 | { 38 | ByteStreamTestHarness test{"overwrite-clear-overwrite", 2}; 39 | 40 | test.execute(Write{"cat"}.with_bytes_written(2)); 41 | test.execute(Pop{2}); 42 | test.execute(Write{"tac"}.with_bytes_written(2)); 43 | 44 | test.execute(InputEnded{false}); 45 | test.execute(BufferEmpty{false}); 46 | test.execute(Eof{false}); 47 | test.execute(BytesRead{2}); 48 | test.execute(BytesWritten{4}); 49 | test.execute(RemainingCapacity{0}); 50 | test.execute(BufferSize{2}); 51 | test.execute(Peek{"ta"}); 52 | } 53 | 54 | { 55 | ByteStreamTestHarness test{"overwrite-pop-overwrite", 2}; 56 | 57 | test.execute(Write{"cat"}.with_bytes_written(2)); 58 | test.execute(Pop{1}); 59 | test.execute(Write{"tac"}.with_bytes_written(1)); 60 | 61 | test.execute(InputEnded{false}); 62 | test.execute(BufferEmpty{false}); 63 | test.execute(Eof{false}); 64 | test.execute(BytesRead{1}); 65 | test.execute(BytesWritten{3}); 66 | test.execute(RemainingCapacity{0}); 67 | test.execute(BufferSize{2}); 68 | test.execute(Peek{"at"}); 69 | } 70 | 71 | } catch (const exception &e) { 72 | cerr << "Exception: " << e.what() << endl; 73 | return EXIT_FAILURE; 74 | } 75 | 76 | return EXIT_SUCCESS; 77 | } 78 | -------------------------------------------------------------------------------- /tests/fsm_retx_relaxed.cc: -------------------------------------------------------------------------------- 1 | #include "fsm_retx.hh" 2 | #include "tcp_config.hh" 3 | #include "tcp_expectation.hh" 4 | #include "util.hh" 5 | 6 | #include 7 | #include 8 | #include 9 | 10 | using namespace std; 11 | using State = TCPTestHarness::State; 12 | 13 | int main() { 14 | try { 15 | TCPConfig cfg{}; 16 | cfg.recv_capacity = 65000; 17 | auto rd = get_random_generator(); 18 | 19 | // NOTE: the timeouts in this test are carefully adjusted to work whether the tcp_state_machine sends 20 | // immediately upon write() or only after the next tick(). If you edit this test, make sure that 21 | // it passes both ways (i.e., with and without calling _try_send() in TCPConnection::write()) 22 | 23 | // NOTE 2: ACK -- I think I was successful, although given unrelated 24 | // refactor to template code it will be more challenging now 25 | // to wait until tick() to send an outgoing segment. 26 | 27 | // single segment re-transmit 28 | { 29 | WrappingInt32 tx_ackno(rd()); 30 | TCPTestHarness test_1 = TCPTestHarness::in_established(cfg, tx_ackno - 1, tx_ackno - 1); 31 | 32 | string data = "asdf"; 33 | test_1.execute(Write{data}); 34 | test_1.execute(Tick(1)); 35 | 36 | check_segment(test_1, data, false, __LINE__); 37 | 38 | test_1.execute(Tick(cfg.rt_timeout - 2)); 39 | test_1.execute(ExpectNoSegment{}, "test 1 failed: re-tx too fast"); 40 | 41 | test_1.execute(Tick(2)); 42 | check_segment(test_1, data, false, __LINE__); 43 | 44 | test_1.execute(Tick(10 * cfg.rt_timeout + 100)); 45 | check_segment(test_1, data, false, __LINE__); 46 | 47 | for (unsigned i = 2; i < TCPConfig::MAX_RETX_ATTEMPTS; ++i) { 48 | test_1.execute(Tick((cfg.rt_timeout << i) - i)); // exponentially increasing delay length 49 | test_1.execute(ExpectNoSegment{}, "test 1 failed: re-tx too fast after timeout"); 50 | test_1.execute(Tick(i)); 51 | check_segment(test_1, data, false, __LINE__); 52 | } 53 | 54 | test_1.execute(ExpectState{State::ESTABLISHED}); 55 | 56 | test_1.execute(Tick(1 + (cfg.rt_timeout << TCPConfig::MAX_RETX_ATTEMPTS))); 57 | test_1.execute(ExpectState{State::RESET}); 58 | test_1.execute(ExpectOneSegment{}.with_rst(true), "test 1 failed: RST on re-tx failure was malformed"); 59 | } 60 | } catch (const exception &e) { 61 | cerr << e.what() << endl; 62 | return 1; 63 | } 64 | 65 | return EXIT_SUCCESS; 66 | } 67 | -------------------------------------------------------------------------------- /tests/fsm_retx.cc: -------------------------------------------------------------------------------- 1 | #include "fsm_retx.hh" 2 | 3 | #include "tcp_config.hh" 4 | #include "tcp_expectation.hh" 5 | #include "util.hh" 6 | 7 | #include 8 | #include 9 | #include 10 | 11 | using namespace std; 12 | using State = TCPTestHarness::State; 13 | 14 | int main() { 15 | try { 16 | TCPConfig cfg{}; 17 | cfg.recv_capacity = 65000; 18 | auto rd = get_random_generator(); 19 | 20 | // NOTE: the timeouts in this test are carefully adjusted to work whether the tcp_state_machine sends 21 | // immediately upon write() or only after the next tick(). If you edit this test, make sure that 22 | // it passes both ways (i.e., with and without calling _try_send() in TCPConnection::write()) 23 | 24 | // NOTE 2: ACK -- I think I was successful, although given unrelated 25 | // refactor to template code it will be more challenging now 26 | // to wait until tick() to send an outgoing segment. 27 | 28 | // single segment re-transmit 29 | { 30 | const WrappingInt32 tx_ackno(rd()); 31 | TCPTestHarness test_1 = TCPTestHarness::in_established(cfg, tx_ackno - 1, tx_ackno - 1); 32 | 33 | string data = "asdf"; 34 | test_1.execute(Write{data}); 35 | test_1.execute(Tick(1)); 36 | 37 | check_segment(test_1, data, false, __LINE__); 38 | 39 | test_1.execute(Tick(cfg.rt_timeout - 2)); 40 | test_1.execute(ExpectNoSegment{}, "test 1 failed: re-tx too fast"); 41 | 42 | test_1.execute(Tick(2)); 43 | check_segment(test_1, data, false, __LINE__); 44 | 45 | test_1.execute(Tick(10 * cfg.rt_timeout + 100)); 46 | check_segment(test_1, data, false, __LINE__); 47 | 48 | for (unsigned i = 2; i < TCPConfig::MAX_RETX_ATTEMPTS; ++i) { 49 | test_1.execute(Tick((cfg.rt_timeout << i) - i)); // exponentially increasing delay length 50 | test_1.execute(ExpectNoSegment{}, "test 1 failed: re-tx too fast after timeout"); 51 | test_1.execute(Tick(i)); 52 | check_segment(test_1, data, false, __LINE__); 53 | } 54 | 55 | test_1.execute(ExpectState{State::ESTABLISHED}); 56 | 57 | test_1.execute(Tick(1 + (cfg.rt_timeout << TCPConfig::MAX_RETX_ATTEMPTS))); 58 | test_1.execute(ExpectState{State::RESET}); 59 | test_1.execute(ExpectOneSegment{}.with_rst(true).with_ack(false).with_seqno(tx_ackno), 60 | "test 1 failed: RST on re-tx failure was malformed"); 61 | } 62 | } catch (const exception &e) { 63 | cerr << e.what() << endl; 64 | return 1; 65 | } 66 | 67 | return EXIT_SUCCESS; 68 | } 69 | -------------------------------------------------------------------------------- /libsponge/util/parser.hh: -------------------------------------------------------------------------------- 1 | #ifndef SPONGE_LIBSPONGE_PARSER_HH 2 | #define SPONGE_LIBSPONGE_PARSER_HH 3 | 4 | #include "buffer.hh" 5 | 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | //! The result of parsing or unparsing an IP datagram or TCP segment 12 | enum class ParseResult { 13 | NoError = 0, //!< Success 14 | BadChecksum, //!< Bad checksum 15 | PacketTooShort, //!< Not enough data to finish parsing 16 | WrongIPVersion, //!< Got a version of IP other than 4 17 | HeaderTooShort, //!< Header length is shorter than minimum required 18 | TruncatedPacket, //!< Packet length is shorter than header claims 19 | }; 20 | 21 | //! Output a string representation of a ParseResult 22 | std::string as_string(const ParseResult r); 23 | 24 | class NetParser { 25 | private: 26 | Buffer _buffer; 27 | ParseResult _error = ParseResult::NoError; //!< Result of parsing so far 28 | 29 | //! Check that there is sufficient data to parse the next token 30 | void _check_size(const size_t size); 31 | 32 | //! Generic integer parsing method (used by u32, u16, u8) 33 | template 34 | T _parse_int(); 35 | 36 | public: 37 | NetParser(Buffer buffer) : _buffer(buffer) {} 38 | 39 | Buffer buffer() const { return _buffer; } 40 | 41 | //! Get the current value stored in BaseParser::_error 42 | ParseResult get_error() const { return _error; } 43 | 44 | //! \brief Set BaseParser::_error 45 | //! \param[in] res is the value to store in BaseParser::_error 46 | void set_error(ParseResult res) { _error = res; } 47 | 48 | //! Returns `true` if there has been an error 49 | bool error() const { return get_error() != ParseResult::NoError; } 50 | 51 | //! Parse a 32-bit integer in network byte order from the data stream 52 | uint32_t u32(); 53 | 54 | //! Parse a 16-bit integer in network byte order from the data stream 55 | uint16_t u16(); 56 | 57 | //! Parse an 8-bit integer in network byte order from the data stream 58 | uint8_t u8(); 59 | 60 | //! Remove n bytes from the buffer 61 | void remove_prefix(const size_t n); 62 | }; 63 | 64 | struct NetUnparser { 65 | template 66 | static void _unparse_int(std::string &s, T val); 67 | 68 | //! Write a 32-bit integer into the data stream in network byte order 69 | static void u32(std::string &s, const uint32_t val); 70 | 71 | //! Write a 16-bit integer into the data stream in network byte order 72 | static void u16(std::string &s, const uint16_t val); 73 | 74 | //! Write an 8-bit integer into the data stream in network byte order 75 | static void u8(std::string &s, const uint8_t val); 76 | }; 77 | 78 | #endif // SPONGE_LIBSPONGE_PARSER_HH 79 | -------------------------------------------------------------------------------- /libsponge/util/util.hh: -------------------------------------------------------------------------------- 1 | #ifndef SPONGE_LIBSPONGE_UTIL_HH 2 | #define SPONGE_LIBSPONGE_UTIL_HH 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | 15 | //! std::system_error plus the name of what was being attempted 16 | class tagged_error : public std::system_error { 17 | private: 18 | std::string _attempt_and_error; //!< What was attempted, and what happened 19 | 20 | public: 21 | //! \brief Construct from a category, an attempt, and an error code 22 | //! \param[in] category is the category of error 23 | //! \param[in] attempt is what was supposed to happen 24 | //! \param[in] error_code is the resulting error 25 | tagged_error(const std::error_category &category, const std::string &attempt, const int error_code) 26 | : system_error(error_code, category), _attempt_and_error(attempt + ": " + std::system_error::what()) {} 27 | 28 | //! Returns a C string describing the error 29 | const char *what() const noexcept override { return _attempt_and_error.c_str(); } 30 | }; 31 | 32 | //! a tagged_error for syscalls 33 | class unix_error : public tagged_error { 34 | public: 35 | //! brief Construct from a syscall name and the resulting errno 36 | //! \param[in] attempt is the name of the syscall attempted 37 | //! \param[in] error is the [errno(3)](\ref man3::errno) that resulted 38 | explicit unix_error(const std::string &attempt, const int error = errno) 39 | : tagged_error(std::system_category(), attempt, error) {} 40 | }; 41 | 42 | //! Error-checking wrapper for most syscalls 43 | int SystemCall(const char *attempt, const int return_value, const int errno_mask = 0); 44 | 45 | //! Version of SystemCall that takes a C++ std::string 46 | int SystemCall(const std::string &attempt, const int return_value, const int errno_mask = 0); 47 | 48 | //! Seed a fast random generator 49 | std::mt19937 get_random_generator(); 50 | 51 | //! Get the time in milliseconds since the program began. 52 | uint64_t timestamp_ms(); 53 | 54 | //! The internet checksum algorithm 55 | class InternetChecksum { 56 | private: 57 | uint32_t _sum; 58 | bool _parity{}; 59 | 60 | public: 61 | InternetChecksum(const uint32_t initial_sum = 0); 62 | void add(std::string_view data); 63 | uint16_t value() const; 64 | }; 65 | 66 | //! Hexdump the contents of a packet (or any other sequence of bytes) 67 | void hexdump(const char *data, const size_t len, const size_t indent = 0); 68 | 69 | //! Hexdump the contents of a packet (or any other sequence of bytes) 70 | void hexdump(const uint8_t *data, const size_t len, const size_t indent = 0); 71 | 72 | #endif // SPONGE_LIBSPONGE_UTIL_HH 73 | -------------------------------------------------------------------------------- /libsponge/tcp_helpers/lossy_fd_adapter.hh: -------------------------------------------------------------------------------- 1 | #ifndef SPONGE_LIBSPONGE_LOSSY_FD_ADAPTER_HH 2 | #define SPONGE_LIBSPONGE_LOSSY_FD_ADAPTER_HH 3 | 4 | #include "file_descriptor.hh" 5 | #include "tcp_config.hh" 6 | #include "tcp_segment.hh" 7 | #include "util.hh" 8 | 9 | #include 10 | #include 11 | #include 12 | 13 | //! An adapter class that adds random dropping behavior to an FD adapter 14 | template 15 | class LossyFdAdapter { 16 | private: 17 | //! Fast RNG used by _should_drop() 18 | std::mt19937 _rand{get_random_generator()}; 19 | 20 | //! The underlying FD adapter 21 | AdapterT _adapter; 22 | 23 | //! \brief Determine whether or not to drop a given read or write 24 | //! \param[in] uplink is `true` to use the uplink loss probability, else use the downlink loss probability 25 | //! \returns `true` if the segment should be dropped 26 | bool _should_drop(bool uplink) { 27 | const auto &cfg = _adapter.config(); 28 | const uint16_t loss = uplink ? cfg.loss_rate_up : cfg.loss_rate_dn; 29 | return loss != 0 && uint16_t(_rand()) < loss; 30 | } 31 | 32 | public: 33 | //! Conversion to a FileDescriptor by returning the underlying AdapterT 34 | operator const FileDescriptor &() const { return _adapter; } 35 | 36 | //! Construct from a FileDescriptor appropriate to the AdapterT constructor 37 | explicit LossyFdAdapter(FileDescriptor &&fd) : _adapter(std::move(fd)) {} 38 | 39 | //! \brief Read from the underlying AdapterT instance, potentially dropping the read datagram 40 | //! \returns std::optional that is empty if the segment was dropped or if 41 | //! the underlying AdapterT returned an empty value 42 | std::optional read() { 43 | auto ret = _adapter.read(); 44 | if (_should_drop(false)) { 45 | return {}; 46 | } 47 | return ret; 48 | } 49 | 50 | //! \brief Write to the underlying AdapterT instance, potentially dropping the datagram to be written 51 | //! \param[in] seg is the packet to either write or drop 52 | void write(TCPSegment &seg) { 53 | if (_should_drop(true)) { 54 | return; 55 | } 56 | return _adapter.write(seg); 57 | } 58 | 59 | //! \name 60 | //! Passthrough functions to the underlying AdapterT instance 61 | 62 | //!@{ 63 | void set_listening(const bool l) { _adapter.set_listening(l); } //!< FdAdapterBase::set_listening passthrough 64 | const FdAdapterConfig &config() const { return _adapter.config(); } //!< FdAdapterBase::config passthrough 65 | FdAdapterConfig &config_mut() { return _adapter.config_mut(); } //!< FdAdapterBase::config_mut passthrough 66 | //!@} 67 | }; 68 | 69 | #endif // SPONGE_LIBSPONGE_LOSSY_FD_ADAPTER_HH 70 | -------------------------------------------------------------------------------- /libsponge/byte_stream.cc: -------------------------------------------------------------------------------- 1 | #include "byte_stream.hh" 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | // Dummy implementation of a flow-controlled in-memory byte stream. 8 | 9 | // For Lab 0, please replace with a real implementation that passes the 10 | // automated checks run by `make check_lab0`. 11 | 12 | // You will need to add private members to the class declaration in `byte_stream.hh` 13 | 14 | // template 15 | // void DUMMY_CODE(Targs &&.../* unused */) {} 16 | 17 | using namespace std; 18 | 19 | ByteStream::ByteStream(const size_t capacity) : _buffer_capacity(capacity) {} 20 | 21 | // #include 22 | 23 | size_t ByteStream::write(const string &data) 24 | { 25 | // std::cout << data << std::endl; 26 | size_t len = data.size(); 27 | if (len > remaining_capacity()) 28 | { 29 | len = remaining_capacity(); 30 | } 31 | _total_size_write += len; 32 | for (size_t i = 0; i < len; ++i) 33 | { 34 | _container.push_back(data[i]); 35 | } 36 | return len; 37 | } 38 | 39 | //! \param[in] len bytes will be copied from the output side of the buffer 40 | string ByteStream::peek_output(const size_t len) const 41 | { 42 | size_t sz = len; 43 | if (sz > buffer_size()) 44 | { 45 | sz = buffer_size(); 46 | } 47 | // string s; 48 | // for (size_t i = 0; i < sz; ++i) 49 | // { 50 | // s.push_back(_container.at(i)); 51 | // } 52 | // return 0; 53 | return string().assign(_container.cbegin(), _container.cbegin() + sz); 54 | } 55 | 56 | //! \param[in] len bytes will be removed from the output side of the buffer 57 | void ByteStream::pop_output(const size_t len) 58 | { 59 | size_t sz = len; 60 | if (sz > buffer_size()) 61 | { 62 | sz = buffer_size(); 63 | } 64 | for (size_t i = 0; i < sz; ++i) 65 | { 66 | _container.pop_front(); 67 | } 68 | _total_size_read += sz; 69 | } 70 | 71 | void ByteStream::end_input() 72 | { 73 | _end_write = true; 74 | } 75 | 76 | bool ByteStream::input_ended() const 77 | { 78 | return _end_write; 79 | } 80 | 81 | size_t ByteStream::buffer_size() const 82 | { 83 | return _container.size(); 84 | } 85 | 86 | bool ByteStream::buffer_empty() const 87 | { 88 | return buffer_size() == 0; 89 | } 90 | 91 | bool ByteStream::eof() const 92 | { 93 | return (buffer_empty() && input_ended()); 94 | } 95 | 96 | size_t ByteStream::bytes_written() const 97 | { 98 | return _total_size_write; 99 | } 100 | 101 | size_t ByteStream::bytes_read() const 102 | { 103 | return _total_size_read; 104 | } 105 | 106 | size_t ByteStream::remaining_capacity() const 107 | { 108 | return _buffer_capacity - buffer_size(); 109 | } 110 | -------------------------------------------------------------------------------- /libsponge/util/buffer.cc: -------------------------------------------------------------------------------- 1 | #include "buffer.hh" 2 | 3 | using namespace std; 4 | 5 | void Buffer::remove_prefix(const size_t n) { 6 | if (n > str().size()) { 7 | throw out_of_range("Buffer::remove_prefix"); 8 | } 9 | _starting_offset += n; 10 | if (_storage and _starting_offset == _storage->size()) { 11 | _storage.reset(); 12 | } 13 | } 14 | 15 | void BufferList::append(const BufferList &other) { 16 | for (const auto &buf : other._buffers) { 17 | _buffers.push_back(buf); 18 | } 19 | } 20 | 21 | BufferList::operator Buffer() const { 22 | switch (_buffers.size()) { 23 | case 0: 24 | return {}; 25 | case 1: 26 | return _buffers[0]; 27 | default: { 28 | throw runtime_error( 29 | "BufferList: please use concatenate() to combine a multi-Buffer BufferList into one Buffer"); 30 | } 31 | } 32 | } 33 | 34 | string BufferList::concatenate() const { 35 | std::string ret; 36 | ret.reserve(size()); 37 | for (const auto &buf : _buffers) { 38 | ret.append(buf); 39 | } 40 | return ret; 41 | } 42 | 43 | size_t BufferList::size() const { 44 | size_t ret = 0; 45 | for (const auto &buf : _buffers) { 46 | ret += buf.size(); 47 | } 48 | return ret; 49 | } 50 | 51 | void BufferList::remove_prefix(size_t n) { 52 | while (n > 0) { 53 | if (_buffers.empty()) { 54 | throw std::out_of_range("BufferList::remove_prefix"); 55 | } 56 | 57 | if (n < _buffers.front().str().size()) { 58 | _buffers.front().remove_prefix(n); 59 | n = 0; 60 | } else { 61 | n -= _buffers.front().str().size(); 62 | _buffers.pop_front(); 63 | } 64 | } 65 | } 66 | 67 | BufferViewList::BufferViewList(const BufferList &buffers) { 68 | for (const auto &x : buffers.buffers()) { 69 | _views.push_back(x); 70 | } 71 | } 72 | 73 | void BufferViewList::remove_prefix(size_t n) { 74 | while (n > 0) { 75 | if (_views.empty()) { 76 | throw std::out_of_range("BufferListView::remove_prefix"); 77 | } 78 | 79 | if (n < _views.front().size()) { 80 | _views.front().remove_prefix(n); 81 | n = 0; 82 | } else { 83 | n -= _views.front().size(); 84 | _views.pop_front(); 85 | } 86 | } 87 | } 88 | 89 | size_t BufferViewList::size() const { 90 | size_t ret = 0; 91 | for (const auto &buf : _views) { 92 | ret += buf.size(); 93 | } 94 | return ret; 95 | } 96 | 97 | vector BufferViewList::as_iovecs() const { 98 | vector ret; 99 | ret.reserve(_views.size()); 100 | for (const auto &x : _views) { 101 | ret.push_back({const_cast(x.data()), x.size()}); 102 | } 103 | return ret; 104 | } 105 | -------------------------------------------------------------------------------- /libsponge/byte_stream.hh: -------------------------------------------------------------------------------- 1 | #ifndef SPONGE_LIBSPONGE_BYTE_STREAM_HH 2 | #define SPONGE_LIBSPONGE_BYTE_STREAM_HH 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | using std::deque; 12 | 13 | //! \brief An in-order byte stream. 14 | 15 | //! Bytes are written on the "input" side and read from the "output" 16 | //! side. The byte stream is finite: the writer can end the input, 17 | //! and then no more bytes can be written. 18 | class ByteStream 19 | { 20 | private: 21 | // Your code here -- add private members as necessary. 22 | deque _container = {}; 23 | size_t _buffer_capacity = 0; 24 | bool _end_write = false; 25 | size_t _total_size_write = 0; 26 | size_t _total_size_read = 0; 27 | bool _error = false; //!< Flag indicating that the stream suffered an error. 28 | 29 | public: 30 | //! Construct a stream with room for `capacity` bytes. 31 | ByteStream(const size_t capacity); 32 | 33 | //! \name "Input" interface for the writer 34 | //!@{ 35 | 36 | //! Write a string of bytes into the stream. Write as many 37 | //! as will fit, and return how many were written. 38 | //! \returns the number of bytes accepted into the stream 39 | size_t write(const std::string &data); 40 | 41 | //! \returns the number of additional bytes that the stream has space for 42 | size_t remaining_capacity() const; 43 | 44 | //! Signal that the byte stream has reached its ending 45 | void end_input(); 46 | 47 | //! Indicate that the stream suffered an error. 48 | void set_error() { _error = true; } 49 | //!@} 50 | 51 | //! \name "Output" interface for the reader 52 | //!@{ 53 | 54 | //! Peek at next "len" bytes of the stream 55 | //! \returns a string 56 | std::string peek_output(const size_t len) const; 57 | 58 | //! Remove bytes from the buffer 59 | void pop_output(const size_t len); 60 | 61 | //! Read (i.e., copy and then pop) the next "len" bytes of the stream 62 | //! \returns a vector of bytes read 63 | std::string read(const size_t len) 64 | { 65 | const auto ret = peek_output(len); 66 | pop_output(len); 67 | return ret; 68 | } 69 | 70 | //! \returns `true` if the stream input has ended 71 | bool input_ended() const; 72 | 73 | //! \returns `true` if the stream has suffered an error 74 | bool error() const { return _error; } 75 | 76 | //! \returns the maximum amount that can currently be read from the stream 77 | size_t buffer_size() const; 78 | 79 | //! \returns `true` if the buffer is empty 80 | bool buffer_empty() const; 81 | 82 | //! \returns `true` if the output has reached the ending 83 | bool eof() const; 84 | //!@} 85 | 86 | //! \name General accounting 87 | //!@{ 88 | 89 | //! Total number of bytes written 90 | size_t bytes_written() const; 91 | 92 | //! Total number of bytes popped 93 | size_t bytes_read() const; 94 | //!@} 95 | }; 96 | 97 | #endif // SPONGE_LIBSPONGE_BYTE_STREAM_HH 98 | -------------------------------------------------------------------------------- /libsponge/stream_reassembler.hh: -------------------------------------------------------------------------------- 1 | #ifndef SPONGE_LIBSPONGE_STREAM_REASSEMBLER_HH 2 | #define SPONGE_LIBSPONGE_STREAM_REASSEMBLER_HH 3 | 4 | #include "byte_stream.hh" 5 | 6 | #include 7 | #include 8 | #include 9 | 10 | //! \brief A class that assembles a series of excerpts from a byte stream (possibly out of order, 11 | //! possibly overlapping) into an in-order byte stream. 12 | class StreamReassembler 13 | { 14 | private: 15 | std::set> _reassemble_cache = {}; 16 | uint64_t _unreceived = 0; 17 | uint64_t _unacceptable = 0; 18 | bool _eof = false; 19 | ByteStream _output; //!< The reassembled in-order byte stream 20 | size_t _capacity = 0; //!< The maximum number of bytes 21 | 22 | void handle_right_edge(std::pair &new_seg); 23 | void handle_left_edge(std::pair &new_seg); 24 | void handle_middle(std::pair &new_seg); 25 | 26 | public: 27 | //! \brief Construct a `StreamReassembler` that will store up to `capacity` bytes. 28 | //! \note This capacity limits both the bytes that have been reassembled, 29 | //! and those that have not yet been reassembled. 30 | StreamReassembler(const size_t capacity); 31 | 32 | //! \brief Receives a substring and writes any newly contiguous bytes into the stream. 33 | //! 34 | //! If accepting all the data would overflow the `capacity` of this 35 | //! `StreamReassembler`, then only the part of the data that fits will be 36 | //! accepted. If the substring is only partially accepted, then the `eof` 37 | //! will be disregarded. 38 | //! 39 | //! \param data the string being added 40 | //! \param index the index of the first byte in `data` 41 | //! \param eof whether or not this segment ends with the end of the stream 42 | void push_substring(const std::string &data, const uint64_t index, const bool eof); 43 | 44 | //! \name Access the reassembled byte stream 45 | //!@{ 46 | const ByteStream &stream_out() const 47 | { 48 | return _output; 49 | } 50 | ByteStream &stream_out() 51 | { 52 | return _output; 53 | } 54 | //!@} 55 | 56 | //! The number of bytes in the substrings stored but not yet reassembled 57 | //! 58 | //! \note If the byte at a particular index has been submitted twice, it 59 | //! should only be counted once for the purpose of this function. 60 | size_t unassembled_bytes() const; 61 | 62 | //! \brief Is the internal state empty (other than the output stream)? 63 | //! \returns `true` if no substrings are waiting to be assembled 64 | bool empty() const; 65 | 66 | // Return the first byte which hasn't been received. 67 | uint64_t first_unreceived_byte() const; 68 | 69 | // Return the byte which can't accept. 70 | uint64_t unacceptable_byte() const; 71 | 72 | uint64_t unreceived_byte() const; 73 | }; 74 | 75 | #endif // SPONGE_LIBSPONGE_STREAM_REASSEMBLER_HH 76 | -------------------------------------------------------------------------------- /libsponge/wrapping_integers.hh: -------------------------------------------------------------------------------- 1 | #ifndef SPONGE_LIBSPONGE_WRAPPING_INTEGERS_HH 2 | #define SPONGE_LIBSPONGE_WRAPPING_INTEGERS_HH 3 | 4 | #include 5 | #include 6 | 7 | //! \brief A 32-bit integer, expressed relative to an arbitrary initial sequence number (ISN) 8 | //! \note This is used to express TCP sequence numbers (seqno) and acknowledgment numbers (ackno) 9 | class WrappingInt32 { 10 | private: 11 | uint32_t _raw_value; //!< The raw 32-bit stored integer 12 | 13 | public: 14 | //! Construct from a raw 32-bit unsigned integer 15 | explicit WrappingInt32(uint32_t raw_value) : _raw_value(raw_value) {} 16 | 17 | uint32_t raw_value() const { return _raw_value; } //!< Access raw stored value 18 | }; 19 | 20 | //! Transform a 64-bit absolute sequence number (zero-indexed) into a 32-bit relative sequence number 21 | //! \param n the absolute sequence number 22 | //! \param isn the initial sequence number 23 | //! \returns the relative sequence number 24 | WrappingInt32 wrap(uint64_t n, WrappingInt32 isn); 25 | 26 | //! Transform a 32-bit relative sequence number into a 64-bit absolute sequence number (zero-indexed) 27 | //! \param n The relative sequence number 28 | //! \param isn The initial sequence number 29 | //! \param checkpoint A recent absolute sequence number 30 | //! \returns the absolute sequence number that wraps to `n` and is closest to `checkpoint` 31 | //! 32 | //! \note Each of the two streams of the TCP connection has its own ISN. One stream 33 | //! runs from the local TCPSender to the remote TCPReceiver and has one ISN, 34 | //! and the other stream runs from the remote TCPSender to the local TCPReceiver and 35 | //! has a different ISN. 36 | uint64_t unwrap(WrappingInt32 n, WrappingInt32 isn, uint64_t checkpoint); 37 | 38 | //! \name Helper functions 39 | //!@{ 40 | 41 | //! \brief The offset of `a` relative to `b` 42 | //! \param b the starting point 43 | //! \param a the ending point 44 | //! \returns the number of increments needed to get from `b` to `a`, 45 | //! negative if the number of decrements needed is less than or equal to 46 | //! the number of increments 47 | inline int32_t operator-(WrappingInt32 a, WrappingInt32 b) { return a.raw_value() - b.raw_value(); } 48 | 49 | //! \brief Whether the two integers are equal. 50 | inline bool operator==(WrappingInt32 a, WrappingInt32 b) { return a.raw_value() == b.raw_value(); } 51 | 52 | //! \brief Whether the two integers are not equal. 53 | inline bool operator!=(WrappingInt32 a, WrappingInt32 b) { return !(a == b); } 54 | 55 | //! \brief Serializes the wrapping integer, `a`. 56 | inline std::ostream &operator<<(std::ostream &os, WrappingInt32 a) { return os << a.raw_value(); } 57 | 58 | //! \brief The point `b` steps past `a`. 59 | inline WrappingInt32 operator+(WrappingInt32 a, uint32_t b) { return WrappingInt32{a.raw_value() + b}; } 60 | 61 | //! \brief The point `b` steps before `a`. 62 | inline WrappingInt32 operator-(WrappingInt32 a, uint32_t b) { return a + -b; } 63 | //!@} 64 | 65 | #endif // SPONGE_LIBSPONGE_WRAPPING_INTEGERS_HH 66 | -------------------------------------------------------------------------------- /tests/fsm_loopback_win.cc: -------------------------------------------------------------------------------- 1 | #include "tcp_config.hh" 2 | #include "tcp_expectation.hh" 3 | #include "tcp_fsm_test_harness.hh" 4 | #include "tcp_header.hh" 5 | #include "tcp_segment.hh" 6 | #include "util.hh" 7 | 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | 18 | using namespace std; 19 | using State = TCPTestHarness::State; 20 | 21 | static constexpr unsigned NREPS = 32; 22 | 23 | int main() { 24 | try { 25 | TCPConfig cfg{}; 26 | cfg.recv_capacity = 65000; 27 | auto rd = get_random_generator(); 28 | 29 | // loop segments back in a different order 30 | for (unsigned rep_no = 0; rep_no < NREPS; ++rep_no) { 31 | const WrappingInt32 rx_offset(rd()); 32 | TCPTestHarness test_2 = TCPTestHarness::in_established(cfg, rx_offset - 1, rx_offset - 1); 33 | test_2.send_ack(rx_offset, rx_offset, 65000); 34 | 35 | string d(cfg.recv_capacity, 0); 36 | generate(d.begin(), d.end(), [&] { return rd(); }); 37 | 38 | vector segs; 39 | size_t sendoff = 0; 40 | while (sendoff < d.size()) { 41 | const size_t len = min(d.size() - sendoff, static_cast(rd()) % 8192); 42 | if (len == 0) { 43 | continue; 44 | } 45 | test_2.execute(Write{d.substr(sendoff, len)}); 46 | test_2.execute(Tick(1)); 47 | 48 | test_2.execute(ExpectSegmentAvailable{}, "test 2 failed: cannot read after write()"); 49 | while (test_2.can_read()) { 50 | segs.emplace_back(test_2.expect_seg(ExpectSegment{})); 51 | } 52 | sendoff += len; 53 | } 54 | 55 | // resend them in shuffled order 56 | vector seg_idx(segs.size(), 0); 57 | iota(seg_idx.begin(), seg_idx.end(), 0); 58 | shuffle(seg_idx.begin(), seg_idx.end(), rd); 59 | vector acks; 60 | for (auto idx : seg_idx) { 61 | test_2.execute(SendSegment{std::move(segs[idx])}); 62 | test_2.execute(Tick(1)); 63 | TCPSegment s = test_2.expect_seg(ExpectOneSegment{}.with_ack(true), "test 2 failed: no ACK after rcvd"); 64 | acks.emplace_back(std::move(s)); 65 | test_2.execute(ExpectNoSegment{}, "test 2 failed: double ACK?"); 66 | } 67 | 68 | // send just the final ack 69 | test_2.execute(SendSegment{std::move(acks.back())}); 70 | test_2.execute(ExpectNoSegment{}, "test 2 failed: ACK for ACK?"); 71 | 72 | test_2.execute(ExpectData{}.with_data(d), "test 2 failed: wrong data after loopback"); 73 | } 74 | } catch (const exception &e) { 75 | cerr << e.what() << endl; 76 | return EXIT_FAILURE; 77 | } 78 | 79 | return EXIT_SUCCESS; 80 | } 81 | -------------------------------------------------------------------------------- /tests/send_ack.cc: -------------------------------------------------------------------------------- 1 | #include "sender_harness.hh" 2 | #include "wrapping_integers.hh" 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | using namespace std; 13 | 14 | int main() { 15 | try { 16 | auto rd = get_random_generator(); 17 | 18 | { 19 | TCPConfig cfg; 20 | WrappingInt32 isn(rd()); 21 | cfg.fixed_isn = isn; 22 | 23 | TCPSenderTestHarness test{"Repeat ACK is ignored", cfg}; 24 | test.execute(ExpectSegment{}.with_no_flags().with_syn(true).with_payload_size(0).with_seqno(isn)); 25 | test.execute(ExpectNoSegment{}); 26 | test.execute(AckReceived{WrappingInt32{isn + 1}}); 27 | test.execute(WriteBytes{"a"}); 28 | test.execute(ExpectSegment{}.with_no_flags().with_data("a")); 29 | test.execute(ExpectNoSegment{}); 30 | test.execute(AckReceived{WrappingInt32{isn + 1}}); 31 | test.execute(ExpectNoSegment{}); 32 | } 33 | 34 | { 35 | TCPConfig cfg; 36 | WrappingInt32 isn(rd()); 37 | cfg.fixed_isn = isn; 38 | 39 | TCPSenderTestHarness test{"Old ACK is ignored", cfg}; 40 | test.execute(ExpectSegment{}.with_no_flags().with_syn(true).with_payload_size(0).with_seqno(isn)); 41 | test.execute(ExpectNoSegment{}); 42 | test.execute(AckReceived{WrappingInt32{isn + 1}}); 43 | test.execute(WriteBytes{"a"}); 44 | test.execute(ExpectSegment{}.with_no_flags().with_data("a")); 45 | test.execute(ExpectNoSegment{}); 46 | test.execute(AckReceived{WrappingInt32{isn + 2}}); 47 | test.execute(ExpectNoSegment{}); 48 | test.execute(WriteBytes{"b"}); 49 | test.execute(ExpectSegment{}.with_no_flags().with_data("b")); 50 | test.execute(ExpectNoSegment{}); 51 | test.execute(AckReceived{WrappingInt32{isn + 1}}); 52 | test.execute(ExpectNoSegment{}); 53 | } 54 | 55 | { 56 | TCPConfig cfg; 57 | WrappingInt32 isn(rd()); 58 | cfg.fixed_isn = isn; 59 | 60 | TCPSenderTestHarness test{"Early ACK results in bare ACK", cfg}; 61 | test.execute(ExpectSegment{}.with_no_flags().with_syn(true).with_payload_size(0).with_seqno(isn)); 62 | test.execute(ExpectNoSegment{}); 63 | test.execute(AckReceived{WrappingInt32{isn + 1}}); 64 | test.execute(WriteBytes{"a"}); 65 | test.execute(ExpectSegment{}.with_no_flags().with_data("a")); 66 | test.execute(ExpectNoSegment{}); 67 | test.execute(AckReceived{WrappingInt32{isn + 17}}); 68 | test.execute(ExpectSegment{}.with_seqno(isn + 2)); 69 | test.execute(ExpectNoSegment{}); 70 | } 71 | 72 | } catch (const exception &e) { 73 | cerr << e.what() << endl; 74 | return 1; 75 | } 76 | 77 | return EXIT_SUCCESS; 78 | } 79 | -------------------------------------------------------------------------------- /libsponge/util/address.hh: -------------------------------------------------------------------------------- 1 | #ifndef SPONGE_LIBSPONGE_ADDRESS_HH 2 | #define SPONGE_LIBSPONGE_ADDRESS_HH 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | //! Wrapper around [IPv4 addresses](@ref man7::ip) and DNS operations. 13 | class Address { 14 | public: 15 | //! \brief Wrapper around [sockaddr_storage](@ref man7::socket). 16 | //! \details A `sockaddr_storage` is enough space to store any socket address (IPv4 or IPv6). 17 | class Raw { 18 | public: 19 | sockaddr_storage storage{}; //!< The wrapped struct itself. 20 | operator sockaddr *(); 21 | operator const sockaddr *() const; 22 | }; 23 | 24 | private: 25 | socklen_t _size; //!< Size of the wrapped address. 26 | Raw _address{}; //!< A wrapped [sockaddr_storage](@ref man7::socket) containing the address. 27 | 28 | //! Constructor from ip/host, service/port, and hints to the resolver. 29 | Address(const std::string &node, const std::string &service, const addrinfo &hints); 30 | 31 | public: 32 | //! Construct by resolving a hostname and servicename. 33 | Address(const std::string &hostname, const std::string &service); 34 | 35 | //! Construct from dotted-quad string ("18.243.0.1") and numeric port. 36 | Address(const std::string &ip, const std::uint16_t port); 37 | 38 | //! Construct from a [sockaddr *](@ref man7::socket). 39 | Address(const sockaddr *addr, const std::size_t size); 40 | 41 | //! Equality comparison. 42 | bool operator==(const Address &other) const; 43 | bool operator!=(const Address &other) const { return not operator==(other); } 44 | 45 | //! \name Conversions 46 | //!@{ 47 | 48 | //! Dotted-quad IP address string ("18.243.0.1") and numeric port. 49 | std::pair ip_port() const; 50 | //! Dotted-quad IP address string ("18.243.0.1"). 51 | std::string ip() const { return ip_port().first; } 52 | //! Numeric port (host byte order). 53 | uint16_t port() const { return ip_port().second; } 54 | //! Numeric IP address as an integer (i.e., in [host byte order](\ref man3::byteorder)). 55 | uint32_t ipv4_numeric() const; 56 | //! Human-readable string, e.g., "8.8.8.8:53". 57 | std::string to_string() const; 58 | //!@} 59 | 60 | //! \name Low-level operations 61 | //!@{ 62 | 63 | //! Size of the underlying address storage. 64 | socklen_t size() const { return _size; } 65 | //! Const pointer to the underlying socket address storage. 66 | operator const sockaddr *() const { return _address; } 67 | //!@} 68 | }; 69 | 70 | //! \class Address 71 | //! For example, you can do DNS lookups: 72 | //! 73 | //! \include address_example_1.cc 74 | //! 75 | //! or you can specify an IP address and port number: 76 | //! 77 | //! \include address_example_2.cc 78 | //! 79 | //! Once you have an address, you can convert it to other useful representations, e.g., 80 | //! 81 | //! \include address_example_3.cc 82 | 83 | #endif // SPONGE_LIBSPONGE_ADDRESS_HH 84 | -------------------------------------------------------------------------------- /tests/fsm_loopback.cc: -------------------------------------------------------------------------------- 1 | #include "tcp_config.hh" 2 | #include "tcp_expectation.hh" 3 | #include "tcp_fsm_test_harness.hh" 4 | #include "tcp_header.hh" 5 | #include "tcp_segment.hh" 6 | #include "util.hh" 7 | 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | 18 | using namespace std; 19 | using State = TCPTestHarness::State; 20 | 21 | static constexpr unsigned NREPS = 64; 22 | 23 | int main() { 24 | try { 25 | TCPConfig cfg{}; 26 | cfg.recv_capacity = 65000; 27 | auto rd = get_random_generator(); 28 | 29 | // loop segments back into the same FSM 30 | for (unsigned rep_no = 0; rep_no < NREPS; ++rep_no) { 31 | const WrappingInt32 rx_offset(rd()); 32 | TCPTestHarness test_1 = TCPTestHarness::in_established(cfg, rx_offset - 1, rx_offset - 1); 33 | test_1.send_ack(rx_offset, rx_offset, 65000); 34 | 35 | string d(cfg.recv_capacity, 0); 36 | generate(d.begin(), d.end(), [&] { return rd(); }); 37 | 38 | size_t sendoff = 0; 39 | while (sendoff < d.size()) { 40 | const size_t len = min(d.size() - sendoff, static_cast(rd()) % 8192); 41 | if (len == 0) { 42 | continue; 43 | } 44 | test_1.execute(Write{d.substr(sendoff, len)}); 45 | test_1.execute(Tick(1)); 46 | test_1.execute(ExpectBytesInFlight{len}); 47 | 48 | test_1.execute(ExpectSegmentAvailable{}, "test 1 failed: cannot read after write()"); 49 | 50 | size_t n_segments = ceil(double(len) / TCPConfig::MAX_PAYLOAD_SIZE); 51 | size_t bytes_remaining = len; 52 | 53 | // Transfer the data segments 54 | for (size_t i = 0; i < n_segments; ++i) { 55 | size_t expected_size = min(bytes_remaining, TCPConfig::MAX_PAYLOAD_SIZE); 56 | auto seg = test_1.expect_seg(ExpectSegment{}.with_payload_size(expected_size)); 57 | bytes_remaining -= expected_size; 58 | test_1.execute(SendSegment{move(seg)}); 59 | test_1.execute(Tick(1)); 60 | } 61 | 62 | // Transfer the (bare) ack segments 63 | for (size_t i = 0; i < n_segments; ++i) { 64 | auto seg = test_1.expect_seg(ExpectSegment{}.with_ack(true).with_payload_size(0)); 65 | test_1.execute(SendSegment{move(seg)}); 66 | test_1.execute(Tick(1)); 67 | } 68 | test_1.execute(ExpectNoSegment{}); 69 | 70 | test_1.execute(ExpectBytesInFlight{0}); 71 | 72 | sendoff += len; 73 | } 74 | 75 | test_1.execute(ExpectData{}.with_data(d), "test 1 falied: got back the wrong data"); 76 | } 77 | } catch (const exception &e) { 78 | cerr << e.what() << endl; 79 | return EXIT_FAILURE; 80 | } 81 | 82 | return EXIT_SUCCESS; 83 | } 84 | -------------------------------------------------------------------------------- /libsponge/tcp_receiver.hh: -------------------------------------------------------------------------------- 1 | #ifndef SPONGE_LIBSPONGE_TCP_RECEIVER_HH 2 | #define SPONGE_LIBSPONGE_TCP_RECEIVER_HH 3 | 4 | #include "byte_stream.hh" 5 | #include "stream_reassembler.hh" 6 | #include "tcp_segment.hh" 7 | #include "wrapping_integers.hh" 8 | 9 | #include 10 | 11 | //! \brief The "receiver" part of a TCP implementation. 12 | 13 | //! Receives and reassembles segments into a ByteStream, and computes 14 | //! the acknowledgment number and window size to advertise back to the 15 | //! remote TCPSender. 16 | class TCPReceiver 17 | { 18 | //! Our data structure for re-assembling bytes. 19 | StreamReassembler _reassembler; 20 | //! The maximum number of bytes we'll store. 21 | size_t _capacity = 0; 22 | //! A recent absolute sequence number 23 | size_t _check_point = 0; 24 | uint32_t _isn = 0; 25 | bool _syn = false; 26 | bool _fin = false; 27 | bool _seqno_out_of_window = false; 28 | 29 | public: 30 | //! \brief Construct a TCP receiver 31 | //! 32 | //! \param capacity the maximum number of bytes that the receiver will 33 | //! store in its buffers at any give time. 34 | TCPReceiver(const size_t capacity) : _reassembler(capacity), _capacity(capacity) {} 35 | 36 | //! \name Accessors to provide feedback to the remote TCPSender 37 | //!@{ 38 | 39 | //! \brief The ackno that should be sent to the peer 40 | //! \returns empty if no SYN has been received 41 | //! 42 | //! This is the beginning of the receiver's window, or in other words, the sequence number 43 | //! of the first byte in the stream that the receiver hasn't received. 44 | std::optional ackno() const; 45 | 46 | //! \brief The window size that should be sent to the peer 47 | //! 48 | //! Operationally: the capacity minus the number of bytes that the 49 | //! TCPReceiver is holding in its byte stream (those that have been 50 | //! reassembled, but not consumed). 51 | //! 52 | //! Formally: the difference between (a) the sequence number of 53 | //! the first byte that falls after the window (and will not be 54 | //! accepted by the receiver) and (b) the sequence number of the 55 | //! beginning of the window (the ackno). 56 | size_t window_size() const; 57 | //!@} 58 | 59 | //! \brief number of bytes stored but not yet reassembled 60 | size_t unassembled_bytes() const { return _reassembler.unassembled_bytes(); } 61 | 62 | //! \brief handle an inbound segment 63 | //! \returns `true` if any part of the segment was inside the window 64 | bool segment_received(const TCPSegment &seg); 65 | 66 | //! \name "Output" interface for the reader 67 | //!@{ 68 | ByteStream &stream_out() { return _reassembler.stream_out(); } 69 | const ByteStream &stream_out() const { return _reassembler.stream_out(); } 70 | //!@} 71 | 72 | const StreamReassembler &stream_rassembler() const { return _reassembler; } 73 | 74 | WrappingInt32 get_isn() const { return WrappingInt32(_isn); } 75 | 76 | bool seqno_out_of_window() { return _seqno_out_of_window; } 77 | void seqno_out_of_window_clean() { _seqno_out_of_window = false; } 78 | }; 79 | 80 | #endif // SPONGE_LIBSPONGE_TCP_RECEIVER_HH 81 | -------------------------------------------------------------------------------- /tests/fsm_ack_rst_win_relaxed.cc: -------------------------------------------------------------------------------- 1 | #include "tcp_config.hh" 2 | #include "tcp_expectation.hh" 3 | #include "tcp_fsm_test_harness.hh" 4 | #include "tcp_header.hh" 5 | #include "tcp_segment.hh" 6 | 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | using namespace std; 13 | using State = TCPTestHarness::State; 14 | 15 | int main() { 16 | try { 17 | TCPConfig cfg{}; 18 | const WrappingInt32 base_seq(1 << 31); 19 | 20 | // test #1: in ESTABLISHED, send unacceptable segments and ACKs 21 | { 22 | TCPTestHarness test_1 = TCPTestHarness::in_established(cfg, base_seq - 1, base_seq - 1); 23 | 24 | // acceptable ack---no response 25 | test_1.send_ack(base_seq, base_seq); 26 | 27 | test_1.execute(ExpectNoSegment{}, "test 1 failed: ACK after acceptable ACK"); 28 | 29 | // ack in the past---no response 30 | test_1.send_ack(base_seq, base_seq - 1); 31 | 32 | test_1.execute(ExpectNoSegment{}, "test 1 failed: ACK after past ACK"); 33 | 34 | // ack in the future---should get ACK back 35 | test_1.send_ack(base_seq, base_seq + 1); 36 | 37 | test_1.execute(ExpectOneSegment{}.with_ackno(base_seq), "test 1 failed: bad ACK after future ACK"); 38 | 39 | // segment out of the window---should get an ACK 40 | test_1.send_byte(base_seq - 1, base_seq, 1); 41 | 42 | test_1.execute(ExpectUnassembledBytes{0}, "test 1 failed: seg queued on early seqno"); 43 | test_1.execute(ExpectOneSegment{}.with_ackno(base_seq), "test 1 failed: no ack on early seqno"); 44 | 45 | // segment out of the window---should get an ACK 46 | test_1.send_byte(base_seq + cfg.recv_capacity, base_seq, 1); 47 | 48 | test_1.execute(ExpectUnassembledBytes{0}, "test 1 failed: seg queued on late seqno"); 49 | test_1.execute(ExpectOneSegment{}.with_ackno(base_seq), "test 1 failed: no ack on late seqno"); 50 | 51 | // segment in the window but late---should get an ACK and seg should be queued 52 | test_1.send_byte(base_seq + cfg.recv_capacity - 1, base_seq, 1); 53 | 54 | test_1.execute(ExpectUnassembledBytes{1}, "seg not queued on end-of-window seqno"); 55 | 56 | test_1.execute(ExpectOneSegment{}.with_ackno(base_seq), "test 1 failed: no ack on end-of-window seqno"); 57 | test_1.execute(ExpectNoData{}, "test 1 failed: no ack on end-of-window seqno"); 58 | 59 | // segment next byte in the window - ack should advance and data should be readable 60 | test_1.send_byte(base_seq, base_seq, 1); 61 | 62 | test_1.execute(ExpectUnassembledBytes{1}, "seg not processed on next seqno"); 63 | test_1.execute(ExpectOneSegment{}.with_ackno(base_seq + 1), "test 1 failed: no ack on next seqno"); 64 | test_1.execute(ExpectData{}, "test 1 failed: no ack on next seqno"); 65 | 66 | test_1.send_rst(base_seq + 1); 67 | test_1.execute(ExpectState{State::RESET}); 68 | } 69 | } catch (const exception &e) { 70 | cerr << e.what() << endl; 71 | return EXIT_FAILURE; 72 | } 73 | 74 | return EXIT_SUCCESS; 75 | } 76 | -------------------------------------------------------------------------------- /libsponge/tcp_helpers/tcp_header.hh: -------------------------------------------------------------------------------- 1 | #ifndef SPONGE_LIBSPONGE_TCP_HEADER_HH 2 | #define SPONGE_LIBSPONGE_TCP_HEADER_HH 3 | 4 | #include "parser.hh" 5 | #include "wrapping_integers.hh" 6 | 7 | //! \brief [TCP](\ref rfc::rfc793) segment header 8 | //! \note TCP options are not supported 9 | struct TCPHeader { 10 | static constexpr size_t LENGTH = 20; //!< [TCP](\ref rfc::rfc793) header length, not including options 11 | 12 | //! \struct TCPHeader 13 | //! ~~~{.txt} 14 | //! 0 1 2 3 15 | //! 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 16 | //! +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 17 | //! | Source Port | Destination Port | 18 | //! +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 19 | //! | Sequence Number | 20 | //! +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 21 | //! | Acknowledgment Number | 22 | //! +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 23 | //! | Data | |U|A|P|R|S|F| | 24 | //! | Offset| Reserved |R|C|S|S|Y|I| Window | 25 | //! | | |G|K|H|T|N|N| | 26 | //! +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 27 | //! | Checksum | Urgent Pointer | 28 | //! +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 29 | //! | Options | Padding | 30 | //! +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 31 | //! | data | 32 | //! +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 33 | //! ~~~ 34 | 35 | //! \name TCP Header fields 36 | //!@{ 37 | uint16_t sport = 0; //!< source port 38 | uint16_t dport = 0; //!< destination port 39 | WrappingInt32 seqno{0}; //!< sequence number 40 | WrappingInt32 ackno{0}; //!< ack number 41 | uint8_t doff = LENGTH / 4; //!< data offset 42 | bool urg = false; //!< urgent flag 43 | bool ack = false; //!< ack flag 44 | bool psh = false; //!< push flag 45 | bool rst = false; //!< rst flag 46 | bool syn = false; //!< syn flag 47 | bool fin = false; //!< fin flag 48 | uint16_t win = 0; //!< window size 49 | uint16_t cksum = 0; //!< checksum 50 | uint16_t uptr = 0; //!< urgent pointer 51 | //!@} 52 | 53 | //! Parse the TCP fields from the provided NetParser 54 | ParseResult parse(NetParser &p); 55 | 56 | //! Serialize the TCP fields 57 | std::string serialize() const; 58 | 59 | //! Return a string containing a header in human-readable format 60 | std::string to_string() const; 61 | 62 | //! Return a string containing a human-readable summary of the header 63 | std::string summary() const; 64 | 65 | bool operator==(const TCPHeader &other) const; 66 | }; 67 | 68 | #endif // SPONGE_LIBSPONGE_TCP_HEADER_HH 69 | -------------------------------------------------------------------------------- /libsponge/tcp_helpers/ipv4_header.hh: -------------------------------------------------------------------------------- 1 | #ifndef SPONGE_LIBSPONGE_IPV4_HEADER_HH 2 | #define SPONGE_LIBSPONGE_IPV4_HEADER_HH 3 | 4 | #include "parser.hh" 5 | 6 | //! \brief [IPv4](\ref rfc::rfc791) Internet datagram header 7 | //! \note IP options are not supported 8 | struct IPv4Header { 9 | static constexpr size_t LENGTH = 20; //!< [IPv4](\ref rfc::rfc791) header length, not including options 10 | static constexpr uint8_t DEFAULT_TTL = 128; //!< A reasonable default TTL value 11 | static constexpr uint8_t PROTO_TCP = 6; //!< Protocol number for [tcp](\ref rfc::rfc793) 12 | 13 | //! \struct IPv4Header 14 | //! ~~~{.txt} 15 | //! 0 1 2 3 16 | //! 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 17 | //! +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 18 | //! |Version| IHL |Type of Service| Total Length | 19 | //! +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 20 | //! | Identification |Flags| Fragment Offset | 21 | //! +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 22 | //! | Time to Live | Protocol | Header Checksum | 23 | //! +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 24 | //! | Source Address | 25 | //! +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 26 | //! | Destination Address | 27 | //! +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 28 | //! | Options | Padding | 29 | //! +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 30 | //! ~~~ 31 | 32 | //! \name IPv4 Header fields 33 | //!@{ 34 | uint8_t ver = 4; //!< IP version 35 | uint8_t hlen = LENGTH / 4; //!< header length (multiples of 32 bits) 36 | uint8_t tos = 0; //!< type of service 37 | uint16_t len = 0; //!< total length of packet 38 | uint16_t id = 0; //!< identification number 39 | bool df = true; //!< don't fragment flag 40 | bool mf = false; //!< more fragments flag 41 | uint16_t offset = 0; //!< fragment offset field 42 | uint8_t ttl = DEFAULT_TTL; //!< time to live field 43 | uint8_t proto = PROTO_TCP; //!< protocol field 44 | uint16_t cksum = 0; //!< checksum field 45 | uint32_t src = 0; //!< src address 46 | uint32_t dst = 0; //!< dst address 47 | //!@} 48 | 49 | //! Parse the IP fields from the provided NetParser 50 | ParseResult parse(NetParser &p); 51 | 52 | //! Serialize the IP fields 53 | std::string serialize() const; 54 | 55 | //! Length of the payload 56 | uint16_t payload_length() const; 57 | 58 | //! [pseudo-header's](\ref rfc::rfc793) contribution to the TCP checksum 59 | uint32_t pseudo_cksum() const; 60 | 61 | //! Return a string containing a header in human-readable format 62 | std::string to_string() const; 63 | }; 64 | 65 | //! \struct IPv4Header 66 | //! This struct can be used to parse an existing IP header or to create a new one. 67 | 68 | #endif // SPONGE_LIBSPONGE_IPV4_HEADER_HH 69 | -------------------------------------------------------------------------------- /tests/fsm_stream_reassembler_dup.cc: -------------------------------------------------------------------------------- 1 | #include "byte_stream.hh" 2 | #include "fsm_stream_reassembler_harness.hh" 3 | #include "stream_reassembler.hh" 4 | #include "util.hh" 5 | 6 | #include 7 | #include 8 | 9 | using namespace std; 10 | 11 | int main() { 12 | try { 13 | auto rd = get_random_generator(); 14 | 15 | { 16 | ReassemblerTestHarness test{65000}; 17 | 18 | test.execute(SubmitSegment{"abcd", 0}); 19 | test.execute(BytesAssembled(4)); 20 | test.execute(BytesAvailable("abcd")); 21 | test.execute(NotAtEof{}); 22 | 23 | test.execute(SubmitSegment{"abcd", 0}); 24 | test.execute(BytesAssembled(4)); 25 | test.execute(BytesAvailable("")); 26 | test.execute(NotAtEof{}); 27 | } 28 | 29 | { 30 | ReassemblerTestHarness test{65000}; 31 | 32 | test.execute(SubmitSegment{"abcd", 0}); 33 | test.execute(BytesAssembled(4)); 34 | test.execute(BytesAvailable("abcd")); 35 | test.execute(NotAtEof{}); 36 | 37 | test.execute(SubmitSegment{"abcd", 4}); 38 | test.execute(BytesAssembled(8)); 39 | test.execute(BytesAvailable("abcd")); 40 | test.execute(NotAtEof{}); 41 | 42 | test.execute(SubmitSegment{"abcd", 0}); 43 | test.execute(BytesAssembled(8)); 44 | test.execute(BytesAvailable("")); 45 | test.execute(NotAtEof{}); 46 | 47 | test.execute(SubmitSegment{"abcd", 4}); 48 | test.execute(BytesAssembled(8)); 49 | test.execute(BytesAvailable("")); 50 | test.execute(NotAtEof{}); 51 | } 52 | 53 | { 54 | ReassemblerTestHarness test{65000}; 55 | 56 | test.execute(SubmitSegment{"abcdefgh", 0}); 57 | test.execute(BytesAssembled(8)); 58 | test.execute(BytesAvailable("abcdefgh")); 59 | test.execute(NotAtEof{}); 60 | string data = "abcdefgh"; 61 | 62 | for (size_t i = 0; i < 1000; ++i) { 63 | size_t start_i = uniform_int_distribution{0, 8}(rd); 64 | auto start = data.begin(); 65 | std::advance(start, start_i); 66 | 67 | size_t end_i = uniform_int_distribution{start_i, 8}(rd); 68 | auto end = data.begin(); 69 | std::advance(end, end_i); 70 | 71 | test.execute(SubmitSegment{string{start, end}, start_i}); 72 | test.execute(BytesAssembled(8)); 73 | test.execute(BytesAvailable("")); 74 | test.execute(NotAtEof{}); 75 | } 76 | } 77 | 78 | { 79 | ReassemblerTestHarness test{65000}; 80 | 81 | test.execute(SubmitSegment{"abcd", 0}); 82 | test.execute(BytesAssembled(4)); 83 | test.execute(BytesAvailable("abcd")); 84 | test.execute(NotAtEof{}); 85 | 86 | test.execute(SubmitSegment{"abcdef", 0}); 87 | test.execute(BytesAssembled(6)); 88 | test.execute(BytesAvailable("ef")); 89 | test.execute(NotAtEof{}); 90 | } 91 | 92 | } catch (const exception &e) { 93 | cerr << "Exception: " << e.what() << endl; 94 | return EXIT_FAILURE; 95 | } 96 | 97 | return EXIT_SUCCESS; 98 | } 99 | -------------------------------------------------------------------------------- /tun.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | show_usage () { 4 | echo "Usage: $0 [tunnum ...]" 5 | exit 1 6 | } 7 | 8 | start_tun () { 9 | local TUNNUM="$1" TUNDEV="tun$1" 10 | ip tuntap add mode tun user "${SUDO_USER}" name "${TUNDEV}" 11 | ip addr add "${TUN_IP_PREFIX}.${TUNNUM}.1/24" dev "${TUNDEV}" 12 | ip link set dev "${TUNDEV}" up 13 | ip route change "${TUN_IP_PREFIX}.${TUNNUM}.0/24" dev "${TUNDEV}" rto_min 10ms 14 | 15 | # Apply NAT (masquerading) only to traffic from CS144's network devices 16 | iptables -t nat -A PREROUTING -s ${TUN_IP_PREFIX}.${TUNNUM}.0/24 -j CONNMARK --set-mark ${TUNNUM} 17 | iptables -t nat -A POSTROUTING -j MASQUERADE -m connmark --mark ${TUNNUM} 18 | echo 1 > /proc/sys/net/ipv4/ip_forward 19 | } 20 | 21 | stop_tun () { 22 | local TUNDEV="tun$1" 23 | iptables -t nat -D PREROUTING -s ${TUN_IP_PREFIX}.${1}.0/24 -j CONNMARK --set-mark ${1} 24 | iptables -t nat -D POSTROUTING -j MASQUERADE -m connmark --mark ${1} 25 | ip tuntap del mode tun name "$TUNDEV" 26 | } 27 | 28 | start_all () { 29 | while [ ! -z "$1" ]; do 30 | local INTF="$1"; shift 31 | start_tun "$INTF" 32 | done 33 | } 34 | 35 | stop_all () { 36 | while [ ! -z "$1" ]; do 37 | local INTF="$1"; shift 38 | stop_tun "$INTF" 39 | done 40 | } 41 | 42 | restart_all() { 43 | stop_all "$@" 44 | start_all "$@" 45 | } 46 | 47 | check_tun () { 48 | [ "$#" != 1 ] && { echo "bad params in check_tun"; exit 1; } 49 | local TUNDEV="tun${1}" 50 | # make sure tun is healthy: device is up, ip_forward is set, and iptables is configured 51 | ip link show ${TUNDEV} &>/dev/null || return 1 52 | [ "$(cat /proc/sys/net/ipv4/ip_forward)" = "1" ] || return 2 53 | } 54 | 55 | check_sudo () { 56 | if [ "$SUDO_USER" = "root" ]; then 57 | echo "please execute this script as a regular user, not as root" 58 | exit 1 59 | fi 60 | if [ -z "$SUDO_USER" ]; then 61 | # if the user didn't call us with sudo, re-execute 62 | exec sudo $0 "$MODE" "$@" 63 | fi 64 | } 65 | 66 | # check arguments 67 | if [ -z "$1" ] || ([ "$1" != "start" ] && [ "$1" != "stop" ] && [ "$1" != "restart" ] && [ "$1" != "check" ]); then 68 | show_usage 69 | fi 70 | MODE=$1; shift 71 | 72 | # set default argument 73 | if [ "$#" = "0" ]; then 74 | set -- 144 145 75 | fi 76 | 77 | # execute 'check' before trying to sudo 78 | # - like start, but exit successfully if everything is OK 79 | if [ "$MODE" = "check" ]; then 80 | declare -a INTFS 81 | MODE="start" 82 | while [ ! -z "$1" ]; do 83 | INTF="$1"; shift 84 | check_tun ${INTF} 85 | RET=$? 86 | if [ "$RET" = "0" ]; then 87 | continue 88 | fi 89 | 90 | if [ "$((RET > 1))" = "1" ]; then 91 | MODE="restart" 92 | fi 93 | INTFS+=($INTF) 94 | done 95 | 96 | # address only the interfaces that need it 97 | set -- "${INTFS[@]}" 98 | if [ "$#" = "0" ]; then 99 | exit 0 100 | fi 101 | echo -e "[$0] Bringing up tunnels ${INTFS[@]}:" 102 | fi 103 | 104 | # sudo if necessary 105 | check_sudo "$@" 106 | 107 | # get configuration 108 | . "$(dirname "$0")"/etc/tunconfig 109 | 110 | # start, stop, or restart all intfs 111 | eval "${MODE}_all" "$@" 112 | -------------------------------------------------------------------------------- /tests/fsm_ack_rst_win.cc: -------------------------------------------------------------------------------- 1 | #include "tcp_config.hh" 2 | #include "tcp_expectation.hh" 3 | #include "tcp_fsm_test_harness.hh" 4 | #include "tcp_header.hh" 5 | #include "tcp_segment.hh" 6 | 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | using namespace std; 13 | using State = TCPTestHarness::State; 14 | 15 | int main() { 16 | try { 17 | TCPConfig cfg{}; 18 | const WrappingInt32 base_seq(1 << 31); 19 | 20 | // test #1: in ESTABLISHED, send unacceptable segments and ACKs 21 | { 22 | TCPTestHarness test_1 = TCPTestHarness::in_established(cfg, base_seq - 1, base_seq - 1); 23 | 24 | // acceptable ack---no response 25 | test_1.send_ack(base_seq, base_seq); 26 | 27 | test_1.execute(ExpectNoSegment{}, "test 1 failed: ACK after acceptable ACK"); 28 | 29 | // ack in the past---no response 30 | test_1.send_ack(base_seq, base_seq - 1); 31 | 32 | test_1.execute(ExpectNoSegment{}, "test 1 failed: ACK after past ACK"); 33 | 34 | // ack in the future---should get ACK back 35 | test_1.send_ack(base_seq, base_seq + 1); 36 | 37 | test_1.execute(ExpectOneSegment{}.with_ackno(base_seq), "test 1 failed: bad ACK after future ACK"); 38 | 39 | // segment out of the window---should get an ACK 40 | test_1.send_byte(base_seq - 1, base_seq, 1); 41 | 42 | test_1.execute(ExpectUnassembledBytes{0}, "test 1 failed: seg queued on early seqno"); 43 | test_1.execute(ExpectOneSegment{}.with_ackno(base_seq), "test 1 failed: no ack on early seqno"); 44 | 45 | // segment out of the window---should get an ACK 46 | test_1.send_byte(base_seq + cfg.recv_capacity, base_seq, 1); 47 | 48 | test_1.execute(ExpectUnassembledBytes{0}, "test 1 failed: seg queued on late seqno"); 49 | test_1.execute(ExpectOneSegment{}.with_ackno(base_seq), "test 1 failed: no ack on late seqno"); 50 | 51 | // segment in the window but late---should get an ACK and seg should be queued 52 | test_1.send_byte(base_seq + cfg.recv_capacity - 1, base_seq, 1); 53 | 54 | test_1.execute(ExpectUnassembledBytes{1}, "seg not queued on end-of-window seqno"); 55 | 56 | test_1.execute(ExpectOneSegment{}.with_ackno(base_seq), "test 1 failed: no ack on end-of-window seqno"); 57 | test_1.execute(ExpectNoData{}, "test 1 failed: no ack on end-of-window seqno"); 58 | 59 | // segment next byte in the window - ack should advance and data should be readable 60 | test_1.send_byte(base_seq, base_seq, 1); 61 | 62 | test_1.execute(ExpectUnassembledBytes{1}, "seg not processed on next seqno"); 63 | test_1.execute(ExpectOneSegment{}.with_ackno(base_seq + 1), "test 1 failed: no ack on next seqno"); 64 | test_1.execute(ExpectData{}, "test 1 failed: no ack on next seqno"); 65 | 66 | // segment not in window with RST set --- should get nothing back 67 | test_1.send_rst(base_seq); 68 | test_1.send_rst(base_seq + cfg.recv_capacity + 1); 69 | test_1.execute(ExpectNoSegment{}, "test 1 failed: got a response to an out-of-window RST"); 70 | 71 | test_1.send_rst(base_seq + 1); 72 | test_1.execute(ExpectState{State::RESET}); 73 | } 74 | } catch (const exception &e) { 75 | cerr << e.what() << endl; 76 | return EXIT_FAILURE; 77 | } 78 | 79 | return EXIT_SUCCESS; 80 | } 81 | -------------------------------------------------------------------------------- /tests/send_connect.cc: -------------------------------------------------------------------------------- 1 | #include "sender_harness.hh" 2 | #include "wrapping_integers.hh" 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | using namespace std; 13 | 14 | int main() { 15 | try { 16 | auto rd = get_random_generator(); 17 | 18 | { 19 | TCPConfig cfg; 20 | WrappingInt32 isn(rd()); 21 | cfg.fixed_isn = isn; 22 | 23 | TCPSenderTestHarness test{"SYN sent test", cfg}; 24 | test.execute(ExpectState{TCPSenderStateSummary::SYN_SENT}); 25 | test.execute(ExpectSegment{}.with_no_flags().with_syn(true).with_payload_size(0).with_seqno(isn)); 26 | test.execute(ExpectBytesInFlight{1}); 27 | } 28 | 29 | { 30 | TCPConfig cfg; 31 | WrappingInt32 isn(rd()); 32 | cfg.fixed_isn = isn; 33 | 34 | TCPSenderTestHarness test{"SYN acked test", cfg}; 35 | test.execute(ExpectState{TCPSenderStateSummary::SYN_SENT}); 36 | test.execute(ExpectSegment{}.with_no_flags().with_syn(true).with_payload_size(0).with_seqno(isn)); 37 | test.execute(ExpectBytesInFlight{1}); 38 | test.execute(AckReceived{WrappingInt32{isn + 1}}); 39 | test.execute(ExpectState{TCPSenderStateSummary::SYN_ACKED}); 40 | test.execute(ExpectNoSegment{}); 41 | test.execute(ExpectBytesInFlight{0}); 42 | } 43 | 44 | { 45 | TCPConfig cfg; 46 | WrappingInt32 isn(rd()); 47 | cfg.fixed_isn = isn; 48 | 49 | TCPSenderTestHarness test{"SYN -> wrong ack test", cfg}; 50 | test.execute(ExpectState{TCPSenderStateSummary::SYN_SENT}); 51 | test.execute(ExpectSegment{}.with_no_flags().with_syn(true).with_payload_size(0).with_seqno(isn)); 52 | test.execute(ExpectBytesInFlight{1}); 53 | test.execute(AckReceived{WrappingInt32{isn}}); 54 | test.execute(ExpectState{TCPSenderStateSummary::SYN_SENT}); 55 | test.execute(ExpectNoSegment{}); 56 | test.execute(ExpectBytesInFlight{1}); 57 | } 58 | 59 | { 60 | TCPConfig cfg; 61 | WrappingInt32 isn(rd()); 62 | cfg.fixed_isn = isn; 63 | 64 | TCPSenderTestHarness test{"SYN acked, data", cfg}; 65 | test.execute(ExpectState{TCPSenderStateSummary::SYN_SENT}); 66 | test.execute(ExpectSegment{}.with_no_flags().with_syn(true).with_payload_size(0).with_seqno(isn)); 67 | test.execute(ExpectBytesInFlight{1}); 68 | test.execute(AckReceived{WrappingInt32{isn + 1}}); 69 | test.execute(ExpectState{TCPSenderStateSummary::SYN_ACKED}); 70 | test.execute(ExpectNoSegment{}); 71 | test.execute(ExpectBytesInFlight{0}); 72 | test.execute(WriteBytes{"abcdefgh"}); 73 | test.execute(Tick{1}); 74 | test.execute(ExpectState{TCPSenderStateSummary::SYN_ACKED}); 75 | test.execute(ExpectSegment{}.with_seqno(isn + 1).with_data("abcdefgh")); 76 | test.execute(ExpectBytesInFlight{8}); 77 | test.execute(AckReceived{WrappingInt32{isn + 9}}); 78 | test.execute(ExpectState{TCPSenderStateSummary::SYN_ACKED}); 79 | test.execute(ExpectNoSegment{}); 80 | test.execute(ExpectBytesInFlight{0}); 81 | test.execute(ExpectSeqno{WrappingInt32{isn + 9}}); 82 | } 83 | 84 | } catch (const exception &e) { 85 | cerr << e.what() << endl; 86 | return 1; 87 | } 88 | 89 | return EXIT_SUCCESS; 90 | } 91 | -------------------------------------------------------------------------------- /libsponge/util/eventloop.hh: -------------------------------------------------------------------------------- 1 | #ifndef SPONGE_LIBSPONGE_EVENTLOOP_HH 2 | #define SPONGE_LIBSPONGE_EVENTLOOP_HH 3 | 4 | #include "file_descriptor.hh" 5 | 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | //! Waits for events on file descriptors and executes corresponding callbacks. 12 | class EventLoop { 13 | public: 14 | //! Indicates interest in reading (In) or writing (Out) a polled fd. 15 | enum class Direction : short { 16 | In = POLLIN, //!< Callback will be triggered when Rule::fd is readable. 17 | Out = POLLOUT //!< Callback will be triggered when Rule::fd is writable. 18 | }; 19 | 20 | private: 21 | using CallbackT = std::function; //!< Callback for ready Rule::fd 22 | using InterestT = std::function; //!< `true` return indicates Rule::fd should be polled. 23 | 24 | //! \brief Specifies a condition and callback that an EventLoop should handle. 25 | //! \details Created by calling EventLoop::add_rule() or EventLoop::add_cancelable_rule(). 26 | class Rule { 27 | public: 28 | FileDescriptor fd; //!< FileDescriptor to monitor for activity. 29 | Direction direction; //!< Direction::In for reading from fd, Direction::Out for writing to fd. 30 | CallbackT callback; //!< A callback that reads or writes fd. 31 | InterestT interest; //!< A callback that returns `true` whenever fd should be polled. 32 | CallbackT cancel; //!< A callback that is called when the rule is cancelled (e.g. on hangup) 33 | 34 | //! Returns the number of times fd has been read or written, depending on the value of Rule::direction. 35 | //! \details This function is used internally by EventLoop; you will not need to call it 36 | unsigned int service_count() const; 37 | }; 38 | 39 | std::list _rules{}; //!< All rules that have been added and not canceled. 40 | 41 | public: 42 | //! Returned by each call to EventLoop::wait_next_event. 43 | enum class Result { 44 | Success, //!< At least one Rule was triggered. 45 | Timeout, //!< No rules were triggered before timeout. 46 | Exit //!< All rules have been canceled or were uninterested; make no further calls to EventLoop::wait_next_event. 47 | }; 48 | 49 | //! Add a rule whose callback will be called when `fd` is ready in the specified Direction. 50 | void add_rule(const FileDescriptor &fd, 51 | const Direction direction, 52 | const CallbackT &callback, 53 | const InterestT &interest = [] { return true; }, 54 | const CallbackT &cancel = [] {}); 55 | 56 | //! Calls [poll(2)](\ref man2::poll) and then executes callback for each ready fd. 57 | Result wait_next_event(const int timeout_ms); 58 | }; 59 | 60 | using Direction = EventLoop::Direction; 61 | 62 | //! \class EventLoop 63 | //! 64 | //! An EventLoop holds a std::list of Rule objects. Each time EventLoop::wait_next_event is 65 | //! executed, the EventLoop uses the Rule objects to construct a call to [poll(2)](\ref man2::poll). 66 | //! 67 | //! When a Rule is installed using EventLoop::add_rule, it will be polled for the specified Rule::direction 68 | //! whenver the Rule::interest callback returns `true`, until Rule::fd is no longer readable 69 | //! (for Rule::direction == Direction::In) or writable (for Rule::direction == Direction::Out). 70 | //! Once this occurs, the Rule is canceled, i.e., the EventLoop deletes it. 71 | //! 72 | //! A Rule installed using EventLoop::add_cancelable_rule will be polled and canceled under the 73 | //! same conditions, with the additional condition that if Rule::callback returns `true`, the 74 | //! Rule will be canceled. 75 | 76 | #endif // SPONGE_LIBSPONGE_EVENTLOOP_HH 77 | -------------------------------------------------------------------------------- /apps/tcp_benchmark.cc: -------------------------------------------------------------------------------- 1 | #include "tcp_connection.hh" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | using namespace std; 10 | using namespace std::chrono; 11 | 12 | constexpr size_t len = 100 * 1024 * 1024; 13 | 14 | void move_segments(TCPConnection &x, TCPConnection &y, vector &segments, const bool reorder) { 15 | while (not x.segments_out().empty()) { 16 | segments.emplace_back(move(x.segments_out().front())); 17 | x.segments_out().pop(); 18 | } 19 | if (reorder) { 20 | for (auto it = segments.rbegin(); it != segments.rend(); ++it) { 21 | y.segment_received(move(*it)); 22 | } 23 | } else { 24 | for (auto it = segments.begin(); it != segments.end(); ++it) { 25 | y.segment_received(move(*it)); 26 | } 27 | } 28 | segments.clear(); 29 | } 30 | 31 | void main_loop(const bool reorder) { 32 | TCPConfig config; 33 | TCPConnection x{config}, y{config}; 34 | 35 | string string_to_send(len, 'x'); 36 | for (auto &ch : string_to_send) { 37 | ch = rand(); 38 | } 39 | 40 | Buffer bytes_to_send{string(string_to_send)}; 41 | x.connect(); 42 | y.end_input_stream(); 43 | 44 | bool x_closed = false; 45 | 46 | string string_received; 47 | string_received.reserve(len); 48 | 49 | const auto first_time = high_resolution_clock::now(); 50 | 51 | auto loop = [&] { 52 | // write input into x 53 | while (bytes_to_send.size() and x.remaining_outbound_capacity()) { 54 | const auto want = min(x.remaining_outbound_capacity(), bytes_to_send.size()); 55 | const auto written = x.write(string(bytes_to_send.str().substr(0, want))); 56 | if (want != written) { 57 | throw runtime_error("want = " + to_string(want) + ", written = " + to_string(written)); 58 | } 59 | bytes_to_send.remove_prefix(written); 60 | } 61 | 62 | if (bytes_to_send.size() == 0 and not x_closed) { 63 | x.end_input_stream(); 64 | x_closed = true; 65 | } 66 | 67 | // exchange segments between x and y but in reverse order 68 | vector segments; 69 | move_segments(x, y, segments, reorder); 70 | move_segments(y, x, segments, false); 71 | 72 | // read output from y 73 | const auto available_output = y.inbound_stream().buffer_size(); 74 | if (available_output > 0) { 75 | string_received.append(y.inbound_stream().read(available_output)); 76 | } 77 | 78 | // time passes 79 | x.tick(1000); 80 | y.tick(1000); 81 | }; 82 | 83 | while (not y.inbound_stream().eof()) { 84 | loop(); 85 | } 86 | 87 | if (string_received != string_to_send) { 88 | throw runtime_error("strings sent vs. received don't match"); 89 | } 90 | 91 | const auto final_time = high_resolution_clock::now(); 92 | 93 | const auto duration = duration_cast(final_time - first_time).count(); 94 | 95 | const auto gigabits_per_second = len * 8.0 / double(duration); 96 | 97 | cout << fixed << setprecision(2); 98 | cout << "CPU-limited throughput" << (reorder ? " with reordering: " : " : ") << gigabits_per_second 99 | << " Gbit/s\n"; 100 | 101 | while (x.active() or y.active()) { 102 | loop(); 103 | } 104 | } 105 | 106 | int main() { 107 | try { 108 | main_loop(false); 109 | main_loop(true); 110 | } catch (const exception &e) { 111 | cerr << e.what() << "\n"; 112 | return EXIT_FAILURE; 113 | } 114 | 115 | return EXIT_SUCCESS; 116 | } 117 | -------------------------------------------------------------------------------- /tests/tcp_expectation_forward.hh: -------------------------------------------------------------------------------- 1 | #ifndef SPONGE_LIBSPONGE_TCP_EXPECTATION_FORWARD_HH 2 | #define SPONGE_LIBSPONGE_TCP_EXPECTATION_FORWARD_HH 3 | 4 | #include "tcp_segment.hh" 5 | #include "tcp_state.hh" 6 | 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | class TCPTestHarness; 13 | 14 | struct TCPTestStep { 15 | public: 16 | virtual ~TCPTestStep() {} 17 | virtual std::string to_string() const { return "TestStep"; } 18 | virtual void execute(TCPTestHarness &) const {} 19 | virtual TCPSegment expect_seg(TCPTestHarness &) const { throw std::runtime_error("unimplemented"); } 20 | }; 21 | 22 | struct TCPExpectation; 23 | struct ExpectNoSegment; 24 | struct ExpectState; 25 | struct ExpectNotInState; 26 | struct ExpectOneSegment; 27 | struct ExpectSegment; 28 | struct ExpectData; 29 | struct ExpectNoData; 30 | struct ExpectSegmentAvailable; 31 | struct ExpectBytesInFlight; 32 | struct ExpectUnassembledBytes; 33 | struct ExpectWaitTimer; 34 | struct SendSegment; 35 | struct Write; 36 | struct Tick; 37 | struct Connect; 38 | struct Listen; 39 | struct Close; 40 | 41 | class TCPExpectationViolation : public std::runtime_error { 42 | public: 43 | TCPExpectationViolation(const std::string msg) : std::runtime_error(msg) {} 44 | }; 45 | 46 | class TCPPropertyViolation : public TCPExpectationViolation { 47 | public: 48 | TCPPropertyViolation(const std::string &msg) : TCPExpectationViolation(msg) {} 49 | template 50 | static TCPPropertyViolation make(const std::string &property_name, const T &expected_value, const T &actual_value) { 51 | std::stringstream ss{}; 52 | ss << "The TCP has `" << property_name << " = " << actual_value << "`, but " << property_name 53 | << " was expected to be `" << expected_value << "`"; 54 | return TCPPropertyViolation{ss.str()}; 55 | } 56 | 57 | template 58 | static TCPPropertyViolation make_not(const std::string &property_name, const T &expected_non_value) { 59 | std::stringstream ss{}; 60 | ss << "The TCP has `" << property_name << " = " << expected_non_value << "`, but " << property_name 61 | << " was expected to **not** be `" << expected_non_value << "`"; 62 | return TCPPropertyViolation{ss.str()}; 63 | } 64 | }; 65 | 66 | class SegmentExpectationViolation : public TCPExpectationViolation { 67 | public: 68 | SegmentExpectationViolation(const std::string &msg) : TCPExpectationViolation(msg) {} 69 | static SegmentExpectationViolation violated_verb(const std::string &msg) { 70 | return SegmentExpectationViolation{"The TCP should have produced a segment that " + msg + ", but it did not"}; 71 | } 72 | template 73 | static SegmentExpectationViolation violated_field(const std::string &field_name, 74 | const T &expected_value, 75 | const T &actual_value) { 76 | std::stringstream ss{}; 77 | ss << "The TCP produced a segment with `" << field_name << " = " << actual_value << "`, but " << field_name 78 | << " was expected to be `" << expected_value << "`"; 79 | return SegmentExpectationViolation{ss.str()}; 80 | } 81 | }; 82 | 83 | class StateExpectationViolation : public TCPExpectationViolation { 84 | public: 85 | StateExpectationViolation(const std::string &msg) : TCPExpectationViolation(msg) {} 86 | StateExpectationViolation(const TCPState &expected_state, const TCPState &actual_state) 87 | : TCPExpectationViolation("The TCP was in state `" + actual_state.name() + 88 | "`, but it was expected to be in state `" + expected_state.name() + "`") {} 89 | }; 90 | 91 | #endif // SPONGE_LIBSPONGE_TCP_EXPECTATION_FORWARD_HH 92 | -------------------------------------------------------------------------------- /libsponge/util/file_descriptor.cc: -------------------------------------------------------------------------------- 1 | #include "file_descriptor.hh" 2 | 3 | #include "util.hh" 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | using namespace std; 13 | 14 | //! \param[in] fd is the file descriptor number returned by [open(2)](\ref man2::open) or similar 15 | FileDescriptor::FDWrapper::FDWrapper(const int fd) : _fd(fd) { 16 | if (fd < 0) { 17 | throw runtime_error("invalid fd number:" + to_string(fd)); 18 | } 19 | } 20 | 21 | void FileDescriptor::FDWrapper::close() { 22 | SystemCall("close", ::close(_fd)); 23 | _eof = _closed = true; 24 | } 25 | 26 | FileDescriptor::FDWrapper::~FDWrapper() { 27 | try { 28 | if (_closed) { 29 | return; 30 | } 31 | close(); 32 | } catch (const exception &e) { 33 | // don't throw an exception from the destructor 34 | std::cerr << "Exception destructing FDWrapper: " << e.what() << std::endl; 35 | } 36 | } 37 | 38 | //! \param[in] fd is the file descriptor number returned by [open(2)](\ref man2::open) or similar 39 | FileDescriptor::FileDescriptor(const int fd) : _internal_fd(make_shared(fd)) {} 40 | 41 | //! Private constructor used by duplicate() 42 | FileDescriptor::FileDescriptor(shared_ptr other_shared_ptr) : _internal_fd(move(other_shared_ptr)) {} 43 | 44 | //! \returns a copy of this FileDescriptor 45 | FileDescriptor FileDescriptor::duplicate() const { return FileDescriptor(_internal_fd); } 46 | 47 | //! \param[in] limit is the maximum number of bytes to read; fewer bytes may be returned 48 | //! \param[out] str is the string to be read 49 | void FileDescriptor::read(std::string &str, const size_t limit) { 50 | constexpr size_t BUFFER_SIZE = 1024 * 1024; // maximum size of a read 51 | const size_t size_to_read = min(BUFFER_SIZE, limit); 52 | str.resize(size_to_read); 53 | 54 | ssize_t bytes_read = SystemCall("read", ::read(fd_num(), str.data(), size_to_read)); 55 | if (limit > 0 && bytes_read == 0) { 56 | _internal_fd->_eof = true; 57 | } 58 | if (bytes_read > static_cast(size_to_read)) { 59 | throw runtime_error("read() read more than requested"); 60 | } 61 | str.resize(bytes_read); 62 | 63 | register_read(); 64 | } 65 | 66 | //! \param[in] limit is the maximum number of bytes to read; fewer bytes may be returned 67 | //! \returns a vector of bytes read 68 | string FileDescriptor::read(const size_t limit) { 69 | string ret; 70 | 71 | read(ret, limit); 72 | 73 | return ret; 74 | } 75 | 76 | size_t FileDescriptor::write(BufferViewList buffer, const bool write_all) { 77 | size_t total_bytes_written = 0; 78 | 79 | do { 80 | auto iovecs = buffer.as_iovecs(); 81 | 82 | const ssize_t bytes_written = SystemCall("writev", ::writev(fd_num(), iovecs.data(), iovecs.size())); 83 | if (bytes_written == 0 and buffer.size() != 0) { 84 | throw runtime_error("write returned 0 given non-empty input buffer"); 85 | } 86 | 87 | if (bytes_written > ssize_t(buffer.size())) { 88 | throw runtime_error("write wrote more than length of input buffer"); 89 | } 90 | 91 | register_write(); 92 | 93 | buffer.remove_prefix(bytes_written); 94 | 95 | total_bytes_written += bytes_written; 96 | } while (write_all and buffer.size()); 97 | 98 | return total_bytes_written; 99 | } 100 | 101 | void FileDescriptor::set_blocking(const bool blocking_state) { 102 | int flags = SystemCall("fcntl", fcntl(fd_num(), F_GETFL)); 103 | if (blocking_state) { 104 | flags ^= (flags & O_NONBLOCK); 105 | } else { 106 | flags |= O_NONBLOCK; 107 | } 108 | 109 | SystemCall("fcntl", fcntl(fd_num(), F_SETFL, flags)); 110 | } 111 | -------------------------------------------------------------------------------- /libsponge/tcp_helpers/tcp_state.hh: -------------------------------------------------------------------------------- 1 | #ifndef SPONGE_LIBSPONGE_TCP_STATE 2 | #define SPONGE_LIBSPONGE_TCP_STATE 3 | 4 | #include "tcp_receiver.hh" 5 | #include "tcp_sender.hh" 6 | 7 | #include 8 | 9 | //! \brief Summary of a TCPConnection's internal state 10 | //! 11 | //! Most TCP implementations have a global per-connection state 12 | //! machine, as described in the [TCP](\ref rfc::rfc793) 13 | //! specification. Sponge is a bit different: we have factored the 14 | //! connection into two independent parts (the sender and the 15 | //! receiver). The TCPSender and TCPReceiver maintain their interval 16 | //! state variables independently (e.g. next_seqno, number of bytes in 17 | //! flight, or whether each stream has ended). There is no notion of a 18 | //! discrete state machine or much overarching state outside the 19 | //! sender and receiver. To test that Sponge follows the TCP spec, we 20 | //! use this class to compare the "official" states with Sponge's 21 | //! sender/receiver states and two variables that belong to the 22 | //! overarching TCPConnection object. 23 | class TCPState { 24 | private: 25 | std::string _sender{}; 26 | std::string _receiver{}; 27 | bool _active{true}; 28 | bool _linger_after_streams_finish{true}; 29 | 30 | public: 31 | bool operator==(const TCPState &other) const; 32 | bool operator!=(const TCPState &other) const; 33 | 34 | //! \brief Official state names from the [TCP](\ref rfc::rfc793) specification 35 | enum class State { 36 | LISTEN = 0, //!< Listening for a peer to connect 37 | SYN_RCVD, //!< Got the peer's SYN 38 | SYN_SENT, //!< Sent a SYN to initiate a connection 39 | ESTABLISHED, //!< Three-way handshake complete 40 | CLOSE_WAIT, //!< Remote side has sent a FIN, connection is half-open 41 | LAST_ACK, //!< Local side sent a FIN from CLOSE_WAIT, waiting for ACK 42 | FIN_WAIT_1, //!< Sent a FIN to the remote side, not yet ACK'd 43 | FIN_WAIT_2, //!< Received an ACK for previously-sent FIN 44 | CLOSING, //!< Received a FIN just after we sent one 45 | TIME_WAIT, //!< Both sides have sent FIN and ACK'd, waiting for 2 MSL 46 | CLOSED, //!< A connection that has terminated normally 47 | RESET, //!< A connection that terminated abnormally 48 | }; 49 | 50 | //! \brief Summarize the TCPState in a string 51 | std::string name() const; 52 | 53 | //! \brief Construct a TCPState given a sender, a receiver, and the TCPConnection's active and linger bits 54 | TCPState(const TCPSender &sender, const TCPReceiver &receiver, const bool active, const bool linger); 55 | 56 | //! \brief Construct a TCPState that corresponds to one of the "official" TCP state names 57 | TCPState(const TCPState::State state); 58 | 59 | //! \brief Summarize the state of a TCPReceiver in a string 60 | static std::string state_summary(const TCPReceiver &receiver); 61 | 62 | //! \brief Summarize the state of a TCPSender in a string 63 | static std::string state_summary(const TCPSender &receiver); 64 | }; 65 | 66 | namespace TCPReceiverStateSummary { 67 | const std::string ERROR = "error (connection was reset)"; 68 | const std::string LISTEN = "waiting for stream to begin (listening for SYN)"; 69 | const std::string SYN_RECV = "stream started"; 70 | const std::string FIN_RECV = "stream finished"; 71 | } // namespace TCPReceiverStateSummary 72 | 73 | namespace TCPSenderStateSummary { 74 | const std::string ERROR = "error (connection was reset)"; 75 | const std::string CLOSED = "waiting for stream to begin (no SYN sent)"; 76 | const std::string SYN_SENT = "stream started but nothing acknowledged"; 77 | const std::string SYN_ACKED = "stream ongoing"; 78 | const std::string FIN_SENT = "stream finished (FIN sent) but not fully acknowledged"; 79 | const std::string FIN_ACKED = "stream finished and fully acknowledged"; 80 | } // namespace TCPSenderStateSummary 81 | 82 | #endif // SPONGE_LIBSPONGE_TCP_STATE 83 | -------------------------------------------------------------------------------- /apps/bidirectional_stream_copy.cc: -------------------------------------------------------------------------------- 1 | #include "bidirectional_stream_copy.hh" 2 | 3 | #include "byte_stream.hh" 4 | #include "eventloop.hh" 5 | 6 | #include 7 | #include 8 | #include 9 | 10 | using namespace std; 11 | 12 | void bidirectional_stream_copy(Socket &socket) { 13 | constexpr size_t max_copy_length = 65536; 14 | constexpr size_t buffer_size = 1048576; 15 | 16 | EventLoop _eventloop{}; 17 | FileDescriptor _input{STDIN_FILENO}; 18 | FileDescriptor _output{STDOUT_FILENO}; 19 | ByteStream _outbound{buffer_size}; 20 | ByteStream _inbound{buffer_size}; 21 | bool _outbound_shutdown{false}; 22 | bool _inbound_shutdown{false}; 23 | 24 | socket.set_blocking(false); 25 | _input.set_blocking(false); 26 | _output.set_blocking(false); 27 | 28 | // rule 1: read from stdin into outbound byte stream 29 | _eventloop.add_rule(_input, 30 | Direction::In, 31 | [&] { 32 | _outbound.write(_input.read(_outbound.remaining_capacity())); 33 | if (_input.eof()) { 34 | _outbound.end_input(); 35 | } 36 | }, 37 | [&] { return (not _outbound.error()) and (_outbound.remaining_capacity() > 0); }, 38 | [&] { _outbound.end_input(); }); 39 | 40 | // rule 2: read from outbound byte stream into socket 41 | _eventloop.add_rule(socket, 42 | Direction::Out, 43 | [&] { 44 | const size_t bytes_to_write = min(max_copy_length, _outbound.buffer_size()); 45 | const size_t bytes_written = socket.write(_outbound.peek_output(bytes_to_write), false); 46 | _outbound.pop_output(bytes_written); 47 | if (_outbound.eof()) { 48 | socket.shutdown(SHUT_WR); 49 | _outbound_shutdown = true; 50 | } 51 | }, 52 | [&] { return (not _outbound.buffer_empty()) or (_outbound.eof() and not _outbound_shutdown); }, 53 | [&] { _outbound.set_error(); }); 54 | 55 | // rule 3: read from socket into inbound byte stream 56 | _eventloop.add_rule(socket, 57 | Direction::In, 58 | [&] { 59 | _inbound.write(socket.read(_inbound.remaining_capacity())); 60 | if (socket.eof()) { 61 | _inbound.end_input(); 62 | } 63 | }, 64 | [&] { return (not _inbound.error()) and (_inbound.remaining_capacity() > 0); }, 65 | [&] { _inbound.end_input(); }); 66 | 67 | // rule 4: read from inbound byte stream into stdout 68 | _eventloop.add_rule(_output, 69 | Direction::Out, 70 | [&] { 71 | const size_t bytes_to_write = min(max_copy_length, _inbound.buffer_size()); 72 | const size_t bytes_written = _output.write(_inbound.peek_output(bytes_to_write), false); 73 | _inbound.pop_output(bytes_written); 74 | 75 | if (_inbound.eof()) { 76 | _output.close(); 77 | _inbound_shutdown = true; 78 | } 79 | }, 80 | [&] { return (not _inbound.buffer_empty()) or (_inbound.eof() and not _inbound_shutdown); }, 81 | [&] { _inbound.set_error(); }); 82 | 83 | // loop until completion 84 | while (true) { 85 | if (EventLoop::Result::Exit == _eventloop.wait_next_event(-1)) { 86 | return; 87 | } 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /tests/fsm_connect_relaxed.cc: -------------------------------------------------------------------------------- 1 | #include "tcp_config.hh" 2 | #include "tcp_expectation.hh" 3 | #include "tcp_fsm_test_harness.hh" 4 | #include "tcp_header.hh" 5 | #include "tcp_segment.hh" 6 | #include "util.hh" 7 | 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | 15 | using namespace std; 16 | using State = TCPTestHarness::State; 17 | 18 | int main() { 19 | try { 20 | TCPConfig cfg{}; 21 | auto rd = get_random_generator(); 22 | 23 | // test #1: START -> SYN_SENT -> SYN/ACK -> ACK 24 | { 25 | TCPTestHarness test_1(cfg); 26 | 27 | // tell the FSM to connect, make sure we get a SYN 28 | test_1.execute(Connect{}); 29 | test_1.execute(Tick(1)); 30 | TCPSegment seg1 = test_1.expect_seg(ExpectOneSegment{}.with_syn(true).with_ack(false), 31 | "test 1 failed: could not parse SYN segment or invalid flags"); 32 | 33 | test_1.execute(ExpectState{State::SYN_SENT}); 34 | 35 | // now send SYN/ACK 36 | const WrappingInt32 isn(rd()); 37 | test_1.send_syn(isn, seg1.header().seqno + 1); 38 | test_1.execute(Tick(1)); 39 | test_1.execute(ExpectState{State::ESTABLISHED}); 40 | 41 | test_1.execute(ExpectOneSegment{}.with_ack(true).with_syn(false).with_ackno(isn + 1)); 42 | 43 | test_1.execute(ExpectBytesInFlight{0UL}); 44 | } 45 | 46 | // test #2: START -> SYN_SENT -> SYN -> ACK -> ESTABLISHED 47 | { 48 | TCPTestHarness test_2(cfg); 49 | 50 | test_2.execute(Connect{}); 51 | test_2.execute(Tick(1)); 52 | 53 | TCPSegment seg = test_2.expect_seg(ExpectOneSegment{}.with_syn(true).with_ack(false), 54 | "test 2 failed: could not parse SYN segment or invalid flags"); 55 | auto &seg_hdr = seg.header(); 56 | 57 | test_2.execute(ExpectState{State::SYN_SENT}); 58 | 59 | // send SYN (no ACK yet) 60 | const WrappingInt32 isn(rd()); 61 | test_2.send_syn(isn); 62 | test_2.execute(Tick(1)); 63 | 64 | test_2.expect_seg(ExpectOneSegment{}.with_syn(false).with_ack(true).with_ackno(isn + 1), 65 | "test 2 failed: bad ACK for SYN"); 66 | 67 | test_2.execute(ExpectState{State::SYN_RCVD}); 68 | 69 | // now send ACK 70 | test_2.send_ack(isn + 1, seg_hdr.seqno + 1); 71 | test_2.execute(Tick(1)); 72 | test_2.execute(ExpectNoSegment{}, "test 2 failed: got spurious ACK after ACKing SYN"); 73 | test_2.execute(ExpectState{State::ESTABLISHED}); 74 | } 75 | 76 | // test #3: START -> SYN_SENT -> SYN/ACK -> ESTABLISHED 77 | { 78 | TCPTestHarness test_3(cfg); 79 | 80 | test_3.execute(Connect{}); 81 | test_3.execute(Tick(1)); 82 | 83 | TCPSegment seg = test_3.expect_seg(ExpectOneSegment{}.with_syn(true).with_ack(false), 84 | "test 3 failed: could not parse SYN segment or invalid flags"); 85 | auto &seg_hdr = seg.header(); 86 | 87 | test_3.execute(ExpectState{State::SYN_SENT}); 88 | 89 | // send SYN (no ACK yet) 90 | const WrappingInt32 isn(rd()); 91 | test_3.send_syn(isn, seg_hdr.seqno + 1); 92 | test_3.execute(Tick(1)); 93 | 94 | test_3.execute(ExpectOneSegment{}.with_ack(true).with_ackno(isn + 1).with_syn(false), 95 | "test 3 failed: bad ACK for SYN"); 96 | 97 | test_3.execute(ExpectState{State::ESTABLISHED}); 98 | } 99 | } catch (const exception &e) { 100 | cerr << e.what() << endl; 101 | return 1; 102 | } 103 | 104 | return EXIT_SUCCESS; 105 | } 106 | -------------------------------------------------------------------------------- /tests/fsm_passive_close.cc: -------------------------------------------------------------------------------- 1 | #include "tcp_config.hh" 2 | #include "tcp_expectation.hh" 3 | #include "tcp_fsm_test_harness.hh" 4 | #include "tcp_header.hh" 5 | #include "tcp_segment.hh" 6 | 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | using namespace std; 14 | using State = TCPTestHarness::State; 15 | 16 | int main() { 17 | try { 18 | TCPConfig cfg{}; 19 | 20 | // test #1: start in LAST_ACK, ack 21 | { 22 | TCPTestHarness test_1 = TCPTestHarness::in_last_ack(cfg); 23 | 24 | test_1.execute(Tick(4 * cfg.rt_timeout)); 25 | 26 | test_1.execute(ExpectState{State::LAST_ACK}); 27 | 28 | test_1.send_ack(WrappingInt32{2}, WrappingInt32{2}); 29 | test_1.execute(Tick(1)); 30 | 31 | test_1.execute(ExpectState{State::CLOSED}); 32 | } 33 | 34 | // test #2: start in CLOSE_WAIT, close(), throw away first FIN, ack re-tx FIN 35 | { 36 | TCPTestHarness test_2 = TCPTestHarness::in_close_wait(cfg); 37 | 38 | test_2.execute(Tick(4 * cfg.rt_timeout)); 39 | 40 | test_2.execute(ExpectState{State::CLOSE_WAIT}); 41 | 42 | test_2.execute(Close{}); 43 | test_2.execute(Tick(1)); 44 | 45 | test_2.execute(ExpectState{State::LAST_ACK}); 46 | 47 | TCPSegment seg1 = test_2.expect_seg(ExpectOneSegment{}.with_fin(true), "test 2 falied: bad seg or no FIN"); 48 | 49 | test_2.execute(Tick(cfg.rt_timeout - 2)); 50 | 51 | test_2.execute(ExpectNoSegment{}, "test 2 failed: FIN re-tx was too fast"); 52 | 53 | test_2.execute(Tick(2)); 54 | 55 | TCPSegment seg2 = test_2.expect_seg(ExpectOneSegment{}.with_fin(true).with_seqno(seg1.header().seqno), 56 | "test 2 failed: bad re-tx FIN"); 57 | 58 | const WrappingInt32 rx_seqno{2}; 59 | const auto ack_expect = rx_seqno; 60 | test_2.send_ack(ack_expect, seg2.header().seqno - 1); // wrong ackno! 61 | test_2.execute(Tick(1)); 62 | 63 | test_2.execute(ExpectState{State::LAST_ACK}); 64 | 65 | test_2.send_ack(ack_expect, seg2.header().seqno + 1); 66 | test_2.execute(Tick(1)); 67 | 68 | test_2.execute(ExpectState{State::CLOSED}); 69 | } 70 | 71 | // test #3: start in ESTABLSHED, send FIN, recv ACK, check for CLOSE_WAIT 72 | { 73 | TCPTestHarness test_3 = TCPTestHarness::in_established(cfg); 74 | 75 | test_3.execute(Tick(4 * cfg.rt_timeout)); 76 | 77 | test_3.execute(ExpectState{State::ESTABLISHED}); 78 | 79 | const WrappingInt32 rx_seqno{1}; 80 | const auto ack_expect = rx_seqno + 1; 81 | test_3.send_fin(rx_seqno, WrappingInt32{0}); 82 | test_3.execute(Tick(1)); 83 | 84 | test_3.execute(ExpectOneSegment{}.with_ack(true).with_ackno(ack_expect), 85 | "test 3 failed: bad seg, no ACK, or wrong ackno"); 86 | 87 | test_3.execute(ExpectState{State::CLOSE_WAIT}); 88 | 89 | test_3.send_fin(rx_seqno, WrappingInt32{0}); 90 | test_3.execute(Tick(1)); 91 | 92 | test_3.execute(ExpectOneSegment{}.with_ack(true).with_ackno(ack_expect), 93 | "test 3 falied: bad response to 2nd FIN"); 94 | 95 | test_3.execute(ExpectState{State::CLOSE_WAIT}); 96 | 97 | test_3.execute(Tick(1)); 98 | test_3.execute(Close{}); 99 | test_3.execute(Tick(1)); 100 | 101 | test_3.execute(ExpectState{State::LAST_ACK}); 102 | 103 | TCPSegment seg3 = test_3.expect_seg(ExpectOneSegment{}.with_fin(true), "test 3 failed: bad seg or no FIN"); 104 | 105 | test_3.send_ack(ack_expect, seg3.header().seqno + 1); 106 | test_3.execute(Tick(1)); 107 | 108 | test_3.execute(ExpectState{State::CLOSED}); 109 | } 110 | } catch (const exception &e) { 111 | cerr << e.what() << endl; 112 | return 1; 113 | } 114 | 115 | return EXIT_SUCCESS; 116 | } 117 | -------------------------------------------------------------------------------- /tests/fsm_winsize.cc: -------------------------------------------------------------------------------- 1 | #include "tcp_config.hh" 2 | #include "tcp_expectation.hh" 3 | #include "tcp_fsm_test_harness.hh" 4 | #include "tcp_header.hh" 5 | #include "tcp_segment.hh" 6 | #include "test_err_if.hh" 7 | #include "util.hh" 8 | 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | 18 | using namespace std; 19 | using State = TCPTestHarness::State; 20 | 21 | static constexpr unsigned NREPS = 32; 22 | static constexpr unsigned MIN_SWIN = 2048; 23 | static constexpr unsigned MAX_SWIN = 34816; 24 | static constexpr unsigned MIN_SWIN_MUL = 2; 25 | static constexpr unsigned MAX_SWIN_MUL = 6; 26 | 27 | int main() { 28 | try { 29 | auto rd = get_random_generator(); 30 | TCPConfig cfg{}; 31 | cfg.send_capacity = MAX_SWIN * MAX_SWIN_MUL; 32 | 33 | // test 1: listen -> established -> check advertised winsize -> check sent bytes before ACK 34 | for (unsigned rep_no = 0; rep_no < NREPS; ++rep_no) { 35 | cfg.recv_capacity = 2048 + (rd() % 32768); 36 | const WrappingInt32 seq_base(rd()); 37 | TCPTestHarness test_1(cfg); 38 | 39 | // connect 40 | test_1.execute(Listen{}); 41 | test_1.send_syn(seq_base); 42 | 43 | TCPSegment seg = test_1.expect_seg( 44 | ExpectOneSegment{}.with_ack(true).with_ackno(seq_base + 1).with_win(cfg.recv_capacity), 45 | "test 1 failed: SYN/ACK invalid"); 46 | auto &seg_hdr = seg.header(); 47 | 48 | const WrappingInt32 ack_base = seg_hdr.seqno; 49 | 50 | // ack 51 | const uint16_t swin = MIN_SWIN + (rd() % (MAX_SWIN - MIN_SWIN)); 52 | test_1.send_ack(seq_base + 1, ack_base + 1, swin); // adjust send window here 53 | 54 | test_1.execute(ExpectNoSegment{}, "test 1 failed: ACK after acceptable ACK"); 55 | test_1.execute(ExpectState{State::ESTABLISHED}); 56 | 57 | // write swin_mul * swin, make sure swin gets sent 58 | const unsigned swin_mul = MIN_SWIN_MUL + (rd() % (MAX_SWIN_MUL - MIN_SWIN_MUL)); 59 | string d(swin_mul * swin, 0); 60 | generate(d.begin(), d.end(), [&] { return rd(); }); 61 | test_1.execute(Write{d}.with_bytes_written(swin_mul * swin)); 62 | test_1.execute(Tick(1)); 63 | 64 | string d_out(swin_mul * swin, 0); 65 | size_t bytes_total = 0; 66 | while (bytes_total < swin_mul * swin) { 67 | test_1.execute(ExpectSegmentAvailable{}, "test 1 failed: nothing sent after write()"); 68 | size_t bytes_read = 0; 69 | while (test_1.can_read()) { 70 | TCPSegment seg2 = test_1.expect_seg( 71 | ExpectSegment{}.with_ack(true).with_ackno(seq_base + 1).with_win(cfg.recv_capacity), 72 | "test 1 failed: invalid datagrams carrying write() data"); 73 | auto &seg2_hdr = seg2.header(); 74 | bytes_read += seg2.payload().size(); 75 | size_t seg2_first = seg2_hdr.seqno - ack_base - 1; 76 | copy(seg2.payload().str().cbegin(), seg2.payload().str().cend(), d_out.begin() + seg2_first); 77 | } 78 | test_err_if( // correct number of bytes sent 79 | bytes_read + TCPConfig::MAX_PAYLOAD_SIZE < swin, 80 | "test 1 failed: sender did not fill window"); 81 | test_1.execute(ExpectBytesInFlight{bytes_read}, "test 1 failed: sender wrong bytes_in_flight"); 82 | 83 | bytes_total += bytes_read; 84 | // NOTE that we don't override send window here because cfg should have been updated 85 | test_1.send_ack(seq_base + 1, ack_base + 1 + bytes_total, swin); 86 | test_1.execute(Tick(1)); 87 | } 88 | test_1.execute(ExpectBytesInFlight{0}, "test 1 failed: after acking, bytes still in flight?"); 89 | test_err_if(!equal(d.cbegin(), d.cend(), d_out.cbegin()), "test 1 failed: data mismatch"); 90 | } 91 | } catch (const exception &e) { 92 | cerr << e.what() << endl; 93 | return err_num; 94 | } 95 | 96 | return EXIT_SUCCESS; 97 | } 98 | -------------------------------------------------------------------------------- /libsponge/tcp_helpers/tunfd_adapter.cc: -------------------------------------------------------------------------------- 1 | #include "tunfd_adapter.hh" 2 | 3 | #include "ipv4_datagram.hh" 4 | #include "ipv4_header.hh" 5 | #include "parser.hh" 6 | 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | using namespace std; 13 | 14 | //! \details This function first attempts to parse an IP header from the next 15 | //! payload read from the TUN device, then attempts to parse a TCP segment from 16 | //! the IP datagram's payload. 17 | //! 18 | //! If this succeeds, it then checks that the received segment is related to the 19 | //! current connection. When a TCP connection has been established, this means 20 | //! checking that the source and destination ports in the TCP header are correct. 21 | //! 22 | //! If the TCP FSM is listening (i.e., TCPOverIPv4OverTunFdAdapter::_listen is `true`) 23 | //! and the TCP segment read from the wire includes a SYN, this function clears the 24 | //! `_listen` flag and records the source and destination addresses and port numbers 25 | //! from the TCP header; it uses this information to filter future reads. 26 | //! \returns a std::optional that is empty if the segment was invalid or unrelated 27 | optional TCPOverIPv4OverTunFdAdapter::read() { 28 | // is the packet a valid IPv4 datagram? 29 | auto ip_dgram = IPv4Datagram{}; 30 | if (ParseResult::NoError != ip_dgram.parse(FileDescriptor::read())) { 31 | return {}; 32 | } 33 | 34 | // is the IPv4 datagram for us? 35 | // Note: it's valid to bind to address "0" (INADDR_ANY) and reply from actual address contacted 36 | if (not listening() and (ip_dgram.header().dst != config().source.ipv4_numeric())) { 37 | return {}; 38 | } 39 | 40 | // is the IPv4 datagram from our peer? 41 | if (not listening() and (ip_dgram.header().src != config().destination.ipv4_numeric())) { 42 | return {}; 43 | } 44 | 45 | // does the IPv4 datagram claim that its payload is a TCP segment? 46 | if (ip_dgram.header().proto != IPv4Header::PROTO_TCP) { 47 | return {}; 48 | } 49 | 50 | // is the payload a valid TCP segment? 51 | TCPSegment tcp_seg; 52 | if (ParseResult::NoError != tcp_seg.parse(ip_dgram.payload(), ip_dgram.header().pseudo_cksum())) { 53 | return {}; 54 | } 55 | 56 | // is the TCP segment for us? 57 | if (tcp_seg.header().dport != config().source.port()) { 58 | return {}; 59 | } 60 | 61 | // should we target this source addr/port (and use its destination addr as our source) in reply? 62 | if (listening()) { 63 | if (tcp_seg.header().syn and not tcp_seg.header().rst) { 64 | config_mutable().source = {inet_ntoa({htobe32(ip_dgram.header().dst)}), config().source.port()}; 65 | config_mutable().destination = {inet_ntoa({htobe32(ip_dgram.header().src)}), tcp_seg.header().sport}; 66 | set_listening(false); 67 | } else { 68 | return {}; 69 | } 70 | } 71 | 72 | // is the TCP segment from our peer? 73 | if (tcp_seg.header().sport != config().destination.port()) { 74 | return {}; 75 | } 76 | 77 | return tcp_seg; 78 | } 79 | 80 | //! Serializes a TCP segment into an IPv4 datagram, serialize the IPv4 datagram, and send it to the TUN device. 81 | //! \param[in] seg is the TCP segment to write 82 | void TCPOverIPv4OverTunFdAdapter::write(TCPSegment &seg) { 83 | // set the port numbers in the TCP segment 84 | seg.header().sport = config().source.port(); 85 | seg.header().dport = config().destination.port(); 86 | 87 | // create an Internet Datagram and set its addresses and length 88 | IPv4Datagram ip_dgram; 89 | ip_dgram.header().src = config().source.ipv4_numeric(); 90 | ip_dgram.header().dst = config().destination.ipv4_numeric(); 91 | ip_dgram.header().len = ip_dgram.header().hlen * 4 + seg.header().doff * 4 + seg.payload().size(); 92 | 93 | // set payload, calculating TCP checksum using information from IP header 94 | ip_dgram.payload() = seg.serialize(ip_dgram.header().pseudo_cksum()); 95 | 96 | // send 97 | FileDescriptor::write(ip_dgram.serialize()); 98 | } 99 | 100 | //! Specialize LossyFdAdapter to TCPOverIPv4OverTunFdAdapter 101 | template class LossyFdAdapter; 102 | -------------------------------------------------------------------------------- /tests/fsm_connect.cc: -------------------------------------------------------------------------------- 1 | #include "tcp_config.hh" 2 | #include "tcp_expectation.hh" 3 | #include "tcp_fsm_test_harness.hh" 4 | #include "tcp_header.hh" 5 | #include "tcp_segment.hh" 6 | #include "util.hh" 7 | 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | 15 | using namespace std; 16 | using State = TCPTestHarness::State; 17 | 18 | int main() { 19 | try { 20 | TCPConfig cfg{}; 21 | auto rd = get_random_generator(); 22 | 23 | // NB: In Riad's version of this test, the ACK was not ignored, but per 24 | // pg 68 of the RFC, I think that it should be. 25 | // 26 | // test #1: START -> SYN_SENT -> ACK (ignored) -> SYN -> SYN_RECV 27 | { 28 | TCPTestHarness test_1(cfg); 29 | 30 | // tell the FSM to connect, make sure we get a SYN 31 | test_1.execute(Connect{}); 32 | test_1.execute(Tick(1)); 33 | TCPSegment seg1 = test_1.expect_seg(ExpectOneSegment{}.with_syn(true).with_ack(false), 34 | "test 1 failed: could not parse SYN segment or invalid flags"); 35 | 36 | test_1.execute(ExpectState{State::SYN_SENT}); 37 | 38 | // send ACK only (no SYN yet) 39 | test_1.send_ack(WrappingInt32{0}, seg1.header().seqno + 1); 40 | test_1.execute(Tick(1)); 41 | test_1.execute(ExpectState{State::SYN_SENT}); 42 | 43 | // now send SYN 44 | const WrappingInt32 isn(rd()); 45 | test_1.send_syn(isn); 46 | test_1.execute(Tick(1)); 47 | test_1.execute(ExpectState{State::SYN_RCVD}); 48 | 49 | test_1.execute(ExpectOneSegment{}.with_ack(true).with_syn(false).with_ackno(isn + 1)); 50 | 51 | test_1.execute(ExpectBytesInFlight{1UL}); 52 | } 53 | 54 | // test #2: START -> SYN_SENT -> SYN -> ACK -> ESTABLISHED 55 | { 56 | TCPTestHarness test_2(cfg); 57 | 58 | test_2.execute(Connect{}); 59 | test_2.execute(Tick(1)); 60 | 61 | TCPSegment seg = test_2.expect_seg(ExpectOneSegment{}.with_syn(true).with_ack(false), 62 | "test 2 failed: could not parse SYN segment or invalid flags"); 63 | auto &seg_hdr = seg.header(); 64 | 65 | test_2.execute(ExpectState{State::SYN_SENT}); 66 | 67 | // send SYN (no ACK yet) 68 | const WrappingInt32 isn(rd()); 69 | test_2.send_syn(isn); 70 | test_2.execute(Tick(1)); 71 | 72 | test_2.expect_seg(ExpectOneSegment{}.with_syn(false).with_ack(true).with_ackno(isn + 1), 73 | "test 2 failed: bad ACK for SYN"); 74 | 75 | test_2.execute(ExpectState{State::SYN_RCVD}); 76 | 77 | // now send ACK 78 | test_2.send_ack(isn + 1, seg_hdr.seqno + 1); 79 | test_2.execute(Tick(1)); 80 | test_2.execute(ExpectNoSegment{}, "test 2 failed: got spurious ACK after ACKing SYN"); 81 | test_2.execute(ExpectState{State::ESTABLISHED}); 82 | } 83 | 84 | // test #3: START -> SYN_SENT -> SYN/ACK -> ESTABLISHED 85 | { 86 | TCPTestHarness test_3(cfg); 87 | 88 | test_3.execute(Connect{}); 89 | test_3.execute(Tick(1)); 90 | 91 | TCPSegment seg = test_3.expect_seg(ExpectOneSegment{}.with_syn(true).with_ack(false), 92 | "test 3 failed: could not parse SYN segment or invalid flags"); 93 | auto &seg_hdr = seg.header(); 94 | 95 | test_3.execute(ExpectState{State::SYN_SENT}); 96 | 97 | // send SYN (no ACK yet) 98 | const WrappingInt32 isn(rd()); 99 | test_3.send_syn(isn, seg_hdr.seqno + 1); 100 | test_3.execute(Tick(1)); 101 | 102 | test_3.execute(ExpectOneSegment{}.with_ack(true).with_ackno(isn + 1).with_syn(false), 103 | "test 3 failed: bad ACK for SYN"); 104 | 105 | test_3.execute(ExpectState{State::ESTABLISHED}); 106 | } 107 | } catch (const exception &e) { 108 | cerr << e.what() << endl; 109 | return 1; 110 | } 111 | 112 | return EXIT_SUCCESS; 113 | } 114 | -------------------------------------------------------------------------------- /libsponge/tcp_connection.hh: -------------------------------------------------------------------------------- 1 | #ifndef SPONGE_LIBSPONGE_TCP_FACTORED_HH 2 | #define SPONGE_LIBSPONGE_TCP_FACTORED_HH 3 | 4 | #include "tcp_config.hh" 5 | #include "tcp_receiver.hh" 6 | #include "tcp_sender.hh" 7 | #include "tcp_state.hh" 8 | 9 | //! \brief A complete endpoint of a TCP connection 10 | class TCPConnection 11 | { 12 | private: 13 | TCPConfig _cfg; 14 | TCPReceiver _receiver{_cfg.recv_capacity}; 15 | TCPSender _sender{_cfg.send_capacity, _cfg.rt_timeout, _cfg.fixed_isn}; 16 | 17 | size_t _time_since_last_segment_received = 0; 18 | bool _active = true; 19 | 20 | //! outbound queue of segments that the TCPConnection wants sent 21 | std::queue _segments_out{}; 22 | 23 | //! Should the TCPConnection stay active (and keep ACKing) 24 | //! for 10 * _cfg.rt_timeout milliseconds after both streams have ended, 25 | //! in case the remote TCPConnection doesn't know we've received its whole stream? 26 | bool _linger_after_streams_finish{true}; 27 | 28 | public: 29 | //! \name "Input" interface for the writer 30 | //!@{ 31 | 32 | //! \brief Initiate a connection by sending a SYN segment 33 | void connect(); 34 | 35 | //! \brief Write data to the outbound byte stream, and send it over TCP if possible 36 | //! \returns the number of bytes from `data` that were actually written. 37 | size_t write(const std::string &data); 38 | 39 | //! \returns the number of `bytes` that can be written right now. 40 | size_t remaining_outbound_capacity() const; 41 | 42 | //! \brief Shut down the outbound byte stream (still allows reading incoming data) 43 | void end_input_stream(); 44 | //!@} 45 | 46 | //! \name "Output" interface for the reader 47 | //!@{ 48 | 49 | //! \brief The inbound byte stream received from the peer 50 | ByteStream &inbound_stream() { return _receiver.stream_out(); } 51 | //!@} 52 | 53 | //! \name Accessors used for testing 54 | 55 | //!@{ 56 | //! \brief number of bytes sent and not yet acknowledged, counting SYN/FIN each as one byte 57 | size_t bytes_in_flight() const; 58 | //! \brief number of bytes not yet reassembled 59 | size_t unassembled_bytes() const; 60 | //! \brief Number of milliseconds since the last segment was received 61 | size_t time_since_last_segment_received() const; 62 | //!< \brief summarize the state of the sender, receiver, and the connection 63 | TCPState state() const { return {_sender, _receiver, active(), _linger_after_streams_finish}; }; 64 | //!@} 65 | 66 | //! \name Methods for the owner or operating system to call 67 | //!@{ 68 | 69 | //! Called when a new segment has been received from the network 70 | void segment_received(const TCPSegment &seg); 71 | 72 | //! Called periodically when time elapses 73 | void tick(const size_t ms_since_last_tick); 74 | 75 | //! \brief TCPSegments that the TCPConnection has enqueued for transmission. 76 | //! \note The owner or operating system will dequeue these and 77 | //! put each one into the payload of a lower-layer datagram (usually Internet datagrams (IP), 78 | //! but could also be user datagrams (UDP) or any other kind). 79 | std::queue &segments_out() { return _segments_out; } 80 | 81 | //! \brief Is the connection still alive in any way? 82 | //! \returns `true` if either stream is still running or if the TCPConnection is lingering 83 | //! after both streams have finished (e.g. to ACK retransmissions from the peer) 84 | bool active() const; 85 | //!@} 86 | 87 | //! Construct a new connection from a configuration 88 | explicit TCPConnection(const TCPConfig &cfg) : _cfg{cfg} {} 89 | 90 | //! \name construction and destruction 91 | //! moving is allowed; copying is disallowed; default construction not possible 92 | 93 | //!@{ 94 | ~TCPConnection(); //!< destructor sends a RST if the connection is still open 95 | TCPConnection() = delete; 96 | TCPConnection(TCPConnection &&other) = default; 97 | TCPConnection &operator=(TCPConnection &&other) = default; 98 | TCPConnection(const TCPConnection &other) = delete; 99 | TCPConnection &operator=(const TCPConnection &other) = delete; 100 | //!@} 101 | 102 | void send_segments(); 103 | void unclean_shutdown(); 104 | void clean_shutdown(); 105 | }; 106 | 107 | #endif // SPONGE_LIBSPONGE_TCP_FACTORED_HH 108 | -------------------------------------------------------------------------------- /tests/recv_connect.cc: -------------------------------------------------------------------------------- 1 | #include "receiver_harness.hh" 2 | #include "wrapping_integers.hh" 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | using namespace std; 13 | 14 | int main() { 15 | try { 16 | // auto rd = get_random_generator(); 17 | 18 | { 19 | TCPReceiverTestHarness test{4000}; 20 | test.execute(ExpectWindow{4000}); 21 | test.execute(ExpectAckno{std::optional{}}); 22 | test.execute(ExpectUnassembledBytes{0}); 23 | test.execute(ExpectTotalAssembledBytes{0}); 24 | test.execute(SegmentArrives{}.with_syn().with_seqno(0).with_result(SegmentArrives::Result::OK)); 25 | test.execute(ExpectAckno{WrappingInt32{1}}); 26 | test.execute(ExpectUnassembledBytes{0}); 27 | test.execute(ExpectTotalAssembledBytes{0}); 28 | } 29 | 30 | { 31 | TCPReceiverTestHarness test{5435}; 32 | test.execute(ExpectAckno{std::optional{}}); 33 | test.execute(ExpectUnassembledBytes{0}); 34 | test.execute(ExpectTotalAssembledBytes{0}); 35 | test.execute(SegmentArrives{}.with_syn().with_seqno(89347598).with_result(SegmentArrives::Result::OK)); 36 | test.execute(ExpectAckno{WrappingInt32{89347599}}); 37 | test.execute(ExpectUnassembledBytes{0}); 38 | test.execute(ExpectTotalAssembledBytes{0}); 39 | } 40 | 41 | { 42 | TCPReceiverTestHarness test{5435}; 43 | test.execute(ExpectAckno{std::optional{}}); 44 | test.execute(ExpectUnassembledBytes{0}); 45 | test.execute(ExpectTotalAssembledBytes{0}); 46 | test.execute(SegmentArrives{}.with_seqno(893475).with_result(SegmentArrives::Result::NOT_SYN)); 47 | test.execute(ExpectAckno{std::optional{}}); 48 | test.execute(ExpectUnassembledBytes{0}); 49 | test.execute(ExpectTotalAssembledBytes{0}); 50 | } 51 | 52 | { 53 | TCPReceiverTestHarness test{5435}; 54 | test.execute(ExpectAckno{std::optional{}}); 55 | test.execute(ExpectUnassembledBytes{0}); 56 | test.execute(ExpectTotalAssembledBytes{0}); 57 | test.execute(SegmentArrives{}.with_ack(0).with_fin().with_seqno(893475).with_result( 58 | SegmentArrives::Result::NOT_SYN)); 59 | test.execute(ExpectAckno{std::optional{}}); 60 | test.execute(ExpectUnassembledBytes{0}); 61 | test.execute(ExpectTotalAssembledBytes{0}); 62 | } 63 | 64 | { 65 | TCPReceiverTestHarness test{5435}; 66 | test.execute(ExpectAckno{std::optional{}}); 67 | test.execute(ExpectUnassembledBytes{0}); 68 | test.execute(ExpectTotalAssembledBytes{0}); 69 | test.execute(SegmentArrives{}.with_ack(0).with_fin().with_seqno(893475).with_result( 70 | SegmentArrives::Result::NOT_SYN)); 71 | test.execute(ExpectAckno{std::optional{}}); 72 | test.execute(ExpectUnassembledBytes{0}); 73 | test.execute(ExpectTotalAssembledBytes{0}); 74 | test.execute(SegmentArrives{}.with_syn().with_seqno(89347598).with_result(SegmentArrives::Result::OK)); 75 | test.execute(ExpectAckno{WrappingInt32{89347599}}); 76 | test.execute(ExpectUnassembledBytes{0}); 77 | test.execute(ExpectTotalAssembledBytes{0}); 78 | } 79 | 80 | { 81 | TCPReceiverTestHarness test{4000}; 82 | test.execute(SegmentArrives{}.with_syn().with_seqno(5).with_fin().with_result(SegmentArrives::Result::OK)); 83 | test.execute(ExpectState{TCPReceiverStateSummary::FIN_RECV}); 84 | test.execute(ExpectAckno{WrappingInt32{7}}); 85 | test.execute(ExpectUnassembledBytes{0}); 86 | test.execute(ExpectTotalAssembledBytes{0}); 87 | } 88 | 89 | { 90 | // Window overflow 91 | size_t cap = static_cast(UINT16_MAX) + 5; 92 | TCPReceiverTestHarness test{cap}; 93 | test.execute(ExpectWindow{cap}); 94 | } 95 | } catch (const exception &e) { 96 | cerr << e.what() << endl; 97 | return 1; 98 | } 99 | 100 | return EXIT_SUCCESS; 101 | } 102 | -------------------------------------------------------------------------------- /libsponge/tcp_helpers/tcp_header.cc: -------------------------------------------------------------------------------- 1 | #include "tcp_header.hh" 2 | 3 | #include 4 | 5 | using namespace std; 6 | 7 | //! \param[in,out] p is a NetParser from which the TCP fields will be extracted 8 | //! \returns a ParseResult indicating success or the reason for failure 9 | //! \details It is important to check for (at least) the following potential errors 10 | //! (but note that NetParser inherently checks for certain errors; 11 | //! use that fact to your advantage!): 12 | //! 13 | //! - data stream inside the NetParser is too short to contain a header 14 | //! - the header's `doff` field is shorter than the minimum allowed 15 | //! - there is less data in the header than the `doff` field claims 16 | //! - the checksum is bad 17 | ParseResult TCPHeader::parse(NetParser &p) { 18 | sport = p.u16(); // source port 19 | dport = p.u16(); // destination port 20 | seqno = WrappingInt32{p.u32()}; // sequence number 21 | ackno = WrappingInt32{p.u32()}; // ack number 22 | doff = p.u8() >> 4; // data offset 23 | 24 | const uint8_t fl_b = p.u8(); // byte including flags 25 | urg = static_cast(fl_b & 0b0010'0000); // binary literals and ' digit separator since C++14!!! 26 | ack = static_cast(fl_b & 0b0001'0000); 27 | psh = static_cast(fl_b & 0b0000'1000); 28 | rst = static_cast(fl_b & 0b0000'0100); 29 | syn = static_cast(fl_b & 0b0000'0010); 30 | fin = static_cast(fl_b & 0b0000'0001); 31 | 32 | win = p.u16(); // window size 33 | cksum = p.u16(); // checksum 34 | uptr = p.u16(); // urgent pointer 35 | 36 | if (doff < 5) { 37 | return ParseResult::HeaderTooShort; 38 | } 39 | 40 | // skip any options or anything extra in the header 41 | p.remove_prefix(doff * 4 - TCPHeader::LENGTH); 42 | 43 | if (p.error()) { 44 | return p.get_error(); 45 | } 46 | 47 | return ParseResult::NoError; 48 | } 49 | 50 | //! Serialize the TCPHeader to a string (does not recompute the checksum) 51 | string TCPHeader::serialize() const { 52 | // sanity check 53 | if (doff < 5) { 54 | throw runtime_error("TCP header too short"); 55 | } 56 | 57 | string ret; 58 | ret.reserve(4 * doff); 59 | 60 | NetUnparser::u16(ret, sport); // source port 61 | NetUnparser::u16(ret, dport); // destination port 62 | NetUnparser::u32(ret, seqno.raw_value()); // sequence number 63 | NetUnparser::u32(ret, ackno.raw_value()); // ack number 64 | NetUnparser::u8(ret, doff << 4); // data offset 65 | 66 | const uint8_t fl_b = (urg ? 0b0010'0000 : 0) | (ack ? 0b0001'0000 : 0) | (psh ? 0b0000'1000 : 0) | 67 | (rst ? 0b0000'0100 : 0) | (syn ? 0b0000'0010 : 0) | (fin ? 0b0000'0001 : 0); 68 | NetUnparser::u8(ret, fl_b); // flags 69 | NetUnparser::u16(ret, win); // window size 70 | 71 | NetUnparser::u16(ret, cksum); // checksum 72 | 73 | NetUnparser::u16(ret, uptr); // urgent pointer 74 | 75 | ret.resize(4 * doff); // expand header to advertised size 76 | 77 | return ret; 78 | } 79 | 80 | //! \returns A string with the header's contents 81 | string TCPHeader::to_string() const { 82 | stringstream ss{}; 83 | ss << hex << boolalpha << "TCP source port: " << +sport << '\n' 84 | << "TCP dest port: " << +dport << '\n' 85 | << "TCP seqno: " << seqno << '\n' 86 | << "TCP ackno: " << ackno << '\n' 87 | << "TCP doff: " << +doff << '\n' 88 | << "Flags: urg: " << urg << " ack: " << ack << " psh: " << psh << " rst: " << rst << " syn: " << syn 89 | << " fin: " << fin << '\n' 90 | << "TCP winsize: " << +win << '\n' 91 | << "TCP cksum: " << +cksum << '\n' 92 | << "TCP uptr: " << +uptr << '\n'; 93 | return ss.str(); 94 | } 95 | 96 | string TCPHeader::summary() const { 97 | stringstream ss{}; 98 | ss << "Header(flags=" << (syn ? "S" : "") << (ack ? "A" : "") << (rst ? "R" : "") << (fin ? "F" : "") 99 | << ",seqno=" << seqno << ",ack=" << ackno << ",win=" << win << ")"; 100 | return ss.str(); 101 | } 102 | 103 | bool TCPHeader::operator==(const TCPHeader &other) const { 104 | // TODO(aozdemir) more complete check (right now we omit cksum, src, dst 105 | return seqno == other.seqno && ackno == other.ackno && doff == other.doff && urg == other.urg && ack == other.ack && 106 | psh == other.psh && rst == other.rst && syn == other.syn && fin == other.fin && win == other.win && 107 | uptr == other.uptr; 108 | } 109 | -------------------------------------------------------------------------------- /tests/byte_stream_test_harness.hh: -------------------------------------------------------------------------------- 1 | #ifndef SPONGE_BYTE_STREAM_HARNESS_HH 2 | #define SPONGE_BYTE_STREAM_HARNESS_HH 3 | 4 | #include "byte_stream.hh" 5 | #include "util.hh" 6 | 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | struct ByteStreamTestStep { 15 | virtual operator std::string() const; 16 | virtual void execute(ByteStream &) const; 17 | virtual ~ByteStreamTestStep(); 18 | }; 19 | 20 | class ByteStreamExpectationViolation : public std::runtime_error { 21 | public: 22 | ByteStreamExpectationViolation(const std::string &msg); 23 | 24 | template 25 | static ByteStreamExpectationViolation property(const std::string &property_name, 26 | const T &expected, 27 | const T &actual); 28 | }; 29 | 30 | struct ByteStreamExpectation : public ByteStreamTestStep { 31 | operator std::string() const override; 32 | virtual std::string description() const; 33 | virtual void execute(ByteStream &) const override; 34 | virtual ~ByteStreamExpectation() override; 35 | }; 36 | 37 | struct ByteStreamAction : public ByteStreamTestStep { 38 | operator std::string() const override; 39 | virtual std::string description() const; 40 | virtual void execute(ByteStream &) const override; 41 | virtual ~ByteStreamAction() override; 42 | }; 43 | 44 | struct EndInput : public ByteStreamAction { 45 | std::string description() const override; 46 | void execute(ByteStream &) const override; 47 | }; 48 | 49 | struct Write : public ByteStreamAction { 50 | std::string _data; 51 | std::optional _bytes_written{}; 52 | 53 | Write(const std::string &data); 54 | Write &with_bytes_written(const size_t bytes_written); 55 | std::string description() const override; 56 | void execute(ByteStream &) const override; 57 | }; 58 | 59 | struct Pop : public ByteStreamAction { 60 | size_t _len; 61 | 62 | Pop(const size_t len); 63 | std::string description() const override; 64 | void execute(ByteStream &) const override; 65 | }; 66 | 67 | struct InputEnded : public ByteStreamExpectation { 68 | bool _input_ended; 69 | 70 | InputEnded(const bool input_ended); 71 | std::string description() const override; 72 | void execute(ByteStream &) const override; 73 | }; 74 | 75 | struct BufferEmpty : public ByteStreamExpectation { 76 | bool _buffer_empty; 77 | 78 | BufferEmpty(const bool buffer_empty); 79 | std::string description() const override; 80 | void execute(ByteStream &) const override; 81 | }; 82 | 83 | struct Eof : public ByteStreamExpectation { 84 | bool _eof; 85 | 86 | Eof(const bool eof); 87 | std::string description() const override; 88 | void execute(ByteStream &) const override; 89 | }; 90 | 91 | struct BufferSize : public ByteStreamExpectation { 92 | size_t _buffer_size; 93 | 94 | BufferSize(const size_t buffer_size); 95 | std::string description() const override; 96 | void execute(ByteStream &) const override; 97 | }; 98 | 99 | struct BytesWritten : public ByteStreamExpectation { 100 | size_t _bytes_written; 101 | 102 | BytesWritten(const size_t bytes_written); 103 | std::string description() const override; 104 | void execute(ByteStream &) const override; 105 | }; 106 | 107 | struct BytesRead : public ByteStreamExpectation { 108 | size_t _bytes_read; 109 | 110 | BytesRead(const size_t bytes_read); 111 | std::string description() const override; 112 | void execute(ByteStream &) const override; 113 | }; 114 | 115 | struct RemainingCapacity : public ByteStreamExpectation { 116 | size_t _remaining_capacity; 117 | 118 | RemainingCapacity(const size_t remaining_capacity); 119 | std::string description() const override; 120 | void execute(ByteStream &) const override; 121 | }; 122 | 123 | struct Peek : public ByteStreamExpectation { 124 | std::string _output; 125 | 126 | Peek(const std::string &output); 127 | std::string description() const override; 128 | void execute(ByteStream &) const override; 129 | }; 130 | 131 | class ByteStreamTestHarness { 132 | std::string _test_name; 133 | ByteStream _byte_stream; 134 | std::vector _steps_executed{}; 135 | 136 | public: 137 | ByteStreamTestHarness(const std::string &test_name, const size_t capacity); 138 | 139 | void execute(const ByteStreamTestStep &step); 140 | }; 141 | 142 | #endif // SPONGE_BYTE_STREAM_HARNESS_HH 143 | --------------------------------------------------------------------------------