├── .clang-format ├── .clang-tidy ├── .gitignore ├── CMakeLists.txt ├── README.md ├── apps ├── CMakeLists.txt └── webget.cc ├── compile_commands.json ├── etc ├── build_type.cmake ├── cflags.cmake ├── scanners.cmake └── tests.cmake ├── scripts ├── lines-of-code └── make-parallel.sh ├── src ├── CMakeLists.txt ├── byte_stream.cc ├── byte_stream.hh ├── byte_stream_helpers.cc ├── network_interface.cc ├── network_interface.hh ├── reassembler.cc ├── reassembler.hh ├── router.cc ├── router.hh ├── tcp_receiver.cc ├── tcp_receiver.hh ├── tcp_sender.cc ├── tcp_sender.hh ├── wrapping_integers.cc └── wrapping_integers.hh ├── tests ├── CMakeLists.txt ├── byte_stream_basics.cc ├── byte_stream_capacity.cc ├── byte_stream_many_writes.cc ├── byte_stream_one_write.cc ├── byte_stream_speed_test.cc ├── byte_stream_stress_test.cc ├── byte_stream_test_harness.hh ├── byte_stream_two_writes.cc ├── common.cc ├── common.hh ├── conversions.hh ├── net_interface.cc ├── network_interface_test_harness.hh ├── reassembler_cap.cc ├── reassembler_dup.cc ├── reassembler_holes.cc ├── reassembler_overlapping.cc ├── reassembler_seq.cc ├── reassembler_single.cc ├── reassembler_speed_test.cc ├── reassembler_test_harness.hh ├── reassembler_win.cc ├── receiver_test_harness.hh ├── recv_close.cc ├── recv_connect.cc ├── recv_reorder.cc ├── recv_reorder_more.cc ├── recv_special.cc ├── recv_transmit.cc ├── recv_window.cc ├── router.cc ├── send_ack.cc ├── send_close.cc ├── send_connect.cc ├── send_extra.cc ├── send_retx.cc ├── send_transmit.cc ├── send_window.cc ├── sender_test_harness.hh ├── test_should_be.hh ├── webget_t.sh ├── wrapping_integers_cmp.cc ├── wrapping_integers_extra.cc ├── wrapping_integers_roundtrip.cc ├── wrapping_integers_unwrap.cc └── wrapping_integers_wrap.cc ├── util ├── CMakeLists.txt ├── address.cc ├── address.hh ├── arp_message.cc ├── arp_message.hh ├── buffer.hh ├── checksum.hh ├── ethernet_frame.hh ├── ethernet_header.cc ├── ethernet_header.hh ├── exception.hh ├── file_descriptor.cc ├── file_descriptor.hh ├── ipv4_datagram.hh ├── ipv4_header.cc ├── ipv4_header.hh ├── parser.hh ├── random.cc ├── random.hh ├── socket.cc ├── socket.hh ├── tcp_config.hh ├── tcp_receiver_message.hh └── tcp_sender_message.hh └── writeups ├── check0.md ├── check1.md ├── check2.md ├── check3.md ├── check4.md └── check5.md /.clang-format: -------------------------------------------------------------------------------- 1 | --- 2 | Language: Cpp 3 | BasedOnStyle: Mozilla 4 | ColumnLimit: 116 5 | SpacesInParentheses: true 6 | AlwaysBreakAfterReturnType: None 7 | AlwaysBreakAfterDefinitionReturnType: None 8 | SpaceBeforeCpp11BracedList: true 9 | BreakBeforeBinaryOperators: All 10 | Cpp11BracedListStyle: true 11 | AllowShortBlocksOnASingleLine: Always 12 | BreakBeforeBraces: Custom 13 | BraceWrapping: 14 | AfterClass: true 15 | AfterControlStatement: Never 16 | AfterFunction: true 17 | AfterStruct: true 18 | AfterEnum: true 19 | SplitEmptyFunction: false 20 | SplitEmptyRecord: false 21 | SplitEmptyNamespace: false 22 | PackConstructorInitializers: NextLine 23 | ... 24 | -------------------------------------------------------------------------------- /.clang-tidy: -------------------------------------------------------------------------------- 1 | Checks: 2 | '*,-altera-*,-llvmlibc-*,-google-build-using-namespace,-modernize-use-trailing-return-type,-readability-implicit-bool-conversion,-*-non-private-member-variables-in-classes,-modernize-use-nodiscard,-*-magic-numbers,-readability-identifier-length,-concurrency-mt-unsafe,-llvm-header-guard,-fuchsia-default-arguments-calls,-fuchsia-default-arguments-declarations,-fuchsia-overloaded-operator,-android-cloexec-accept' 3 | 4 | WarningsAsErrors: 5 | '*' 6 | 7 | CheckOptions: 8 | hicpp-signed-bitwise.IgnorePositiveIntegerLiterals: 'true' 9 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /cmake-build-debug 2 | /.idea 3 | /build 4 | /.ccls-cache 5 | /.vscode 6 | *.swp 7 | /.cache 8 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.24.2) 2 | 3 | project(minnow CXX) 4 | 5 | if(${CMAKE_GENERATOR} STREQUAL "Unix Makefiles") 6 | set(CMAKE_MAKE_PROGRAM "${PROJECT_SOURCE_DIR}/scripts/make-parallel.sh" CACHE STRING "" FORCE) 7 | endif() 8 | 9 | if(${PROJECT_SOURCE_DIR} STREQUAL ${CMAKE_BINARY_DIR}) 10 | message(FATAL_ERROR "Minnow must be built outside its source directory, e.g. `cmake -B build`.") 11 | endif() 12 | 13 | include(etc/build_type.cmake) 14 | include(etc/cflags.cmake) 15 | include(etc/scanners.cmake) 16 | include(etc/tests.cmake) 17 | 18 | include_directories("${PROJECT_SOURCE_DIR}/util") 19 | include_directories("${PROJECT_SOURCE_DIR}/src") 20 | 21 | add_subdirectory("${PROJECT_SOURCE_DIR}/util") 22 | add_subdirectory("${PROJECT_SOURCE_DIR}/src") 23 | add_subdirectory("${PROJECT_SOURCE_DIR}/tests") 24 | add_subdirectory("${PROJECT_SOURCE_DIR}/apps") 25 | 26 | set(CMAKE_EXPORT_COMPILE_COMMANDS ON) 27 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # CS144 2 | 3 | Personal archive of solutions to tasks of CS144 at Stanford University for Spring 2023. 4 | 5 | ## Course Info 6 | 7 | Principles and practice. Structure and components of computer networks, with focus on the Internet. Packet switching, layering, and routing. Transport and TCP: reliable delivery over an unreliable network, flow control, congestion control. Network names, addresses and ethernet switching. Includes significant programming component in C/C++; students build portions of the internet TCP/IP software. Prerequisite: CS110. 8 | 9 | Title: Introduction to Computer Networking 10 | Semester: Spring 2023 11 | Website: https://cs144.stanford.edu 12 | -------------------------------------------------------------------------------- /apps/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | macro(add_app exec_name) 2 | add_executable("${exec_name}" "${exec_name}.cc") 3 | target_link_libraries("${exec_name}" minnow_debug) 4 | target_link_libraries("${exec_name}" util_debug) 5 | endmacro(add_app) 6 | 7 | add_app(webget) 8 | -------------------------------------------------------------------------------- /apps/webget.cc: -------------------------------------------------------------------------------- 1 | #include "socket.hh" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | using namespace std; 9 | 10 | void get_URL( const string& host, const string& path ) 11 | { 12 | auto socket = TCPSocket(); 13 | socket.connect( Address( host, "http" ) ); 14 | 15 | auto write_line = [&]( std::string_view s ) { 16 | socket.write( s ); 17 | socket.write( "\r\n" ); 18 | }; 19 | write_line( "GET " + path + " HTTP/1.1" ); 20 | write_line( "Host: " + host ); 21 | write_line( "Connection: close" ); 22 | write_line( "" ); 23 | 24 | std::string buffer; 25 | while ( !socket.eof() ) { 26 | socket.read( buffer ); 27 | std::cout << buffer; 28 | } 29 | } 30 | 31 | int main( int argc, char* argv[] ) 32 | { 33 | try { 34 | if ( argc <= 0 ) { 35 | abort(); // For sticklers: don't try to access argv[0] if argc <= 0. 36 | } 37 | 38 | auto args = span( argv, argc ); 39 | 40 | // The program takes two command-line arguments: the hostname and "path" part of the URL. 41 | // Print the usage message unless there are these two arguments (plus the program name 42 | // itself, so arg count = 3 in total). 43 | if ( argc != 3 ) { 44 | cerr << "Usage: " << args.front() << " HOST PATH\n"; 45 | cerr << "\tExample: " << args.front() << " stanford.edu /class/cs144\n"; 46 | return EXIT_FAILURE; 47 | } 48 | 49 | // Get the command-line arguments. 50 | const string host { args[1] }; 51 | const string path { args[2] }; 52 | 53 | // Call the student-written function. 54 | get_URL( host, path ); 55 | } catch ( const exception& e ) { 56 | cerr << e.what() << "\n"; 57 | return EXIT_FAILURE; 58 | } 59 | 60 | return EXIT_SUCCESS; 61 | } 62 | -------------------------------------------------------------------------------- /compile_commands.json: -------------------------------------------------------------------------------- 1 | build/compile_commands.json -------------------------------------------------------------------------------- /etc/build_type.cmake: -------------------------------------------------------------------------------- 1 | set(default_build_type "Debug") 2 | 3 | if (NOT (CMAKE_BUILD_TYPE_SHADOW STREQUAL CMAKE_BUILD_TYPE)) 4 | if (NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) 5 | message (STATUS "Setting build type to '${default_build_type}'") 6 | set (CMAKE_BUILD_TYPE "${default_build_type}" CACHE STRING "Choose the type of build." FORCE) 7 | endif () 8 | set (CMAKE_BUILD_TYPE_SHADOW ${CMAKE_BUILD_TYPE} CACHE STRING "used to detect changes in build type" FORCE) 9 | endif () 10 | 11 | message (STATUS "Building in '${CMAKE_BUILD_TYPE}' mode.") 12 | -------------------------------------------------------------------------------- /etc/cflags.cmake: -------------------------------------------------------------------------------- 1 | set (CMAKE_CXX_STANDARD 20) 2 | set (CMAKE_EXPORT_COMPILE_COMMANDS ON) 3 | 4 | set(SANITIZING_FLAGS -fno-sanitize-recover=all -fsanitize=undefined -fsanitize=address) 5 | 6 | # ask for more warnings from the compiler 7 | set (CMAKE_BASE_CXX_FLAGS "${CMAKE_CXX_FLAGS}") 8 | set (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wpedantic -Wextra -Weffc++ -Werror -Wshadow -Wpointer-arith -Wcast-qual -Wformat=2 -Wno-unqualified-std-cast-call") 9 | -------------------------------------------------------------------------------- /etc/scanners.cmake: -------------------------------------------------------------------------------- 1 | file (GLOB_RECURSE MINNOW_CC_FILES ${CMAKE_SOURCE_DIR}/src/*.cc) 2 | 3 | file (GLOB_RECURSE ALL_SRC_FILES *.hh *.cc) 4 | 5 | add_custom_target (format "clang-format" -i ${ALL_SRC_FILES} COMMENT "Formatting source code...") 6 | 7 | foreach (tidy_target ${ALL_SRC_FILES}) 8 | get_filename_component (basename ${tidy_target} NAME) 9 | get_filename_component (dirname ${tidy_target} DIRECTORY) 10 | get_filename_component (basedir ${dirname} NAME) 11 | set (tidy_target_name "${basedir}__${basename}") 12 | set (tidy_command clang-tidy --quiet -header-filter=.* -p=${PROJECT_BINARY_DIR} ${tidy_target}) 13 | add_custom_target (tidy_${tidy_target_name} ${tidy_command}) 14 | list (APPEND ALL_TIDY_TARGETS tidy_${tidy_target_name}) 15 | 16 | if (${tidy_target} IN_LIST MINNOW_CC_FILES) 17 | list (APPEND MINNOW_TIDY_TARGETS tidy_${tidy_target_name}) 18 | endif () 19 | endforeach (tidy_target) 20 | 21 | add_custom_target (tidy DEPENDS ${MINNOW_TIDY_TARGETS}) 22 | 23 | add_custom_target (tidy-all DEPENDS ${ALL_TIDY_TARGETS}) 24 | -------------------------------------------------------------------------------- /etc/tests.cmake: -------------------------------------------------------------------------------- 1 | include(CTest) 2 | 3 | list(APPEND CMAKE_CTEST_ARGUMENTS --output-on-failure --stop-on-failure --timeout 10 -E 'speed_test|optimization') 4 | 5 | set(compile_name "compile with bug-checkers") 6 | add_test(NAME ${compile_name} 7 | COMMAND "${CMAKE_COMMAND}" --build "${CMAKE_BINARY_DIR}" -t functionality_testing webget) 8 | 9 | macro (ttest name) 10 | add_test(NAME ${name} COMMAND "${name}_sanitized") 11 | set_property(TEST ${name} PROPERTY FIXTURES_REQUIRED compile) 12 | endmacro (ttest) 13 | 14 | set_property(TEST ${compile_name} PROPERTY TIMEOUT -1) 15 | set_tests_properties(${compile_name} PROPERTIES FIXTURES_SETUP compile) 16 | 17 | add_test(NAME t_webget COMMAND "${PROJECT_SOURCE_DIR}/tests/webget_t.sh" "${PROJECT_BINARY_DIR}") 18 | set_property(TEST t_webget PROPERTY FIXTURES_REQUIRED compile) 19 | 20 | ttest(byte_stream_basics) 21 | ttest(byte_stream_capacity) 22 | ttest(byte_stream_one_write) 23 | ttest(byte_stream_two_writes) 24 | ttest(byte_stream_many_writes) 25 | ttest(byte_stream_stress_test) 26 | 27 | ttest(reassembler_single) 28 | ttest(reassembler_cap) 29 | ttest(reassembler_seq) 30 | ttest(reassembler_dup) 31 | ttest(reassembler_holes) 32 | ttest(reassembler_overlapping) 33 | ttest(reassembler_win) 34 | 35 | ttest(wrapping_integers_cmp) 36 | ttest(wrapping_integers_wrap) 37 | ttest(wrapping_integers_unwrap) 38 | ttest(wrapping_integers_roundtrip) 39 | ttest(wrapping_integers_extra) 40 | 41 | ttest(recv_connect) 42 | ttest(recv_transmit) 43 | ttest(recv_window) 44 | ttest(recv_reorder) 45 | ttest(recv_reorder_more) 46 | ttest(recv_close) 47 | ttest(recv_special) 48 | 49 | ttest(send_connect) 50 | ttest(send_transmit) 51 | ttest(send_retx) 52 | ttest(send_window) 53 | ttest(send_ack) 54 | ttest(send_close) 55 | ttest(send_extra) 56 | 57 | ttest(net_interface) 58 | 59 | ttest(router) 60 | 61 | add_custom_target (check0 COMMAND ${CMAKE_CTEST_COMMAND} --output-on-failure --stop-on-failure --timeout 12 -R 'webget|^byte_stream_') 62 | 63 | add_custom_target (check_webget COMMAND ${CMAKE_CTEST_COMMAND} --output-on-failure --timeout 12 -R 'webget') 64 | 65 | add_custom_target (check1 COMMAND ${CMAKE_CTEST_COMMAND} --output-on-failure --stop-on-failure --timeout 12 -R '^byte_stream_|^reassembler_') 66 | 67 | add_custom_target (check2 COMMAND ${CMAKE_CTEST_COMMAND} --output-on-failure --stop-on-failure --timeout 12 -R '^byte_stream_|^reassembler_|^wrapping|^recv') 68 | 69 | add_custom_target (check3 COMMAND ${CMAKE_CTEST_COMMAND} --output-on-failure --stop-on-failure --timeout 12 -R '^byte_stream_|^reassembler_|^wrapping|^recv|^send') 70 | 71 | add_custom_target (check4 COMMAND ${CMAKE_CTEST_COMMAND} --output-on-failure --stop-on-failure --timeout 12 -R '^net_interface') 72 | 73 | add_custom_target (check5 COMMAND ${CMAKE_CTEST_COMMAND} --output-on-failure --stop-on-failure --timeout 12 -R '^net_interface|^router') 74 | 75 | ### 76 | 77 | add_custom_target (speed COMMAND ${CMAKE_CTEST_COMMAND} --output-on-failure --timeout 12 -R '_speed_test') 78 | 79 | set(compile_name_opt "compile with optimization") 80 | add_test(NAME ${compile_name_opt} 81 | COMMAND "${CMAKE_COMMAND}" --build "${CMAKE_BINARY_DIR}" -t speed_testing) 82 | 83 | macro (stest name) 84 | add_test(NAME ${name} COMMAND ${name}) 85 | set_property(TEST ${name} PROPERTY FIXTURES_REQUIRED compile_opt) 86 | endmacro (stest) 87 | 88 | set_property(TEST ${compile_name_opt} PROPERTY TIMEOUT -1) 89 | set_tests_properties(${compile_name_opt} PROPERTIES FIXTURES_SETUP compile_opt) 90 | 91 | stest(byte_stream_speed_test) 92 | stest(reassembler_speed_test) 93 | -------------------------------------------------------------------------------- /scripts/lines-of-code: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | import os 4 | import subprocess 5 | import re 6 | import sys 7 | 8 | base = sys.argv[1] if len(sys.argv) > 1 else '.' 9 | modules = [['byte_stream', 'ByteStream'], 10 | ['reassembler', 'Reassembler'], 11 | ['tcp_receiver', 'TCPReceiver'], 12 | ['wrapping_integers', 'Wrap32'], 13 | ['tcp_sender', 'TCPSender'], 14 | ['network_interface', 'NetworkInterface'], 15 | ['router', 'Router']] 16 | longest_module_length = max([len(x[1]) for x in modules]) 17 | 18 | 19 | def count_lines(filename): 20 | full_filename = base + '/src/' + filename 21 | report = subprocess.run(['sloccount', full_filename], capture_output=True) 22 | report.check_returncode() 23 | loc = re.search(r'Total Physical Source Lines of Code \(SLOC\) * = (\d+)', 24 | report.stdout.decode('ascii')) 25 | return int(loc.group(1)) 26 | 27 | 28 | if os.environ.get('MAKE_TERMOUT'): 29 | tty = open('/dev/tty', 'w') 30 | else: 31 | tty = sys.stdout 32 | 33 | 34 | for i in modules: 35 | x = count_lines(i[0] + '.hh') 36 | y = count_lines(i[0] + '.cc') 37 | spacing = longest_module_length - len(i[1]) 38 | tty.write('%s%s:%s%5d lines of code\n' 39 | % (' ' * 13, i[1], ' ' * spacing, x + y)) 40 | -------------------------------------------------------------------------------- /scripts/make-parallel.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | exec make -j`nproc` "$@" 3 | -------------------------------------------------------------------------------- /src/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | file(GLOB LIB_SOURCES "*.cc") 2 | 3 | add_library(minnow_debug STATIC ${LIB_SOURCES}) 4 | 5 | add_library(minnow_sanitized EXCLUDE_FROM_ALL STATIC ${LIB_SOURCES}) 6 | target_compile_options(minnow_sanitized PUBLIC ${SANITIZING_FLAGS}) 7 | 8 | add_library(minnow_optimized EXCLUDE_FROM_ALL STATIC ${LIB_SOURCES}) 9 | target_compile_options(minnow_optimized PUBLIC "-O2") 10 | -------------------------------------------------------------------------------- /src/byte_stream.cc: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "byte_stream.hh" 4 | 5 | using namespace std; 6 | 7 | ByteStream::ByteStream( uint64_t capacity ) 8 | : capacity_( capacity ), pushed_( 0 ), popped_( 0 ), has_error_( false ), is_closed_( false ), data_() 9 | {} 10 | 11 | uint64_t ByteStream::StringQueue::size() const 12 | { 13 | return data_.size() - popped_; 14 | } 15 | 16 | void ByteStream::StringQueue::push( char c ) 17 | { 18 | data_.push_back( c ); 19 | } 20 | 21 | uint64_t ByteStream::StringQueue::pop( uint64_t len ) 22 | { 23 | const uint64_t bytes_to_pop = std::min( len, data_.size() - popped_ ); 24 | popped_ += bytes_to_pop; 25 | 26 | if ( popped_ >= data_.size() / 2 ) { 27 | data_ = data_.substr( popped_ ); 28 | popped_ = 0; 29 | } 30 | 31 | return bytes_to_pop; 32 | } 33 | 34 | std::string_view ByteStream::StringQueue::peek() const 35 | { 36 | return std::string_view { data_ }.substr( popped_ ); 37 | } 38 | 39 | void Writer::push( string data ) 40 | { 41 | for ( auto c : data ) { 42 | if ( data_.size() >= capacity_ ) { 43 | break; 44 | } 45 | data_.push( c ); 46 | ++pushed_; 47 | } 48 | } 49 | 50 | void Writer::close() 51 | { 52 | is_closed_ = true; 53 | } 54 | 55 | void Writer::set_error() 56 | { 57 | has_error_ = true; 58 | } 59 | 60 | bool Writer::is_closed() const 61 | { 62 | return is_closed_; 63 | } 64 | 65 | uint64_t Writer::available_capacity() const 66 | { 67 | return capacity_ - data_.size(); 68 | } 69 | 70 | uint64_t Writer::bytes_pushed() const 71 | { 72 | return pushed_; 73 | } 74 | 75 | string_view Reader::peek() const 76 | { 77 | return data_.peek(); 78 | } 79 | 80 | bool Reader::is_finished() const 81 | { 82 | return is_closed_ && data_.size() == 0; 83 | } 84 | 85 | bool Reader::has_error() const 86 | { 87 | return has_error_; 88 | } 89 | 90 | void Reader::pop( uint64_t len ) 91 | { 92 | popped_ += data_.pop( len ); 93 | } 94 | 95 | uint64_t Reader::bytes_buffered() const 96 | { 97 | return data_.size(); 98 | } 99 | 100 | uint64_t Reader::bytes_popped() const 101 | { 102 | return popped_; 103 | } 104 | -------------------------------------------------------------------------------- /src/byte_stream.hh: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | class Reader; 9 | class Writer; 10 | 11 | class ByteStream 12 | { 13 | protected: 14 | uint64_t capacity_; 15 | uint64_t pushed_; 16 | uint64_t popped_; 17 | bool has_error_; 18 | bool is_closed_; 19 | class StringQueue 20 | { 21 | std::string data_ {}; 22 | uint64_t popped_ { 0 }; 23 | 24 | public: 25 | StringQueue() = default; 26 | uint64_t size() const; // Size of the queue. 27 | void push( char c ); // Push a single byte to the queue. 28 | uint64_t pop( 29 | uint64_t len ); // Pop specified number of bytes, and return the number of bytes that are popped successfully. 30 | std::string_view peek() const; // Return a string_view of the queue. 31 | } data_; 32 | 33 | public: 34 | explicit ByteStream( uint64_t capacity ); 35 | 36 | // Helper functions (provided) to access the ByteStream's Reader and Writer interfaces 37 | Reader& reader(); 38 | const Reader& reader() const; 39 | Writer& writer(); 40 | const Writer& writer() const; 41 | }; 42 | 43 | class Writer : public ByteStream 44 | { 45 | public: 46 | void push( std::string data ); // Push data to stream, but only as much as available capacity allows. 47 | 48 | void close(); // Signal that the stream has reached its ending. Nothing more will be written. 49 | void set_error(); // Signal that the stream suffered an error. 50 | 51 | bool is_closed() const; // Has the stream been closed? 52 | uint64_t available_capacity() const; // How many bytes can be pushed to the stream right now? 53 | uint64_t bytes_pushed() const; // Total number of bytes cumulatively pushed to the stream 54 | }; 55 | 56 | class Reader : public ByteStream 57 | { 58 | public: 59 | std::string_view peek() const; // Peek at the next bytes in the buffer 60 | void pop( uint64_t len ); // Remove `len` bytes from the buffer 61 | 62 | bool is_finished() const; // Is the stream finished (closed and fully popped)? 63 | bool has_error() const; // Has the stream had an error? 64 | 65 | uint64_t bytes_buffered() const; // Number of bytes currently buffered (pushed and not popped) 66 | uint64_t bytes_popped() const; // Total number of bytes cumulatively popped from stream 67 | }; 68 | 69 | /* 70 | * read: A (provided) helper function thats peeks and pops up to `len` bytes 71 | * from a ByteStream Reader into a string; 72 | */ 73 | void read( Reader& reader, uint64_t len, std::string& out ); 74 | -------------------------------------------------------------------------------- /src/byte_stream_helpers.cc: -------------------------------------------------------------------------------- 1 | #include "byte_stream.hh" 2 | 3 | #include 4 | #include 5 | 6 | /* 7 | * read: A helper function thats peeks and pops up to `len` bytes 8 | * from a ByteStream Reader into a string; 9 | */ 10 | void read( Reader& reader, uint64_t len, std::string& out ) 11 | { 12 | out.clear(); 13 | 14 | while ( reader.bytes_buffered() and out.size() < len ) { 15 | auto view = reader.peek(); 16 | 17 | if ( view.empty() ) { 18 | throw std::runtime_error( "Reader::peek() returned empty string_view" ); 19 | } 20 | 21 | view = view.substr( 0, len - out.size() ); // Don't return more bytes than desired. 22 | out += view; 23 | reader.pop( view.size() ); 24 | } 25 | } 26 | 27 | Reader& ByteStream::reader() 28 | { 29 | static_assert( sizeof( Reader ) == sizeof( ByteStream ), 30 | "Please add member variables to the ByteStream base, not the ByteStream Reader." ); 31 | 32 | return static_cast( *this ); // NOLINT(*-downcast) 33 | } 34 | 35 | const Reader& ByteStream::reader() const 36 | { 37 | static_assert( sizeof( Reader ) == sizeof( ByteStream ), 38 | "Please add member variables to the ByteStream base, not the ByteStream Reader." ); 39 | 40 | return static_cast( *this ); // NOLINT(*-downcast) 41 | } 42 | 43 | Writer& ByteStream::writer() 44 | { 45 | static_assert( sizeof( Writer ) == sizeof( ByteStream ), 46 | "Please add member variables to the ByteStream base, not the ByteStream Writer." ); 47 | 48 | return static_cast( *this ); // NOLINT(*-downcast) 49 | } 50 | 51 | const Writer& ByteStream::writer() const 52 | { 53 | static_assert( sizeof( Writer ) == sizeof( ByteStream ), 54 | "Please add member variables to the ByteStream base, not the ByteStream Writer." ); 55 | 56 | return static_cast( *this ); // NOLINT(*-downcast) 57 | } 58 | -------------------------------------------------------------------------------- /src/network_interface.cc: -------------------------------------------------------------------------------- 1 | #include "network_interface.hh" 2 | 3 | #include "arp_message.hh" 4 | #include "ethernet_frame.hh" 5 | 6 | using namespace std; 7 | 8 | // ethernet_address: Ethernet (what ARP calls "hardware") address of the interface 9 | // ip_address: IP (what ARP calls "protocol") address of the interface 10 | NetworkInterface::NetworkInterface( const EthernetAddress& ethernet_address, const Address& ip_address ) 11 | : ethernet_address_( ethernet_address ), ip_address_( ip_address ) 12 | { 13 | cerr << "DEBUG: Network interface has Ethernet address " << to_string( ethernet_address_ ) << " and IP address " 14 | << ip_address.ip() << "\n"; 15 | } 16 | 17 | // dgram: the IPv4 datagram to be sent 18 | // next_hop: the IP address of the interface to send it to (typically a router or default gateway, but 19 | // may also be another host if directly connected to the same network as the destination) 20 | 21 | // Note: the Address type can be converted to a uint32_t (raw 32-bit IP address) by using the 22 | // Address::ipv4_numeric() method. 23 | void NetworkInterface::send_datagram( const InternetDatagram& dgram, const Address& next_hop ) 24 | { 25 | auto ethernet_address = look_for_mapping( next_hop ); 26 | if ( ethernet_address.has_value() ) { 27 | buffer_for_sending( dgram, ethernet_address.value() ); 28 | } else { 29 | buffer_datagrams[next_hop].push( dgram ); 30 | } 31 | } 32 | 33 | // frame: the incoming Ethernet frame 34 | optional NetworkInterface::recv_frame( const EthernetFrame& frame ) 35 | { 36 | if ( frame.header.dst != ethernet_address_ && frame.header.dst != ETHERNET_BROADCAST ) { 37 | return {}; 38 | } 39 | InternetDatagram dgram; 40 | switch ( frame.header.type ) { 41 | case EthernetHeader::TYPE_IPv4: 42 | if ( parse( dgram, frame.payload ) ) { 43 | return dgram; 44 | } 45 | break; 46 | case EthernetHeader::TYPE_ARP: 47 | handle_ARP_payload( frame.payload ); 48 | break; 49 | default: 50 | cerr << "Unknown ethernet frame type\n"; 51 | } 52 | return {}; 53 | } 54 | 55 | // ms_since_last_tick: the number of milliseconds since the last call to this method 56 | void NetworkInterface::tick( const size_t ms_since_last_tick ) 57 | { 58 | timestamp += ms_since_last_tick; 59 | } 60 | 61 | optional NetworkInterface::maybe_send() 62 | { 63 | if ( buffer_frames.empty() ) { 64 | return {}; 65 | } 66 | auto frame = buffer_frames.front(); 67 | buffer_frames.pop(); 68 | return frame; 69 | } 70 | std::optional NetworkInterface::look_for_mapping( const Address& address ) 71 | { 72 | if ( mappings.contains( address ) ) { 73 | auto& [ethernet_address, added_time] = mappings[address]; 74 | if ( timestamp - added_time <= EXPIRE_TIME_IN_MS ) { 75 | return ethernet_address; 76 | } 77 | mappings.erase( address ); 78 | } 79 | send_ARP_request_for( address ); 80 | return {}; 81 | } 82 | 83 | void NetworkInterface::buffer_for_sending( const EthernetFrame& frame ) 84 | { 85 | buffer_frames.push( frame ); 86 | } 87 | 88 | void NetworkInterface::buffer_for_sending( const InternetDatagram& dgram, const EthernetAddress& ethernet_address ) 89 | { 90 | EthernetFrame frame; 91 | frame.header.dst = ethernet_address; 92 | frame.header.src = ethernet_address_; 93 | frame.header.type = EthernetHeader::TYPE_IPv4; 94 | frame.payload = serialize( dgram ); 95 | 96 | buffer_for_sending( frame ); 97 | } 98 | 99 | void NetworkInterface::send_ARP_request_for( const Address& address ) 100 | { 101 | if ( in_flight_ARP.contains( address ) && timestamp - in_flight_ARP[address] <= ARP_REQUEST_INTERVAL ) { 102 | return; 103 | } 104 | in_flight_ARP[address] = timestamp; 105 | 106 | ARPMessage message; 107 | message.opcode = ARPMessage::OPCODE_REQUEST; 108 | message.sender_ethernet_address = ethernet_address_; 109 | message.sender_ip_address = ip_address_.ipv4_numeric(); 110 | message.target_ip_address = address.ipv4_numeric(); 111 | 112 | EthernetFrame frame; 113 | frame.header.dst = ETHERNET_BROADCAST; 114 | frame.header.src = ethernet_address_; 115 | frame.header.type = EthernetHeader::TYPE_ARP; 116 | frame.payload = serialize( message ); 117 | 118 | buffer_for_sending( frame ); 119 | } 120 | 121 | void NetworkInterface::send_ARP_reply_to( const EthernetAddress& ethernet_address, const Address& address ) 122 | { 123 | ARPMessage message; 124 | message.opcode = ARPMessage::OPCODE_REPLY; 125 | message.sender_ethernet_address = ethernet_address_; 126 | message.sender_ip_address = ip_address_.ipv4_numeric(); 127 | message.target_ethernet_address = ethernet_address; 128 | message.target_ip_address = address.ipv4_numeric(); 129 | 130 | EthernetFrame frame; 131 | frame.header.dst = ethernet_address; 132 | frame.header.src = ethernet_address_; 133 | frame.header.type = EthernetHeader::TYPE_ARP; 134 | frame.payload = serialize( message ); 135 | 136 | buffer_for_sending( frame ); 137 | } 138 | 139 | void NetworkInterface::add_mapping( const Address& address, const EthernetAddress& ethernet_address ) 140 | { 141 | mappings[address] = std::make_pair( ethernet_address, timestamp ); 142 | if ( buffer_datagrams.contains( address ) ) { 143 | auto& q = buffer_datagrams[address]; 144 | while ( !q.empty() ) { 145 | buffer_for_sending( q.front(), ethernet_address ); 146 | q.pop(); 147 | } 148 | buffer_datagrams.erase( address ); 149 | } 150 | } 151 | 152 | void NetworkInterface::handle_ARP_payload( const vector& payload ) 153 | { 154 | ARPMessage message; 155 | parse( message, payload ); 156 | 157 | add_mapping( Address::from_ipv4_numeric( message.sender_ip_address ), message.sender_ethernet_address ); 158 | 159 | if ( message.opcode == ARPMessage::OPCODE_REQUEST && message.target_ip_address == ip_address_.ipv4_numeric() ) { 160 | send_ARP_reply_to( message.sender_ethernet_address, Address::from_ipv4_numeric( message.sender_ip_address ) ); 161 | } 162 | } 163 | -------------------------------------------------------------------------------- /src/network_interface.hh: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "address.hh" 4 | #include "ethernet_frame.hh" 5 | #include "ipv4_datagram.hh" 6 | 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | template<> 15 | struct std::hash
16 | { 17 | size_t operator()( const Address& b ) const { return std::hash()( b.to_string() ); } 18 | }; 19 | 20 | // A "network interface" that connects IP (the internet layer, or network layer) 21 | // with Ethernet (the network access layer, or link layer). 22 | 23 | // This module is the lowest layer of a TCP/IP stack 24 | // (connecting IP with the lower-layer network protocol, 25 | // e.g. Ethernet). But the same module is also used repeatedly 26 | // as part of a router: a router generally has many network 27 | // interfaces, and the router's job is to route Internet datagrams 28 | // between the different interfaces. 29 | 30 | // The network interface translates datagrams (coming from the 31 | // "customer," e.g. a TCP/IP stack or router) into Ethernet 32 | // frames. To fill in the Ethernet destination address, it looks up 33 | // the Ethernet address of the next IP hop of each datagram, making 34 | // requests with the [Address Resolution Protocol](\ref rfc::rfc826). 35 | // In the opposite direction, the network interface accepts Ethernet 36 | // frames, checks if they are intended for it, and if so, processes 37 | // the the payload depending on its type. If it's an IPv4 datagram, 38 | // the network interface passes it up the stack. If it's an ARP 39 | // request or reply, the network interface processes the frame 40 | // and learns or replies as necessary. 41 | class NetworkInterface 42 | { 43 | private: 44 | // Time that a mapping is kept in cache 45 | static const size_t EXPIRE_TIME_IN_MS = 30000; 46 | static const size_t ARP_REQUEST_INTERVAL = 5000; 47 | 48 | // Timestamp in ms of the `NetworkInterface`. 49 | // It is incremented when `tick` is called and used for determining when a mapping was set up. 50 | size_t timestamp { 0 }; 51 | 52 | // Mappings from IP addresses to ethernet addresses and their timestamps when they were established 53 | std::unordered_map> mappings {}; 54 | 55 | // A buffer storing unsent datagrams, which will be sent immediately when an ethernet address for an 56 | // IP address of a certain datagram is known. 57 | std::unordered_map> buffer_datagrams {}; 58 | 59 | // A buffer storing unsent ethernet frames, which come from `buffer_datagrams` when their ethernet 60 | // addresses are determined. The frames will be sent when `maybe_send` is called. 61 | std::queue buffer_frames {}; 62 | 63 | // All ARP requests in flight and the timestamps when they are sent 64 | std::unordered_map in_flight_ARP {}; 65 | 66 | // Ethernet (known as hardware, network-access, or link-layer) address of the interface 67 | EthernetAddress ethernet_address_; 68 | 69 | // IP (known as Internet-layer or network-layer) address of the interface 70 | Address ip_address_; 71 | 72 | // Buffer an ethernet frame for sending 73 | void buffer_for_sending( const EthernetFrame& ); 74 | // Buffer an internet datagram for sending 75 | void buffer_for_sending( const InternetDatagram&, const EthernetAddress& ); 76 | 77 | // Look up an existing mapping for a certain address. 78 | // If it does not exist or has expired, and no previous ARP request sent within `ARP_REQUEST_INTERVAL`ms 79 | // is for this address, an ARP request will be sent. 80 | std::optional look_for_mapping( const Address& address ); 81 | 82 | // Send an ARP request for an address 83 | void send_ARP_request_for( const Address& ); 84 | // Send an ARP reply to an ethernet address 85 | void send_ARP_reply_to( const EthernetAddress&, const Address& ); 86 | 87 | // Add a mapping from IP address to ethernet address. If there are any buffered datagram to the 88 | // added IP address, buffer them for sending. 89 | void add_mapping( const Address&, const EthernetAddress& ); 90 | 91 | // Handle the payload of an ARP ethernet frame. Set up mapping for the source IP and ethernet addresses, 92 | // and send an ARP reply if the frame is an ARP request for our IP address. 93 | void handle_ARP_payload( const std::vector& ); 94 | 95 | public: 96 | // Construct a network interface with given Ethernet (network-access-layer) and IP (internet-layer) 97 | // addresses 98 | NetworkInterface( const EthernetAddress& ethernet_address, const Address& ip_address ); 99 | 100 | // Access queue of Ethernet frames awaiting transmission 101 | std::optional maybe_send(); 102 | 103 | // Sends an IPv4 datagram, encapsulated in an Ethernet frame (if it knows the Ethernet destination 104 | // address). Will need to use [ARP](\ref rfc::rfc826) to look up the Ethernet destination address 105 | // for the next hop. 106 | // ("Sending" is accomplished by making sure maybe_send() will release the frame when next called, 107 | // but please consider the frame sent as soon as it is generated.) 108 | void send_datagram( const InternetDatagram& dgram, const Address& next_hop ); 109 | 110 | // Receives an Ethernet frame and responds appropriately. 111 | // If type is IPv4, returns the datagram. 112 | // If type is ARP request, learn a mapping from the "sender" fields, and send an ARP reply. 113 | // If type is ARP reply, learn a mapping from the "sender" fields. 114 | std::optional recv_frame( const EthernetFrame& frame ); 115 | 116 | // Called periodically when time elapses 117 | void tick( size_t ms_since_last_tick ); 118 | }; 119 | -------------------------------------------------------------------------------- /src/reassembler.cc: -------------------------------------------------------------------------------- 1 | #include "reassembler.hh" 2 | 3 | using namespace std; 4 | 5 | void Reassembler::insert( uint64_t first_index, string data, bool is_last_substring, Writer& output ) 6 | { 7 | if ( is_last_substring ) { 8 | last_substring_inserted = true; 9 | } 10 | 11 | if ( first_index < front ) { 12 | const uint64_t to_pop = front - first_index; 13 | if ( to_pop >= data.size() ) { 14 | return; 15 | } 16 | data = data.substr( to_pop ); 17 | first_index = 0; 18 | } else { 19 | first_index -= front; 20 | first_index = std::min( first_index, output.available_capacity() ); 21 | } 22 | // Trim bytes that exceed capacity 23 | data.resize( std::min( data.size(), output.available_capacity() - first_index ) ); 24 | buffer.resize( std::max( buffer.size(), first_index + data.size() ) ); 25 | 26 | for ( uint64_t i = 0; i < data.size(); ++i ) { 27 | auto& [validity, byte] = buffer[i + first_index]; 28 | byte = data[i]; 29 | bytes_valid -= validity; 30 | validity = true; 31 | bytes_valid += validity; 32 | } 33 | 34 | data.clear(); 35 | while ( !buffer.empty() && buffer.front().first ) { 36 | front += 1; 37 | bytes_valid -= 1; 38 | 39 | data.push_back( buffer.front().second ); 40 | buffer.pop_front(); 41 | } 42 | if ( !data.empty() ) { 43 | output.push( data ); 44 | } 45 | 46 | if ( last_substring_inserted && buffer.empty() ) { 47 | output.close(); 48 | } 49 | } 50 | 51 | uint64_t Reassembler::bytes_pending() const 52 | { 53 | // Your code here. 54 | return bytes_valid; 55 | } 56 | -------------------------------------------------------------------------------- /src/reassembler.hh: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "byte_stream.hh" 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | class Reassembler 11 | { 12 | private: 13 | // The index of the first byte stored in the Reassembler. 14 | uint64_t front { 0 }; 15 | // Number of set bytes in data. 16 | uint64_t bytes_valid { 0 }; 17 | // Whether the last substring has been inserted. 18 | bool last_substring_inserted { false }; 19 | // A pair represents validity and data of a byte. 20 | std::deque> buffer {}; 21 | 22 | public: 23 | /* 24 | * Insert a new substring to be reassembled into a ByteStream. 25 | * `first_index`: the index of the first byte of the substring 26 | * `data`: the substring itself 27 | * `is_last_substring`: this substring represents the end of the stream 28 | * `output`: a mutable reference to the Writer 29 | * 30 | * The Reassembler's job is to reassemble the indexed substrings (possibly out-of-order 31 | * and possibly overlapping) back into the original ByteStream. As soon as the Reassembler 32 | * learns the next byte in the stream, it should write it to the output. 33 | * 34 | * If the Reassembler learns about bytes that fit within the stream's available capacity 35 | * but can't yet be written (because earlier bytes remain unknown), it should store them 36 | * internally until the gaps are filled in. 37 | * 38 | * The Reassembler should discard any bytes that lie beyond the stream's available capacity 39 | * (i.e., bytes that couldn't be written even if earlier gaps get filled in). 40 | * 41 | * The Reassembler should close the stream after writing the last byte. 42 | */ 43 | void insert( uint64_t first_index, std::string data, bool is_last_substring, Writer& output ); 44 | 45 | // How many bytes are stored in the Reassembler itself? 46 | uint64_t bytes_pending() const; 47 | }; 48 | -------------------------------------------------------------------------------- /src/router.cc: -------------------------------------------------------------------------------- 1 | #include "router.hh" 2 | 3 | #include 4 | #include 5 | 6 | using namespace std; 7 | 8 | // route_prefix: The "up-to-32-bit" IPv4 address prefix to match the datagram's destination address against 9 | // prefix_length: For this route to be applicable, how many high-order (most-significant) bits of 10 | // the route_prefix will need to match the corresponding bits of the datagram's destination address? 11 | // next_hop: The IP address of the next hop. Will be empty if the network is directly attached to the router (in 12 | // which case, the next hop address should be the datagram's final destination). 13 | // interface_num: The index of the interface to send the datagram out on. 14 | void Router::add_route( const uint32_t route_prefix, 15 | const uint8_t prefix_length, 16 | const optional
next_hop, 17 | const size_t interface_num ) 18 | { 19 | cerr << "DEBUG: adding route " << Address::from_ipv4_numeric( route_prefix ).ip() << "/" 20 | << static_cast( prefix_length ) << " => " << ( next_hop.has_value() ? next_hop->ip() : "(direct)" ) 21 | << " on interface " << interface_num << "\n"; 22 | 23 | std::shared_ptr node = root; 24 | for ( size_t i = 0; i < prefix_length; ++i ) { 25 | unsigned int bit = static_cast( route_prefix & ( 1U << ( 31 - i ) ) ); 26 | if ( node->next.at( bit ) == nullptr ) { 27 | node->next.at( bit ) = std::make_shared(); 28 | } 29 | node = node->next.at( bit ); 30 | } 31 | 32 | node->value = { next_hop, interface_num }; 33 | } 34 | 35 | std::optional, size_t>> Router::match( uint32_t raw_address ) const 36 | { 37 | std::optional, size_t>> result {}; 38 | 39 | std::shared_ptr node = root; 40 | for ( size_t i = 0; i < 32; ++i ) { 41 | if ( node->value.has_value() ) { 42 | result = node->value; 43 | } 44 | 45 | unsigned int bit = static_cast( raw_address & ( 1U << ( 31 - i ) ) ); 46 | if ( node->next.at( bit ) == nullptr ) { 47 | break; 48 | } 49 | node = node->next.at( bit ); 50 | } 51 | 52 | if ( node->value.has_value() ) { 53 | result = node->value; 54 | } 55 | return result; 56 | } 57 | 58 | void Router::route_datagram( InternetDatagram&& dgram ) 59 | { 60 | InternetDatagram datagram = std::move( dgram ); 61 | if ( datagram.header.ttl <= 1U ) { 62 | return; 63 | } 64 | datagram.header.ttl -= 1; 65 | datagram.header.compute_checksum(); 66 | 67 | auto matching = match( dgram.header.dst ); 68 | if ( matching.has_value() ) { 69 | const auto& [next_hop, interface_num] = matching.value(); 70 | Address address = next_hop.has_value() ? next_hop.value() : Address::from_ipv4_numeric( datagram.header.dst ); 71 | 72 | interface( interface_num ).send_datagram( datagram, address ); 73 | } 74 | } 75 | 76 | void Router::route() 77 | { 78 | for ( size_t i = 0; i < interfaces_.size(); ++i ) { 79 | auto dgram = interface( i ).maybe_receive(); 80 | while ( dgram.has_value() ) { 81 | route_datagram( std::move( dgram.value() ) ); 82 | dgram = interface( i ).maybe_receive(); 83 | } 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /src/router.hh: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "network_interface.hh" 4 | 5 | #include 6 | #include 7 | 8 | // A wrapper for NetworkInterface that makes the host-side 9 | // interface asynchronous: instead of returning received datagrams 10 | // immediately (from the `recv_frame` method), it stores them for 11 | // later retrieval. Otherwise, behaves identically to the underlying 12 | // implementation of NetworkInterface. 13 | class AsyncNetworkInterface : public NetworkInterface 14 | { 15 | std::queue datagrams_in_ {}; 16 | 17 | public: 18 | using NetworkInterface::NetworkInterface; 19 | 20 | // Construct from a NetworkInterface 21 | explicit AsyncNetworkInterface( NetworkInterface&& interface ) : NetworkInterface( interface ) {} 22 | 23 | // \brief Receives and Ethernet frame and responds appropriately. 24 | 25 | // - If type is IPv4, pushes to the `datagrams_out` queue for later retrieval by the owner. 26 | // - If type is ARP request, learn a mapping from the "sender" fields, and send an ARP reply. 27 | // - If type is ARP reply, learn a mapping from the "target" fields. 28 | // 29 | // \param[in] frame the incoming Ethernet frame 30 | void recv_frame( const EthernetFrame& frame ) 31 | { 32 | auto optional_dgram = NetworkInterface::recv_frame( frame ); 33 | if ( optional_dgram.has_value() ) { 34 | datagrams_in_.push( std::move( optional_dgram.value() ) ); 35 | } 36 | }; 37 | 38 | // Access queue of Internet datagrams that have been received 39 | std::optional maybe_receive() 40 | { 41 | if ( datagrams_in_.empty() ) { 42 | return {}; 43 | } 44 | 45 | InternetDatagram datagram = std::move( datagrams_in_.front() ); 46 | datagrams_in_.pop(); 47 | return datagram; 48 | } 49 | }; 50 | 51 | // A router that has multiple network interfaces and 52 | // performs longest-prefix-match routing between them. 53 | class Router 54 | { 55 | 56 | // The router's collection of network interfaces 57 | std::vector interfaces_ {}; 58 | 59 | // This abstracts nodes of a trie. Trie is a kind of data structure that 60 | // can be used to match prefixes. 61 | struct Node 62 | { 63 | std::array, 2> next { nullptr, nullptr }; 64 | std::optional, size_t>> value {}; 65 | }; 66 | 67 | // The root node of the trie. 68 | std::shared_ptr root { std::make_shared() }; 69 | 70 | // Perform a longest prefix matching and return the matched Address and 71 | // interface_num if found. 72 | std::optional, size_t>> match( uint32_t raw_address ) const; 73 | 74 | // Send a single internet datagram to appropriate interface and address. 75 | void route_datagram( InternetDatagram&& dgram ); 76 | 77 | public: 78 | // Add an interface to the router 79 | // interface: an already-constructed network interface 80 | // returns the index of the interface after it has been added to the router 81 | size_t add_interface( AsyncNetworkInterface&& interface ) 82 | { 83 | interfaces_.push_back( std::move( interface ) ); 84 | return interfaces_.size() - 1; 85 | } 86 | 87 | // Access an interface by index 88 | AsyncNetworkInterface& interface( size_t N ) { return interfaces_.at( N ); } 89 | 90 | // Add a route (a forwarding rule) 91 | void add_route( uint32_t route_prefix, 92 | uint8_t prefix_length, 93 | std::optional
next_hop, 94 | size_t interface_num ); 95 | 96 | // Route packets between the interfaces. For each interface, use the 97 | // maybe_receive() method to consume every incoming datagram and 98 | // send it on one of interfaces to the correct next hop. The router 99 | // chooses the outbound interface and next-hop as specified by the 100 | // route with the longest prefix_length that matches the datagram's 101 | // destination address. 102 | void route(); 103 | }; 104 | -------------------------------------------------------------------------------- /src/tcp_receiver.cc: -------------------------------------------------------------------------------- 1 | #include "tcp_receiver.hh" 2 | 3 | using namespace std; 4 | 5 | void TCPReceiver::receive( TCPSenderMessage message, Reassembler& reassembler, Writer& inbound_stream ) 6 | { 7 | auto& [seqno, SYN, payload, FIN] = message; 8 | if ( SYN ) { 9 | synced = true; 10 | initial_seqno = seqno; 11 | } 12 | uint64_t const absolute_index = seqno.unwrap( initial_seqno, inbound_stream.bytes_pushed() ) - !SYN; 13 | reassembler.insert( absolute_index, payload, FIN, inbound_stream ); 14 | } 15 | 16 | TCPReceiverMessage TCPReceiver::send( const Writer& inbound_stream ) const 17 | { 18 | TCPReceiverMessage message; 19 | if ( synced ) { 20 | message.ackno 21 | = Wrap32::wrap( inbound_stream.bytes_pushed(), initial_seqno ) + synced + inbound_stream.is_closed(); 22 | } 23 | if ( inbound_stream.available_capacity() < UINT16_MAX ) { 24 | message.window_size = inbound_stream.available_capacity(); 25 | } else { 26 | message.window_size = UINT16_MAX; 27 | } 28 | return message; 29 | } 30 | -------------------------------------------------------------------------------- /src/tcp_receiver.hh: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "reassembler.hh" 4 | #include "tcp_receiver_message.hh" 5 | #include "tcp_sender_message.hh" 6 | 7 | class TCPReceiver 8 | { 9 | bool synced { false }; 10 | Wrap32 initial_seqno { 0 }; 11 | 12 | public: 13 | /* 14 | * The TCPReceiver receives TCPSenderMessages, inserting their payload into the Reassembler 15 | * at the correct stream index. 16 | */ 17 | void receive( TCPSenderMessage message, Reassembler& reassembler, Writer& inbound_stream ); 18 | 19 | /* The TCPReceiver sends TCPReceiverMessages back to the TCPSender. */ 20 | TCPReceiverMessage send( const Writer& inbound_stream ) const; 21 | }; 22 | -------------------------------------------------------------------------------- /src/tcp_sender.cc: -------------------------------------------------------------------------------- 1 | #include "tcp_sender.hh" 2 | #include "tcp_config.hh" 3 | 4 | #include 5 | #include 6 | 7 | using namespace std; 8 | 9 | Timer::Timer( uint64_t init_RTO ) : timer( init_RTO ), initial_RTO( init_RTO ), RTO( init_RTO ), running( false ) {} 10 | 11 | void Timer::elapse( uint64_t time_elapsed ) 12 | { 13 | if ( running ) { 14 | timer -= std::min( timer, time_elapsed ); 15 | } 16 | } 17 | 18 | void Timer::double_RTO() 19 | { 20 | if ( RTO & ( 1ULL << 63 ) ) { 21 | RTO = UINT64_MAX; 22 | } else { 23 | RTO <<= 1; 24 | } 25 | } 26 | 27 | void Timer::reset() 28 | { 29 | timer = RTO; 30 | } 31 | 32 | void Timer::start() 33 | { 34 | running = true; 35 | } 36 | 37 | void Timer::stop() 38 | { 39 | running = false; 40 | } 41 | 42 | bool Timer::expired() const 43 | { 44 | return timer == 0; 45 | } 46 | 47 | bool Timer::is_stopped() const 48 | { 49 | return !running || expired(); 50 | } 51 | 52 | void Timer::restore_RTO() 53 | { 54 | RTO = initial_RTO; 55 | } 56 | 57 | void Timer::restart() 58 | { 59 | reset(); 60 | start(); 61 | } 62 | 63 | /* TCPSender constructor (uses a random ISN if none given) */ 64 | TCPSender::TCPSender( uint64_t initial_RTO_ms, optional fixed_isn ) 65 | : isn_( fixed_isn.value_or( Wrap32 { random_device()() } ) ), timer( initial_RTO_ms ) 66 | {} 67 | 68 | uint64_t TCPSender::sequence_numbers_in_flight() const 69 | { 70 | return pushed_no - ack_no; 71 | } 72 | 73 | uint64_t TCPSender::consecutive_retransmissions() const 74 | { 75 | return retransmissions; 76 | } 77 | 78 | optional TCPSender::maybe_send() 79 | { 80 | if ( !messages_to_be_sent.empty() ) { 81 | if ( timer.is_stopped() ) { 82 | timer.restart(); 83 | } 84 | 85 | auto msg = messages_to_be_sent.front(); 86 | messages_to_be_sent.pop(); 87 | return *msg; 88 | } 89 | return std::nullopt; 90 | } 91 | 92 | void TCPSender::push( Reader& outbound_stream ) 93 | { 94 | uint64_t allowed_no = received_ack_no + std::max( window_size, uint16_t { 1 } ); 95 | if ( pushed_no == 0 ) { 96 | std::shared_ptr message = std::make_shared( send_empty_message() ); 97 | 98 | if ( outbound_stream.is_finished() && !FIN_sent && allowed_no - pushed_no > 1 ) { 99 | message->FIN = true; 100 | } 101 | 102 | if ( message->FIN ) { 103 | FIN_sent = true; 104 | } 105 | 106 | pushed_no += message->sequence_length(); 107 | messages_to_be_sent.push( message ); 108 | outstanding_messages.push( message ); 109 | } 110 | 111 | while ( ( outbound_stream.bytes_buffered() > 0 || ( outbound_stream.is_finished() && !FIN_sent ) ) 112 | && pushed_no < allowed_no ) { 113 | uint64_t msg_len 114 | = std::min( { allowed_no - pushed_no, outbound_stream.bytes_buffered(), TCPConfig::MAX_PAYLOAD_SIZE } ); 115 | Buffer buffer { std::string { outbound_stream.peek().substr( 0, msg_len ) } }; 116 | outbound_stream.pop( msg_len ); 117 | 118 | bool FIN = outbound_stream.is_finished() && !FIN_sent && allowed_no - pushed_no > msg_len; 119 | 120 | std::shared_ptr message = std::make_shared(); 121 | message->seqno = Wrap32::wrap( pushed_no, isn_ ); 122 | message->SYN = false; 123 | message->payload = buffer; 124 | message->FIN = FIN; 125 | 126 | if ( message->FIN ) { 127 | FIN_sent = true; 128 | } 129 | 130 | pushed_no += message->sequence_length(); 131 | messages_to_be_sent.push( message ); 132 | outstanding_messages.push( message ); 133 | } 134 | } 135 | 136 | TCPSenderMessage TCPSender::send_empty_message() const 137 | { 138 | TCPSenderMessage message; 139 | message.seqno = Wrap32::wrap( pushed_no, isn_ ); 140 | message.SYN = ( pushed_no == 0 ); 141 | message.payload = {}; 142 | message.FIN = false; 143 | 144 | return message; 145 | } 146 | 147 | void TCPSender::receive( const TCPReceiverMessage& msg ) 148 | { 149 | if ( msg.ackno.has_value() ) { 150 | uint64_t received_ackno = msg.ackno.value().unwrap( isn_, ack_no ); 151 | if ( received_ackno <= pushed_no ) { 152 | while ( !outstanding_messages.empty() 153 | && received_ackno >= ack_no + outstanding_messages.front()->sequence_length() ) { 154 | ack_no += outstanding_messages.front()->sequence_length(); 155 | outstanding_messages.pop(); 156 | 157 | timer.restore_RTO(); 158 | retransmissions = 0; 159 | if ( !outstanding_messages.empty() ) { 160 | timer.restart(); 161 | } else { 162 | timer.stop(); 163 | } 164 | } 165 | received_ack_no = std::max( received_ack_no, received_ackno ); 166 | } 167 | } 168 | window_size = msg.window_size; 169 | } 170 | 171 | void TCPSender::tick( const size_t ms_since_last_tick ) 172 | { 173 | timer.elapse( ms_since_last_tick ); 174 | if ( timer.expired() ) { 175 | messages_to_be_sent.push( outstanding_messages.front() ); 176 | 177 | if ( window_size ) { 178 | timer.double_RTO(); 179 | retransmissions += 1; 180 | } 181 | 182 | timer.restart(); 183 | } 184 | } 185 | -------------------------------------------------------------------------------- /src/tcp_sender.hh: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "byte_stream.hh" 4 | #include "tcp_receiver_message.hh" 5 | #include "tcp_sender_message.hh" 6 | 7 | /* 8 | * The Timer class used for determining when to resend an outstanding message. 9 | */ 10 | class Timer 11 | { 12 | uint64_t timer; 13 | uint64_t initial_RTO; 14 | uint64_t RTO; 15 | bool running; 16 | 17 | /* It turns out that `reset` and `start` are always used together, so I 18 | * made them private and created `restart` as their replacement */ 19 | void reset(); 20 | void start(); 21 | 22 | public: 23 | explicit Timer( uint64_t init_RTO ); 24 | void elapse( uint64_t time_elapsed ); 25 | void double_RTO(); 26 | void stop(); 27 | bool expired() const; 28 | 29 | /* A timer is considered stopped if it was manually stopped or expired */ 30 | bool is_stopped() const; 31 | void restore_RTO(); 32 | void restart(); 33 | }; 34 | 35 | class TCPSender 36 | { 37 | Wrap32 isn_; 38 | 39 | uint16_t window_size { 1 }; 40 | 41 | /* The greatest valid ackno ever received */ 42 | uint64_t received_ack_no { 0 }; 43 | 44 | /* 45 | * The actual ackno the sender use. It differs from `received_ack_no` because we 46 | * only consider bytes in a completely acknowledged segment acknowledged. Those 47 | * bytes within `received_ack_no` but not in an acknowledged segment are not acked 48 | * and may be retransmitted if needed. 49 | */ 50 | uint64_t ack_no { 0 }; 51 | 52 | /* Number of bytes (including SYN and FIN) pushed to be sent */ 53 | uint64_t pushed_no { 0 }; 54 | 55 | /* Number of consecutive retransmissions */ 56 | uint64_t retransmissions { 0 }; 57 | 58 | /* 59 | * The purpose of using `shared_ptr` rather than directly storing `TCPSenderMessage` 60 | * instances is to avoid wasteful copying. 61 | * This can also be achieved by overloading move assignment operator for 62 | * `TCPSenderMessage`, but that can be even more cumbersome. 63 | * It is notable that the payload inside the `TCPSenderMessage` is already implemented 64 | * with `shared_ptr`, so even if `shared_ptr` is not used here the payload data will not 65 | * be copied and stored multiple times. 66 | */ 67 | 68 | /* A buffer storing messages to be sent */ 69 | std::queue> messages_to_be_sent {}; 70 | std::queue> outstanding_messages {}; 71 | 72 | Timer timer; 73 | 74 | /* Whether a FIN has already been sent */ 75 | bool FIN_sent { false }; 76 | 77 | public: 78 | /* Construct TCP sender with given default Retransmission Timeout and possible ISN */ 79 | TCPSender( uint64_t initial_RTO_ms, std::optional fixed_isn ); 80 | 81 | /* Push bytes from the outbound stream */ 82 | void push( Reader& outbound_stream ); 83 | 84 | /* Send a TCPSenderMessage if needed (or empty optional otherwise) */ 85 | std::optional maybe_send(); 86 | 87 | /* Generate an empty TCPSenderMessage */ 88 | TCPSenderMessage send_empty_message() const; 89 | 90 | /* Receive an act on a TCPReceiverMessage from the peer's receiver */ 91 | void receive( const TCPReceiverMessage& msg ); 92 | 93 | /* Time has passed by the given # of milliseconds since the last time the tick() method was called. */ 94 | void tick( uint64_t ms_since_last_tick ); 95 | 96 | /* Accessors for use in testing */ 97 | uint64_t sequence_numbers_in_flight() const; // How many sequence numbers are outstanding? 98 | uint64_t consecutive_retransmissions() const; // How many consecutive *re*transmissions have happened? 99 | }; 100 | -------------------------------------------------------------------------------- /src/wrapping_integers.cc: -------------------------------------------------------------------------------- 1 | #include "wrapping_integers.hh" 2 | 3 | using namespace std; 4 | 5 | Wrap32 Wrap32::wrap( uint64_t n, Wrap32 zero_point ) 6 | { 7 | return zero_point + static_cast( n ); 8 | } 9 | 10 | uint64_t Wrap32::unwrap( Wrap32 zero_point, uint64_t checkpoint ) const 11 | { 12 | uint32_t const low_bits = raw_value_ - zero_point.raw_value_; 13 | uint64_t const high_bits = checkpoint >> 32; 14 | 15 | auto distance = []( uint64_t a, uint64_t b ) { return a > b ? a - b : b - a; }; 16 | 17 | uint64_t const r = high_bits << 32 | low_bits; 18 | uint64_t const d = distance( checkpoint, r ); 19 | 20 | if ( distance( checkpoint, r + ( 1ULL << 32 ) ) < d ) { 21 | return r + ( 1ULL << 32 ); 22 | } 23 | if ( distance( checkpoint, r - ( 1ULL << 32 ) ) < d ) { 24 | return r - ( 1ULL << 32 ); 25 | } 26 | return r; 27 | } 28 | -------------------------------------------------------------------------------- /src/wrapping_integers.hh: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | /* 6 | * The Wrap32 type represents a 32-bit unsigned integer that: 7 | * - starts at an arbitrary "zero point" (initial value), and 8 | * - wraps back to zero when it reaches 2^32 - 1. 9 | */ 10 | 11 | class Wrap32 12 | { 13 | protected: 14 | uint32_t raw_value_ {}; 15 | 16 | public: 17 | explicit Wrap32( uint32_t raw_value ) : raw_value_( raw_value ) {} 18 | 19 | /* Construct a Wrap32 given an absolute sequence number n and the zero point. */ 20 | static Wrap32 wrap( uint64_t n, Wrap32 zero_point ); 21 | 22 | /* 23 | * The unwrap method returns an absolute sequence number that wraps to this Wrap32, given the zero point 24 | * and a "checkpoint": another absolute sequence number near the desired answer. 25 | * 26 | * There are many possible absolute sequence numbers that all wrap to the same Wrap32. 27 | * The unwrap method should return the one that is closest to the checkpoint. 28 | */ 29 | uint64_t unwrap( Wrap32 zero_point, uint64_t checkpoint ) const; 30 | 31 | Wrap32 operator+( uint32_t n ) const { return Wrap32 { raw_value_ + n }; } 32 | bool operator==( const Wrap32& other ) const { return raw_value_ == other.raw_value_; } 33 | }; 34 | -------------------------------------------------------------------------------- /tests/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_library(minnow_testing_debug STATIC common.cc) 2 | 3 | add_library(minnow_testing_sanitized EXCLUDE_FROM_ALL STATIC common.cc) 4 | target_compile_options(minnow_testing_sanitized PUBLIC ${SANITIZING_FLAGS}) 5 | 6 | add_custom_target(functionality_testing) 7 | add_custom_target(speed_testing) 8 | 9 | macro(add_test_exec exec_name) 10 | add_executable("${exec_name}_sanitized" EXCLUDE_FROM_ALL "${exec_name}.cc") 11 | target_compile_options("${exec_name}_sanitized" PUBLIC ${SANITIZING_FLAGS}) 12 | target_link_options("${exec_name}_sanitized" PUBLIC ${SANITIZING_FLAGS}) 13 | target_link_libraries("${exec_name}_sanitized" minnow_testing_sanitized) 14 | target_link_libraries("${exec_name}_sanitized" minnow_sanitized) 15 | target_link_libraries("${exec_name}_sanitized" util_sanitized) 16 | add_dependencies(functionality_testing "${exec_name}_sanitized") 17 | 18 | add_executable("${exec_name}" EXCLUDE_FROM_ALL "${exec_name}.cc") 19 | target_link_libraries("${exec_name}" minnow_testing_debug) 20 | target_link_libraries("${exec_name}" minnow_debug) 21 | target_link_libraries("${exec_name}" util_debug) 22 | add_dependencies(functionality_testing "${exec_name}") 23 | endmacro(add_test_exec) 24 | 25 | macro(add_speed_test exec_name) 26 | add_executable("${exec_name}" EXCLUDE_FROM_ALL "${exec_name}.cc") 27 | target_compile_options("${exec_name}" PUBLIC "-O2") 28 | target_link_libraries("${exec_name}" minnow_optimized) 29 | target_link_libraries("${exec_name}" util_optimized) 30 | add_dependencies(speed_testing "${exec_name}") 31 | endmacro(add_speed_test) 32 | 33 | add_test_exec(byte_stream_basics) 34 | add_test_exec(byte_stream_capacity) 35 | add_test_exec(byte_stream_one_write) 36 | add_test_exec(byte_stream_two_writes) 37 | add_test_exec(byte_stream_many_writes) 38 | add_test_exec(byte_stream_stress_test) 39 | 40 | add_test_exec(reassembler_single) 41 | add_test_exec(reassembler_cap) 42 | add_test_exec(reassembler_seq) 43 | add_test_exec(reassembler_dup) 44 | add_test_exec(reassembler_holes) 45 | add_test_exec(reassembler_overlapping) 46 | add_test_exec(reassembler_win) 47 | 48 | add_test_exec(wrapping_integers_cmp) 49 | add_test_exec(wrapping_integers_wrap) 50 | add_test_exec(wrapping_integers_unwrap) 51 | add_test_exec(wrapping_integers_roundtrip) 52 | add_test_exec(wrapping_integers_extra) 53 | 54 | add_test_exec(recv_connect) 55 | add_test_exec(recv_transmit) 56 | add_test_exec(recv_window) 57 | add_test_exec(recv_reorder) 58 | add_test_exec(recv_reorder_more) 59 | add_test_exec(recv_close) 60 | add_test_exec(recv_special) 61 | 62 | add_test_exec(send_connect) 63 | add_test_exec(send_transmit) 64 | add_test_exec(send_retx) 65 | add_test_exec(send_window) 66 | add_test_exec(send_ack) 67 | add_test_exec(send_close) 68 | add_test_exec(send_extra) 69 | 70 | add_test_exec(net_interface) 71 | 72 | add_test_exec(router) 73 | 74 | add_speed_test(byte_stream_speed_test) 75 | add_speed_test(reassembler_speed_test) 76 | -------------------------------------------------------------------------------- /tests/byte_stream_basics.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 | void all_zeroes( ByteStreamTestHarness& test ) 10 | { 11 | test.execute( BytesBuffered { 0 } ); 12 | test.execute( AvailableCapacity { 15 } ); 13 | test.execute( BytesPushed { 0 } ); 14 | test.execute( BytesPopped { 0 } ); 15 | } 16 | 17 | int main() 18 | { 19 | try { 20 | { 21 | ByteStreamTestHarness test { "construction", 15 }; 22 | test.execute( IsClosed { false } ); 23 | test.execute( IsFinished { false } ); 24 | test.execute( HasError { false } ); 25 | all_zeroes( test ); 26 | } 27 | 28 | { 29 | ByteStreamTestHarness test { "close", 15 }; 30 | test.execute( Close {} ); 31 | test.execute( IsClosed { true } ); 32 | test.execute( IsFinished { true } ); 33 | test.execute( HasError { false } ); 34 | all_zeroes( test ); 35 | } 36 | 37 | { 38 | ByteStreamTestHarness test { "set-error", 15 }; 39 | test.execute( SetError {} ); 40 | test.execute( IsClosed { false } ); 41 | test.execute( IsFinished { false } ); 42 | test.execute( HasError { true } ); 43 | all_zeroes( test ); 44 | } 45 | 46 | } catch ( const exception& e ) { 47 | cerr << "Exception: " << e.what() << "\n"; 48 | return EXIT_FAILURE; 49 | } 50 | 51 | return EXIT_SUCCESS; 52 | } 53 | -------------------------------------------------------------------------------- /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 | { 11 | try { 12 | { 13 | ByteStreamTestHarness test { "overwrite", 2 }; 14 | 15 | test.execute( Push { "cat" } ); 16 | test.execute( IsClosed { false } ); 17 | test.execute( BufferEmpty { false } ); 18 | test.execute( IsFinished { false } ); 19 | test.execute( BytesPopped { 0 } ); 20 | test.execute( BytesPushed { 2 } ); 21 | test.execute( AvailableCapacity { 0 } ); 22 | test.execute( BytesBuffered { 2 } ); 23 | test.execute( Peek { "ca" } ); 24 | 25 | test.execute( Push { "t" } ); 26 | 27 | test.execute( IsClosed { false } ); 28 | test.execute( BufferEmpty { false } ); 29 | test.execute( IsFinished { false } ); 30 | test.execute( BytesPopped { 0 } ); 31 | test.execute( BytesPushed { 2 } ); 32 | test.execute( AvailableCapacity { 0 } ); 33 | test.execute( BytesBuffered { 2 } ); 34 | test.execute( Peek { "ca" } ); 35 | } 36 | 37 | { 38 | ByteStreamTestHarness test { "overwrite-clear-overwrite", 2 }; 39 | 40 | test.execute( Push { "cat" } ); 41 | test.execute( BytesPushed { 2 } ); 42 | test.execute( Pop { 2 } ); 43 | test.execute( Push { "tac" } ); 44 | 45 | test.execute( IsClosed { false } ); 46 | test.execute( BufferEmpty { false } ); 47 | test.execute( IsFinished { false } ); 48 | test.execute( BytesPopped { 2 } ); 49 | test.execute( BytesPushed { 4 } ); 50 | test.execute( AvailableCapacity { 0 } ); 51 | test.execute( BytesBuffered { 2 } ); 52 | test.execute( Peek { "ta" } ); 53 | } 54 | 55 | { 56 | ByteStreamTestHarness test { "overwrite-pop-overwrite", 2 }; 57 | 58 | test.execute( Push { "cat" } ); 59 | test.execute( BytesPushed { 2 } ); 60 | test.execute( Pop { 1 } ); 61 | test.execute( Push { "tac" } ); 62 | 63 | test.execute( IsClosed { false } ); 64 | test.execute( BufferEmpty { false } ); 65 | test.execute( IsFinished { false } ); 66 | test.execute( BytesPopped { 1 } ); 67 | test.execute( BytesPushed { 3 } ); 68 | test.execute( AvailableCapacity { 0 } ); 69 | test.execute( BytesBuffered { 2 } ); 70 | test.execute( Peek { "at" } ); 71 | } 72 | 73 | { 74 | ByteStreamTestHarness test { "peeks", 2 }; 75 | test.execute( Push { "" } ); 76 | test.execute( Push { "" } ); 77 | test.execute( Push { "" } ); 78 | test.execute( Push { "" } ); 79 | test.execute( Push { "" } ); 80 | test.execute( Push { "cat" } ); 81 | test.execute( Push { "" } ); 82 | test.execute( Push { "" } ); 83 | test.execute( Push { "" } ); 84 | test.execute( Push { "" } ); 85 | test.execute( Push { "" } ); 86 | test.execute( Peek { "ca" } ); 87 | test.execute( Peek { "ca" } ); 88 | test.execute( BytesBuffered { 2 } ); 89 | test.execute( Peek { "ca" } ); 90 | test.execute( Peek { "ca" } ); 91 | test.execute( Pop { 1 } ); 92 | test.execute( Push { "" } ); 93 | test.execute( Push { "" } ); 94 | test.execute( Push { "" } ); 95 | test.execute( Peek { "a" } ); 96 | test.execute( Peek { "a" } ); 97 | test.execute( BytesBuffered { 1 } ); 98 | } 99 | 100 | } catch ( const exception& e ) { 101 | cerr << "Exception: " << e.what() << endl; 102 | return EXIT_FAILURE; 103 | } 104 | 105 | return EXIT_SUCCESS; 106 | } 107 | -------------------------------------------------------------------------------- /tests/byte_stream_many_writes.cc: -------------------------------------------------------------------------------- 1 | #include "byte_stream.hh" 2 | #include "byte_stream_test_harness.hh" 3 | #include "random.hh" 4 | 5 | #include 6 | #include 7 | #include 8 | 9 | using namespace std; 10 | 11 | int main() 12 | { 13 | try { 14 | auto rd = get_random_engine(); 15 | const size_t NREPS = 1000; 16 | const size_t MIN_WRITE = 10; 17 | const size_t MAX_WRITE = 200; 18 | const size_t CAPACITY = MAX_WRITE * NREPS; 19 | 20 | { 21 | ByteStreamTestHarness test { "many writes", CAPACITY }; 22 | 23 | size_t acc = 0; 24 | for ( size_t i = 0; i < NREPS; ++i ) { 25 | const size_t size = MIN_WRITE + ( rd() % ( MAX_WRITE - MIN_WRITE ) ); 26 | string d( size, 0 ); 27 | generate( d.begin(), d.end(), [&] { return 'a' + ( rd() % 26 ); } ); 28 | 29 | test.execute( Push { d } ); 30 | acc += size; 31 | 32 | test.execute( IsClosed { false } ); 33 | test.execute( BufferEmpty { false } ); 34 | test.execute( IsFinished { false } ); 35 | test.execute( BytesPopped { 0 } ); 36 | test.execute( BytesPushed { acc } ); 37 | test.execute( AvailableCapacity { CAPACITY - acc } ); 38 | test.execute( BytesBuffered { acc } ); 39 | } 40 | } 41 | 42 | } catch ( const exception& e ) { 43 | cerr << "Exception: " << e.what() << endl; 44 | return EXIT_FAILURE; 45 | } 46 | 47 | return EXIT_SUCCESS; 48 | } 49 | -------------------------------------------------------------------------------- /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 | { 11 | try { 12 | { 13 | ByteStreamTestHarness test { "write-end-pop", 15 }; 14 | 15 | test.execute( Push { "cat" } ); 16 | 17 | test.execute( IsClosed { false } ); 18 | test.execute( BufferEmpty { false } ); 19 | test.execute( IsFinished { false } ); 20 | test.execute( BytesPopped { 0 } ); 21 | test.execute( BytesPushed { 3 } ); 22 | test.execute( AvailableCapacity { 12 } ); 23 | test.execute( BytesBuffered { 3 } ); 24 | test.execute( Peek { "cat" } ); 25 | 26 | test.execute( Close {} ); 27 | 28 | test.execute( IsClosed { true } ); 29 | test.execute( BufferEmpty { false } ); 30 | test.execute( IsFinished { false } ); 31 | test.execute( BytesPopped { 0 } ); 32 | test.execute( BytesPushed { 3 } ); 33 | test.execute( AvailableCapacity { 12 } ); 34 | test.execute( BytesBuffered { 3 } ); 35 | test.execute( Peek { "cat" } ); 36 | 37 | test.execute( Pop { 3 } ); 38 | 39 | test.execute( IsClosed { true } ); 40 | test.execute( BufferEmpty { true } ); 41 | test.execute( IsFinished { true } ); 42 | test.execute( BytesPopped { 3 } ); 43 | test.execute( BytesPushed { 3 } ); 44 | test.execute( AvailableCapacity { 15 } ); 45 | test.execute( BytesBuffered { 0 } ); 46 | } 47 | 48 | { 49 | ByteStreamTestHarness test { "write-pop-end", 15 }; 50 | 51 | test.execute( Push { "cat" } ); 52 | 53 | test.execute( IsClosed { false } ); 54 | test.execute( BufferEmpty { false } ); 55 | test.execute( IsFinished { false } ); 56 | test.execute( BytesPopped { 0 } ); 57 | test.execute( BytesPushed { 3 } ); 58 | test.execute( AvailableCapacity { 12 } ); 59 | test.execute( BytesBuffered { 3 } ); 60 | test.execute( Peek { "cat" } ); 61 | 62 | test.execute( Pop { 3 } ); 63 | 64 | test.execute( IsClosed { false } ); 65 | test.execute( BufferEmpty { true } ); 66 | test.execute( IsFinished { false } ); 67 | test.execute( BytesPopped { 3 } ); 68 | test.execute( BytesPushed { 3 } ); 69 | test.execute( AvailableCapacity { 15 } ); 70 | test.execute( BytesBuffered { 0 } ); 71 | 72 | test.execute( Close {} ); 73 | 74 | test.execute( IsClosed { true } ); 75 | test.execute( BufferEmpty { true } ); 76 | test.execute( IsFinished { true } ); 77 | test.execute( BytesPopped { 3 } ); 78 | test.execute( BytesPushed { 3 } ); 79 | test.execute( AvailableCapacity { 15 } ); 80 | test.execute( BytesBuffered { 0 } ); 81 | } 82 | 83 | { 84 | ByteStreamTestHarness test { "write-pop2-end", 15 }; 85 | 86 | test.execute( Push { "cat" } ); 87 | 88 | test.execute( IsClosed { false } ); 89 | test.execute( BufferEmpty { false } ); 90 | test.execute( IsFinished { false } ); 91 | test.execute( BytesPopped { 0 } ); 92 | test.execute( BytesPushed { 3 } ); 93 | test.execute( AvailableCapacity { 12 } ); 94 | test.execute( BytesBuffered { 3 } ); 95 | test.execute( Peek { "cat" } ); 96 | 97 | test.execute( Pop { 1 } ); 98 | 99 | test.execute( IsClosed { false } ); 100 | test.execute( BufferEmpty { false } ); 101 | test.execute( IsFinished { false } ); 102 | test.execute( BytesPopped { 1 } ); 103 | test.execute( BytesPushed { 3 } ); 104 | test.execute( AvailableCapacity { 13 } ); 105 | test.execute( BytesBuffered { 2 } ); 106 | test.execute( Peek { "at" } ); 107 | 108 | test.execute( Pop { 2 } ); 109 | 110 | test.execute( IsClosed { false } ); 111 | test.execute( BufferEmpty { true } ); 112 | test.execute( IsFinished { false } ); 113 | test.execute( BytesPopped { 3 } ); 114 | test.execute( BytesPushed { 3 } ); 115 | test.execute( AvailableCapacity { 15 } ); 116 | test.execute( BytesBuffered { 0 } ); 117 | 118 | test.execute( Close {} ); 119 | 120 | test.execute( IsClosed { true } ); 121 | test.execute( BufferEmpty { true } ); 122 | test.execute( IsFinished { true } ); 123 | test.execute( BytesPopped { 3 } ); 124 | test.execute( BytesPushed { 3 } ); 125 | test.execute( AvailableCapacity { 15 } ); 126 | test.execute( BytesBuffered { 0 } ); 127 | } 128 | 129 | } catch ( const exception& e ) { 130 | cerr << "Exception: " << e.what() << endl; 131 | return EXIT_FAILURE; 132 | } 133 | 134 | return EXIT_SUCCESS; 135 | } 136 | -------------------------------------------------------------------------------- /tests/byte_stream_speed_test.cc: -------------------------------------------------------------------------------- 1 | #include "byte_stream.hh" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | using namespace std; 12 | using namespace std::chrono; 13 | 14 | void speed_test( const size_t input_len, // NOLINT(bugprone-easily-swappable-parameters) 15 | const size_t capacity, // NOLINT(bugprone-easily-swappable-parameters) 16 | const size_t random_seed, // NOLINT(bugprone-easily-swappable-parameters) 17 | const size_t write_size, // NOLINT(bugprone-easily-swappable-parameters) 18 | const size_t read_size ) // NOLINT(bugprone-easily-swappable-parameters) 19 | { 20 | // Generate the data to be written 21 | const string data = [&random_seed, &input_len] { 22 | default_random_engine rd { random_seed }; 23 | uniform_int_distribution ud; 24 | string ret; 25 | for ( size_t i = 0; i < input_len; ++i ) { 26 | ret += ud( rd ); 27 | } 28 | return ret; 29 | }(); 30 | 31 | // Split the data into segments before writing 32 | queue split_data; 33 | for ( size_t i = 0; i < data.size(); i += write_size ) { 34 | split_data.emplace( data.substr( i, write_size ) ); 35 | } 36 | 37 | ByteStream bs { capacity }; 38 | string output_data; 39 | output_data.reserve( data.size() ); 40 | 41 | const auto start_time = steady_clock::now(); 42 | while ( not bs.reader().is_finished() ) { 43 | if ( split_data.empty() ) { 44 | if ( not bs.writer().is_closed() ) { 45 | bs.writer().close(); 46 | } 47 | } else { 48 | if ( split_data.front().size() <= bs.writer().available_capacity() ) { 49 | bs.writer().push( move( split_data.front() ) ); 50 | split_data.pop(); 51 | } 52 | } 53 | 54 | if ( bs.reader().bytes_buffered() ) { 55 | auto peeked = bs.reader().peek().substr( 0, read_size ); 56 | if ( peeked.empty() ) { 57 | throw runtime_error( "ByteStream::reader().peek() returned empty view" ); 58 | } 59 | output_data += peeked; 60 | bs.reader().pop( peeked.size() ); 61 | } 62 | } 63 | 64 | const auto stop_time = steady_clock::now(); 65 | 66 | if ( data != output_data ) { 67 | throw runtime_error( "Mismatch between data written and read" ); 68 | } 69 | 70 | auto test_duration = duration_cast>( stop_time - start_time ); 71 | auto bytes_per_second = static_cast( input_len ) / test_duration.count(); 72 | auto bits_per_second = 8 * bytes_per_second; 73 | auto gigabits_per_second = bits_per_second / 1e9; 74 | 75 | fstream debug_output; 76 | debug_output.open( "/dev/tty" ); 77 | 78 | cout << "ByteStream with capacity=" << capacity << ", write_size=" << write_size << ", read_size=" << read_size 79 | << " reached " << fixed << setprecision( 2 ) << gigabits_per_second << " Gbit/s.\n"; 80 | 81 | debug_output << " ByteStream throughput: " << fixed << setprecision( 2 ) << gigabits_per_second 82 | << " Gbit/s\n"; 83 | 84 | if ( gigabits_per_second < 0.1 ) { 85 | throw runtime_error( "ByteStream did not meet minimum speed of 0.1 Gbit/s." ); 86 | } 87 | } 88 | 89 | void program_body() 90 | { 91 | speed_test( 1e7, 32768, 789, 1500, 128 ); 92 | } 93 | 94 | int main() 95 | { 96 | try { 97 | program_body(); 98 | } catch ( const exception& e ) { 99 | cerr << "Exception: " << e.what() << "\n"; 100 | return EXIT_FAILURE; 101 | } 102 | 103 | return EXIT_SUCCESS; 104 | } 105 | -------------------------------------------------------------------------------- /tests/byte_stream_stress_test.cc: -------------------------------------------------------------------------------- 1 | #include "byte_stream_test_harness.hh" 2 | 3 | #include 4 | #include 5 | 6 | using namespace std; 7 | 8 | void stress_test( const size_t input_len, // NOLINT(bugprone-easily-swappable-parameters) 9 | const size_t capacity, // NOLINT(bugprone-easily-swappable-parameters) 10 | const size_t random_seed ) // NOLINT(bugprone-easily-swappable-parameters) 11 | { 12 | default_random_engine rd { random_seed }; 13 | 14 | const string data = [&rd, &input_len] { 15 | uniform_int_distribution ud; 16 | string ret; 17 | for ( size_t i = 0; i < input_len; ++i ) { 18 | ret += ud( rd ); 19 | } 20 | return ret; 21 | }(); 22 | 23 | ByteStreamTestHarness bs { "stress test input=" + to_string( input_len ) + ", capacity=" + to_string( capacity ), 24 | capacity }; 25 | 26 | size_t expected_bytes_pushed {}; 27 | size_t expected_bytes_popped {}; 28 | size_t expected_available_capacity { capacity }; 29 | while ( expected_bytes_pushed < data.size() or expected_bytes_popped < data.size() ) { 30 | bs.execute( BytesPushed { expected_bytes_pushed } ); 31 | bs.execute( BytesPopped { expected_bytes_popped } ); 32 | bs.execute( AvailableCapacity { expected_available_capacity } ); 33 | bs.execute( BytesBuffered { expected_bytes_pushed - expected_bytes_popped } ); 34 | 35 | /* write something */ 36 | uniform_int_distribution bytes_to_push_dist { 0, data.size() - expected_bytes_pushed }; 37 | const size_t amount_to_push = bytes_to_push_dist( rd ); 38 | bs.execute( Push { data.substr( expected_bytes_pushed, amount_to_push ) } ); 39 | expected_bytes_pushed += min( amount_to_push, expected_available_capacity ); 40 | expected_available_capacity -= min( amount_to_push, expected_available_capacity ); 41 | 42 | bs.execute( BytesPushed { expected_bytes_pushed } ); 43 | bs.execute( AvailableCapacity { expected_available_capacity } ); 44 | 45 | if ( expected_bytes_pushed == data.size() ) { 46 | bs.execute( Close {} ); 47 | } 48 | 49 | /* read something */ 50 | const size_t peek_size = bs.peek_size(); 51 | if ( ( expected_bytes_pushed != expected_bytes_popped ) and peek_size == 0 ) { 52 | throw runtime_error( "ByteStream::reader().peek() returned empty view" ); 53 | } 54 | if ( expected_bytes_popped + peek_size > expected_bytes_pushed ) { 55 | throw runtime_error( "ByteStream::reader().peek() returned too-large view" ); 56 | } 57 | 58 | bs.execute( PeekOnce { data.substr( expected_bytes_popped, peek_size ) } ); 59 | 60 | uniform_int_distribution bytes_to_pop_dist { 0, peek_size }; 61 | const size_t amount_to_pop = bytes_to_pop_dist( rd ); 62 | 63 | bs.execute( Pop { amount_to_pop } ); 64 | expected_bytes_popped += amount_to_pop; 65 | expected_available_capacity += amount_to_pop; 66 | bs.execute( BytesPopped { expected_bytes_popped } ); 67 | } 68 | 69 | bs.execute( IsClosed { true } ); 70 | bs.execute( IsFinished { true } ); 71 | } 72 | 73 | void program_body() 74 | { 75 | stress_test( 19, 3, 10110 ); 76 | stress_test( 18, 17, 12345 ); 77 | stress_test( 1111, 17, 98765 ); 78 | stress_test( 4097, 4096, 11101 ); 79 | } 80 | 81 | int main() 82 | { 83 | try { 84 | program_body(); 85 | } catch ( const exception& e ) { 86 | cerr << "Exception: " << e.what() << "\n"; 87 | return EXIT_FAILURE; 88 | } 89 | 90 | return EXIT_SUCCESS; 91 | } 92 | -------------------------------------------------------------------------------- /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 | { 11 | try { 12 | { 13 | ByteStreamTestHarness test { "write-write-end-pop-pop", 15 }; 14 | 15 | test.execute( Push { "cat" } ); 16 | 17 | test.execute( IsClosed { false } ); 18 | test.execute( BufferEmpty { false } ); 19 | test.execute( IsFinished { false } ); 20 | test.execute( BytesPopped { 0 } ); 21 | test.execute( BytesPushed { 3 } ); 22 | test.execute( AvailableCapacity { 12 } ); 23 | test.execute( BytesBuffered { 3 } ); 24 | test.execute( Peek { "cat" } ); 25 | 26 | test.execute( Push { "tac" } ); 27 | 28 | test.execute( IsClosed { false } ); 29 | test.execute( BufferEmpty { false } ); 30 | test.execute( IsFinished { false } ); 31 | test.execute( BytesPopped { 0 } ); 32 | test.execute( BytesPushed { 6 } ); 33 | test.execute( AvailableCapacity { 9 } ); 34 | test.execute( BytesBuffered { 6 } ); 35 | test.execute( Peek { "cattac" } ); 36 | 37 | test.execute( Close {} ); 38 | 39 | test.execute( IsClosed { true } ); 40 | test.execute( BufferEmpty { false } ); 41 | test.execute( IsFinished { false } ); 42 | test.execute( BytesPopped { 0 } ); 43 | test.execute( BytesPushed { 6 } ); 44 | test.execute( AvailableCapacity { 9 } ); 45 | test.execute( BytesBuffered { 6 } ); 46 | test.execute( Peek { "cattac" } ); 47 | 48 | test.execute( Pop { 2 } ); 49 | 50 | test.execute( IsClosed { true } ); 51 | test.execute( BufferEmpty { false } ); 52 | test.execute( IsFinished { false } ); 53 | test.execute( BytesPopped { 2 } ); 54 | test.execute( BytesPushed { 6 } ); 55 | test.execute( AvailableCapacity { 11 } ); 56 | test.execute( BytesBuffered { 4 } ); 57 | test.execute( Peek { "ttac" } ); 58 | 59 | test.execute( Pop { 4 } ); 60 | 61 | test.execute( IsClosed { true } ); 62 | test.execute( BufferEmpty { true } ); 63 | test.execute( IsFinished { true } ); 64 | test.execute( BytesPopped { 6 } ); 65 | test.execute( BytesPushed { 6 } ); 66 | test.execute( AvailableCapacity { 15 } ); 67 | test.execute( BytesBuffered { 0 } ); 68 | } 69 | 70 | { 71 | ByteStreamTestHarness test { "write-pop-write-end-pop", 15 }; 72 | 73 | test.execute( Push { "cat" } ); 74 | 75 | test.execute( IsClosed { false } ); 76 | test.execute( BufferEmpty { false } ); 77 | test.execute( IsFinished { false } ); 78 | test.execute( BytesPopped { 0 } ); 79 | test.execute( BytesPushed { 3 } ); 80 | test.execute( AvailableCapacity { 12 } ); 81 | test.execute( BytesBuffered { 3 } ); 82 | test.execute( Peek { "cat" } ); 83 | 84 | test.execute( Pop { 2 } ); 85 | 86 | test.execute( IsClosed { false } ); 87 | test.execute( BufferEmpty { false } ); 88 | test.execute( IsFinished { false } ); 89 | test.execute( BytesPopped { 2 } ); 90 | test.execute( BytesPushed { 3 } ); 91 | test.execute( AvailableCapacity { 14 } ); 92 | test.execute( BytesBuffered { 1 } ); 93 | test.execute( Peek { "t" } ); 94 | 95 | test.execute( Push { "tac" } ); 96 | 97 | test.execute( IsClosed { false } ); 98 | test.execute( BufferEmpty { false } ); 99 | test.execute( IsFinished { false } ); 100 | test.execute( BytesPopped { 2 } ); 101 | test.execute( BytesPushed { 6 } ); 102 | test.execute( AvailableCapacity { 11 } ); 103 | test.execute( BytesBuffered { 4 } ); 104 | test.execute( Peek { "ttac" } ); 105 | 106 | test.execute( Close {} ); 107 | 108 | test.execute( IsClosed { true } ); 109 | test.execute( BufferEmpty { false } ); 110 | test.execute( IsFinished { false } ); 111 | test.execute( BytesPopped { 2 } ); 112 | test.execute( BytesPushed { 6 } ); 113 | test.execute( AvailableCapacity { 11 } ); 114 | test.execute( BytesBuffered { 4 } ); 115 | test.execute( Peek { "ttac" } ); 116 | 117 | test.execute( Pop { 4 } ); 118 | 119 | test.execute( IsClosed { true } ); 120 | test.execute( BufferEmpty { true } ); 121 | test.execute( IsFinished { true } ); 122 | test.execute( BytesPopped { 6 } ); 123 | test.execute( BytesPushed { 6 } ); 124 | test.execute( AvailableCapacity { 15 } ); 125 | test.execute( BytesBuffered { 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/common.cc: -------------------------------------------------------------------------------- 1 | #include "common.hh" 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | using namespace std; 8 | 9 | void Printer::diagnostic( std::string_view test_name, 10 | const vector>& steps_executed, 11 | const string& failing_step, 12 | const exception& e ) const 13 | { 14 | const string quote = Printer::with_color( Printer::def, "\"" ); 15 | cerr << "\nThe test " << quote << Printer::with_color( Printer::def, test_name ) << quote 16 | << " failed after these steps:\n\n"; 17 | unsigned int step_num = 0; 18 | for ( const auto& [str, col] : steps_executed ) { 19 | cerr << " " << step_num++ << "." 20 | << "\t" << with_color( col, str ) << "\n"; 21 | } 22 | cerr << with_color( red, " ***** Unsuccessful " + failing_step + " *****\n\n" ); 23 | 24 | cerr << with_color( red, demangle( typeid( e ).name() ) ) << ": " << with_color( def, e.what() ) << "\n\n"; 25 | } 26 | 27 | string Printer::prettify( string_view str, size_t max_length ) 28 | { 29 | ostringstream ss; 30 | const string_view str_prefix = str.substr( 0, max_length ); 31 | for ( const uint8_t ch : str_prefix ) { 32 | if ( isprint( ch ) ) { 33 | ss << ch; 34 | } else { 35 | ss << "\\x" << fixed << setw( 2 ) << setfill( '0' ) << hex << static_cast( ch ); 36 | } 37 | } 38 | if ( str.size() > str_prefix.size() ) { 39 | ss << "..."; 40 | } 41 | return ss.str(); 42 | } 43 | 44 | Printer::Printer() : is_terminal_( isatty( STDERR_FILENO ) or getenv( "MAKE_TERMOUT" ) ) {} 45 | 46 | string Printer::with_color( int color_value, string_view str ) const 47 | { 48 | string ret; 49 | if ( is_terminal_ ) { 50 | ret += "\033[1;" + to_string( color_value ) + "m"; 51 | } 52 | 53 | ret += str; 54 | 55 | if ( is_terminal_ ) { 56 | ret += "\033[m"; 57 | } 58 | 59 | return ret; 60 | } 61 | -------------------------------------------------------------------------------- /tests/common.hh: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "conversions.hh" 4 | #include "exception.hh" 5 | 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | class ExpectationViolation : public std::runtime_error 14 | { 15 | public: 16 | static constexpr std::string boolstr( bool b ) { return b ? "true" : "false"; } 17 | 18 | explicit ExpectationViolation( const std::string& msg ) : std::runtime_error( msg ) {} 19 | 20 | template 21 | inline ExpectationViolation( const std::string& property_name, const T& expected, const T& actual ); 22 | }; 23 | 24 | template 25 | ExpectationViolation::ExpectationViolation( const std::string& property_name, const T& expected, const T& actual ) 26 | : ExpectationViolation { "The object should have had " + property_name + " = " + to_string( expected ) 27 | + ", but instead it was " + to_string( actual ) + "." } 28 | {} 29 | 30 | template<> 31 | inline ExpectationViolation::ExpectationViolation( const std::string& property_name, 32 | const bool& expected, 33 | const bool& actual ) 34 | : ExpectationViolation { "The object should have had " + property_name + " = " + boolstr( expected ) 35 | + ", but instead it was " + boolstr( actual ) + "." } 36 | {} 37 | 38 | template 39 | struct TestStep 40 | { 41 | virtual std::string str() const = 0; 42 | virtual void execute( T& ) const = 0; 43 | virtual uint8_t color() const = 0; 44 | 45 | TestStep() = default; 46 | TestStep( const TestStep& other ) = default; 47 | TestStep( TestStep&& other ) noexcept = default; 48 | TestStep& operator=( const TestStep& other ) = default; 49 | TestStep& operator=( TestStep&& other ) noexcept = default; 50 | virtual ~TestStep() = default; 51 | }; 52 | 53 | class Printer 54 | { 55 | bool is_terminal_; 56 | 57 | public: 58 | Printer(); 59 | 60 | static constexpr int red = 31; 61 | static constexpr int green = 32; 62 | static constexpr int blue = 34; 63 | static constexpr int def = 39; 64 | 65 | std::string with_color( int color_value, std::string_view str ) const; 66 | 67 | static std::string prettify( std::string_view str, size_t max_length = 32 ); 68 | 69 | void diagnostic( std::string_view test_name, 70 | const std::vector>& steps_executed, 71 | const std::string& failing_step, 72 | const std::exception& e ) const; 73 | }; 74 | 75 | template 76 | class TestHarness 77 | { 78 | std::string test_name_; 79 | T obj_; 80 | 81 | std::vector> steps_executed_ {}; 82 | Printer pr_ {}; 83 | 84 | protected: 85 | explicit TestHarness( std::string test_name, std::string_view desc, T&& object ) 86 | : test_name_( std::move( test_name ) ), obj_( std::move( object ) ) 87 | { 88 | steps_executed_.emplace_back( "Initialized " + demangle( typeid( T ).name() ) + " with " + std::string { desc }, 89 | Printer::def ); 90 | } 91 | 92 | const T& object() const { return obj_; } 93 | 94 | public: 95 | void execute( const TestStep& step ) 96 | { 97 | try { 98 | step.execute( obj_ ); 99 | steps_executed_.emplace_back( step.str(), step.color() ); 100 | } catch ( const ExpectationViolation& e ) { 101 | pr_.diagnostic( test_name_, steps_executed_, step.str(), e ); 102 | throw std::runtime_error { "The test \"" + test_name_ + "\" failed." }; 103 | } catch ( const std::exception& e ) { 104 | pr_.diagnostic( test_name_, steps_executed_, step.str(), e ); 105 | throw std::runtime_error { "The test \"" + test_name_ + "\" made your code throw an exception." }; 106 | } 107 | } 108 | }; 109 | 110 | template 111 | struct Expectation : public TestStep 112 | { 113 | std::string str() const override { return "Expectation: " + description(); } 114 | virtual std::string description() const = 0; 115 | uint8_t color() const override { return Printer::green; } 116 | }; 117 | 118 | template 119 | struct Action : public TestStep 120 | { 121 | std::string str() const override { return "Action: " + description(); } 122 | virtual std::string description() const = 0; 123 | uint8_t color() const override { return Printer::blue; } 124 | }; 125 | 126 | template 127 | struct ExpectBool : public Expectation 128 | { 129 | bool value_; 130 | explicit ExpectBool( bool value ) : value_( value ) {} 131 | std::string description() const override { return name() + " = " + ExpectationViolation::boolstr( value_ ); } 132 | virtual std::string name() const = 0; 133 | virtual bool value( T& ) const = 0; 134 | void execute( T& obj ) const override 135 | { 136 | const bool result = value( obj ); 137 | if ( result != value_ ) { 138 | throw ExpectationViolation { name(), value_, result }; 139 | } 140 | } 141 | }; 142 | 143 | template 144 | struct ExpectNumber : public Expectation 145 | { 146 | Num num_; 147 | explicit ExpectNumber( Num num ) : num_( num ) {} 148 | std::string description() const override { return name() + " = " + to_string( num_ ); } 149 | virtual std::string name() const = 0; 150 | virtual Num value( T& ) const = 0; 151 | void execute( T& obj ) const override 152 | { 153 | const Num result { value( obj ) }; 154 | if ( result != num_ ) { 155 | throw ExpectationViolation { name(), num_, result }; 156 | } 157 | } 158 | }; 159 | -------------------------------------------------------------------------------- /tests/conversions.hh: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "wrapping_integers.hh" 4 | 5 | #include 6 | #include 7 | #include 8 | 9 | // https://stackoverflow.com/questions/33399594/making-a-user-defined-class-stdto-stringable 10 | 11 | namespace minnow_conversions { 12 | using std::to_string; 13 | 14 | class DebugWrap32 : public Wrap32 15 | { 16 | public: 17 | uint32_t debug_get_raw_value() { return raw_value_; } 18 | }; 19 | 20 | inline std::string to_string( Wrap32 i ) 21 | { 22 | return "Wrap32<" + std::to_string( DebugWrap32 { i }.debug_get_raw_value() ) + ">"; 23 | } 24 | 25 | template 26 | std::string to_string( const std::optional& v ) 27 | { 28 | if ( v.has_value() ) { 29 | return "Some(" + to_string( v.value() ) + ")"; 30 | } 31 | 32 | return "None"; 33 | } 34 | 35 | template 36 | std::string as_string( T&& t ) 37 | { 38 | return to_string( std::forward( t ) ); 39 | } 40 | } // namespace minnow_conversions 41 | 42 | template 43 | std::string to_string( T&& t ) 44 | { 45 | return minnow_conversions::as_string( std::forward( t ) ); 46 | } 47 | 48 | inline std::ostream& operator<<( std::ostream& os, Wrap32 a ) 49 | { 50 | return os << to_string( a ); 51 | } 52 | 53 | inline bool operator!=( Wrap32 a, Wrap32 b ) 54 | { 55 | return not( a == b ); 56 | } 57 | 58 | inline int64_t operator-( Wrap32 a, Wrap32 b ) 59 | { 60 | return static_cast( minnow_conversions::DebugWrap32 { a }.debug_get_raw_value() ) 61 | - static_cast( minnow_conversions::DebugWrap32 { b }.debug_get_raw_value() ); 62 | } 63 | -------------------------------------------------------------------------------- /tests/network_interface_test_harness.hh: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | #include "arp_message.hh" 8 | #include "common.hh" 9 | #include "network_interface.hh" 10 | 11 | class NetworkInterfaceTestHarness : public TestHarness 12 | { 13 | public: 14 | NetworkInterfaceTestHarness( std::string test_name, 15 | const EthernetAddress& ethernet_address, 16 | const Address& ip_address ) 17 | : TestHarness( move( test_name ), 18 | "eth=" + to_string( ethernet_address ) + ", ip=" + ip_address.ip(), 19 | NetworkInterface { ethernet_address, ip_address } ) 20 | {} 21 | }; 22 | 23 | inline std::string summary( const EthernetFrame& frame ); 24 | 25 | struct SendDatagram : public Action 26 | { 27 | InternetDatagram dgram; 28 | Address next_hop; 29 | 30 | std::string description() const override 31 | { 32 | return "request to send datagram (to next hop " + next_hop.ip() + "): " + dgram.header.to_string(); 33 | } 34 | 35 | void execute( NetworkInterface& interface ) const override { interface.send_datagram( dgram, next_hop ); } 36 | 37 | SendDatagram( InternetDatagram d, Address n ) : dgram( std::move( d ) ), next_hop( n ) {} 38 | }; 39 | 40 | template 41 | bool equal( const T& t1, const T& t2 ) 42 | { 43 | const std::vector t1s = serialize( t1 ); 44 | const std::vector t2s = serialize( t2 ); 45 | 46 | std::string t1concat; 47 | for ( const auto& x : t1s ) { 48 | t1concat.append( x ); 49 | } 50 | 51 | std::string t2concat; 52 | for ( const auto& x : t2s ) { 53 | t2concat.append( x ); 54 | } 55 | 56 | return t1concat == t2concat; 57 | } 58 | 59 | struct ReceiveFrame : public Action 60 | { 61 | EthernetFrame frame; 62 | std::optional expected; 63 | 64 | std::string description() const override { return "frame arrives (" + summary( frame ) + ")"; } 65 | void execute( NetworkInterface& interface ) const override 66 | { 67 | const std::optional result = interface.recv_frame( frame ); 68 | 69 | if ( ( not result.has_value() ) and ( not expected.has_value() ) ) { 70 | return; 71 | } 72 | 73 | if ( result.has_value() and not expected.has_value() ) { 74 | throw ExpectationViolation( 75 | "an arriving Ethernet frame was passed up the stack as an Internet datagram, but was not expected to be " 76 | "(did destination address match our interface?)" ); 77 | } 78 | 79 | if ( expected.has_value() and not result.has_value() ) { 80 | throw ExpectationViolation( 81 | "an arriving Ethernet frame was expected to be passed up the stack as an Internet datagram, but wasn't" ); 82 | } 83 | 84 | if ( not equal( result.value(), expected.value() ) ) { 85 | throw ExpectationViolation( 86 | std::string( "NetworkInterface::recv_frame() produced a different Internet datagram than was expected: " ) 87 | + "actual={" + result.value().header.to_string() + "}" ); 88 | } 89 | } 90 | 91 | ReceiveFrame( EthernetFrame f, std::optional e ) 92 | : frame( std::move( f ) ), expected( std::move( e ) ) 93 | {} 94 | }; 95 | 96 | struct ExpectFrame : public Expectation 97 | { 98 | EthernetFrame expected; 99 | 100 | std::string description() const override { return "frame transmitted (" + summary( expected ) + ")"; } 101 | void execute( NetworkInterface& interface ) const override 102 | { 103 | auto frame = interface.maybe_send(); 104 | if ( not frame.has_value() ) { 105 | throw ExpectationViolation( "NetworkInterface was expected to send an Ethernet frame, but did not" ); 106 | } 107 | 108 | if ( not equal( frame.value(), expected ) ) { 109 | throw ExpectationViolation( "NetworkInterface sent a different Ethernet frame than was expected: actual={" 110 | + summary( frame.value() ) + "}" ); 111 | } 112 | } 113 | 114 | explicit ExpectFrame( EthernetFrame e ) : expected( std::move( e ) ) {} 115 | }; 116 | 117 | struct ExpectNoFrame : public Expectation 118 | { 119 | std::string description() const override { return "no frame transmitted"; } 120 | void execute( NetworkInterface& interface ) const override 121 | { 122 | if ( interface.maybe_send().has_value() ) { 123 | throw ExpectationViolation( "NetworkInterface sent an Ethernet frame although none was expected" ); 124 | } 125 | } 126 | }; 127 | 128 | struct Tick : public Action 129 | { 130 | size_t _ms; 131 | 132 | std::string description() const override { return to_string( _ms ) + " ms pass"; } 133 | void execute( NetworkInterface& interface ) const override { interface.tick( _ms ); } 134 | 135 | explicit Tick( const size_t ms ) : _ms( ms ) {} 136 | }; 137 | 138 | inline std::string concat( std::vector& buffers ) 139 | { 140 | return std::accumulate( 141 | buffers.begin(), buffers.end(), std::string {}, []( const std::string& x, const Buffer& y ) { 142 | return x + static_cast( y ); 143 | } ); 144 | } 145 | 146 | inline std::string summary( const EthernetFrame& frame ) 147 | { 148 | std::string out = frame.header.to_string() + ", payload: "; 149 | switch ( frame.header.type ) { 150 | case EthernetHeader::TYPE_IPv4: { 151 | InternetDatagram dgram; 152 | if ( parse( dgram, frame.payload ) ) { 153 | out.append( "IPv4: " + dgram.header.to_string() + " payload=\"" 154 | + Printer::prettify( concat( dgram.payload ) ) + "\"" ); 155 | } else { 156 | out.append( "bad IPv4 datagram" ); 157 | } 158 | } break; 159 | case EthernetHeader::TYPE_ARP: { 160 | ARPMessage arp; 161 | if ( parse( arp, frame.payload ) ) { 162 | out.append( "ARP: " + arp.to_string() ); 163 | } else { 164 | out.append( "bad ARP message" ); 165 | } 166 | } break; 167 | default: 168 | out.append( "unknown frame type" ); 169 | break; 170 | } 171 | return out; 172 | } 173 | -------------------------------------------------------------------------------- /tests/reassembler_cap.cc: -------------------------------------------------------------------------------- 1 | #include "reassembler_test_harness.hh" 2 | 3 | #include 4 | #include 5 | 6 | using namespace std; 7 | 8 | int main() 9 | { 10 | try { 11 | { 12 | ReassemblerTestHarness test { "all within capacity", 2 }; 13 | 14 | test.execute( Insert { "ab", 0 } ); 15 | test.execute( BytesPushed( 2 ) ); 16 | test.execute( BytesPending( 0 ) ); 17 | test.execute( ReadAll( "ab" ) ); 18 | 19 | test.execute( Insert { "cd", 2 } ); 20 | test.execute( BytesPushed( 4 ) ); 21 | test.execute( BytesPending( 0 ) ); 22 | test.execute( ReadAll( "cd" ) ); 23 | 24 | test.execute( Insert { "ef", 4 } ); 25 | test.execute( BytesPushed( 6 ) ); 26 | test.execute( BytesPending( 0 ) ); 27 | test.execute( ReadAll( "ef" ) ); 28 | } 29 | 30 | { 31 | ReassemblerTestHarness test { "insert beyond capacity", 2 }; 32 | 33 | test.execute( Insert { "ab", 0 } ); 34 | test.execute( BytesPushed( 2 ) ); 35 | test.execute( BytesPending( 0 ) ); 36 | 37 | test.execute( Insert { "cd", 2 } ); 38 | test.execute( BytesPushed( 2 ) ); 39 | test.execute( BytesPending( 0 ) ); 40 | 41 | test.execute( ReadAll( "ab" ) ); 42 | test.execute( BytesPushed( 2 ) ); 43 | test.execute( BytesPending( 0 ) ); 44 | 45 | test.execute( Insert { "cd", 2 } ); 46 | test.execute( BytesPushed( 4 ) ); 47 | test.execute( BytesPending( 0 ) ); 48 | 49 | test.execute( ReadAll( "cd" ) ); 50 | } 51 | 52 | { 53 | ReassemblerTestHarness test { "overlapping inserts", 1 }; 54 | 55 | test.execute( Insert { "ab", 0 } ); 56 | test.execute( BytesPushed( 1 ) ); 57 | test.execute( BytesPending( 0 ) ); 58 | 59 | test.execute( Insert { "ab", 0 } ); 60 | test.execute( BytesPushed( 1 ) ); 61 | test.execute( BytesPending( 0 ) ); 62 | 63 | test.execute( ReadAll( "a" ) ); 64 | test.execute( BytesPushed( 1 ) ); 65 | test.execute( BytesPending( 0 ) ); 66 | 67 | test.execute( Insert { "abc", 0 } ); 68 | test.execute( BytesPushed( 2 ) ); 69 | test.execute( BytesPending( 0 ) ); 70 | 71 | test.execute( ReadAll( "b" ) ); 72 | test.execute( BytesPushed( 2 ) ); 73 | test.execute( BytesPending( 0 ) ); 74 | } 75 | 76 | { 77 | ReassemblerTestHarness test { "insert beyond capacity repeated with different data", 2 }; 78 | 79 | test.execute( Insert { "b", 1 } ); 80 | test.execute( BytesPushed( 0 ) ); 81 | test.execute( BytesPending( 1 ) ); 82 | 83 | test.execute( Insert { "bX", 2 } ); 84 | test.execute( BytesPushed( 0 ) ); 85 | test.execute( BytesPending( 1 ) ); 86 | 87 | test.execute( Insert { "a", 0 } ); 88 | 89 | test.execute( BytesPushed( 2 ) ); 90 | test.execute( BytesPending( 0 ) ); 91 | test.execute( ReadAll( "ab" ) ); 92 | 93 | test.execute( Insert { "bc", 1 } ); 94 | test.execute( BytesPushed( 3 ) ); 95 | test.execute( BytesPending( 0 ) ); 96 | 97 | test.execute( ReadAll( "c" ) ); 98 | } 99 | 100 | } catch ( const exception& e ) { 101 | cerr << "Exception: " << e.what() << endl; 102 | return EXIT_FAILURE; 103 | } 104 | 105 | return EXIT_SUCCESS; 106 | } 107 | -------------------------------------------------------------------------------- /tests/reassembler_dup.cc: -------------------------------------------------------------------------------- 1 | #include "random.hh" 2 | #include "reassembler_test_harness.hh" 3 | 4 | #include 5 | #include 6 | 7 | using namespace std; 8 | 9 | int main() 10 | { 11 | try { 12 | auto rd = get_random_engine(); 13 | 14 | { 15 | ReassemblerTestHarness test { "dup 1", 65000 }; 16 | 17 | test.execute( Insert { "abcd", 0 } ); 18 | test.execute( BytesPushed( 4 ) ); 19 | test.execute( ReadAll( "abcd" ) ); 20 | test.execute( IsFinished { false } ); 21 | 22 | test.execute( Insert { "abcd", 0 } ); 23 | test.execute( BytesPushed( 4 ) ); 24 | test.execute( ReadAll( "" ) ); 25 | test.execute( IsFinished { false } ); 26 | } 27 | 28 | { 29 | ReassemblerTestHarness test { "dup 2", 65000 }; 30 | 31 | test.execute( Insert { "abcd", 0 } ); 32 | test.execute( BytesPushed( 4 ) ); 33 | test.execute( ReadAll( "abcd" ) ); 34 | test.execute( IsFinished { false } ); 35 | 36 | test.execute( Insert { "abcd", 4 } ); 37 | test.execute( BytesPushed( 8 ) ); 38 | test.execute( ReadAll( "abcd" ) ); 39 | test.execute( IsFinished { false } ); 40 | 41 | test.execute( Insert { "abcd", 0 } ); 42 | test.execute( BytesPushed( 8 ) ); 43 | test.execute( ReadAll( "" ) ); 44 | test.execute( IsFinished { false } ); 45 | 46 | test.execute( Insert { "abcd", 4 } ); 47 | test.execute( BytesPushed( 8 ) ); 48 | test.execute( ReadAll( "" ) ); 49 | test.execute( IsFinished { false } ); 50 | } 51 | 52 | { 53 | ReassemblerTestHarness test { "dup 3", 65000 }; 54 | 55 | test.execute( Insert { "abcdefgh", 0 } ); 56 | test.execute( BytesPushed( 8 ) ); 57 | test.execute( ReadAll( "abcdefgh" ) ); 58 | test.execute( IsFinished { false } ); 59 | string data = "abcdefgh"; 60 | 61 | for ( size_t i = 0; i < 1000; ++i ) { 62 | const size_t start_i = uniform_int_distribution { 0, 8 }( rd ); 63 | auto start = data.begin(); 64 | std::advance( start, start_i ); 65 | 66 | const size_t end_i = uniform_int_distribution { start_i, 8 }( rd ); 67 | auto end = data.begin(); 68 | std::advance( end, end_i ); 69 | 70 | test.execute( Insert { string { start, end }, start_i } ); 71 | test.execute( BytesPushed( 8 ) ); 72 | test.execute( ReadAll( "" ) ); 73 | test.execute( IsFinished { false } ); 74 | } 75 | } 76 | 77 | { 78 | ReassemblerTestHarness test { "dup 4", 65000 }; 79 | 80 | test.execute( Insert { "abcd", 0 } ); 81 | test.execute( BytesPushed( 4 ) ); 82 | test.execute( ReadAll( "abcd" ) ); 83 | test.execute( IsFinished { false } ); 84 | 85 | test.execute( Insert { "abcdef", 0 } ); 86 | test.execute( BytesPushed( 6 ) ); 87 | test.execute( ReadAll( "ef" ) ); 88 | test.execute( IsFinished { false } ); 89 | } 90 | 91 | } catch ( const exception& e ) { 92 | cerr << "Exception: " << e.what() << endl; 93 | return EXIT_FAILURE; 94 | } 95 | 96 | return EXIT_SUCCESS; 97 | } 98 | -------------------------------------------------------------------------------- /tests/reassembler_holes.cc: -------------------------------------------------------------------------------- 1 | #include "reassembler_test_harness.hh" 2 | 3 | #include 4 | #include 5 | 6 | using namespace std; 7 | 8 | int main() 9 | { 10 | try { 11 | { 12 | ReassemblerTestHarness test { "holes 1", 65000 }; 13 | 14 | test.execute( Insert { "b", 1 } ); 15 | 16 | test.execute( BytesPushed( 0 ) ); 17 | test.execute( ReadAll( "" ) ); 18 | test.execute( IsFinished { false } ); 19 | } 20 | 21 | { 22 | ReassemblerTestHarness test { "holes 2", 65000 }; 23 | 24 | test.execute( Insert { "b", 1 } ); 25 | test.execute( Insert { "a", 0 } ); 26 | 27 | test.execute( BytesPushed( 2 ) ); 28 | test.execute( ReadAll( "ab" ) ); 29 | test.execute( IsFinished { false } ); 30 | } 31 | 32 | { 33 | ReassemblerTestHarness test { "holes 3", 65000 }; 34 | 35 | test.execute( Insert { "b", 1 }.is_last() ); 36 | 37 | test.execute( BytesPushed( 0 ) ); 38 | test.execute( ReadAll( "" ) ); 39 | test.execute( IsFinished { false } ); 40 | 41 | test.execute( Insert { "a", 0 } ); 42 | 43 | test.execute( BytesPushed( 2 ) ); 44 | test.execute( ReadAll( "ab" ) ); 45 | test.execute( IsFinished { true } ); 46 | } 47 | 48 | { 49 | ReassemblerTestHarness test { "holes 4", 65000 }; 50 | 51 | test.execute( Insert { "b", 1 } ); 52 | test.execute( Insert { "ab", 0 } ); 53 | 54 | test.execute( BytesPushed( 2 ) ); 55 | test.execute( ReadAll( "ab" ) ); 56 | test.execute( IsFinished { false } ); 57 | } 58 | 59 | { 60 | ReassemblerTestHarness test { "holes 5", 65000 }; 61 | 62 | test.execute( Insert { "b", 1 } ); 63 | test.execute( BytesPushed( 0 ) ); 64 | test.execute( ReadAll( "" ) ); 65 | test.execute( IsFinished { false } ); 66 | 67 | test.execute( Insert { "d", 3 } ); 68 | test.execute( BytesPushed( 0 ) ); 69 | test.execute( ReadAll( "" ) ); 70 | test.execute( IsFinished { false } ); 71 | 72 | test.execute( Insert { "c", 2 } ); 73 | test.execute( BytesPushed( 0 ) ); 74 | test.execute( ReadAll( "" ) ); 75 | test.execute( IsFinished { false } ); 76 | 77 | test.execute( Insert { "a", 0 } ); 78 | 79 | test.execute( BytesPushed( 4 ) ); 80 | test.execute( ReadAll( "abcd" ) ); 81 | test.execute( IsFinished { false } ); 82 | } 83 | 84 | { 85 | ReassemblerTestHarness test { "holes 6", 65000 }; 86 | 87 | test.execute( Insert { "b", 1 } ); 88 | test.execute( BytesPushed( 0 ) ); 89 | test.execute( ReadAll( "" ) ); 90 | test.execute( IsFinished { false } ); 91 | 92 | test.execute( Insert { "d", 3 } ); 93 | test.execute( BytesPushed( 0 ) ); 94 | test.execute( ReadAll( "" ) ); 95 | test.execute( IsFinished { false } ); 96 | 97 | test.execute( Insert { "abc", 0 } ); 98 | 99 | test.execute( BytesPushed( 4 ) ); 100 | test.execute( ReadAll( "abcd" ) ); 101 | test.execute( IsFinished { false } ); 102 | } 103 | 104 | { 105 | ReassemblerTestHarness test { "holes 7", 65000 }; 106 | 107 | test.execute( Insert { "b", 1 } ); 108 | test.execute( BytesPushed( 0 ) ); 109 | test.execute( ReadAll( "" ) ); 110 | test.execute( IsFinished { false } ); 111 | 112 | test.execute( Insert { "d", 3 } ); 113 | test.execute( BytesPushed( 0 ) ); 114 | test.execute( ReadAll( "" ) ); 115 | test.execute( IsFinished { false } ); 116 | 117 | test.execute( Insert { "a", 0 } ); 118 | test.execute( BytesPushed( 2 ) ); 119 | test.execute( ReadAll( "ab" ) ); 120 | test.execute( IsFinished { false } ); 121 | 122 | test.execute( Insert { "c", 2 } ); 123 | test.execute( BytesPushed( 4 ) ); 124 | test.execute( ReadAll( "cd" ) ); 125 | test.execute( IsFinished { false } ); 126 | 127 | test.execute( Insert { "", 4 }.is_last() ); 128 | test.execute( BytesPushed( 4 ) ); 129 | test.execute( ReadAll( "" ) ); 130 | test.execute( IsFinished { true } ); 131 | } 132 | } catch ( const exception& e ) { 133 | cerr << "Exception: " << e.what() << endl; 134 | return EXIT_FAILURE; 135 | } 136 | 137 | return EXIT_SUCCESS; 138 | } 139 | -------------------------------------------------------------------------------- /tests/reassembler_seq.cc: -------------------------------------------------------------------------------- 1 | #include "reassembler_test_harness.hh" 2 | 3 | #include 4 | #include 5 | 6 | using namespace std; 7 | 8 | int main() 9 | { 10 | try { 11 | { 12 | ReassemblerTestHarness test { "seq 1", 65000 }; 13 | 14 | test.execute( Insert { "abcd", 0 } ); 15 | test.execute( BytesPushed( 4 ) ); 16 | test.execute( ReadAll( "abcd" ) ); 17 | test.execute( IsFinished { false } ); 18 | 19 | test.execute( Insert { "efgh", 4 } ); 20 | test.execute( BytesPushed( 8 ) ); 21 | test.execute( ReadAll( "efgh" ) ); 22 | test.execute( IsFinished { false } ); 23 | } 24 | 25 | { 26 | ReassemblerTestHarness test { "seq 2", 65000 }; 27 | 28 | test.execute( Insert { "abcd", 0 } ); 29 | test.execute( BytesPushed( 4 ) ); 30 | test.execute( IsFinished { false } ); 31 | test.execute( Insert { "efgh", 4 } ); 32 | test.execute( BytesPushed( 8 ) ); 33 | 34 | test.execute( ReadAll( "abcdefgh" ) ); 35 | test.execute( IsFinished { false } ); 36 | } 37 | 38 | { 39 | ReassemblerTestHarness test { "seq 3", 65000 }; 40 | std::ostringstream ss; 41 | 42 | for ( size_t i = 0; i < 100; ++i ) { 43 | test.execute( BytesPushed( 4 * i ) ); 44 | test.execute( Insert { "abcd", 4 * i } ); 45 | test.execute( IsFinished { false } ); 46 | 47 | ss << "abcd"; 48 | } 49 | 50 | test.execute( ReadAll( ss.str() ) ); 51 | test.execute( IsFinished { false } ); 52 | } 53 | 54 | { 55 | ReassemblerTestHarness test { "seq 4", 65000 }; 56 | for ( size_t i = 0; i < 100; ++i ) { 57 | test.execute( BytesPushed( 4 * i ) ); 58 | test.execute( Insert { "abcd", 4 * i } ); 59 | test.execute( IsFinished { false } ); 60 | 61 | test.execute( ReadAll( "abcd" ) ); 62 | } 63 | } 64 | 65 | { 66 | ReassemblerTestHarness test { "zero-valued byte in substring", 16 }; 67 | 68 | test.execute( Insert { { 0x30, 0x0d, 0x62, 0x00, 0x61, 0x00, 0x00 }, 9 } ); 69 | test.execute( BytesPushed( 0 ) ); 70 | test.execute( ReadAll( "" ) ); 71 | test.execute( IsFinished { false } ); 72 | 73 | test.execute( Insert { { 0x0d, 0x0a, 0x63, 0x61, 0x0a, 0x66 }, 0 } ); 74 | test.execute( BytesPushed( 6 ) ); 75 | 76 | test.execute( Insert { { 0x0d, 0x0a, 0x63, 0x61, 0x0a, 0x66, 0x65, 0x20, 0x62, 0x30 }, 0 } ); 77 | test.execute( BytesPushed( 16 ) ); 78 | test.execute( BytesPending( 0 ) ); 79 | test.execute( ReadAll( 80 | { 0x0d, 0x0a, 0x63, 0x61, 0x0a, 0x66, 0x65, 0x20, 0x62, 0x30, 0x0d, 0x62, 0x00, 0x61, 0x00, 0x00 } ) ); 81 | } 82 | } catch ( const exception& e ) { 83 | cerr << "Exception: " << e.what() << endl; 84 | return EXIT_FAILURE; 85 | } 86 | 87 | return EXIT_SUCCESS; 88 | } 89 | -------------------------------------------------------------------------------- /tests/reassembler_single.cc: -------------------------------------------------------------------------------- 1 | #include "reassembler_test_harness.hh" 2 | 3 | #include 4 | #include 5 | 6 | using namespace std; 7 | 8 | int main() 9 | { 10 | try { 11 | { 12 | ReassemblerTestHarness test { "construction", 65000 }; 13 | 14 | test.execute( BytesPushed( 0 ) ); 15 | test.execute( IsFinished { false } ); 16 | } 17 | 18 | { 19 | ReassemblerTestHarness test { "insert a @ 0", 65000 }; 20 | 21 | test.execute( Insert { "a", 0 } ); 22 | 23 | test.execute( BytesPushed( 1 ) ); 24 | test.execute( ReadAll( "a" ) ); 25 | test.execute( IsFinished { false } ); 26 | } 27 | 28 | { 29 | ReassemblerTestHarness test { "insert a @ 0 [last]", 65000 }; 30 | 31 | test.execute( Insert { "a", 0 }.is_last() ); 32 | 33 | test.execute( BytesPushed( 1 ) ); 34 | test.execute( ReadAll( "a" ) ); 35 | test.execute( IsFinished { true } ); 36 | } 37 | 38 | { 39 | ReassemblerTestHarness test { "empty stream", 65000 }; 40 | 41 | test.execute( Insert { "", 0 }.is_last() ); 42 | 43 | test.execute( BytesPushed( 0 ) ); 44 | test.execute( IsFinished { true } ); 45 | } 46 | 47 | { 48 | ReassemblerTestHarness test { "insert b @ 0 [last]", 65000 }; 49 | 50 | test.execute( Insert { "b", 0 }.is_last() ); 51 | 52 | test.execute( BytesPushed( 1 ) ); 53 | test.execute( ReadAll( "b" ) ); 54 | test.execute( IsFinished { true } ); 55 | } 56 | 57 | { 58 | ReassemblerTestHarness test { "insert empty string @ 0", 65000 }; 59 | 60 | test.execute( Insert { "", 0 } ); 61 | 62 | test.execute( BytesPushed( 0 ) ); 63 | test.execute( IsFinished { false } ); 64 | } 65 | 66 | // credit: Joshua Dong 67 | { 68 | ReassemblerTestHarness test { "insert a after 'first unacceptable'", 1 }; 69 | 70 | test.execute( Insert { "g", 3 } ); 71 | 72 | test.execute( BytesPushed( 0 ) ); 73 | test.execute( IsFinished { false } ); 74 | } 75 | 76 | { 77 | ReassemblerTestHarness test { "insert b before 'first unassembled'", 1 }; 78 | 79 | test.execute( Insert { "b", 0 } ); 80 | test.execute( ReadAll( "b" ) ); 81 | test.execute( BytesPushed( 1 ) ); 82 | test.execute( Insert { "b", 0 } ); 83 | test.execute( BytesPushed( 1 ) ); 84 | test.execute( IsFinished { false } ); 85 | } 86 | } catch ( const exception& e ) { 87 | cerr << "Exception: " << e.what() << endl; 88 | return EXIT_FAILURE; 89 | } 90 | 91 | return EXIT_SUCCESS; 92 | } 93 | -------------------------------------------------------------------------------- /tests/reassembler_speed_test.cc: -------------------------------------------------------------------------------- 1 | #include "reassembler.hh" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | using namespace std; 14 | using namespace std::chrono; 15 | 16 | void speed_test( const size_t num_chunks, // NOLINT(bugprone-easily-swappable-parameters) 17 | const size_t capacity, // NOLINT(bugprone-easily-swappable-parameters) 18 | const size_t random_seed ) // NOLINT(bugprone-easily-swappable-parameters) 19 | { 20 | // Generate the data to be written 21 | const string data = [&] { 22 | default_random_engine rd { random_seed }; 23 | uniform_int_distribution ud; 24 | string ret; 25 | for ( size_t i = 0; i < num_chunks * capacity; ++i ) { 26 | ret += ud( rd ); 27 | } 28 | return ret; 29 | }(); 30 | 31 | // Split the data into segments before writing 32 | queue> split_data; 33 | for ( size_t i = 0; i < data.size(); i += capacity ) { 34 | split_data.emplace( i + 2, data.substr( i + 2, capacity * 2 ), i + 2 + capacity * 2 >= data.size() ); 35 | split_data.emplace( i, data.substr( i, capacity * 2 ), i + capacity * 2 >= data.size() ); 36 | split_data.emplace( i + 1, data.substr( i + 1, capacity * 2 ), i + 1 + capacity * 2 >= data.size() ); 37 | } 38 | 39 | ByteStream stream { capacity }; 40 | Reassembler reassembler; 41 | 42 | string output_data; 43 | output_data.reserve( data.size() ); 44 | 45 | const auto start_time = steady_clock::now(); 46 | while ( not split_data.empty() ) { 47 | auto& next = split_data.front(); 48 | reassembler.insert( get( next ), move( get( next ) ), get( next ), stream.writer() ); 49 | split_data.pop(); 50 | 51 | while ( stream.reader().bytes_buffered() ) { 52 | output_data += stream.reader().peek(); 53 | stream.reader().pop( output_data.size() - stream.reader().bytes_popped() ); 54 | } 55 | } 56 | 57 | const auto stop_time = steady_clock::now(); 58 | 59 | if ( not stream.reader().is_finished() ) { 60 | throw runtime_error( "Reassembler did not close ByteStream when finished" ); 61 | } 62 | 63 | if ( data != output_data ) { 64 | throw runtime_error( "Mismatch between data written and read" ); 65 | } 66 | 67 | auto test_duration = duration_cast>( stop_time - start_time ); 68 | auto bytes_per_second = static_cast( num_chunks * capacity ) / test_duration.count(); 69 | auto bits_per_second = 8 * bytes_per_second; 70 | auto gigabits_per_second = bits_per_second / 1e9; 71 | 72 | fstream debug_output; 73 | debug_output.open( "/dev/tty" ); 74 | 75 | cout << "Reassembler to ByteStream with capacity=" << capacity << " reached " << fixed << setprecision( 2 ) 76 | << gigabits_per_second << " Gbit/s.\n"; 77 | 78 | debug_output << " Reassembler throughput: " << fixed << setprecision( 2 ) << gigabits_per_second 79 | << " Gbit/s\n"; 80 | 81 | if ( gigabits_per_second < 0.1 ) { 82 | throw runtime_error( "Reassembler did not meet minimum speed of 0.1 Gbit/s." ); 83 | } 84 | } 85 | 86 | void program_body() 87 | { 88 | speed_test( 10000, 1500, 1370 ); 89 | } 90 | 91 | int main() 92 | { 93 | try { 94 | program_body(); 95 | } catch ( const exception& e ) { 96 | cerr << "Exception: " << e.what() << "\n"; 97 | return EXIT_FAILURE; 98 | } 99 | 100 | return EXIT_SUCCESS; 101 | } 102 | -------------------------------------------------------------------------------- /tests/reassembler_test_harness.hh: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "byte_stream_test_harness.hh" 4 | #include "common.hh" 5 | #include "reassembler.hh" 6 | 7 | #include 8 | #include 9 | #include 10 | 11 | using StreamAndReassembler = std::pair; 12 | 13 | template> T> 14 | struct ReassemblerByteStreamTestStep : public TestStep 15 | { 16 | T step_; 17 | 18 | template 19 | explicit ReassemblerByteStreamTestStep( T byte_stream_test_step ) 20 | : TestStep(), step_( std::move( byte_stream_test_step ) ) 21 | {} 22 | 23 | std::string str() const override { return step_.str(); } 24 | uint8_t color() const override { return step_.color(); } 25 | void execute( StreamAndReassembler& sr ) const override { step_.execute( sr.first ); } 26 | }; 27 | 28 | class ReassemblerTestHarness : public TestHarness 29 | { 30 | public: 31 | ReassemblerTestHarness( std::string test_name, uint64_t capacity ) 32 | : TestHarness( move( test_name ), 33 | "capacity=" + std::to_string( capacity ), 34 | { ByteStream { capacity }, Reassembler {} } ) 35 | {} 36 | 37 | template> T> 38 | void execute( const T& test ) 39 | { 40 | TestHarness::execute( ReassemblerByteStreamTestStep { test } ); 41 | } 42 | 43 | using TestHarness::execute; 44 | }; 45 | 46 | struct BytesPending : public ExpectNumber 47 | { 48 | using ExpectNumber::ExpectNumber; 49 | std::string name() const override { return "bytes_pending"; } 50 | uint64_t value( StreamAndReassembler& sr ) const override { return sr.second.bytes_pending(); } 51 | }; 52 | 53 | struct Insert : public Action 54 | { 55 | std::string data_; 56 | uint64_t first_index_; 57 | bool is_last_substring_ {}; 58 | 59 | Insert( std::string data, uint64_t first_index ) : data_( move( data ) ), first_index_( first_index ) {} 60 | 61 | Insert& is_last( bool status = true ) 62 | { 63 | is_last_substring_ = status; 64 | return *this; 65 | } 66 | 67 | std::string description() const override 68 | { 69 | std::ostringstream ss; 70 | ss << "insert \"" << Printer::prettify( data_ ) << "\" @ index " << first_index_; 71 | if ( is_last_substring_ ) { 72 | ss << " [last substring]"; 73 | } 74 | return ss.str(); 75 | } 76 | 77 | void execute( StreamAndReassembler& sr ) const override 78 | { 79 | sr.second.insert( first_index_, data_, is_last_substring_, sr.first.writer() ); 80 | } 81 | }; 82 | -------------------------------------------------------------------------------- /tests/reassembler_win.cc: -------------------------------------------------------------------------------- 1 | #include "random.hh" 2 | #include "reassembler_test_harness.hh" 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | using namespace std; 12 | 13 | static constexpr size_t NREPS = 32; 14 | static constexpr size_t NSEGS = 128; 15 | static constexpr size_t MAX_SEG_LEN = 2048; 16 | 17 | int main() 18 | { 19 | try { 20 | auto rd = get_random_engine(); 21 | 22 | // overlapping segments 23 | for ( unsigned rep_no = 0; rep_no < NREPS; ++rep_no ) { 24 | ReassemblerTestHarness sr { "win test " + to_string( rep_no ), NSEGS * MAX_SEG_LEN }; 25 | 26 | vector> seq_size; 27 | size_t offset = 0; 28 | for ( unsigned i = 0; i < NSEGS; ++i ) { 29 | const size_t size = 1 + ( rd() % ( MAX_SEG_LEN - 1 ) ); 30 | const size_t offs = min( offset, 1 + ( static_cast( rd() ) % 1023 ) ); 31 | seq_size.emplace_back( offset - offs, size + offs ); 32 | offset += size; 33 | } 34 | shuffle( seq_size.begin(), seq_size.end(), rd ); 35 | 36 | string d( offset, 0 ); 37 | generate( d.begin(), d.end(), [&] { return rd(); } ); 38 | 39 | for ( auto [off, sz] : seq_size ) { 40 | sr.execute( Insert { d.substr( off, sz ), off }.is_last( off + sz == offset ) ); 41 | } 42 | 43 | sr.execute( ReadAll { d } ); 44 | } 45 | } catch ( const exception& e ) { 46 | cerr << "Exception: " << e.what() << endl; 47 | return EXIT_FAILURE; 48 | } 49 | 50 | return EXIT_SUCCESS; 51 | } 52 | -------------------------------------------------------------------------------- /tests/receiver_test_harness.hh: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "common.hh" 4 | #include "reassembler_test_harness.hh" 5 | #include "tcp_receiver.hh" 6 | #include "tcp_receiver_message.hh" 7 | 8 | #include 9 | #include 10 | #include 11 | 12 | using ReceiverSet = std::pair; 13 | 14 | template> T> 15 | struct ReceiverSetTestStep : public TestStep 16 | { 17 | T step_; 18 | 19 | template 20 | explicit ReceiverSetTestStep( T sr_test_step ) : TestStep(), step_( std::move( sr_test_step ) ) 21 | {} 22 | 23 | std::string str() const override { return step_.str(); } 24 | uint8_t color() const override { return step_.color(); } 25 | void execute( ReceiverSet& sr ) const override { step_.execute( sr.first ); } 26 | }; 27 | 28 | class TCPReceiverTestHarness : public TestHarness 29 | { 30 | public: 31 | TCPReceiverTestHarness( std::string test_name, uint64_t capacity ) 32 | : TestHarness( move( test_name ), 33 | "capacity=" + std::to_string( capacity ), 34 | { { ByteStream { capacity }, Reassembler {} }, TCPReceiver {} } ) 35 | {} 36 | 37 | template> T> 38 | void execute( const T& test ) 39 | { 40 | TestHarness::execute( ReceiverSetTestStep { test } ); 41 | } 42 | 43 | template> T> 44 | void execute( const T& test ) 45 | { 46 | TestHarness::execute( ReceiverSetTestStep { ReassemblerByteStreamTestStep { test } } ); 47 | } 48 | 49 | using TestHarness::execute; 50 | }; 51 | 52 | struct ExpectWindow : public ExpectNumber 53 | { 54 | using ExpectNumber::ExpectNumber; 55 | std::string name() const override { return "window_size"; } 56 | uint16_t value( ReceiverSet& rs ) const override { return rs.second.send( rs.first.first.writer() ).window_size; } 57 | }; 58 | 59 | struct ExpectAckno : public ExpectNumber> 60 | { 61 | using ExpectNumber::ExpectNumber; 62 | std::string name() const override { return "ackno"; } 63 | std::optional value( ReceiverSet& rs ) const override 64 | { 65 | return rs.second.send( rs.first.first.writer() ).ackno; 66 | } 67 | }; 68 | 69 | struct ExpectAcknoBetween : public Expectation 70 | { 71 | Wrap32 isn_; 72 | uint64_t checkpoint_; 73 | uint64_t min_, max_; 74 | ExpectAcknoBetween( Wrap32 isn, uint64_t checkpoint, uint64_t min, uint64_t max ) // NOLINT(*-swappable-*) 75 | : isn_( isn ), checkpoint_( checkpoint ), min_( min ), max_( max ) 76 | {} 77 | 78 | std::string description() const override 79 | { 80 | return "ackno unwraps to between " + to_string( min_ ) + " and " + to_string( max_ ); 81 | } 82 | 83 | void execute( ReceiverSet& rs ) const override 84 | { 85 | auto ackno = rs.second.send( rs.first.first.writer() ).ackno; 86 | if ( not ackno.has_value() ) { 87 | throw ExpectationViolation( "TCPReceiver did not have ackno when expected" ); 88 | } 89 | const uint64_t ackno_absolute = ackno.value().unwrap( isn_, checkpoint_ ); 90 | 91 | if ( ackno_absolute < min_ or ackno_absolute > max_ ) { 92 | throw ExpectationViolation( "ackno outside expected range" ); 93 | } 94 | } 95 | }; 96 | 97 | struct HasAckno : public ExpectBool 98 | { 99 | using ExpectBool::ExpectBool; 100 | std::string name() const override { return "ackno.has_value()"; } 101 | bool value( ReceiverSet& rs ) const override 102 | { 103 | return rs.second.send( rs.first.first.writer() ).ackno.has_value(); 104 | } 105 | }; 106 | 107 | struct SegmentArrives : public Action 108 | { 109 | TCPSenderMessage msg_ {}; 110 | HasAckno ackno_expected_ { true }; 111 | 112 | SegmentArrives& with_syn() 113 | { 114 | msg_.SYN = true; 115 | return *this; 116 | } 117 | 118 | SegmentArrives& with_fin() 119 | { 120 | msg_.FIN = true; 121 | return *this; 122 | } 123 | 124 | SegmentArrives& with_seqno( Wrap32 seqno_ ) 125 | { 126 | msg_.seqno = seqno_; 127 | return *this; 128 | } 129 | 130 | SegmentArrives& with_seqno( uint32_t seqno_ ) { return with_seqno( Wrap32 { seqno_ } ); } 131 | 132 | SegmentArrives& with_data( std::string data ) 133 | { 134 | msg_.payload = move( data ); 135 | return *this; 136 | } 137 | 138 | SegmentArrives& without_ackno() 139 | { 140 | ackno_expected_ = HasAckno { false }; 141 | return *this; 142 | } 143 | 144 | void execute( ReceiverSet& rs ) const override 145 | { 146 | rs.second.receive( msg_, rs.first.second, rs.first.first.writer() ); 147 | ackno_expected_.execute( rs ); 148 | } 149 | 150 | std::string description() const override 151 | { 152 | std::ostringstream ss; 153 | ss << "receive segment: ("; 154 | ss << "seqno=" << msg_.seqno; 155 | if ( msg_.SYN ) { 156 | ss << " +SYN"; 157 | } 158 | if ( not msg_.payload.empty() ) { 159 | ss << " payload=\"" << Printer::prettify( msg_.payload ) << "\""; 160 | } 161 | if ( msg_.FIN ) { 162 | ss << " +FIN"; 163 | } 164 | ss << ")"; 165 | 166 | if ( ackno_expected_.value_ ) { 167 | ss << " with ackno expected"; 168 | } else { 169 | ss << " with ackno not expected"; 170 | } 171 | return ss.str(); 172 | } 173 | }; 174 | -------------------------------------------------------------------------------- /tests/recv_close.cc: -------------------------------------------------------------------------------- 1 | #include "random.hh" 2 | #include "receiver_test_harness.hh" 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | using namespace std; 11 | 12 | int main() 13 | { 14 | try { 15 | auto rd = get_random_engine(); 16 | 17 | { 18 | const uint32_t isn = uniform_int_distribution { 0, UINT32_MAX }( rd ); 19 | TCPReceiverTestHarness test { "close 1", 4000 }; 20 | test.execute( HasAckno { false } ); 21 | test.execute( SegmentArrives {}.with_syn().with_seqno( isn + 0 ) ); 22 | test.execute( IsClosed { false } ); 23 | test.execute( SegmentArrives {}.with_fin().with_seqno( isn + 1 ) ); 24 | test.execute( ExpectAckno { Wrap32 { isn + 2 } } ); 25 | test.execute( BytesPending { 0 } ); 26 | test.execute( Peek { "" } ); 27 | test.execute( BytesPushed { 0 } ); 28 | test.execute( IsClosed { true } ); 29 | } 30 | 31 | { 32 | const uint32_t isn = uniform_int_distribution { 0, UINT32_MAX }( rd ); 33 | TCPReceiverTestHarness test { "close 2", 4000 }; 34 | test.execute( HasAckno { false } ); 35 | test.execute( SegmentArrives {}.with_syn().with_seqno( isn + 0 ) ); 36 | test.execute( IsClosed { false } ); 37 | test.execute( SegmentArrives {}.with_fin().with_seqno( isn + 1 ).with_data( "a" ) ); 38 | test.execute( IsClosed { true } ); 39 | test.execute( ExpectAckno { Wrap32 { isn + 3 } } ); 40 | test.execute( BytesPending { 0 } ); 41 | test.execute( ReadAll { "a" } ); 42 | test.execute( BytesPushed { 1 } ); 43 | test.execute( IsClosed { true } ); 44 | test.execute( IsFinished { true } ); 45 | } 46 | 47 | } catch ( const exception& e ) { 48 | cerr << e.what() << endl; 49 | return 1; 50 | } 51 | 52 | return EXIT_SUCCESS; 53 | } 54 | -------------------------------------------------------------------------------- /tests/recv_connect.cc: -------------------------------------------------------------------------------- 1 | #include "reassembler_test_harness.hh" 2 | #include "receiver_test_harness.hh" 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | using namespace std; 13 | 14 | int main() 15 | { 16 | try { 17 | { 18 | TCPReceiverTestHarness test { "connect 1", 4000 }; 19 | test.execute( ExpectWindow { 4000 } ); 20 | test.execute( ExpectAckno { std::optional {} } ); 21 | test.execute( BytesPending { 0 } ); 22 | test.execute( BytesPushed { 0 } ); 23 | test.execute( SegmentArrives {}.with_syn().with_seqno( 0 ) ); 24 | test.execute( ExpectAckno { Wrap32 { 1 } } ); 25 | test.execute( BytesPending { 0 } ); 26 | test.execute( BytesPushed { 0 } ); 27 | } 28 | 29 | { 30 | TCPReceiverTestHarness test { "connect 2", 5435 }; 31 | test.execute( ExpectAckno { std::optional {} } ); 32 | test.execute( BytesPending { 0 } ); 33 | test.execute( BytesPushed { 0 } ); 34 | test.execute( SegmentArrives {}.with_syn().with_seqno( 89347598 ) ); 35 | test.execute( ExpectAckno { Wrap32 { 89347599 } } ); 36 | test.execute( BytesPending { 0 } ); 37 | test.execute( BytesPushed { 0 } ); 38 | } 39 | 40 | { 41 | TCPReceiverTestHarness test { "connect 3", 5435 }; 42 | test.execute( ExpectAckno { std::optional {} } ); 43 | test.execute( BytesPending { 0 } ); 44 | test.execute( BytesPushed { 0 } ); 45 | test.execute( SegmentArrives {}.with_seqno( 893475 ).without_ackno() ); 46 | test.execute( ExpectAckno { std::optional {} } ); 47 | test.execute( BytesPending { 0 } ); 48 | test.execute( BytesPushed { 0 } ); 49 | } 50 | 51 | { 52 | TCPReceiverTestHarness test { "connect 4", 5435 }; 53 | test.execute( ExpectAckno { std::optional {} } ); 54 | test.execute( BytesPending { 0 } ); 55 | test.execute( BytesPushed { 0 } ); 56 | test.execute( SegmentArrives {}.with_fin().with_seqno( 893475 ).without_ackno() ); 57 | test.execute( ExpectAckno { std::optional {} } ); 58 | test.execute( BytesPending { 0 } ); 59 | test.execute( BytesPushed { 0 } ); 60 | } 61 | 62 | { 63 | TCPReceiverTestHarness test { "connect 5", 5435 }; 64 | test.execute( ExpectAckno { std::optional {} } ); 65 | test.execute( BytesPending { 0 } ); 66 | test.execute( BytesPushed { 0 } ); 67 | test.execute( SegmentArrives {}.with_fin().with_seqno( 893475 ).without_ackno() ); 68 | test.execute( ExpectAckno { std::optional {} } ); 69 | test.execute( BytesPending { 0 } ); 70 | test.execute( BytesPushed { 0 } ); 71 | test.execute( SegmentArrives {}.with_syn().with_seqno( 89347598 ) ); 72 | test.execute( ExpectAckno { Wrap32 { 89347599 } } ); 73 | test.execute( BytesPending { 0 } ); 74 | test.execute( BytesPushed { 0 } ); 75 | } 76 | 77 | { 78 | TCPReceiverTestHarness test { "connect 6", 4000 }; 79 | test.execute( SegmentArrives {}.with_syn().with_seqno( 5 ).with_fin() ); 80 | test.execute( IsClosed { true } ); 81 | test.execute( ExpectAckno { Wrap32 { 7 } } ); 82 | test.execute( BytesPending { 0 } ); 83 | test.execute( BytesPushed { 0 } ); 84 | } 85 | 86 | { 87 | TCPReceiverTestHarness test { "window size zero", 0 }; 88 | test.execute( ExpectWindow { 0 } ); 89 | } 90 | 91 | { 92 | TCPReceiverTestHarness test { "window size 50", 50 }; 93 | test.execute( ExpectWindow { 50 } ); 94 | } 95 | 96 | { 97 | TCPReceiverTestHarness test { "window size at max", UINT16_MAX }; 98 | test.execute( ExpectWindow { UINT16_MAX } ); 99 | } 100 | 101 | { 102 | TCPReceiverTestHarness test { "window size at max+1", UINT16_MAX + 1 }; 103 | test.execute( ExpectWindow { UINT16_MAX } ); 104 | } 105 | 106 | { 107 | TCPReceiverTestHarness test { "window size at max+5", UINT16_MAX + 5 }; 108 | test.execute( ExpectWindow { UINT16_MAX } ); 109 | } 110 | 111 | { 112 | TCPReceiverTestHarness test { "window size at 10M", 10'000'000 }; 113 | test.execute( ExpectWindow { UINT16_MAX } ); 114 | } 115 | } catch ( const exception& e ) { 116 | cerr << e.what() << endl; 117 | return 1; 118 | } 119 | 120 | return EXIT_SUCCESS; 121 | } 122 | -------------------------------------------------------------------------------- /tests/recv_reorder_more.cc: -------------------------------------------------------------------------------- 1 | #include "random.hh" 2 | #include "receiver_test_harness.hh" 3 | #include "tcp_config.hh" 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | 15 | using namespace std; 16 | 17 | static constexpr unsigned NREPS = 64; 18 | 19 | void do_test_1( const TCPConfig& cfg, default_random_engine& rd ) 20 | { 21 | const Wrap32 rx_isn( rd() ); 22 | TCPReceiverTestHarness test_1 { "non-overlapping out-of-order segments", cfg.recv_capacity }; 23 | test_1.execute( SegmentArrives {}.with_seqno( rx_isn ).with_syn() ); 24 | vector> seq_size; 25 | size_t datalen = 0; 26 | while ( datalen < cfg.recv_capacity ) { 27 | const size_t size = min( 1 + ( static_cast( rd() ) % ( TCPConfig::MAX_PAYLOAD_SIZE - 1 ) ), 28 | cfg.recv_capacity - datalen ); 29 | seq_size.emplace_back( datalen, size ); 30 | datalen += size; 31 | } 32 | shuffle( seq_size.begin(), seq_size.end(), rd ); 33 | 34 | string d( datalen, 0 ); 35 | generate( d.begin(), d.end(), [&] { return rd(); } ); 36 | 37 | uint64_t min_expect_ackno = 1; 38 | uint64_t max_expect_ackno = 1; 39 | for ( auto [off, sz] : seq_size ) { 40 | test_1.execute( SegmentArrives {}.with_seqno( rx_isn + 1 + off ).with_data( d.substr( off, sz ) ) ); 41 | if ( off + 1 == min_expect_ackno ) { 42 | min_expect_ackno = min_expect_ackno + sz; 43 | } 44 | max_expect_ackno = max_expect_ackno + sz; 45 | test_1.execute( ExpectAcknoBetween { rx_isn, off, min_expect_ackno, max_expect_ackno } ); 46 | } 47 | 48 | test_1.execute( BytesPending { 0 } ); 49 | test_1.execute( ReadAll { d } ); 50 | } 51 | 52 | void do_test_2( const TCPConfig& cfg, default_random_engine& rd ) 53 | { 54 | const Wrap32 rx_isn( rd() ); 55 | TCPReceiverTestHarness test_2 { "overlapping out-of-order segments", cfg.recv_capacity }; 56 | test_2.execute( SegmentArrives {}.with_seqno( rx_isn ).with_syn() ); 57 | vector> seq_size; 58 | size_t datalen = 0; 59 | while ( datalen < cfg.recv_capacity ) { 60 | const size_t size = min( 1 + ( static_cast( rd() ) % ( TCPConfig::MAX_PAYLOAD_SIZE - 1 ) ), 61 | cfg.recv_capacity - datalen ); 62 | const size_t rem = TCPConfig::MAX_PAYLOAD_SIZE - size; 63 | size_t offs = 0; 64 | if ( rem == 0 ) { 65 | offs = 0; 66 | } else if ( rem == 1 ) { 67 | offs = min( static_cast( 1 ), datalen ); 68 | } else { 69 | offs = min( min( datalen, rem ), 1 + ( static_cast( rd() ) % ( rem - 1 ) ) ); 70 | } 71 | 72 | if ( size + offs > TCPConfig::MAX_PAYLOAD_SIZE ) { 73 | throw runtime_error( "test 2 internal error: bad payload size" ); 74 | } 75 | seq_size.emplace_back( datalen - offs, size + offs ); 76 | datalen += size; 77 | } 78 | if ( datalen > cfg.recv_capacity ) { 79 | throw runtime_error( "test 2 internal error: bad offset sequence" ); 80 | } 81 | shuffle( seq_size.begin(), seq_size.end(), rd ); 82 | 83 | string d( datalen, 0 ); 84 | generate( d.begin(), d.end(), [&] { return rd(); } ); 85 | 86 | uint64_t min_expect_ackno = 1; 87 | uint64_t max_expect_ackno = 1; 88 | for ( auto [off, sz] : seq_size ) { 89 | test_2.execute( SegmentArrives {}.with_seqno( rx_isn + 1 + off ).with_data( d.substr( off, sz ) ) ); 90 | if ( off + 1 <= min_expect_ackno && off + sz + 1 > min_expect_ackno ) { 91 | min_expect_ackno = sz + off; 92 | } 93 | max_expect_ackno = max_expect_ackno + sz; // really loose because of overlap 94 | test_2.execute( ExpectAcknoBetween { rx_isn, off, min_expect_ackno, max_expect_ackno } ); 95 | } 96 | 97 | test_2.execute( BytesPending { 0 } ); 98 | test_2.execute( ReadAll { d } ); 99 | } 100 | 101 | int main() 102 | { 103 | try { 104 | TCPConfig cfg {}; 105 | cfg.recv_capacity = 65000; 106 | auto rd = get_random_engine(); 107 | 108 | // non-overlapping out-of-order segments 109 | for ( unsigned rep_no = 0; rep_no < NREPS; ++rep_no ) { 110 | do_test_1( cfg, rd ); 111 | } 112 | 113 | // overlapping out-of-order segments 114 | for ( unsigned rep_no = 0; rep_no < NREPS; ++rep_no ) { 115 | do_test_2( cfg, rd ); 116 | } 117 | } catch ( const exception& e ) { 118 | cerr << e.what() << endl; 119 | return 1; 120 | } 121 | 122 | return EXIT_SUCCESS; 123 | } 124 | -------------------------------------------------------------------------------- /tests/recv_transmit.cc: -------------------------------------------------------------------------------- 1 | #include "random.hh" 2 | #include "receiver_test_harness.hh" 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | using namespace std; 12 | 13 | int main() 14 | { 15 | try { 16 | auto rd = get_random_engine(); 17 | 18 | { 19 | TCPReceiverTestHarness test { "transmit 1", 4000 }; 20 | test.execute( SegmentArrives {}.with_syn().with_seqno( 0 ) ); 21 | test.execute( SegmentArrives {}.with_seqno( 1 ).with_data( "abcd" ) ); 22 | test.execute( ExpectAckno { Wrap32 { 5 } } ); 23 | test.execute( ReadAll { "abcd" } ); 24 | test.execute( BytesPending { 0 } ); 25 | test.execute( BytesPushed { 4 } ); 26 | } 27 | 28 | { 29 | const uint32_t isn = 384678; 30 | TCPReceiverTestHarness test { "transmit 2", 4000 }; 31 | test.execute( SegmentArrives {}.with_syn().with_seqno( isn ) ); 32 | test.execute( SegmentArrives {}.with_seqno( isn + 1 ).with_data( "abcd" ) ); 33 | test.execute( ExpectAckno { Wrap32 { isn + 5 } } ); 34 | test.execute( BytesPending { 0 } ); 35 | test.execute( BytesPushed { 4 } ); 36 | test.execute( ReadAll { "abcd" } ); 37 | test.execute( SegmentArrives {}.with_seqno( isn + 5 ).with_data( "efgh" ) ); 38 | test.execute( ExpectAckno { Wrap32 { isn + 9 } } ); 39 | test.execute( BytesPending { 0 } ); 40 | test.execute( BytesPushed { 8 } ); 41 | test.execute( ReadAll { "efgh" } ); 42 | } 43 | 44 | { 45 | const uint32_t isn = 5; 46 | TCPReceiverTestHarness test { "transmit 3", 4000 }; 47 | test.execute( SegmentArrives {}.with_syn().with_seqno( isn ) ); 48 | test.execute( SegmentArrives {}.with_seqno( isn + 1 ).with_data( "abcd" ) ); 49 | test.execute( ExpectAckno { Wrap32 { isn + 5 } } ); 50 | test.execute( BytesPending { 0 } ); 51 | test.execute( BytesPushed { 4 } ); 52 | test.execute( SegmentArrives {}.with_seqno( isn + 5 ).with_data( "efgh" ) ); 53 | test.execute( ExpectAckno { Wrap32 { isn + 9 } } ); 54 | test.execute( BytesPending { 0 } ); 55 | test.execute( BytesPushed { 8 } ); 56 | test.execute( ReadAll { "abcdefgh" } ); 57 | } 58 | 59 | // Many (arrive/read)s 60 | { 61 | TCPReceiverTestHarness test { "transmit 4", 4000 }; 62 | const uint32_t max_block_size = 10; 63 | const uint32_t n_rounds = 10000; 64 | const uint32_t isn = 893472; 65 | size_t bytes_sent = 0; 66 | test.execute( SegmentArrives {}.with_syn().with_seqno( isn ) ); 67 | for ( uint32_t i = 0; i < n_rounds; ++i ) { 68 | string data; 69 | const uint32_t block_size = uniform_int_distribution { 1, max_block_size }( rd ); 70 | for ( uint32_t j = 0; j < block_size; ++j ) { 71 | const uint8_t c = 'a' + ( ( i + j ) % 26 ); 72 | data.push_back( static_cast( c ) ); 73 | } 74 | test.execute( ExpectAckno { make_optional( isn + bytes_sent + 1 ) } ); 75 | test.execute( BytesPushed { bytes_sent } ); 76 | test.execute( SegmentArrives {}.with_seqno( isn + bytes_sent + 1 ).with_data( data ) ); 77 | bytes_sent += block_size; 78 | test.execute( ReadAll { std::move( data ) } ); 79 | } 80 | } 81 | 82 | // Many arrives, one read 83 | { 84 | const uint64_t max_block_size = 10; 85 | const uint64_t n_rounds = 100; 86 | TCPReceiverTestHarness test { "transmit 5", max_block_size * n_rounds }; 87 | const uint32_t isn = 238; 88 | size_t bytes_sent = 0; 89 | test.execute( SegmentArrives {}.with_syn().with_seqno( isn ) ); 90 | string all_data; 91 | for ( uint32_t i = 0; i < n_rounds; ++i ) { 92 | string data; 93 | const uint32_t block_size = uniform_int_distribution { 1, max_block_size }( rd ); 94 | for ( uint32_t j = 0; j < block_size; ++j ) { 95 | const uint8_t c = 'a' + ( ( i + j ) % 26 ); 96 | const char ch = static_cast( c ); 97 | data.push_back( ch ); 98 | all_data.push_back( ch ); 99 | } 100 | test.execute( ExpectAckno { make_optional( isn + bytes_sent + 1 ) } ); 101 | test.execute( BytesPushed { bytes_sent } ); 102 | test.execute( SegmentArrives {}.with_seqno( isn + bytes_sent + 1 ).with_data( data ) ); 103 | bytes_sent += block_size; 104 | } 105 | test.execute( ReadAll { std::move( all_data ) } ); 106 | } 107 | 108 | } catch ( const exception& e ) { 109 | cerr << e.what() << endl; 110 | return 1; 111 | } 112 | 113 | return EXIT_SUCCESS; 114 | } 115 | -------------------------------------------------------------------------------- /tests/recv_window.cc: -------------------------------------------------------------------------------- 1 | #include "receiver_test_harness.hh" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | using namespace std; 10 | 11 | int main() 12 | { 13 | try { 14 | { 15 | const size_t cap = 4000; 16 | const uint32_t isn = 23452; 17 | TCPReceiverTestHarness test { "window size decreases appropriately", cap }; 18 | test.execute( SegmentArrives {}.with_syn().with_seqno( isn ) ); 19 | test.execute( ExpectAckno { Wrap32 { isn + 1 } } ); 20 | test.execute( ExpectWindow { cap } ); 21 | test.execute( SegmentArrives {}.with_seqno( isn + 1 ).with_data( "abcd" ) ); 22 | test.execute( ExpectAckno { Wrap32 { isn + 5 } } ); 23 | test.execute( ExpectWindow { cap - 4 } ); 24 | test.execute( SegmentArrives {}.with_seqno( isn + 9 ).with_data( "ijkl" ) ); 25 | test.execute( ExpectAckno { Wrap32 { isn + 5 } } ); 26 | test.execute( ExpectWindow { cap - 4 } ); 27 | test.execute( SegmentArrives {}.with_seqno( isn + 5 ).with_data( "efgh" ) ); 28 | test.execute( ExpectAckno { Wrap32 { isn + 13 } } ); 29 | test.execute( ExpectWindow { cap - 12 } ); 30 | } 31 | 32 | { 33 | const size_t cap = 4000; 34 | const uint32_t isn = 23452; 35 | TCPReceiverTestHarness test { "window size expands after pop", cap }; 36 | test.execute( SegmentArrives {}.with_syn().with_seqno( isn ) ); 37 | test.execute( ExpectAckno { Wrap32 { isn + 1 } } ); 38 | test.execute( ExpectWindow { cap } ); 39 | test.execute( SegmentArrives {}.with_seqno( isn + 1 ).with_data( "abcd" ) ); 40 | test.execute( ExpectAckno { Wrap32 { isn + 5 } } ); 41 | test.execute( ExpectWindow { cap - 4 } ); 42 | test.execute( ReadAll { "abcd" } ); 43 | test.execute( ExpectAckno { Wrap32 { isn + 5 } } ); 44 | test.execute( ExpectWindow { cap } ); 45 | } 46 | 47 | { 48 | const size_t cap = 2; 49 | const uint32_t isn = 23452; 50 | TCPReceiverTestHarness test { "arriving segment with high seqno", cap }; 51 | test.execute( SegmentArrives {}.with_syn().with_seqno( isn ) ); 52 | test.execute( SegmentArrives {}.with_seqno( isn + 2 ).with_data( "bc" ) ); 53 | test.execute( BytesPushed { 0 } ); 54 | test.execute( SegmentArrives {}.with_seqno( isn + 1 ).with_data( "a" ) ); 55 | test.execute( ExpectAckno { Wrap32 { isn + 3 } } ); 56 | test.execute( ExpectWindow { 0 } ); 57 | test.execute( BytesPushed { 2 } ); 58 | test.execute( ReadAll { "ab" } ); 59 | test.execute( ExpectWindow { 2 } ); 60 | } 61 | 62 | { 63 | const size_t cap = 4; 64 | const uint32_t isn = 294058; 65 | TCPReceiverTestHarness test { "arriving segment with low seqno", cap }; 66 | test.execute( SegmentArrives {}.with_syn().with_seqno( isn ) ); 67 | test.execute( SegmentArrives {}.with_data( "ab" ).with_seqno( isn + 1 ) ); 68 | test.execute( BytesPushed { 2 } ); 69 | test.execute( ExpectWindow { cap - 2 } ); 70 | test.execute( SegmentArrives {}.with_data( "abc" ).with_seqno( isn + 1 ) ); 71 | test.execute( BytesPushed { 3 } ); 72 | test.execute( ExpectWindow { cap - 3 } ); 73 | test.execute( ReadAll { "abc" } ); 74 | } 75 | 76 | { 77 | const size_t cap = 4; 78 | const uint32_t isn = 23452; 79 | TCPReceiverTestHarness test { "segment overflowing window on left side", cap }; 80 | test.execute( SegmentArrives {}.with_syn().with_seqno( isn ) ); 81 | test.execute( SegmentArrives {}.with_seqno( isn + 1 ).with_data( "ab" ) ); 82 | test.execute( SegmentArrives {}.with_seqno( isn + 3 ).with_data( "cdef" ) ); 83 | test.execute( ReadAll { "abcd" } ); 84 | } 85 | 86 | { 87 | const size_t cap = 4; 88 | const uint32_t isn = 23452; 89 | TCPReceiverTestHarness test { "segment matching window", cap }; 90 | test.execute( SegmentArrives {}.with_syn().with_seqno( isn ) ); 91 | test.execute( SegmentArrives {}.with_seqno( isn + 1 ).with_data( "ab" ) ); 92 | test.execute( SegmentArrives {}.with_seqno( isn + 3 ).with_data( "cd" ) ); 93 | test.execute( ReadAll { "abcd" } ); 94 | } 95 | 96 | // credit for test: Jared Wasserman + Anonymous 97 | { 98 | const size_t cap = 4; 99 | const uint32_t isn = 23452; 100 | TCPReceiverTestHarness test { "byte with invalid stream index should be ignored", cap }; 101 | test.execute( SegmentArrives {}.with_syn().with_seqno( isn ) ); 102 | test.execute( SegmentArrives {}.with_seqno( isn ).with_data( "a" ) ); 103 | test.execute( BytesPushed { 0 } ); 104 | test.execute( ExpectAckno { Wrap32 { isn + 1 } } ); 105 | test.execute( BytesPending( 0 ) ); 106 | } 107 | 108 | } catch ( const exception& e ) { 109 | cerr << e.what() << endl; 110 | return 1; 111 | } 112 | 113 | return EXIT_SUCCESS; 114 | } 115 | -------------------------------------------------------------------------------- /tests/send_ack.cc: -------------------------------------------------------------------------------- 1 | #include "random.hh" 2 | #include "sender_test_harness.hh" 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | using namespace std; 13 | 14 | int main() 15 | { 16 | try { 17 | auto rd = get_random_engine(); 18 | 19 | { 20 | TCPConfig cfg; 21 | const Wrap32 isn( rd() ); 22 | cfg.fixed_isn = isn; 23 | 24 | TCPSenderTestHarness test { "Repeat ACK is ignored", cfg }; 25 | test.execute( Push {} ); 26 | test.execute( ExpectMessage {}.with_no_flags().with_syn( true ).with_payload_size( 0 ).with_seqno( isn ) ); 27 | test.execute( ExpectNoSegment {} ); 28 | test.execute( AckReceived { Wrap32 { isn + 1 } } ); 29 | test.execute( Push { "a" } ); 30 | test.execute( ExpectMessage {}.with_no_flags().with_data( "a" ) ); 31 | test.execute( ExpectNoSegment {} ); 32 | test.execute( AckReceived { Wrap32 { isn + 1 } } ); 33 | test.execute( ExpectNoSegment {} ); 34 | } 35 | 36 | { 37 | TCPConfig cfg; 38 | const Wrap32 isn( rd() ); 39 | cfg.fixed_isn = isn; 40 | 41 | TCPSenderTestHarness test { "Old ACK is ignored", cfg }; 42 | test.execute( Push {} ); 43 | test.execute( ExpectMessage {}.with_no_flags().with_syn( true ).with_payload_size( 0 ).with_seqno( isn ) ); 44 | test.execute( ExpectNoSegment {} ); 45 | test.execute( AckReceived { Wrap32 { isn + 1 } } ); 46 | test.execute( Push { "a" } ); 47 | test.execute( ExpectMessage {}.with_no_flags().with_data( "a" ) ); 48 | test.execute( ExpectNoSegment {} ); 49 | test.execute( AckReceived { Wrap32 { isn + 2 } } ); 50 | test.execute( ExpectNoSegment {} ); 51 | test.execute( Push { "b" } ); 52 | test.execute( ExpectMessage {}.with_no_flags().with_data( "b" ) ); 53 | test.execute( ExpectNoSegment {} ); 54 | test.execute( AckReceived { Wrap32 { isn + 1 } } ); 55 | test.execute( ExpectNoSegment {} ); 56 | } 57 | 58 | // credit for test: Jared Wasserman (2020) 59 | { 60 | TCPConfig cfg; 61 | const Wrap32 isn( rd() ); 62 | cfg.fixed_isn = isn; 63 | 64 | TCPSenderTestHarness test { "Impossible ackno (beyond next seqno) is ignored", cfg }; 65 | test.execute( Push {} ); 66 | test.execute( ExpectMessage {}.with_no_flags().with_syn( true ).with_payload_size( 0 ).with_seqno( isn ) ); 67 | test.execute( ExpectSeqnosInFlight { 1 } ); 68 | test.execute( AckReceived { Wrap32 { isn + 2 } }.with_win( 1000 ) ); 69 | test.execute( ExpectSeqnosInFlight { 1 } ); 70 | } 71 | } catch ( const exception& e ) { 72 | cerr << e.what() << endl; 73 | return 1; 74 | } 75 | 76 | return EXIT_SUCCESS; 77 | } 78 | -------------------------------------------------------------------------------- /tests/send_connect.cc: -------------------------------------------------------------------------------- 1 | #include "sender_test_harness.hh" 2 | 3 | #include "random.hh" 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | using namespace std; 14 | 15 | int main() 16 | { 17 | try { 18 | auto rd = get_random_engine(); 19 | 20 | { 21 | TCPConfig cfg; 22 | const Wrap32 isn( rd() ); 23 | cfg.fixed_isn = isn; 24 | 25 | TCPSenderTestHarness test { "SYN sent after first push", cfg }; 26 | test.execute( Push {} ); 27 | test.execute( ExpectMessage {}.with_no_flags().with_syn( true ).with_payload_size( 0 ).with_seqno( isn ) ); 28 | test.execute( ExpectSeqno { isn + 1 } ); 29 | test.execute( ExpectSeqnosInFlight { 1 } ); 30 | } 31 | 32 | { 33 | TCPConfig cfg; 34 | const Wrap32 isn( rd() ); 35 | cfg.fixed_isn = isn; 36 | 37 | TCPSenderTestHarness test { "SYN acked test", cfg }; 38 | test.execute( Push {} ); 39 | test.execute( ExpectMessage {}.with_no_flags().with_syn( true ).with_payload_size( 0 ).with_seqno( isn ) ); 40 | test.execute( ExpectSeqno { isn + 1 } ); 41 | test.execute( ExpectSeqnosInFlight { 1 } ); 42 | test.execute( AckReceived { isn + 1 } ); 43 | test.execute( ExpectNoSegment {} ); 44 | test.execute( ExpectSeqnosInFlight { 0 } ); 45 | } 46 | 47 | { 48 | TCPConfig cfg; 49 | const Wrap32 isn( rd() ); 50 | cfg.fixed_isn = isn; 51 | 52 | TCPSenderTestHarness test { "SYN -> wrong ack test", cfg }; 53 | test.execute( Push {} ); 54 | test.execute( ExpectMessage {}.with_no_flags().with_syn( true ).with_payload_size( 0 ).with_seqno( isn ) ); 55 | test.execute( ExpectSeqno { isn + 1 } ); 56 | test.execute( ExpectSeqnosInFlight { 1 } ); 57 | test.execute( AckReceived { isn } ); 58 | test.execute( ExpectSeqno { isn + 1 } ); 59 | test.execute( ExpectNoSegment {} ); 60 | test.execute( ExpectSeqnosInFlight { 1 } ); 61 | } 62 | 63 | { 64 | TCPConfig cfg; 65 | const Wrap32 isn( rd() ); 66 | cfg.fixed_isn = isn; 67 | 68 | TCPSenderTestHarness test { "SYN acked, data", cfg }; 69 | test.execute( Push {} ); 70 | test.execute( ExpectMessage {}.with_no_flags().with_syn( true ).with_payload_size( 0 ).with_seqno( isn ) ); 71 | test.execute( ExpectSeqno { isn + 1 } ); 72 | test.execute( ExpectSeqnosInFlight { 1 } ); 73 | test.execute( AckReceived { isn + 1 } ); 74 | test.execute( ExpectNoSegment {} ); 75 | test.execute( ExpectSeqnosInFlight { 0 } ); 76 | test.execute( Push { "abcdefgh" } ); 77 | test.execute( Tick { 1 } ); 78 | test.execute( ExpectMessage {}.with_seqno( isn + 1 ).with_data( "abcdefgh" ) ); 79 | test.execute( ExpectSeqno { isn + 9 } ); 80 | test.execute( ExpectSeqnosInFlight { 8 } ); 81 | test.execute( AckReceived { isn + 9 } ); 82 | test.execute( ExpectNoSegment {} ); 83 | test.execute( ExpectSeqnosInFlight { 0 } ); 84 | test.execute( ExpectSeqno { isn + 9 } ); 85 | } 86 | 87 | } catch ( const exception& e ) { 88 | cerr << e.what() << endl; 89 | return 1; 90 | } 91 | 92 | return EXIT_SUCCESS; 93 | } 94 | -------------------------------------------------------------------------------- /tests/send_retx.cc: -------------------------------------------------------------------------------- 1 | #include "random.hh" 2 | #include "sender_test_harness.hh" 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | using namespace std; 13 | 14 | int main() 15 | { 16 | try { 17 | auto rd = get_random_engine(); 18 | 19 | { 20 | TCPConfig cfg; 21 | const Wrap32 isn( rd() ); 22 | const uint16_t retx_timeout = uniform_int_distribution { 10, 10000 }( rd ); 23 | cfg.fixed_isn = isn; 24 | cfg.rt_timeout = retx_timeout; 25 | 26 | TCPSenderTestHarness test { "Retx SYN twice at the right times, then ack", cfg }; 27 | test.execute( Push {} ); 28 | test.execute( ExpectMessage {}.with_no_flags().with_syn( true ).with_payload_size( 0 ).with_seqno( isn ) ); 29 | test.execute( ExpectNoSegment {} ); 30 | test.execute( ExpectSeqno { isn + 1 } ); 31 | test.execute( ExpectSeqnosInFlight { 1 } ); 32 | test.execute( Tick { retx_timeout - 1U } ); 33 | test.execute( ExpectNoSegment {} ); 34 | test.execute( Tick { 1 } ); 35 | test.execute( ExpectMessage {}.with_no_flags().with_syn( true ).with_payload_size( 0 ).with_seqno( isn ) ); 36 | test.execute( ExpectSeqno { isn + 1 } ); 37 | test.execute( ExpectSeqnosInFlight { 1 } ); 38 | // Wait twice as long b/c exponential back-off 39 | test.execute( Tick { 2 * retx_timeout - 1U } ); 40 | test.execute( ExpectNoSegment {} ); 41 | test.execute( Tick { 1 } ); 42 | test.execute( ExpectMessage {}.with_no_flags().with_syn( true ).with_payload_size( 0 ).with_seqno( isn ) ); 43 | test.execute( ExpectSeqno { isn + 1 } ); 44 | test.execute( ExpectSeqnosInFlight { 1 } ); 45 | test.execute( AckReceived { Wrap32 { isn + 1 } } ); 46 | test.execute( ExpectSeqno { isn + 1 } ); 47 | test.execute( ExpectSeqnosInFlight { 0 } ); 48 | } 49 | 50 | { 51 | TCPConfig cfg; 52 | const Wrap32 isn( rd() ); 53 | const uint16_t retx_timeout = uniform_int_distribution { 10, 10000 }( rd ); 54 | cfg.fixed_isn = isn; 55 | cfg.rt_timeout = retx_timeout; 56 | 57 | TCPSenderTestHarness test { "Retx SYN until too many retransmissions", cfg }; 58 | test.execute( Push {} ); 59 | test.execute( ExpectMessage {}.with_no_flags().with_syn( true ).with_payload_size( 0 ).with_seqno( isn ) ); 60 | test.execute( ExpectNoSegment {} ); 61 | test.execute( ExpectSeqno { isn + 1 } ); 62 | test.execute( ExpectSeqnosInFlight { 1 } ); 63 | for ( size_t attempt_no = 0; attempt_no < TCPConfig::MAX_RETX_ATTEMPTS; attempt_no++ ) { 64 | test.execute( Tick { ( retx_timeout << attempt_no ) - 1U }.with_max_retx_exceeded( false ) ); 65 | test.execute( ExpectNoSegment {} ); 66 | test.execute( Tick { 1 }.with_max_retx_exceeded( false ) ); 67 | test.execute( ExpectMessage {}.with_no_flags().with_syn( true ).with_payload_size( 0 ).with_seqno( isn ) ); 68 | test.execute( ExpectSeqno { isn + 1 } ); 69 | test.execute( ExpectSeqnosInFlight { 1 } ); 70 | } 71 | test.execute( 72 | Tick { ( retx_timeout << TCPConfig::MAX_RETX_ATTEMPTS ) - 1U }.with_max_retx_exceeded( false ) ); 73 | test.execute( Tick { 1 }.with_max_retx_exceeded( true ) ); 74 | } 75 | 76 | { 77 | TCPConfig cfg; 78 | const Wrap32 isn( rd() ); 79 | const uint16_t retx_timeout = uniform_int_distribution { 10, 10000 }( rd ); 80 | cfg.fixed_isn = isn; 81 | cfg.rt_timeout = retx_timeout; 82 | 83 | TCPSenderTestHarness test { "Send some data, the retx and succeed, then retx till limit", cfg }; 84 | test.execute( Push {} ); 85 | test.execute( ExpectMessage {}.with_no_flags().with_syn( true ).with_payload_size( 0 ).with_seqno( isn ) ); 86 | test.execute( ExpectNoSegment {} ); 87 | test.execute( AckReceived { Wrap32 { isn + 1 } } ); 88 | test.execute( Push { "abcd" } ); 89 | test.execute( ExpectMessage {}.with_payload_size( 4 ) ); 90 | test.execute( ExpectNoSegment {} ); 91 | test.execute( AckReceived { Wrap32 { isn + 5 } } ); 92 | test.execute( ExpectSeqnosInFlight { 0 } ); 93 | test.execute( Push { "efgh" } ); 94 | test.execute( ExpectMessage {}.with_payload_size( 4 ) ); 95 | test.execute( ExpectNoSegment {} ); 96 | test.execute( Tick { retx_timeout }.with_max_retx_exceeded( false ) ); 97 | test.execute( ExpectMessage {}.with_payload_size( 4 ) ); 98 | test.execute( ExpectNoSegment {} ); 99 | test.execute( AckReceived { Wrap32 { isn + 9 } } ); 100 | test.execute( ExpectSeqnosInFlight { 0 } ); 101 | test.execute( Push { "ijkl" } ); 102 | test.execute( ExpectMessage {}.with_payload_size( 4 ).with_seqno( isn + 9 ) ); 103 | for ( size_t attempt_no = 0; attempt_no < TCPConfig::MAX_RETX_ATTEMPTS; attempt_no++ ) { 104 | test.execute( Tick { ( retx_timeout << attempt_no ) - 1U }.with_max_retx_exceeded( false ) ); 105 | test.execute( ExpectNoSegment {} ); 106 | test.execute( Tick { 1 }.with_max_retx_exceeded( false ) ); 107 | test.execute( ExpectMessage {}.with_payload_size( 4 ).with_seqno( isn + 9 ) ); 108 | test.execute( ExpectSeqnosInFlight { 4 } ); 109 | } 110 | test.execute( 111 | Tick { ( retx_timeout << TCPConfig::MAX_RETX_ATTEMPTS ) - 1U }.with_max_retx_exceeded( false ) ); 112 | test.execute( Tick { 1 }.with_max_retx_exceeded( true ) ); 113 | } 114 | 115 | } catch ( const exception& e ) { 116 | cerr << e.what() << endl; 117 | return 1; 118 | } 119 | 120 | return EXIT_SUCCESS; 121 | } 122 | -------------------------------------------------------------------------------- /tests/test_should_be.hh: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "conversions.hh" 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | // NOLINTNEXTLINE(cppcoreguidelines-macro-usage)) 12 | #define test_should_be( act, exp ) test_should_be_helper( act, exp, #act, #exp, __LINE__ ) 13 | 14 | template 15 | static void test_should_be_helper( const T& actual, 16 | const T& expected, 17 | const char* actual_s, 18 | const char* expected_s, 19 | const int lineno ) 20 | { 21 | if ( actual != expected ) { 22 | std::ostringstream ss; 23 | ss << "`" << actual_s << "` should have been `" << expected_s << "`, but the former is\n\t" 24 | << to_string( actual ) << "\nand the latter is\n\t" << to_string( expected ) << " (difference of " 25 | << static_cast( expected - actual ) << ")\n" 26 | << " (at line " << lineno << ")\n"; 27 | throw std::runtime_error( ss.str() ); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /tests/webget_t.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | WEB_HASH=`${1}/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 | -------------------------------------------------------------------------------- /tests/wrapping_integers_cmp.cc: -------------------------------------------------------------------------------- 1 | #include "random.hh" 2 | #include "test_should_be.hh" 3 | #include "wrapping_integers.hh" 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | using namespace std; 13 | 14 | int main() 15 | { 16 | try { 17 | // Comparing low-number adjacent seqnos 18 | test_should_be( Wrap32( 3 ) != Wrap32( 1 ), true ); 19 | test_should_be( Wrap32( 3 ) == Wrap32( 1 ), false ); 20 | 21 | constexpr size_t N_REPS = 32768; 22 | 23 | auto rd = get_random_engine(); 24 | 25 | for ( size_t i = 0; i < N_REPS; i++ ) { 26 | const uint32_t n = rd(); 27 | const uint8_t diff = rd(); 28 | const uint32_t m = n + diff; 29 | test_should_be( Wrap32( n ) == Wrap32( m ), n == m ); 30 | test_should_be( Wrap32( n ) != Wrap32( m ), n != m ); 31 | } 32 | 33 | } catch ( const exception& e ) { 34 | cerr << e.what() << endl; 35 | return 1; 36 | } 37 | 38 | return EXIT_SUCCESS; 39 | } 40 | -------------------------------------------------------------------------------- /tests/wrapping_integers_extra.cc: -------------------------------------------------------------------------------- 1 | #include "test_should_be.hh" 2 | #include "wrapping_integers.hh" 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | using namespace std; 14 | 15 | int main() 16 | { 17 | try { 18 | for ( uint64_t checkpoint = 0; checkpoint < 100000; ++checkpoint ) { 19 | test_should_be( Wrap32::wrap( 0UL, Wrap32 { 19 } ).unwrap( Wrap32 { 19 }, checkpoint ), 0UL ); 20 | } 21 | 22 | for ( uint64_t checkpoint = 0; checkpoint < 100000; ++checkpoint ) { 23 | test_should_be( Wrap32::wrap( 1UL, Wrap32 { 19 } ).unwrap( Wrap32 { 19 }, checkpoint ), 1UL ); 24 | } 25 | 26 | for ( uint64_t checkpoint = UINT32_MAX - 100000UL; checkpoint < UINT32_MAX + 100000UL; ++checkpoint ) { 27 | test_should_be( Wrap32::wrap( UINT32_MAX - 1UL, Wrap32 { 19 } ).unwrap( Wrap32 { 19 }, checkpoint ), 28 | UINT32_MAX - 1UL ); 29 | } 30 | 31 | for ( uint64_t checkpoint = UINT32_MAX - 100000UL; checkpoint < UINT32_MAX + 100000UL; ++checkpoint ) { 32 | test_should_be( Wrap32::wrap( UINT32_MAX, Wrap32 { 19 } ).unwrap( Wrap32 { 19 }, checkpoint ), 33 | uint64_t { UINT32_MAX } ); 34 | } 35 | 36 | for ( uint64_t checkpoint = UINT32_MAX - 100000UL; checkpoint < UINT32_MAX + 100000UL; ++checkpoint ) { 37 | test_should_be( Wrap32::wrap( UINT32_MAX + 1UL, Wrap32 { 19 } ).unwrap( Wrap32 { 19 }, checkpoint ), 38 | UINT32_MAX + 1UL ); 39 | } 40 | 41 | for ( uint64_t checkpoint = UINT32_MAX - 100000UL; checkpoint < UINT32_MAX + 100000UL; ++checkpoint ) { 42 | test_should_be( Wrap32::wrap( UINT32_MAX + 2UL, Wrap32 { 19 } ).unwrap( Wrap32 { 19 }, checkpoint ), 43 | UINT32_MAX + 2UL ); 44 | } 45 | 46 | for ( uint64_t checkpoint = 2UL * UINT32_MAX - 100000UL; checkpoint < 2UL * UINT32_MAX + 100000UL; 47 | ++checkpoint ) { 48 | test_should_be( Wrap32::wrap( 2UL * UINT32_MAX - 1UL, Wrap32 { 19 } ).unwrap( Wrap32 { 19 }, checkpoint ), 49 | 2UL * UINT32_MAX - 1UL ); 50 | } 51 | 52 | for ( uint64_t checkpoint = 2UL * UINT32_MAX - 100000UL; checkpoint < 2UL * UINT32_MAX + 100000UL; 53 | ++checkpoint ) { 54 | test_should_be( Wrap32::wrap( 2UL * UINT32_MAX, Wrap32 { 19 } ).unwrap( Wrap32 { 19 }, checkpoint ), 55 | 2UL * UINT32_MAX ); 56 | } 57 | 58 | for ( uint64_t checkpoint = 2UL * UINT32_MAX - 100000UL; checkpoint < 2UL * UINT32_MAX + 100000UL; 59 | ++checkpoint ) { 60 | test_should_be( Wrap32::wrap( 2UL * UINT32_MAX + 1UL, Wrap32 { 19 } ).unwrap( Wrap32 { 19 }, checkpoint ), 61 | 2UL * UINT32_MAX + 1UL ); 62 | } 63 | 64 | for ( uint64_t checkpoint = 2UL * UINT32_MAX - 100000UL; checkpoint < 2UL * UINT32_MAX + 100000UL; 65 | ++checkpoint ) { 66 | test_should_be( Wrap32::wrap( 2UL * UINT32_MAX + 2UL, Wrap32 { 19 } ).unwrap( Wrap32 { 19 }, checkpoint ), 67 | 2UL * UINT32_MAX + 2UL ); 68 | } 69 | 70 | for ( int64_t i = -100000; i < 100000; ++i ) { 71 | test_should_be( Wrap32::wrap( UINT32_MAX + i, Wrap32 { 19 } ).unwrap( Wrap32 { 19 }, UINT32_MAX ), 72 | uint64_t( UINT32_MAX + i ) ); 73 | } 74 | 75 | for ( int64_t i = -100000; i < 100000; ++i ) { 76 | test_should_be( Wrap32::wrap( 2UL * UINT32_MAX + i, Wrap32 { 19 } ).unwrap( Wrap32 { 19 }, 2UL * UINT32_MAX ), 77 | 2UL * UINT32_MAX + i ); 78 | } 79 | } catch ( const exception& e ) { 80 | cerr << e.what() << endl; 81 | return 1; 82 | } 83 | 84 | return EXIT_SUCCESS; 85 | } 86 | -------------------------------------------------------------------------------- /tests/wrapping_integers_roundtrip.cc: -------------------------------------------------------------------------------- 1 | #include "conversions.hh" 2 | #include "random.hh" 3 | #include "wrapping_integers.hh" 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | using namespace std; 11 | 12 | void check_roundtrip( const Wrap32 isn, const uint64_t value, const uint64_t checkpoint ) 13 | { 14 | if ( Wrap32::wrap( value, isn ).unwrap( isn, checkpoint ) != value ) { 15 | ostringstream ss; 16 | 17 | ss << "Expected unwrap(wrap()) to recover same value, and it didn't!\n"; 18 | ss << " unwrap(wrap(value, isn), isn, checkpoint) did not equal value\n"; 19 | ss << " where value = " << value << ", isn = " << isn << ", and checkpoint = " << checkpoint << "\n"; 20 | ss << " (Difference between value and checkpoint is " << value - checkpoint << ".)\n"; 21 | throw runtime_error( ss.str() ); 22 | } 23 | } 24 | 25 | int main() 26 | { 27 | try { 28 | auto rd = get_random_engine(); 29 | uniform_int_distribution dist31minus1 { 0, ( uint32_t { 1 } << 31 ) - 1 }; 30 | uniform_int_distribution dist32 { 0, numeric_limits::max() }; 31 | uniform_int_distribution dist63 { 0, uint64_t { 1 } << 63 }; 32 | 33 | const uint64_t big_offset = ( uint64_t { 1 } << 31 ) - 1; 34 | 35 | for ( unsigned int i = 0; i < 1000000; i++ ) { 36 | const Wrap32 isn { dist32( rd ) }; 37 | const uint64_t val { dist63( rd ) }; 38 | const uint64_t offset { dist31minus1( rd ) }; 39 | 40 | check_roundtrip( isn, val, val ); 41 | check_roundtrip( isn, val + 1, val ); 42 | check_roundtrip( isn, val - 1, val ); 43 | check_roundtrip( isn, val + offset, val ); 44 | check_roundtrip( isn, val - offset, val ); 45 | check_roundtrip( isn, val + big_offset, val ); 46 | check_roundtrip( isn, val - big_offset, val ); 47 | } 48 | } catch ( const exception& e ) { 49 | cerr << e.what() << endl; 50 | return 1; 51 | } 52 | 53 | return EXIT_SUCCESS; 54 | } 55 | -------------------------------------------------------------------------------- /tests/wrapping_integers_unwrap.cc: -------------------------------------------------------------------------------- 1 | #include "test_should_be.hh" 2 | #include "wrapping_integers.hh" 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | using namespace std; 11 | 12 | int main() 13 | { 14 | try { 15 | // Unwrap the first byte after ISN 16 | test_should_be( Wrap32( 1 ).unwrap( Wrap32( 0 ), 0 ), 1UL ); 17 | // Unwrap the first byte after the first wrap 18 | test_should_be( Wrap32( 1 ).unwrap( Wrap32( 0 ), UINT32_MAX ), ( 1UL << 32 ) + 1 ); 19 | // Unwrap the last byte before the third wrap 20 | test_should_be( Wrap32( UINT32_MAX - 1 ).unwrap( Wrap32( 0 ), 3 * ( 1UL << 32 ) ), 3 * ( 1UL << 32 ) - 2 ); 21 | // Unwrap the 10th from last byte before the third wrap 22 | test_should_be( Wrap32( UINT32_MAX - 10 ).unwrap( Wrap32( 0 ), 3 * ( 1UL << 32 ) ), 3 * ( 1UL << 32 ) - 11 ); 23 | // Non-zero ISN 24 | test_should_be( Wrap32( UINT32_MAX ).unwrap( Wrap32( 10 ), 3 * ( 1UL << 32 ) ), 3 * ( 1UL << 32 ) - 11 ); 25 | // Big unwrap 26 | test_should_be( Wrap32( UINT32_MAX ).unwrap( Wrap32( 0 ), 0 ), static_cast( UINT32_MAX ) ); 27 | // Unwrap a non-zero ISN 28 | test_should_be( Wrap32( 16 ).unwrap( Wrap32( 16 ), 0 ), 0UL ); 29 | 30 | // Big unwrap with non-zero ISN 31 | test_should_be( Wrap32( 15 ).unwrap( Wrap32( 16 ), 0 ), static_cast( UINT32_MAX ) ); 32 | // Big unwrap with non-zero ISN 33 | test_should_be( Wrap32( 0 ).unwrap( Wrap32( INT32_MAX ), 0 ), static_cast( INT32_MAX ) + 2 ); 34 | // Barely big unwrap with non-zero ISN 35 | test_should_be( Wrap32( UINT32_MAX ).unwrap( Wrap32( INT32_MAX ), 0 ), static_cast( 1 ) << 31 ); 36 | // Nearly big unwrap with non-zero ISN 37 | test_should_be( Wrap32( UINT32_MAX ).unwrap( Wrap32( 1UL << 31 ), 0 ), 38 | static_cast( UINT32_MAX ) >> 1 ); 39 | } catch ( const exception& e ) { 40 | cerr << e.what() << endl; 41 | return 1; 42 | } 43 | 44 | return EXIT_SUCCESS; 45 | } 46 | -------------------------------------------------------------------------------- /tests/wrapping_integers_wrap.cc: -------------------------------------------------------------------------------- 1 | #include "test_should_be.hh" 2 | #include "wrapping_integers.hh" 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | using namespace std; 12 | 13 | int main() 14 | { 15 | try { 16 | test_should_be( Wrap32::wrap( 3 * ( 1LL << 32 ), Wrap32( 0 ) ), Wrap32( 0 ) ); 17 | test_should_be( Wrap32::wrap( 3 * ( 1LL << 32 ) + 17, Wrap32( 15 ) ), Wrap32( 32 ) ); 18 | test_should_be( Wrap32::wrap( 7 * ( 1LL << 32 ) - 2, Wrap32( 15 ) ), Wrap32( 13 ) ); 19 | } catch ( const exception& e ) { 20 | cerr << e.what() << endl; 21 | return EXIT_FAILURE; 22 | } 23 | 24 | return EXIT_SUCCESS; 25 | } 26 | -------------------------------------------------------------------------------- /util/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | file(GLOB LIB_SOURCES "*.cc") 2 | 3 | add_library(util_debug STATIC ${LIB_SOURCES}) 4 | 5 | add_library(util_sanitized EXCLUDE_FROM_ALL STATIC ${LIB_SOURCES}) 6 | target_compile_options(util_sanitized PUBLIC ${SANITIZING_FLAGS}) 7 | 8 | add_library(util_optimized EXCLUDE_FROM_ALL STATIC ${LIB_SOURCES}) 9 | target_compile_options(util_optimized PUBLIC "-O2") 10 | -------------------------------------------------------------------------------- /util/address.hh: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 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 | { 15 | public: 16 | //! \brief Wrapper around [sockaddr_storage](@ref man7::socket). 17 | //! \details A `sockaddr_storage` is enough space to store any socket address (IPv4 or IPv6). 18 | class Raw 19 | { 20 | public: 21 | sockaddr_storage storage {}; //!< The wrapped struct itself. 22 | // NOLINTBEGIN (*-explicit-*) 23 | operator sockaddr*(); 24 | operator const sockaddr*() const; 25 | // NOLINTEND (*-explicit-*) 26 | }; 27 | 28 | private: 29 | socklen_t _size; //!< Size of the wrapped address. 30 | Raw _address {}; //!< A wrapped [sockaddr_storage](@ref man7::socket) containing the address. 31 | 32 | //! Constructor from ip/host, service/port, and hints to the resolver. 33 | Address( const std::string& node, const std::string& service, const addrinfo& hints ); 34 | 35 | public: 36 | //! Construct by resolving a hostname and servicename. 37 | Address( const std::string& hostname, const std::string& service ); 38 | 39 | //! Construct from dotted-quad string ("18.243.0.1") and numeric port. 40 | explicit Address( const std::string& ip, std::uint16_t port = 0 ); 41 | 42 | //! Construct from a [sockaddr *](@ref man7::socket). 43 | Address( const sockaddr* addr, std::size_t size ); 44 | 45 | //! Equality comparison. 46 | bool operator==( const Address& other ) const; 47 | bool operator!=( const Address& other ) const { return not operator==( other ); } 48 | 49 | //! \name Conversions 50 | //!@{ 51 | 52 | //! Dotted-quad IP address string ("18.243.0.1") and numeric port. 53 | std::pair ip_port() const; 54 | //! Dotted-quad IP address string ("18.243.0.1"). 55 | std::string ip() const { return ip_port().first; } 56 | //! Numeric port (host byte order). 57 | uint16_t port() const { return ip_port().second; } 58 | //! Numeric IP address as an integer (i.e., in [host byte order](\ref man3::byteorder)). 59 | uint32_t ipv4_numeric() const; 60 | //! Create an Address from a 32-bit raw numeric IP address 61 | static Address from_ipv4_numeric( uint32_t ip_address ); 62 | //! Human-readable string, e.g., "8.8.8.8:53". 63 | std::string to_string() const; 64 | //!@} 65 | 66 | //! \name Low-level operations 67 | //!@{ 68 | 69 | //! Size of the underlying address storage. 70 | socklen_t size() const { return _size; } 71 | //! Const pointer to the underlying socket address storage. 72 | operator const sockaddr*() const { return static_cast( _address ); } // NOLINT(*-explicit-*) 73 | //! Safely convert to underlying sockaddr type 74 | template 75 | const sockaddr_type* as() const; 76 | 77 | //!@} 78 | }; 79 | -------------------------------------------------------------------------------- /util/arp_message.cc: -------------------------------------------------------------------------------- 1 | #include "arp_message.hh" 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | using namespace std; 8 | 9 | bool ARPMessage::supported() const 10 | { 11 | return hardware_type == TYPE_ETHERNET and protocol_type == EthernetHeader::TYPE_IPv4 12 | and hardware_address_size == sizeof( EthernetHeader::src ) 13 | and protocol_address_size == sizeof( IPv4Header::src ) 14 | and ( ( opcode == OPCODE_REQUEST ) or ( opcode == OPCODE_REPLY ) ); 15 | } 16 | 17 | string ARPMessage::to_string() const 18 | { 19 | stringstream ss {}; 20 | string opcode_str = "(unknown type)"; 21 | if ( opcode == OPCODE_REQUEST ) { 22 | opcode_str = "REQUEST"; 23 | } 24 | if ( opcode == OPCODE_REPLY ) { 25 | opcode_str = "REPLY"; 26 | } 27 | ss << "opcode=" << opcode_str << ", sender=" << ::to_string( sender_ethernet_address ) << "/" 28 | << inet_ntoa( { htobe32( sender_ip_address ) } ) << ", target=" << ::to_string( target_ethernet_address ) 29 | << "/" << inet_ntoa( { htobe32( target_ip_address ) } ); 30 | return ss.str(); 31 | } 32 | 33 | void ARPMessage::parse( Parser& parser ) 34 | { 35 | parser.integer( hardware_type ); 36 | parser.integer( protocol_type ); 37 | parser.integer( hardware_address_size ); 38 | parser.integer( protocol_address_size ); 39 | parser.integer( opcode ); 40 | 41 | if ( not supported() ) { 42 | parser.set_error(); 43 | return; 44 | } 45 | 46 | // read sender addresses (Ethernet and IP) 47 | for ( auto& b : sender_ethernet_address ) { 48 | parser.integer( b ); 49 | } 50 | parser.integer( sender_ip_address ); 51 | 52 | // read target addresses (Ethernet and IP) 53 | for ( auto& b : target_ethernet_address ) { 54 | parser.integer( b ); 55 | } 56 | parser.integer( target_ip_address ); 57 | } 58 | 59 | void ARPMessage::serialize( Serializer& serializer ) const 60 | { 61 | if ( not supported() ) { 62 | throw runtime_error( "ARPMessage: unsupported field combination (must be Ethernet/IP, and request or reply)" ); 63 | } 64 | 65 | serializer.integer( hardware_type ); 66 | serializer.integer( protocol_type ); 67 | serializer.integer( hardware_address_size ); 68 | serializer.integer( protocol_address_size ); 69 | serializer.integer( opcode ); 70 | 71 | // read sender addresses (Ethernet and IP) 72 | for ( const auto& b : sender_ethernet_address ) { 73 | serializer.integer( b ); 74 | } 75 | serializer.integer( sender_ip_address ); 76 | 77 | // read target addresses (Ethernet and IP) 78 | for ( const auto& b : target_ethernet_address ) { 79 | serializer.integer( b ); 80 | } 81 | serializer.integer( target_ip_address ); 82 | } 83 | -------------------------------------------------------------------------------- /util/arp_message.hh: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "ethernet_header.hh" 4 | #include "ipv4_header.hh" 5 | #include "parser.hh" 6 | 7 | // [ARP](\ref rfc::rfc826) message 8 | struct ARPMessage 9 | { 10 | static constexpr size_t LENGTH = 28; // ARP message length in bytes 11 | static constexpr uint16_t TYPE_ETHERNET = 1; // ARP type for Ethernet/Wi-Fi as link-layer protocol 12 | static constexpr uint16_t OPCODE_REQUEST = 1; 13 | static constexpr uint16_t OPCODE_REPLY = 2; 14 | 15 | uint16_t hardware_type = TYPE_ETHERNET; // Type of the link-layer protocol (generally Ethernet/Wi-Fi) 16 | uint16_t protocol_type = EthernetHeader::TYPE_IPv4; // Type of the Internet-layer protocol (generally IPv4) 17 | uint8_t hardware_address_size = sizeof( EthernetHeader::src ); 18 | uint8_t protocol_address_size = sizeof( IPv4Header::src ); 19 | uint16_t opcode {}; // Request or reply 20 | 21 | EthernetAddress sender_ethernet_address {}; 22 | uint32_t sender_ip_address {}; 23 | 24 | EthernetAddress target_ethernet_address {}; 25 | uint32_t target_ip_address {}; 26 | 27 | // Return a string containing the ARP message in human-readable format 28 | std::string to_string() const; 29 | 30 | // Is this type of ARP message supported by the parser? 31 | bool supported() const; 32 | 33 | void parse( Parser& parser ); 34 | void serialize( Serializer& serializer ) const; 35 | }; 36 | -------------------------------------------------------------------------------- /util/buffer.hh: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | class Buffer 7 | { 8 | std::shared_ptr buffer_; 9 | 10 | public: 11 | // NOLINTBEGIN(*-explicit-*) 12 | 13 | Buffer( std::string str = {} ) : buffer_( make_shared( std::move( str ) ) ) {} 14 | operator std::string_view() const { return *buffer_; } 15 | operator std::string&() { return *buffer_; } 16 | 17 | // NOLINTEND(*-explicit-*) 18 | 19 | std::string&& release() { return std::move( *buffer_ ); } 20 | size_t size() const { return buffer_->size(); } 21 | size_t length() const { return buffer_->length(); } 22 | bool empty() const { return buffer_->empty(); } 23 | }; 24 | -------------------------------------------------------------------------------- /util/checksum.hh: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "buffer.hh" 4 | 5 | #include 6 | #include 7 | #include 8 | 9 | //! The internet checksum algorithm 10 | class InternetChecksum 11 | { 12 | private: 13 | uint32_t sum_; 14 | bool parity_ {}; 15 | 16 | public: 17 | explicit InternetChecksum( const uint32_t sum = 0 ) : sum_( sum ) {} 18 | void add( std::string_view data ) 19 | { 20 | for ( const uint8_t i : data ) { 21 | uint16_t val = i; 22 | if ( not parity_ ) { 23 | val <<= 8; 24 | } 25 | sum_ += val; 26 | parity_ = !parity_; 27 | } 28 | } 29 | 30 | uint16_t value() const 31 | { 32 | uint32_t ret = sum_; 33 | 34 | while ( ret > 0xffff ) { 35 | ret = ( ret >> 16 ) + static_cast( ret ); 36 | } 37 | 38 | return ~ret; 39 | } 40 | 41 | void add( const std::vector& data ) 42 | { 43 | for ( const auto& x : data ) { 44 | add( x ); 45 | } 46 | } 47 | }; 48 | -------------------------------------------------------------------------------- /util/ethernet_frame.hh: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "buffer.hh" 4 | #include "ethernet_header.hh" 5 | #include "parser.hh" 6 | 7 | #include 8 | 9 | struct EthernetFrame 10 | { 11 | EthernetHeader header {}; 12 | std::vector payload {}; 13 | 14 | void parse( Parser& parser ) 15 | { 16 | header.parse( parser ); 17 | parser.all_remaining( payload ); 18 | } 19 | 20 | void serialize( Serializer& serializer ) const 21 | { 22 | header.serialize( serializer ); 23 | serializer.buffer( payload ); 24 | } 25 | }; 26 | -------------------------------------------------------------------------------- /util/ethernet_header.cc: -------------------------------------------------------------------------------- 1 | #include "ethernet_header.hh" 2 | 3 | #include 4 | #include 5 | 6 | using namespace std; 7 | 8 | //! \returns A string with a textual representation of an Ethernet address 9 | string to_string( const EthernetAddress address ) 10 | { 11 | stringstream ss {}; 12 | for ( size_t index = 0; index < address.size(); index++ ) { 13 | ss.width( 2 ); 14 | ss << setfill( '0' ) << hex << static_cast( address.at( index ) ); 15 | if ( index != address.size() - 1 ) { 16 | ss << ":"; 17 | } 18 | } 19 | return ss.str(); 20 | } 21 | 22 | //! \returns A string with the header's contents 23 | string EthernetHeader::to_string() const 24 | { 25 | stringstream ss {}; 26 | ss << "dst=" << ::to_string( dst ); 27 | ss << ", src=" << ::to_string( src ); 28 | ss << ", type="; 29 | switch ( type ) { 30 | case TYPE_IPv4: 31 | ss << "IPv4"; 32 | break; 33 | case TYPE_ARP: 34 | ss << "ARP"; 35 | break; 36 | default: 37 | ss << "[unknown type " << hex << type << "!]"; 38 | break; 39 | } 40 | 41 | return ss.str(); 42 | } 43 | 44 | void EthernetHeader::parse( Parser& parser ) 45 | { 46 | // read destination address 47 | for ( auto& b : dst ) { 48 | parser.integer( b ); 49 | } 50 | 51 | // read source address 52 | for ( auto& b : src ) { 53 | parser.integer( b ); 54 | } 55 | 56 | // read frame type (e.g. IPv4, ARP, or something else) 57 | parser.integer( type ); 58 | } 59 | 60 | void EthernetHeader::serialize( Serializer& serializer ) const 61 | { 62 | // write destination address 63 | for ( const auto& b : dst ) { 64 | serializer.integer( b ); 65 | } 66 | 67 | // write source address 68 | for ( const auto& b : src ) { 69 | serializer.integer( b ); 70 | } 71 | 72 | // write frame type (e.g. IPv4, ARP, or something else) 73 | serializer.integer( type ); 74 | } 75 | -------------------------------------------------------------------------------- /util/ethernet_header.hh: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "parser.hh" 4 | 5 | #include 6 | #include 7 | #include 8 | 9 | // Helper type for an Ethernet address (an array of six bytes) 10 | using EthernetAddress = std::array; 11 | 12 | // Ethernet broadcast address (ff:ff:ff:ff:ff:ff) 13 | constexpr EthernetAddress ETHERNET_BROADCAST = { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff }; 14 | 15 | // Printable representation of an EthernetAddress 16 | std::string to_string( EthernetAddress address ); 17 | 18 | // Ethernet frame header 19 | struct EthernetHeader 20 | { 21 | static constexpr size_t LENGTH = 14; //!< Ethernet header length in bytes 22 | static constexpr uint16_t TYPE_IPv4 = 0x800; //!< Type number for [IPv4](\ref rfc::rfc791) 23 | static constexpr uint16_t TYPE_ARP = 0x806; //!< Type number for [ARP](\ref rfc::rfc826) 24 | 25 | EthernetAddress dst; 26 | EthernetAddress src; 27 | uint16_t type; 28 | 29 | // Return a string containing a header in human-readable format 30 | std::string to_string() const; 31 | 32 | void parse( Parser& parser ); 33 | void serialize( Serializer& serializer ) const; 34 | }; 35 | -------------------------------------------------------------------------------- /util/exception.hh: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | class tagged_error : public std::system_error 10 | { 11 | private: 12 | std::string attempt_and_error_; 13 | int error_code_; 14 | 15 | public: 16 | tagged_error( const std::error_category& category, const std::string_view s_attempt, const int error_code ) 17 | : system_error( error_code, category ) 18 | , attempt_and_error_( std::string( s_attempt ) + ": " + std::system_error::what() ) 19 | , error_code_( error_code ) 20 | {} 21 | 22 | const char* what() const noexcept override { return attempt_and_error_.c_str(); } 23 | 24 | int error_code() const { return error_code_; } 25 | }; 26 | 27 | class unix_error : public tagged_error 28 | { 29 | public: 30 | explicit unix_error( const std::string_view s_attempt, const int s_errno = errno ) 31 | : tagged_error( std::system_category(), s_attempt, s_errno ) 32 | {} 33 | }; 34 | 35 | inline int CheckSystemCall( const std::string_view s_attempt, const int return_value ) 36 | { 37 | if ( return_value >= 0 ) { 38 | return return_value; 39 | } 40 | 41 | throw unix_error { s_attempt }; 42 | } 43 | 44 | template 45 | inline T* notnull( const std::string_view context, T* const x ) 46 | { 47 | return x ? x : throw std::runtime_error( std::string( context ) + ": returned null pointer" ); 48 | } 49 | 50 | inline std::string demangle( const char* name ) 51 | { 52 | int status {}; 53 | const std::unique_ptr res { abi::__cxa_demangle( name, nullptr, nullptr, &status ), 54 | free }; 55 | if ( status ) { 56 | throw std::runtime_error( "cxa_demangle" ); 57 | } 58 | return res.get(); 59 | } 60 | -------------------------------------------------------------------------------- /util/file_descriptor.cc: -------------------------------------------------------------------------------- 1 | #include "file_descriptor.hh" 2 | 3 | #include "exception.hh" 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | using namespace std; 15 | 16 | template 17 | T FileDescriptor::FDWrapper::CheckSystemCall( string_view s_attempt, T return_value ) const 18 | { 19 | if ( return_value >= 0 ) { 20 | return return_value; 21 | } 22 | 23 | if ( non_blocking_ and ( errno == EAGAIN or errno == EINPROGRESS ) ) { 24 | return 0; 25 | } 26 | 27 | throw unix_error { s_attempt }; 28 | } 29 | 30 | template 31 | T FileDescriptor::CheckSystemCall( std::string_view s_attempt, T return_value ) const 32 | { 33 | if ( not internal_fd_ ) { 34 | throw runtime_error( "internal error: missing internal_fd_" ); 35 | } 36 | return internal_fd_->CheckSystemCall( s_attempt, return_value ); 37 | } 38 | 39 | // fd is the file descriptor number returned by [open(2)](\ref man2::open) or similar 40 | FileDescriptor::FDWrapper::FDWrapper( int fd ) : fd_( fd ) 41 | { 42 | if ( fd < 0 ) { 43 | throw runtime_error( "invalid fd number:" + to_string( fd ) ); 44 | } 45 | 46 | const int flags = CheckSystemCall( "fcntl", fcntl( fd, F_GETFL ) ); // NOLINT(*-vararg) 47 | non_blocking_ = flags & O_NONBLOCK; // NOLINT(*-bitwise) 48 | } 49 | 50 | void FileDescriptor::FDWrapper::close() 51 | { 52 | CheckSystemCall( "close", ::close( fd_ ) ); 53 | eof_ = closed_ = true; 54 | } 55 | 56 | FileDescriptor::FDWrapper::~FDWrapper() 57 | { 58 | try { 59 | if ( closed_ ) { 60 | return; 61 | } 62 | close(); 63 | } catch ( const exception& e ) { 64 | // don't throw an exception from the destructor 65 | cerr << "Exception destructing FDWrapper: " << e.what() << endl; 66 | } 67 | } 68 | 69 | // fd is the file descriptor number returned by [open(2)](\ref man2::open) or similar 70 | FileDescriptor::FileDescriptor( int fd ) : internal_fd_( make_shared( fd ) ) {} 71 | 72 | // Private constructor used by duplicate() 73 | FileDescriptor::FileDescriptor( shared_ptr other_shared_ptr ) : internal_fd_( move( other_shared_ptr ) ) 74 | {} 75 | 76 | // returns a copy of this FileDescriptor 77 | FileDescriptor FileDescriptor::duplicate() const 78 | { 79 | return FileDescriptor { internal_fd_ }; 80 | } 81 | 82 | // buffer is the string to be read into 83 | void FileDescriptor::read( string& buffer ) 84 | { 85 | buffer.clear(); 86 | buffer.resize( kReadBufferSize ); 87 | 88 | const ssize_t bytes_read = ::read( fd_num(), buffer.data(), buffer.size() ); 89 | if ( bytes_read < 0 ) { 90 | if ( internal_fd_->non_blocking_ and ( errno == EAGAIN or errno == EINPROGRESS ) ) { 91 | return; 92 | } 93 | throw unix_error { "read" }; 94 | } 95 | 96 | register_read(); 97 | 98 | if ( bytes_read == 0 ) { 99 | internal_fd_->eof_ = true; 100 | } 101 | 102 | if ( bytes_read > static_cast( buffer.size() ) ) { 103 | throw runtime_error( "read() read more than requested" ); 104 | } 105 | 106 | buffer.resize( bytes_read ); 107 | } 108 | 109 | void FileDescriptor::read( vector>& buffers ) 110 | { 111 | if ( buffers.empty() ) { 112 | return; 113 | } 114 | 115 | buffers.back()->clear(); 116 | buffers.back()->resize( kReadBufferSize ); 117 | 118 | vector iovecs; 119 | iovecs.reserve( buffers.size() ); 120 | size_t total_size = 0; 121 | for ( const auto& x : buffers ) { 122 | iovecs.push_back( { const_cast( x->data() ), x->size() } ); // NOLINT(*-const-cast) 123 | total_size += x->size(); 124 | } 125 | 126 | const ssize_t bytes_read = ::readv( fd_num(), iovecs.data(), static_cast( iovecs.size() ) ); 127 | if ( bytes_read < 0 ) { 128 | if ( internal_fd_->non_blocking_ and ( errno == EAGAIN or errno == EINPROGRESS ) ) { 129 | return; 130 | } 131 | throw unix_error { "read" }; 132 | } 133 | 134 | register_read(); 135 | 136 | if ( bytes_read > static_cast( total_size ) ) { 137 | throw runtime_error( "read() read more than requested" ); 138 | } 139 | 140 | size_t remaining_size = bytes_read; 141 | for ( auto& buf : buffers ) { 142 | if ( remaining_size <= buf->size() ) { 143 | remaining_size -= buf->size(); 144 | } else if ( remaining_size == 0 ) { 145 | buf->clear(); 146 | } else { 147 | buf->resize( remaining_size ); 148 | remaining_size = 0; 149 | } 150 | } 151 | } 152 | 153 | size_t FileDescriptor::write( string_view buffer ) 154 | { 155 | return write( vector { buffer } ); 156 | } 157 | 158 | size_t FileDescriptor::write( const vector& buffers ) 159 | { 160 | vector iovecs; 161 | iovecs.reserve( buffers.size() ); 162 | size_t total_size = 0; 163 | for ( const auto x : buffers ) { 164 | iovecs.push_back( { const_cast( x.data() ), x.size() } ); // NOLINT(*-const-cast) 165 | total_size += x.size(); 166 | } 167 | 168 | const ssize_t bytes_written 169 | = CheckSystemCall( "writev", ::writev( fd_num(), iovecs.data(), static_cast( iovecs.size() ) ) ); 170 | register_write(); 171 | 172 | if ( bytes_written == 0 and total_size != 0 ) { 173 | throw runtime_error( "write returned 0 given non-empty input buffer" ); 174 | } 175 | 176 | if ( bytes_written > static_cast( total_size ) ) { 177 | throw runtime_error( "write wrote more than length of input buffer" ); 178 | } 179 | 180 | return bytes_written; 181 | } 182 | 183 | void FileDescriptor::set_blocking( bool blocking ) 184 | { 185 | int flags = CheckSystemCall( "fcntl", fcntl( fd_num(), F_GETFL ) ); // NOLINT(*-vararg) 186 | if ( blocking ) { 187 | flags ^= ( flags & O_NONBLOCK ); // NOLINT(*-bitwise) 188 | } else { 189 | flags |= O_NONBLOCK; // NOLINT(*-bitwise) 190 | } 191 | 192 | CheckSystemCall( "fcntl", fcntl( fd_num(), F_SETFL, flags ) ); // NOLINT(*-vararg) 193 | 194 | internal_fd_->non_blocking_ = not blocking; 195 | } 196 | -------------------------------------------------------------------------------- /util/file_descriptor.hh: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | // A reference-counted handle to a file descriptor 9 | class FileDescriptor 10 | { 11 | // FDWrapper: A handle on a kernel file descriptor. 12 | // FileDescriptor objects contain a std::shared_ptr to a FDWrapper. 13 | class FDWrapper 14 | { 15 | public: 16 | int fd_; // The file descriptor number returned by the kernel 17 | bool eof_ = false; // Flag indicating whether FDWrapper::fd_ is at EOF 18 | bool closed_ = false; // Flag indicating whether FDWrapper::fd_ has been closed 19 | bool non_blocking_ = false; // Flag indicating whether FDWrapper::fd_ is non-blocking 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( 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 | template 31 | T CheckSystemCall( std::string_view s_attempt, T return_value ) const; 32 | 33 | // An FDWrapper cannot be copied or moved 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 | // A reference-counted handle to a shared FDWrapper 41 | std::shared_ptr internal_fd_; 42 | 43 | // private constructor used to duplicate the FileDescriptor (increase the reference count) 44 | explicit FileDescriptor( std::shared_ptr other_shared_ptr ); 45 | 46 | protected: 47 | // size of buffer to allocate for read() 48 | static constexpr size_t kReadBufferSize = 16384; 49 | 50 | void set_eof() { internal_fd_->eof_ = true; } 51 | void register_read() { ++internal_fd_->read_count_; } // increment read count 52 | void register_write() { ++internal_fd_->write_count_; } // increment write count 53 | 54 | template 55 | T CheckSystemCall( std::string_view s_attempt, T return_value ) const; 56 | 57 | public: 58 | // Construct from a file descriptor number returned by the kernel 59 | explicit FileDescriptor( int fd ); 60 | 61 | // Free the std::shared_ptr; the FDWrapper destructor calls close() when the refcount goes to zero. 62 | ~FileDescriptor() = default; 63 | 64 | // Read into `buffer` 65 | void read( std::string& buffer ); 66 | void read( std::vector>& buffers ); 67 | 68 | // Attempt to write a buffer 69 | // returns number of bytes written 70 | size_t write( std::string_view buffer ); 71 | size_t write( const std::vector& buffers ); 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( bool blocking ); 81 | 82 | // Size of file 83 | off_t size() const; 84 | 85 | // FDWrapper accessors 86 | int fd_num() const { return internal_fd_->fd_; } // underlying descriptor number 87 | bool eof() const { return internal_fd_->eof_; } // EOF flag state 88 | bool closed() const { return internal_fd_->closed_; } // closed flag state 89 | unsigned int read_count() const { return internal_fd_->read_count_; } // number of reads 90 | unsigned int write_count() const { return internal_fd_->write_count_; } // number of writes 91 | 92 | // Copy/move constructor/assignment operators 93 | // FileDescriptor can be moved, but cannot be copied implicitly (see duplicate()) 94 | FileDescriptor( const FileDescriptor& other ) = delete; // copy construction is forbidden 95 | FileDescriptor& operator=( const FileDescriptor& other ) = delete; // copy assignment is forbidden 96 | FileDescriptor( FileDescriptor&& other ) = default; // move construction is allowed 97 | FileDescriptor& operator=( FileDescriptor&& other ) = default; // move assignment is allowed 98 | }; 99 | -------------------------------------------------------------------------------- /util/ipv4_datagram.hh: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "ipv4_header.hh" 4 | #include "parser.hh" 5 | 6 | #include 7 | #include 8 | #include 9 | 10 | //! \brief [IPv4](\ref rfc::rfc791) Internet datagram 11 | struct IPv4Datagram 12 | { 13 | IPv4Header header {}; 14 | std::vector payload {}; 15 | 16 | void parse( Parser& parser ) 17 | { 18 | header.parse( parser ); 19 | parser.all_remaining( payload ); 20 | } 21 | 22 | void serialize( Serializer& serializer ) const 23 | { 24 | header.serialize( serializer ); 25 | for ( const auto& x : payload ) { 26 | serializer.buffer( x ); 27 | } 28 | } 29 | }; 30 | 31 | using InternetDatagram = IPv4Datagram; 32 | -------------------------------------------------------------------------------- /util/ipv4_header.cc: -------------------------------------------------------------------------------- 1 | #include "ipv4_header.hh" 2 | #include "checksum.hh" 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | using namespace std; 10 | 11 | // Parse from string. 12 | void IPv4Header::parse( Parser& parser ) 13 | { 14 | uint8_t first_byte {}; 15 | parser.integer( first_byte ); 16 | ver = first_byte >> 4; // version 17 | hlen = first_byte & 0x0f; // header length 18 | parser.integer( tos ); // type of service 19 | parser.integer( len ); 20 | parser.integer( id ); 21 | 22 | uint16_t fo_val {}; 23 | parser.integer( fo_val ); 24 | df = static_cast( fo_val & 0x4000 ); // don't fragment 25 | mf = static_cast( fo_val & 0x2000 ); // more fragments 26 | offset = fo_val & 0x1fff; // offset 27 | 28 | parser.integer( ttl ); 29 | parser.integer( proto ); 30 | parser.integer( cksum ); 31 | parser.integer( src ); 32 | parser.integer( dst ); 33 | 34 | if ( ver != 4 ) { 35 | parser.set_error(); 36 | } 37 | 38 | if ( hlen < 5 ) { 39 | parser.set_error(); 40 | } 41 | 42 | parser.remove_prefix( static_cast( hlen ) * 4 - IPv4Header::LENGTH ); 43 | 44 | // Verify checksum 45 | const uint16_t given_cksum = cksum; 46 | compute_checksum(); 47 | if ( cksum != given_cksum ) { 48 | parser.set_error(); 49 | } 50 | } 51 | 52 | // Serialize the IPv4Header (does not recompute the checksum) 53 | void IPv4Header::serialize( Serializer& serializer ) const 54 | { 55 | // consistency checks 56 | if ( ver != 4 ) { 57 | throw runtime_error( "wrong IP version" ); 58 | } 59 | 60 | const uint8_t first_byte = ( static_cast( ver ) << 4 ) | ( hlen & 0xfU ); 61 | serializer.integer( first_byte ); // version and header length 62 | serializer.integer( tos ); 63 | serializer.integer( len ); 64 | serializer.integer( id ); 65 | 66 | const uint16_t fo_val = ( df ? 0x4000U : 0 ) | ( mf ? 0x2000U : 0 ) | ( offset & 0x1fffU ); 67 | serializer.integer( fo_val ); 68 | 69 | serializer.integer( ttl ); 70 | serializer.integer( proto ); 71 | 72 | serializer.integer( cksum ); 73 | 74 | serializer.integer( src ); 75 | serializer.integer( dst ); 76 | } 77 | 78 | uint16_t IPv4Header::payload_length() const 79 | { 80 | return len - 4 * hlen; 81 | } 82 | 83 | //! \details This value is needed when computing the checksum of an encapsulated TCP segment. 84 | //! ~~~{.txt} 85 | //! 0 7 8 15 16 23 24 31 86 | //! +--------+--------+--------+--------+ 87 | //! | source address | 88 | //! +--------+--------+--------+--------+ 89 | //! | destination address | 90 | //! +--------+--------+--------+--------+ 91 | //! | zero |protocol| payload length | 92 | //! +--------+--------+--------+--------+ 93 | //! ~~~ 94 | uint32_t IPv4Header::pseudo_checksum() const 95 | { 96 | uint32_t pcksum = ( src >> 16 ) + static_cast( src ); // source addr 97 | pcksum += ( dst >> 16 ) + static_cast( dst ); 98 | pcksum += proto; // protocol 99 | pcksum += payload_length(); // payload length 100 | return pcksum; 101 | } 102 | 103 | void IPv4Header::compute_checksum() 104 | { 105 | cksum = 0; 106 | Serializer s; 107 | serialize( s ); 108 | 109 | // calculate checksum -- taken over header only 110 | InternetChecksum check; 111 | check.add( s.output() ); 112 | cksum = check.value(); 113 | } 114 | 115 | std::string IPv4Header::to_string() const 116 | { 117 | stringstream ss {}; 118 | ss << hex << boolalpha << "IPv" << +ver << ", " 119 | << "len=" << +len << ", " 120 | << "protocol=" << +proto << ", " << ( ttl >= 10 ? "" : "ttl=" + ::to_string( ttl ) + ", " ) 121 | << "src=" << inet_ntoa( { htobe32( src ) } ) << ", " 122 | << "dst=" << inet_ntoa( { htobe32( dst ) } ); 123 | return ss.str(); 124 | } 125 | -------------------------------------------------------------------------------- /util/ipv4_header.hh: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "parser.hh" 4 | 5 | #include 6 | #include 7 | #include 8 | 9 | // IPv4 Internet datagram header (note: IP options are not supported) 10 | struct IPv4Header 11 | { 12 | static constexpr size_t LENGTH = 20; // IPv4 header length, not including options 13 | static constexpr uint8_t DEFAULT_TTL = 128; // A reasonable default TTL value 14 | static constexpr uint8_t PROTO_TCP = 6; // Protocol number for TCP 15 | 16 | static constexpr uint64_t serialized_length() { return LENGTH; } 17 | 18 | /* 19 | * 0 1 2 3 20 | * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 21 | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 22 | * |Version| IHL |Type of Service| Total Length | 23 | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 24 | * | Identification |Flags| Fragment Offset | 25 | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 26 | * | Time to Live | Protocol | Header Checksum | 27 | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 28 | * | Source Address | 29 | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 30 | * | Destination Address | 31 | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 32 | * | Options | Padding | 33 | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 34 | */ 35 | 36 | // IPv4 Header fields 37 | uint8_t ver = 4; // IP version 38 | uint8_t hlen = LENGTH / 4; // header length (multiples of 32 bits) 39 | uint8_t tos = 0; // type of service 40 | uint16_t len = 0; // total length of packet 41 | uint16_t id = 0; // identification number 42 | bool df = true; // don't fragment flag 43 | bool mf = false; // more fragments flag 44 | uint16_t offset = 0; // fragment offset field 45 | uint8_t ttl = DEFAULT_TTL; // time to live field 46 | uint8_t proto = PROTO_TCP; // protocol field 47 | uint16_t cksum = 0; // checksum field 48 | uint32_t src = 0; // src address 49 | uint32_t dst = 0; // dst address 50 | 51 | // Length of the payload 52 | uint16_t payload_length() const; 53 | 54 | // Pseudo-header's contribution to the TCP checksum 55 | uint32_t pseudo_checksum() const; 56 | 57 | // Set checksum to correct value 58 | void compute_checksum(); 59 | 60 | // Return a string containing a header in human-readable format 61 | std::string to_string() const; 62 | 63 | void parse( Parser& parser ); 64 | void serialize( Serializer& serializer ) const; 65 | }; 66 | -------------------------------------------------------------------------------- /util/parser.hh: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "buffer.hh" 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | 17 | class Serializer; 18 | 19 | class Parser 20 | { 21 | class BufferList 22 | { 23 | uint64_t size_ {}; 24 | std::deque buffer_ {}; 25 | uint64_t skip_ {}; 26 | 27 | public: 28 | // NOLINTNEXTLINE(*-explicit-*) 29 | BufferList( const std::vector& buffers ) 30 | { 31 | for ( const auto& x : buffers ) { 32 | append( x ); 33 | } 34 | } 35 | 36 | uint64_t size() const { return size_; } 37 | uint64_t serialized_length() const { return size(); } 38 | bool empty() const { return size_ == 0; } 39 | 40 | std::string_view peek() const 41 | { 42 | if ( buffer_.empty() ) { 43 | throw std::runtime_error( "peek on empty BufferList" ); 44 | } 45 | return std::string_view { buffer_.front() }.substr( skip_ ); 46 | } 47 | 48 | void remove_prefix( uint64_t len ) 49 | { 50 | while ( len and not buffer_.empty() ) { 51 | const uint64_t to_pop_now = std::min( len, peek().size() ); 52 | skip_ += to_pop_now; 53 | len -= to_pop_now; 54 | size_ -= to_pop_now; 55 | if ( skip_ == buffer_.front().size() ) { 56 | buffer_.pop_front(); 57 | skip_ = 0; 58 | } 59 | } 60 | } 61 | 62 | void dump_all( std::vector& out ) 63 | { 64 | out.clear(); 65 | if ( empty() ) { 66 | return; 67 | } 68 | std::string first_str = std::move( buffer_.front() ); 69 | if ( skip_ ) { 70 | first_str = first_str.substr( skip_ ); 71 | } 72 | out.emplace_back( std::move( first_str ) ); 73 | buffer_.pop_front(); 74 | for ( auto&& x : buffer_ ) { 75 | out.emplace_back( std::move( x ) ); 76 | } 77 | } 78 | 79 | void dump_all( Buffer& out ) 80 | { 81 | std::vector concat; 82 | dump_all( concat ); 83 | if ( concat.size() == 1 ) { 84 | out = concat.front(); 85 | return; 86 | } 87 | 88 | out.release().clear(); 89 | for ( const auto& s : concat ) { 90 | out.release().append( s ); 91 | } 92 | } 93 | 94 | void append( Buffer str ) 95 | { 96 | size_ += str.size(); 97 | buffer_.push_back( std::move( str ) ); 98 | } 99 | }; 100 | 101 | BufferList input_; 102 | bool error_ {}; 103 | 104 | void check_size( const size_t size ) 105 | { 106 | if ( size > input_.size() ) { 107 | error_ = true; 108 | } 109 | } 110 | 111 | public: 112 | explicit Parser( const std::vector& input ) : input_( input ) {} 113 | 114 | const BufferList& input() const { return input_; } 115 | 116 | bool has_error() const { return error_; } 117 | void set_error() { error_ = true; } 118 | void remove_prefix( size_t n ) { input_.remove_prefix( n ); } 119 | 120 | template 121 | void integer( T& out ) 122 | { 123 | check_size( sizeof( T ) ); 124 | if ( has_error() ) { 125 | return; 126 | } 127 | 128 | if constexpr ( sizeof( T ) == 1 ) { 129 | out = static_cast( input_.peek().front() ); 130 | input_.remove_prefix( 1 ); 131 | return; 132 | } else { 133 | out = static_cast( 0 ); 134 | for ( size_t i = 0; i < sizeof( T ); i++ ) { 135 | out <<= 8; 136 | out |= static_cast( input_.peek().front() ); 137 | input_.remove_prefix( 1 ); 138 | } 139 | } 140 | } 141 | 142 | void string( std::span out ) 143 | { 144 | check_size( out.size() ); 145 | if ( has_error() ) { 146 | return; 147 | } 148 | 149 | auto next = out.begin(); 150 | while ( next != out.end() ) { 151 | const auto view = input_.peek().substr( 0, out.end() - next ); 152 | next = std::copy( view.begin(), view.end(), next ); 153 | input_.remove_prefix( view.size() ); 154 | } 155 | } 156 | 157 | void all_remaining( std::vector& out ) { input_.dump_all( out ); } 158 | void all_remaining( Buffer& out ) { input_.dump_all( out ); } 159 | }; 160 | 161 | class Serializer 162 | { 163 | std::vector output_ {}; 164 | std::string buffer_ {}; 165 | 166 | public: 167 | Serializer() = default; 168 | explicit Serializer( std::string&& buffer ) : buffer_( std::move( buffer ) ) {} 169 | 170 | template 171 | void integer( const T& val ) 172 | { 173 | constexpr uint64_t len = sizeof( T ); 174 | 175 | for ( uint64_t i = 0; i < len; ++i ) { 176 | const uint8_t byte_val = val >> ( ( len - i - 1 ) * 8 ); 177 | buffer_.push_back( byte_val ); 178 | } 179 | } 180 | 181 | void buffer( const Buffer& buf ) 182 | { 183 | flush(); 184 | output_.push_back( buf ); 185 | } 186 | 187 | void buffer( const std::vector& bufs ) 188 | { 189 | for ( const auto& b : bufs ) { 190 | buffer( b ); 191 | } 192 | } 193 | 194 | void flush() 195 | { 196 | output_.emplace_back( std::move( buffer_ ) ); 197 | buffer_.clear(); 198 | } 199 | 200 | std::vector output() 201 | { 202 | flush(); 203 | return output_; 204 | } 205 | }; 206 | 207 | // Helper to serialize any object (without constructing a Serializer of the caller's own) 208 | template 209 | std::vector serialize( const T& obj ) 210 | { 211 | Serializer s; 212 | obj.serialize( s ); 213 | return s.output(); 214 | } 215 | 216 | // Helper to parse any object (without constructing a Parser of the caller's own). Returns true if successful. 217 | template 218 | bool parse( T& obj, const std::vector& buffers ) 219 | { 220 | Parser p { buffers }; 221 | obj.parse( p ); 222 | return not p.has_error(); 223 | } 224 | -------------------------------------------------------------------------------- /util/random.cc: -------------------------------------------------------------------------------- 1 | #include "random.hh" 2 | 3 | #include 4 | #include 5 | 6 | using namespace std; 7 | 8 | default_random_engine get_random_engine() 9 | { 10 | auto rd = random_device(); 11 | array seed_data {}; 12 | generate( seed_data.begin(), seed_data.end(), [&] { return rd(); } ); 13 | seed_seq seed( seed_data.begin(), seed_data.end() ); 14 | return default_random_engine( seed ); 15 | } 16 | -------------------------------------------------------------------------------- /util/random.hh: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | std::default_random_engine get_random_engine(); 6 | -------------------------------------------------------------------------------- /util/socket.hh: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "address.hh" 4 | #include "file_descriptor.hh" 5 | 6 | #include 7 | #include 8 | #include 9 | 10 | //! \brief Base class for network sockets (TCP, UDP, etc.) 11 | //! \details Socket is generally used via a subclass. See TCPSocket and UDPSocket for usage examples. 12 | class Socket : public FileDescriptor 13 | { 14 | private: 15 | //! Get the local or peer address the socket is connected to 16 | Address get_address( const std::string& name_of_function, 17 | const std::function& function ) const; 18 | 19 | protected: 20 | //! Construct via [socket(2)](\ref man2::socket) 21 | Socket( int domain, int type, int protocol = 0 ); 22 | 23 | //! Construct from a file descriptor. 24 | Socket( FileDescriptor&& fd, int domain, int type, int protocol = 0 ); 25 | 26 | //! Wrapper around [getsockopt(2)](\ref man2::getsockopt) 27 | template 28 | socklen_t getsockopt( int level, int option, option_type& option_value ) const; 29 | 30 | //! Wrappers around [setsockopt(2)](\ref man2::setsockopt) 31 | template 32 | void setsockopt( int level, int option, const option_type& option_value ); 33 | 34 | void setsockopt( int level, int option, std::string_view option_val ); 35 | 36 | public: 37 | //! Bind a socket to a specified address with [bind(2)](\ref man2::bind), usually for listen/accept 38 | void bind( const Address& address ); 39 | 40 | //! Bind a socket to a specified device 41 | void bind_to_device( std::string_view device_name ); 42 | 43 | //! Connect a socket to a specified peer address with [connect(2)](\ref man2::connect) 44 | void connect( const Address& address ); 45 | 46 | //! Shut down a socket via [shutdown(2)](\ref man2::shutdown) 47 | void shutdown( int how ); 48 | 49 | //! Get local address of socket with [getsockname(2)](\ref man2::getsockname) 50 | Address local_address() const; 51 | //! Get peer address of socket with [getpeername(2)](\ref man2::getpeername) 52 | Address peer_address() const; 53 | 54 | //! Allow local address to be reused sooner via [SO_REUSEADDR](\ref man7::socket) 55 | void set_reuseaddr(); 56 | 57 | //! Check for errors (will be seen on non-blocking sockets) 58 | void throw_if_error() const; 59 | }; 60 | 61 | class DatagramSocket : public Socket 62 | { 63 | using Socket::Socket; 64 | 65 | public: 66 | //! Receive a datagram and the Address of its sender 67 | void recv( Address& source_address, std::string& payload ); 68 | 69 | //! Send a datagram to specified Address 70 | void sendto( const Address& destination, std::string_view payload ); 71 | 72 | //! Send datagram to the socket's connected address (must call connect() first) 73 | void send( std::string_view payload ); 74 | }; 75 | 76 | //! A wrapper around [UDP sockets](\ref man7::udp) 77 | class UDPSocket : public DatagramSocket 78 | { 79 | //! \param[in] fd is the FileDescriptor from which to construct 80 | explicit UDPSocket( FileDescriptor&& fd ) : DatagramSocket( std::move( fd ), AF_INET, SOCK_DGRAM ) {} 81 | 82 | public: 83 | //! Default: construct an unbound, unconnected UDP socket 84 | UDPSocket() : DatagramSocket( AF_INET, SOCK_DGRAM ) {} 85 | }; 86 | 87 | //! A wrapper around [TCP sockets](\ref man7::tcp) 88 | class TCPSocket : public Socket 89 | { 90 | private: 91 | //! \brief Construct from FileDescriptor (used by accept()) 92 | //! \param[in] fd is the FileDescriptor from which to construct 93 | explicit TCPSocket( FileDescriptor&& fd ) : Socket( std::move( fd ), AF_INET, SOCK_STREAM ) {} 94 | 95 | public: 96 | //! Default: construct an unbound, unconnected TCP socket 97 | TCPSocket() : Socket( AF_INET, SOCK_STREAM ) {} 98 | 99 | //! Mark a socket as listening for incoming connections 100 | void listen( int backlog = 16 ); 101 | 102 | //! Accept a new incoming connection 103 | TCPSocket accept(); 104 | }; 105 | 106 | //! A wrapper around [packet sockets](\ref man7:packet) 107 | class PacketSocket : public DatagramSocket 108 | { 109 | public: 110 | PacketSocket( const int type, const int protocol ) : DatagramSocket( AF_PACKET, type, protocol ) {} 111 | 112 | void set_promiscuous(); 113 | }; 114 | -------------------------------------------------------------------------------- /util/tcp_config.hh: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "wrapping_integers.hh" 4 | 5 | #include 6 | #include 7 | #include 8 | 9 | //! Config for TCP sender and receiver 10 | class TCPConfig 11 | { 12 | public: 13 | static constexpr size_t DEFAULT_CAPACITY = 64000; //!< Default capacity 14 | static constexpr size_t MAX_PAYLOAD_SIZE = 1000; //!< Conservative max payload size for real Internet 15 | static constexpr uint16_t TIMEOUT_DFLT = 1000; //!< Default re-transmit timeout is 1 second 16 | static constexpr unsigned MAX_RETX_ATTEMPTS = 8; //!< Maximum re-transmit attempts before giving up 17 | 18 | uint16_t rt_timeout = TIMEOUT_DFLT; //!< Initial value of the retransmission timeout, in milliseconds 19 | size_t recv_capacity = DEFAULT_CAPACITY; //!< Receive capacity, in bytes 20 | size_t send_capacity = DEFAULT_CAPACITY; //!< Sender capacity, in bytes 21 | std::optional fixed_isn {}; 22 | }; 23 | -------------------------------------------------------------------------------- /util/tcp_receiver_message.hh: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "wrapping_integers.hh" 4 | 5 | #include 6 | 7 | /* 8 | * The TCPReceiverMessage structure contains the information sent from a TCP receiver to its sender. 9 | * 10 | * It contains two fields: 11 | * 12 | * 1) The acknowledgment number (ackno): the *next* sequence number needed by the TCP Receiver. 13 | * This is an optional field that is empty if the TCPReceiver hasn't yet received the Initial Sequence Number. 14 | * 15 | * 2) The window size. This is the number of sequence numbers that the TCP receiver is interested 16 | * to receive, starting from the ackno if present. The maximum value is 65,535 (UINT16_MAX from 17 | * the header). 18 | */ 19 | 20 | struct TCPReceiverMessage 21 | { 22 | std::optional ackno {}; 23 | uint16_t window_size {}; 24 | }; 25 | -------------------------------------------------------------------------------- /util/tcp_sender_message.hh: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "buffer.hh" 4 | #include "wrapping_integers.hh" 5 | 6 | #include 7 | 8 | /* 9 | * The TCPSenderMessage structure contains the information sent from a TCP sender to its receiver. 10 | * 11 | * It contains four fields: 12 | * 13 | * 1) The sequence number (seqno) of the beginning of the segment. If the SYN flag is set, this is the 14 | * sequence number of the SYN flag. Otherwise, it's the sequence number of the beginning of the payload. 15 | * 16 | * 2) The SYN flag. If set, it means this segment is the beginning of the byte stream, and that 17 | * the seqno field contains the Initial Sequence Number (ISN) -- the zero point. 18 | * 19 | * 3) The payload: a substring (possibly empty) of the byte stream. 20 | * 21 | * 4) The FIN flag. If set, it means the payload represents the ending of the byte stream. 22 | */ 23 | 24 | struct TCPSenderMessage 25 | { 26 | Wrap32 seqno { 0 }; 27 | bool SYN { false }; 28 | Buffer payload {}; 29 | bool FIN { false }; 30 | 31 | // How many sequence numbers does this segment use? 32 | size_t sequence_length() const { return SYN + payload.size() + FIN; } 33 | }; 34 | -------------------------------------------------------------------------------- /writeups/check0.md: -------------------------------------------------------------------------------- 1 | Checkpoint 0 Writeup 2 | ==================== 3 | 4 | My name: [your name here] 5 | 6 | My SUNet ID: [your sunetid here] 7 | 8 | I collaborated with: [list sunetids here] 9 | 10 | I would like to credit/thank these classmates for their help: [list sunetids here] 11 | 12 | This lab took me about [n] hours to do. I [did/did not] attend the lab session. 13 | 14 | My secret code from section 2.1 was: [code here] 15 | 16 | - Optional: I had unexpected difficulty with: [describe] 17 | 18 | - Optional: I think you could make this lab better by: [describe] 19 | 20 | - Optional: I was surprised by: [describe] 21 | 22 | - Optional: I'm not sure about: [describe] 23 | -------------------------------------------------------------------------------- /writeups/check1.md: -------------------------------------------------------------------------------- 1 | Checkpoint 1 Writeup 2 | ==================== 3 | 4 | My name: [your name here] 5 | 6 | My SUNet ID: [your sunetid here] 7 | 8 | I collaborated with: [list sunetids here] 9 | 10 | I would like to thank/reward these classmates for their help: [list sunetids here] 11 | 12 | This lab took me about [n] hours to do. I [did/did not] attend the lab session. 13 | 14 | Program Structure and Design of the Reassembler: 15 | [] 16 | 17 | Implementation Challenges: 18 | [] 19 | 20 | Remaining Bugs: 21 | [] 22 | 23 | - Optional: I had unexpected difficulty with: [describe] 24 | 25 | - Optional: I think you could make this lab better by: [describe] 26 | 27 | - Optional: I was surprised by: [describe] 28 | 29 | - Optional: I'm not sure about: [describe] 30 | -------------------------------------------------------------------------------- /writeups/check2.md: -------------------------------------------------------------------------------- 1 | Checkpoint 2 Writeup 2 | ==================== 3 | 4 | My name: [your name here] 5 | 6 | My SUNet ID: [your sunetid here] 7 | 8 | I collaborated with: [list sunetids here] 9 | 10 | I would like to thank/reward these classmates for their help: [list sunetids here] 11 | 12 | This lab took me about [n] hours to do. I [did/did not] attend the lab session. 13 | 14 | Program Structure and Design of the TCPReceiver and wrap/unwrap routines: 15 | [] 16 | 17 | Implementation Challenges: 18 | [] 19 | 20 | Remaining Bugs: 21 | [] 22 | 23 | - Optional: I had unexpected difficulty with: [describe] 24 | 25 | - Optional: I think you could make this lab better by: [describe] 26 | 27 | - Optional: I was surprised by: [describe] 28 | 29 | - Optional: I'm not sure about: [describe] 30 | 31 | - Optional: I made an extra test I think will be helpful in catching bugs: [describe where to find] 32 | -------------------------------------------------------------------------------- /writeups/check3.md: -------------------------------------------------------------------------------- 1 | Checkpoint 3 Writeup 2 | ==================== 3 | 4 | My name: [your name here] 5 | 6 | My SUNet ID: [your sunetid here] 7 | 8 | I collaborated with: [list sunetids here] 9 | 10 | I would like to thank/reward these classmates for their help: [list sunetids here] 11 | 12 | This checkpoint took me about [n] hours to do. I [did/did not] attend the lab session. 13 | 14 | Program Structure and Design of the TCPSender: 15 | [] 16 | 17 | Implementation Challenges: 18 | [] 19 | 20 | Remaining Bugs: 21 | [] 22 | 23 | - Optional: I had unexpected difficulty with: [describe] 24 | 25 | - Optional: I think you could make this lab better by: [describe] 26 | 27 | - Optional: I was surprised by: [describe] 28 | 29 | - Optional: I'm not sure about: [describe] 30 | -------------------------------------------------------------------------------- /writeups/check4.md: -------------------------------------------------------------------------------- 1 | Checkpoint 4 Writeup 2 | ==================== 3 | 4 | My name: [your name here] 5 | 6 | My SUNet ID: [your sunetid here] 7 | 8 | I collaborated with: [list sunetids here] 9 | 10 | I would like to thank/reward these classmates for their help: [list sunetids here] 11 | 12 | This checkpoint took me about [n] hours to do. I [did/did not] attend the lab session. 13 | 14 | Program Structure and Design of the NetworkInterface: 15 | [] 16 | 17 | Implementation Challenges: 18 | [] 19 | 20 | Remaining Bugs: 21 | [] 22 | 23 | - Optional: I had unexpected difficulty with: [describe] 24 | 25 | - Optional: I think you could make this lab better by: [describe] 26 | 27 | - Optional: I was surprised by: [describe] 28 | 29 | - Optional: I'm not sure about: [describe] 30 | -------------------------------------------------------------------------------- /writeups/check5.md: -------------------------------------------------------------------------------- 1 | Checkpoint 5 Writeup 2 | ==================== 3 | 4 | My name: [your name here] 5 | 6 | My SUNet ID: [your sunetid here] 7 | 8 | I collaborated with: [list sunetids here] 9 | 10 | I would like to thank/reward these classmates for their help: [list sunetids here] 11 | 12 | This checkpoint took me about [n] hours to do. I [did/did not] attend the lab session. 13 | 14 | Program Structure and Design of the Router: 15 | [] 16 | 17 | Implementation Challenges: 18 | [] 19 | 20 | Remaining Bugs: 21 | [] 22 | 23 | - Optional: I had unexpected difficulty with: [describe] 24 | 25 | - Optional: I think you could make this lab better by: [describe] 26 | 27 | - Optional: I was surprised by: [describe] 28 | 29 | - Optional: I'm not sure about: [describe] 30 | --------------------------------------------------------------------------------