CS144 Lab checkpoint 0

This commit is contained in:
Keith Winstein 2020-09-15 14:54:11 -07:00
commit 898f0c05bd
64 changed files with 55670 additions and 0 deletions

26
.clang-format Normal file
View File

@ -0,0 +1,26 @@
---
Language: Cpp
BasedOnStyle: LLVM
AllowAllParametersOfDeclarationOnNextLine: false
AlwaysBreakTemplateDeclarations: true
BinPackArguments: false
BinPackParameters: false
BreakConstructorInitializers: BeforeComma
ColumnLimit: 120
CommentPragmas: '^(!|NOLINT)'
ConstructorInitializerAllOnOneLineOrOnePerLine: true
IncludeBlocks: Regroup
IncludeCategories:
- Regex: '^<.*'
Priority: 2
- Regex: '.*'
Priority: 1
IncludeIsMainRegex: '(_dt|_win)?$'
IndentCaseLabels: true
IndentWidth: 4
KeepEmptyLinesAtTheStartOfBlocks: false
PenaltyReturnTypeOnItsOwnLine: 200
SpacesBeforeTrailingComments: 2
TabWidth: 4
UseTab: Never
...

2
.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
/build
/.ccls-cache

27
CMakeLists.txt Normal file
View File

@ -0,0 +1,27 @@
cmake_minimum_required (VERSION 2.8.5)
cmake_policy (SET CMP0054 NEW)
project (Sponge)
include (etc/build_defs.cmake)
include (etc/build_type.cmake)
include (etc/cflags.cmake)
include (etc/doxygen.cmake)
include (etc/clang_format.cmake)
include (etc/clang_tidy.cmake)
include (etc/cppcheck.cmake)
include_directories ("${PROJECT_SOURCE_DIR}/libsponge/util")
include_directories ("${PROJECT_SOURCE_DIR}/libsponge/tcp_helpers")
include_directories ("${PROJECT_SOURCE_DIR}/libsponge")
add_subdirectory ("${PROJECT_SOURCE_DIR}/libsponge")
add_subdirectory ("${PROJECT_SOURCE_DIR}/apps")
add_subdirectory ("${PROJECT_SOURCE_DIR}/tests")
add_subdirectory ("${PROJECT_SOURCE_DIR}/doctests")
include (etc/tests.cmake)

73
README.md Normal file
View File

