CS144 Lab checkpoint 0
This commit is contained in:
commit
898f0c05bd
26
.clang-format
Normal file
26
.clang-format
Normal 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
2
.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
/build
|
||||
/.ccls-cache
|
||||
27
CMakeLists.txt
Normal file
27
CMakeLists.txt
Normal 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
73
README.md
Normal 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
1
apps/CMakeLists.txt
Normal file
@ -0,0 +1 @@
|
||||
add_sponge_exec (webget)
|
||||
51
apps/webget.cc
Normal file
51
apps/webget.cc
Normal 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
1
compile_commands.json
Symbolic link
@ -0,0 +1 @@
|
||||
build/compile_commands.json
|
||||
3
doctests/CMakeLists.txt
Normal file
3
doctests/CMakeLists.txt
Normal 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
21
doctests/address_dt.cc
Normal 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;
|
||||
}
|
||||
1
doctests/address_example_1.cc
Normal file
1
doctests/address_example_1.cc
Normal file
@ -0,0 +1 @@
|
||||
const Address google_webserver("www.google.com", "https");
|
||||
1
doctests/address_example_2.cc
Normal file
1
doctests/address_example_2.cc
Normal file
@ -0,0 +1 @@
|
||||
const Address a_dns_server("18.71.0.151", 53);
|
||||
1
doctests/address_example_3.cc
Normal file
1
doctests/address_example_3.cc
Normal file
@ -0,0 +1 @@
|
||||
const uint32_t a_dns_server_numeric = a_dns_server.ipv4_numeric();
|
||||
15
doctests/parser_dt.cc
Normal file
15
doctests/parser_dt.cc
Normal 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;
|
||||
}
|
||||
32
doctests/parser_example.cc
Normal file
32
doctests/parser_example.cc
Normal 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
26
doctests/socket_dt.cc
Normal 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;
|
||||
}
|
||||
20
doctests/socket_example_1.cc
Normal file
20
doctests/socket_example_1.cc
Normal 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");
|
||||
}
|
||||
26
doctests/socket_example_2.cc
Normal file
26
doctests/socket_example_2.cc
Normal 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");
|
||||
}
|
||||
14
doctests/socket_example_3.cc
Normal file
14
doctests/socket_example_3.cc
Normal 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
48
etc/Doxyfile.in
Normal 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
6
etc/build_defs.cmake
Normal 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
16
etc/build_type.cmake
Normal 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
20
etc/cflags.cmake
Normal 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
23
etc/clang_format.cmake
Normal 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
33
etc/clang_tidy.cmake
Normal 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
18
etc/cppcheck.cmake
Normal 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)
|
||||
35489
etc/cppreference-doxygen-web.tag.xml
Normal file
35489
etc/cppreference-doxygen-web.tag.xml
Normal file
File diff suppressed because it is too large
Load Diff
12
etc/doxygen.cmake
Normal file
12
etc/doxygen.cmake
Normal 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 ()
|
||||
16735
etc/linux-man-doxygen-web.tag.xml
Normal file
16735
etc/linux-man-doxygen-web.tag.xml
Normal file
File diff suppressed because it is too large
Load Diff
33
etc/rfc-doxygen-web.tag.xml
Normal file
33
etc/rfc-doxygen-web.tag.xml
Normal 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
11
etc/sponge_doxygen.css
Normal 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
BIN
etc/sponge_small.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 18 KiB |
238
etc/tests.cmake
Normal file
238
etc/tests.cmake
Normal 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
1
etc/tunconfig
Normal file
@ -0,0 +1 @@
|
||||
TUN_IP_PREFIX=169.254
|
||||
2
libsponge/CMakeLists.txt
Normal file
2
libsponge/CMakeLists.txt
Normal 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
53
libsponge/byte_stream.cc
Normal 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
85
libsponge/byte_stream.hh
Normal 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
134
libsponge/util/address.cc
Normal 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
85
libsponge/util/address.hh
Normal 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
104
libsponge/util/buffer.cc
Normal 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
130
libsponge/util/buffer.hh
Normal 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
145
libsponge/util/eventloop.cc
Normal 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;
|
||||
}
|
||||
76
libsponge/util/eventloop.hh
Normal file
76
libsponge/util/eventloop.hh
Normal 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
|
||||
110
libsponge/util/file_descriptor.cc
Normal file
110
libsponge/util/file_descriptor.cc
Normal 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));
|
||||
}
|
||||
117
libsponge/util/file_descriptor.hh
Normal file
117
libsponge/util/file_descriptor.hh
Normal 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
72
libsponge/util/parser.cc
Normal 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
79
libsponge/util/parser.hh
Normal 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
168
libsponge/util/socket.cc
Normal 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
124
libsponge/util/socket.hh
Normal 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
36
libsponge/util/tun.cc
Normal 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
29
libsponge/util/tun.hh
Normal 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
144
libsponge/util/util.cc
Normal 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
72
libsponge/util/util.hh
Normal 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
13
tests/CMakeLists.txt
Normal 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)
|
||||
113
tests/byte_stream_capacity.cc
Normal file
113
tests/byte_stream_capacity.cc
Normal 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;
|
||||
}
|
||||
40
tests/byte_stream_construction.cc
Normal file
40
tests/byte_stream_construction.cc
Normal 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;
|
||||
}
|
||||
46
tests/byte_stream_many_writes.cc
Normal file
46
tests/byte_stream_many_writes.cc
Normal 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;
|
||||
}
|
||||
134
tests/byte_stream_one_write.cc
Normal file
134
tests/byte_stream_one_write.cc
Normal 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;
|
||||
}
|
||||
184
tests/byte_stream_test_harness.cc
Normal file
184
tests/byte_stream_test_harness.cc
Normal 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 + "\"");
|
||||
}
|
||||
}
|
||||
142
tests/byte_stream_test_harness.hh
Normal file
142
tests/byte_stream_test_harness.hh
Normal 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
|
||||
133
tests/byte_stream_two_writes.cc
Normal file
133
tests/byte_stream_two_writes.cc
Normal 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
18
tests/test_err_if.hh
Normal 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
28
tests/test_should_be.hh
Normal 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
10
tests/webget_t.sh
Executable 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
20
writeups/lab0.md
Normal 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]
|
||||
Loading…
Reference in New Issue
Block a user