├── 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 └── tests.cmake ├── apps ├── CMakeLists.txt └── webget.cc ├── 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 ├── libsponge ├── CMakeLists.txt ├── util │ ├── tun.cc │ ├── tun.hh │ ├── parser.cc │ ├── util.hh │ ├── parser.hh │ ├── buffer.cc │ ├── address.hh │ ├── eventloop.hh │ ├── file_descriptor.cc │ ├── buffer.hh │ ├── socket.hh │ ├── file_descriptor.hh │ ├── address.cc │ ├── util.cc │ ├── eventloop.cc │ └── socket.cc ├── byte_stream.cc └── byte_stream.hh ├── .gitignore ├── tests ├── webget_t.sh ├── CMakeLists.txt ├── test_err_if.hh ├── test_should_be.hh ├── byte_stream_construction.cc ├── byte_stream_many_writes.cc ├── byte_stream_capacity.cc ├── byte_stream_test_harness.hh ├── byte_stream_one_write.cc ├── byte_stream_two_writes.cc └── byte_stream_test_harness.cc ├── writeups └── lab0.md ├── .clang-format ├── CMakeLists.txt └── README.md /etc/tunconfig: -------------------------------------------------------------------------------- 1 | TUN_IP_PREFIX=169.254 2 | -------------------------------------------------------------------------------- /apps/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_sponge_exec (webget) 2 | -------------------------------------------------------------------------------- /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/wine99/cs144-20fa/HEAD/etc/sponge_small.png -------------------------------------------------------------------------------- /doctests/address_example_3.cc: -------------------------------------------------------------------------------- 1 | const uint32_t a_dns_server_numeric = a_dns_server.ipv4_numeric(); 2 | -------------------------------------------------------------------------------- /doctests/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_sponge_exec (address_dt) 2 | add_sponge_exec (parser_dt) 3 | add_sponge_exec (socket_dt) 4 | -------------------------------------------------------------------------------- /libsponge/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | file (GLOB LIB_SOURCES "*.cc" "util/*.cc" "tcp_helpers/*.cc") 2 | add_library (sponge STATIC ${LIB_SOURCES}) 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.a 2 | *.o 3 | .vscode 4 | temp.cc 5 | /build 6 | CMakeCache.txt 7 | CMakeFiles 8 | Makefile 9 | cmake_install.cmake 10 | CTestTestfile.cmake 11 | compile_commands.json 12 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /tests/webget_t.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | WEB_HASH=`./apps/webget cs144.keithw.org /nph-hasher/xyzzy | tee /dev/stderr | tail -n 1` 4 | CORRECT_HASH="7SmXqWkrLKzVBCEalbSPqBcvs11Pw263K7x4Wv3JckI" 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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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. I [did/did not] attend the lab session. 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_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/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_library (spongechecks STATIC 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 (byte_stream_construction) 10 | add_test_exec (byte_stream_one_write) 11 | add_test_exec (byte_stream_two_writes) 12 | add_test_exec (byte_stream_capacity) 13 | add_test_exec (byte_stream_many_writes) 14 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /.clang-format: -------------------------------------------------------------------------------- 1 | --- 2 | Language: Cpp 3 | BasedOnStyle: LLVM 4 | AllowAllParametersOfDeclarationOnNextLine: false 5 | AlwaysBreakTemplateDeclarations: true 6 | BinPackArguments: false 7 | BinPackParameters: false 8 | BreakConstructorInitializers: BeforeComma 9 | ColumnLimit: 120 10 | CommentPragmas: '^(!|NOLINT)' 11 | ConstructorInitializerAllOnOneLineOrOnePerLine: true 12 | IncludeBlocks: Regroup 13 | IncludeCategories: 14 | - Regex: '^<.*' 15 | Priority: 2 16 | - Regex: '.*' 17 | Priority: 1 18 | IncludeIsMainRegex: '(_dt|_win)?$' 19 | IndentCaseLabels: true 20 | IndentWidth: 4 21 | KeepEmptyLinesAtTheStartOfBlocks: false 22 | PenaltyReturnTypeOnItsOwnLine: 200 23 | SpacesBeforeTrailingComments: 2 24 | TabWidth: 4 25 | UseTab: Never 26 | ... 27 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | rfc826 21 | rfc826 22 | 23 | 24 | 25 | 26 | 27 | rfc6298 28 | rfc6298 29 | 30 | 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/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 or TAP device, specified at its creation. 16 | //! \param[in] is_tun is `true` for a TUN device (expects IP datagrams), or `false` for a TAP device (expects Ethernet frames) 17 | //! 18 | //! To create a TUN device, you should already have run 19 | //! 20 | //! ip tuntap add mode tun user `username` name `devname` 21 | //! 22 | //! as root before calling this function. 23 | 24 | TunTapFD::TunTapFD(const string &devname, const bool is_tun) 25 | : FileDescriptor(SystemCall("open", open(CLONEDEV, O_RDWR))) { 26 | struct ifreq tun_req {}; 27 | 28 | tun_req.ifr_flags = (is_tun ? IFF_TUN : IFF_TAP) | IFF_NO_PI; // tun device with no packetinfo 29 | 30 | // copy devname to ifr_name, making sure to null terminate 31 | 32 | strncpy(static_cast(tun_req.ifr_name), devname.data(), IFNAMSIZ - 1); 33 | tun_req.ifr_name[IFNAMSIZ - 1] = '\0'; 34 | 35 | SystemCall("ioctl", ioctl(fd_num(), TUNSETIFF, static_cast(&tun_req))); 36 | } 37 | -------------------------------------------------------------------------------- /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/TAP](https://www.kernel.org/doc/Documentation/networking/tuntap.txt) device 9 | class TunTapFD : public FileDescriptor { 10 | public: 11 | //! Open an existing persistent [TUN or TAP device](https://www.kernel.org/doc/Documentation/networking/tuntap.txt). 12 | explicit TunTapFD(const std::string &devname, const bool is_tun); 13 | }; 14 | 15 | //! A FileDescriptor to a [Linux TUN](https://www.kernel.org/doc/Documentation/networking/tuntap.txt) device 16 | class TunFD : public TunTapFD { 17 | public: 18 | //! Open an existing persistent [TUN device](https://www.kernel.org/doc/Documentation/networking/tuntap.txt). 19 | explicit TunFD(const std::string &devname) : TunTapFD(devname, true) {} 20 | }; 21 | 22 | //! A FileDescriptor to a [Linux TAP](https://www.kernel.org/doc/Documentation/networking/tuntap.txt) device 23 | class TapFD : public TunTapFD { 24 | public: 25 | //! Open an existing persistent [TAP device](https://www.kernel.org/doc/Documentation/networking/tuntap.txt). 26 | explicit TapFD(const std::string &devname) : TunTapFD(devname, false) {} 27 | }; 28 | 29 | #endif // SPONGE_LIBSPONGE_TUN_HH 30 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 "util.hh" 3 | 4 | #include 5 | #include 6 | 7 | using namespace std; 8 | 9 | void get_URL(const string &host, const string &path) { 10 | // Your code here. 11 | 12 | // You will need to connect to the "http" service on 13 | // the computer whose name is in the "host" string, 14 | // then request the URL path given in the "path" string. 15 | 16 | // Then you'll need to print out everything the server sends back, 17 | // (not just one call to read() -- everything) until you reach 18 | // the "eof" (end of file). 19 | 20 | // cerr << "Function called: get_URL(" << host << ", " << path << ").\n"; 21 | // cerr << "Warning: get_URL() has not been implemented yet.\n"; 22 | 23 | TCPSocket sock1; 24 | sock1.connect(Address(host, "http")); 25 | sock1.write("GET " + path + " HTTP/1.1\r\n" + "Host: " + host + "\r\n" + "Connection: close\r\n\r\n"); 26 | while (!sock1.eof()) { 27 | cout << sock1.read(); 28 | } 29 | sock1.shutdown(SHUT_WR); 30 | } 31 | 32 | int main(int argc, char *argv[]) { 33 | try { 34 | if (argc <= 0) { 35 | abort(); // For sticklers: don't try to access argv[0] if argc <= 0. 36 | } 37 | 38 | // The program takes two command-line arguments: the hostname and "path" part of the URL. 39 | // Print the usage message unless there are these two arguments (plus the program name 40 | // itself, so arg count = 3 in total). 41 | if (argc != 3) { 42 | cerr << "Usage: " << argv[0] << " HOST PATH\n"; 43 | cerr << "\tExample: " << argv[0] << " stanford.edu /class/cs144\n"; 44 | return EXIT_FAILURE; 45 | } 46 | 47 | // Get the command-line arguments. 48 | const string host = argv[1]; 49 | const string path = argv[2]; 50 | 51 | // Call the student-written function. 52 | get_URL(host, path); 53 | } catch (const exception &e) { 54 | cerr << e.what() << "\n"; 55 | return EXIT_FAILURE; 56 | } 57 | 58 | return EXIT_SUCCESS; 59 | } 60 | -------------------------------------------------------------------------------- /libsponge/byte_stream.cc: -------------------------------------------------------------------------------- 1 | #include "byte_stream.hh" 2 | 3 | #include 4 | 5 | // Dummy implementation of a flow-controlled in-memory byte stream. 6 | 7 | // For Lab 0, please replace with a real implementation that passes the 8 | // automated checks run by `make check_lab0`. 9 | 10 | // You will need to add private members to the class declaration in `byte_stream.hh` 11 | 12 | template 13 | void DUMMY_CODE(Targs &&... /* unused */) {} 14 | 15 | using namespace std; 16 | 17 | ByteStream::ByteStream(const size_t capacity) : _capacity(capacity) {} 18 | 19 | size_t ByteStream::write(const string &data) { 20 | size_t write_count = 0; 21 | for (const char c : data) { 22 | // not very efficient to do conditional in loop 23 | if (_capacity - _buffer_size <= 0) 24 | break; 25 | else { 26 | _stream.push_back(c); 27 | ++_buffer_size; 28 | ++_bytes_written; 29 | ++write_count; 30 | } 31 | } 32 | 33 | return write_count; 34 | } 35 | 36 | //! \param[in] len bytes will be copied from the output side of the buffer 37 | string ByteStream::peek_output(const size_t len) const { 38 | const size_t peek_length = len > _buffer_size ? _buffer_size : len; 39 | list::const_iterator it = _stream.begin(); 40 | advance(it, peek_length); 41 | return string(_stream.begin(), it); 42 | } 43 | 44 | //! \param[in] len bytes will be removed from the output side of the buffer 45 | void ByteStream::pop_output(const size_t len) { 46 | size_t pop_length = len > _buffer_size ? _buffer_size : len; 47 | _bytes_read += pop_length; 48 | _buffer_size -= pop_length; 49 | while (pop_length--) 50 | _stream.pop_front(); 51 | } 52 | 53 | //! Read (i.e., copy and then pop) the next "len" bytes of the stream 54 | //! \param[in] len bytes will be popped and returned 55 | //! \returns a string 56 | std::string ByteStream::read(const size_t len) { 57 | const string result = peek_output(len); 58 | pop_output(len); 59 | return result; 60 | } 61 | 62 | void ByteStream::end_input() { _input_ended = true; } 63 | 64 | bool ByteStream::input_ended() const { return _input_ended; } 65 | 66 | size_t ByteStream::buffer_size() const { return _buffer_size; } 67 | 68 | bool ByteStream::buffer_empty() const { return _stream.size() == 0; } 69 | 70 | bool ByteStream::eof() const { return _input_ended && buffer_empty(); } 71 | 72 | size_t ByteStream::bytes_written() const { return _bytes_written; } 73 | 74 | size_t ByteStream::bytes_read() const { return _bytes_read; } 75 | 76 | size_t ByteStream::remaining_capacity() const { return _capacity - _buffer_size; } 77 | -------------------------------------------------------------------------------- /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/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, TCP segment, Ethernet frame, or ARP message 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 | Unsupported //!< Packet uses unsupported features 20 | }; 21 | 22 | //! Output a string representation of a ParseResult 23 | std::string as_string(const ParseResult r); 24 | 25 | class NetParser { 26 | private: 27 | Buffer _buffer; 28 | ParseResult _error = ParseResult::NoError; //!< Result of parsing so far 29 | 30 | //! Check that there is sufficient data to parse the next token 31 | void _check_size(const size_t size); 32 | 33 | //! Generic integer parsing method (used by u32, u16, u8) 34 | template 35 | T _parse_int(); 36 | 37 | public: 38 | NetParser(Buffer buffer) : _buffer(buffer) {} 39 | 40 | Buffer buffer() const { return _buffer; } 41 | 42 | //! Get the current value stored in BaseParser::_error 43 | ParseResult get_error() const { return _error; } 44 | 45 | //! \brief Set BaseParser::_error 46 | //! \param[in] res is the value to store in BaseParser::_error 47 | void set_error(ParseResult res) { _error = res; } 48 | 49 | //! Returns `true` if there has been an error 50 | bool error() const { return get_error() != ParseResult::NoError; } 51 | 52 | //! Parse a 32-bit integer in network byte order from the data stream 53 | uint32_t u32(); 54 | 55 | //! Parse a 16-bit integer in network byte order from the data stream 56 | uint16_t u16(); 57 | 58 | //! Parse an 8-bit integer in network byte order from the data stream 59 | uint8_t u8(); 60 | 61 | //! Remove n bytes from the buffer 62 | void remove_prefix(const size_t n); 63 | }; 64 | 65 | struct NetUnparser { 66 | template 67 | static void _unparse_int(std::string &s, T val); 68 | 69 | //! Write a 32-bit integer into the data stream in network byte order 70 | static void u32(std::string &s, const uint32_t val); 71 | 72 | //! Write a 16-bit integer into the data stream in network byte order 73 | static void u16(std::string &s, const uint16_t val); 74 | 75 | //! Write an 8-bit integer into the data stream in network byte order 76 | static void u8(std::string &s, const uint8_t val); 77 | }; 78 | 79 | #endif // SPONGE_LIBSPONGE_PARSER_HH 80 | -------------------------------------------------------------------------------- /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 | 7 | //! \brief An in-order byte stream. 8 | 9 | //! Bytes are written on the "input" side and read from the "output" 10 | //! side. The byte stream is finite: the writer can end the input, 11 | //! and then no more bytes can be written. 12 | class ByteStream { 13 | private: 14 | // Your code here -- add private members as necessary. 15 | 16 | // Hint: This doesn't need to be a sophisticated data structure at 17 | // all, but if any of your tests are taking longer than a second, 18 | // that's a sign that you probably want to keep exploring 19 | // different approaches. 20 | 21 | bool _error = false; //!< Flag indicating that the stream suffered an error. 22 | bool _input_ended = false; 23 | size_t _capacity; 24 | size_t _buffer_size = 0; 25 | size_t _bytes_written = 0; 26 | size_t _bytes_read = 0; 27 | std::list _stream{}; 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 string 63 | std::string read(const size_t len); 64 | 65 | //! \returns `true` if the stream input has ended 66 | bool input_ended() const; 67 | 68 | //! \returns `true` if the stream has suffered an error 69 | bool error() const { return _error; } 70 | 71 | //! \returns the maximum amount that can currently be read from the stream 72 | size_t buffer_size() const; 73 | 74 | //! \returns `true` if the buffer is empty 75 | bool buffer_empty() const; 76 | 77 | //! \returns `true` if the output has reached the ending 78 | bool eof() const; 79 | //!@} 80 | 81 | //! \name General accounting 82 | //!@{ 83 | 84 | //! Total number of bytes written 85 | size_t bytes_written() const; 86 | 87 | //! Total number of bytes popped 88 | size_t bytes_read() const; 89 | //!@} 90 | }; 91 | 92 | #endif // SPONGE_LIBSPONGE_BYTE_STREAM_HH 93 | -------------------------------------------------------------------------------- /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 = 0); 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 | //! Create an Address from a 32-bit raw numeric IP address 57 | static Address from_ipv4_numeric(const uint32_t ip_address); 58 | //! Human-readable string, e.g., "8.8.8.8:53". 59 | std::string to_string() const; 60 | //!@} 61 | 62 | //! \name Low-level operations 63 | //!@{ 64 | 65 | //! Size of the underlying address storage. 66 | socklen_t size() const { return _size; } 67 | //! Const pointer to the underlying socket address storage. 68 | operator const sockaddr *() const { return _address; } 69 | //!@} 70 | }; 71 | 72 | //! \class Address 73 | //! For example, you can do DNS lookups: 74 | //! 75 | //! \include address_example_1.cc 76 | //! 77 | //! or you can specify an IP address and port number: 78 | //! 79 | //! \include address_example_2.cc 80 | //! 81 | //! Once you have an address, you can convert it to other useful representations, e.g., 82 | //! 83 | //! \include address_example_3.cc 84 | 85 | #endif // SPONGE_LIBSPONGE_ADDRESS_HH 86 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | { 72 | ByteStreamTestHarness test{"long-stream", 3}; 73 | 74 | test.execute(Write{"abcdef"}.with_bytes_written(3)); 75 | test.execute(Peek{"abc"}); 76 | test.execute(Pop{1}); 77 | 78 | for (unsigned int i = 0; i < 99997; i++) { 79 | test.execute(RemainingCapacity{1}); 80 | test.execute(BufferSize{2}); 81 | test.execute(Write{"abc"}.with_bytes_written(1)); 82 | test.execute(RemainingCapacity{0}); 83 | test.execute(Peek{"bca"}); 84 | test.execute(Pop{1}); 85 | 86 | test.execute(RemainingCapacity{1}); 87 | test.execute(BufferSize{2}); 88 | test.execute(Write{"bca"}.with_bytes_written(1)); 89 | test.execute(RemainingCapacity{0}); 90 | test.execute(Peek{"cab"}); 91 | test.execute(Pop{1}); 92 | 93 | test.execute(RemainingCapacity{1}); 94 | test.execute(BufferSize{2}); 95 | test.execute(Write{"cab"}.with_bytes_written(1)); 96 | test.execute(RemainingCapacity{0}); 97 | test.execute(Peek{"abc"}); 98 | test.execute(Pop{1}); 99 | } 100 | 101 | test.execute(EndInput{}); 102 | test.execute(Peek{"bc"}); 103 | test.execute(Pop{2}); 104 | test.execute(Eof{true}); 105 | } 106 | 107 | } catch (const exception &e) { 108 | cerr << "Exception: " << e.what() << endl; 109 | return EXIT_FAILURE; 110 | } 111 | 112 | return EXIT_SUCCESS; 113 | } 114 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /libsponge/util/buffer.hh: -------------------------------------------------------------------------------- 1 | #ifndef SPONGE_LIBSPONGE_BUFFER_HH 2 | #define SPONGE_LIBSPONGE_BUFFER_HH 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | //! \brief A reference-counted read-only string that can discard bytes from the front 14 | class Buffer { 15 | private: 16 | std::shared_ptr _storage{}; 17 | size_t _starting_offset{}; 18 | 19 | public: 20 | Buffer() = default; 21 | 22 | //! \brief Construct by taking ownership of a string 23 | Buffer(std::string &&str) noexcept : _storage(std::make_shared(std::move(str))) {} 24 | 25 | //! \name Expose contents as a std::string_view 26 | //!@{ 27 | std::string_view str() const { 28 | if (not _storage) { 29 | return {}; 30 | } 31 | return {_storage->data() + _starting_offset, _storage->size() - _starting_offset}; 32 | } 33 | 34 | operator std::string_view() const { return str(); } 35 | //!@} 36 | 37 | //! \brief Get character at location `n` 38 | uint8_t at(const size_t n) const { return str().at(n); } 39 | 40 | //! \brief Size of the string 41 | size_t size() const { return str().size(); } 42 | 43 | //! \brief Make a copy to a new std::string 44 | std::string copy() const { return std::string(str()); } 45 | 46 | //! \brief Discard the first `n` bytes of the string (does not require a copy or move) 47 | //! \note Doesn't free any memory until the whole string has been discarded in all copies of the Buffer. 48 | void remove_prefix(const size_t n); 49 | }; 50 | 51 | //! \brief A reference-counted discontiguous string that can discard bytes from the front 52 | //! \note Used to model packets that contain multiple sets of headers 53 | //! + a payload. This allows us to prepend headers (e.g., to 54 | //! encapsulate a TCP payload in a TCPSegment, and then encapsulate 55 | //! the TCPSegment in an IPv4Datagram) without copying the payload. 56 | class BufferList { 57 | private: 58 | std::deque _buffers{}; 59 | 60 | public: 61 | //! \name Constructors 62 | //!@{ 63 | 64 | BufferList() = default; 65 | 66 | //! \brief Construct from a Buffer 67 | BufferList(Buffer buffer) : _buffers{buffer} {} 68 | 69 | //! \brief Construct by taking ownership of a std::string 70 | BufferList(std::string &&str) noexcept { 71 | Buffer buf{std::move(str)}; 72 | append(buf); 73 | } 74 | //!@} 75 | 76 | //! \brief Access the underlying queue of Buffers 77 | const std::deque &buffers() const { return _buffers; } 78 | 79 | //! \brief Append a BufferList 80 | void append(const BufferList &other); 81 | 82 | //! \brief Transform to a Buffer 83 | //! \note Throws an exception unless BufferList is contiguous 84 | operator Buffer() const; 85 | 86 | //! \brief Discard the first `n` bytes of the string (does not require a copy or move) 87 | void remove_prefix(size_t n); 88 | 89 | //! \brief Size of the string 90 | size_t size() const; 91 | 92 | //! \brief Make a copy to a new std::string 93 | std::string concatenate() const; 94 | }; 95 | 96 | //! \brief A non-owning temporary view (similar to std::string_view) of a discontiguous string 97 | class BufferViewList { 98 | std::deque _views{}; 99 | 100 | public: 101 | //! \name Constructors 102 | //!@{ 103 | 104 | //! \brief Construct from a std::string 105 | BufferViewList(const std::string &str) : BufferViewList(std::string_view(str)) {} 106 | 107 | //! \brief Construct from a C string (must be NULL-terminated) 108 | BufferViewList(const char *s) : BufferViewList(std::string_view(s)) {} 109 | 110 | //! \brief Construct from a BufferList 111 | BufferViewList(const BufferList &buffers); 112 | 113 | //! \brief Construct from a std::string_view 114 | BufferViewList(std::string_view str) { _views.push_back({const_cast(str.data()), str.size()}); } 115 | //!@} 116 | 117 | //! \brief Discard the first `n` bytes of the string (does not require a copy or move) 118 | void remove_prefix(size_t n); 119 | 120 | //! \brief Size of the string 121 | size_t size() const; 122 | 123 | //! \brief Convert to a vector of `iovec` structures 124 | //! \note used for system calls that write discontiguous buffers, 125 | //! e.g. [writev(2)](\ref man2::writev) and [sendmsg(2)](\ref man2::sendmsg) 126 | std::vector as_iovecs() const; 127 | }; 128 | 129 | #endif // SPONGE_LIBSPONGE_BUFFER_HH 130 | -------------------------------------------------------------------------------- /tests/byte_stream_one_write.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{"write-end-pop", 15}; 13 | 14 | test.execute(Write{"cat"}); 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{3}); 21 | test.execute(RemainingCapacity{12}); 22 | test.execute(BufferSize{3}); 23 | test.execute(Peek{"cat"}); 24 | 25 | test.execute(EndInput{}); 26 | 27 | test.execute(InputEnded{true}); 28 | test.execute(BufferEmpty{false}); 29 | test.execute(Eof{false}); 30 | test.execute(BytesRead{0}); 31 | test.execute(BytesWritten{3}); 32 | test.execute(RemainingCapacity{12}); 33 | test.execute(BufferSize{3}); 34 | test.execute(Peek{"cat"}); 35 | 36 | test.execute(Pop{3}); 37 | 38 | test.execute(InputEnded{true}); 39 | test.execute(BufferEmpty{true}); 40 | test.execute(Eof{true}); 41 | test.execute(BytesRead{3}); 42 | test.execute(BytesWritten{3}); 43 | test.execute(RemainingCapacity{15}); 44 | test.execute(BufferSize{0}); 45 | } 46 | 47 | { 48 | ByteStreamTestHarness test{"write-pop-end", 15}; 49 | 50 | test.execute(Write{"cat"}); 51 | 52 | test.execute(InputEnded{false}); 53 | test.execute(BufferEmpty{false}); 54 | test.execute(Eof{false}); 55 | test.execute(BytesRead{0}); 56 | test.execute(BytesWritten{3}); 57 | test.execute(RemainingCapacity{12}); 58 | test.execute(BufferSize{3}); 59 | test.execute(Peek{"cat"}); 60 | 61 | test.execute(Pop{3}); 62 | 63 | test.execute(InputEnded{false}); 64 | test.execute(BufferEmpty{true}); 65 | test.execute(Eof{false}); 66 | test.execute(BytesRead{3}); 67 | test.execute(BytesWritten{3}); 68 | test.execute(RemainingCapacity{15}); 69 | test.execute(BufferSize{0}); 70 | 71 | test.execute(EndInput{}); 72 | 73 | test.execute(InputEnded{true}); 74 | test.execute(BufferEmpty{true}); 75 | test.execute(Eof{true}); 76 | test.execute(BytesRead{3}); 77 | test.execute(BytesWritten{3}); 78 | test.execute(RemainingCapacity{15}); 79 | test.execute(BufferSize{0}); 80 | } 81 | 82 | { 83 | ByteStreamTestHarness test{"write-pop2-end", 15}; 84 | 85 | test.execute(Write{"cat"}); 86 | 87 | test.execute(InputEnded{false}); 88 | test.execute(BufferEmpty{false}); 89 | test.execute(Eof{false}); 90 | test.execute(BytesRead{0}); 91 | test.execute(BytesWritten{3}); 92 | test.execute(RemainingCapacity{12}); 93 | test.execute(BufferSize{3}); 94 | test.execute(Peek{"cat"}); 95 | 96 | test.execute(Pop{1}); 97 | 98 | test.execute(InputEnded{false}); 99 | test.execute(BufferEmpty{false}); 100 | test.execute(Eof{false}); 101 | test.execute(BytesRead{1}); 102 | test.execute(BytesWritten{3}); 103 | test.execute(RemainingCapacity{13}); 104 | test.execute(BufferSize{2}); 105 | test.execute(Peek{"at"}); 106 | 107 | test.execute(Pop{2}); 108 | 109 | test.execute(InputEnded{false}); 110 | test.execute(BufferEmpty{true}); 111 | test.execute(Eof{false}); 112 | test.execute(BytesRead{3}); 113 | test.execute(BytesWritten{3}); 114 | test.execute(RemainingCapacity{15}); 115 | test.execute(BufferSize{0}); 116 | 117 | test.execute(EndInput{}); 118 | 119 | test.execute(InputEnded{true}); 120 | test.execute(BufferEmpty{true}); 121 | test.execute(Eof{true}); 122 | test.execute(BytesRead{3}); 123 | test.execute(BytesWritten{3}); 124 | test.execute(RemainingCapacity{15}); 125 | test.execute(BufferSize{0}); 126 | } 127 | 128 | } catch (const exception &e) { 129 | cerr << "Exception: " << e.what() << endl; 130 | return EXIT_FAILURE; 131 | } 132 | 133 | return EXIT_SUCCESS; 134 | } 135 | -------------------------------------------------------------------------------- /tests/byte_stream_two_writes.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{"write-write-end-pop-pop", 15}; 13 | 14 | test.execute(Write{"cat"}); 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{3}); 21 | test.execute(RemainingCapacity{12}); 22 | test.execute(BufferSize{3}); 23 | test.execute(Peek{"cat"}); 24 | 25 | test.execute(Write{"tac"}); 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{6}); 32 | test.execute(RemainingCapacity{9}); 33 | test.execute(BufferSize{6}); 34 | test.execute(Peek{"cattac"}); 35 | 36 | test.execute(EndInput{}); 37 | 38 | test.execute(InputEnded{true}); 39 | test.execute(BufferEmpty{false}); 40 | test.execute(Eof{false}); 41 | test.execute(BytesRead{0}); 42 | test.execute(BytesWritten{6}); 43 | test.execute(RemainingCapacity{9}); 44 | test.execute(BufferSize{6}); 45 | test.execute(Peek{"cattac"}); 46 | 47 | test.execute(Pop{2}); 48 | 49 | test.execute(InputEnded{true}); 50 | test.execute(BufferEmpty{false}); 51 | test.execute(Eof{false}); 52 | test.execute(BytesRead{2}); 53 | test.execute(BytesWritten{6}); 54 | test.execute(RemainingCapacity{11}); 55 | test.execute(BufferSize{4}); 56 | test.execute(Peek{"ttac"}); 57 | 58 | test.execute(Pop{4}); 59 | 60 | test.execute(InputEnded{true}); 61 | test.execute(BufferEmpty{true}); 62 | test.execute(Eof{true}); 63 | test.execute(BytesRead{6}); 64 | test.execute(BytesWritten{6}); 65 | test.execute(RemainingCapacity{15}); 66 | test.execute(BufferSize{0}); 67 | } 68 | 69 | { 70 | ByteStreamTestHarness test{"write-pop-write-end-pop", 15}; 71 | 72 | test.execute(Write{"cat"}); 73 | 74 | test.execute(InputEnded{false}); 75 | test.execute(BufferEmpty{false}); 76 | test.execute(Eof{false}); 77 | test.execute(BytesRead{0}); 78 | test.execute(BytesWritten{3}); 79 | test.execute(RemainingCapacity{12}); 80 | test.execute(BufferSize{3}); 81 | test.execute(Peek{"cat"}); 82 | 83 | test.execute(Pop{2}); 84 | 85 | test.execute(InputEnded{false}); 86 | test.execute(BufferEmpty{false}); 87 | test.execute(Eof{false}); 88 | test.execute(BytesRead{2}); 89 | test.execute(BytesWritten{3}); 90 | test.execute(RemainingCapacity{14}); 91 | test.execute(BufferSize{1}); 92 | test.execute(Peek{"t"}); 93 | 94 | test.execute(Write{"tac"}); 95 | 96 | test.execute(InputEnded{false}); 97 | test.execute(BufferEmpty{false}); 98 | test.execute(Eof{false}); 99 | test.execute(BytesRead{2}); 100 | test.execute(BytesWritten{6}); 101 | test.execute(RemainingCapacity{11}); 102 | test.execute(BufferSize{4}); 103 | test.execute(Peek{"ttac"}); 104 | 105 | test.execute(EndInput{}); 106 | 107 | test.execute(InputEnded{true}); 108 | test.execute(BufferEmpty{false}); 109 | test.execute(Eof{false}); 110 | test.execute(BytesRead{2}); 111 | test.execute(BytesWritten{6}); 112 | test.execute(RemainingCapacity{11}); 113 | test.execute(BufferSize{4}); 114 | test.execute(Peek{"ttac"}); 115 | 116 | test.execute(Pop{4}); 117 | 118 | test.execute(InputEnded{true}); 119 | test.execute(BufferEmpty{true}); 120 | test.execute(Eof{true}); 121 | test.execute(BytesRead{6}); 122 | test.execute(BytesWritten{6}); 123 | test.execute(RemainingCapacity{15}); 124 | test.execute(BufferSize{0}); 125 | } 126 | 127 | } catch (const exception &e) { 128 | cerr << "Exception: " << e.what() << endl; 129 | return EXIT_FAILURE; 130 | } 131 | 132 | return EXIT_SUCCESS; 133 | } 134 | -------------------------------------------------------------------------------- /libsponge/util/socket.hh: -------------------------------------------------------------------------------- 1 | #ifndef SPONGE_LIBSPONGE_SOCKET_HH 2 | #define SPONGE_LIBSPONGE_SOCKET_HH 3 | 4 | #include "address.hh" 5 | #include "file_descriptor.hh" 6 | 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | //! \brief Base class for network sockets (TCP, UDP, etc.) 13 | //! \details Socket is generally used via a subclass. See TCPSocket and UDPSocket for usage examples. 14 | class Socket : public FileDescriptor { 15 | private: 16 | //! Get the local or peer address the socket is connected to 17 | Address get_address(const std::string &name_of_function, 18 | const std::function &function) const; 19 | 20 | protected: 21 | //! Construct via [socket(2)](\ref man2::socket) 22 | Socket(const int domain, const int type); 23 | 24 | //! Construct from a file descriptor. 25 | Socket(FileDescriptor &&fd, const int domain, const int type); 26 | 27 | //! Wrapper around [setsockopt(2)](\ref man2::setsockopt) 28 | template 29 | void setsockopt(const int level, const int option, const option_type &option_value); 30 | 31 | public: 32 | //! Bind a socket to a specified address with [bind(2)](\ref man2::bind), usually for listen/accept 33 | void bind(const Address &address); 34 | 35 | //! Connect a socket to a specified peer address with [connect(2)](\ref man2::connect) 36 | void connect(const Address &address); 37 | 38 | //! Shut down a socket via [shutdown(2)](\ref man2::shutdown) 39 | void shutdown(const int how); 40 | 41 | //! Get local address of socket with [getsockname(2)](\ref man2::getsockname) 42 | Address local_address() const; 43 | //! Get peer address of socket with [getpeername(2)](\ref man2::getpeername) 44 | Address peer_address() const; 45 | 46 | //! Allow local address to be reused sooner via [SO_REUSEADDR](\ref man7::socket) 47 | void set_reuseaddr(); 48 | }; 49 | 50 | //! A wrapper around [UDP sockets](\ref man7::udp) 51 | class UDPSocket : public Socket { 52 | protected: 53 | //! \brief Construct from FileDescriptor (used by TCPOverUDPSocketAdapter) 54 | //! \param[in] fd is the FileDescriptor from which to construct 55 | explicit UDPSocket(FileDescriptor &&fd) : Socket(std::move(fd), AF_INET, SOCK_DGRAM) {} 56 | 57 | public: 58 | //! Default: construct an unbound, unconnected UDP socket 59 | UDPSocket() : Socket(AF_INET, SOCK_DGRAM) {} 60 | 61 | //! Returned by UDPSocket::recv; carries received data and information about the sender 62 | struct received_datagram { 63 | Address source_address; //!< Address from which this datagram was received 64 | std::string payload; //!< UDP datagram payload 65 | }; 66 | 67 | //! Receive a datagram and the Address of its sender 68 | received_datagram recv(const size_t mtu = 65536); 69 | 70 | //! Receive a datagram and the Address of its sender (caller can allocate storage) 71 | void recv(received_datagram &datagram, const size_t mtu = 65536); 72 | 73 | //! Send a datagram to specified Address 74 | void sendto(const Address &destination, const BufferViewList &payload); 75 | 76 | //! Send datagram to the socket's connected address (must call connect() first) 77 | void send(const BufferViewList &payload); 78 | }; 79 | 80 | //! \class UDPSocket 81 | //! Functions in this class are essentially wrappers over their POSIX eponyms. 82 | //! 83 | //! Example: 84 | //! 85 | //! \include socket_example_1.cc 86 | 87 | //! A wrapper around [TCP sockets](\ref man7::tcp) 88 | class TCPSocket : public Socket { 89 | private: 90 | //! \brief Construct from FileDescriptor (used by accept()) 91 | //! \param[in] fd is the FileDescriptor from which to construct 92 | explicit TCPSocket(FileDescriptor &&fd) : Socket(std::move(fd), AF_INET, SOCK_STREAM) {} 93 | 94 | public: 95 | //! Default: construct an unbound, unconnected TCP socket 96 | TCPSocket() : Socket(AF_INET, SOCK_STREAM) {} 97 | 98 | //! Mark a socket as listening for incoming connections 99 | void listen(const int backlog = 16); 100 | 101 | //! Accept a new incoming connection 102 | TCPSocket accept(); 103 | }; 104 | 105 | //! \class TCPSocket 106 | //! Functions in this class are essentially wrappers over their POSIX eponyms. 107 | //! 108 | //! Example: 109 | //! 110 | //! \include socket_example_2.cc 111 | 112 | //! A wrapper around [Unix-domain stream sockets](\ref man7::unix) 113 | class LocalStreamSocket : public Socket { 114 | public: 115 | //! Construct from a file descriptor 116 | explicit LocalStreamSocket(FileDescriptor &&fd) : Socket(std::move(fd), AF_UNIX, SOCK_STREAM) {} 117 | }; 118 | 119 | //! \class LocalStreamSocket 120 | //! Example: 121 | //! 122 | //! \include socket_example_3.cc 123 | 124 | #endif // SPONGE_LIBSPONGE_SOCKET_HH 125 | -------------------------------------------------------------------------------- /libsponge/util/file_descriptor.hh: -------------------------------------------------------------------------------- 1 | #ifndef SPONGE_LIBSPONGE_FILE_DESCRIPTOR_HH 2 | #define SPONGE_LIBSPONGE_FILE_DESCRIPTOR_HH 3 | 4 | #include "buffer.hh" 5 | 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | //! A reference-counted handle to a file descriptor 12 | class FileDescriptor { 13 | //! \brief A handle on a kernel file descriptor. 14 | //! \details FileDescriptor objects contain a std::shared_ptr to a FDWrapper. 15 | class FDWrapper { 16 | public: 17 | int _fd; //!< The file descriptor number returned by the kernel 18 | bool _eof = false; //!< Flag indicating whether FDWrapper::_fd is at EOF 19 | bool _closed = false; //!< Flag indicating whether FDWrapper::_fd has been closed 20 | unsigned _read_count = 0; //!< The number of times FDWrapper::_fd has been read 21 | unsigned _write_count = 0; //!< The numberof times FDWrapper::_fd has been written 22 | 23 | //! Construct from a file descriptor number returned by the kernel 24 | explicit FDWrapper(const int fd); 25 | //! Closes the file descriptor upon destruction 26 | ~FDWrapper(); 27 | //! Calls [close(2)](\ref man2::close) on FDWrapper::_fd 28 | void close(); 29 | 30 | //! \name 31 | //! An FDWrapper cannot be copied or moved 32 | 33 | //!@{ 34 | FDWrapper(const FDWrapper &other) = delete; 35 | FDWrapper &operator=(const FDWrapper &other) = delete; 36 | FDWrapper(FDWrapper &&other) = delete; 37 | FDWrapper &operator=(FDWrapper &&other) = delete; 38 | //!@} 39 | }; 40 | 41 | //! A reference-counted handle to a shared FDWrapper 42 | std::shared_ptr _internal_fd; 43 | 44 | // private constructor used to duplicate the FileDescriptor (increase the reference count) 45 | explicit FileDescriptor(std::shared_ptr other_shared_ptr); 46 | 47 | protected: 48 | void register_read() { ++_internal_fd->_read_count; } //!< increment read count 49 | void register_write() { ++_internal_fd->_write_count; } //!< increment write count 50 | 51 | public: 52 | //! Construct from a file descriptor number returned by the kernel 53 | explicit FileDescriptor(const int fd); 54 | 55 | //! Free the std::shared_ptr; the FDWrapper destructor calls close() when the refcount goes to zero. 56 | ~FileDescriptor() = default; 57 | 58 | //! Read up to `limit` bytes 59 | std::string read(const size_t limit = std::numeric_limits::max()); 60 | 61 | //! Read up to `limit` bytes into `str` (caller can allocate storage) 62 | void read(std::string &str, const size_t limit = std::numeric_limits::max()); 63 | 64 | //! Write a string, possibly blocking until all is written 65 | size_t write(const char *str, const bool write_all = true) { return write(BufferViewList(str), write_all); } 66 | 67 | //! Write a string, possibly blocking until all is written 68 | size_t write(const std::string &str, const bool write_all = true) { return write(BufferViewList(str), write_all); } 69 | 70 | //! Write a buffer (or list of buffers), possibly blocking until all is written 71 | size_t write(BufferViewList buffer, const bool write_all = true); 72 | 73 | //! Close the underlying file descriptor 74 | void close() { _internal_fd->close(); } 75 | 76 | //! Copy a FileDescriptor explicitly, increasing the FDWrapper refcount 77 | FileDescriptor duplicate() const; 78 | 79 | //! Set blocking(true) or non-blocking(false) 80 | void set_blocking(const bool blocking_state); 81 | 82 | //! \name FDWrapper accessors 83 | //!@{ 84 | int fd_num() const { return _internal_fd->_fd; } //!< \brief underlying descriptor number 85 | bool eof() const { return _internal_fd->_eof; } //!< \brief EOF flag state 86 | bool closed() const { return _internal_fd->_closed; } //!< \brief closed flag state 87 | unsigned int read_count() const { return _internal_fd->_read_count; } //!< \brief number of reads 88 | unsigned int write_count() const { return _internal_fd->_write_count; } //!< \brief number of writes 89 | //!@} 90 | 91 | //! \name Copy/move constructor/assignment operators 92 | //! FileDescriptor can be moved, but cannot be copied (but see duplicate()) 93 | //!@{ 94 | FileDescriptor(const FileDescriptor &other) = delete; //!< \brief copy construction is forbidden 95 | FileDescriptor &operator=(const FileDescriptor &other) = delete; //!< \brief copy assignment is forbidden 96 | FileDescriptor(FileDescriptor &&other) = default; //!< \brief move construction is allowed 97 | FileDescriptor &operator=(FileDescriptor &&other) = default; //!< \brief move assignment is allowed 98 | //!@} 99 | }; 100 | 101 | //! \class FileDescriptor 102 | //! In addition, FileDescriptor tracks EOF state and calls to FileDescriptor::read and 103 | //! FileDescriptor::write, which EventLoop uses to detect busy loop conditions. 104 | //! 105 | //! For an example of FileDescriptor use, see the EventLoop class documentation. 106 | 107 | #endif // SPONGE_LIBSPONGE_FILE_DESCRIPTOR_HH 108 | -------------------------------------------------------------------------------- /libsponge/util/address.cc: -------------------------------------------------------------------------------- 1 | #include "address.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 | //! Converts Raw to `sockaddr *`. 15 | Address::Raw::operator sockaddr *() { return reinterpret_cast(&storage); } 16 | 17 | //! Converts Raw to `const sockaddr *`. 18 | Address::Raw::operator const sockaddr *() const { return reinterpret_cast(&storage); } 19 | 20 | //! \param[in] addr points to a raw socket address 21 | //! \param[in] size is `addr`'s length 22 | Address::Address(const sockaddr *addr, const size_t size) : _size(size) { 23 | // make sure proposed sockaddr can fit 24 | if (size > sizeof(_address.storage)) { 25 | throw runtime_error("invalid sockaddr size"); 26 | } 27 | 28 | memcpy(&_address.storage, addr, size); 29 | } 30 | 31 | //! Error category for getaddrinfo and getnameinfo failures. 32 | class gai_error_category : public error_category { 33 | public: 34 | //! The name of the wrapped error 35 | const char *name() const noexcept override { return "gai_error_category"; } 36 | //! \brief An error message 37 | //! \param[in] return_value the error return value from [getaddrinfo(3)](\ref man3::getaddrinfo) 38 | //! or [getnameinfo(3)](\ref man3::getnameinfo) 39 | string message(const int return_value) const noexcept override { return gai_strerror(return_value); } 40 | }; 41 | 42 | //! \param[in] node is the hostname or dotted-quad address 43 | //! \param[in] service is the service name or numeric string 44 | //! \param[in] hints are criteria for resolving the supplied name 45 | Address::Address(const string &node, const string &service, const addrinfo &hints) : _size() { 46 | // prepare for the answer 47 | addrinfo *resolved_address = nullptr; 48 | 49 | // look up the name or names 50 | const int gai_ret = getaddrinfo(node.c_str(), service.c_str(), &hints, &resolved_address); 51 | if (gai_ret != 0) { 52 | throw tagged_error(gai_error_category(), "getaddrinfo(" + node + ", " + service + ")", gai_ret); 53 | } 54 | 55 | // if success, should always have at least one entry 56 | if (resolved_address == nullptr) { 57 | throw runtime_error("getaddrinfo returned successfully but with no results"); 58 | } 59 | 60 | // put resolved_address in a wrapper so it will get freed if we have to throw an exception 61 | auto addrinfo_deleter = [](addrinfo *const x) { freeaddrinfo(x); }; 62 | unique_ptr wrapped_address(resolved_address, move(addrinfo_deleter)); 63 | 64 | // assign to our private members (making sure size fits) 65 | *this = Address(wrapped_address->ai_addr, wrapped_address->ai_addrlen); 66 | } 67 | 68 | //! \brief Build a `struct addrinfo` containing hints for [getaddrinfo(3)](\ref man3::getaddrinfo) 69 | //! \param[in] ai_flags is the value of the `ai_flags` field in the [struct addrinfo](\ref man3::getaddrinfo) 70 | //! \param[in] ai_family is the value of the `ai_family` field in the [struct addrinfo](\ref man3::getaddrinfo) 71 | static inline addrinfo make_hints(const int ai_flags, const int ai_family) { 72 | addrinfo hints{}; // value initialized to all zeros 73 | hints.ai_flags = ai_flags; 74 | hints.ai_family = ai_family; 75 | return hints; 76 | } 77 | 78 | //! \param[in] hostname to resolve 79 | //! \param[in] service name (from `/etc/services`, e.g., "http" is port 80) 80 | Address::Address(const string &hostname, const string &service) 81 | : Address(hostname, service, make_hints(AI_ALL, AF_INET)) {} 82 | 83 | //! \param[in] ip address as a dotted quad ("1.1.1.1") 84 | //! \param[in] port number 85 | Address::Address(const string &ip, const uint16_t port) 86 | // tell getaddrinfo that we don't want to resolve anything 87 | : Address(ip, ::to_string(port), make_hints(AI_NUMERICHOST | AI_NUMERICSERV, AF_INET)) {} 88 | 89 | // accessors 90 | pair Address::ip_port() const { 91 | array ip{}; 92 | array port{}; 93 | 94 | const int gni_ret = 95 | getnameinfo(_address, _size, ip.data(), ip.size(), port.data(), port.size(), NI_NUMERICHOST | NI_NUMERICSERV); 96 | if (gni_ret != 0) { 97 | throw tagged_error(gai_error_category(), "getnameinfo", gni_ret); 98 | } 99 | 100 | return {ip.data(), stoi(port.data())}; 101 | } 102 | 103 | string Address::to_string() const { 104 | const auto ip_and_port = ip_port(); 105 | return ip_and_port.first + ":" + ::to_string(ip_and_port.second); 106 | } 107 | 108 | uint32_t Address::ipv4_numeric() const { 109 | if (_address.storage.ss_family != AF_INET or _size != sizeof(sockaddr_in)) { 110 | throw runtime_error("ipv4_numeric called on non-IPV4 address"); 111 | } 112 | 113 | sockaddr_in ipv4_addr{}; 114 | memcpy(&ipv4_addr, &_address.storage, _size); 115 | 116 | return be32toh(ipv4_addr.sin_addr.s_addr); 117 | } 118 | 119 | Address Address::from_ipv4_numeric(const uint32_t ip_address) { 120 | sockaddr_in ipv4_addr{}; 121 | ipv4_addr.sin_family = AF_INET; 122 | ipv4_addr.sin_addr.s_addr = htobe32(ip_address); 123 | 124 | return {reinterpret_cast(&ipv4_addr), sizeof(ipv4_addr)}; 125 | } 126 | 127 | // equality 128 | bool Address::operator==(const Address &other) const { 129 | if (_size != other._size) { 130 | return false; 131 | } 132 | 133 | return 0 == memcmp(&_address, &other._address, _size); 134 | } 135 | -------------------------------------------------------------------------------- /libsponge/util/util.cc: -------------------------------------------------------------------------------- 1 | #include "util.hh" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | using namespace std; 12 | 13 | //! \returns the number of milliseconds since the program started 14 | uint64_t timestamp_ms() { 15 | using time_point = std::chrono::steady_clock::time_point; 16 | static const time_point program_start = std::chrono::steady_clock::now(); 17 | const time_point now = std::chrono::steady_clock::now(); 18 | return std::chrono::duration_cast(now - program_start).count(); 19 | } 20 | 21 | //! \param[in] attempt is the name of the syscall to try (for error reporting) 22 | //! \param[in] return_value is the return value of the syscall 23 | //! \param[in] errno_mask is any errno value that is acceptable, e.g., `EAGAIN` when reading a non-blocking fd 24 | //! \details This function works for any syscall that returns less than 0 on error and sets errno: 25 | //! 26 | //! For example, to wrap a call to [open(2)](\ref man2::open), you might say: 27 | //! 28 | //! ~~~{.cc} 29 | //! const int foo_fd = SystemCall("open", ::open("/tmp/foo", O_RDWR)); 30 | //! ~~~ 31 | //! 32 | //! If you don't have permission to open the file, SystemCall will throw a std::runtime_error. 33 | //! If you don't want to throw in that case, you can pass `EACCESS` in `errno_mask`: 34 | //! 35 | //! ~~~{.cc} 36 | //! // open a file, or print an error if permission was denied 37 | //! const int foo_fd = SystemCall("open", ::open("/tmp/foo", O_RDWR), EACCESS); 38 | //! if (foo_fd < 0) { 39 | //! std::cerr << "Access to /tmp/foo was denied." << std::endl; 40 | //! } 41 | //! ~~~ 42 | int SystemCall(const char *attempt, const int return_value, const int errno_mask) { 43 | if (return_value >= 0 || errno == errno_mask) { 44 | return return_value; 45 | } 46 | 47 | throw unix_error(attempt); 48 | } 49 | 50 | //! \param[in] attempt is the name of the syscall to try (for error reporting) 51 | //! \param[in] return_value is the return value of the syscall 52 | //! \param[in] errno_mask is any errno value that is acceptable, e.g., `EAGAIN` when reading a non-blocking fd 53 | //! \details see the other SystemCall() documentation for more details 54 | int SystemCall(const string &attempt, const int return_value, const int errno_mask) { 55 | return SystemCall(attempt.c_str(), return_value, errno_mask); 56 | } 57 | 58 | //! \details A properly seeded mt19937 generator takes a lot of entropy! 59 | //! 60 | //! This code borrows from the following: 61 | //! 62 | //! - https://kristerw.blogspot.com/2017/05/seeding-stdmt19937-random-number-engine.html 63 | //! - http://www.pcg-random.org/posts/cpps-random_device.html 64 | mt19937 get_random_generator() { 65 | auto rd = random_device(); 66 | array seed_data{}; 67 | generate(seed_data.begin(), seed_data.end(), [&] { return rd(); }); 68 | seed_seq seed(seed_data.begin(), seed_data.end()); 69 | return mt19937(seed); 70 | } 71 | 72 | //! \note This class returns the checksum in host byte order. 73 | //! See https://commandcenter.blogspot.com/2012/04/byte-order-fallacy.html for rationale 74 | //! \details This class can be used to either check or compute an Internet checksum 75 | //! (e.g., for an IP datagram header or a TCP segment). 76 | //! 77 | //! The Internet checksum is defined such that evaluating inet_cksum() on a TCP segment (IP datagram, etc) 78 | //! containing a correct checksum header will return zero. In other words, if you read a correct TCP segment 79 | //! off the wire and pass it untouched to inet_cksum(), the return value will be 0. 80 | //! 81 | //! Meanwhile, to compute the checksum for an outgoing TCP segment (IP datagram, etc.), you must first set 82 | //! the checksum header to zero, then call inet_cksum(), and finally set the checksum header to the return 83 | //! value. 84 | //! 85 | //! For more information, see the [Wikipedia page](https://en.wikipedia.org/wiki/IPv4_header_checksum) 86 | //! on the Internet checksum, and consult the [IP](\ref rfc::rfc791) and [TCP](\ref rfc::rfc793) RFCs. 87 | InternetChecksum::InternetChecksum(const uint32_t initial_sum) : _sum(initial_sum) {} 88 | 89 | void InternetChecksum::add(std::string_view data) { 90 | for (size_t i = 0; i < data.size(); i++) { 91 | uint16_t val = uint8_t(data[i]); 92 | if (not _parity) { 93 | val <<= 8; 94 | } 95 | _sum += val; 96 | _parity = !_parity; 97 | } 98 | } 99 | 100 | uint16_t InternetChecksum::value() const { 101 | uint32_t ret = _sum; 102 | 103 | while (ret > 0xffff) { 104 | ret = (ret >> 16) + (ret & 0xffff); 105 | } 106 | 107 | return ~ret; 108 | } 109 | 110 | //! \param[in] data is a pointer to the bytes to show 111 | //! \param[in] len is the number of bytes to show 112 | //! \param[in] indent is the number of spaces to indent 113 | void hexdump(const uint8_t *data, const size_t len, const size_t indent) { 114 | const auto flags(cout.flags()); 115 | const string indent_string(indent, ' '); 116 | int printed = 0; 117 | stringstream pchars(" "); 118 | cout << hex << setfill('0'); 119 | for (unsigned i = 0; i < len; ++i) { 120 | if ((printed & 0xf) == 0) { 121 | if (printed != 0) { 122 | cout << " " << pchars.str() << "\n"; 123 | pchars.str(" "); 124 | } 125 | cout << indent_string << setw(8) << printed << ": "; 126 | } else if ((printed & 1) == 0) { 127 | cout << ' '; 128 | } 129 | cout << setw(2) << +data[i]; 130 | pchars << (static_cast(isprint(data[i])) ? static_cast(data[i]) : '.'); 131 | printed += 1; 132 | } 133 | const int print_rem = (16 - (printed & 0xf)) % 16; // extra spacing before final chars 134 | cout << string(2 * print_rem + print_rem / 2 + 4, ' ') << pchars.str(); 135 | cout << '\n' << endl; 136 | cout.flags(flags); 137 | } 138 | 139 | //! \param[in] data is a pointer to the bytes to show 140 | //! \param[in] len is the number of bytes to show 141 | //! \param[in] indent is the number of spaces to indent 142 | void hexdump(const char *data, const size_t len, const size_t indent) { 143 | hexdump(reinterpret_cast(data), len, indent); 144 | } 145 | -------------------------------------------------------------------------------- /libsponge/util/eventloop.cc: -------------------------------------------------------------------------------- 1 | #include "eventloop.hh" 2 | 3 | #include "util.hh" 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | using namespace std; 12 | 13 | unsigned int EventLoop::Rule::service_count() const { 14 | return direction == Direction::In ? fd.read_count() : fd.write_count(); 15 | } 16 | 17 | //! \param[in] fd is the FileDescriptor to be polled 18 | //! \param[in] direction indicates whether to poll for reading (Direction::In) or writing (Direction::Out) 19 | //! \param[in] callback is called when `fd` is ready. 20 | //! \param[in] interest is called by EventLoop::wait_next_event. If it returns `true`, `fd` will 21 | //! be polled, otherwise `fd` will be ignored only for this execution of `wait_next_event. 22 | //! \param[in] cancel is called when the rule is cancelled (e.g. on hangup, EOF, or closure). 23 | void EventLoop::add_rule(const FileDescriptor &fd, 24 | const Direction direction, 25 | const CallbackT &callback, 26 | const InterestT &interest, 27 | const CallbackT &cancel) { 28 | _rules.push_back({fd.duplicate(), direction, callback, interest, cancel}); 29 | } 30 | 31 | //! \param[in] timeout_ms is the timeout value passed to [poll(2)](\ref man2::poll); `wait_next_event` 32 | //! returns Result::Timeout if no fd is ready after the timeout expires. 33 | //! \returns Eventloop::Result indicating success, timeout, or no more Rule objects to poll. 34 | //! 35 | //! For each Rule, this function first calls Rule::interest; if `true`, Rule::fd is added to the 36 | //! list of file descriptors to be polled for readability (if Rule::direction == Direction::In) or 37 | //! writability (if Rule::direction == Direction::Out) unless Rule::fd has reached EOF, in which case 38 | //! the Rule is canceled (i.e., deleted from EventLoop::_rules). 39 | //! 40 | //! Next, this function calls [poll(2)](\ref man2::poll) with timeout value `timeout_ms`. 41 | //! 42 | //! Then, for each ready file descriptor, this function calls Rule::callback. If fd reaches EOF or 43 | //! if the Rule was registered using EventLoop::add_cancelable_rule and Rule::callback returns true, 44 | //! this Rule is canceled. 45 | //! 46 | //! If an error occurs during polling, this function throws a std::runtime_error. 47 | //! 48 | //! If a [signal(7)](\ref man7::signal) was caught during polling or if EventLoop::_rules becomes empty, 49 | //! this function returns Result::Exit. 50 | //! 51 | //! If a timeout occurred while polling (i.e., no fd became ready), this function returns Result::Timeout. 52 | //! 53 | //! Otherwise, this function returns Result::Success. 54 | //! 55 | //! \b IMPORTANT: every call to Rule::callback must read from or write to Rule::fd, or the `interest` 56 | //! callback must stop returning true after the callback completes. 57 | //! If none of these conditions occur, EventLoop::wait_next_event will throw std::runtime_error. This is 58 | //! because [poll(2)](\ref man2::poll) is level triggered, so failing to act on a ready file descriptor 59 | //! will result in a busy loop (poll returns on a ready file descriptor; file descriptor is not read or 60 | //! written, so it is still ready; the next call to poll will immediately return). 61 | EventLoop::Result EventLoop::wait_next_event(const int timeout_ms) { 62 | vector pollfds{}; 63 | pollfds.reserve(_rules.size()); 64 | bool something_to_poll = false; 65 | 66 | // set up the pollfd for each rule 67 | for (auto it = _rules.cbegin(); it != _rules.cend();) { // NOTE: it gets erased or incremented in loop body 68 | const auto &this_rule = *it; 69 | if (this_rule.direction == Direction::In && this_rule.fd.eof()) { 70 | // no more reading on this rule, it's reached eof 71 | this_rule.cancel(); 72 | it = _rules.erase(it); 73 | continue; 74 | } 75 | 76 | if (this_rule.fd.closed()) { 77 | this_rule.cancel(); 78 | it = _rules.erase(it); 79 | continue; 80 | } 81 | 82 | if (this_rule.interest()) { 83 | pollfds.push_back({this_rule.fd.fd_num(), static_cast(this_rule.direction), 0}); 84 | something_to_poll = true; 85 | } else { 86 | pollfds.push_back({this_rule.fd.fd_num(), 0, 0}); // placeholder --- we still want errors 87 | } 88 | ++it; 89 | } 90 | 91 | // quit if there is nothing left to poll 92 | if (not something_to_poll) { 93 | return Result::Exit; 94 | } 95 | 96 | // call poll -- wait until one of the fds satisfies one of the rules (writeable/readable) 97 | try { 98 | if (0 == SystemCall("poll", ::poll(pollfds.data(), pollfds.size(), timeout_ms))) { 99 | return Result::Timeout; 100 | } 101 | } catch (unix_error const &e) { 102 | if (e.code().value() == EINTR) { 103 | return Result::Exit; 104 | } 105 | } 106 | 107 | // go through the poll results 108 | 109 | for (auto [it, idx] = make_pair(_rules.begin(), size_t(0)); it != _rules.end(); ++idx) { 110 | const auto &this_pollfd = pollfds[idx]; 111 | 112 | const auto poll_error = static_cast(this_pollfd.revents & (POLLERR | POLLNVAL)); 113 | if (poll_error) { 114 | throw runtime_error("EventLoop: error on polled file descriptor"); 115 | } 116 | 117 | const auto &this_rule = *it; 118 | const auto poll_ready = static_cast(this_pollfd.revents & this_pollfd.events); 119 | const auto poll_hup = static_cast(this_pollfd.revents & POLLHUP); 120 | if (poll_hup && this_pollfd.events && !poll_ready) { 121 | // if we asked for the status, and the _only_ condition was a hangup, this FD is defunct: 122 | // - if it was POLLIN and nothing is readable, no more will ever be readable 123 | // - if it was POLLOUT, it will not be writable again 124 | this_rule.cancel(); 125 | it = _rules.erase(it); 126 | continue; 127 | } 128 | 129 | if (poll_ready) { 130 | // we only want to call callback if revents includes the event we asked for 131 | const auto count_before = this_rule.service_count(); 132 | this_rule.callback(); 133 | 134 | // only check for busy wait if we're not canceling or exiting 135 | if (count_before == this_rule.service_count() and this_rule.interest()) { 136 | throw runtime_error( 137 | "EventLoop: busy wait detected: callback did not read/write fd and is still interested"); 138 | } 139 | } 140 | 141 | ++it; // if we got here, it means we didn't call _rules.erase() 142 | } 143 | 144 | return Result::Success; 145 | } 146 | -------------------------------------------------------------------------------- /libsponge/util/socket.cc: -------------------------------------------------------------------------------- 1 | #include "socket.hh" 2 | 3 | #include "util.hh" 4 | 5 | #include 6 | #include 7 | #include 8 | 9 | using namespace std; 10 | 11 | // default constructor for socket of (subclassed) domain and type 12 | //! \param[in] domain is as described in [socket(7)](\ref man7::socket), probably `AF_INET` or `AF_UNIX` 13 | //! \param[in] type is as described in [socket(7)](\ref man7::socket) 14 | Socket::Socket(const int domain, const int type) : FileDescriptor(SystemCall("socket", socket(domain, type, 0))) {} 15 | 16 | // construct from file descriptor 17 | //! \param[in] fd is the FileDescriptor from which to construct 18 | //! \param[in] domain is `fd`'s domain; throws std::runtime_error if wrong value is supplied 19 | //! \param[in] type is `fd`'s type; throws std::runtime_error if wrong value is supplied 20 | Socket::Socket(FileDescriptor &&fd, const int domain, const int type) : FileDescriptor(move(fd)) { 21 | int actual_value; 22 | socklen_t len; 23 | 24 | // verify domain 25 | len = sizeof(actual_value); 26 | SystemCall("getsockopt", getsockopt(fd_num(), SOL_SOCKET, SO_DOMAIN, &actual_value, &len)); 27 | if ((len != sizeof(actual_value)) or (actual_value != domain)) { 28 | throw runtime_error("socket domain mismatch"); 29 | } 30 | 31 | // verify type 32 | len = sizeof(actual_value); 33 | SystemCall("getsockopt", getsockopt(fd_num(), SOL_SOCKET, SO_TYPE, &actual_value, &len)); 34 | if ((len != sizeof(actual_value)) or (actual_value != type)) { 35 | throw runtime_error("socket type mismatch"); 36 | } 37 | } 38 | 39 | // get the local or peer address the socket is connected to 40 | //! \param[in] name_of_function is the function to call (string passed to SystemCall()) 41 | //! \param[in] function is a pointer to the function 42 | //! \returns the requested Address 43 | Address Socket::get_address(const string &name_of_function, 44 | const function &function) const { 45 | Address::Raw address; 46 | socklen_t size = sizeof(address); 47 | 48 | SystemCall(name_of_function, function(fd_num(), address, &size)); 49 | 50 | return {address, size}; 51 | } 52 | 53 | //! \returns the local Address of the socket 54 | Address Socket::local_address() const { return get_address("getsockname", getsockname); } 55 | 56 | //! \returns the socket's peer's Address 57 | Address Socket::peer_address() const { return get_address("getpeername", getpeername); } 58 | 59 | // bind socket to a specified local address (usually to listen/accept) 60 | //! \param[in] address is a local Address to bind 61 | void Socket::bind(const Address &address) { SystemCall("bind", ::bind(fd_num(), address, address.size())); } 62 | 63 | // connect socket to a specified peer address 64 | //! \param[in] address is the peer's Address 65 | void Socket::connect(const Address &address) { SystemCall("connect", ::connect(fd_num(), address, address.size())); } 66 | 67 | // shut down a socket in the specified way 68 | //! \param[in] how can be `SHDN_RD`, `SHDN_WR`, or `SHDN_RDWR`; see [shutdown(2)](\ref man2::shutdown) 69 | void Socket::shutdown(const int how) { 70 | SystemCall("shutdown", ::shutdown(fd_num(), how)); 71 | switch (how) { 72 | case SHUT_RD: 73 | register_read(); 74 | break; 75 | case SHUT_WR: 76 | register_write(); 77 | break; 78 | case SHUT_RDWR: 79 | register_read(); 80 | register_write(); 81 | break; 82 | default: 83 | throw runtime_error("Socket::shutdown() called with invalid `how`"); 84 | } 85 | } 86 | 87 | //! \note If `mtu` is too small to hold the received datagram, this method throws a std::runtime_error 88 | void UDPSocket::recv(received_datagram &datagram, const size_t mtu) { 89 | // receive source address and payload 90 | Address::Raw datagram_source_address; 91 | datagram.payload.resize(mtu); 92 | 93 | socklen_t fromlen = sizeof(datagram_source_address); 94 | 95 | const ssize_t recv_len = SystemCall( 96 | "recvfrom", 97 | ::recvfrom( 98 | fd_num(), datagram.payload.data(), datagram.payload.size(), MSG_TRUNC, datagram_source_address, &fromlen)); 99 | 100 | if (recv_len > ssize_t(mtu)) { 101 | throw runtime_error("recvfrom (oversized datagram)"); 102 | } 103 | 104 | register_read(); 105 | datagram.source_address = {datagram_source_address, fromlen}; 106 | datagram.payload.resize(recv_len); 107 | } 108 | 109 | UDPSocket::received_datagram UDPSocket::recv(const size_t mtu) { 110 | received_datagram ret{{nullptr, 0}, ""}; 111 | recv(ret, mtu); 112 | return ret; 113 | } 114 | 115 | void sendmsg_helper(const int fd_num, 116 | const sockaddr *destination_address, 117 | const socklen_t destination_address_len, 118 | const BufferViewList &payload) { 119 | auto iovecs = payload.as_iovecs(); 120 | 121 | msghdr message{}; 122 | message.msg_name = const_cast(destination_address); 123 | message.msg_namelen = destination_address_len; 124 | message.msg_iov = iovecs.data(); 125 | message.msg_iovlen = iovecs.size(); 126 | 127 | const ssize_t bytes_sent = SystemCall("sendmsg", ::sendmsg(fd_num, &message, 0)); 128 | 129 | if (size_t(bytes_sent) != payload.size()) { 130 | throw runtime_error("datagram payload too big for sendmsg()"); 131 | } 132 | } 133 | 134 | void UDPSocket::sendto(const Address &destination, const BufferViewList &payload) { 135 | sendmsg_helper(fd_num(), destination, destination.size(), payload); 136 | register_write(); 137 | } 138 | 139 | void UDPSocket::send(const BufferViewList &payload) { 140 | sendmsg_helper(fd_num(), nullptr, 0, payload); 141 | register_write(); 142 | } 143 | 144 | // mark the socket as listening for incoming connections 145 | //! \param[in] backlog is the number of waiting connections to queue (see [listen(2)](\ref man2::listen)) 146 | void TCPSocket::listen(const int backlog) { SystemCall("listen", ::listen(fd_num(), backlog)); } 147 | 148 | // accept a new incoming connection 149 | //! \returns a new TCPSocket connected to the peer. 150 | //! \note This function blocks until a new connection is available 151 | TCPSocket TCPSocket::accept() { 152 | register_read(); 153 | return TCPSocket(FileDescriptor(SystemCall("accept", ::accept(fd_num(), nullptr, nullptr)))); 154 | } 155 | 156 | // set socket option 157 | //! \param[in] level The protocol level at which the argument resides 158 | //! \param[in] option A single option to set 159 | //! \param[in] option_value The value to set 160 | //! \details See [setsockopt(2)](\ref man2::setsockopt) for details. 161 | template 162 | void Socket::setsockopt(const int level, const int option, const option_type &option_value) { 163 | SystemCall("setsockopt", ::setsockopt(fd_num(), level, option, &option_value, sizeof(option_value))); 164 | } 165 | 166 | // allow local address to be reused sooner, at the cost of some robustness 167 | //! \note Using `SO_REUSEADDR` may reduce the robustness of your application 168 | void Socket::set_reuseaddr() { setsockopt(SOL_SOCKET, SO_REUSEADDR, int(true)); } 169 | -------------------------------------------------------------------------------- /tests/byte_stream_test_harness.cc: -------------------------------------------------------------------------------- 1 | #include "byte_stream_test_harness.hh" 2 | 3 | #include 4 | #include 5 | 6 | using namespace std; 7 | 8 | // ByteStreamTestStep 9 | 10 | ByteStreamTestStep::operator std::string() const { return "ByteStreamTestStep"; } 11 | 12 | void ByteStreamTestStep::execute(ByteStream &) const {} 13 | 14 | ByteStreamTestStep::~ByteStreamTestStep() {} 15 | 16 | // ByteStreamExpectationViolation 17 | 18 | ByteStreamExpectationViolation::ByteStreamExpectationViolation(const std::string &msg) : std::runtime_error(msg) {} 19 | 20 | template 21 | ByteStreamExpectationViolation ByteStreamExpectationViolation::property(const std::string &property_name, 22 | const T &expected, 23 | const T &actual) { 24 | return ByteStreamExpectationViolation("The ByteStream should have had " + property_name + " equal to " + 25 | to_string(expected) + " but instead it was " + to_string(actual)); 26 | } 27 | 28 | // ByteStreamExpectation 29 | 30 | ByteStreamExpectation::operator std::string() const { return "Expectation: " + description(); } 31 | 32 | std::string ByteStreamExpectation::description() const { return "description missing"; } 33 | 34 | void ByteStreamExpectation::execute(ByteStream &) const {} 35 | 36 | ByteStreamExpectation::~ByteStreamExpectation() {} 37 | 38 | // ByteStreamAction 39 | 40 | ByteStreamAction::operator std::string() const { return " Action: " + description(); } 41 | 42 | std::string ByteStreamAction::description() const { return "description missing"; } 43 | 44 | void ByteStreamAction::execute(ByteStream &) const {} 45 | 46 | ByteStreamAction::~ByteStreamAction() {} 47 | 48 | ByteStreamTestHarness::ByteStreamTestHarness(const std::string &test_name, const size_t capacity) 49 | : _test_name(test_name), _byte_stream(capacity) { 50 | std::ostringstream ss; 51 | ss << "Initialized with (" 52 | << "capacity=" << capacity << ")"; 53 | _steps_executed.emplace_back(ss.str()); 54 | } 55 | 56 | void ByteStreamTestHarness::execute(const ByteStreamTestStep &step) { 57 | try { 58 | step.execute(_byte_stream); 59 | _steps_executed.emplace_back(step); 60 | } catch (const ByteStreamExpectationViolation &e) { 61 | std::cerr << "Test Failure on expectation:\n\t" << std::string(step); 62 | std::cerr << "\n\nFailure message:\n\t" << e.what(); 63 | std::cerr << "\n\nList of steps that executed successfully:"; 64 | for (const std::string &s : _steps_executed) { 65 | std::cerr << "\n\t" << s; 66 | } 67 | std::cerr << std::endl << std::endl; 68 | throw ByteStreamExpectationViolation("The test \"" + _test_name + "\" failed"); 69 | } catch (const exception &e) { 70 | std::cerr << "Test Failure on expectation:\n\t" << std::string(step); 71 | std::cerr << "\n\nException:\n\t" << e.what(); 72 | std::cerr << "\n\nList of steps that executed successfully:"; 73 | for (const std::string &s : _steps_executed) { 74 | std::cerr << "\n\t" << s; 75 | } 76 | std::cerr << std::endl << std::endl; 77 | throw ByteStreamExpectationViolation("The test \"" + _test_name + 78 | "\" caused your implementation to throw an exception!"); 79 | } 80 | } 81 | 82 | // EndInput 83 | std::string EndInput::description() const { return "end input"; } 84 | void EndInput::execute(ByteStream &bs) const { bs.end_input(); } 85 | 86 | // Write 87 | Write::Write(const std::string &data) : _data(data) {} 88 | Write &Write::with_bytes_written(const size_t bytes_written) { 89 | _bytes_written = bytes_written; 90 | return *this; 91 | } 92 | std::string Write::description() const { return "write \"" + _data + "\" to the stream"; } 93 | void Write::execute(ByteStream &bs) const { 94 | auto bytes_written = bs.write(_data); 95 | if (_bytes_written and bytes_written != _bytes_written.value()) { 96 | throw ByteStreamExpectationViolation::property("bytes_written", _bytes_written.value(), bytes_written); 97 | } 98 | } 99 | 100 | // Pop 101 | Pop::Pop(const size_t len) : _len(len) {} 102 | std::string Pop::description() const { return "pop " + to_string(_len); } 103 | void Pop::execute(ByteStream &bs) const { bs.pop_output(_len); } 104 | 105 | // InputEnded 106 | InputEnded::InputEnded(const bool input_ended) : _input_ended(input_ended) {} 107 | std::string InputEnded::description() const { return "input_ended: " + to_string(_input_ended); } 108 | void InputEnded::execute(ByteStream &bs) const { 109 | auto input_ended = bs.input_ended(); 110 | if (input_ended != _input_ended) { 111 | throw ByteStreamExpectationViolation::property("input_ended", _input_ended, input_ended); 112 | } 113 | } 114 | 115 | // BufferEmpty 116 | BufferEmpty::BufferEmpty(const bool buffer_empty) : _buffer_empty(buffer_empty) {} 117 | std::string BufferEmpty::description() const { return "buffer_empty: " + to_string(_buffer_empty); } 118 | void BufferEmpty::execute(ByteStream &bs) const { 119 | auto buffer_empty = bs.buffer_empty(); 120 | if (buffer_empty != _buffer_empty) { 121 | throw ByteStreamExpectationViolation::property("buffer_empty", _buffer_empty, buffer_empty); 122 | } 123 | } 124 | 125 | // Eof 126 | Eof::Eof(const bool eof) : _eof(eof) {} 127 | std::string Eof::description() const { return "eof: " + to_string(_eof); } 128 | void Eof::execute(ByteStream &bs) const { 129 | auto eof = bs.eof(); 130 | if (eof != _eof) { 131 | throw ByteStreamExpectationViolation::property("eof", _eof, eof); 132 | } 133 | } 134 | 135 | // BufferSize 136 | BufferSize::BufferSize(const size_t buffer_size) : _buffer_size(buffer_size) {} 137 | std::string BufferSize::description() const { return "buffer_size: " + to_string(_buffer_size); } 138 | void BufferSize::execute(ByteStream &bs) const { 139 | auto buffer_size = bs.buffer_size(); 140 | if (buffer_size != _buffer_size) { 141 | throw ByteStreamExpectationViolation::property("buffer_size", _buffer_size, buffer_size); 142 | } 143 | } 144 | 145 | // RemainingCapacity 146 | RemainingCapacity::RemainingCapacity(const size_t remaining_capacity) : _remaining_capacity(remaining_capacity) {} 147 | std::string RemainingCapacity::description() const { return "remaining_capacity: " + to_string(_remaining_capacity); } 148 | void RemainingCapacity::execute(ByteStream &bs) const { 149 | auto remaining_capacity = bs.remaining_capacity(); 150 | if (remaining_capacity != _remaining_capacity) { 151 | throw ByteStreamExpectationViolation::property("remaining_capacity", _remaining_capacity, remaining_capacity); 152 | } 153 | } 154 | 155 | // BytesWritten 156 | BytesWritten::BytesWritten(const size_t bytes_written) : _bytes_written(bytes_written) {} 157 | std::string BytesWritten::description() const { return "bytes_written: " + to_string(_bytes_written); } 158 | void BytesWritten::execute(ByteStream &bs) const { 159 | auto bytes_written = bs.bytes_written(); 160 | if (bytes_written != _bytes_written) { 161 | throw ByteStreamExpectationViolation::property("bytes_written", _bytes_written, bytes_written); 162 | } 163 | } 164 | 165 | // BytesRead 166 | BytesRead::BytesRead(const size_t bytes_read) : _bytes_read(bytes_read) {} 167 | std::string BytesRead::description() const { return "bytes_read: " + to_string(_bytes_read); } 168 | void BytesRead::execute(ByteStream &bs) const { 169 | auto bytes_read = bs.bytes_read(); 170 | if (bytes_read != _bytes_read) { 171 | throw ByteStreamExpectationViolation::property("bytes_read", _bytes_read, bytes_read); 172 | } 173 | } 174 | 175 | // Peek 176 | Peek::Peek(const std::string &output) : _output(output) {} 177 | std::string Peek::description() const { return "\"" + _output + "\" at the front of the stream"; } 178 | void Peek::execute(ByteStream &bs) const { 179 | auto output = bs.peek_output(_output.size()); 180 | if (output != _output) { 181 | throw ByteStreamExpectationViolation("Expected \"" + _output + "\" at the front of the stream, but found \"" + 182 | output + "\""); 183 | } 184 | } 185 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # CS144 Lab Assignments - 手写TCP - LAB0 2 | 3 | > CS 144: Introduction to Computer Networking, Fall 2020 4 | > https://cs144.github.io/ 5 | > 6 | > My Repo 7 | > https://github.com/wine99/cs144-20fa 8 | > 9 | 10 | LAB0 在 master 分支,LAB1 - 7 在对应名字的分支。 11 | 12 | ## webget 13 | 14 | ### What is webget? 15 | 16 | 参照 lab0.pdf 2.1 Fetch a Web page, 如下所示。 17 | 18 | [![sbYMvD.png](https://s3.ax1x.com/2021/01/24/sbYMvD.png)](https://imgchr.com/i/sbYMvD) 19 | 20 | 其效果等同于 21 | 22 | [![sbTh4A.png](https://s3.ax1x.com/2021/01/24/sbTh4A.png)](https://imgchr.com/i/sbTh4A) 23 | 24 | ### Write Webget 25 | 26 | 参考 API 文档 https://cs144.github.io/doc/lab0/class_t_c_p_socket.html。 27 | 28 | 注意 lab0.pdf 中的几点提示: 29 | 30 | + Please note that in HTTP, each line must be ended with “\r\n” (it’s not sufficient to use just “\n” or endl). 31 | + Don’t forget to include the “Connection: close” line in your client’s request. This tells the server that it shouldn’t wait around for your client to send any more requests after this one. Instead, the server will send one reply and then will immediately end its outgoing bytestream (the one from the server’s socket to your socket). You’ll discover that your incoming byte stream has ended because your socket will reach “EOF” (end of file) when you have read the entire byte stream coming from the server. That’s how your client will know that the server has finished its reply. 32 | + Make sure to read and print all the output from the server until the socket reaches “EOF” (end of file) — a single call to read is not enough. 33 | 34 | 在 GET 请求中写明 `Connection: close` 可以让服务器马上进入连接释放的过程(发一个 FIN 过来),我们在发送完 GET 同样需要 `shutdown(SHUT_WR)`,进入连接释放过程(发一个 FIN 过去)。(不太理解的话,写完 Lab4,就能理解了。) 35 | 36 | ```cpp 37 | void get_URL(const string &host, const string &path) { 38 | // Your code here. 39 | 40 | // You will need to connect to the "http" service on 41 | // the computer whose name is in the "host" string, 42 | // then request the URL path given in the "path" string. 43 | 44 | // Then you'll need to print out everything the server sends back, 45 | // (not just one call to read() -- everything) until you reach 46 | // the "eof" (end of file). 47 | 48 | // cerr << "Function called: get_URL(" << host << ", " << path << ").\n"; 49 | // cerr << "Warning: get_URL() has not been implemented yet.\n"; 50 | 51 | TCPSocket sock1; 52 | sock1.connect(Address(host, "http")); 53 | sock1.write("GET " + path + " HTTP/1.1\r\n" + "Host: " + host + "\r\n" + "Connection: close\r\n\r\n"); 54 | while (!sock1.eof()) { 55 | cout << sock1.read(); 56 | } 57 | sock1.shutdown(SHUT_WR); 58 | } 59 | ``` 60 | 61 | ## An in-memory reliable byte stream 62 | 63 | 注意下面代码中的 `buffer_size` 为缓冲的内容大小(等于 `_stream.size()`),`capacity` 才是缓冲的大小。只有当 `input_ended` 为真并且 `buffer_size` 为 0 时,才是 EOF。 64 | 65 | `byte_stream.hh`: 66 | 67 | ```cpp 68 | class ByteStream { 69 | private: 70 | // Your code here -- add private members as necessary. 71 | 72 | // Hint: This doesn't need to be a sophisticated data structure at 73 | // all, but if any of your tests are taking longer than a second, 74 | // that's a sign that you probably want to keep exploring 75 | // different approaches. 76 | 77 | bool _error = false; //!< Flag indicating that the stream suffered an error. 78 | bool _input_ended = false; 79 | size_t _capacity; 80 | size_t _buffer_size = 0; 81 | size_t _bytes_written = 0; 82 | size_t _bytes_read = 0; 83 | std::list _stream{}; 84 | 85 | public: 86 | ``` 87 | 88 | `byte_stream.cc`: 89 | 90 | ```cpp 91 | #include "byte_stream.hh" 92 | 93 | #include 94 | 95 | // Dummy implementation of a flow-controlled in-memory byte stream. 96 | 97 | // For Lab 0, please replace with a real implementation that passes the 98 | // automated checks run by `make check_lab0`. 99 | 100 | // You will need to add private members to the class declaration in `byte_stream.hh` 101 | 102 | template 103 | void DUMMY_CODE(Targs &&... /* unused */) {} 104 | 105 | using namespace std; 106 | 107 | ByteStream::ByteStream(const size_t capacity) : _capacity(capacity) {} 108 | 109 | size_t ByteStream::write(const string &data) { 110 | size_t write_count = 0; 111 | for (const char c : data) { 112 | // not very efficient to do conditional in loop 113 | if (_capacity - _buffer_size <= 0) 114 | break; 115 | else { 116 | _stream.push_back(c); 117 | ++_buffer_size; 118 | ++_bytes_written; 119 | ++write_count; 120 | } 121 | } 122 | 123 | return write_count; 124 | } 125 | 126 | //! \param[in] len bytes will be copied from the output side of the buffer 127 | string ByteStream::peek_output(const size_t len) const { 128 | const size_t peek_length = len > _buffer_size ? _buffer_size : len; 129 | list::const_iterator it = _stream.begin(); 130 | advance(it, peek_length); 131 | return string(_stream.begin(), it); 132 | } 133 | 134 | //! \param[in] len bytes will be removed from the output side of the buffer 135 | void ByteStream::pop_output(const size_t len) { 136 | size_t pop_length = len > _buffer_size ? _buffer_size : len; 137 | _bytes_read += pop_length; 138 | _buffer_size -= pop_length; 139 | while (pop_length--) 140 | _stream.pop_front(); 141 | } 142 | 143 | //! Read (i.e., copy and then pop) the next "len" bytes of the stream 144 | //! \param[in] len bytes will be popped and returned 145 | //! \returns a string 146 | std::string ByteStream::read(const size_t len) { 147 | const string result = peek_output(len); 148 | pop_output(len); 149 | return result; 150 | } 151 | 152 | void ByteStream::end_input() { _input_ended = true; } 153 | 154 | bool ByteStream::input_ended() const { return _input_ended; } 155 | 156 | size_t ByteStream::buffer_size() const { return _buffer_size; } 157 | 158 | bool ByteStream::buffer_empty() const { return _stream.size() == 0; } 159 | 160 | bool ByteStream::eof() const { return _input_ended && buffer_empty(); } 161 | 162 | size_t ByteStream::bytes_written() const { return _bytes_written; } 163 | 164 | size_t ByteStream::bytes_read() const { return _bytes_read; } 165 | 166 | size_t ByteStream::remaining_capacity() const { return _capacity - _buffer_size; } 167 | ``` 168 | 169 | --- 170 | 171 | BELOW IS THE ORIGINAL README OF THIS LAB. 172 | 173 | --- 174 | 175 | For build prereqs, see [the CS144 VM setup instructions](https://web.stanford.edu/class/cs144/vm_howto). 176 | 177 | ## Sponge quickstart 178 | 179 | To set up your build directory: 180 | 181 | $ mkdir -p /build 182 | $ cd /build 183 | $ cmake .. 184 | 185 | **Note:** all further commands listed below should be run from the `build` dir. 186 | 187 | To build: 188 | 189 | $ make 190 | 191 | You can use the `-j` switch to build in parallel, e.g., 192 | 193 | $ make -j$(nproc) 194 | 195 | To test (after building; make sure you've got the [build prereqs](https://web.stanford.edu/class/cs144/vm_howto) installed!) 196 | 197 | $ make check_lab0 198 | 199 | or 200 | 201 | $ make check_lab1 202 | 203 | etc. 204 | 205 | The first time you run a `make check`, it may run `sudo` to configure two 206 | [TUN](https://www.kernel.org/doc/Documentation/networking/tuntap.txt) devices for use during testing. 207 | 208 | ### build options 209 | 210 | You can specify a different compiler when you run cmake: 211 | 212 | $ CC=clang CXX=clang++ cmake .. 213 | 214 | You can also specify `CLANG_TIDY=` or `CLANG_FORMAT=` (see "other useful targets", below). 215 | 216 | Sponge's build system supports several different build targets. By default, cmake chooses the `Release` 217 | target, which enables the usual optimizations. The `Debug` target enables debugging and reduces the 218 | level of optimization. To choose the `Debug` target: 219 | 220 | $ cmake .. -DCMAKE_BUILD_TYPE=Debug 221 | 222 | The following targets are supported: 223 | 224 | - `Release` - optimizations 225 | - `Debug` - debug symbols and `-Og` 226 | - `RelASan` - release build with [ASan](https://en.wikipedia.org/wiki/AddressSanitizer) and 227 | [UBSan](https://developers.redhat.com/blog/2014/10/16/gcc-undefined-behavior-sanitizer-ubsan/) 228 | - `RelTSan` - release build with 229 | [ThreadSan](https://developer.mozilla.org/en-US/docs/Mozilla/Projects/Thread_Sanitizer) 230 | - `DebugASan` - debug build with ASan and UBSan 231 | - `DebugTSan` - debug build with ThreadSan 232 | 233 | Of course, you can combine all of the above, e.g., 234 | 235 | $ CLANG_TIDY=clang-tidy-6.0 CXX=clang++-6.0 .. -DCMAKE_BUILD_TYPE=Debug 236 | 237 | **Note:** if you want to change `CC`, `CXX`, `CLANG_TIDY`, or `CLANG_FORMAT`, you need to remove 238 | `build/CMakeCache.txt` and re-run cmake. (This isn't necessary for `CMAKE_BUILD_TYPE`.) 239 | 240 | ### other useful targets 241 | 242 | To generate documentation (you'll need `doxygen`; output will be in `build/doc/`): 243 | 244 | $ make doc 245 | 246 | To lint (you'll need `clang-tidy`): 247 | 248 | $ make -j$(nproc) tidy 249 | 250 | To run cppcheck (you'll need `cppcheck`): 251 | 252 | $ make cppcheck 253 | 254 | To format (you'll need `clang-format`): 255 | 256 | $ make format 257 | 258 | To see all available targets, 259 | 260 | $ make help 261 | -------------------------------------------------------------------------------- /etc/tests.cmake: -------------------------------------------------------------------------------- 1 | enable_testing () 2 | 3 | set (LOSS_RATE 0.1) 4 | 5 | add_test(NAME t_wrapping_ints_cmp COMMAND wrapping_integers_cmp) 6 | add_test(NAME t_wrapping_ints_unwrap COMMAND wrapping_integers_unwrap) 7 | add_test(NAME t_wrapping_ints_wrap COMMAND wrapping_integers_wrap) 8 | 9 | add_test(NAME t_recv_connect COMMAND recv_connect) 10 | add_test(NAME t_recv_transmit COMMAND recv_transmit) 11 | add_test(NAME t_recv_window COMMAND recv_window) 12 | add_test(NAME t_recv_reorder COMMAND recv_reorder) 13 | add_test(NAME t_recv_close COMMAND recv_close) 14 | 15 | add_test(NAME t_send_connect COMMAND send_connect) 16 | add_test(NAME t_send_transmit COMMAND send_transmit) 17 | add_test(NAME t_send_retx COMMAND send_retx) 18 | add_test(NAME t_send_window COMMAND send_window) 19 | add_test(NAME t_send_ack COMMAND send_ack) 20 | add_test(NAME t_send_close COMMAND send_close) 21 | 22 | add_test(NAME t_strm_reassem_cap COMMAND fsm_stream_reassembler_cap) 23 | add_test(NAME t_strm_reassem_single COMMAND fsm_stream_reassembler_single) 24 | add_test(NAME t_strm_reassem_seq COMMAND fsm_stream_reassembler_seq) 25 | add_test(NAME t_strm_reassem_dup COMMAND fsm_stream_reassembler_dup) 26 | add_test(NAME t_strm_reassem_holes COMMAND fsm_stream_reassembler_holes) 27 | add_test(NAME t_strm_reassem_many COMMAND fsm_stream_reassembler_many) 28 | add_test(NAME t_strm_reassem_overlapping COMMAND fsm_stream_reassembler_overlapping) 29 | add_test(NAME t_strm_reassem_win COMMAND fsm_stream_reassembler_win) 30 | 31 | add_test(NAME t_byte_stream_construction COMMAND byte_stream_construction) 32 | add_test(NAME t_byte_stream_one_write COMMAND byte_stream_one_write) 33 | add_test(NAME t_byte_stream_two_writes COMMAND byte_stream_two_writes) 34 | add_test(NAME t_byte_stream_capacity COMMAND byte_stream_capacity) 35 | add_test(NAME t_byte_stream_many_writes COMMAND byte_stream_many_writes) 36 | 37 | add_test(NAME t_webget COMMAND "${PROJECT_SOURCE_DIR}/tests/webget_t.sh") 38 | 39 | add_test(NAME arp_network_interface COMMAND net_interface) 40 | 41 | add_test(NAME router_test COMMAND network_simulator) 42 | 43 | add_test(NAME t_tcp_parser COMMAND tcp_parser "${PROJECT_SOURCE_DIR}/tests/ipv4_parser.data") 44 | add_test(NAME t_ipv4_parser COMMAND ipv4_parser "${PROJECT_SOURCE_DIR}/tests/ipv4_parser.data") 45 | add_test(NAME t_active_close COMMAND fsm_active_close) 46 | add_test(NAME t_passive_close COMMAND fsm_passive_close) 47 | add_test(NAME ec_ack_rst COMMAND fsm_ack_rst) 48 | add_test(NAME t_ack_rst COMMAND fsm_ack_rst_relaxed) 49 | add_test(NAME ec_ack_rst_win COMMAND fsm_ack_rst_win) 50 | add_test(NAME t_ack_rst_win COMMAND fsm_ack_rst_win_relaxed) 51 | add_test(NAME ec_connect COMMAND fsm_connect) 52 | add_test(NAME t_connect COMMAND fsm_connect_relaxed) 53 | add_test(NAME ec_listen COMMAND fsm_listen) 54 | add_test(NAME t_listen COMMAND fsm_listen_relaxed) 55 | add_test(NAME t_winsize COMMAND fsm_winsize) 56 | add_test(NAME ec_retx COMMAND fsm_retx) 57 | add_test(NAME t_retx COMMAND fsm_retx_relaxed) 58 | add_test(NAME t_retx_win COMMAND fsm_retx_win) 59 | add_test(NAME t_loopback COMMAND fsm_loopback) 60 | add_test(NAME t_loopback_win COMMAND fsm_loopback_win) 61 | add_test(NAME t_reorder COMMAND fsm_reorder) 62 | 63 | add_test(NAME t_address_dt COMMAND address_dt) 64 | add_test(NAME t_parser_dt COMMAND parser_dt) 65 | add_test(NAME t_socket_dt COMMAND socket_dt) 66 | 67 | add_test(NAME t_udp_client_send COMMAND "${PROJECT_SOURCE_DIR}/txrx.sh" -ucS) 68 | add_test(NAME t_udp_server_send COMMAND "${PROJECT_SOURCE_DIR}/txrx.sh" -usS) 69 | add_test(NAME t_udp_client_recv COMMAND "${PROJECT_SOURCE_DIR}/txrx.sh" -ucR) 70 | add_test(NAME t_udp_server_recv COMMAND "${PROJECT_SOURCE_DIR}/txrx.sh" -usR) 71 | add_test(NAME t_udp_client_dupl COMMAND "${PROJECT_SOURCE_DIR}/txrx.sh" -ucD) 72 | add_test(NAME t_udp_server_dupl COMMAND "${PROJECT_SOURCE_DIR}/txrx.sh" -usD) 73 | 74 | add_test(NAME t_ucS_1M_32k COMMAND "${PROJECT_SOURCE_DIR}/txrx.sh" -ucSd 1M -w 32K) 75 | add_test(NAME t_ucS_128K_8K COMMAND "${PROJECT_SOURCE_DIR}/txrx.sh" -ucSd 128K -w 8K) 76 | add_test(NAME t_ucS_16_1 COMMAND "${PROJECT_SOURCE_DIR}/txrx.sh" -ucSd 16 -w 1) 77 | add_test(NAME t_ucS_32K_d COMMAND "${PROJECT_SOURCE_DIR}/txrx.sh" -ucSd 32K) 78 | add_test(NAME t_ucR_1M_32k COMMAND "${PROJECT_SOURCE_DIR}/txrx.sh" -ucRd 1M -w 32K) 79 | add_test(NAME t_ucR_128K_8K COMMAND "${PROJECT_SOURCE_DIR}/txrx.sh" -ucRd 128K -w 8K) 80 | add_test(NAME t_ucR_16_1 COMMAND "${PROJECT_SOURCE_DIR}/txrx.sh" -ucRd 16 -w 1) 81 | add_test(NAME t_ucR_32K_d COMMAND "${PROJECT_SOURCE_DIR}/txrx.sh" -ucRd 32K) 82 | add_test(NAME t_ucD_1M_32k COMMAND "${PROJECT_SOURCE_DIR}/txrx.sh" -ucDd 1M -w 32K) 83 | add_test(NAME t_ucD_128K_8K COMMAND "${PROJECT_SOURCE_DIR}/txrx.sh" -ucDd 128K -w 8K) 84 | add_test(NAME t_ucD_16_1 COMMAND "${PROJECT_SOURCE_DIR}/txrx.sh" -ucDd 16 -w 1) 85 | add_test(NAME t_ucD_32K_d COMMAND "${PROJECT_SOURCE_DIR}/txrx.sh" -ucDd 32K) 86 | 87 | add_test(NAME t_usS_1M_32k COMMAND "${PROJECT_SOURCE_DIR}/txrx.sh" -usSd 1M -w 32K) 88 | add_test(NAME t_usS_128K_8K COMMAND "${PROJECT_SOURCE_DIR}/txrx.sh" -usSd 128K -w 8K) 89 | add_test(NAME t_usS_16_1 COMMAND "${PROJECT_SOURCE_DIR}/txrx.sh" -usSd 16 -w 1) 90 | add_test(NAME t_usS_32K_d COMMAND "${PROJECT_SOURCE_DIR}/txrx.sh" -usSd 32K) 91 | add_test(NAME t_usR_1M_32k COMMAND "${PROJECT_SOURCE_DIR}/txrx.sh" -usRd 1M -w 32K) 92 | add_test(NAME t_usR_128K_8K COMMAND "${PROJECT_SOURCE_DIR}/txrx.sh" -usRd 128K -w 8K) 93 | add_test(NAME t_usR_16_1 COMMAND "${PROJECT_SOURCE_DIR}/txrx.sh" -usRd 16 -w 1) 94 | add_test(NAME t_usR_32K_d COMMAND "${PROJECT_SOURCE_DIR}/txrx.sh" -usRd 32K) 95 | add_test(NAME t_usD_1M_32k COMMAND "${PROJECT_SOURCE_DIR}/txrx.sh" -usDd 1M -w 32K) 96 | add_test(NAME t_usD_128K_8K COMMAND "${PROJECT_SOURCE_DIR}/txrx.sh" -usDd 128K -w 8K) 97 | add_test(NAME t_usD_16_1 COMMAND "${PROJECT_SOURCE_DIR}/txrx.sh" -usDd 16 -w 1) 98 | add_test(NAME t_usD_32K_d COMMAND "${PROJECT_SOURCE_DIR}/txrx.sh" -usDd 32K) 99 | 100 | add_test(NAME t_ucS_128K_8K_l COMMAND "${PROJECT_SOURCE_DIR}/txrx.sh" -ucSd 128K -w 8K -l ${LOSS_RATE}) 101 | add_test(NAME t_ucS_128K_8K_L COMMAND "${PROJECT_SOURCE_DIR}/txrx.sh" -ucSd 128K -w 8K -L ${LOSS_RATE}) 102 | add_test(NAME t_ucS_128K_8K_lL COMMAND "${PROJECT_SOURCE_DIR}/txrx.sh" -ucSd 128K -w 8K -l ${LOSS_RATE} -L ${LOSS_RATE}) 103 | add_test(NAME t_ucR_128K_8K_l COMMAND "${PROJECT_SOURCE_DIR}/txrx.sh" -ucRd 128K -w 8K -l ${LOSS_RATE}) 104 | add_test(NAME t_ucR_128K_8K_L COMMAND "${PROJECT_SOURCE_DIR}/txrx.sh" -ucRd 128K -w 8K -L ${LOSS_RATE}) 105 | add_test(NAME t_ucR_128K_8K_lL COMMAND "${PROJECT_SOURCE_DIR}/txrx.sh" -ucRd 128K -w 8K -l ${LOSS_RATE} -L ${LOSS_RATE}) 106 | add_test(NAME t_ucD_128K_8K_l COMMAND "${PROJECT_SOURCE_DIR}/txrx.sh" -ucDd 128K -w 8K -l ${LOSS_RATE}) 107 | add_test(NAME t_ucD_128K_8K_L COMMAND "${PROJECT_SOURCE_DIR}/txrx.sh" -ucDd 128K -w 8K -L ${LOSS_RATE}) 108 | add_test(NAME t_ucD_128K_8K_lL COMMAND "${PROJECT_SOURCE_DIR}/txrx.sh" -ucDd 128K -w 8K -l ${LOSS_RATE} -L ${LOSS_RATE}) 109 | 110 | add_test(NAME t_usS_128K_8K_l COMMAND "${PROJECT_SOURCE_DIR}/txrx.sh" -usSd 128K -w 8K -l ${LOSS_RATE}) 111 | add_test(NAME t_usS_128K_8K_L COMMAND "${PROJECT_SOURCE_DIR}/txrx.sh" -usSd 128K -w 8K -L ${LOSS_RATE}) 112 | add_test(NAME t_usS_128K_8K_lL COMMAND "${PROJECT_SOURCE_DIR}/txrx.sh" -usSd 128K -w 8K -l ${LOSS_RATE} -L ${LOSS_RATE}) 113 | add_test(NAME t_usR_128K_8K_l COMMAND "${PROJECT_SOURCE_DIR}/txrx.sh" -usRd 128K -w 8K -l ${LOSS_RATE}) 114 | add_test(NAME t_usR_128K_8K_L COMMAND "${PROJECT_SOURCE_DIR}/txrx.sh" -usRd 128K -w 8K -L ${LOSS_RATE}) 115 | add_test(NAME t_usR_128K_8K_lL COMMAND "${PROJECT_SOURCE_DIR}/txrx.sh" -usRd 128K -w 8K -l ${LOSS_RATE} -L ${LOSS_RATE}) 116 | add_test(NAME t_usD_128K_8K_l COMMAND "${PROJECT_SOURCE_DIR}/txrx.sh" -usDd 128K -w 8K -l ${LOSS_RATE}) 117 | add_test(NAME t_usD_128K_8K_L COMMAND "${PROJECT_SOURCE_DIR}/txrx.sh" -usDd 128K -w 8K -L ${LOSS_RATE}) 118 | add_test(NAME t_usD_128K_8K_lL COMMAND "${PROJECT_SOURCE_DIR}/txrx.sh" -usDd 128K -w 8K -l ${LOSS_RATE} -L ${LOSS_RATE}) 119 | 120 | add_test(NAME t_ipv4_client_send COMMAND "${PROJECT_SOURCE_DIR}/txrx.sh" -icS) 121 | add_test(NAME t_ipv4_server_send COMMAND "${PROJECT_SOURCE_DIR}/txrx.sh" -isS) 122 | add_test(NAME t_ipv4_client_recv COMMAND "${PROJECT_SOURCE_DIR}/txrx.sh" -icR) 123 | add_test(NAME t_ipv4_server_recv COMMAND "${PROJECT_SOURCE_DIR}/txrx.sh" -isR) 124 | add_test(NAME t_ipv4_client_dupl COMMAND "${PROJECT_SOURCE_DIR}/txrx.sh" -icD) 125 | add_test(NAME t_ipv4_server_dupl COMMAND "${PROJECT_SOURCE_DIR}/txrx.sh" -isD) 126 | 127 | add_test(NAME t_icS_1M_32k COMMAND "${PROJECT_SOURCE_DIR}/txrx.sh" -icSd 1M -w 32K) 128 | add_test(NAME t_icS_128K_8K COMMAND "${PROJECT_SOURCE_DIR}/txrx.sh" -icSd 128K -w 8K) 129 | add_test(NAME t_icS_16_1 COMMAND "${PROJECT_SOURCE_DIR}/txrx.sh" -icSd 16 -w 1) 130 | add_test(NAME t_icS_32K_d COMMAND "${PROJECT_SOURCE_DIR}/txrx.sh" -icSd 32K) 131 | add_test(NAME t_icR_1M_32k COMMAND "${PROJECT_SOURCE_DIR}/txrx.sh" -icRd 1M -w 32K) 132 | add_test(NAME t_icR_128K_8K COMMAND "${PROJECT_SOURCE_DIR}/txrx.sh" -icRd 128K -w 8K) 133 | add_test(NAME t_icR_16_1 COMMAND "${PROJECT_SOURCE_DIR}/txrx.sh" -icRd 16 -w 1) 134 | add_test(NAME t_icR_32K_d COMMAND "${PROJECT_SOURCE_DIR}/txrx.sh" -icRd 32K) 135 | add_test(NAME t_icD_1M_32k COMMAND "${PROJECT_SOURCE_DIR}/txrx.sh" -icDd 1M -w 32K) 136 | add_test(NAME t_icD_128K_8K COMMAND "${PROJECT_SOURCE_DIR}/txrx.sh" -icDd 128K -w 8K) 137 | add_test(NAME t_icD_16_1 COMMAND "${PROJECT_SOURCE_DIR}/txrx.sh" -icDd 16 -w 1) 138 | add_test(NAME t_icD_32K_d COMMAND "${PROJECT_SOURCE_DIR}/txrx.sh" -icDd 32K) 139 | 140 | add_test(NAME t_isS_1M_32k COMMAND "${PROJECT_SOURCE_DIR}/txrx.sh" -isSd 1M -w 32K) 141 | add_test(NAME t_isS_128K_8K COMMAND "${PROJECT_SOURCE_DIR}/txrx.sh" -isSd 128K -w 8K) 142 | add_test(NAME t_isS_16_1 COMMAND "${PROJECT_SOURCE_DIR}/txrx.sh" -isSd 16 -w 1) 143 | add_test(NAME t_isS_32K_d COMMAND "${PROJECT_SOURCE_DIR}/txrx.sh" -isSd 32K) 144 | add_test(NAME t_isR_1M_32k COMMAND "${PROJECT_SOURCE_DIR}/txrx.sh" -isRd 1M -w 32K) 145 | add_test(NAME t_isR_128K_8K COMMAND "${PROJECT_SOURCE_DIR}/txrx.sh" -isRd 128K -w 8K) 146 | add_test(NAME t_isR_16_1 COMMAND "${PROJECT_SOURCE_DIR}/txrx.sh" -isRd 16 -w 1) 147 | add_test(NAME t_isR_32K_d COMMAND "${PROJECT_SOURCE_DIR}/txrx.sh" -isRd 32K) 148 | add_test(NAME t_isD_1M_32k COMMAND "${PROJECT_SOURCE_DIR}/txrx.sh" -isDd 1M -w 32K) 149 | add_test(NAME t_isD_128K_8K COMMAND "${PROJECT_SOURCE_DIR}/txrx.sh" -isDd 128K -w 8K) 150 | add_test(NAME t_isD_16_1 COMMAND "${PROJECT_SOURCE_DIR}/txrx.sh" -isDd 16 -w 1) 151 | add_test(NAME t_isD_32K_d COMMAND "${PROJECT_SOURCE_DIR}/txrx.sh" -isDd 32K) 152 | 153 | add_test(NAME t_icS_128K_8K_l COMMAND "${PROJECT_SOURCE_DIR}/txrx.sh" -icSd 128K -w 8K -l ${LOSS_RATE}) 154 | add_test(NAME t_icS_128K_8K_L COMMAND "${PROJECT_SOURCE_DIR}/txrx.sh" -icSd 128K -w 8K -L ${LOSS_RATE}) 155 | add_test(NAME t_icS_128K_8K_lL COMMAND "${PROJECT_SOURCE_DIR}/txrx.sh" -icSd 128K -w 8K -l ${LOSS_RATE} -L ${LOSS_RATE}) 156 | add_test(NAME t_icR_128K_8K_l COMMAND "${PROJECT_SOURCE_DIR}/txrx.sh" -icRd 128K -w 8K -l ${LOSS_RATE}) 157 | add_test(NAME t_icR_128K_8K_L COMMAND "${PROJECT_SOURCE_DIR}/txrx.sh" -icRd 128K -w 8K -L ${LOSS_RATE}) 158 | add_test(NAME t_icR_128K_8K_lL COMMAND "${PROJECT_SOURCE_DIR}/txrx.sh" -icRd 128K -w 8K -l ${LOSS_RATE} -L ${LOSS_RATE}) 159 | add_test(NAME t_icD_128K_8K_l COMMAND "${PROJECT_SOURCE_DIR}/txrx.sh" -icDd 128K -w 8K -l ${LOSS_RATE}) 160 | add_test(NAME t_icD_128K_8K_L COMMAND "${PROJECT_SOURCE_DIR}/txrx.sh" -icDd 128K -w 8K -L ${LOSS_RATE}) 161 | add_test(NAME t_icD_128K_8K_lL COMMAND "${PROJECT_SOURCE_DIR}/txrx.sh" -icDd 128K -w 8K -l ${LOSS_RATE} -L ${LOSS_RATE}) 162 | 163 | add_test(NAME t_isS_128K_8K_l COMMAND "${PROJECT_SOURCE_DIR}/txrx.sh" -isSd 128K -w 8K -l ${LOSS_RATE}) 164 | add_test(NAME t_isS_128K_8K_L COMMAND "${PROJECT_SOURCE_DIR}/txrx.sh" -isSd 128K -w 8K -L ${LOSS_RATE}) 165 | add_test(NAME t_isS_128K_8K_lL COMMAND "${PROJECT_SOURCE_DIR}/txrx.sh" -isSd 128K -w 8K -l ${LOSS_RATE} -L ${LOSS_RATE}) 166 | add_test(NAME t_isR_128K_8K_l COMMAND "${PROJECT_SOURCE_DIR}/txrx.sh" -isRd 128K -w 8K -l ${LOSS_RATE}) 167 | add_test(NAME t_isR_128K_8K_L COMMAND "${PROJECT_SOURCE_DIR}/txrx.sh" -isRd 128K -w 8K -L ${LOSS_RATE}) 168 | add_test(NAME t_isR_128K_8K_lL COMMAND "${PROJECT_SOURCE_DIR}/txrx.sh" -isRd 128K -w 8K -l ${LOSS_RATE} -L ${LOSS_RATE}) 169 | add_test(NAME t_isD_128K_8K_l COMMAND "${PROJECT_SOURCE_DIR}/txrx.sh" -isDd 128K -w 8K -l ${LOSS_RATE}) 170 | add_test(NAME t_isD_128K_8K_L COMMAND "${PROJECT_SOURCE_DIR}/txrx.sh" -isDd 128K -w 8K -L ${LOSS_RATE}) 171 | add_test(NAME t_isD_128K_8K_lL COMMAND "${PROJECT_SOURCE_DIR}/txrx.sh" -isDd 128K -w 8K -l ${LOSS_RATE} -L ${LOSS_RATE}) 172 | 173 | add_test(NAME t_icnS_128K_8K_l COMMAND "${PROJECT_SOURCE_DIR}/txrx.sh" -icSnd 128K -w 8K -l ${LOSS_RATE}) 174 | add_test(NAME t_icnS_128K_8K_L COMMAND "${PROJECT_SOURCE_DIR}/txrx.sh" -icSnd 128K -w 8K -L ${LOSS_RATE}) 175 | add_test(NAME t_icnS_128K_8K_lL COMMAND "${PROJECT_SOURCE_DIR}/txrx.sh" -icSnd 128K -w 8K -l ${LOSS_RATE} -L ${LOSS_RATE}) 176 | add_test(NAME t_icnR_128K_8K_l COMMAND "${PROJECT_SOURCE_DIR}/txrx.sh" -icRnd 128K -w 8K -l ${LOSS_RATE}) 177 | add_test(NAME t_icnR_128K_8K_L COMMAND "${PROJECT_SOURCE_DIR}/txrx.sh" -icRnd 128K -w 8K -L ${LOSS_RATE}) 178 | add_test(NAME t_icnR_128K_8K_lL COMMAND "${PROJECT_SOURCE_DIR}/txrx.sh" -icRnd 128K -w 8K -l ${LOSS_RATE} -L ${LOSS_RATE}) 179 | add_test(NAME t_icnD_128K_8K_l COMMAND "${PROJECT_SOURCE_DIR}/txrx.sh" -icDnd 128K -w 8K -l ${LOSS_RATE}) 180 | add_test(NAME t_icnD_128K_8K_L COMMAND "${PROJECT_SOURCE_DIR}/txrx.sh" -icDnd 128K -w 8K -L ${LOSS_RATE}) 181 | add_test(NAME t_icnD_128K_8K_lL COMMAND "${PROJECT_SOURCE_DIR}/txrx.sh" -icDnd 128K -w 8K -l ${LOSS_RATE} -L ${LOSS_RATE}) 182 | 183 | add_test(NAME t_isnS_128K_8K_l COMMAND "${PROJECT_SOURCE_DIR}/txrx.sh" -isSnd 128K -w 8K -l ${LOSS_RATE}) 184 | add_test(NAME t_isnS_128K_8K_L COMMAND "${PROJECT_SOURCE_DIR}/txrx.sh" -isSnd 128K -w 8K -L ${LOSS_RATE}) 185 | add_test(NAME t_isnS_128K_8K_lL COMMAND "${PROJECT_SOURCE_DIR}/txrx.sh" -isSnd 128K -w 8K -l ${LOSS_RATE} -L ${LOSS_RATE}) 186 | add_test(NAME t_isnR_128K_8K_l COMMAND "${PROJECT_SOURCE_DIR}/txrx.sh" -isRnd 128K -w 8K -l ${LOSS_RATE}) 187 | add_test(NAME t_isnR_128K_8K_L COMMAND "${PROJECT_SOURCE_DIR}/txrx.sh" -isRnd 128K -w 8K -L ${LOSS_RATE}) 188 | add_test(NAME t_isnR_128K_8K_lL COMMAND "${PROJECT_SOURCE_DIR}/txrx.sh" -isRnd 128K -w 8K -l ${LOSS_RATE} -L ${LOSS_RATE}) 189 | add_test(NAME t_isnD_128K_8K_l COMMAND "${PROJECT_SOURCE_DIR}/txrx.sh" -isDnd 128K -w 8K -l ${LOSS_RATE}) 190 | add_test(NAME t_isnD_128K_8K_L COMMAND "${PROJECT_SOURCE_DIR}/txrx.sh" -isDnd 128K -w 8K -L ${LOSS_RATE}) 191 | add_test(NAME t_isnD_128K_8K_lL COMMAND "${PROJECT_SOURCE_DIR}/txrx.sh" -isDnd 128K -w 8K -l ${LOSS_RATE} -L ${LOSS_RATE}) 192 | 193 | #add_test(NAME t_icoS_128K_8K_l COMMAND "${PROJECT_SOURCE_DIR}/txrx.sh" -icSod 128K -w 8K -l ${LOSS_RATE}) 194 | #add_test(NAME t_icoS_128K_8K_L COMMAND "${PROJECT_SOURCE_DIR}/txrx.sh" -icSod 128K -w 8K -L ${LOSS_RATE}) 195 | #add_test(NAME t_icoS_128K_8K_lL COMMAND "${PROJECT_SOURCE_DIR}/txrx.sh" -icSod 128K -w 8K -l ${LOSS_RATE} -L ${LOSS_RATE}) 196 | #add_test(NAME t_icoR_128K_8K_l COMMAND "${PROJECT_SOURCE_DIR}/txrx.sh" -icRod 128K -w 8K -l ${LOSS_RATE}) 197 | #add_test(NAME t_icoR_128K_8K_L COMMAND "${PROJECT_SOURCE_DIR}/txrx.sh" -icRod 128K -w 8K -L ${LOSS_RATE}) 198 | #add_test(NAME t_icoR_128K_8K_lL COMMAND "${PROJECT_SOURCE_DIR}/txrx.sh" -icRod 128K -w 8K -l ${LOSS_RATE} -L ${LOSS_RATE}) 199 | #add_test(NAME t_icoD_128K_8K_l COMMAND "${PROJECT_SOURCE_DIR}/txrx.sh" -icDod 128K -w 8K -l ${LOSS_RATE}) 200 | #add_test(NAME t_icoD_128K_8K_L COMMAND "${PROJECT_SOURCE_DIR}/txrx.sh" -icDod 128K -w 8K -L ${LOSS_RATE}) 201 | #add_test(NAME t_icoD_128K_8K_lL COMMAND "${PROJECT_SOURCE_DIR}/txrx.sh" -icDod 128K -w 8K -l ${LOSS_RATE} -L ${LOSS_RATE}) 202 | 203 | #add_test(NAME t_isoS_128K_8K_l COMMAND "${PROJECT_SOURCE_DIR}/txrx.sh" -isSod 128K -w 8K -l ${LOSS_RATE}) 204 | #add_test(NAME t_isoS_128K_8K_L COMMAND "${PROJECT_SOURCE_DIR}/txrx.sh" -isSod 128K -w 8K -L ${LOSS_RATE}) 205 | #add_test(NAME t_isoS_128K_8K_lL COMMAND "${PROJECT_SOURCE_DIR}/txrx.sh" -isSod 128K -w 8K -l ${LOSS_RATE} -L ${LOSS_RATE}) 206 | #add_test(NAME t_isoR_128K_8K_l COMMAND "${PROJECT_SOURCE_DIR}/txrx.sh" -isRod 128K -w 8K -l ${LOSS_RATE}) 207 | #add_test(NAME t_isoR_128K_8K_L COMMAND "${PROJECT_SOURCE_DIR}/txrx.sh" -isRod 128K -w 8K -L ${LOSS_RATE}) 208 | #add_test(NAME t_isoR_128K_8K_lL COMMAND "${PROJECT_SOURCE_DIR}/txrx.sh" -isRod 128K -w 8K -l ${LOSS_RATE} -L ${LOSS_RATE}) 209 | #add_test(NAME t_isoD_128K_8K_l COMMAND "${PROJECT_SOURCE_DIR}/txrx.sh" -isDod 128K -w 8K -l ${LOSS_RATE}) 210 | #add_test(NAME t_isoD_128K_8K_L COMMAND "${PROJECT_SOURCE_DIR}/txrx.sh" -isDod 128K -w 8K -L ${LOSS_RATE}) 211 | #add_test(NAME t_isoD_128K_8K_lL COMMAND "${PROJECT_SOURCE_DIR}/txrx.sh" -isDod 128K -w 8K -l ${LOSS_RATE} -L ${LOSS_RATE}) 212 | 213 | add_custom_target (check_webget COMMAND ${CMAKE_CTEST_COMMAND} --output-on-failure --timeout 10 -R 't_webget' 214 | COMMENT "Testing webget...") 215 | add_custom_target (check_lab0 COMMAND ${CMAKE_CTEST_COMMAND} --output-on-failure --timeout 10 -R 't_webget|t_byte_stream|_dt' 216 | COMMENT "Testing Lab 0...") 217 | add_custom_target (check_lab1 COMMAND ${CMAKE_CTEST_COMMAND} --output-on-failure --timeout 10 -R 't_strm_reassem_|t_webget|t_byte_stream|_dt' 218 | COMMENT "Testing the stream reassembler...") 219 | add_custom_target (check_lab2 COMMAND ${CMAKE_CTEST_COMMAND} --output-on-failure --timeout 10 -R 't_recv_|t_wrapping_|t_strm_reassem_|t_webget|t_byte_stream|_dt' 220 | COMMENT "Testing the TCP receiver...") 221 | add_custom_target (check_lab3 COMMAND ${CMAKE_CTEST_COMMAND} --output-on-failure --timeout 10 -R 't_send_|t_recv_|t_wrapping_|t_strm_reassem_|t_webget|t_byte_stream|_dt' 222 | COMMENT "Testing the TCP sender...") 223 | add_custom_target (check_lab4 COMMAND "${PROJECT_SOURCE_DIR}/tun.sh" check 144 145 224 | COMMAND ${CMAKE_CTEST_COMMAND} --output-on-failure --timeout 10 -R "^t_" 225 | COMMENT "Testing the TCP connection...") 226 | add_custom_target (check_lab5 COMMAND "${PROJECT_SOURCE_DIR}/tap.sh" check 10 227 | COMMAND ${CMAKE_CTEST_COMMAND} --output-on-failure --timeout 10 -R '^t_webget|^arp_' 228 | COMMENT "Testing Lab 5...") 229 | add_custom_target (check_lab6 COMMAND ${CMAKE_CTEST_COMMAND} --output-on-failure --timeout 10 -R '^arp_|^router_' 230 | COMMENT "Testing Lab 6...") 231 | 232 | add_custom_target (check COMMAND "${PROJECT_SOURCE_DIR}/tun.sh" check 144 145 233 | COMMAND "${PROJECT_SOURCE_DIR}/tap.sh" check 10 234 | COMMAND ${CMAKE_CTEST_COMMAND} --output-on-failure --timeout 10 -R '^t_|^arp_|^router_' 235 | COMMENT "Testing libsponge...") 236 | --------------------------------------------------------------------------------