@ -0,0 +1,73 @@
For build prereqs, see [the CS144 VM setup instructions](https://web.stanford.edu/class/cs144/vm_howto).
## Sponge quickstart
To set up your build directory:
$ mkdir -p <path/to/sponge>/build
$ cd <path/to/sponge>/build
$ cmake ..
**Note:** all further commands listed below should be run from the `build` dir.
To build:
$ make
You can use the `-j` switch to build in parallel, e.g.,
$ make -j$(nproc)
To test (after building; make sure you've got the [build prereqs](https://web.stanford.edu/class/cs144/vm_howto) installed!)
$ make check_labN *(replacing N with a checkpoint number)*
The first time you run `make check_lab...`, it will run `sudo` to configure two
[TUN](https://www.kernel.org/doc/Documentation/networking/tuntap.txt) devices for use during
testing.
### build options
You can specify a different compiler when you run cmake:
$ CC=clang CXX=clang++ cmake ..
You can also specify `CLANG_TIDY=` or `CLANG_FORMAT=` (see "other useful targets", below).
Sponge's build system supports several different build targets. By default, cmake chooses the `Release`
target, which enables the usual optimizations. The `Debug` target enables debugging and reduces the
level of optimization. To choose the `Debug` target:
$ cmake .. -DCMAKE_BUILD_TYPE=Debug
The following targets are supported:
- `Release` - optimizations
- `Debug` - debug symbols and `-Og`
- `RelASan` - release build with [ASan](https://en.wikipedia.org/wiki/AddressSanitizer) and
[UBSan](https://developers.redhat.com/blog/2014/10/16/gcc-undefined-behavior-sanitizer-ubsan/)
- `RelTSan` - release build with
[ThreadSan](https://developer.mozilla.org/en-US/docs/Mozilla/Projects/Thread_Sanitizer)
- `DebugASan` - debug build with ASan and UBSan
- `DebugTSan` - debug build with ThreadSan
Of course, you can combine all of the above, e.g.,
$ CLANG_TIDY=clang-tidy-6.0 CXX=clang++-6.0 .. -DCMAKE_BUILD_TYPE=Debug
**Note:** if you want to change `CC`, `CXX`, `CLANG_TIDY`, or `CLANG_FORMAT`, you need to remove
`build/CMakeCache.txt` and re-run cmake. (This isn't necessary for `CMAKE_BUILD_TYPE`.)
### other useful targets
To generate documentation (you'll need `doxygen`; output will be in `build/doc/`):
$ make doc
To format (you'll need `clang-format`):
$ make format
To see all available targets,
$ make help

1
apps/CMakeLists.txt Normal file
View File

@ -0,0 +1 @@
add_sponge_exec (webget)

51
apps/webget.cc Normal file
View File

@ -0,0 +1,51 @@
#include "socket.hh"
#include "util.hh"
#include <cstdlib>
#include <iostream>
using namespace std;
void get_URL(const string &host, const string &path) {
// Your code here.
// You will need to connect to the "http" service on
// the computer whose name is in the "host" string,
// then request the URL path given in the "path" string.
// Then you'll need to print out everything the server sends back,
// (not just one call to read() -- everything) until you reach
// the "eof" (end of file).
cerr << "Function called: get_URL(" << host << ", " << path << ").\n";
cerr << "Warning: get_URL() has not been implemented yet.\n";
}
int main(int argc, char *argv[]) {
try {
if (argc <= 0) {
abort(); // For sticklers: don't try to access argv[0] if argc <= 0.
}
// The program takes two command-line arguments: the hostname and "path" part of the URL.
// Print the usage message unless there are these two arguments (plus the program name
// itself, so arg count = 3 in total).
if (argc != 3) {
cerr << "Usage: " << argv[0] << " HOST PATH\n";
cerr << "\tExample: " << argv[0] << " stanford.edu /class/cs144\n";
return EXIT_FAILURE;
}
// Get the command-line arguments.
const string host = argv[1];
const string path = argv[2];
// Call the student-written function.
get_URL(host, path);
} catch (const exception &e) {
cerr << e.what() << "\n";
return EXIT_FAILURE;
}
return EXIT_SUCCESS;
}

1
compile_commands.json Symbolic link
View File

@ -0,0 +1 @@
build/compile_commands.json

3
doctests/CMakeLists.txt Normal file
View File

@ -0,0 +1,3 @@
add_sponge_exec (address_dt)
add_sponge_exec (parser_dt)
add_sponge_exec (socket_dt)

21
doctests/address_dt.cc Normal file
View File

@ -0,0 +1,21 @@
#include "address.hh"
#include <cstdlib>
#include <iostream>
#include <stdexcept>
int main() {
try {
#include "address_example_1.cc"
#include "address_example_2.cc"
#include "address_example_3.cc"
if ((google_webserver.port() != 443) || (a_dns_server_numeric != 0x12'47'00'97)) {
throw std::runtime_error("unexpected value");
}
} catch (const std::exception &e) {
std::cerr << "This test requires Internet access and working DNS.\n";
std::cerr << "Error: " << e.what() << "\n";
return EXIT_FAILURE;
}
return EXIT_SUCCESS;
}

View File

@ -0,0 +1 @@
const Address google_webserver("www.google.com", "https");

View File

@ -0,0 +1 @@
const Address a_dns_server("18.71.0.151", 53);

View File

@ -0,0 +1 @@
const uint32_t a_dns_server_numeric = a_dns_server.ipv4_numeric();

15
doctests/parser_dt.cc Normal file
View File

@ -0,0 +1,15 @@
#include "parser.hh"
#include <cstdint>
#include <cstdlib>
#include <stdexcept>
#include <vector>
int main() {
try {
#include "parser_example.cc"
} catch (...) {
return EXIT_FAILURE;
}
return EXIT_SUCCESS;
}

View File

@ -0,0 +1,32 @@
const uint32_t val1 = 0xdeadbeef;
const uint16_t val2 = 0xc0c0;
const uint8_t val3 = 0xff;
const uint32_t val4 = 0x0c05fefe;
// first, let's serialize it
std::string buffer;
buffer.push_back(0x32); // manually added to beginning of string
{
NetUnparser p;
p.u32(buffer, val1);
p.u16(buffer, val2);
p.u8(buffer, val3);
p.u32(buffer, val4);
} // p goes out of scope, data is in buffer
// now let's deserialize it
uint8_t out0, out3;
uint32_t out1, out4;
uint16_t out2;
{
NetParser p{std::string(buffer)}; // NOTE: starting at offset 0
out0 = p.u8(); // buffer[0], which we manually set to 0x32 above
out1 = p.u32(); // parse out val1
out2 = p.u16(); // val2
out3 = p.u8(); // val3
out4 = p.u32(); // val4
} // p goes out of scope
if (out0 != 0x32 || out1 != val1 || out2 != val2 || out3 != val3 || out4 != val4) {
throw std::runtime_error("bad parse");
}

26
doctests/socket_dt.cc Normal file
View File

@ -0,0 +1,26 @@
#include "socket.hh"
#include "address.hh"
#include "util.hh"
#include <array>
#include <cstdlib>
#include <random>
#include <stdexcept>
#include <sys/socket.h>
#include <vector>
int main() {
try {
{
#include "socket_example_1.cc"
} {
#include "socket_example_2.cc"
} {
#include "socket_example_3.cc"
}
} catch (...) {
return EXIT_FAILURE;
}
return EXIT_SUCCESS;
}

View File

@ -0,0 +1,20 @@
const uint16_t portnum = ((std::random_device()()) % 50000) + 1025;
// create a UDP socket and bind it to a local address
UDPSocket sock1;
sock1.bind(Address("127.0.0.1", portnum));
// create another UDP socket and send a datagram to the first socket without connecting
UDPSocket sock2;
sock2.sendto(Address("127.0.0.1", portnum), "hi there");
// receive sent datagram, connect the socket to the peer's address, and send a response
auto recvd = sock1.recv();
sock1.connect(recvd.source_address);
sock1.send("hi yourself");
auto recvd2 = sock2.recv();
if (recvd.payload != "hi there" || recvd2.payload != "hi yourself") {
throw std::runtime_error("wrong data received");
}

View File

@ -0,0 +1,26 @@
const uint16_t portnum = ((std::random_device()()) % 50000) + 1025;
// create a TCP socket, bind it to a local address, and listen
TCPSocket sock1;
sock1.bind(Address("127.0.0.1", portnum));
sock1.listen(1);
// create another socket and connect to the first one
TCPSocket sock2;
sock2.connect(Address("127.0.0.1", portnum));
// accept the connection
auto sock3 = sock1.accept();
sock3.write("hi there");
auto recvd = sock2.read();
sock2.write("hi yourself");
auto recvd2 = sock3.read();
sock1.close(); // don't need to accept any more connections
sock2.close(); // you can call close(2) on a socket
sock3.shutdown(SHUT_RDWR); // you can also shutdown(2) a socket
if (recvd != "hi there" || recvd2 != "hi yourself") {
throw std::runtime_error("wrong data received");
}

View File

@ -0,0 +1,14 @@
// create a pair of stream sockets
std::array<int, 2> fds{};
SystemCall("socketpair", ::socketpair(AF_UNIX, SOCK_STREAM, 0, fds.data()));
LocalStreamSocket pipe1{FileDescriptor(fds[0])}, pipe2{FileDescriptor(fds[1])};
pipe1.write("hi there");
auto recvd = pipe2.read();
pipe2.write("hi yourself");
auto recvd2 = pipe1.read();
if (recvd != "hi there" || recvd2 != "hi yourself") {
throw std::runtime_error("wrong data received");
}

48
etc/Doxyfile.in Normal file
View File

@ -0,0 +1,48 @@
# Doxyfile 1.8.14
DOXYFILE_ENCODING = UTF-8
PROJECT_NAME = "Sponge"
PROJECT_BRIEF = "CS144's user-space TCP library"
PROJECT_LOGO = "@PROJECT_SOURCE_DIR@/etc/sponge_small.png"
INPUT = @PROJECT_SOURCE_DIR@
RECURSIVE = YES
EXCLUDE = @PROJECT_SOURCE_DIR@/etc @PROJECT_SOURCE_DIR@/build @PROJECT_SOURCE_DIR@/tests @PROJECT_SOURCE_DIR@/writeups
OUTPUT_DIRECTORY = "@PROJECT_BINARY_DIR@/doc"
CASE_SENSE_NAMES = NO
SORT_BRIEF_DOCS = YES
SORT_MEMBERS_CTORS_1ST = YES
SHOW_NAMESPACES = NO
USE_MDFILE_AS_MAINPAGE = @PROJECT_SOURCE_DIR@/README.md
SOURCE_BROWSER = YES
EXT_LINKS_IN_WINDOW = YES
INCLUDE_PATH = @PROJECT_SOURCE_DIR@/libsponge
TAGFILES = "@PROJECT_SOURCE_DIR@/etc/cppreference-doxygen-web.tag.xml=https://en.cppreference.com/w/"
TAGFILES += "@PROJECT_SOURCE_DIR@/etc/linux-man-doxygen-web.tag.xml=https://man7.org/linux/man-pages/"
TAGFILES += "@PROJECT_SOURCE_DIR@/etc/rfc-doxygen-web.tag.xml=https://tools.ietf.org/html/"
HIDE_UNDOC_RELATIONS = NO
INLINE_GROUPED_CLASSES = YES
INLINE_SIMPLE_STRUCTS = YES
HTML_COLORSTYLE_HUE = 204
HTML_COLORSTYLE_SAT = 120
HTML_COLORSTYLE_GAMMA = 60
HTML_EXTRA_STYLESHEET = "@PROJECT_SOURCE_DIR@/etc/sponge_doxygen.css"
GENERATE_LATEX = NO
EXAMPLE_PATH = "@PROJECT_SOURCE_DIR@/doctests"
# cmake detects whether dot is available
HAVE_DOT = @DOXYGEN_DOT_FOUND@
CLASS_GRAPH = YES
TEMPLATE_RELATIONS = YES
DOT_IMAGE_FORMAT = png
INTERACTIVE_SVG = NO
COLLABORATION_GRAPH = NO
# ??? temporary
EXTRACT_ALL = YES
EXTRACT_PRIVATE = YES
EXTRACT_STATIC = YES
EXTRACT_ANON_NSPACES = YES
# do u liek eclips
GENERATE_ECLIPSEHELP = NO
ECLIPSE_DOC_ID = edu.stanford.cs144.sponge

6
etc/build_defs.cmake Normal file
View File

@ -0,0 +1,6 @@
find_library (LIBPCAP pcap)
find_library (LIBPTHREAD pthread)
macro (add_sponge_exec exec_name)
add_executable ("${exec_name}" "${exec_name}.cc")
target_link_libraries ("${exec_name}" ${ARGN} sponge ${LIBPTHREAD})
endmacro (add_sponge_exec)

16
etc/build_type.cmake Normal file
View File

@ -0,0 +1,16 @@
set (default_build_type "Release")
if (NOT (CMAKE_BUILD_TYPE_SHADOW STREQUAL CMAKE_BUILD_TYPE))
if (NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES)
message (STATUS "Setting build type to '${default_build_type}'")
set (CMAKE_BUILD_TYPE "${default_build_type}" CACHE STRING "Choose the type of build." FORCE)
else ()
message (STATUS "Building in ${CMAKE_BUILD_TYPE} mode as requested.")
endif ()
set (CMAKE_BUILD_TYPE_SHADOW ${CMAKE_BUILD_TYPE} CACHE STRING "used to detect changes in build type" FORCE)
endif ()
message (STATUS " NOTE: You can choose a build type by calling cmake with one of:")
message (STATUS " -DCMAKE_BUILD_TYPE=Release -- full optimizations")
message (STATUS " -DCMAKE_BUILD_TYPE=Debug -- better debugging experience in gdb")
message (STATUS " -DCMAKE_BUILD_TYPE=RelASan -- full optimizations plus address and undefined-behavior sanitizers")
message (STATUS " -DCMAKE_BUILD_TYPE=DebugASan -- debug plus sanitizers")

20
etc/cflags.cmake Normal file
View File

@ -0,0 +1,20 @@
set (CMAKE_CXX_STANDARD 17)
set (CMAKE_EXPORT_COMPILE_COMMANDS ON)
set (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++17 -g -pedantic -pedantic-errors -Werror -Wall -Wextra -Wshadow -Wpointer-arith -Wcast-qual -Wformat=2 -Weffc++ -Wold-style-cast")
# check for supported compiler versions
set (IS_GNU_COMPILER ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "GNU"))
set (IS_CLANG_COMPILER ("${CMAKE_CXX_COMPILER_ID}" MATCHES "[Cc][Ll][Aa][Nn][Gg]"))
set (CXX_VERSION_LT_6 ("${CMAKE_CXX_COMPILER_VERSION}" VERSION_LESS 6))
set (CXX_VERSION_LT_8 ("${CMAKE_CXX_COMPILER_VERSION}" VERSION_LESS 8))
if ((${IS_GNU_COMPILER} AND ${CXX_VERSION_LT_8}) OR (${IS_CLANG_COMPILER} AND ${CXX_VERSION_LT_6}))
message (FATAL_ERROR "You must compile this project with g++ >= 8 or clang >= 6.")
endif ()
if (${IS_CLANG_COMPILER})
set (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wloop-analysis")
endif ()
# add some flags for the Release, Debug, and DebugSan modes
set (CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -ggdb3 -Og")
set (CMAKE_CXX_FLAGS_DEBUGASAN "${CMAKE_CXX_FLAGS_DEBUG} -fsanitize=undefined -fsanitize=address")
set (CMAKE_CXX_FLAGS_RELASAN "${CMAKE_CXX_FLAGS_RELEASE} -fsanitize=undefined -fsanitize=address")

23
etc/clang_format.cmake Normal file
View File

@ -0,0 +1,23 @@
if (NOT CLANG_FORMAT)
if (DEFINED ENV{CLANG_FORMAT})
set (CLANG_FORMAT_TMP $ENV{CLANG_FORMAT})
else (NOT DEFINED ENV{CLANG_FORMAT})
set (CLANG_FORMAT_TMP clang-format-6.0)
endif (DEFINED ENV{CLANG_FORMAT})
# figure out which version of clang-format we're using
execute_process (COMMAND ${CLANG_FORMAT_TMP} --version RESULT_VARIABLE CLANG_FORMAT_RESULT OUTPUT_VARIABLE CLANG_FORMAT_VERSION)
if (${CLANG_FORMAT_RESULT} EQUAL 0)
string (REGEX MATCH "version [0-9]" CLANG_FORMAT_VERSION ${CLANG_FORMAT_VERSION})
message (STATUS "Found clang-format " ${CLANG_FORMAT_VERSION})
set(CLANG_FORMAT ${CLANG_FORMAT_TMP} CACHE STRING "clang-format executable name")
endif (${CLANG_FORMAT_RESULT} EQUAL 0)
endif (NOT CLANG_FORMAT)
if (DEFINED CLANG_FORMAT)
file (GLOB_RECURSE ALL_CC_FILES *.cc)
file (GLOB_RECURSE ALL_HH_FILES *.hh)
add_custom_target (format ${CLANG_FORMAT} -i ${ALL_CC_FILES} ${ALL_HH_FILES} COMMENT "Formatted all source files.")
else (NOT DEFINED CLANG_FORMAT)
add_custom_target (format echo "Could not find clang-format. Please install and re-run cmake")
endif (DEFINED CLANG_FORMAT)

33
etc/clang_tidy.cmake Normal file
View File

@ -0,0 +1,33 @@
if (NOT CLANG_TIDY)
if (DEFINED ENV{CLANG_TIDY})
set (CLANG_TIDY_TMP $ENV{CLANG_TIDY})
else (NOT DEFINED ENV{CLANG_TIDY})
set (CLANG_TIDY_TMP clang-tidy)
endif (DEFINED ENV{CLANG_TIDY})
# is clang-tidy available?
execute_process (COMMAND ${CLANG_TIDY_TMP} --version RESULT_VARIABLE CLANG_TIDY_RESULT OUTPUT_VARIABLE CLANG_TIDY_VERSION)
if (${CLANG_TIDY_RESULT} EQUAL 0)
string (REGEX MATCH "version [0-9]" CLANG_TIDY_VERSION ${CLANG_TIDY_VERSION})
message (STATUS "Found clang-tidy " ${CLANG_TIDY_VERSION})
set (CLANG_TIDY ${CLANG_TIDY_TMP} CACHE STRING "clang-tidy executable name")
endif (${CLANG_TIDY_RESULT} EQUAL 0)
endif (NOT CLANG_TIDY)
if (DEFINED CLANG_TIDY)
file (GLOB_RECURSE ALL_CC_FILES *.cc)
set (CLANG_TIDY_CHECKS "'*,-fuchsia-*,-hicpp-signed-bitwise,-google-build-using-namespace,-android*,-cppcoreguidelines-pro-bounds-pointer-arithmetic,-google-runtime-references,-readability-avoid-const-params-in-decls,-llvm-header-guard'")
foreach (tidy_target ${ALL_CC_FILES})
get_filename_component (basename ${tidy_target} NAME)
get_filename_component (dirname ${tidy_target} DIRECTORY)
get_filename_component (basedir ${dirname} NAME)
set (tidy_target_name "${basedir}__${basename}")
set (tidy_command ${CLANG_TIDY} -checks=${CLANG_TIDY_CHECKS} -header-filter=.* -p=${PROJECT_BINARY_DIR} ${tidy_target})
add_custom_target (tidy_quiet_${tidy_target_name} ${tidy_command} 2>/dev/null)
add_custom_target (tidy_${tidy_target_name} ${tidy_command})
list (APPEND ALL_TIDY_TARGETS tidy_quiet_${tidy_target_name})
list (APPEND ALL_TIDY_VERBOSE_TARGETS tidy_${tidy_target_name})
endforeach (tidy_target)
add_custom_target (tidy DEPENDS ${ALL_TIDY_TARGETS})
add_custom_target (tidy_verbose DEPENDS ${ALL_TIDY_VERBOSE_TARGETS})
endif (DEFINED CLANG_TIDY)

18
etc/cppcheck.cmake Normal file
View File

@ -0,0 +1,18 @@
if (NOT CPPCHECK)
if (DEFINED ENV{CPPCHECK})
set (CPPCHECK_TMP $ENV{CPPCHECK})
else (NOT DEFINED ENV{CPPCHECK})
set (CPPCHECK_TMP cppcheck)
endif ()
# is cppcheck available?
execute_process (COMMAND ${CPPCHECK_TMP} --version RESULT_VARIABLE CPPCHECK_RESULT OUTPUT_VARIABLE CPPCHECK_OUTPUT)
if (${CPPCHECK_RESULT} EQUAL 0)
message (STATUS "Found cppcheck")
set (CPPCHECK ${CPPCHECK_TMP} CACHE STRING "cppcheck executable name")
endif()
endif (NOT CPPCHECK)
if (DEFINED CPPCHECK)
add_custom_target (cppcheck ${CPPCHECK} --enable=all --project="${PROJECT_BINARY_DIR}/compile_commands.json")
endif (DEFINED CPPCHECK)

File diff suppressed because it is too large Load Diff

12
etc/doxygen.cmake Normal file
View File

@ -0,0 +1,12 @@
find_package (Doxygen)
if (DOXYGEN_FOUND)
if (Doxygen_dot_FOUND)
set (DOXYGEN_DOT_FOUND YES)
else (NOT Doxygen_dot_FOUND)
set (DOXYGEN_DOT_FOUND NO)
endif (Doxygen_dot_FOUND)
configure_file ("${PROJECT_SOURCE_DIR}/etc/Doxyfile.in" "${PROJECT_BINARY_DIR}/Doxyfile" @ONLY)
add_custom_target (doc "${DOXYGEN_EXECUTABLE}" "${PROJECT_BINARY_DIR}/Doxyfile"
WORKING_DIRECTORY "${PROJECT_BINARY_DIR}"
COMMENT "Generate docs using Doxygen" VERBATIM)
endif ()

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,33 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes" ?>
<tagfile>
<compound kind="namespace"><name>rfc</name><filename></filename>
<member kind="function">
<type></type>
<name>rfc791</name>
<anchorfile>rfc791</anchorfile>
<anchor></anchor>
<arglist></arglist>
</member>
<member kind="function">
<type></type>
<name>rfc793</name>
<anchorfile>rfc793</anchorfile>
<anchor></anchor>
<arglist></arglist>
</member>
<member kind="function">
<type></type>
<name>rfc826</name>
<anchorfile>rfc826</anchorfile>
<anchor></anchor>
<arglist></arglist>
</member>
<member kind="function">
<type></type>
<name>rfc6298</name>
<anchorfile>rfc6298</anchorfile>
<anchor></anchor>
<arglist></arglist>
</member>
</compound>
</tagfile>

11
etc/sponge_doxygen.css Normal file
View File

@ -0,0 +1,11 @@
html, body { background-color: #F8F8F8; }
div.textblock>p,div.memdoc>p,dl.section.note>dd { max-width: 750px; }
div.line,pre.fragment { line-height: 1.5; }
div.contents {
padding: 12px;
margin-top: auto;
margin-bottom: auto;
margin-left: 3%;
margin-right: 6%;
border-radius: 8px;
}

BIN
etc/sponge_small.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

238
etc/tests.cmake Normal file
View File

@ -0,0 +1,238 @@
enable_testing ()
set (LOSS_RATE 0.1)
add_test(NAME t_wrapping_ints_cmp COMMAND wrapping_integers_cmp)
add_test(NAME t_wrapping_ints_unwrap COMMAND wrapping_integers_unwrap)
add_test(NAME t_wrapping_ints_wrap COMMAND wrapping_integers_wrap)
add_test(NAME t_wrapping_ints_roundtrip COMMAND wrapping_integers_roundtrip)
add_test(NAME t_recv_connect COMMAND recv_connect)
add_test(NAME t_recv_transmit COMMAND recv_transmit)
add_test(NAME t_recv_window COMMAND recv_window)
add_test(NAME t_recv_reorder COMMAND recv_reorder)
add_test(NAME t_recv_close COMMAND recv_close)
add_test(NAME t_recv_special COMMAND recv_special)
add_test(NAME t_send_connect COMMAND send_connect)
add_test(NAME t_send_transmit COMMAND send_transmit)
add_test(NAME t_send_retx COMMAND send_retx)
add_test(NAME t_send_window COMMAND send_window)
add_test(NAME t_send_ack COMMAND send_ack)
add_test(NAME t_send_close COMMAND send_close)
add_test(NAME t_send_extra COMMAND send_extra)
add_test(NAME t_strm_reassem_single COMMAND fsm_stream_reassembler_single)
add_test(NAME t_strm_reassem_seq COMMAND fsm_stream_reassembler_seq)
add_test(NAME t_strm_reassem_dup COMMAND fsm_stream_reassembler_dup)
add_test(NAME t_strm_reassem_holes COMMAND fsm_stream_reassembler_holes)
add_test(NAME t_strm_reassem_many COMMAND fsm_stream_reassembler_many)
add_test(NAME t_strm_reassem_overlapping COMMAND fsm_stream_reassembler_overlapping)
add_test(NAME t_strm_reassem_win COMMAND fsm_stream_reassembler_win)
add_test(NAME t_strm_reassem_cap COMMAND fsm_stream_reassembler_cap)
add_test(NAME t_byte_stream_construction COMMAND byte_stream_construction)
add_test(NAME t_byte_stream_one_write COMMAND byte_stream_one_write)
add_test(NAME t_byte_stream_two_writes COMMAND byte_stream_two_writes)
add_test(NAME t_byte_stream_capacity COMMAND byte_stream_capacity)
add_test(NAME t_byte_stream_many_writes COMMAND byte_stream_many_writes)
add_test(NAME t_webget COMMAND "${PROJECT_SOURCE_DIR}/tests/webget_t.sh")
add_test(NAME arp_network_interface COMMAND net_interface)
add_test(NAME router_test COMMAND network_simulator)
add_test(NAME t_tcp_parser COMMAND tcp_parser "${PROJECT_SOURCE_DIR}/tests/ipv4_parser.data")
add_test(NAME t_ipv4_parser COMMAND ipv4_parser "${PROJECT_SOURCE_DIR}/tests/ipv4_parser.data")
add_test(NAME t_active_close COMMAND fsm_active_close)
add_test(NAME t_passive_close COMMAND fsm_passive_close)
add_test(NAME ec_ack_rst COMMAND fsm_ack_rst)
add_test(NAME t_ack_rst COMMAND fsm_ack_rst_relaxed)
add_test(NAME ec_ack_rst_win COMMAND fsm_ack_rst_win)
add_test(NAME t_ack_rst_win COMMAND fsm_ack_rst_win_relaxed)
add_test(NAME ec_connect COMMAND fsm_connect)
add_test(NAME t_connect COMMAND fsm_connect_relaxed)
add_test(NAME ec_listen COMMAND fsm_listen)
add_test(NAME t_listen COMMAND fsm_listen_relaxed)
add_test(NAME t_winsize COMMAND fsm_winsize)
add_test(NAME ec_retx COMMAND fsm_retx)
add_test(NAME t_retx COMMAND fsm_retx_relaxed)
add_test(NAME t_retx_win COMMAND fsm_retx_win)
add_test(NAME t_loopback COMMAND fsm_loopback)
add_test(NAME t_loopback_win COMMAND fsm_loopback_win)
add_test(NAME t_reorder COMMAND fsm_reorder)
add_test(NAME t_address_dt COMMAND address_dt)
add_test(NAME t_parser_dt COMMAND parser_dt)
add_test(NAME t_socket_dt COMMAND socket_dt)
add_test(NAME t_udp_client_send COMMAND "${PROJECT_SOURCE_DIR}/txrx.sh" -ucS)
add_test(NAME t_udp_server_send COMMAND "${PROJECT_SOURCE_DIR}/txrx.sh" -usS)
add_test(NAME t_udp_client_recv COMMAND "${PROJECT_SOURCE_DIR}/txrx.sh" -ucR)
add_test(NAME t_udp_server_recv COMMAND "${PROJECT_SOURCE_DIR}/txrx.sh" -usR)
add_test(NAME t_udp_client_dupl COMMAND "${PROJECT_SOURCE_DIR}/txrx.sh" -ucD)
add_test(NAME t_udp_server_dupl COMMAND "${PROJECT_SOURCE_DIR}/txrx.sh" -usD)
add_test(NAME t_ucS_1M_32k COMMAND "${PROJECT_SOURCE_DIR}/txrx.sh" -ucSd 1M -w 32K)
add_test(NAME t_ucS_128K_8K COMMAND "${PROJECT_SOURCE_DIR}/txrx.sh" -ucSd 128K -w 8K)
add_test(NAME t_ucS_16_1 COMMAND "${PROJECT_SOURCE_DIR}/txrx.sh" -ucSd 16 -w 1)
add_test(NAME t_ucS_32K_d COMMAND "${PROJECT_SOURCE_DIR}/txrx.sh" -ucSd 32K)
add_test(NAME t_ucR_1M_32k COMMAND "${PROJECT_SOURCE_DIR}/txrx.sh" -ucRd 1M -w 32K)
add_test(NAME t_ucR_128K_8K COMMAND "${PROJECT_SOURCE_DIR}/txrx.sh" -ucRd 128K -w 8K)
add_test(NAME t_ucR_16_1 COMMAND "${PROJECT_SOURCE_DIR}/txrx.sh" -ucRd 16 -w 1)
add_test(NAME t_ucR_32K_d COMMAND "${PROJECT_SOURCE_DIR}/txrx.sh" -ucRd 32K)
add_test(NAME t_ucD_1M_32k COMMAND "${PROJECT_SOURCE_DIR}/txrx.sh" -ucDd 1M -w 32K)
add_test(NAME t_ucD_128K_8K COMMAND "${PROJECT_SOURCE_DIR}/txrx.sh" -ucDd 128K -w 8K)
add_test(NAME t_ucD_16_1 COMMAND "${PROJECT_SOURCE_DIR}/txrx.sh" -ucDd 16 -w 1)
add_test(NAME t_ucD_32K_d COMMAND "${PROJECT_SOURCE_DIR}/txrx.sh" -ucDd 32K)
add_test(NAME t_usS_1M_32k COMMAND "${PROJECT_SOURCE_DIR}/txrx.sh" -usSd 1M -w 32K)
add_test(NAME t_usS_128K_8K COMMAND "${PROJECT_SOURCE_DIR}/txrx.sh" -usSd 128K -w 8K)
add_test(NAME t_usS_16_1 COMMAND "${PROJECT_SOURCE_DIR}/txrx.sh" -usSd 16 -w 1)
add_test(NAME t_usS_32K_d COMMAND "${PROJECT_SOURCE_DIR}/txrx.sh" -usSd 32K)
add_test(NAME t_usR_1M_32k COMMAND "${PROJECT_SOURCE_DIR}/txrx.sh" -usRd 1M -w 32K)
add_test(NAME t_usR_128K_8K COMMAND "${PROJECT_SOURCE_DIR}/txrx.sh" -usRd 128K -w 8K)
add_test(NAME t_usR_16_1 COMMAND "${PROJECT_SOURCE_DIR}/txrx.sh" -usRd 16 -w 1)
add_test(NAME t_usR_32K_d COMMAND "${PROJECT_SOURCE_DIR}/txrx.sh" -usRd 32K)
add_test(NAME t_usD_1M_32k COMMAND "${PROJECT_SOURCE_DIR}/txrx.sh" -usDd 1M -w 32K)
add_test(NAME t_usD_128K_8K COMMAND "${PROJECT_SOURCE_DIR}/txrx.sh" -usDd 128K -w 8K)
add_test(NAME t_usD_16_1 COMMAND "${PROJECT_SOURCE_DIR}/txrx.sh" -usDd 16 -w 1)
add_test(NAME t_usD_32K_d COMMAND "${PROJECT_SOURCE_DIR}/txrx.sh" -usDd 32K)
add_test(NAME t_ucS_128K_8K_l COMMAND "${PROJECT_SOURCE_DIR}/txrx.sh" -ucSd 128K -w 8K -l ${LOSS_RATE})
add_test(NAME t_ucS_128K_8K_L COMMAND "${PROJECT_SOURCE_DIR}/txrx.sh" -ucSd 128K -w 8K -L ${LOSS_RATE})
add_test(NAME t_ucS_128K_8K_lL COMMAND "${PROJECT_SOURCE_DIR}/txrx.sh" -ucSd 128K -w 8K -l ${LOSS_RATE} -L ${LOSS_RATE})
add_test(NAME t_ucR_128K_8K_l COMMAND "${PROJECT_SOURCE_DIR}/txrx.sh" -ucRd 128K -w 8K -l ${LOSS_RATE})
add_test(NAME t_ucR_128K_8K_L COMMAND "${PROJECT_SOURCE_DIR}/txrx.sh" -ucRd 128K -w 8K -L ${LOSS_RATE})
add_test(NAME t_ucR_128K_8K_lL COMMAND "${PROJECT_SOURCE_DIR}/txrx.sh" -ucRd 128K -w 8K -l ${LOSS_RATE} -L ${LOSS_RATE})
add_test(NAME t_ucD_128K_8K_l COMMAND "${PROJECT_SOURCE_DIR}/txrx.sh" -ucDd 128K -w 8K -l ${LOSS_RATE})
add_test(NAME t_ucD_128K_8K_L COMMAND "${PROJECT_SOURCE_DIR}/txrx.sh" -ucDd 128K -w 8K -L ${LOSS_RATE})
add_test(NAME t_ucD_128K_8K_lL COMMAND "${PROJECT_SOURCE_DIR}/txrx.sh" -ucDd 128K -w 8K -l ${LOSS_RATE} -L ${LOSS_RATE})
add_test(NAME t_usS_128K_8K_l COMMAND "${PROJECT_SOURCE_DIR}/txrx.sh" -usSd 128K -w 8K -l ${LOSS_RATE})
add_test(NAME t_usS_128K_8K_L COMMAND "${PROJECT_SOURCE_DIR}/txrx.sh" -usSd 128K -w 8K -L ${LOSS_RATE})
add_test(NAME t_usS_128K_8K_lL COMMAND "${PROJECT_SOURCE_DIR}/txrx.sh" -usSd 128K -w 8K -l ${LOSS_RATE} -L ${LOSS_RATE})
add_test(NAME t_usR_128K_8K_l COMMAND "${PROJECT_SOURCE_DIR}/txrx.sh" -usRd 128K -w 8K -l ${LOSS_RATE})
add_test(NAME t_usR_128K_8K_L COMMAND "${PROJECT_SOURCE_DIR}/txrx.sh" -usRd 128K -w 8K -L ${LOSS_RATE})
add_test(NAME t_usR_128K_8K_lL COMMAND "${PROJECT_SOURCE_DIR}/txrx.sh" -usRd 128K -w 8K -l ${LOSS_RATE} -L ${LOSS_RATE})
add_test(NAME t_usD_128K_8K_l COMMAND "${PROJECT_SOURCE_DIR}/txrx.sh" -usDd 128K -w 8K -l ${LOSS_RATE})
add_test(NAME t_usD_128K_8K_L COMMAND "${PROJECT_SOURCE_DIR}/txrx.sh" -usDd 128K -w 8K -L ${LOSS_RATE})
add_test(NAME t_usD_128K_8K_lL COMMAND "${PROJECT_SOURCE_DIR}/txrx.sh" -usDd 128K -w 8K -l ${LOSS_RATE} -L ${LOSS_RATE})
add_test(NAME t_ipv4_client_send COMMAND "${PROJECT_SOURCE_DIR}/txrx.sh" -icS)
add_test(NAME t_ipv4_server_send COMMAND "${PROJECT_SOURCE_DIR}/txrx.sh" -isS)
add_test(NAME t_ipv4_client_recv COMMAND "${PROJECT_SOURCE_DIR}/txrx.sh" -icR)
add_test(NAME t_ipv4_server_recv COMMAND "${PROJECT_SOURCE_DIR}/txrx.sh" -isR)
add_test(NAME t_ipv4_client_dupl COMMAND "${PROJECT_SOURCE_DIR}/txrx.sh" -icD)
add_test(NAME t_ipv4_server_dupl COMMAND "${PROJECT_SOURCE_DIR}/txrx.sh" -isD)
add_test(NAME t_icS_1M_32k COMMAND "${PROJECT_SOURCE_DIR}/txrx.sh" -icSd 1M -w 32K)
add_test(NAME t_icS_128K_8K COMMAND "${PROJECT_SOURCE_DIR}/txrx.sh" -icSd 128K -w 8K)
add_test(NAME t_icS_16_1 COMMAND "${PROJECT_SOURCE_DIR}/txrx.sh" -icSd 16 -w 1)
add_test(NAME t_icS_32K_d COMMAND "${PROJECT_SOURCE_DIR}/txrx.sh" -icSd 32K)
add_test(NAME t_icR_1M_32k COMMAND "${PROJECT_SOURCE_DIR}/txrx.sh" -icRd 1M -w 32K)
add_test(NAME t_icR_128K_8K COMMAND "${PROJECT_SOURCE_DIR}/txrx.sh" -icRd 128K -w 8K)
add_test(NAME t_icR_16_1 COMMAND "${PROJECT_SOURCE_DIR}/txrx.sh" -icRd 16 -w 1)
add_test(NAME t_icR_32K_d COMMAND "${PROJECT_SOURCE_DIR}/txrx.sh" -icRd 32K)
add_test(NAME t_icD_1M_32k COMMAND "${PROJECT_SOURCE_DIR}/txrx.sh" -icDd 1M -w 32K)
add_test(NAME t_icD_128K_8K COMMAND "${PROJECT_SOURCE_DIR}/txrx.sh" -icDd 128K -w 8K)
add_test(NAME t_icD_16_1 COMMAND "${PROJECT_SOURCE_DIR}/txrx.sh" -icDd 16 -w 1)
add_test(NAME t_icD_32K_d COMMAND "${PROJECT_SOURCE_DIR}/txrx.sh" -icDd 32K)
add_test(NAME t_isS_1M_32k COMMAND "${PROJECT_SOURCE_DIR}/txrx.sh" -isSd 1M -w 32K)
add_test(NAME t_isS_128K_8K COMMAND "${PROJECT_SOURCE_DIR}/txrx.sh" -isSd 128K -w 8K)
add_test(NAME t_isS_16_1 COMMAND "${PROJECT_SOURCE_DIR}/txrx.sh" -isSd 16 -w 1)
add_test(NAME t_isS_32K_d COMMAND "${PROJECT_SOURCE_DIR}/txrx.sh" -isSd 32K)
add_test(NAME t_isR_1M_32k COMMAND "${PROJECT_SOURCE_DIR}/txrx.sh" -isRd 1M -w 32K)
add_test(NAME t_isR_128K_8K COMMAND "${PROJECT_SOURCE_DIR}/txrx.sh" -isRd 128K -w 8K)
add_test(NAME t_isR_16_1 COMMAND "${PROJECT_SOURCE_DIR}/txrx.sh" -isRd 16 -w 1)
add_test(NAME t_isR_32K_d COMMAND "${PROJECT_SOURCE_DIR}/txrx.sh" -isRd 32K)
add_test(NAME t_isD_1M_32k COMMAND "${PROJECT_SOURCE_DIR}/txrx.sh" -isDd 1M -w 32K)
add_test(NAME t_isD_128K_8K COMMAND "${PROJECT_SOURCE_DIR}/txrx.sh" -isDd 128K -w 8K)
add_test(NAME t_isD_16_1 COMMAND "${PROJECT_SOURCE_DIR}/txrx.sh" -isDd 16 -w 1)
add_test(NAME t_isD_32K_d COMMAND "${PROJECT_SOURCE_DIR}/txrx.sh" -isDd 32K)
add_test(NAME t_icS_128K_8K_l COMMAND "${PROJECT_SOURCE_DIR}/txrx.sh" -icSd 128K -w 8K -l ${LOSS_RATE})
add_test(NAME t_icS_128K_8K_L COMMAND "${PROJECT_SOURCE_DIR}/txrx.sh" -icSd 128K -w 8K -L ${LOSS_RATE})
add_test(NAME t_icS_128K_8K_lL COMMAND "${PROJECT_SOURCE_DIR}/txrx.sh" -icSd 128K -w 8K -l ${LOSS_RATE} -L ${LOSS_RATE})
add_test(NAME t_icR_128K_8K_l COMMAND "${PROJECT_SOURCE_DIR}/txrx.sh" -icRd 128K -w 8K -l ${LOSS_RATE})
add_test(NAME t_icR_128K_8K_L COMMAND "${PROJECT_SOURCE_DIR}/txrx.sh" -icRd 128K -w 8K -L ${LOSS_RATE})
add_test(NAME t_icR_128K_8K_lL COMMAND "${PROJECT_SOURCE_DIR}/txrx.sh" -icRd 128K -w 8K -l ${LOSS_RATE} -L ${LOSS_RATE})
add_test(NAME t_icD_128K_8K_l COMMAND "${PROJECT_SOURCE_DIR}/txrx.sh" -icDd 128K -w 8K -l ${LOSS_RATE})
add_test(NAME t_icD_128K_8K_L COMMAND "${PROJECT_SOURCE_DIR}/txrx.sh" -icDd 128K -w 8K -L ${LOSS_RATE})
add_test(NAME t_icD_128K_8K_lL COMMAND "${PROJECT_SOURCE_DIR}/txrx.sh" -icDd 128K -w 8K -l ${LOSS_RATE} -L ${LOSS_RATE})
add_test(NAME t_isS_128K_8K_l COMMAND "${PROJECT_SOURCE_DIR}/txrx.sh" -isSd 128K -w 8K -l ${LOSS_RATE})
add_test(NAME t_isS_128K_8K_L COMMAND "${PROJECT_SOURCE_DIR}/txrx.sh" -isSd 128K -w 8K -L ${LOSS_RATE})
add_test(NAME t_isS_128K_8K_lL COMMAND "${PROJECT_SOURCE_DIR}/txrx.sh" -isSd 128K -w 8K -l ${LOSS_RATE} -L ${LOSS_RATE})
add_test(NAME t_isR_128K_8K_l COMMAND "${PROJECT_SOURCE_DIR}/txrx.sh" -isRd 128K -w 8K -l ${LOSS_RATE})
add_test(NAME t_isR_128K_8K_L COMMAND "${PROJECT_SOURCE_DIR}/txrx.sh" -isRd 128K -w 8K -L ${LOSS_RATE})
add_test(NAME t_isR_128K_8K_lL COMMAND "${PROJECT_SOURCE_DIR}/txrx.sh" -isRd 128K -w 8K -l ${LOSS_RATE} -L ${LOSS_RATE})
add_test(NAME t_isD_128K_8K_l COMMAND "${PROJECT_SOURCE_DIR}/txrx.sh" -isDd 128K -w 8K -l ${LOSS_RATE})
add_test(NAME t_isD_128K_8K_L COMMAND "${PROJECT_SOURCE_DIR}/txrx.sh" -isDd 128K -w 8K -L ${LOSS_RATE})
add_test(NAME t_isD_128K_8K_lL COMMAND "${PROJECT_SOURCE_DIR}/txrx.sh" -isDd 128K -w 8K -l ${LOSS_RATE} -L ${LOSS_RATE})
add_test(NAME t_icnS_128K_8K_l COMMAND "${PROJECT_SOURCE_DIR}/txrx.sh" -icSnd 128K -w 8K -l ${LOSS_RATE})
add_test(NAME t_icnS_128K_8K_L COMMAND "${PROJECT_SOURCE_DIR}/txrx.sh" -icSnd 128K -w 8K -L ${LOSS_RATE})
add_test(NAME t_icnS_128K_8K_lL COMMAND "${PROJECT_SOURCE_DIR}/txrx.sh" -icSnd 128K -w 8K -l ${LOSS_RATE} -L ${LOSS_RATE})
add_test(NAME t_icnR_128K_8K_l COMMAND "${PROJECT_SOURCE_DIR}/txrx.sh" -icRnd 128K -w 8K -l ${LOSS_RATE})
add_test(NAME t_icnR_128K_8K_L COMMAND "${PROJECT_SOURCE_DIR}/txrx.sh" -icRnd 128K -w 8K -L ${LOSS_RATE})
add_test(NAME t_icnR_128K_8K_lL COMMAND "${PROJECT_SOURCE_DIR}/txrx.sh" -icRnd 128K -w 8K -l ${LOSS_RATE} -L ${LOSS_RATE})
add_test(NAME t_icnD_128K_8K_l COMMAND "${PROJECT_SOURCE_DIR}/txrx.sh" -icDnd 128K -w 8K -l ${LOSS_RATE})
add_test(NAME t_icnD_128K_8K_L COMMAND "${PROJECT_SOURCE_DIR}/txrx.sh" -icDnd 128K -w 8K -L ${LOSS_RATE})
add_test(NAME t_icnD_128K_8K_lL COMMAND "${PROJECT_SOURCE_DIR}/txrx.sh" -icDnd 128K -w 8K -l ${LOSS_RATE} -L ${LOSS_RATE})
add_test(NAME t_isnS_128K_8K_l COMMAND "${PROJECT_SOURCE_DIR}/txrx.sh" -isSnd 128K -w 8K -l ${LOSS_RATE})
add_test(NAME t_isnS_128K_8K_L COMMAND "${PROJECT_SOURCE_DIR}/txrx.sh" -isSnd 128K -w 8K -L ${LOSS_RATE})
add_test(NAME t_isnS_128K_8K_lL COMMAND "${PROJECT_SOURCE_DIR}/txrx.sh" -isSnd 128K -w 8K -l ${LOSS_RATE} -L ${LOSS_RATE})
add_test(NAME t_isnR_128K_8K_l COMMAND "${PROJECT_SOURCE_DIR}/txrx.sh" -isRnd 128K -w 8K -l ${LOSS_RATE})
add_test(NAME t_isnR_128K_8K_L COMMAND "${PROJECT_SOURCE_DIR}/txrx.sh" -isRnd 128K -w 8K -L ${LOSS_RATE})
add_test(NAME t_isnR_128K_8K_lL COMMAND "${PROJECT_SOURCE_DIR}/txrx.sh" -isRnd 128K -w 8K -l ${LOSS_RATE} -L ${LOSS_RATE})
add_test(NAME t_isnD_128K_8K_l COMMAND "${PROJECT_SOURCE_DIR}/txrx.sh" -isDnd 128K -w 8K -l ${LOSS_RATE})
add_test(NAME t_isnD_128K_8K_L COMMAND "${PROJECT_SOURCE_DIR}/txrx.sh" -isDnd 128K -w 8K -L ${LOSS_RATE})
add_test(NAME t_isnD_128K_8K_lL COMMAND "${PROJECT_SOURCE_DIR}/txrx.sh" -isDnd 128K -w 8K -l ${LOSS_RATE} -L ${LOSS_RATE})
#add_test(NAME t_icoS_128K_8K_l COMMAND "${PROJECT_SOURCE_DIR}/txrx.sh" -icSod 128K -w 8K -l ${LOSS_RATE})
#add_test(NAME t_icoS_128K_8K_L COMMAND "${PROJECT_SOURCE_DIR}/txrx.sh" -icSod 128K -w 8K -L ${LOSS_RATE})
#add_test(NAME t_icoS_128K_8K_lL COMMAND "${PROJECT_SOURCE_DIR}/txrx.sh" -icSod 128K -w 8K -l ${LOSS_RATE} -L ${LOSS_RATE})
#add_test(NAME t_icoR_128K_8K_l COMMAND "${PROJECT_SOURCE_DIR}/txrx.sh" -icRod 128K -w 8K -l ${LOSS_RATE})
#add_test(NAME t_icoR_128K_8K_L COMMAND "${PROJECT_SOURCE_DIR}/txrx.sh" -icRod 128K -w 8K -L ${LOSS_RATE})
#add_test(NAME t_icoR_128K_8K_lL COMMAND "${PROJECT_SOURCE_DIR}/txrx.sh" -icRod 128K -w 8K -l ${LOSS_RATE} -L ${LOSS_RATE})
#add_test(NAME t_icoD_128K_8K_l COMMAND "${PROJECT_SOURCE_DIR}/txrx.sh" -icDod 128K -w 8K -l ${LOSS_RATE})
#add_test(NAME t_icoD_128K_8K_L COMMAND "${PROJECT_SOURCE_DIR}/txrx.sh" -icDod 128K -w 8K -L ${LOSS_RATE})
#add_test(NAME t_icoD_128K_8K_lL COMMAND "${PROJECT_SOURCE_DIR}/txrx.sh" -icDod 128K -w 8K -l ${LOSS_RATE} -L ${LOSS_RATE})
#add_test(NAME t_isoS_128K_8K_l COMMAND "${PROJECT_SOURCE_DIR}/txrx.sh" -isSod 128K -w 8K -l ${LOSS_RATE})
#add_test(NAME t_isoS_128K_8K_L COMMAND "${PROJECT_SOURCE_DIR}/txrx.sh" -isSod 128K -w 8K -L ${LOSS_RATE})
#add_test(NAME t_isoS_128K_8K_lL COMMAND "${PROJECT_SOURCE_DIR}/txrx.sh" -isSod 128K -w 8K -l ${LOSS_RATE} -L ${LOSS_RATE})
#add_test(NAME t_isoR_128K_8K_l COMMAND "${PROJECT_SOURCE_DIR}/txrx.sh" -isRod 128K -w 8K -l ${LOSS_RATE})
#add_test(NAME t_isoR_128K_8K_L COMMAND "${PROJECT_SOURCE_DIR}/txrx.sh" -isRod 128K -w 8K -L ${LOSS_RATE})
#add_test(NAME t_isoR_128K_8K_lL COMMAND "${PROJECT_SOURCE_DIR}/txrx.sh" -isRod 128K -w 8K -l ${LOSS_RATE} -L ${LOSS_RATE})
#add_test(NAME t_isoD_128K_8K_l COMMAND "${PROJECT_SOURCE_DIR}/txrx.sh" -isDod 128K -w 8K -l ${LOSS_RATE})
#add_test(NAME t_isoD_128K_8K_L COMMAND "${PROJECT_SOURCE_DIR}/txrx.sh" -isDod 128K -w 8K -L ${LOSS_RATE})
#add_test(NAME t_isoD_128K_8K_lL COMMAND "${PROJECT_SOURCE_DIR}/txrx.sh" -isDod 128K -w 8K -l ${LOSS_RATE} -L ${LOSS_RATE})
add_custom_target (check_webget COMMAND ${CMAKE_CTEST_COMMAND} --output-on-failure --timeout 10 -R 't_webget'
COMMENT "Testing webget...")
add_custom_target (check_lab0 COMMAND ${CMAKE_CTEST_COMMAND} --output-on-failure --timeout 10 -R 't_webget|t_byte_stream|_dt'
COMMENT "Testing Lab 0...")
add_custom_target (check_lab1 COMMAND ${CMAKE_CTEST_COMMAND} --output-on-failure --timeout 10 -R 't_strm_reassem_|t_byte_stream|_dt'
COMMENT "Testing the stream reassembler...")
add_custom_target (check_lab2 COMMAND ${CMAKE_CTEST_COMMAND} --output-on-failure --timeout 10 -R 't_recv_|t_wrapping_|t_strm_reassem_|t_byte_stream|_dt'
COMMENT "Testing the TCP receiver...")
add_custom_target (check_lab3 COMMAND ${CMAKE_CTEST_COMMAND} --output-on-failure --timeout 10 -R 't_send_|t_recv_|t_wrapping_|t_strm_reassem_|t_byte_stream|_dt'
COMMENT "Testing the TCP sender...")
add_custom_target (check_lab4 COMMAND "${PROJECT_SOURCE_DIR}/tun.sh" check 144 145
COMMAND ${CMAKE_CTEST_COMMAND} --output-on-failure --timeout 10 -R "^t_"
COMMENT "Testing the TCP connection...")
add_custom_target (check_lab5 COMMAND "${PROJECT_SOURCE_DIR}/tap.sh" check 10
COMMAND ${CMAKE_CTEST_COMMAND} --output-on-failure --timeout 10 -R '^t_webget|^arp_'
COMMENT "Testing Lab 5...")
add_custom_target (check_lab6 COMMAND ${CMAKE_CTEST_COMMAND} --output-on-failure --timeout 10 -R '^arp_|^router_'
COMMENT "Testing Lab 6...")
add_custom_target (check COMMAND "${PROJECT_SOURCE_DIR}/tun.sh" check 144 145
COMMAND "${PROJECT_SOURCE_DIR}/tap.sh" check 10
COMMAND ${CMAKE_CTEST_COMMAND} --output-on-failure --timeout 10 -R '^t_|^arp_|^router_'
COMMENT "Testing libsponge...")

1
etc/tunconfig Normal file
View File

@ -0,0 +1 @@
TUN_IP_PREFIX=169.254

2
libsponge/CMakeLists.txt Normal file
View File

@ -0,0 +1,2 @@
file (GLOB LIB_SOURCES "*.cc" "util/*.cc" "tcp_helpers/*.cc")
add_library (sponge STATIC ${LIB_SOURCES})

53
libsponge/byte_stream.cc Normal file
View File

@ -0,0 +1,53 @@
#include "byte_stream.hh"
// Dummy implementation of a flow-controlled in-memory byte stream.
// For Lab 0, please replace with a real implementation that passes the
// automated checks run by `make check_lab0`.
// You will need to add private members to the class declaration in `byte_stream.hh`
template <typename... Targs>
void DUMMY_CODE(Targs &&... /* unused */) {}
using namespace std;
ByteStream::ByteStream(const size_t capacity) { DUMMY_CODE(capacity); }
size_t ByteStream::write(const string &data) {
DUMMY_CODE(data);
return {};
}
//! \param[in] len bytes will be copied from the output side of the buffer
string ByteStream::peek_output(const size_t len) const {
DUMMY_CODE(len);
return {};
}
//! \param[in] len bytes will be removed from the output side of the buffer
void ByteStream::pop_output(const size_t len) { DUMMY_CODE(len); }
//! Read (i.e., copy and then pop) the next "len" bytes of the stream
//! \param[in] len bytes will be popped and returned
//! \returns a string
std::string ByteStream::read(const size_t len) {
DUMMY_CODE(len);
return {};
}
void ByteStream::end_input() {}
bool ByteStream::input_ended() const { return {}; }
size_t ByteStream::buffer_size() const { return {}; }
bool ByteStream::buffer_empty() const { return {}; }
bool ByteStream::eof() const { return false; }
size_t ByteStream::bytes_written() const { return {}; }
size_t ByteStream::bytes_read() const { return {}; }
size_t ByteStream::remaining_capacity() const { return {}; }

85
libsponge/byte_stream.hh Normal file
View File

@ -0,0 +1,85 @@
#ifndef SPONGE_LIBSPONGE_BYTE_STREAM_HH
#define SPONGE_LIBSPONGE_BYTE_STREAM_HH
#include <string>
//! \brief An in-order byte stream.
//! Bytes are written on the "input" side and read from the "output"
//! side. The byte stream is finite: the writer can end the input,
//! and then no more bytes can be written.
class ByteStream {
private:
// Your code here -- add private members as necessary.
// Hint: This doesn't need to be a sophisticated data structure at
// all, but if any of your tests are taking longer than a second,
// that's a sign that you probably want to keep exploring
// different approaches.
bool _error{}; //!< Flag indicating that the stream suffered an error.
public:
//! Construct a stream with room for `capacity` bytes.
ByteStream(const size_t capacity);
//! \name "Input" interface for the writer
//!@{
//! Write a string of bytes into the stream. Write as many
//! as will fit, and return how many were written.
//! \returns the number of bytes accepted into the stream
size_t write(const std::string &data);
//! \returns the number of additional bytes that the stream has space for
size_t remaining_capacity() const;
//! Signal that the byte stream has reached its ending
void end_input();
//! Indicate that the stream suffered an error.
void set_error() { _error = true; }
//!@}
//! \name "Output" interface for the reader
//!@{
//! Peek at next "len" bytes of the stream
//! \returns a string
std::string peek_output(const size_t len) const;
//! Remove bytes from the buffer
void pop_output(const size_t len);
//! Read (i.e., copy and then pop) the next "len" bytes of the stream
//! \returns a string
std::string read(const size_t len);
//! \returns `true` if the stream input has ended
bool input_ended() const;
//! \returns `true` if the stream has suffered an error
bool error() const { return _error; }
//! \returns the maximum amount that can currently be read from the stream
size_t buffer_size() const;
//! \returns `true` if the buffer is empty
bool buffer_empty() const;
//! \returns `true` if the output has reached the ending
bool eof() const;
//!@}
//! \name General accounting
//!@{
//! Total number of bytes written
size_t bytes_written() const;
//! Total number of bytes popped
size_t bytes_read() const;
//!@}
};
#endif // SPONGE_LIBSPONGE_BYTE_STREAM_HH

134
libsponge/util/address.cc Normal file
View File

@ -0,0 +1,134 @@
#include "address.hh"
#include "util.hh"
#include <arpa/inet.h>
#include <cstring>
#include <memory>
#include <netdb.h>
#include <stdexcept>
#include <system_error>
using namespace std;
//! Converts Raw to `sockaddr *`.
Address::Raw::operator sockaddr *() { return reinterpret_cast<sockaddr *>(&storage); }
//! Converts Raw to `const sockaddr *`.
Address::Raw::operator const sockaddr *() const { return reinterpret_cast<const sockaddr *>(&storage); }
//! \param[in] addr points to a raw socket address
//! \param[in] size is `addr`'s length
Address::Address(const sockaddr *addr, const size_t size) : _size(size) {
// make sure proposed sockaddr can fit
if (size > sizeof(_address.storage)) {
throw runtime_error("invalid sockaddr size");
}
memcpy(&_address.storage, addr, size);
}
//! Error category for getaddrinfo and getnameinfo failures.
class gai_error_category : public error_category {
public:
//! The name of the wrapped error
const char *name() const noexcept override { return "gai_error_category"; }
//! \brief An error message
//! \param[in] return_value the error return value from [getaddrinfo(3)](\ref man3::getaddrinfo)
//! or [getnameinfo(3)](\ref man3::getnameinfo)
string message(const int return_value) const noexcept override { return gai_strerror(return_value); }
};
//! \param[in] node is the hostname or dotted-quad address
//! \param[in] service is the service name or numeric string
//! \param[in] hints are criteria for resolving the supplied name
Address::Address(const string &node, const string &service, const addrinfo &hints) : _size() {
// prepare for the answer
addrinfo *resolved_address = nullptr;
// look up the name or names
const int gai_ret = getaddrinfo(node.c_str(), service.c_str(), &hints, &resolved_address);
if (gai_ret != 0) {
throw tagged_error(gai_error_category(), "getaddrinfo(" + node + ", " + service + ")", gai_ret);
}
// if success, should always have at least one entry
if (resolved_address == nullptr) {
throw runtime_error("getaddrinfo returned successfully but with no results");
}
// put resolved_address in a wrapper so it will get freed if we have to throw an exception
auto addrinfo_deleter = [](addrinfo *const x) { freeaddrinfo(x); };
unique_ptr<addrinfo, decltype(addrinfo_deleter)> wrapped_address(resolved_address, move(addrinfo_deleter));
// assign to our private members (making sure size fits)
*this = Address(wrapped_address->ai_addr, wrapped_address->ai_addrlen);
}
//! \brief Build a `struct addrinfo` containing hints for [getaddrinfo(3)](\ref man3::getaddrinfo)
//! \param[in] ai_flags is the value of the `ai_flags` field in the [struct addrinfo](\ref man3::getaddrinfo)
//! \param[in] ai_family is the value of the `ai_family` field in the [struct addrinfo](\ref man3::getaddrinfo)
static inline addrinfo make_hints(const int ai_flags, const int ai_family) {
addrinfo hints{}; // value initialized to all zeros
hints.ai_flags = ai_flags;
hints.ai_family = ai_family;
return hints;
}
//! \param[in] hostname to resolve
//! \param[in] service name (from `/etc/services`, e.g., "http" is port 80)
Address::Address(const string &hostname, const string &service)
: Address(hostname, service, make_hints(AI_ALL, AF_INET)) {}
//! \param[in] ip address as a dotted quad ("1.1.1.1")
//! \param[in] port number
Address::Address(const string &ip, const uint16_t port)
// tell getaddrinfo that we don't want to resolve anything
: Address(ip, ::to_string(port), make_hints(AI_NUMERICHOST | AI_NUMERICSERV, AF_INET)) {}
// accessors
pair<string, uint16_t> Address::ip_port() const {
array<char, NI_MAXHOST> ip{};
array<char, NI_MAXSERV> port{};
const int gni_ret =
getnameinfo(_address, _size, ip.data(), ip.size(), port.data(), port.size(), NI_NUMERICHOST | NI_NUMERICSERV);
if (gni_ret != 0) {
throw tagged_error(gai_error_category(), "getnameinfo", gni_ret);
}
return {ip.data(), stoi(port.data())};
}
string Address::to_string() const {
const auto ip_and_port = ip_port();
return ip_and_port.first + ":" + ::to_string(ip_and_port.second);
}
uint32_t Address::ipv4_numeric() const {
if (_address.storage.ss_family != AF_INET or _size != sizeof(sockaddr_in)) {
throw runtime_error("ipv4_numeric called on non-IPV4 address");
}
sockaddr_in ipv4_addr{};
memcpy(&ipv4_addr, &_address.storage, _size);
return be32toh(ipv4_addr.sin_addr.s_addr);
}
Address Address::from_ipv4_numeric(const uint32_t ip_address) {
sockaddr_in ipv4_addr{};
ipv4_addr.sin_family = AF_INET;
ipv4_addr.sin_addr.s_addr = htobe32(ip_address);
return {reinterpret_cast<sockaddr *>(&ipv4_addr), sizeof(ipv4_addr)};
}
// equality
bool Address::operator==(const Address &other) const {
if (_size != other._size) {
return false;
}
return 0 == memcmp(&_address, &other._address, _size);
}

85
libsponge/util/address.hh Normal file
View File

@ -0,0 +1,85 @@
#ifndef SPONGE_LIBSPONGE_ADDRESS_HH
#define SPONGE_LIBSPONGE_ADDRESS_HH
#include <cstddef>
#include <cstdint>
#include <netdb.h>
#include <netinet/in.h>
#include <string>
#include <sys/socket.h>
#include <utility>
//! Wrapper around [IPv4 addresses](@ref man7::ip) and DNS operations.
class Address {
public:
//! \brief Wrapper around [sockaddr_storage](@ref man7::socket).
//! \details A `sockaddr_storage` is enough space to store any socket address (IPv4 or IPv6).
class Raw {
public:
sockaddr_storage storage{}; //!< The wrapped struct itself.
operator sockaddr *();
operator const sockaddr *() const;
};
private:
socklen_t _size; //!< Size of the wrapped address.
Raw _address{}; //!< A wrapped [sockaddr_storage](@ref man7::socket) containing the address.
//! Constructor from ip/host, service/port, and hints to the resolver.
Address(const std::string &node, const std::string &service, const addrinfo &hints);
public:
//! Construct by resolving a hostname and servicename.
Address(const std::string &hostname, const std::string &service);
//! Construct from dotted-quad string ("18.243.0.1") and numeric port.
Address(const std::string &ip, const std::uint16_t port = 0);
//! Construct from a [sockaddr *](@ref man7::socket).
Address(const sockaddr *addr, const std::size_t size);
//! Equality comparison.
bool operator==(const Address &other) const;
bool operator!=(const Address &other) const { return not operator==(other); }
//! \name Conversions
//!@{
//! Dotted-quad IP address string ("18.243.0.1") and numeric port.
std::pair<std::string, uint16_t> ip_port() const;
//! Dotted-quad IP address string ("18.243.0.1").
std::string ip() const { return ip_port().first; }
//! Numeric port (host byte order).
uint16_t port() const { return ip_port().second; }
//! Numeric IP address as an integer (i.e., in [host byte order](\ref man3::byteorder)).
uint32_t ipv4_numeric() const;
//! Create an Address from a 32-bit raw numeric IP address
static Address from_ipv4_numeric(const uint32_t ip_address);
//! Human-readable string, e.g., "8.8.8.8:53".
std::string to_string() const;
//!@}
//! \name Low-level operations
//!@{
//! Size of the underlying address storage.
socklen_t size() const { return _size; }
//! Const pointer to the underlying socket address storage.
operator const sockaddr *() const { return _address; }
//!@}
};
//! \class Address
//! For example, you can do DNS lookups:
//!
//! \include address_example_1.cc
//!
//! or you can specify an IP address and port number:
//!
//! \include address_example_2.cc
//!
//! Once you have an address, you can convert it to other useful representations, e.g.,
//!
//! \include address_example_3.cc
#endif // SPONGE_LIBSPONGE_ADDRESS_HH

104
libsponge/util/buffer.cc Normal file
View File

@ -0,0 +1,104 @@
#include "buffer.hh"
using namespace std;
void Buffer::remove_prefix(const size_t n) {
if (n > str().size()) {
throw out_of_range("Buffer::remove_prefix");
}
_starting_offset += n;
if (_storage and _starting_offset == _storage->size()) {
_storage.reset();
}
}
void BufferList::append(const BufferList &other) {
for (const auto &buf : other._buffers) {
_buffers.push_back(buf);
}
}
BufferList::operator Buffer() const {
switch (_buffers.size()) {
case 0:
return {};
case 1:
return _buffers[0];
default: {
throw runtime_error(
"BufferList: please use concatenate() to combine a multi-Buffer BufferList into one Buffer");
}
}
}
string BufferList::concatenate() const {
std::string ret;
ret.reserve(size());
for (const auto &buf : _buffers) {
ret.append(buf);
}
return ret;
}
size_t BufferList::size() const {
size_t ret = 0;
for (const auto &buf : _buffers) {
ret += buf.size();
}
return ret;
}
void BufferList::remove_prefix(size_t n) {
while (n > 0) {
if (_buffers.empty()) {
throw std::out_of_range("BufferList::remove_prefix");
}
if (n < _buffers.front().str().size()) {
_buffers.front().remove_prefix(n);
n = 0;
} else {
n -= _buffers.front().str().size();
_buffers.pop_front();
}
}
}
BufferViewList::BufferViewList(const BufferList &buffers) {
for (const auto &x : buffers.buffers()) {
_views.push_back(x);
}
}
void BufferViewList::remove_prefix(size_t n) {
while (n > 0) {
if (_views.empty()) {
throw std::out_of_range("BufferListView::remove_prefix");
}
if (n < _views.front().size()) {
_views.front().remove_prefix(n);
n = 0;
} else {
n -= _views.front().size();
_views.pop_front();
}
}
}
size_t BufferViewList::size() const {
size_t ret = 0;
for (const auto &buf : _views) {
ret += buf.size();
}
return ret;
}
vector<iovec> BufferViewList::as_iovecs() const {
vector<iovec> ret;
ret.reserve(_views.size());
for (const auto &x : _views) {
ret.push_back({const_cast<char *>(x.data()), x.size()});
}
return ret;
}

130
libsponge/util/buffer.hh Normal file
View File

@ -0,0 +1,130 @@
#ifndef SPONGE_LIBSPONGE_BUFFER_HH
#define SPONGE_LIBSPONGE_BUFFER_HH
#include <algorithm>
#include <deque>
#include <memory>
#include <numeric>
#include <stdexcept>
#include <string>
#include <string_view>
#include <sys/uio.h>
#include <vector>
//! \brief A reference-counted read-only string that can discard bytes from the front
class Buffer {
private:
std::shared_ptr<std::string> _storage{};
size_t _starting_offset{};
public:
Buffer() = default;
//! \brief Construct by taking ownership of a string
Buffer(std::string &&str) noexcept : _storage(std::make_shared<std::string>(std::move(str))) {}
//! \name Expose contents as a std::string_view
//!@{
std::string_view str() const {
if (not _storage) {
return {};
}
return {_storage->data() + _starting_offset, _storage->size() - _starting_offset};
}
operator std::string_view() const { return str(); }
//!@}
//! \brief Get character at location `n`
uint8_t at(const size_t n) const { return str().at(n); }
//! \brief Size of the string
size_t size() const { return str().size(); }
//! \brief Make a copy to a new std::string
std::string copy() const { return std::string(str()); }
//! \brief Discard the first `n` bytes of the string (does not require a copy or move)
//! \note Doesn't free any memory until the whole string has been discarded in all copies of the Buffer.
void remove_prefix(const size_t n);
};
//! \brief A reference-counted discontiguous string that can discard bytes from the front
//! \note Used to model packets that contain multiple sets of headers
//! + a payload. This allows us to prepend headers (e.g., to
//! encapsulate a TCP payload in a TCPSegment, and then encapsulate
//! the TCPSegment in an IPv4Datagram) without copying the payload.
class BufferList {
private:
std::deque<Buffer> _buffers{};
public:
//! \name Constructors
//!@{
BufferList() = default;
//! \brief Construct from a Buffer
BufferList(Buffer buffer) : _buffers{buffer} {}
//! \brief Construct by taking ownership of a std::string
BufferList(std::string &&str) noexcept {
Buffer buf{std::move(str)};
append(buf);
}
//!@}
//! \brief Access the underlying queue of Buffers
const std::deque<Buffer> &buffers() const { return _buffers; }
//! \brief Append a BufferList
void append(const BufferList &other);
//! \brief Transform to a Buffer
//! \note Throws an exception unless BufferList is contiguous
operator Buffer() const;
//! \brief Discard the first `n` bytes of the string (does not require a copy or move)
void remove_prefix(size_t n);
//! \brief Size of the string
size_t size() const;
//! \brief Make a copy to a new std::string
std::string concatenate() const;
};
//! \brief A non-owning temporary view (similar to std::string_view) of a discontiguous string
class BufferViewList {
std::deque<std::string_view> _views{};
public:
//! \name Constructors
//!@{
//! \brief Construct from a std::string
BufferViewList(const std::string &str) : BufferViewList(std::string_view(str)) {}
//! \brief Construct from a C string (must be NULL-terminated)
BufferViewList(const char *s) : BufferViewList(std::string_view(s)) {}
//! \brief Construct from a BufferList
BufferViewList(const BufferList &buffers);
//! \brief Construct from a std::string_view
BufferViewList(std::string_view str) { _views.push_back({const_cast<char *>(str.data()), str.size()}); }
//!@}
//! \brief Discard the first `n` bytes of the string (does not require a copy or move)
void remove_prefix(size_t n);
//! \brief Size of the string
size_t size() const;
//! \brief Convert to a vector of `iovec` structures
//! \note used for system calls that write discontiguous buffers,
//! e.g. [writev(2)](\ref man2::writev) and [sendmsg(2)](\ref man2::sendmsg)
std::vector<iovec> as_iovecs() const;
};
#endif // SPONGE_LIBSPONGE_BUFFER_HH

145
libsponge/util/eventloop.cc Normal file
View File

@ -0,0 +1,145 @@
#include "eventloop.hh"
#include "util.hh"
#include <cerrno>
#include <stdexcept>
#include <system_error>
#include <utility>
#include <vector>
using namespace std;
unsigned int EventLoop::Rule::service_count() const {
return direction == Direction::In ? fd.read_count() : fd.write_count();
}
//! \param[in] fd is the FileDescriptor to be polled
//! \param[in] direction indicates whether to poll for reading (Direction::In) or writing (Direction::Out)
//! \param[in] callback is called when `fd` is ready.
//! \param[in] interest is called by EventLoop::wait_next_event. If it returns `true`, `fd` will
//! be polled, otherwise `fd` will be ignored only for this execution of `wait_next_event.
//! \param[in] cancel is called when the rule is cancelled (e.g. on hangup, EOF, or closure).
void EventLoop::add_rule(const FileDescriptor &fd,
const Direction direction,
const CallbackT &callback,
const InterestT &interest,
const CallbackT &cancel) {
_rules.push_back({fd.duplicate(), direction, callback, interest, cancel});
}
//! \param[in] timeout_ms is the timeout value passed to [poll(2)](\ref man2::poll); `wait_next_event`
//! returns Result::Timeout if no fd is ready after the timeout expires.
//! \returns Eventloop::Result indicating success, timeout, or no more Rule objects to poll.
//!
//! For each Rule, this function first calls Rule::interest; if `true`, Rule::fd is added to the
//! list of file descriptors to be polled for readability (if Rule::direction == Direction::In) or
//! writability (if Rule::direction == Direction::Out) unless Rule::fd has reached EOF, in which case
//! the Rule is canceled (i.e., deleted from EventLoop::_rules).
//!
//! Next, this function calls [poll(2)](\ref man2::poll) with timeout value `timeout_ms`.
//!
//! Then, for each ready file descriptor, this function calls Rule::callback. If fd reaches EOF or
//! if the Rule was registered using EventLoop::add_cancelable_rule and Rule::callback returns true,
//! this Rule is canceled.
//!
//! If an error occurs during polling, this function throws a std::runtime_error.
//!
//! If a [signal(7)](\ref man7::signal) was caught during polling or if EventLoop::_rules becomes empty,
//! this function returns Result::Exit.
//!
//! If a timeout occurred while polling (i.e., no fd became ready), this function returns Result::Timeout.
//!
//! Otherwise, this function returns Result::Success.
//!
//! \b IMPORTANT: every call to Rule::callback must read from or write to Rule::fd, or the `interest`
//! callback must stop returning true after the callback completes.
//! If none of these conditions occur, EventLoop::wait_next_event will throw std::runtime_error. This is
//! because [poll(2)](\ref man2::poll) is level triggered, so failing to act on a ready file descriptor
//! will result in a busy loop (poll returns on a ready file descriptor; file descriptor is not read or
//! written, so it is still ready; the next call to poll will immediately return).
EventLoop::Result EventLoop::wait_next_event(const int timeout_ms) {
vector<pollfd> pollfds{};
pollfds.reserve(_rules.size());
bool something_to_poll = false;
// set up the pollfd for each rule
for (auto it = _rules.cbegin(); it != _rules.cend();) { // NOTE: it gets erased or incremented in loop body
const auto &this_rule = *it;
if (this_rule.direction == Direction::In && this_rule.fd.eof()) {
// no more reading on this rule, it's reached eof
this_rule.cancel();
it = _rules.erase(it);
continue;
}
if (this_rule.fd.closed()) {
this_rule.cancel();
it = _rules.erase(it);
continue;
}
if (this_rule.interest()) {
pollfds.push_back({this_rule.fd.fd_num(), static_cast<short>(this_rule.direction), 0});
something_to_poll = true;
} else {
pollfds.push_back({this_rule.fd.fd_num(), 0, 0}); // placeholder --- we still want errors
}
++it;
}
// quit if there is nothing left to poll
if (not something_to_poll) {
return Result::Exit;
}
// call poll -- wait until one of the fds satisfies one of the rules (writeable/readable)
try {
if (0 == SystemCall("poll", ::poll(pollfds.data(), pollfds.size(), timeout_ms))) {
return Result::Timeout;
}
} catch (unix_error const &e) {
if (e.code().value() == EINTR) {
return Result::Exit;
}
}
// go through the poll results
for (auto [it, idx] = make_pair(_rules.begin(), size_t(0)); it != _rules.end(); ++idx) {
const auto &this_pollfd = pollfds[idx];
const auto poll_error = static_cast<bool>(this_pollfd.revents & (POLLERR | POLLNVAL));
if (poll_error) {
throw runtime_error("EventLoop: error on polled file descriptor");
}
const auto &this_rule = *it;
const auto poll_ready = static_cast<bool>(this_pollfd.revents & this_pollfd.events);
const auto poll_hup = static_cast<bool>(this_pollfd.revents & POLLHUP);
if (poll_hup && this_pollfd.events && !poll_ready) {
// if we asked for the status, and the _only_ condition was a hangup, this FD is defunct:
// - if it was POLLIN and nothing is readable, no more will ever be readable
// - if it was POLLOUT, it will not be writable again
this_rule.cancel();
it = _rules.erase(it);
continue;
}
if (poll_ready) {
// we only want to call callback if revents includes the event we asked for
const auto count_before = this_rule.service_count();
this_rule.callback();
// only check for busy wait if we're not canceling or exiting
if (count_before == this_rule.service_count() and this_rule.interest()) {
throw runtime_error(
"EventLoop: busy wait detected: callback did not read/write fd and is still interested");
}
}
++it; // if we got here, it means we didn't call _rules.erase()
}
return Result::Success;
}

View File

@ -0,0 +1,76 @@
#ifndef SPONGE_LIBSPONGE_EVENTLOOP_HH
#define SPONGE_LIBSPONGE_EVENTLOOP_HH
#include "file_descriptor.hh"
#include <cstdlib>
#include <functional>
#include <list>
#include <poll.h>
//! Waits for events on file descriptors and executes corresponding callbacks.
class EventLoop {
public:
//! Indicates interest in reading (In) or writing (Out) a polled fd.
enum class Direction : short {
In = POLLIN, //!< Callback will be triggered when Rule::fd is readable.
Out = POLLOUT //!< Callback will be triggered when Rule::fd is writable.
};
private:
using CallbackT = std::function<void(void)>; //!< Callback for ready Rule::fd
using InterestT = std::function<bool(void)>; //!< `true` return indicates Rule::fd should be polled.
//! \brief Specifies a condition and callback that an EventLoop should handle.
//! \details Created by calling EventLoop::add_rule() or EventLoop::add_cancelable_rule().
class Rule {
public:
FileDescriptor fd; //!< FileDescriptor to monitor for activity.
Direction direction; //!< Direction::In for reading from fd, Direction::Out for writing to fd.
CallbackT callback; //!< A callback that reads or writes fd.
InterestT interest; //!< A callback that returns `true` whenever fd should be polled.
CallbackT cancel; //!< A callback that is called when the rule is cancelled (e.g. on hangup)
//! Returns the number of times fd has been read or written, depending on the value of Rule::direction.
//! \details This function is used internally by EventLoop; you will not need to call it
unsigned int service_count() const;
};
std::list<Rule> _rules{}; //!< All rules that have been added and not canceled.
public:
//! Returned by each call to EventLoop::wait_next_event.
enum class Result {
Success, //!< At least one Rule was triggered.
Timeout, //!< No rules were triggered before timeout.
Exit //!< All rules have been canceled or were uninterested; make no further calls to EventLoop::wait_next_event.
};
//! Add a rule whose callback will be called when `fd` is ready in the specified Direction.
void add_rule(const FileDescriptor &fd,
const Direction direction,
const CallbackT &callback,
const InterestT &interest = [] { return true; },
const CallbackT &cancel = [] {});
//! Calls [poll(2)](\ref man2::poll) and then executes callback for each ready fd.
Result wait_next_event(const int timeout_ms);
};
using Direction = EventLoop::Direction;
//! \class EventLoop
//!
//! An EventLoop holds a std::list of Rule objects. Each time EventLoop::wait_next_event is
//! executed, the EventLoop uses the Rule objects to construct a call to [poll(2)](\ref man2::poll).
//!
//! When a Rule is installed using EventLoop::add_rule, it will be polled for the specified Rule::direction
//! whenver the Rule::interest callback returns `true`, until Rule::fd is no longer readable
//! (for Rule::direction == Direction::In) or writable (for Rule::direction == Direction::Out).
//! Once this occurs, the Rule is canceled, i.e., the EventLoop deletes it.
//!
//! A Rule installed using EventLoop::add_cancelable_rule will be polled and canceled under the
//! same conditions, with the additional condition that if Rule::callback returns `true`, the
//! Rule will be canceled.
#endif // SPONGE_LIBSPONGE_EVENTLOOP_HH

View File

@ -0,0 +1,110 @@
#include "file_descriptor.hh"
#include "util.hh"
#include <algorithm>
#include <fcntl.h>
#include <iostream>
#include <stdexcept>
#include <sys/uio.h>
#include <unistd.h>
using namespace std;
//! \param[in] fd is the file descriptor number returned by [open(2)](\ref man2::open) or similar
FileDescriptor::FDWrapper::FDWrapper(const int fd) : _fd(fd) {
if (fd < 0) {
throw runtime_error("invalid fd number:" + to_string(fd));
}
}
void FileDescriptor::FDWrapper::close() {
SystemCall("close", ::close(_fd));
_eof = _closed = true;
}
FileDescriptor::FDWrapper::~FDWrapper() {
try {
if (_closed) {
return;
}
close();
} catch (const exception &e) {
// don't throw an exception from the destructor
std::cerr << "Exception destructing FDWrapper: " << e.what() << std::endl;
}
}
//! \param[in] fd is the file descriptor number returned by [open(2)](\ref man2::open) or similar
FileDescriptor::FileDescriptor(const int fd) : _internal_fd(make_shared<FDWrapper>(fd)) {}
//! Private constructor used by duplicate()
FileDescriptor::FileDescriptor(shared_ptr<FDWrapper> other_shared_ptr) : _internal_fd(move(other_shared_ptr)) {}
//! \returns a copy of this FileDescriptor
FileDescriptor FileDescriptor::duplicate() const { return FileDescriptor(_internal_fd); }
//! \param[in] limit is the maximum number of bytes to read; fewer bytes may be returned
//! \param[out] str is the string to be read
void FileDescriptor::read(std::string &str, const size_t limit) {
constexpr size_t BUFFER_SIZE = 1024 * 1024; // maximum size of a read
const size_t size_to_read = min(BUFFER_SIZE, limit);
str.resize(size_to_read);
ssize_t bytes_read = SystemCall("read", ::read(fd_num(), str.data(), size_to_read));
if (limit > 0 && bytes_read == 0) {
_internal_fd->_eof = true;
}
if (bytes_read > static_cast<ssize_t>(size_to_read)) {
throw runtime_error("read() read more than requested");
}
str.resize(bytes_read);
register_read();
}
//! \param[in] limit is the maximum number of bytes to read; fewer bytes may be returned
//! \returns a vector of bytes read
string FileDescriptor::read(const size_t limit) {
string ret;
read(ret, limit);
return ret;
}
size_t FileDescriptor::write(BufferViewList buffer, const bool write_all) {
size_t total_bytes_written = 0;
do {
auto iovecs = buffer.as_iovecs();
const ssize_t bytes_written = SystemCall("writev", ::writev(fd_num(), iovecs.data(), iovecs.size()));
if (bytes_written == 0 and buffer.size() != 0) {
throw runtime_error("write returned 0 given non-empty input buffer");
}
if (bytes_written > ssize_t(buffer.size())) {
throw runtime_error("write wrote more than length of input buffer");
}
register_write();
buffer.remove_prefix(bytes_written);
total_bytes_written += bytes_written;
} while (write_all and buffer.size());
return total_bytes_written;
}
void FileDescriptor::set_blocking(const bool blocking_state) {
int flags = SystemCall("fcntl", fcntl(fd_num(), F_GETFL));
if (blocking_state) {
flags ^= (flags & O_NONBLOCK);
} else {
flags |= O_NONBLOCK;
}
SystemCall("fcntl", fcntl(fd_num(), F_SETFL, flags));
}

View File

@ -0,0 +1,117 @@
#ifndef SPONGE_LIBSPONGE_FILE_DESCRIPTOR_HH
#define SPONGE_LIBSPONGE_FILE_DESCRIPTOR_HH
#include "buffer.hh"
#include <array>
#include <cstddef>
#include <limits>
#include <memory>
//! A reference-counted handle to a file descriptor
class FileDescriptor {
//! \brief A handle on a kernel file descriptor.
//! \details FileDescriptor objects contain a std::shared_ptr to a FDWrapper.
class FDWrapper {
public:
int _fd; //!< The file descriptor number returned by the kernel
bool _eof = false; //!< Flag indicating whether FDWrapper::_fd is at EOF
bool _closed = false; //!< Flag indicating whether FDWrapper::_fd has been closed
unsigned _read_count = 0; //!< The number of times FDWrapper::_fd has been read
unsigned _write_count = 0; //!< The numberof times FDWrapper::_fd has been written
//! Construct from a file descriptor number returned by the kernel
explicit FDWrapper(const int fd);
//! Closes the file descriptor upon destruction
~FDWrapper();
//! Calls [close(2)](\ref man2::close) on FDWrapper::_fd
void close();
//! \name
//! An FDWrapper cannot be copied or moved
//!@{
FDWrapper(const FDWrapper &other) = delete;
FDWrapper &operator=(const FDWrapper &other) = delete;
FDWrapper(FDWrapper &&other) = delete;
FDWrapper &operator=(FDWrapper &&other) = delete;
//!@}
};
//! A reference-counted handle to a shared FDWrapper
std::shared_ptr<FDWrapper> _internal_fd;
// private constructor used to duplicate the FileDescriptor (increase the reference count)
explicit FileDescriptor(std::shared_ptr<FDWrapper> other_shared_ptr);
protected:
void register_read() { ++_internal_fd->_read_count; } //!< increment read count
void register_write() { ++_internal_fd->_write_count; } //!< increment write count
public:
//! Construct from a file descriptor number returned by the kernel
explicit FileDescriptor(const int fd);
//! Free the std::shared_ptr; the FDWrapper destructor calls close() when the refcount goes to zero.
~FileDescriptor() = default;
//! Read up to `limit` bytes
std::string read(const size_t limit = std::numeric_limits<size_t>::max());
//! Read up to `limit` bytes into `str` (caller can allocate storage)
void read(std::string &str, const size_t limit = std::numeric_limits<size_t>::max());
//! Write a string, possibly blocking until all is written
size_t write(const char *str, const bool write_all = true) { return write(BufferViewList(str), write_all); }
//! Write a string, possibly blocking until all is written
size_t write(const std::string &str, const bool write_all = true) { return write(BufferViewList(str), write_all); }
//! Write a buffer (or list of buffers), possibly blocking until all is written
size_t write(BufferViewList buffer, const bool write_all = true);
//! Close the underlying file descriptor
void close() { _internal_fd->close(); }
//! Copy a FileDescriptor explicitly, increasing the FDWrapper refcount
FileDescriptor duplicate() const;
//! Set blocking(true) or non-blocking(false)
void set_blocking(const bool blocking_state);
//! \name FDWrapper accessors
//!@{
//! underlying descriptor number
int fd_num() const { return _internal_fd->_fd; }
//! EOF flag state
bool eof() const { return _internal_fd->_eof; }
//! closed flag state
bool closed() const { return _internal_fd->_closed; }
//! number of reads
unsigned int read_count() const { return _internal_fd->_read_count; }
//! number of writes
unsigned int write_count() const { return _internal_fd->_write_count; }
//!@}
//! \name Copy/move constructor/assignment operators
//! FileDescriptor can be moved, but cannot be copied (but see duplicate())
//!@{
FileDescriptor(const FileDescriptor &other) = delete; //!< \brief copy construction is forbidden
FileDescriptor &operator=(const FileDescriptor &other) = delete; //!< \brief copy assignment is forbidden
FileDescriptor(FileDescriptor &&other) = default; //!< \brief move construction is allowed
FileDescriptor &operator=(FileDescriptor &&other) = default; //!< \brief move assignment is allowed
//!@}
};
//! \class FileDescriptor
//! In addition, FileDescriptor tracks EOF state and calls to FileDescriptor::read and
//! FileDescriptor::write, which EventLoop uses to detect busy loop conditions.
//!
//! For an example of FileDescriptor use, see the EventLoop class documentation.
#endif // SPONGE_LIBSPONGE_FILE_DESCRIPTOR_HH

72
libsponge/util/parser.cc Normal file
View File

@ -0,0 +1,72 @@
#include "parser.hh"
using namespace std;
//! \param[in] r is the ParseResult to show
//! \returns a string representation of the ParseResult
string as_string(const ParseResult r) {
static constexpr const char *_names[] = {
"NoError",
"BadChecksum",
"PacketTooShort",
"WrongIPVersion",
"HeaderTooShort",
"TruncatedPacket",
};
return _names[static_cast<size_t>(r)];
}
void NetParser::_check_size(const size_t size) {
if (size > _buffer.size()) {
set_error(ParseResult::PacketTooShort);
}
}
template <typename T>
T NetParser::_parse_int() {
constexpr size_t len = sizeof(T);
_check_size(len);
if (error()) {
return 0;
}
T ret = 0;
for (size_t i = 0; i < len; i++) {
ret <<= 8;
ret += uint8_t(_buffer.at(i));
}
_buffer.remove_prefix(len);
return ret;
}
void NetParser::remove_prefix(const size_t n) {
_check_size(n);
if (error()) {
return;
}
_buffer.remove_prefix(n);
}
template <typename T>
void NetUnparser::_unparse_int(string &s, T val) {
constexpr size_t len = sizeof(T);
for (size_t i = 0; i < len; ++i) {
const uint8_t the_byte = (val >> ((len - i - 1) * 8)) & 0xff;
s.push_back(the_byte);
}
}
uint32_t NetParser::u32() { return _parse_int<uint32_t>(); }
uint16_t NetParser::u16() { return _parse_int<uint16_t>(); }
uint8_t NetParser::u8() { return _parse_int<uint8_t>(); }
void NetUnparser::u32(string &s, const uint32_t val) { return _unparse_int<uint32_t>(s, val); }
void NetUnparser::u16(string &s, const uint16_t val) { return _unparse_int<uint16_t>(s, val); }
void NetUnparser::u8(string &s, const uint8_t val) { return _unparse_int<uint8_t>(s, val); }

79
libsponge/util/parser.hh Normal file
View File

@ -0,0 +1,79 @@
#ifndef SPONGE_LIBSPONGE_PARSER_HH
#define SPONGE_LIBSPONGE_PARSER_HH
#include "buffer.hh"
#include <cstdint>
#include <cstdlib>
#include <string>
#include <utility>
//! The result of parsing or unparsing an IP datagram, TCP segment, Ethernet frame, or ARP message
enum class ParseResult {
NoError = 0, //!< Success
BadChecksum, //!< Bad checksum
PacketTooShort, //!< Not enough data to finish parsing
WrongIPVersion, //!< Got a version of IP other than 4
HeaderTooShort, //!< Header length is shorter than minimum required
TruncatedPacket, //!< Packet length is shorter than header claims
Unsupported //!< Packet uses unsupported features
};
//! Output a string representation of a ParseResult
std::string as_string(const ParseResult r);
class NetParser {
private:
Buffer _buffer;
ParseResult _error = ParseResult::NoError; //!< Result of parsing so far
//! Check that there is sufficient data to parse the next token
void _check_size(const size_t size);
//! Generic integer parsing method (used by u32, u16, u8)
template <typename T>
T _parse_int();
public:
NetParser(Buffer buffer) : _buffer(buffer) {}
Buffer buffer() const { return _buffer; }
//! Get the current value stored in BaseParser::_error
ParseResult get_error() const { return _error; }
//! \brief Set BaseParser::_error
//! \param[in] res is the value to store in BaseParser::_error
void set_error(ParseResult res) { _error = res; }
//! Returns `true` if there has been an error
bool error() const { return get_error() != ParseResult::NoError; }
//! Parse a 32-bit integer in network byte order from the data stream
uint32_t u32();
//! Parse a 16-bit integer in network byte order from the data stream
uint16_t u16();
//! Parse an 8-bit integer in network byte order from the data stream
uint8_t u8();
//! Remove n bytes from the buffer
void remove_prefix(const size_t n);
};
struct NetUnparser {
template <typename T>
static void _unparse_int(std::string &s, T val);
//! Write a 32-bit integer into the data stream in network byte order
static void u32(std::string &s, const uint32_t val);
//! Write a 16-bit integer into the data stream in network byte order
static void u16(std::string &s, const uint16_t val);
//! Write an 8-bit integer into the data stream in network byte order
static void u8(std::string &s, const uint8_t val);
};
#endif // SPONGE_LIBSPONGE_PARSER_HH

168
libsponge/util/socket.cc Normal file
View File

@ -0,0 +1,168 @@
#include "socket.hh"
#include "util.hh"
#include <cstddef>
#include <stdexcept>
#include <unistd.h>
using namespace std;
// default constructor for socket of (subclassed) domain and type
//! \param[in] domain is as described in [socket(7)](\ref man7::socket), probably `AF_INET` or `AF_UNIX`
//! \param[in] type is as described in [socket(7)](\ref man7::socket)
Socket::Socket(const int domain, const int type) : FileDescriptor(SystemCall("socket", socket(domain, type, 0))) {}
// construct from file descriptor
//! \param[in] fd is the FileDescriptor from which to construct
//! \param[in] domain is `fd`'s domain; throws std::runtime_error if wrong value is supplied
//! \param[in] type is `fd`'s type; throws std::runtime_error if wrong value is supplied
Socket::Socket(FileDescriptor &&fd, const int domain, const int type) : FileDescriptor(move(fd)) {
int actual_value;
socklen_t len;
// verify domain
len = sizeof(actual_value);
SystemCall("getsockopt", getsockopt(fd_num(), SOL_SOCKET, SO_DOMAIN, &actual_value, &len));
if ((len != sizeof(actual_value)) or (actual_value != domain)) {
throw runtime_error("socket domain mismatch");
}
// verify type
len = sizeof(actual_value);
SystemCall("getsockopt", getsockopt(fd_num(), SOL_SOCKET, SO_TYPE, &actual_value, &len));
if ((len != sizeof(actual_value)) or (actual_value != type)) {
throw runtime_error("socket type mismatch");
}
}
// get the local or peer address the socket is connected to
//! \param[in] name_of_function is the function to call (string passed to SystemCall())
//! \param[in] function is a pointer to the function
//! \returns the requested Address
Address Socket::get_address(const string &name_of_function,
const function<int(int, sockaddr *, socklen_t *)> &function) const {
Address::Raw address;
socklen_t size = sizeof(address);
SystemCall(name_of_function, function(fd_num(), address, &size));
return {address, size};
}
//! \returns the local Address of the socket
Address Socket::local_address() const { return get_address("getsockname", getsockname); }
//! \returns the socket's peer's Address
Address Socket::peer_address() const { return get_address("getpeername", getpeername); }
// bind socket to a specified local address (usually to listen/accept)
//! \param[in] address is a local Address to bind
void Socket::bind(const Address &address) { SystemCall("bind", ::bind(fd_num(), address, address.size())); }
// connect socket to a specified peer address
//! \param[in] address is the peer's Address
void Socket::connect(const Address &address) { SystemCall("connect", ::connect(fd_num(), address, address.size())); }
// shut down a socket in the specified way
//! \param[in] how can be `SHUT_RD`, `SHUT_WR`, or `SHUT_RDWR`; see [shutdown(2)](\ref man2::shutdown)
void Socket::shutdown(const int how) {
SystemCall("shutdown", ::shutdown(fd_num(), how));
switch (how) {
case SHUT_RD:
register_read();
break;
case SHUT_WR:
register_write();
break;
case SHUT_RDWR:
register_read();
register_write();
break;
default:
throw runtime_error("Socket::shutdown() called with invalid `how`");
}
}
//! \note If `mtu` is too small to hold the received datagram, this method throws a std::runtime_error
void UDPSocket::recv(received_datagram &datagram, const size_t mtu) {
// receive source address and payload
Address::Raw datagram_source_address;
datagram.payload.resize(mtu);
socklen_t fromlen = sizeof(datagram_source_address);
const ssize_t recv_len = SystemCall(
"recvfrom",
::recvfrom(
fd_num(), datagram.payload.data(), datagram.payload.size(), MSG_TRUNC, datagram_source_address, &fromlen));
if (recv_len > ssize_t(mtu)) {
throw runtime_error("recvfrom (oversized datagram)");
}
register_read();
datagram.source_address = {datagram_source_address, fromlen};
datagram.payload.resize(recv_len);
}
UDPSocket::received_datagram UDPSocket::recv(const size_t mtu) {
received_datagram ret{{nullptr, 0}, ""};
recv(ret, mtu);
return ret;
}
void sendmsg_helper(const int fd_num,
const sockaddr *destination_address,
const socklen_t destination_address_len,
const BufferViewList &payload) {
auto iovecs = payload.as_iovecs();
msghdr message{};
message.msg_name = const_cast<sockaddr *>(destination_address);
message.msg_namelen = destination_address_len;
message.msg_iov = iovecs.data();
message.msg_iovlen = iovecs.size();
const ssize_t bytes_sent = SystemCall("sendmsg", ::sendmsg(fd_num, &message, 0));
if (size_t(bytes_sent) != payload.size()) {
throw runtime_error("datagram payload too big for sendmsg()");
}
}
void UDPSocket::sendto(const Address &destination, const BufferViewList &payload) {
sendmsg_helper(fd_num(), destination, destination.size(), payload);
register_write();
}
void UDPSocket::send(const BufferViewList &payload) {
sendmsg_helper(fd_num(), nullptr, 0, payload);
register_write();
}
// mark the socket as listening for incoming connections
//! \param[in] backlog is the number of waiting connections to queue (see [listen(2)](\ref man2::listen))
void TCPSocket::listen(const int backlog) { SystemCall("listen", ::listen(fd_num(), backlog)); }
// accept a new incoming connection
//! \returns a new TCPSocket connected to the peer.
//! \note This function blocks until a new connection is available
TCPSocket TCPSocket::accept() {
register_read();
return TCPSocket(FileDescriptor(SystemCall("accept", ::accept(fd_num(), nullptr, nullptr))));
}
// set socket option
//! \param[in] level The protocol level at which the argument resides
//! \param[in] option A single option to set
//! \param[in] option_value The value to set
//! \details See [setsockopt(2)](\ref man2::setsockopt) for details.
template <typename option_type>
void Socket::setsockopt(const int level, const int option, const option_type &option_value) {
SystemCall("setsockopt", ::setsockopt(fd_num(), level, option, &option_value, sizeof(option_value)));
}
// allow local address to be reused sooner, at the cost of some robustness
//! \note Using `SO_REUSEADDR` may reduce the robustness of your application
void Socket::set_reuseaddr() { setsockopt(SOL_SOCKET, SO_REUSEADDR, int(true)); }

124
libsponge/util/socket.hh Normal file
View File

@ -0,0 +1,124 @@
#ifndef SPONGE_LIBSPONGE_SOCKET_HH
#define SPONGE_LIBSPONGE_SOCKET_HH
#include "address.hh"
#include "file_descriptor.hh"
#include <cstdint>
#include <functional>
#include <string>
#include <sys/socket.h>
//! \brief Base class for network sockets (TCP, UDP, etc.)
//! \details Socket is generally used via a subclass. See TCPSocket and UDPSocket for usage examples.
class Socket : public FileDescriptor {
private:
//! Get the local or peer address the socket is connected to
Address get_address(const std::string &name_of_function,
const std::function<int(int, sockaddr *, socklen_t *)> &function) const;
protected:
//! Construct via [socket(2)](\ref man2::socket)
Socket(const int domain, const int type);
//! Construct from a file descriptor.
Socket(FileDescriptor &&fd, const int domain, const int type);
//! Wrapper around [setsockopt(2)](\ref man2::setsockopt)
template <typename option_type>
void setsockopt(const int level, const int option, const option_type &option_value);
public:
//! Bind a socket to a specified address with [bind(2)](\ref man2::bind), usually for listen/accept
void bind(const Address &address);
//! Connect a socket to a specified peer address with [connect(2)](\ref man2::connect)
void connect(const Address &address);
//! Shut down a socket via [shutdown(2)](\ref man2::shutdown)
void shutdown(const int how);
//! Get local address of socket with [getsockname(2)](\ref man2::getsockname)
Address local_address() const;
//! Get peer address of socket with [getpeername(2)](\ref man2::getpeername)
Address peer_address() const;
//! Allow local address to be reused sooner via [SO_REUSEADDR](\ref man7::socket)
void set_reuseaddr();
};
//! A wrapper around [UDP sockets](\ref man7::udp)
class UDPSocket : public Socket {
protected:
//! \brief Construct from FileDescriptor (used by TCPOverUDPSocketAdapter)
//! \param[in] fd is the FileDescriptor from which to construct
explicit UDPSocket(FileDescriptor &&fd) : Socket(std::move(fd), AF_INET, SOCK_DGRAM) {}
public:
//! Default: construct an unbound, unconnected UDP socket
UDPSocket() : Socket(AF_INET, SOCK_DGRAM) {}
//! Returned by UDPSocket::recv; carries received data and information about the sender
struct received_datagram {
Address source_address; //!< Address from which this datagram was received
std::string payload; //!< UDP datagram payload
};
//! Receive a datagram and the Address of its sender
received_datagram recv(const size_t mtu = 65536);
//! Receive a datagram and the Address of its sender (caller can allocate storage)
void recv(received_datagram &datagram, const size_t mtu = 65536);
//! Send a datagram to specified Address
void sendto(const Address &destination, const BufferViewList &payload);
//! Send datagram to the socket's connected address (must call connect() first)
void send(const BufferViewList &payload);
};
//! \class UDPSocket
//! Functions in this class are essentially wrappers over their POSIX eponyms.
//!
//! Example:
//!
//! \include socket_example_1.cc
//! A wrapper around [TCP sockets](\ref man7::tcp)
class TCPSocket : public Socket {
private:
//! \brief Construct from FileDescriptor (used by accept())
//! \param[in] fd is the FileDescriptor from which to construct
explicit TCPSocket(FileDescriptor &&fd) : Socket(std::move(fd), AF_INET, SOCK_STREAM) {}
public:
//! Default: construct an unbound, unconnected TCP socket
TCPSocket() : Socket(AF_INET, SOCK_STREAM) {}
//! Mark a socket as listening for incoming connections
void listen(const int backlog = 16);
//! Accept a new incoming connection
TCPSocket accept();
};
//! \class TCPSocket
//! Functions in this class are essentially wrappers over their POSIX eponyms.
//!
//! Example:
//!
//! \include socket_example_2.cc
//! A wrapper around [Unix-domain stream sockets](\ref man7::unix)
class LocalStreamSocket : public Socket {
public:
//! Construct from a file descriptor
explicit LocalStreamSocket(FileDescriptor &&fd) : Socket(std::move(fd), AF_UNIX, SOCK_STREAM) {}
};
//! \class LocalStreamSocket
//! Example:
//!
//! \include socket_example_3.cc
#endif // SPONGE_LIBSPONGE_SOCKET_HH

36
libsponge/util/tun.cc Normal file
View File

@ -0,0 +1,36 @@
#include "tun.hh"
#include "util.hh"
#include <cstring>
#include <fcntl.h>
#include <linux/if.h>
#include <linux/if_tun.h>
#include <sys/ioctl.h>
static constexpr const char *CLONEDEV = "/dev/net/tun";
using namespace std;
//! \param[in] devname is the name of the TUN or TAP device, specified at its creation.
//! \param[in] is_tun is `true` for a TUN device (expects IP datagrams), or `false` for a TAP device (expects Ethernet frames)
//!
//! To create a TUN device, you should already have run
//!
//! ip tuntap add mode tun user `username` name `devname`
//!
//! as root before calling this function.
TunTapFD::TunTapFD(const string &devname, const bool is_tun)
: FileDescriptor(SystemCall("open", open(CLONEDEV, O_RDWR))) {
struct ifreq tun_req {};
tun_req.ifr_flags = (is_tun ? IFF_TUN : IFF_TAP) | IFF_NO_PI; // tun device with no packetinfo
// copy devname to ifr_name, making sure to null terminate
strncpy(static_cast<char *>(tun_req.ifr_name), devname.data(), IFNAMSIZ - 1);
tun_req.ifr_name[IFNAMSIZ - 1] = '\0';
SystemCall("ioctl", ioctl(fd_num(), TUNSETIFF, static_cast<void *>(&tun_req)));
}

29
libsponge/util/tun.hh Normal file
View File

@ -0,0 +1,29 @@
#ifndef SPONGE_LIBSPONGE_TUN_HH
#define SPONGE_LIBSPONGE_TUN_HH
#include "file_descriptor.hh"
#include <string>
//! A FileDescriptor to a [Linux TUN/TAP](https://www.kernel.org/doc/Documentation/networking/tuntap.txt) device
class TunTapFD : public FileDescriptor {
public:
//! Open an existing persistent [TUN or TAP device](https://www.kernel.org/doc/Documentation/networking/tuntap.txt).
explicit TunTapFD(const std::string &devname, const bool is_tun);
};
//! A FileDescriptor to a [Linux TUN](https://www.kernel.org/doc/Documentation/networking/tuntap.txt) device
class TunFD : public TunTapFD {
public:
//! Open an existing persistent [TUN device](https://www.kernel.org/doc/Documentation/networking/tuntap.txt).
explicit TunFD(const std::string &devname) : TunTapFD(devname, true) {}
};
//! A FileDescriptor to a [Linux TAP](https://www.kernel.org/doc/Documentation/networking/tuntap.txt) device
class TapFD : public TunTapFD {
public:
//! Open an existing persistent [TAP device](https://www.kernel.org/doc/Documentation/networking/tuntap.txt).
explicit TapFD(const std::string &devname) : TunTapFD(devname, false) {}
};
#endif // SPONGE_LIBSPONGE_TUN_HH

144
libsponge/util/util.cc Normal file
View File

@ -0,0 +1,144 @@
#include "util.hh"
#include <array>
#include <cctype>
#include <chrono>
#include <iomanip>
#include <iostream>
#include <sstream>
#include <sys/socket.h>
using namespace std;
//! \returns the number of milliseconds since the program started
uint64_t timestamp_ms() {
using time_point = std::chrono::steady_clock::time_point;
static const time_point program_start = std::chrono::steady_clock::now();
const time_point now = std::chrono::steady_clock::now();
return std::chrono::duration_cast<std::chrono::milliseconds>(now - program_start).count();
}
//! \param[in] attempt is the name of the syscall to try (for error reporting)
//! \param[in] return_value is the return value of the syscall
//! \param[in] errno_mask is any errno value that is acceptable, e.g., `EAGAIN` when reading a non-blocking fd
//! \details This function works for any syscall that returns less than 0 on error and sets errno:
//!
//! For example, to wrap a call to [open(2)](\ref man2::open), you might say:
//!
//! ~~~{.cc}
//! const int foo_fd = SystemCall("open", ::open("/tmp/foo", O_RDWR));
//! ~~~
//!
//! If you don't have permission to open the file, SystemCall will throw a std::runtime_error.
//! If you don't want to throw in that case, you can pass `EACCESS` in `errno_mask`:
//!
//! ~~~{.cc}
//! // open a file, or print an error if permission was denied
//! const int foo_fd = SystemCall("open", ::open("/tmp/foo", O_RDWR), EACCESS);
//! if (foo_fd < 0) {
//! std::cerr << "Access to /tmp/foo was denied." << std::endl;
//! }
//! ~~~
int SystemCall(const char *attempt, const int return_value, const int errno_mask) {
if (return_value >= 0 || errno == errno_mask) {
return return_value;
}
throw unix_error(attempt);
}
//! \param[in] attempt is the name of the syscall to try (for error reporting)
//! \param[in] return_value is the return value of the syscall
//! \param[in] errno_mask is any errno value that is acceptable, e.g., `EAGAIN` when reading a non-blocking fd
//! \details see the other SystemCall() documentation for more details
int SystemCall(const string &attempt, const int return_value, const int errno_mask) {
return SystemCall(attempt.c_str(), return_value, errno_mask);
}
//! \details A properly seeded mt19937 generator takes a lot of entropy!
//!
//! This code borrows from the following:
//!
//! - https://kristerw.blogspot.com/2017/05/seeding-stdmt19937-random-number-engine.html
//! - http://www.pcg-random.org/posts/cpps-random_device.html
mt19937 get_random_generator() {
auto rd = random_device();
array<uint32_t, mt19937::state_size> seed_data{};
generate(seed_data.begin(), seed_data.end(), [&] { return rd(); });
seed_seq seed(seed_data.begin(), seed_data.end());
return mt19937(seed);
}
//! \note This class returns the checksum in host byte order.
//! See https://commandcenter.blogspot.com/2012/04/byte-order-fallacy.html for rationale
//! \details This class can be used to either check or compute an Internet checksum
//! (e.g., for an IP datagram header or a TCP segment).
//!
//! The Internet checksum is defined such that evaluating inet_cksum() on a TCP segment (IP datagram, etc)
//! containing a correct checksum header will return zero. In other words, if you read a correct TCP segment
//! off the wire and pass it untouched to inet_cksum(), the return value will be 0.
//!
//! Meanwhile, to compute the checksum for an outgoing TCP segment (IP datagram, etc.), you must first set
//! the checksum header to zero, then call inet_cksum(), and finally set the checksum header to the return
//! value.
//!
//! For more information, see the [Wikipedia page](https://en.wikipedia.org/wiki/IPv4_header_checksum)
//! on the Internet checksum, and consult the [IP](\ref rfc::rfc791) and [TCP](\ref rfc::rfc793) RFCs.
InternetChecksum::InternetChecksum(const uint32_t initial_sum) : _sum(initial_sum) {}
void InternetChecksum::add(std::string_view data) {
for (size_t i = 0; i < data.size(); i++) {
uint16_t val = uint8_t(data[i]);
if (not _parity) {
val <<= 8;
}
_sum += val;
_parity = !_parity;
}
}
uint16_t InternetChecksum::value() const {
uint32_t ret = _sum;
while (ret > 0xffff) {
ret = (ret >> 16) + (ret & 0xffff);
}
return ~ret;
}
//! \param[in] data is a pointer to the bytes to show
//! \param[in] len is the number of bytes to show
//! \param[in] indent is the number of spaces to indent
void hexdump(const uint8_t *data, const size_t len, const size_t indent) {
const auto flags(cout.flags());
const string indent_string(indent, ' ');
int printed = 0;
stringstream pchars(" ");
cout << hex << setfill('0');
for (unsigned i = 0; i < len; ++i) {
if ((printed & 0xf) == 0) {
if (printed != 0) {
cout << " " << pchars.str() << "\n";
pchars.str(" ");
}
cout << indent_string << setw(8) << printed << ": ";
} else if ((printed & 1) == 0) {
cout << ' ';
}
cout << setw(2) << +data[i];
pchars << (static_cast<bool>(isprint(data[i])) ? static_cast<char>(data[i]) : '.');
printed += 1;
}
const int print_rem = (16 - (printed & 0xf)) % 16; // extra spacing before final chars
cout << string(2 * print_rem + print_rem / 2 + 4, ' ') << pchars.str();
cout << '\n' << endl;
cout.flags(flags);
}
//! \param[in] data is a pointer to the bytes to show
//! \param[in] len is the number of bytes to show
//! \param[in] indent is the number of spaces to indent
void hexdump(const char *data, const size_t len, const size_t indent) {
hexdump(reinterpret_cast<const uint8_t *>(data), len, indent);
}

72
libsponge/util/util.hh Normal file
View File

@ -0,0 +1,72 @@
#ifndef SPONGE_LIBSPONGE_UTIL_HH
#define SPONGE_LIBSPONGE_UTIL_HH
#include <algorithm>
#include <cerrno>
#include <cstddef>
#include <cstdint>
#include <iterator>
#include <ostream>
#include <random>
#include <string>
#include <system_error>
#include <vector>
//! std::system_error plus the name of what was being attempted
class tagged_error : public std::system_error {
private:
std::string _attempt_and_error; //!< What was attempted, and what happened
public:
//! \brief Construct from a category, an attempt, and an error code
//! \param[in] category is the category of error
//! \param[in] attempt is what was supposed to happen
//! \param[in] error_code is the resulting error
tagged_error(const std::error_category &category, const std::string &attempt, const int error_code)
: system_error(error_code, category), _attempt_and_error(attempt + ": " + std::system_error::what()) {}
//! Returns a C string describing the error
const char *what() const noexcept override { return _attempt_and_error.c_str(); }
};
//! a tagged_error for syscalls
class unix_error : public tagged_error {
public:
//! brief Construct from a syscall name and the resulting errno
//! \param[in] attempt is the name of the syscall attempted
//! \param[in] error is the [errno(3)](\ref man3::errno) that resulted
explicit unix_error(const std::string &attempt, const int error = errno)
: tagged_error(std::system_category(), attempt, error) {}
};
//! Error-checking wrapper for most syscalls
int SystemCall(const char *attempt, const int return_value, const int errno_mask = 0);
//! Version of SystemCall that takes a C++ std::string
int SystemCall(const std::string &attempt, const int return_value, const int errno_mask = 0);
//! Seed a fast random generator
std::mt19937 get_random_generator();
//! Get the time in milliseconds since the program began.
uint64_t timestamp_ms();
//! The internet checksum algorithm
class InternetChecksum {
private:
uint32_t _sum;
bool _parity{};
public:
InternetChecksum(const uint32_t initial_sum = 0);
void add(std::string_view data);
uint16_t value() const;
};
//! Hexdump the contents of a packet (or any other sequence of bytes)
void hexdump(const char *data, const size_t len, const size_t indent = 0);
//! Hexdump the contents of a packet (or any other sequence of bytes)
void hexdump(const uint8_t *data, const size_t len, const size_t indent = 0);
#endif // SPONGE_LIBSPONGE_UTIL_HH

13
tests/CMakeLists.txt Normal file
View File

@ -0,0 +1,13 @@
add_library (spongechecks STATIC byte_stream_test_harness.cc)
macro (add_test_exec exec_name)
add_executable ("${exec_name}" "${exec_name}.cc")
target_link_libraries ("${exec_name}" spongechecks ${ARGN})
target_link_libraries ("${exec_name}" sponge ${ARGN})
endmacro (add_test_exec)
add_test_exec (byte_stream_construction)
add_test_exec (byte_stream_one_write)
add_test_exec (byte_stream_two_writes)
add_test_exec (byte_stream_capacity)
add_test_exec (byte_stream_many_writes)

View File

@ -0,0 +1,113 @@
#include "byte_stream.hh"
#include "byte_stream_test_harness.hh"
#include <exception>
#include <iostream>
using namespace std;
int main() {
try {
{
ByteStreamTestHarness test{"overwrite", 2};
test.execute(Write{"cat"}.with_bytes_written(2));
test.execute(InputEnded{false});
test.execute(BufferEmpty{false});
test.execute(Eof{false});
test.execute(BytesRead{0});
test.execute(BytesWritten{2});
test.execute(RemainingCapacity{0});
test.execute(BufferSize{2});
test.execute(Peek{"ca"});
test.execute(Write{"t"}.with_bytes_written(0));
test.execute(InputEnded{false});
test.execute(BufferEmpty{false});
test.execute(Eof{false});
test.execute(BytesRead{0});
test.execute(BytesWritten{2});
test.execute(RemainingCapacity{0});
test.execute(BufferSize{2});
test.execute(Peek{"ca"});
}
{
ByteStreamTestHarness test{"overwrite-clear-overwrite", 2};
test.execute(Write{"cat"}.with_bytes_written(2));
test.execute(Pop{2});
test.execute(Write{"tac"}.with_bytes_written(2));
test.execute(InputEnded{false});
test.execute(BufferEmpty{false});
test.execute(Eof{false});
test.execute(BytesRead{2});
test.execute(BytesWritten{4});
test.execute(RemainingCapacity{0});
test.execute(BufferSize{2});
test.execute(Peek{"ta"});
}
{
ByteStreamTestHarness test{"overwrite-pop-overwrite", 2};
test.execute(Write{"cat"}.with_bytes_written(2));
test.execute(Pop{1});
test.execute(Write{"tac"}.with_bytes_written(1));
test.execute(InputEnded{false});
test.execute(BufferEmpty{false});
test.execute(Eof{false});
test.execute(BytesRead{1});
test.execute(BytesWritten{3});
test.execute(RemainingCapacity{0});
test.execute(BufferSize{2});
test.execute(Peek{"at"});
}
{
ByteStreamTestHarness test{"long-stream", 3};
test.execute(Write{"abcdef"}.with_bytes_written(3));
test.execute(Peek{"abc"});
test.execute(Pop{1});
for (unsigned int i = 0; i < 99997; i++) {
test.execute(RemainingCapacity{1});
test.execute(BufferSize{2});
test.execute(Write{"abc"}.with_bytes_written(1));
test.execute(RemainingCapacity{0});
test.execute(Peek{"bca"});
test.execute(Pop{1});
test.execute(RemainingCapacity{1});
test.execute(BufferSize{2});
test.execute(Write{"bca"}.with_bytes_written(1));
test.execute(RemainingCapacity{0});
test.execute(Peek{"cab"});
test.execute(Pop{1});
test.execute(RemainingCapacity{1});
test.execute(BufferSize{2});
test.execute(Write{"cab"}.with_bytes_written(1));
test.execute(RemainingCapacity{0});
test.execute(Peek{"abc"});
test.execute(Pop{1});
}
test.execute(EndInput{});
test.execute(Peek{"bc"});
test.execute(Pop{2});
test.execute(Eof{true});
}
} catch (const exception &e) {
cerr << "Exception: " << e.what() << endl;
return EXIT_FAILURE;
}
return EXIT_SUCCESS;
}

View File

@ -0,0 +1,40 @@
#include "byte_stream.hh"
#include "byte_stream_test_harness.hh"
#include <exception>
#include <iostream>
using namespace std;
int main() {
try {
{
ByteStreamTestHarness test{"construction", 15};
test.execute(InputEnded{false});
test.execute(BufferEmpty{true});
test.execute(Eof{false});
test.execute(BytesRead{0});
test.execute(BytesWritten{0});
test.execute(RemainingCapacity{15});
test.execute(BufferSize{0});
}
{
ByteStreamTestHarness test{"construction-end", 15};
test.execute(EndInput{});
test.execute(InputEnded{true});
test.execute(BufferEmpty{true});
test.execute(Eof{true});
test.execute(BytesRead{0});
test.execute(BytesWritten{0});
test.execute(RemainingCapacity{15});
test.execute(BufferSize{0});
}
} catch (const exception &e) {
cerr << "Exception: " << e.what() << endl;
return EXIT_FAILURE;
}
return EXIT_SUCCESS;
}

View File

@ -0,0 +1,46 @@
#include "byte_stream.hh"
#include "byte_stream_test_harness.hh"
#include "util.hh"
#include <exception>
#include <iostream>
using namespace std;
int main() {
try {
auto rd = get_random_generator();
const size_t NREPS = 1000;
const size_t MIN_WRITE = 10;
const size_t MAX_WRITE = 200;
const size_t CAPACITY = MAX_WRITE * NREPS;
{
ByteStreamTestHarness test{"many writes", CAPACITY};
size_t acc = 0;
for (size_t i = 0; i < NREPS; ++i) {
const size_t size = MIN_WRITE + (rd() % (MAX_WRITE - MIN_WRITE));
string d(size, 0);
generate(d.begin(), d.end(), [&] { return 'a' + (rd() % 26); });
test.execute(Write{d}.with_bytes_written(size));
acc += size;
test.execute(InputEnded{false});
test.execute(BufferEmpty{false});
test.execute(Eof{false});
test.execute(BytesRead{0});
test.execute(BytesWritten{acc});
test.execute(RemainingCapacity{CAPACITY - acc});
test.execute(BufferSize{acc});
}
}
} catch (const exception &e) {
cerr << "Exception: " << e.what() << endl;
return EXIT_FAILURE;
}
return EXIT_SUCCESS;
}

View File

@ -0,0 +1,134 @@
#include "byte_stream.hh"
#include "byte_stream_test_harness.hh"
#include <exception>
#include <iostream>
using namespace std;
int main() {
try {
{
ByteStreamTestHarness test{"write-end-pop", 15};
test.execute(Write{"cat"});
test.execute(InputEnded{false});
test.execute(BufferEmpty{false});
test.execute(Eof{false});
test.execute(BytesRead{0});
test.execute(BytesWritten{3});
test.execute(RemainingCapacity{12});
test.execute(BufferSize{3});
test.execute(Peek{"cat"});
test.execute(EndInput{});
test.execute(InputEnded{true});
test.execute(BufferEmpty{false});
test.execute(Eof{false});
test.execute(BytesRead{0});
test.execute(BytesWritten{3});
test.execute(RemainingCapacity{12});
test.execute(BufferSize{3});
test.execute(Peek{"cat"});
test.execute(Pop{3});
test.execute(InputEnded{true});
test.execute(BufferEmpty{true});
test.execute(Eof{true});
test.execute(BytesRead{3});
test.execute(BytesWritten{3});
test.execute(RemainingCapacity{15});
test.execute(BufferSize{0});
}
{
ByteStreamTestHarness test{"write-pop-end", 15};
test.execute(Write{"cat"});
test.execute(InputEnded{false});
test.execute(BufferEmpty{false});
test.execute(Eof{false});
test.execute(BytesRead{0});
test.execute(BytesWritten{3});
test.execute(RemainingCapacity{12});
test.execute(BufferSize{3});
test.execute(Peek{"cat"});
test.execute(Pop{3});
test.execute(InputEnded{false});
test.execute(BufferEmpty{true});
test.execute(Eof{false});
test.execute(BytesRead{3});
test.execute(BytesWritten{3});
test.execute(RemainingCapacity{15});
test.execute(BufferSize{0});
test.execute(EndInput{});
test.execute(InputEnded{true});
test.execute(BufferEmpty{true});
test.execute(Eof{true});
test.execute(BytesRead{3});
test.execute(BytesWritten{3});
test.execute(RemainingCapacity{15});
test.execute(BufferSize{0});
}
{
ByteStreamTestHarness test{"write-pop2-end", 15};
test.execute(Write{"cat"});
test.execute(InputEnded{false});
test.execute(BufferEmpty{false});
test.execute(Eof{false});
test.execute(BytesRead{0});
test.execute(BytesWritten{3});
test.execute(RemainingCapacity{12});
test.execute(BufferSize{3});
test.execute(Peek{"cat"});
test.execute(Pop{1});
test.execute(InputEnded{false});
test.execute(BufferEmpty{false});
test.execute(Eof{false});
test.execute(BytesRead{1});
test.execute(BytesWritten{3});
test.execute(RemainingCapacity{13});
test.execute(BufferSize{2});
test.execute(Peek{"at"});
test.execute(Pop{2});
test.execute(InputEnded{false});
test.execute(BufferEmpty{true});
test.execute(Eof{false});
test.execute(BytesRead{3});
test.execute(BytesWritten{3});
test.execute(RemainingCapacity{15});
test.execute(BufferSize{0});
test.execute(EndInput{});
test.execute(InputEnded{true});
test.execute(BufferEmpty{true});
test.execute(Eof{true});
test.execute(BytesRead{3});
test.execute(BytesWritten{3});
test.execute(RemainingCapacity{15});
test.execute(BufferSize{0});
}
} catch (const exception &e) {
cerr << "Exception: " << e.what() << endl;
return EXIT_FAILURE;
}
return EXIT_SUCCESS;
}

View File

@ -0,0 +1,184 @@
#include "byte_stream_test_harness.hh"
#include <memory>
#include <sstream>
using namespace std;
// ByteStreamTestStep
ByteStreamTestStep::operator std::string() const { return "ByteStreamTestStep"; }
void ByteStreamTestStep::execute(ByteStream &) const {}
ByteStreamTestStep::~ByteStreamTestStep() {}
// ByteStreamExpectationViolation
ByteStreamExpectationViolation::ByteStreamExpectationViolation(const std::string &msg) : std::runtime_error(msg) {}
template <typename T>
ByteStreamExpectationViolation ByteStreamExpectationViolation::property(const std::string &property_name,
const T &expected,
const T &actual) {
return ByteStreamExpectationViolation("The ByteStream should have had " + property_name + " equal to " +
to_string(expected) + " but instead it was " + to_string(actual));
}
// ByteStreamExpectation
ByteStreamExpectation::operator std::string() const { return "Expectation: " + description(); }
std::string ByteStreamExpectation::description() const { return "description missing"; }
void ByteStreamExpectation::execute(ByteStream &) const {}
ByteStreamExpectation::~ByteStreamExpectation() {}
// ByteStreamAction
ByteStreamAction::operator std::string() const { return " Action: " + description(); }
std::string ByteStreamAction::description() const { return "description missing"; }
void ByteStreamAction::execute(ByteStream &) const {}
ByteStreamAction::~ByteStreamAction() {}
ByteStreamTestHarness::ByteStreamTestHarness(const std::string &test_name, const size_t capacity)
: _test_name(test_name), _byte_stream(capacity) {
std::ostringstream ss;
ss << "Initialized with ("
<< "capacity=" << capacity << ")";
_steps_executed.emplace_back(ss.str());
}
void ByteStreamTestHarness::execute(const ByteStreamTestStep &step) {
try {
step.execute(_byte_stream);
_steps_executed.emplace_back(step);
} catch (const ByteStreamExpectationViolation &e) {
std::cerr << "Test Failure on expectation:\n\t" << std::string(step);
std::cerr << "\n\nFailure message:\n\t" << e.what();
std::cerr << "\n\nList of steps that executed successfully:";
for (const std::string &s : _steps_executed) {
std::cerr << "\n\t" << s;
}
std::cerr << std::endl << std::endl;
throw ByteStreamExpectationViolation("The test \"" + _test_name + "\" failed");
} catch (const exception &e) {
std::cerr << "Test Failure on expectation:\n\t" << std::string(step);
std::cerr << "\n\nException:\n\t" << e.what();
std::cerr << "\n\nList of steps that executed successfully:";
for (const std::string &s : _steps_executed) {
std::cerr << "\n\t" << s;
}
std::cerr << std::endl << std::endl;
throw ByteStreamExpectationViolation("The test \"" + _test_name +
"\" caused your implementation to throw an exception!");
}
}
// EndInput
std::string EndInput::description() const { return "end input"; }
void EndInput::execute(ByteStream &bs) const { bs.end_input(); }
// Write
Write::Write(const std::string &data) : _data(data) {}
Write &Write::with_bytes_written(const size_t bytes_written) {
_bytes_written = bytes_written;
return *this;
}
std::string Write::description() const { return "write \"" + _data + "\" to the stream"; }
void Write::execute(ByteStream &bs) const {
auto bytes_written = bs.write(_data);
if (_bytes_written and bytes_written != _bytes_written.value()) {
throw ByteStreamExpectationViolation::property("bytes_written", _bytes_written.value(), bytes_written);
}
}
// Pop
Pop::Pop(const size_t len) : _len(len) {}
std::string Pop::description() const { return "pop " + to_string(_len); }
void Pop::execute(ByteStream &bs) const { bs.pop_output(_len); }
// InputEnded
InputEnded::InputEnded(const bool input_ended) : _input_ended(input_ended) {}
std::string InputEnded::description() const { return "input_ended: " + to_string(_input_ended); }
void InputEnded::execute(ByteStream &bs) const {
auto input_ended = bs.input_ended();
if (input_ended != _input_ended) {
throw ByteStreamExpectationViolation::property("input_ended", _input_ended, input_ended);
}
}
// BufferEmpty
BufferEmpty::BufferEmpty(const bool buffer_empty) : _buffer_empty(buffer_empty) {}
std::string BufferEmpty::description() const { return "buffer_empty: " + to_string(_buffer_empty); }
void BufferEmpty::execute(ByteStream &bs) const {
auto buffer_empty = bs.buffer_empty();
if (buffer_empty != _buffer_empty) {
throw ByteStreamExpectationViolation::property("buffer_empty", _buffer_empty, buffer_empty);
}
}
// Eof
Eof::Eof(const bool eof) : _eof(eof) {}
std::string Eof::description() const { return "eof: " + to_string(_eof); }
void Eof::execute(ByteStream &bs) const {
auto eof = bs.eof();
if (eof != _eof) {
throw ByteStreamExpectationViolation::property("eof", _eof, eof);
}
}
// BufferSize
BufferSize::BufferSize(const size_t buffer_size) : _buffer_size(buffer_size) {}
std::string BufferSize::description() const { return "buffer_size: " + to_string(_buffer_size); }
void BufferSize::execute(ByteStream &bs) const {
auto buffer_size = bs.buffer_size();
if (buffer_size != _buffer_size) {
throw ByteStreamExpectationViolation::property("buffer_size", _buffer_size, buffer_size);
}
}
// RemainingCapacity
RemainingCapacity::RemainingCapacity(const size_t remaining_capacity) : _remaining_capacity(remaining_capacity) {}
std::string RemainingCapacity::description() const { return "remaining_capacity: " + to_string(_remaining_capacity); }
void RemainingCapacity::execute(ByteStream &bs) const {
auto remaining_capacity = bs.remaining_capacity();
if (remaining_capacity != _remaining_capacity) {
throw ByteStreamExpectationViolation::property("remaining_capacity", _remaining_capacity, remaining_capacity);
}
}
// BytesWritten
BytesWritten::BytesWritten(const size_t bytes_written) : _bytes_written(bytes_written) {}
std::string BytesWritten::description() const { return "bytes_written: " + to_string(_bytes_written); }
void BytesWritten::execute(ByteStream &bs) const {
auto bytes_written = bs.bytes_written();
if (bytes_written != _bytes_written) {
throw ByteStreamExpectationViolation::property("bytes_written", _bytes_written, bytes_written);
}
}
// BytesRead
BytesRead::BytesRead(const size_t bytes_read) : _bytes_read(bytes_read) {}
std::string BytesRead::description() const { return "bytes_read: " + to_string(_bytes_read); }
void BytesRead::execute(ByteStream &bs) const {
auto bytes_read = bs.bytes_read();
if (bytes_read != _bytes_read) {
throw ByteStreamExpectationViolation::property("bytes_read", _bytes_read, bytes_read);
}
}
// Peek
Peek::Peek(const std::string &output) : _output(output) {}
std::string Peek::description() const { return "\"" + _output + "\" at the front of the stream"; }
void Peek::execute(ByteStream &bs) const {
auto output = bs.peek_output(_output.size());
if (output != _output) {
throw ByteStreamExpectationViolation("Expected \"" + _output + "\" at the front of the stream, but found \"" +
output + "\"");
}
}

View File

@ -0,0 +1,142 @@
#ifndef SPONGE_BYTE_STREAM_HARNESS_HH
#define SPONGE_BYTE_STREAM_HARNESS_HH
#include "byte_stream.hh"
#include "util.hh"
#include <exception>
#include <initializer_list>
#include <iostream>
#include <memory>
#include <optional>
#include <string>
struct ByteStreamTestStep {
virtual operator std::string() const;
virtual void execute(ByteStream &) const;
virtual ~ByteStreamTestStep();
};
class ByteStreamExpectationViolation : public std::runtime_error {
public:
ByteStreamExpectationViolation(const std::string &msg);
template <typename T>
static ByteStreamExpectationViolation property(const std::string &property_name,
const T &expected,
const T &actual);
};
struct ByteStreamExpectation : public ByteStreamTestStep {
operator std::string() const override;
virtual std::string description() const;
virtual void execute(ByteStream &) const override;
virtual ~ByteStreamExpectation() override;
};
struct ByteStreamAction : public ByteStreamTestStep {
operator std::string() const override;
virtual std::string description() const;
virtual void execute(ByteStream &) const override;
virtual ~ByteStreamAction() override;
};
struct EndInput : public ByteStreamAction {
std::string description() const override;
void execute(ByteStream &) const override;
};
struct Write : public ByteStreamAction {
std::string _data;
std::optional<size_t> _bytes_written{};
Write(const std::string &data);
Write &with_bytes_written(const size_t bytes_written);
std::string description() const override;
void execute(ByteStream &) const override;
};
struct Pop : public ByteStreamAction {
size_t _len;
Pop(const size_t len);
std::string description() const override;
void execute(ByteStream &) const override;
};
struct InputEnded : public ByteStreamExpectation {
bool _input_ended;
InputEnded(const bool input_ended);
std::string description() const override;
void execute(ByteStream &) const override;
};
struct BufferEmpty : public ByteStreamExpectation {
bool _buffer_empty;
BufferEmpty(const bool buffer_empty);
std::string description() const override;
void execute(ByteStream &) const override;
};
struct Eof : public ByteStreamExpectation {
bool _eof;
Eof(const bool eof);
std::string description() const override;
void execute(ByteStream &) const override;
};
struct BufferSize : public ByteStreamExpectation {
size_t _buffer_size;
BufferSize(const size_t buffer_size);
std::string description() const override;
void execute(ByteStream &) const override;
};
struct BytesWritten : public ByteStreamExpectation {
size_t _bytes_written;
BytesWritten(const size_t bytes_written);
std::string description() const override;
void execute(ByteStream &) const override;
};
struct BytesRead : public ByteStreamExpectation {
size_t _bytes_read;
BytesRead(const size_t bytes_read);
std::string description() const override;
void execute(ByteStream &) const override;
};
struct RemainingCapacity : public ByteStreamExpectation {
size_t _remaining_capacity;
RemainingCapacity(const size_t remaining_capacity);
std::string description() const override;
void execute(ByteStream &) const override;
};
struct Peek : public ByteStreamExpectation {
std::string _output;
Peek(const std::string &output);
std::string description() const override;
void execute(ByteStream &) const override;
};
class ByteStreamTestHarness {
std::string _test_name;
ByteStream _byte_stream;
std::vector<std::string> _steps_executed{};
public:
ByteStreamTestHarness(const std::string &test_name, const size_t capacity);
void execute(const ByteStreamTestStep &step);
};
#endif // SPONGE_BYTE_STREAM_HARNESS_HH

View File

@ -0,0 +1,133 @@
#include "byte_stream.hh"
#include "byte_stream_test_harness.hh"
#include <exception>
#include <iostream>
using namespace std;
int main() {
try {
{
ByteStreamTestHarness test{"write-write-end-pop-pop", 15};
test.execute(Write{"cat"});
test.execute(InputEnded{false});
test.execute(BufferEmpty{false});
test.execute(Eof{false});
test.execute(BytesRead{0});
test.execute(BytesWritten{3});
test.execute(RemainingCapacity{12});
test.execute(BufferSize{3});
test.execute(Peek{"cat"});
test.execute(Write{"tac"});
test.execute(InputEnded{false});
test.execute(BufferEmpty{false});
test.execute(Eof{false});
test.execute(BytesRead{0});
test.execute(BytesWritten{6});
test.execute(RemainingCapacity{9});
test.execute(BufferSize{6});
test.execute(Peek{"cattac"});
test.execute(EndInput{});
test.execute(InputEnded{true});
test.execute(BufferEmpty{false});
test.execute(Eof{false});
test.execute(BytesRead{0});
test.execute(BytesWritten{6});
test.execute(RemainingCapacity{9});
test.execute(BufferSize{6});
test.execute(Peek{"cattac"});
test.execute(Pop{2});
test.execute(InputEnded{true});
test.execute(BufferEmpty{false});
test.execute(Eof{false});
test.execute(BytesRead{2});
test.execute(BytesWritten{6});
test.execute(RemainingCapacity{11});
test.execute(BufferSize{4});
test.execute(Peek{"ttac"});
test.execute(Pop{4});
test.execute(InputEnded{true});
test.execute(BufferEmpty{true});
test.execute(Eof{true});
test.execute(BytesRead{6});
test.execute(BytesWritten{6});
test.execute(RemainingCapacity{15});
test.execute(BufferSize{0});
}
{
ByteStreamTestHarness test{"write-pop-write-end-pop", 15};
test.execute(Write{"cat"});
test.execute(InputEnded{false});
test.execute(BufferEmpty{false});
test.execute(Eof{false});
test.execute(BytesRead{0});
test.execute(BytesWritten{3});
test.execute(RemainingCapacity{12});
test.execute(BufferSize{3});
test.execute(Peek{"cat"});
test.execute(Pop{2});
test.execute(InputEnded{false});
test.execute(BufferEmpty{false});
test.execute(Eof{false});
test.execute(BytesRead{2});
test.execute(BytesWritten{3});
test.execute(RemainingCapacity{14});
test.execute(BufferSize{1});
test.execute(Peek{"t"});
test.execute(Write{"tac"});
test.execute(InputEnded{false});
test.execute(BufferEmpty{false});
test.execute(Eof{false});
test.execute(BytesRead{2});
test.execute(BytesWritten{6});
test.execute(RemainingCapacity{11});
test.execute(BufferSize{4});
test.execute(Peek{"ttac"});
test.execute(EndInput{});
test.execute(InputEnded{true});
test.execute(BufferEmpty{false});
test.execute(Eof{false});
test.execute(BytesRead{2});
test.execute(BytesWritten{6});
test.execute(RemainingCapacity{11});
test.execute(BufferSize{4});
test.execute(Peek{"ttac"});
test.execute(Pop{4});
test.execute(InputEnded{true});
test.execute(BufferEmpty{true});
test.execute(Eof{true});
test.execute(BytesRead{6});
test.execute(BytesWritten{6});
test.execute(RemainingCapacity{15});
test.execute(BufferSize{0});
}
} catch (const exception &e) {
cerr << "Exception: " << e.what() << endl;
return EXIT_FAILURE;
}
return EXIT_SUCCESS;
}

18
tests/test_err_if.hh Normal file
View File

@ -0,0 +1,18 @@
#ifndef SPONGE_TESTS_TEST_ERR_IF_HH
#define SPONGE_TESTS_TEST_ERR_IF_HH
#include <stdexcept>
#include <string>
static int err_num = 1;
#define test_err_if(c, s) _test_err_if(c, s, __LINE__)
static void _test_err_if(const bool err_condition, const std::string &err_string, const int lineno) {
if (err_condition) {
throw std::runtime_error(err_string + " (at line " + std::to_string(lineno) + ")");
}
++err_num;
}
#endif // SPONGE_TESTS_TEST_ERR_IF_HH

28
tests/test_should_be.hh Normal file
View File

@ -0,0 +1,28 @@
#ifndef SPONGE_TESTS_TEST_SHOULD_BE_HH
#define SPONGE_TESTS_TEST_SHOULD_BE_HH
#include "string_conversions.hh"
#include <optional>
#include <sstream>
#include <stdexcept>
#include <string>
#define test_should_be(act, exp) _test_should_be(act, exp, #act, #exp, __LINE__)
template <typename T>
static void _test_should_be(const T &actual,
const T &expected,
const char *actual_s,
const char *expected_s,
const int lineno) {
if (actual != expected) {
std::ostringstream ss;
ss << "`" << actual_s << "` should have been `" << expected_s << "`, but the former is\n\t" << to_string(actual)
<< "\nand the latter is\n\t" << to_string(expected) << " (difference of " << expected - actual << ")\n"
<< " (at line " << lineno << ")\n";
throw std::runtime_error(ss.str());
}
}
#endif // SPONGE_TESTS_TEST_SHOULD_BE_HH

10
tests/webget_t.sh Executable file
View File

@ -0,0 +1,10 @@
#!/bin/bash
WEB_HASH=`./apps/webget cs144.keithw.org /nph-hasher/xyzzy | tee /dev/stderr | tail -n 1`
CORRECT_HASH="7SmXqWkrLKzVBCEalbSPqBcvs11Pw263K7x4Wv3JckI"
if [ "${WEB_HASH}" != "${CORRECT_HASH}" ]; then
echo ERROR: webget returned output that did not match the test\'s expectations
exit 1
fi
exit 0

20
writeups/lab0.md Normal file
View File

@ -0,0 +1,20 @@
Lab 0 Writeup
=============
My name: [your name here]
My SUNet ID: [your sunetid here]
I collaborated with: [list sunetids here]
This lab took me about [n] hours to do. I [did/did not] attend the lab session.
My secret code from section 2.1 was: [code here]
- Optional: I had unexpected difficulty with: [describe]
- Optional: I think you could make this lab better by: [describe]
- Optional: I was surprised by: [describe]
- Optional: I'm not sure about: [describe]