diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..d9a3f85 --- /dev/null +++ b/.gitignore @@ -0,0 +1,69 @@ +# Prerequisites +*.d + +# Object files +*.o +*.ko +*.obj +*.elf + +# Linker output +*.ilk +*.map +*.exp + +# Precompiled headers +*.gch +*.pch + +# Libraries +*.a +*.lib +*.la +*.lo + +# Shared objects +*.so +*.dll +*.so.* +*.dylib + +# Executables +*.exe +*.com +*.app + +# Debug files +*.dSYM/ +*.su +*.idb +*.pdb + +# Vi/Vim +*.vim + +# Emacs +*~ +\#*\# +.\#* + +# VS Code +.vscode/ + +# vi +[._]*.s[a-v][a-z] +[._]*.sw[a-p] +[._]s[a-rt-v][a-z] +[._]ss[a-gi-z] +[._]sw[a-p] + +# macOS/Windows +*.DS_Store +._* +Thumbs.db +*.lnk + +# Files specific to this project +student/results.txt +student/tests + diff --git a/.travis.yml b/.travis.yml index fadcc52..8375967 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,6 +1,18 @@ -language: c +language: python + +python: +- '3.6' + +services: +- mongodb +- docker before_install: - sudo apt-get install libcunit1 libcunit1-dev +- pip3 install git+https://github.com/Maxmawt/INGInious # Until UCL-INGI/INGInious has fixed his part ;-) +- docker pull ingi/inginious-c-base +- docker pull ingi/inginious-c-default +- docker pull ingi/inginious-c-cpp +- docker pull ingi/inginious-c-pyjavacpp script: ci/run_ci diff --git a/ci/courses/LSINF1252 b/ci/courses/LSINF1252 new file mode 100755 index 0000000..f74eea6 --- /dev/null +++ b/ci/courses/LSINF1252 @@ -0,0 +1,25 @@ +#!/bin/bash + +# Assume we're in the correct directory + +case $1 in + init) + echo "Git clone" + git clone https://github.com/UCL-INGI/LSINF1252.git + ;; + before_autotest) + echo "Before autotest" + # Patch the advanced_queue task's run file + sed -i '/ret = run/i \ \ \ \ subprocess.run(shlex.split("sed -i '"'"'s|\\$(sort \\$(wildcard \\*\\.c))|$(wildcard *.c)|'"'"' student/Makefile"))' advanced_queue/run && echo -e 'Patched advanced_queue' +# cp $2/LSINF1252_advanced_queue_run ./advanced_queue/run + # Patch the three tasks with test submissions to remove the + # "no-use-fifty" option + for i in advanced_queue order_relation_linked_list strcpy; + do + sed -i 's/--no-use-fifty//' $i/run && echo -e 'Patched' $i + done + ;; +esac + +# That's all + diff --git a/ci/run_ci b/ci/run_ci index 0df5267..4482025 100755 --- a/ci/run_ci +++ b/ci/run_ci @@ -1,7 +1,8 @@ #!/bin/bash -declare -a tests=("test-simple-success" "test-simple-fail") -cd "$(dirname "$0")" +_term() { + kill -TERM "$child" 2>/dev/null +} exec_test() { mkdir env/ @@ -11,22 +12,24 @@ exec_test() { pushd env make - echo "### $1: executing ..." - ./tests + echo -e "##### $1: executing ...\n" + ./tests & + child=$! + wait "$child" if [ -f ./results.txt ]; then cmp --silent results.txt expected_results.txt if [ $? -eq 0 ]; then - echo '###' $1 ': OK' + echo -e "\n#####" $1 ": OK" pushd rm -rf env return 1 else - echo '###' $1 ': results.txt diverges from the expected output:' + echo -e "\n#####" $1 ": results.txt diverges from the expected output:" diff results.txt expected_results.txt fi else - echo '###' $1 ': CTester did not create a results.txt file' + echo -e "\n#####" $1 ": CTester did not create a results.txt file" fi popd @@ -34,23 +37,66 @@ exec_test() { return 0 } +cd "$(dirname "$0")" + +trap _term SIGTERM + +if [ "$#" -eq "1" ]; then + # Execute only the specified test + echo -e "##### Executing test" $1 + exec_test $1 + if [ $? -eq '1' ]; then + echo -e "##### Successful test" + exit 0 + else + exit 1 + fi +fi + +declare -a tests=() +while IFS= read -r -d $'\0'; do + tests+=("$REPLY") +done < <(find . ! -path "." -type d ! -name "courses" -print0) -echo '### Executing tests' +if ((${#tests[@]} == 0)); then + echo -e "No tests found!" >&2 + exit 1 +fi + +echo -e "Found following tests: " ${tests[@]} + +echo -e "##### Executing tests" tests_ok=0 for i in "${tests[@]}" do - echo '##########' $i + echo -e "\n####################" $i exec_test $i if [ $? -eq '1' ]; then tests_ok=$((tests_ok+1)) fi - + echo -e "####################" done -echo '###' $tests_ok '/' ${#tests[@]} 'tests succeeded' +echo -e "\n####################" +echo -e "### End of tests ###" +echo -e "####################" +echo -e "#####" $tests_ok "/" ${#tests[@]} "tests succeeded" if ((tests_ok != ${#tests[@]})); then - exit 1 + exitval=1 else - exit 0 + exitval=0 +fi + + +echo -e "\n####################" +echo -e "## Testing courses #" +echo -e "####################" + +./run_course + +if (($? == 0)); then + exit $exitval +else + exit 1 fi diff --git a/ci/run_course b/ci/run_course new file mode 100755 index 0000000..5922773 --- /dev/null +++ b/ci/run_course @@ -0,0 +1,232 @@ +#!/bin/bash + +# ADDING MORE COURSE TO THE TESTS +# Simply put a script with the course name inside the ci/courses/ folder. +# Note that after running this script, this test script expects the course +# to have the same name as the folder in which it is located; you should +# change the folder name to match the course name if it is not the case. +# Also, it is recommanded that no task matches the course name ;-) +# If a task uses a full custom version of CTester, put a file named +# no_ctester_test +# inside the task. This test script will not modify anything inside of this task. +# This test script updates the CTester files in the following way: +# - If the full CTester is present (run.py, student/Makefile, student/CTester), +# removes all to let the task use the common one, and updates the run file. +# - If the CTester folder is present, but one of the other two is missing, +# updates the run file. +# - If no CTester folder is present, then if the run file contains allusions +# to CTester, it will be replaced. +# The script will be called at various time in the process: +# - init: at the start. Should download/install the course, perform some modifs. +# It happens moments before we copy the course, create test folders, and call +# the first inginious-autotest. +# - before_autotest: This is just after we've replaced CTester, and before +# we call inginious-autotest for the second time. Perform there any patching. +# Note: we're located in the course folder. The second parameter is the "ci" +# folder. Note: you can also use it to restore some tasks, if this script +# messed up. + +_term() { + kill -TERM "$child" 2>/dev/null +} + +prepare_course() { + if [ "$2" == "mkdir" ]; then + domkdir='-true' + else + domkdir='-false' + fi + test -f $1 && echo "WARNING: found a task with the same name as the course" >&2 + mkdir $1 + find . -type d ! -path "*/$1/*" -exec test -f {}/task.yaml \; \ + -print \ + \( \( $domkdir -exec mkdir {}/test \; \) -o -true \) \ + -exec cp -r {} $1/$(basename {}) \; + find . -name "course.yml" -o -name "course.yaml" -print -exec cp {} $1/{} \; -quit +} + +exec_course() { + # $1: name of course, should match the folder name + mkdir env + pushd env + # We're in CTester/ci/env + # Call course script, so that it downloads itself + # Note: this script should also tweak the tasks that use a full custom version of CTester, + # by adding a ".no_ctester_test" file in the top directory + echo -e "Initializing folder using custom script" + ../courses/$1 init + echo -e "Done initializing" + mv $1 _$1 + pushd _$1 + # We're in CTester/ci/env/_ + + # Test the course in its default configuration: + # if it fails, skip the new CTester, as we don't want to check errors + # that don't come from us. + echo -e "Preparing course" + prepare_course $1 mkdir + + echo -e "First autotest, testing if course is OK" + inginious-autotest . $1 -l + if [ $? -ne '0' ]; then + echo -e "Course is already failing; skipping it" + popd + popd + rm -rf env + retval=1 + return $retval + fi + + echo -e "Preparing course for second run" + # Cleanup + rm -r $1 + prepare_course $1 nomkdir + + echo -e "Creating local copy of CTester" + mkdir -p CTester/student + cp -r ../../../run ../../../run.py CTester/ + cp -r ../../../student/Makefile ../../../student/CTester CTester/student/ + + echo -e "Patching local copy of CTester" + # Patch the Makefile to be less strict + sed -i 's/-Wshadow// ; s/-Wextra//' CTester/student/Makefile + # Remove the "sort" as it causes bugs in the current version. NOTE: moved to custom + # Patch the run file to be less strict too. NOTE: moved to custom + + pushd $1 + # We're in CTester/ci/env/_/ + # CTester's root is at ../../../../ + # Our patched copy is at ../CTester/ + + echo -e "Replacing CTester in each valid task" + # Remove $common/CTester and $common/student/CTester, as we don't need them. + rm -rf \$common/CTester + rm -rf \$common/student/CTester + # Find and replace all versions of CTester in each task + # Only replace CTester if it's fully there. + # If a task only has two of either student/CTester/, student/Makefile, + # or run.py, do not replace (by deleting), as CTester allows to provide + # custom versions of these files; + # the new copy of CTester will be provided upon run. + + # Tasks with none of the three use the default CTester, or no CTester at all: + # replace only the run file. + # Tasks with only one or two of the three probably use CTester, + # with custom modifications: replace only the run file + # If there is a CTester folder, then it is surely CTester + find . -name "student" -type d \ + ! -path "*/\$common/*" \ + -exec test ! -f {}/../no_ctester_test \; \ + -exec test -d {}/CTester \; \ + \( \( -exec cp ../CTester/run {}/../run \; -printf "%p:\t\tUpdated folder, case 1\n" \) \ + -o -printf "%p:\t\tFAILED updating folder, case 1\n" \) + # If there is no CTester folder, then the presence of a run file + # or a Makefile doesn't guarantee that it uses CTester. + # So we check the content of the run file + find . -name "run" \ + \( -exec grep -iq "CTester" {} \; -o -exec grep -iq "BAN_FUNC" {} \; \) \ + -execdir test ! -f no_ctester_test \; \ + \( \( -exec cp ../CTester/run {} \; -printf "%p:\t\tUpdated folder, case 2\n" \) \ + -o -printf "%p:\t\tFAILED updating folder, case 2\n" \) + + # Tasks with all three use a local CTester: we can test this by overwriting all files with the new ones + find . -name "student" \ + ! -path "*/\$common/*" \ + -exec test -f {}/Makefile \; \ + -exec test -d {}/CTester \; \ + -exec test ! -f {}/../no_ctester_test \; \ + \( \( -exec rm -f {}/../run.py {}/Makefile \; \ + -exec rm -r {}/CTester \; \ + -printf "%p:\t\tUpdated folder, case 3\n" \) \ + -o -printf "%p:\t\tFAILED updating folder, case 3\n" \) + # No need to copy the run file, it has been taken care of before + # It also remove the CTester folder, so we completely replace it. + # Note: for backward compatibility reasons, we don't check if the task + # contains run.py; the following lines were removed. +# -exec test -f {}/../run.py \; \ +# -printf "%p: OK1 " \ + + # Put CTester into common + mkdir -p \$common/CTester/student/ || true + cp ../CTester/run ../CTester/run.py \$common/CTester/ + cp ../CTester/student/Makefile \$common/CTester/student/ + cp -r ../CTester/student/CTester \$common/CTester/student/ + + echo -e "Calling custom script before autotest, and after replacing CTester" + ../../../courses/$1 before_autotest + + popd + # We're in CTester/ci/env/_ + + echo -e "Second run of inginious-autotest" + sleep 5 + inginious-autotest . $1 -l + if [ $? -eq 0 ]; then + echo -e "\n#####" $1 ": OK" + retval=1 + else + echo -e "\n#####" $1 ": course test failed" + retval=0 + fi + + popd + popd + # We're in CTester/ci + rm -rf env + return $retval +} + +cd "$(dirname "$0")" + +rm -rf env/ + +trap _term SIGTERM + +if [ "$#" -eq "1" ]; then + echo -e "##### Executing test of course" $1 + exec_course $1 + if [ $? -eq '1' ]; then + echo -e "##### Successfully tested course" + exit 0 + else + echo -e "##### Failed testing course" + exit 1 + fi +fi + +shopt -s nullglob +pushd courses +declare -a courses=(*) +popd +shopt -u nullglob + +if ((${#courses[@]} == 0)); then + echo -e "No courses found! Skip testing" + exit 0 +fi + +echo -e "Found following courses: " ${courses[@]} + +echo -e "##### Executing tests of courses" +courses_ok=0 + +for i in "${courses[@]}" +do + echo -e "\n####################" $i + exec_course $i + if [ $? -eq '1' ]; then + courses_ok=$((courses_ok+1)) + fi + echo -e "####################" +done + +echo -e "\n################################" +echo -e "### End of tests for courses ###" +echo -e "################################" +echo -e "#####" $courses_ok "/" ${#courses[@]} "courses succeeded" +if ((courses_ok != ${#courses[@]})); then + exit 1 +else + exit 0 +fi + diff --git a/ci/test_network_dns/expected_results.txt b/ci/test_network_dns/expected_results.txt new file mode 100644 index 0000000..d05ee31 --- /dev/null +++ b/ci/test_network_dns/expected_results.txt @@ -0,0 +1,3 @@ +substitute_gai#SUCCESS#Test if the substitution functions etc work correctly#1# +checked_freeaddrinfo#SUCCESS#Tests the checked freeaddrinfo#1# +simple_gai_fai#SUCCESS#Tests simple_gai and simple_fai#1# diff --git a/ci/test_network_dns/student_code.c b/ci/test_network_dns/student_code.c new file mode 100644 index 0000000..2a60980 --- /dev/null +++ b/ci/test_network_dns/student_code.c @@ -0,0 +1,215 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "student_code.h" + +/** + * This is the simple example from the BeeJ guide. + */ +void run_student_tests_wrapper_stats_1(struct f1_stats *stats) +{ + memset(stats, 0, sizeof(*stats)); + struct addrinfo hints, *rep, *rp; + memset(&hints, 0, sizeof(hints)); + hints.ai_family = AF_UNSPEC; + hints.ai_socktype = SOCK_DGRAM; + hints.ai_flags = 0; + hints.ai_protocol = 0; + int s = getaddrinfo("inginious.info.ucl.ac.be", "443", &hints, &rep); + if (s != 0) { + fprintf(stderr, "%s\n", gai_strerror(s)); + return; + } + stats->getaddrinfo_success = true; + int sfd = -1; + for (rp = rep; rp != NULL; rp = rp->ai_next) { + stats->nb_addr_tried++; + sfd = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol); + if (sfd == -1) { + perror("socket"); + continue; + } + stats->nb_connect++; + if (connect(sfd, rp->ai_addr, rp->ai_addrlen) == 0) + break; + perror("connect"); + close(sfd); + sfd = -1; + } + freeaddrinfo(rep); + if (sfd != -1) { + char coucou[] = "coucou"; + ssize_t nsent; + do { + nsent = send(sfd, coucou, sizeof(coucou), 0); + stats->nb_send++; + if (nsent != -1) { + stats->nb_bytes += nsent; + } else { + perror("send"); + } + if (nsent != sizeof(coucou)) { + break; + } + } while (nsent != -1 && stats->nb_bytes < 42); + } +} + +/* + * The following two codes are adapted from the Linux man pages + * for getaddrinfo + */ +void run_student_tests_wrapper_stats_2_client(struct stats2_client *stats) +{ + errno = 0; + memset(stats, 0, sizeof(*stats)); + struct addrinfo hints, *rep, *rp; + int sfd = -1, s; + ssize_t nread; + char buf[500]; + memset(&hints, 0, sizeof(hints)); + hints.ai_family = AF_INET; + hints.ai_socktype = SOCK_DGRAM; + hints.ai_protocol = 0; + hints.ai_flags = 0; + s = getaddrinfo(NULL, "1618", &hints, &rep); + stats->ngai = 1; + if (s == -1) { + fprintf(stderr, "%s\n", gai_strerror(s)); + return; + } + for (rp = rep; rp != NULL; rp = rp->ai_next) { + sfd = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol); + stats->nsocket++; + if (sfd == -1) { + perror("socket"); + continue; + } + if (connect(sfd, rp->ai_addr, rp->ai_addrlen) == 0) { + stats->nconnect++; + break; + } + perror("connect"); + stats->nconnect++; + close(sfd); + sfd = -1; + } + if (rp == NULL || sfd == -1) + return; + freeaddrinfo(rep); + char *words[3]; + words[0] = "coucou"; + words[1] = "salut"; + words[2] = "STOP"; + for (int i = 0; i < 3; i++) { + // Send the three messages + int len = strlen(words[i]) + 1; + // Let's use a struct msghdr to see if it works + struct msghdr msg; + msg.msg_name = NULL; + msg.msg_namelen = 0; + struct iovec msg_iov[1]; + msg_iov[0].iov_base = words[i]; + msg_iov[0].iov_len = len; + msg.msg_iov = msg_iov; + msg.msg_iovlen = 1; + msg.msg_control = NULL; + msg.msg_controllen = 0; + msg.msg_flags = 0; + ssize_t nsent = sendmsg(sfd, &msg, 0); + if (nsent == -1) + perror("send"); + stats->nsend++; + if (nsent != len) { + fprintf(stderr, "ACouldn't write\n"); + continue; + } + // Let's read the data again + memset(buf, 0, sizeof(buf)); + nread = recv(sfd, buf, 500, 0); + stats->nrecv++; + if (nread == -1) { + perror("Arecv"); + continue; + } + if (strcmp(buf, words[i]) != 0) { + fprintf(stderr, "AMessage don't match\n"); + continue; + } + } +} + +void run_student_tests_wrapper_stats_2_server(struct stats2_server *stats) +{ + errno = 0; + memset(stats, 0, sizeof(*stats)); + struct addrinfo hints, *rep, *rp; + int sfd = -1, s; + struct sockaddr_storage peer_addr; + socklen_t peer_addrlen; + ssize_t nread; + char buf[500]; + memset(&hints, 0, sizeof(hints)); + hints.ai_family = AF_INET; + hints.ai_socktype = SOCK_DGRAM; + hints.ai_protocol = 0; + hints.ai_flags = AI_PASSIVE; + s = getaddrinfo(NULL, "1618", &hints, &rep); + stats->ngai = 1; + if (s == -1) { + fprintf(stderr, "%s\n", gai_strerror(s)); + return; + } + for (rp = rep; rp != NULL; rp = rp->ai_next) { + sfd = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol); + stats->nsocket++; + if (sfd == -1) { + perror("Bsocket"); + continue; + } + if (bind(sfd, rp->ai_addr, rp->ai_addrlen) == 0) { + stats->nbind++; + break; + } + perror("Bbind"); + stats->nbind++; + close(sfd); + sfd = -1; + } + if (rp == NULL || sfd == -1) + return; + freeaddrinfo(rep); + for (int failures = 0; failures < 10;) { + memset(buf, 0, sizeof(buf)); + // Stops at the reception of "stop" + peer_addrlen = sizeof(peer_addr); + nread = recvfrom(sfd, buf, 500, 0, (struct sockaddr*) &peer_addr, &peer_addrlen); + stats->nrecvfrom++; + if (nread == -1) + perror("recvfrom"); + if (nread == -1) { + failures++; + continue; + } + // Let's skip the printing here + ssize_t nsent = sendto(sfd, buf, nread, 0, (struct sockaddr*) &peer_addr, peer_addrlen); + stats->nsendto++; + if (nsent != nread) { + failures++; + } + if (nsent == -1) { + perror("Bsent"); + } + if (strcmp(buf, "STOP") == 0) { + break; + } + } +} + diff --git a/ci/test_network_dns/student_code.h b/ci/test_network_dns/student_code.h new file mode 100644 index 0000000..342c2d1 --- /dev/null +++ b/ci/test_network_dns/student_code.h @@ -0,0 +1,31 @@ +#include +struct f1_stats { + bool getaddrinfo_success; + int nb_addr_tried; + int nb_connect; + int nb_send; + ssize_t nb_bytes; +}; +struct stats2_server { + int ngai; + int nsocket; + int nbind; + int nrecvfrom; + int nsendto; +}; +struct stats2_client { + int ngai; + int nsocket; + int nconnect; + int nsend; + int nrecv; +}; + +void run_student_tests_wrapper_stats_1(struct f1_stats *stats); + +void run_student_tests_wrapper_stats_2_client(struct stats2_client *stats); + +void run_student_tests_wrapper_stats_2_server(struct stats2_server *stats); + +// void run_student_tests_wrapper_stats_3(); + diff --git a/ci/test_network_dns/tests.c b/ci/test_network_dns/tests.c new file mode 100644 index 0000000..16a11ea --- /dev/null +++ b/ci/test_network_dns/tests.c @@ -0,0 +1,890 @@ +#include +#include +#include +#include +#include + +#include "CTester/CTester.h" +#include "student_code.h" + +int __real_getaddrinfo(const char *node, const char *service, const struct addrinfo *hints, struct addrinfo **res); +int __real_getnameinfo(const struct sockaddr *addr, socklen_t addrlen, char *host, socklen_t hostlen, char *serv, socklen_t servlen, int flags); +void __real_freeaddrinfo(struct addrinfo *res); +const char *__real_gai_strerror(int errcode); + +const char gai1_cname[] = "CGAI1"; +const char gai2_cname[] = "CGAI2"; +struct custom_gai1_stats_t { + int called; + const char *node; + const char *serv; + const struct addrinfo *hints; + struct addrinfo **res; +} custom_gai1_stats; +int custom_gai1(const char *node, const char *serv, const struct addrinfo *hints, struct addrinfo **res) +{ + custom_gai1_stats.called++; + custom_gai1_stats.node = node; + custom_gai1_stats.serv = serv; + custom_gai1_stats.hints = hints; + custom_gai1_stats.res = res; + *res = calloc(1, sizeof(struct addrinfo)); + (*res)->ai_family = AF_INET; + (*res)->ai_socktype = SOCK_DGRAM; + (*res)->ai_protocol = 0; + (*res)->ai_addrlen = 0; + (*res)->ai_addr = NULL; + (*res)->ai_canonname = NULL; + (*res)->ai_next = NULL; + if (node != NULL) { + return 0; + } else if (serv == NULL) { + return 1; + } else { + return 2; + } +} + +struct custom_gai2_stats_t { + int called; + const char *node; + const char *serv; + const struct addrinfo *hints; + struct addrinfo **res; +} custom_gai2_stats; +int custom_gai2(const char *node, const char *serv, const struct addrinfo *hints, struct addrinfo **res) +{ + custom_gai2_stats.called++; + custom_gai2_stats.node = node; + custom_gai2_stats.serv = serv; + custom_gai2_stats.hints = hints; + custom_gai2_stats.res = res; + return __real_getaddrinfo(node, serv, hints, res); +} + +struct custom_fai1_stats_t { + int called; + struct addrinfo *res; + struct addrinfo *no_free; +} custom_fai1_stats; +void custom_fai1(struct addrinfo *res) +{ + custom_fai1_stats.called++; + custom_fai1_stats.res = res; + if (custom_fai1_stats.no_free != res) { + free(res); + } +} +void custom_fai1_prevent_free(struct addrinfo *res) +{ + custom_fai1_stats.no_free = res; +} + +struct custom_fai2_stats_t { + int called; + struct addrinfo *res; +} custom_fai2_stats; +void custom_fai2(struct addrinfo *res) +{ + custom_fai2_stats.called++; + custom_fai2_stats.res = res; + __real_freeaddrinfo(res); +} + +struct custom_gni1_stats_t { + int called; + const struct sockaddr *addr; + socklen_t addrlen; + socklen_t hostlen; + socklen_t servlen; + int flags; + char *host; + char *serv; +} custom_gni1_stats; +int custom_gn1(const struct sockaddr *addr, socklen_t addrlen, char *host, socklen_t hostlen, char *serv, socklen_t servlen, int flags) +{ + custom_gni1_stats.called++; + custom_gni1_stats.addr = addr; + custom_gni1_stats.addrlen = addrlen; + custom_gni1_stats.host = host; + custom_gni1_stats.hostlen = hostlen; + custom_gni1_stats.serv = serv; + custom_gni1_stats.servlen = servlen; + custom_gni1_stats.flags = flags; + char *s1 = "Hello"; + char *s2 = "world"; + memcpy(host, s1, 6); + memcpy(serv, s2, 6); + return 0; +} + +struct custom_gni2_stats_t { + int called; + const struct sockaddr *addr; + socklen_t addrlen; + socklen_t hostlen; + socklen_t servlen; + int flags; + char *host; + char *serv; +} custom_gni2_stats; +int custom_gn2(const struct sockaddr *addr, socklen_t addrlen, char *host, socklen_t hostlen, char *serv, socklen_t servlen, int flags) +{ + custom_gni2_stats.called++; + custom_gni2_stats.addr = addr; + custom_gni2_stats.addrlen = addrlen; + custom_gni2_stats.host = host; + custom_gni2_stats.hostlen = hostlen; + custom_gni2_stats.serv = serv; + custom_gni2_stats.servlen = servlen; + custom_gni2_stats.flags = flags; + return __real_getnameinfo(addr, addrlen, host, hostlen, serv, servlen, flags); +} + +struct custom_fai_reporter_stats_t { + int called; +} custom_fair_stats; +void custom_checked_fai_reporter() +{ + custom_fair_stats.called++; +} + +struct custom_gse1_stats_t { + int called; + int code; +} custom_gse1_stats; +const char *custom_gai_strerror1(int code) +{ + custom_gse1_stats.called++; + custom_gse1_stats.code = code; + char *s1 = "GSE1"; + return s1; +} + +struct custom_gse2_stats_t { + int called; + int code; +} custom_gse2_stats; +const char *custom_gai_strerror2(int code) +{ + custom_gse2_stats.called++; + custom_gse2_stats.code = code; + char *s2 = "GSE2"; + return s2; +} + +/* +What to test: +- set_gai works, as well as none (from none) +- set_gais all, gai, fai, none (null) work +- set_gai and then reset it works +- set_gais and then reset both works +- set_gni works, both set and reset +- set_gai_strerror should work, as well as reset +- checked freeaddrinfo should work: activate, pass a correct, pass an incorrect, deactivate, pass an incorrect +- the reporter for the checked fai should also work, both set and reset. + */ +void test_gai_substitutes() +{ + set_test_metadata("substitute_gai", _("Test if the substitution functions etc work correctly"), 1); + reinit_all_monitored(); + reinit_all_stats(); + reset_gai_fai_gstr_gni_methods(); + memset(&custom_gai1_stats, 0, sizeof(custom_gai1_stats)); + memset(&custom_gai2_stats, 0, sizeof(custom_gai2_stats)); + memset(&custom_fai1_stats, 0, sizeof(custom_fai1_stats)); + memset(&custom_fai2_stats, 0, sizeof(custom_fai2_stats)); + memset(&custom_gni1_stats, 0, sizeof(custom_gni1_stats)); + memset(&custom_gni2_stats, 0, sizeof(custom_gni2_stats)); + memset(&custom_fair_stats, 0, sizeof(custom_fair_stats)); + memset(&custom_gse1_stats, 0, sizeof(custom_gse1_stats)); + memset(&custom_gse2_stats, 0, sizeof(custom_gse2_stats)); + set_check_freeaddrinfo(false); // Disable it for all tests + + int i1=-1, i2=-1, i3=-1, i4=-1, i5=-1, i6=-1, i7=-1, i8=-1, i9=-1; + struct addrinfo hints1, *res1, *res2, *res3, *res4, *res5, *res6, *res7, *res8, *res9; + memset(&hints1, 0, sizeof(hints1)); + hints1.ai_family = AF_INET; + hints1.ai_socktype = SOCK_DGRAM; + hints1.ai_flags = AI_NUMERICHOST | AI_NUMERICSERV; + char h1[]="0.0.0.1", h2[]="0.0.0.2", h3[]="0.0.0.3", h4[]="0.0.0.4"; + char h5[]="0.0.0.5", h6[]="0.0.0.6", h7[]="0.0.0.7", h8[]="0.0.0.8"; + (void)h8; + (void)res8; + (void)res9; + (void)i9; + monitored.getaddrinfo = monitored.freeaddrinfo = false; + SANDBOX_BEGIN; + i1 = getaddrinfo(h1, "80", &hints1, &res1); + SANDBOX_END; + CU_ASSERT_EQUAL(i1, 0); + CU_ASSERT_PTR_NOT_NULL(res1); + CU_ASSERT_EQUAL(stats.getaddrinfo.called, 0); // Not enabled yet + SANDBOX_BEGIN; + freeaddrinfo(res1); + SANDBOX_END; + CU_ASSERT_EQUAL(stats.freeaddrinfo.called, 0); // Not enabled yet + monitored.getaddrinfo = monitored.freeaddrinfo = true; + i1=-1; + res1 = NULL; + SANDBOX_BEGIN; + i1 = getaddrinfo(h2, "80", &hints1, &res1); + freeaddrinfo(res1); + SANDBOX_END; + CU_ASSERT_EQUAL(stats.getaddrinfo.called, 1); + CU_ASSERT_EQUAL(stats.getaddrinfo.last_params.res, &res1); + CU_ASSERT_EQUAL(stats.getaddrinfo.last_params.hints, &hints1); + CU_ASSERT_EQUAL(stats.getaddrinfo.last_params.node, h2); + CU_ASSERT_EQUAL(stats.freeaddrinfo.called, 1); + CU_ASSERT_EQUAL(stats.freeaddrinfo.last_param, res1); + // Now, let's set getaddrinfo to someone else + set_getaddrinfo_method(custom_gai2); // That's just a wrapper, compatible with __real_freeaddrinfo + SANDBOX_BEGIN; + i2 = getaddrinfo(h3, "80", &hints1, &res2); + freeaddrinfo(res2); + SANDBOX_END; + CU_ASSERT_EQUAL(stats.getaddrinfo.called, 2); + CU_ASSERT_EQUAL(stats.getaddrinfo.last_params.node, h3); + CU_ASSERT_EQUAL(stats.getaddrinfo.last_params.hints, &hints1); + CU_ASSERT_EQUAL(stats.getaddrinfo.last_params.res, &res2); + CU_ASSERT_EQUAL(stats.freeaddrinfo.called, 2); + CU_ASSERT_EQUAL(stats.freeaddrinfo.last_param, res2); + CU_ASSERT_EQUAL(custom_gai2_stats.called, 1); + CU_ASSERT_EQUAL(custom_gai2_stats.node, h3); + CU_ASSERT_EQUAL(custom_gai2_stats.res, &res2); + res2 = NULL; + CU_ASSERT_EQUAL(getaddrinfo(h3, "80", &hints1, &res2), 0); + CU_ASSERT_EQUAL(stats.getaddrinfo.called, 2); // No change + CU_ASSERT_EQUAL(custom_gai2_stats.called, 1); // Neither + set_getaddrinfo_method(NULL); + SANDBOX_BEGIN; + i3 = getaddrinfo(h4, "80", &hints1, &res3); + freeaddrinfo(res3); + SANDBOX_END; + CU_ASSERT_EQUAL(stats.getaddrinfo.called, 3); + CU_ASSERT_EQUAL(custom_gai2_stats.called, 1); + CU_ASSERT_EQUAL(custom_fai2_stats.called, 0); + CU_ASSERT_EQUAL(stats.freeaddrinfo.called, 3); + set_gai_methods(custom_gai1, custom_fai1); + SANDBOX_BEGIN; + i4 = getaddrinfo(h5, "80", &hints1, &res4); + freeaddrinfo(res4); + SANDBOX_END; + CU_ASSERT_EQUAL(i4, 0); + CU_ASSERT_EQUAL(stats.getaddrinfo.called, 4); + CU_ASSERT_EQUAL(custom_gai1_stats.called, 1); + CU_ASSERT_EQUAL(custom_gai1_stats.node, h5); + CU_ASSERT_EQUAL(stats.freeaddrinfo.called, 4); + CU_ASSERT_EQUAL(custom_fai1_stats.called, 1); + CU_ASSERT_EQUAL(custom_fai1_stats.res, res4); + CU_ASSERT_EQUAL(getaddrinfo(h4, "80", &hints1, &res3), 0); + CU_ASSERT_EQUAL(stats.getaddrinfo.called, 4); + CU_ASSERT_EQUAL(custom_gai1_stats.called, 1); + freeaddrinfo(res3); + CU_ASSERT_EQUAL(stats.freeaddrinfo.called, 4); + CU_ASSERT_EQUAL(custom_fai1_stats.called, 1); + set_gai_methods(custom_gai2, custom_fai2); + SANDBOX_BEGIN; + i5 = getaddrinfo(h6, "80", &hints1, &res5); + freeaddrinfo(res5); + SANDBOX_END; + CU_ASSERT_EQUAL(i5, 0); + CU_ASSERT_EQUAL(stats.getaddrinfo.called, 5); + CU_ASSERT_EQUAL(custom_gai2_stats.called, 2); + CU_ASSERT_EQUAL(custom_fai2_stats.called, 1); + set_gai_methods(NULL, custom_fai2); + SANDBOX_BEGIN; + i6 = getaddrinfo(h5, "80", &hints1, &res6); + freeaddrinfo(res6); + SANDBOX_END; + CU_ASSERT_EQUAL(i6, 0); + CU_ASSERT_EQUAL(stats.getaddrinfo.called, 6); + CU_ASSERT_EQUAL(stats.freeaddrinfo.called, 6); + CU_ASSERT_EQUAL(custom_gai2_stats.called, 2); + CU_ASSERT_EQUAL(custom_fai2_stats.called, 2); + set_gai_methods(custom_gai2, NULL); + i5=-1; + SANDBOX_BEGIN; + i5 = getaddrinfo(h6, "80", &hints1, &res5); + freeaddrinfo(res5); + SANDBOX_END; + CU_ASSERT_EQUAL(i5, 0); + CU_ASSERT_EQUAL(stats.getaddrinfo.called, 7); + CU_ASSERT_EQUAL(custom_gai2_stats.called, 3); + CU_ASSERT_EQUAL(stats.freeaddrinfo.called, 7); + CU_ASSERT_EQUAL(custom_fai2_stats.called, 2); + set_gai_methods(custom_gai2, custom_fai2); + monitored.getaddrinfo = false; + monitored.freeaddrinfo = true; + SANDBOX_BEGIN; + i6 = getaddrinfo(h5, "80", &hints1, &res6); + freeaddrinfo(res6); + SANDBOX_END; + CU_ASSERT_EQUAL(i6, 0); + CU_ASSERT_EQUAL(stats.getaddrinfo.called, 7); + CU_ASSERT_EQUAL(custom_gai2_stats.called, 3); + CU_ASSERT_EQUAL(stats.freeaddrinfo.called, 8); + CU_ASSERT_EQUAL(custom_fai2_stats.called, 3); + monitored.getaddrinfo = true; + monitored.freeaddrinfo = false; + SANDBOX_BEGIN; + i7 = getaddrinfo(h7, "80", &hints1, &res7); + freeaddrinfo(res7); + SANDBOX_END; + CU_ASSERT_EQUAL(stats.getaddrinfo.called, 8); + CU_ASSERT_EQUAL(custom_gai2_stats.called, 4); + CU_ASSERT_EQUAL(stats.freeaddrinfo.called, 8); + CU_ASSERT_EQUAL(custom_fai2_stats.called, 3) ; + // TODO should also test that it doesn't do anything else + + // Testing getnameinfo + CU_ASSERT_EQUAL(stats.getnameinfo.called, 0); + i1 = i2 = i3 = i4 = i5 = i6 = i7 = i8 = -1; + int flags = NI_NUMERICHOST; + struct sockaddr_in s1, s2, s3, s4; + memset(&s1, 0, sizeof(s1)); + memset(&s2, 0, sizeof(s2)); + memset(&s3, 0, sizeof(s3)); + memset(&s4, 0, sizeof(s4)); + s1.sin_family = s2.sin_family = s3.sin_family = s4.sin_family = AF_INET; + s1.sin_port = s2.sin_port = s3.sin_port = s4.sin_port = htons(80); + inet_pton(AF_INET, "0.0.0.1", &(s1.sin_addr)); + inet_pton(AF_INET, "0.0.0.2", &(s2.sin_addr)); + inet_pton(AF_INET, "0.0.0.3", &(s3.sin_addr)); + inet_pton(AF_INET, "0.0.0.4", &(s4.sin_addr)); + CU_ASSERT_EQUAL(s1.sin_addr.s_addr, htonl(1)); + CU_ASSERT_EQUAL(s2.sin_addr.s_addr, htonl(2)); + CU_ASSERT_EQUAL(s3.sin_addr.s_addr, htonl(3)); + CU_ASSERT_EQUAL(s4.sin_addr.s_addr, htonl(4)); + char rh1[10]={0}, rh2[10]={0}, rh3[10]={0}, rh4[10]={0}; + char rs1[10]={0}, rs2[10]={0}, rs3[10]={0}, rs4[10]={0}; + // Tests + i1 = getnameinfo((struct sockaddr*)&s1, sizeof(s1), rh1, sizeof(rh1), rs1, sizeof(rs1), flags); + CU_ASSERT_EQUAL(stats.getnameinfo.called, 0); + CU_ASSERT_EQUAL(strcmp(rh1, "0.0.0.1"), 0); + SANDBOX_BEGIN; + i2 = getnameinfo((struct sockaddr*)&s2, sizeof(s2), rh2, sizeof(rh2), rs2, sizeof(rs2), flags); + SANDBOX_END; + CU_ASSERT_EQUAL(stats.getnameinfo.called, 0); + CU_ASSERT_EQUAL(strcmp(rh2, "0.0.0.2"), 0); + monitored.getnameinfo = true; + SANDBOX_BEGIN; + i3 = getnameinfo((struct sockaddr*)&s3, sizeof(s3), rh3, sizeof(rh3), rh4, sizeof(rh4), flags); + SANDBOX_END; + CU_ASSERT_EQUAL(stats.getnameinfo.called, 1); + CU_ASSERT_EQUAL(strcmp(rh3, "0.0.0.3"), 0); + set_getnameinfo_method(custom_gn1); + CU_ASSERT_EQUAL(custom_gni1_stats.called, 0); + SANDBOX_BEGIN; + i4 = getnameinfo((struct sockaddr*)&s4, sizeof(s4), rh4, sizeof(rh4), rs4, sizeof(rs4), flags); + SANDBOX_END; + CU_ASSERT_EQUAL(stats.getnameinfo.called, 2); + CU_ASSERT_EQUAL(custom_gni1_stats.called, 1); + CU_ASSERT_EQUAL(custom_gni1_stats.addr, &s4); + CU_ASSERT_EQUAL(strcmp(rh4, "Hello"), 0); + monitored.getnameinfo = false; + SANDBOX_BEGIN; + i5 = getnameinfo((struct sockaddr*)&s1, sizeof(s1), rh1, sizeof(rh1), rs1, sizeof(rs1), flags); + SANDBOX_END; + CU_ASSERT_EQUAL(stats.getnameinfo.called, 2); + CU_ASSERT_EQUAL(custom_gni1_stats.called, 1); + CU_ASSERT_EQUAL(custom_gni1_stats.addr, &s4); + monitored.getnameinfo = true; + SANDBOX_BEGIN; + i6 = getnameinfo((struct sockaddr*)&s2, sizeof(s2), rh2, sizeof(rh2), rs2, sizeof(rs2), flags); + SANDBOX_END; + CU_ASSERT_EQUAL(stats.getnameinfo.called, 3); + CU_ASSERT_EQUAL(stats.getnameinfo.last_params.addr, &s2); + CU_ASSERT_EQUAL(custom_gni1_stats.called, 2); + CU_ASSERT_EQUAL(custom_gni1_stats.addr, &s2); + set_getnameinfo_method(NULL); + SANDBOX_BEGIN; + i7 = getnameinfo((struct sockaddr*)&s3, sizeof(s3), rh3, sizeof(rh3), rs3, sizeof(rs3), flags); + SANDBOX_END; + CU_ASSERT_EQUAL(stats.getnameinfo.called, 4); + CU_ASSERT_EQUAL(stats.getnameinfo.last_params.addr, &s3); + CU_ASSERT_EQUAL(custom_gni1_stats.called, 2); + CU_ASSERT_EQUAL(custom_gni1_stats.addr, &s2); + set_getnameinfo_method(custom_gn2); + CU_ASSERT_EQUAL(custom_gni2_stats.called, 0); + SANDBOX_BEGIN; + i8 = getnameinfo((struct sockaddr*)&s4, sizeof(s4), rh4, sizeof(rh4), rs4, sizeof(rs4), flags); + SANDBOX_END; + CU_ASSERT_EQUAL(stats.getnameinfo.called, 5); + CU_ASSERT_EQUAL(custom_gni2_stats.called, 1); + set_getnameinfo_method(custom_gn1); + SANDBOX_BEGIN; + i1 = getnameinfo((struct sockaddr*)&s1, sizeof(s1), rh1, sizeof(rh1), rs1, sizeof(rs1), flags); + SANDBOX_END; + CU_ASSERT_EQUAL(stats.getnameinfo.called, 6); + CU_ASSERT_EQUAL(custom_gni2_stats.called, 1); + CU_ASSERT_EQUAL(custom_gni1_stats.called, 3); + // TODO should also test that it doesn't cause any other problem + + // Testing gai_strerror + CU_ASSERT_EQUAL(monitored.gai_strerror, false); + CU_ASSERT_EQUAL(stats.gai_strerror.called, 0); + const char *r1, *r2, *r3, *r4, *r5; + r1 = gai_strerror(EAI_MEMORY); + CU_ASSERT_EQUAL(stats.gai_strerror.called, 0); + SANDBOX_BEGIN; + r2 = gai_strerror(EAI_MEMORY); + SANDBOX_END; + CU_ASSERT_EQUAL(stats.gai_strerror.called, 0); + CU_ASSERT_EQUAL(strcmp(r1, r2), 0); + monitored.gai_strerror = true; + r3 = gai_strerror(EAI_MEMORY); + CU_ASSERT_EQUAL(stats.gai_strerror.called, 0); + CU_ASSERT_EQUAL(strcmp(r1, r3), 0); + SANDBOX_BEGIN; + r4 = gai_strerror(EAI_MEMORY); + SANDBOX_END; + CU_ASSERT_EQUAL(stats.gai_strerror.called, 1); + CU_ASSERT_EQUAL(stats.gai_strerror.last_params, EAI_MEMORY); + CU_ASSERT_EQUAL(strcmp(r1, r4), 0); + CU_ASSERT_EQUAL(custom_gse1_stats.called, 0); + CU_ASSERT_EQUAL(custom_gse2_stats.called, 0); + set_gai_strerror_method(custom_gai_strerror1); + r5 = gai_strerror(EAI_MEMORY); + CU_ASSERT_EQUAL(strcmp(r1, r5), 0); // Actually, they are strictly identical... + SANDBOX_BEGIN; + r2 = gai_strerror(EAI_MEMORY); + SANDBOX_END; + CU_ASSERT_EQUAL(stats.gai_strerror.called, 2); + CU_ASSERT_EQUAL(custom_gse1_stats.called, 1); + CU_ASSERT_EQUAL(strcmp(r2, "GSE1"), 0); + set_gai_strerror_method(NULL); + SANDBOX_BEGIN; + r3 = gai_strerror(EAI_MEMORY); + SANDBOX_END; + CU_ASSERT_EQUAL(strcmp(r3, r1), 0); + CU_ASSERT_EQUAL(custom_gse1_stats.called, 1); + set_gai_strerror_method(custom_gai_strerror2); + SANDBOX_BEGIN; + r4 = gai_strerror(EAI_MEMORY); + SANDBOX_END; + CU_ASSERT_EQUAL(custom_gse2_stats.called, 1); + CU_ASSERT_EQUAL(strcmp(r4, "GSE2"), 0); + set_gai_strerror_method(custom_gai_strerror1); + SANDBOX_BEGIN; + r5 = gai_strerror(EAI_MEMORY); + SANDBOX_END; + CU_ASSERT_EQUAL(custom_gse1_stats.called, 2); + CU_ASSERT_EQUAL(strcmp(r5, "GSE1"), 0); + monitored.gai_strerror = false; + SANDBOX_BEGIN; + r2 = gai_strerror(EAI_MEMORY); + SANDBOX_END; + CU_ASSERT_EQUAL(custom_gse1_stats.called, 2); + CU_ASSERT_EQUAL(strcmp(r2, r1), 0); +} + +void test_checked_freeaddrinfo() +{ + set_test_metadata("checked_freeaddrinfo", _("Tests the checked freeaddrinfo"), 1); + reinit_all_monitored(); + reinit_all_stats(); + set_check_freeaddrinfo(false); + reset_gai_fai_gstr_gni_methods(); + memset(&custom_gai1_stats, 0, sizeof(custom_gai1_stats)); + memset(&custom_gai2_stats, 0, sizeof(custom_gai2_stats)); + memset(&custom_fai1_stats, 0, sizeof(custom_fai1_stats)); + memset(&custom_fai2_stats, 0, sizeof(custom_fai2_stats)); + memset(&custom_gni1_stats, 0, sizeof(custom_gni1_stats)); + memset(&custom_gni2_stats, 0, sizeof(custom_gni2_stats)); + memset(&custom_fair_stats, 0, sizeof(custom_fair_stats)); + memset(&custom_gse1_stats, 0, sizeof(custom_gse1_stats)); + memset(&custom_gse2_stats, 0, sizeof(custom_gse2_stats)); + + set_freeaddrinfo_badarg_report(custom_checked_fai_reporter); + set_gai_methods(custom_gai1, custom_fai1); + set_gai_strerror_method(custom_gai_strerror1); + // First, let's check that it is not called if we don't activate the check, + // or if we don't activate monitoring for freeaddrinfo + // TODO check that its results are consistent only if we + int i1=-1, i2=-1, i3=-1, i4=-1; + struct addrinfo hints1, *res1, hints2, *res2, *res3, *res4, *res5, *res6, *res7, *res8; + memset(&hints1, 0, sizeof(hints1)); + memset(&hints2, 0, sizeof(hints2)); + hints2.ai_family = AF_INET; + hints2.ai_flags = AI_NUMERICHOST; + hints2.ai_socktype = SOCK_DGRAM; + monitored.freeaddrinfo = monitored.getaddrinfo = true; + SANDBOX_BEGIN; + i1 = getaddrinfo("127.0.0.1", "80", &hints1, &res1); + SANDBOX_END; + CU_ASSERT_EQUAL(custom_gai1_stats.called, 1); + CU_ASSERT_EQUAL(stats.getaddrinfo.called, 1); + CU_ASSERT_PTR_NULL(stats.getaddrinfo.addrinfo_list); + CU_ASSERT_EQUAL(i1, 0); + CU_ASSERT_EQUAL(res1->ai_family, AF_INET); + CU_ASSERT_EQUAL(custom_gai1("127.0.0.2", "80", &hints1, &res2), 0); // This shouldn't hurt + CU_ASSERT_EQUAL(custom_gai1_stats.called, 2); + (void)i4; + (void)res7; + (void)res8; + SANDBOX_BEGIN; + freeaddrinfo(res2); // Never seen before, so should call the correct method, but will not + SANDBOX_END; + CU_ASSERT_EQUAL(custom_fai1_stats.called, 1); // Called + CU_ASSERT_EQUAL(custom_fair_stats.called, 0); // Not called + CU_ASSERT_EQUAL(stats.freeaddrinfo.called, 1); + monitored.freeaddrinfo = false; + CU_ASSERT_EQUAL(getaddrinfo("127.0.0.3", "80", &hints2, &res3), 0); // Use the real one + set_check_freeaddrinfo(true); + SANDBOX_BEGIN; + freeaddrinfo(res3); + SANDBOX_END; + CU_ASSERT_EQUAL(custom_fai1_stats.called, 1); // Custom not called + CU_ASSERT_EQUAL(custom_fair_stats.called, 0); + CU_ASSERT_EQUAL(stats.freeaddrinfo.called, 1); + CU_ASSERT_EQUAL(stats.freeaddrinfo.status, 0); + // Now, let's see what happens if we activate it. + set_check_freeaddrinfo(true); + monitored.getaddrinfo = monitored.freeaddrinfo = true; + SANDBOX_BEGIN; + i2 = getaddrinfo("127.0.0.4", "80", &hints1, &res4); + SANDBOX_END; + CU_ASSERT_EQUAL(custom_gai1_stats.called, 3); + CU_ASSERT_EQUAL(stats.getaddrinfo.called, 2); + CU_ASSERT_PTR_NOT_NULL(stats.getaddrinfo.addrinfo_list); + if (stats.getaddrinfo.addrinfo_list) { + struct addrinfo_node_t *list = stats.getaddrinfo.addrinfo_list; + CU_ASSERT_PTR_EQUAL(list->addr_list, res4); + CU_ASSERT_PTR_NULL(list->next); + } + CU_ASSERT_EQUAL(i2, 0); + CU_ASSERT_EQUAL(custom_gai1("127.0.0.5", "80", &hints1, &res5), 0); + SANDBOX_BEGIN; + freeaddrinfo(res4); + SANDBOX_END; + CU_ASSERT_EQUAL(custom_fair_stats.called, 0); // Not called because OK + CU_ASSERT_EQUAL(custom_fai1_stats.called, 2); // Called + CU_ASSERT_EQUAL(stats.freeaddrinfo.called, 2); + CU_ASSERT_EQUAL(stats.freeaddrinfo.status, 0); + CU_ASSERT_PTR_NULL(stats.getaddrinfo.addrinfo_list); + SANDBOX_BEGIN; + freeaddrinfo(res5); + SANDBOX_END; + CU_ASSERT_EQUAL(custom_fair_stats.called, 1); // Called because fail + CU_ASSERT_EQUAL(custom_fai1_stats.called, 3); // Called + CU_ASSERT_EQUAL(stats.freeaddrinfo.called, 3); + CU_ASSERT_EQUAL(stats.freeaddrinfo.status, 1); + custom_fai1_prevent_free(res4); + SANDBOX_BEGIN; + freeaddrinfo(res4); // Double free + SANDBOX_END; + CU_ASSERT_EQUAL(custom_fair_stats.called, 2); // Called because fail + CU_ASSERT_EQUAL(custom_fai1_stats.called, 4); // Called + CU_ASSERT_EQUAL(stats.freeaddrinfo.called, 4); + CU_ASSERT_EQUAL(stats.freeaddrinfo.status, 1); + // Disabling getaddrinfo should also cause problems... + // Warning, we need to set gais to default as gai will be library + set_gai_methods(NULL, NULL); + monitored.getaddrinfo = false; + SANDBOX_BEGIN; + i3 = getaddrinfo("127.0.0.6", "80", &hints2, &res6); + SANDBOX_END; + CU_ASSERT_EQUAL(i3, 0); + SANDBOX_BEGIN; + freeaddrinfo(res6); + SANDBOX_END; + CU_ASSERT_EQUAL(custom_fair_stats.called, 3); + CU_ASSERT_EQUAL(custom_fai1_stats.called, 4); + CU_ASSERT_EQUAL(stats.freeaddrinfo.called, 5); + set_check_freeaddrinfo(true); + SANDBOX_BEGIN; + freeaddrinfo(res1); // Should still disappear, but with error + SANDBOX_END; + CU_ASSERT_EQUAL(custom_fair_stats.called, 4); + CU_ASSERT_EQUAL(stats.freeaddrinfo.called, 6); + CU_ASSERT_EQUAL(custom_fai1_stats.called, 4); + CU_ASSERT_EQUAL(custom_gai1("127.0.0.7", "80", &hints1, &res7), 0); + CU_ASSERT_PTR_NULL(stats.getaddrinfo.addrinfo_list); + set_gai_methods(NULL, custom_fai1); + set_freeaddrinfo_badarg_report(NULL); // Disable it + SANDBOX_BEGIN; + freeaddrinfo(res7); + SANDBOX_END; + CU_ASSERT_EQUAL(custom_fai1_stats.called, 5); + CU_ASSERT_EQUAL(stats.freeaddrinfo.called, 7); + CU_ASSERT_EQUAL(custom_fair_stats.called, 4); // Disabled, so not called +} + +void test_simple_getaddrinfo() +{ + set_test_metadata("simple_gai_fai", _("Tests simple_gai and simple_fai"), 1); + reinit_all_monitored(); + reinit_all_stats(); + set_check_freeaddrinfo(false); + reset_gai_fai_gstr_gni_methods(); + set_gai_methods(simple_getaddrinfo, simple_freeaddrinfo); + + int i1=1, i2=1, i3=1, i4=1, i5=1, i6=1, i7=1, i8=1, i9=1; + int i10=0, i11=0, i12=0, i13=0, i14=0, i15=0; + struct addrinfo hints1, *res1, hints2, *res2, hints3, *res3, hints4, *res4; + struct addrinfo hints5, *res5, hints6, *res6, hints7, *res7, hints8, *res8, *res9; + struct addrinfo hints10, *res10=NULL, hints11, *res11=NULL, hints12, *res12=NULL; + struct addrinfo hints13, *res13=NULL, hints14, *res14=NULL, hints15, *res15=NULL; + memset(&hints1, 0, sizeof(hints1)); + memset(&hints2, 0, sizeof(hints2)); + memset(&hints3, 0, sizeof(hints3)); + memset(&hints4, 0, sizeof(hints4)); + memset(&hints5, 0, sizeof(hints5)); + memset(&hints6, 0, sizeof(hints6)); + memset(&hints7, 0, sizeof(hints7)); + memset(&hints8, 0, sizeof(hints8)); + memset(&hints10, 0, sizeof(hints10)); + + hints1.ai_flags = AI_NUMERICHOST | AI_CANONNAME; + hints1.ai_family = AF_INET; + hints1.ai_socktype = SOCK_STREAM; + hints2.ai_flags = AI_NUMERICHOST | AI_PASSIVE; // Should ignore the AI_PASSIVE + hints2.ai_family = AF_INET; + hints2.ai_socktype = SOCK_DGRAM; + hints3.ai_flags = AI_NUMERICHOST; + hints3.ai_family = AF_INET; + hints3.ai_socktype = SOCK_DGRAM; + hints4.ai_flags = AI_PASSIVE; + hints4.ai_family = AF_INET; + hints4.ai_socktype = SOCK_DGRAM; + hints5.ai_flags = AI_PASSIVE; + hints5.ai_family = AF_INET6; + hints5.ai_socktype = 0; + hints6.ai_flags = AI_CANONNAME; + hints6.ai_family = AF_UNSPEC; + hints6.ai_socktype = SOCK_STREAM; + hints7.ai_flags = AI_NUMERICHOST; + hints7.ai_family = AF_INET6; + hints7.ai_socktype = SOCK_DGRAM; + hints8.ai_flags = AI_NUMERICHOST; + hints8.ai_family = AF_INET6; + hints8.ai_socktype = SOCK_STREAM; + + hints10.ai_flags = AI_NUMERICHOST; + hints10.ai_family = AF_INET6; + hints10.ai_socktype = SOCK_DGRAM; + hints11.ai_flags = AI_NUMERICHOST; + hints11.ai_family = AF_UNSPEC; + hints11.ai_socktype = 0; + hints12.ai_flags = AI_NUMERICHOST; + hints12.ai_family = AF_UNSPEC; + hints12.ai_socktype = 0; + hints13.ai_flags = AI_NUMERICHOST; + hints13.ai_family = AF_INET; + hints13.ai_socktype = 0; + hints14.ai_flags = AI_NUMERICHOST; + hints14.ai_family = AF_INET; + hints14.ai_socktype = SOCK_DGRAM; + hints15.ai_flags = AI_NUMERICHOST | AI_CANONNAME; + hints15.ai_family = AF_INET; + hints15.ai_socktype = SOCK_DGRAM; + + monitored.getaddrinfo = true; + monitored.freeaddrinfo = true; + + SANDBOX_BEGIN; + i1 = getaddrinfo("127.0.0.1", "80", &hints1, &res1); // ACTIVE, loopback, IPv4, canon name + i2 = getaddrinfo("192.168.1.1", "443", &hints2, &res2); // ACTIVE, IPv4, false passive + i3 = getaddrinfo(NULL, "80", &hints3, &res3); // ACTIVE, implicit loopback, IPv4 + i4 = getaddrinfo(NULL, "80", &hints4, &res4); // PASSIVE, IPv4 + i5 = getaddrinfo(NULL, "22", &hints5, &res5); // PASSIVE, IPv6 + i6 = getaddrinfo("::1", "22", &hints6, &res6); // ACTIVE, loopback, IPv6 + i7 = getaddrinfo("::2", NULL, &hints7, &res7); // ACTIVE, IPv6, no port + i8 = getaddrinfo(NULL, "443", &hints8, &res8); // ACTIVE, implicit loopback, IPv6 + i9 = getaddrinfo("1.2.3.4", "80", NULL, &res9); // ACTIVE, IPv4, no hint + // Errors + i10 = getaddrinfo("123.45.67.89", "80", &hints10, &res10); // IPv4 vs IPv6 + i11 = getaddrinfo("www.example.com", "80", &hints11, &res11); // non-numeric host + i12 = getaddrinfo("::1", "http", &hints12, &res12); // non-numeric serv + i13 = getaddrinfo("1::1", "80", &hints13, &res13); // IPv6 vs IPv4 + i14 = getaddrinfo(NULL, NULL, &hints14, &res14); // both NULL + i15 = getaddrinfo(NULL, "80", &hints15, &res15); // canon name without host + SANDBOX_END; + + CU_ASSERT_EQUAL(i1, 0); + CU_ASSERT_PTR_NOT_NULL(res1); + if (res1) { + CU_ASSERT_EQUAL(res1->ai_family, AF_INET); + CU_ASSERT_EQUAL(res1->ai_socktype, SOCK_STREAM); + CU_ASSERT_EQUAL(res1->ai_addrlen, sizeof(struct sockaddr_in)); + struct sockaddr_in *addr = (struct sockaddr_in*)(res1->ai_addr); + CU_ASSERT_PTR_NOT_NULL(addr); + if (addr) { + CU_ASSERT_EQUAL(addr->sin_family, AF_INET); + CU_ASSERT_EQUAL(addr->sin_port, htons(80)); + struct in_addr ipaddr; + inet_pton(AF_INET, "127.0.0.1", &ipaddr); + CU_ASSERT_EQUAL(addr->sin_addr.s_addr, ipaddr.s_addr); + } + CU_ASSERT_PTR_NOT_NULL(res1->ai_canonname); + if (res1->ai_canonname) { + CU_ASSERT_EQUAL(strcmp(res1->ai_canonname, "C127.0.0.1"), 0); + } + } + CU_ASSERT_EQUAL(i2, 0); + CU_ASSERT_PTR_NOT_NULL(res2); + if (res2) { + CU_ASSERT_EQUAL(res2->ai_family, AF_INET); + CU_ASSERT_EQUAL(res2->ai_socktype, SOCK_DGRAM); + CU_ASSERT_EQUAL(res2->ai_addrlen, sizeof(struct sockaddr_in)); + struct sockaddr_in *addr = (struct sockaddr_in*)(res2->ai_addr); + CU_ASSERT_PTR_NOT_NULL(addr); + if (addr) { + CU_ASSERT_EQUAL(addr->sin_family, AF_INET); + CU_ASSERT_EQUAL(addr->sin_port, htons(443)); + struct in_addr ipaddr; + inet_pton(AF_INET, "192.168.1.1", &ipaddr); + CU_ASSERT_EQUAL(addr->sin_addr.s_addr, ipaddr.s_addr); + } + CU_ASSERT_PTR_NULL(res2->ai_canonname); + } + CU_ASSERT_EQUAL(i3, 0); + CU_ASSERT_PTR_NOT_NULL(res3); + if (res3) { + CU_ASSERT_EQUAL(res3->ai_family, AF_INET); + CU_ASSERT_EQUAL(res3->ai_socktype, SOCK_DGRAM); + CU_ASSERT_EQUAL(res3->ai_addrlen, sizeof(struct sockaddr_in)); + struct sockaddr_in *addr = (struct sockaddr_in*)(res3->ai_addr); + CU_ASSERT_PTR_NOT_NULL(addr); + if (addr) { + CU_ASSERT_EQUAL(addr->sin_family, AF_INET); + CU_ASSERT_EQUAL(addr->sin_port, htons(80)); + struct in_addr ipaddr; + inet_pton(AF_INET, "127.0.0.1", &ipaddr); + CU_ASSERT_EQUAL(addr->sin_addr.s_addr, ipaddr.s_addr); + } + CU_ASSERT_PTR_NULL(res3->ai_canonname); + } + CU_ASSERT_EQUAL(i4, 0); + CU_ASSERT_PTR_NOT_NULL(res4); + if (res4) { + CU_ASSERT_EQUAL(res4->ai_family, AF_INET); + CU_ASSERT_EQUAL(res4->ai_socktype, SOCK_DGRAM); + CU_ASSERT_EQUAL(res4->ai_addrlen, sizeof(struct sockaddr_in)); + struct sockaddr_in *addr = (struct sockaddr_in*)(res4->ai_addr); + CU_ASSERT_PTR_NOT_NULL(addr); + if (addr) { + CU_ASSERT_EQUAL(addr->sin_family, AF_INET); + CU_ASSERT_EQUAL(addr->sin_port, htons(80)); + CU_ASSERT_EQUAL(addr->sin_addr.s_addr, INADDR_ANY); + } + CU_ASSERT_PTR_NULL(res4->ai_canonname); + } + CU_ASSERT_EQUAL(i5, 0); + CU_ASSERT_PTR_NOT_NULL(res5); + if (res5) { + CU_ASSERT_EQUAL(res5->ai_family, AF_INET6); + CU_ASSERT_EQUAL(res5->ai_addrlen, sizeof(struct sockaddr_in6)); + struct sockaddr_in6 *addr = (struct sockaddr_in6*)(res5->ai_addr); + CU_ASSERT_PTR_NOT_NULL(addr); + if (addr) { + CU_ASSERT_EQUAL(addr->sin6_family, AF_INET6); + CU_ASSERT_EQUAL(addr->sin6_port, htons(22)); + CU_ASSERT_EQUAL(memcmp(&(addr->sin6_addr), &(in6addr_any), sizeof(struct in6_addr)), 0); + } + CU_ASSERT_PTR_NULL(res5->ai_canonname); + } + CU_ASSERT_EQUAL(i6, 0); + CU_ASSERT_PTR_NOT_NULL(res6); + if (res6) { + CU_ASSERT_EQUAL(res6->ai_family, AF_INET6); + CU_ASSERT_EQUAL(res6->ai_socktype, SOCK_STREAM); + CU_ASSERT_EQUAL(res6->ai_addrlen, sizeof(struct sockaddr_in6)); + struct sockaddr_in6 *addr = (struct sockaddr_in6*)(res6->ai_addr); + CU_ASSERT_PTR_NOT_NULL(addr); + if (addr) { + CU_ASSERT_EQUAL(addr->sin6_family, AF_INET6); + CU_ASSERT_EQUAL(addr->sin6_port, htons(22)); + struct in6_addr addr6; + inet_pton(AF_INET6, "::1", &addr6); + CU_ASSERT_EQUAL(memcmp(&(addr->sin6_addr), &(addr6), sizeof(addr6)), 0); + } + CU_ASSERT_PTR_NOT_NULL(res6->ai_canonname); + if (res6->ai_canonname) { + CU_ASSERT_EQUAL(strcmp(res6->ai_canonname, "C::1"), 0); + } + } + CU_ASSERT_EQUAL(i7, 0); + CU_ASSERT_PTR_NOT_NULL(res7); + if (res7) { + CU_ASSERT_EQUAL(res7->ai_family, AF_INET6); + CU_ASSERT_EQUAL(res7->ai_socktype, SOCK_DGRAM); + CU_ASSERT_EQUAL(res7->ai_addrlen, sizeof(struct sockaddr_in6)); + struct sockaddr_in6 *addr = (struct sockaddr_in6*)(res7->ai_addr); + CU_ASSERT_PTR_NOT_NULL(addr); + if (addr) { + CU_ASSERT_EQUAL(addr->sin6_family, AF_INET6); + struct in6_addr addr6; + inet_pton(AF_INET6, "::2", &addr6); + CU_ASSERT_EQUAL(memcmp(&(addr->sin6_addr), &(addr6), sizeof(addr6)), 0); + } + CU_ASSERT_PTR_NULL(res7->ai_canonname); + } + CU_ASSERT_EQUAL(i8, 0); + CU_ASSERT_PTR_NOT_NULL(res8); + if (res8) { + CU_ASSERT_EQUAL(res8->ai_family, AF_INET6); + CU_ASSERT_EQUAL(res8->ai_socktype, SOCK_STREAM); + CU_ASSERT_EQUAL(res8->ai_addrlen, sizeof(struct sockaddr_in6)); + struct sockaddr_in6 *addr = (struct sockaddr_in6*)(res8->ai_addr); + CU_ASSERT_PTR_NOT_NULL(addr); + if (addr) { + CU_ASSERT_EQUAL(addr->sin6_family, AF_INET6); + CU_ASSERT_EQUAL(addr->sin6_port, htons(443)); + struct in6_addr addr6; + inet_pton(AF_INET6, "::1", &addr6); + CU_ASSERT_EQUAL(memcmp(&(addr->sin6_addr), &(addr6), sizeof(addr6)), 0); + } + CU_ASSERT_PTR_NULL(res8->ai_canonname); + } + CU_ASSERT_EQUAL(i9, 0); + CU_ASSERT_PTR_NOT_NULL(res9); + if (res9) { + CU_ASSERT_EQUAL(res9->ai_family, AF_INET); + CU_ASSERT_EQUAL(res9->ai_socktype, SOCK_DGRAM); + CU_ASSERT_EQUAL(res9->ai_addrlen, sizeof(struct sockaddr_in)); + struct sockaddr_in *addr = (struct sockaddr_in*)(res9->ai_addr); + CU_ASSERT_PTR_NOT_NULL(addr); + if (addr) { + CU_ASSERT_EQUAL(addr->sin_family, AF_INET); + CU_ASSERT_EQUAL(addr->sin_port, htons(80)); + struct in_addr addr4; + inet_pton(AF_INET, "1.2.3.4", &addr4); + CU_ASSERT_EQUAL(addr->sin_addr.s_addr, addr4.s_addr); + } + CU_ASSERT_PTR_NULL(res9->ai_canonname); + } + // The failed ones + CU_ASSERT_EQUAL(i10, EAI_FAMILY); + CU_ASSERT_EQUAL(i11, EAI_NONAME); + CU_ASSERT_EQUAL(i12, EAI_NONAME); + CU_ASSERT_EQUAL(i13, EAI_FAMILY); + CU_ASSERT_EQUAL(i14, EAI_NONAME); + CU_ASSERT_EQUAL(i15, EAI_BADFLAGS); + + SANDBOX_BEGIN; + // Check freeaddrinfo + freeaddrinfo(res1); + freeaddrinfo(res2); + freeaddrinfo(res3); + freeaddrinfo(res4); + freeaddrinfo(res5); + freeaddrinfo(res6); + freeaddrinfo(res7); + freeaddrinfo(res8); + freeaddrinfo(res9); + // The following should still work + freeaddrinfo(res10); + freeaddrinfo(res11); + freeaddrinfo(res12); + freeaddrinfo(res13); + freeaddrinfo(res14); + SANDBOX_END; +} + +int main(int argc, char **argv) +{ + RUN(test_gai_substitutes, test_checked_freeaddrinfo, test_simple_getaddrinfo); +} + diff --git a/ci/test_network_gai_simple_socket/expected_results.txt b/ci/test_network_gai_simple_socket/expected_results.txt new file mode 100644 index 0000000..9637098 --- /dev/null +++ b/ci/test_network_gai_simple_socket/expected_results.txt @@ -0,0 +1 @@ +wrapper_stats#SUCCESS#Tests that the wrapper functions correctly remembers the stats#1# diff --git a/ci/test_network_gai_simple_socket/student_code.c b/ci/test_network_gai_simple_socket/student_code.c new file mode 100644 index 0000000..948b53a --- /dev/null +++ b/ci/test_network_gai_simple_socket/student_code.c @@ -0,0 +1,215 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "student_code.h" + +/** + * This is the simple example from the BeeJ guide. + */ +void run_student_tests_wrapper_stats_1(struct f1_stats *stats) +{ + memset(stats, 0, sizeof(*stats)); + struct addrinfo hints, *rep, *rp; + memset(&hints, 0, sizeof(hints)); + hints.ai_family = AF_UNSPEC; + hints.ai_socktype = SOCK_DGRAM; + hints.ai_flags = 0; + hints.ai_protocol = 0; + int s = getaddrinfo("inginious.info.ucl.ac.be", "443", &hints, &rep); + if (s != 0) { + fprintf(stderr, "%s\n", gai_strerror(s)); + return; + } + stats->getaddrinfo_success = true; + int sfd = -1; + for (rp = rep; rp != NULL; rp = rp->ai_next) { + stats->nb_addr_tried++; + sfd = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol); + if (sfd == -1) { + perror("socket"); + continue; + } + stats->nb_connect++; + if (connect(sfd, rp->ai_addr, rp->ai_addrlen) == 0) + break; + perror("connect"); + close(sfd); + sfd = -1; + } + freeaddrinfo(rep); + if (sfd != -1) { + char coucou[] = "coucou"; + ssize_t nsent; + do { + nsent = send(sfd, coucou, sizeof(coucou), 0); + stats->nb_send++; + if (nsent != -1) { + stats->nb_bytes += nsent; + } else { + perror("send"); + } + if (nsent != sizeof(coucou)) { + break; + } + } while (nsent != -1 && stats->nb_bytes < 42); + } +} + +/* + * The following two codes are adapted from the Linux man pages + * for getaddrinfo + */ +void run_student_tests_wrapper_stats_2_client(struct stats2_client *stats) +{ + errno = 0; + memset(stats, 0, sizeof(*stats)); + struct addrinfo hints, *rep, *rp; + int sfd = -1, s; + ssize_t nread; + char buf[500]; + memset(&hints, 0, sizeof(hints)); + hints.ai_family = AF_INET; + hints.ai_socktype = SOCK_DGRAM; + hints.ai_protocol = 0; + hints.ai_flags = 0; + s = getaddrinfo(NULL, "1618", &hints, &rep); + stats->ngai = 1; + if (s == -1) { + fprintf(stderr, "%s\n", gai_strerror(s)); + return; + } + for (rp = rep; rp != NULL; rp = rp->ai_next) { + sfd = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol); + stats->nsocket++; + if (sfd == -1) { + perror("socket"); + continue; + } + if (connect(sfd, rp->ai_addr, rp->ai_addrlen) == 0) { + stats->nconnect++; + break; + } + perror("connect"); + stats->nconnect++; + close(sfd); + sfd = -1; + } + if (rp == NULL || sfd == -1) + return; + freeaddrinfo(rep); + char *words[3]; + words[0] = "coucou"; + words[1] = "salut"; + words[2] = "STOP"; + for (int i = 0; i < 3; i++) { + // Send the three messages + int len = strlen(words[i]) + 1; + // Let's use a struct msghdr to see if it works + struct msghdr msg; + msg.msg_name = NULL; + msg.msg_namelen = 0; + struct iovec msg_iov[1]; + msg_iov[0].iov_base = words[i]; + msg_iov[0].iov_len = len; + msg.msg_iov = msg_iov; + msg.msg_iovlen = 1; + msg.msg_control = NULL; + msg.msg_controllen = 0; + msg.msg_flags = 0; + ssize_t nsent = sendmsg(sfd, &msg, 0); + if (nsent == -1) + perror("send"); + stats->nsend++; + if (nsent != len) { + fprintf(stderr, "ACouldn't write\n"); + continue; + } + // Let's read the data again + memset(buf, 0, sizeof(buf)); + nread = recv(sfd, buf, 500, 0); + stats->nrecv++; + if (nread == -1) { + perror("recv"); + continue; + } + if (strcmp(buf, words[i]) != 0) { + fprintf(stderr, "AMessage don't match\n"); + continue; + } + } +} + +void run_student_tests_wrapper_stats_2_server(struct stats2_server *stats) +{ + errno = 0; + memset(stats, 0, sizeof(*stats)); + struct addrinfo hints, *rep, *rp; + int sfd = -1, s; + struct sockaddr_storage peer_addr; + socklen_t peer_addrlen; + ssize_t nread; + char buf[500]; + memset(&hints, 0, sizeof(hints)); + hints.ai_family = AF_INET; + hints.ai_socktype = SOCK_DGRAM; + hints.ai_protocol = 0; + hints.ai_flags = AI_PASSIVE; + s = getaddrinfo(NULL, "1618", &hints, &rep); + stats->ngai = 1; + if (s == -1) { + fprintf(stderr, "%s\n", gai_strerror(s)); + return; + } + for (rp = rep; rp != NULL; rp = rp->ai_next) { + sfd = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol); + stats->nsocket++; + if (sfd == -1) { + perror("Bsocket"); + continue; + } + if (bind(sfd, rp->ai_addr, rp->ai_addrlen) == 0) { + stats->nbind++; + break; + } + perror("Bbind"); + stats->nbind++; + close(sfd); + sfd = -1; + } + if (rp == NULL || sfd == -1) + return; + freeaddrinfo(rep); + for (int failures = 0; failures < 10;) { + memset(buf, 0, sizeof(buf)); + // Stops at the reception of "stop" + peer_addrlen = sizeof(peer_addr); + nread = recvfrom(sfd, buf, 500, 0, (struct sockaddr*) &peer_addr, &peer_addrlen); + stats->nrecvfrom++; + if (nread == -1) + perror("recvfrom"); + if (nread == -1) { + failures++; + continue; + } + // Let's skip the printing here + ssize_t nsent = sendto(sfd, buf, nread, 0, (struct sockaddr*) &peer_addr, peer_addrlen); + stats->nsendto++; + if (nsent != nread) { + failures++; + } + if (nsent == -1) { + perror("Bsent"); + } + if (strcmp(buf, "STOP") == 0) { + break; + } + } +} + diff --git a/ci/test_network_gai_simple_socket/student_code.h b/ci/test_network_gai_simple_socket/student_code.h new file mode 100644 index 0000000..342c2d1 --- /dev/null +++ b/ci/test_network_gai_simple_socket/student_code.h @@ -0,0 +1,31 @@ +#include +struct f1_stats { + bool getaddrinfo_success; + int nb_addr_tried; + int nb_connect; + int nb_send; + ssize_t nb_bytes; +}; +struct stats2_server { + int ngai; + int nsocket; + int nbind; + int nrecvfrom; + int nsendto; +}; +struct stats2_client { + int ngai; + int nsocket; + int nconnect; + int nsend; + int nrecv; +}; + +void run_student_tests_wrapper_stats_1(struct f1_stats *stats); + +void run_student_tests_wrapper_stats_2_client(struct stats2_client *stats); + +void run_student_tests_wrapper_stats_2_server(struct stats2_server *stats); + +// void run_student_tests_wrapper_stats_3(); + diff --git a/ci/test_network_gai_simple_socket/tests.c b/ci/test_network_gai_simple_socket/tests.c new file mode 100644 index 0000000..554b389 --- /dev/null +++ b/ci/test_network_gai_simple_socket/tests.c @@ -0,0 +1,185 @@ +#include +#include +#include +#include + +#include "CTester/CTester.h" +#include "student_code.h" + +void __real_exit(int status); // Needed as otherwise we'll get a segfault + +void test_wrapper_stats() +{ + // Let's enable the stats for all functions + set_test_metadata("wrapper_stats", _("Tests that the wrapper functions correctly remembers the stats"), 1); + CU_ASSERT_EQUAL(stats.accept.called, 0); + CU_ASSERT_EQUAL(stats.bind.called, 0); + CU_ASSERT_EQUAL(stats.connect.called, 0); + CU_ASSERT_EQUAL(stats.listen.called, 0); + CU_ASSERT_EQUAL(stats.recv.called, 0); + CU_ASSERT_EQUAL(stats.recvfrom.called, 0); + CU_ASSERT_EQUAL(stats.recvmsg.called, 0); + CU_ASSERT_EQUAL(stats.recv_all.called, 0); + CU_ASSERT_EQUAL(stats.send.called, 0); + CU_ASSERT_EQUAL(stats.sendto.called, 0); + CU_ASSERT_EQUAL(stats.sendmsg.called, 0); + CU_ASSERT_EQUAL(stats.send_all.called, 0); + CU_ASSERT_EQUAL(stats.socket.called, 0); + + CU_ASSERT_EQUAL(stats.accept.last_return, 0); + CU_ASSERT_EQUAL(stats.bind.last_return, 0); + CU_ASSERT_EQUAL(stats.connect.last_return, 0); + CU_ASSERT_EQUAL(stats.listen.last_return, 0); + CU_ASSERT_EQUAL(stats.recv.last_return, 0); + CU_ASSERT_EQUAL(stats.recvfrom.last_return, 0); + CU_ASSERT_EQUAL(stats.recvmsg.last_return, 0); + CU_ASSERT_EQUAL(stats.send.last_return, 0); + CU_ASSERT_EQUAL(stats.sendto.last_return, 0); + CU_ASSERT_EQUAL(stats.sendmsg.last_return, 0); + CU_ASSERT_EQUAL(stats.socket.last_return, 0); + + monitored.getaddrinfo = monitored.freeaddrinfo = true; + monitored.accept = monitored.bind = monitored.connect = monitored.listen = monitored.socket = true; + MONITOR_ALL_RECV(monitored, true); + MONITOR_ALL_SEND(monitored, true); + CU_ASSERT_EQUAL(monitored.recv, true); + CU_ASSERT_EQUAL(monitored.recvfrom, true); + CU_ASSERT_EQUAL(monitored.recvmsg, true); + CU_ASSERT_EQUAL(monitored.send, true); + CU_ASSERT_EQUAL(monitored.sendto, true); + CU_ASSERT_EQUAL(monitored.sendmsg, true); + + // wrap_monitoring is set to true inside the sandbox, and set to false at the end of it. + struct f1_stats stats1; + SANDBOX_BEGIN; + run_student_tests_wrapper_stats_1(&stats1); + SANDBOX_END; + + CU_ASSERT_EQUAL(stats.accept.called, 0); + CU_ASSERT_EQUAL(stats.bind.called, 0); + CU_ASSERT_EQUAL(stats.connect.called, stats1.nb_connect); + CU_ASSERT_EQUAL(stats.listen.called, 0); + CU_ASSERT_EQUAL(stats.recv.called, 0); + CU_ASSERT_EQUAL(stats.recvfrom.called, 0); + CU_ASSERT_EQUAL(stats.recvmsg.called, 0); + CU_ASSERT_EQUAL(stats.recv_all.called, 0); + CU_ASSERT_EQUAL(stats.send.called, stats1.nb_send); + CU_ASSERT_EQUAL(stats.sendto.called, 0); + CU_ASSERT_EQUAL(stats.sendmsg.called, 0); + CU_ASSERT_EQUAL(stats.send_all.called, stats1.nb_send); + CU_ASSERT_EQUAL(stats.socket.called, stats1.nb_addr_tried); + + reinit_stats_network_dns(); + reinit_network_socket_stats(); // Simpler for the next calls + + int pid; + pid = fork(); + if (pid == 0) { + struct stats2_server statss; + SANDBOX_BEGIN; + run_student_tests_wrapper_stats_2_server(&statss); + SANDBOX_END; + // I must exit to let the other process terminate. No test here ! + __real_exit(0); + } else if (pid != -1) { + struct stats2_client statss; + memset(&statss, 0, sizeof(statss)); + sleep(1); // Strangely extremely important FIXME + SANDBOX_BEGIN; + run_student_tests_wrapper_stats_2_client(&statss); + SANDBOX_END; + int status = 0; + waitpid(pid, &status, 0); + // Let's have a look at the statistics for the client + CU_ASSERT_EQUAL(stats.socket.called, statss.nsocket); + CU_ASSERT_EQUAL(stats.connect.called, statss.nconnect); + CU_ASSERT_EQUAL(stats.getaddrinfo.called, statss.ngai); + CU_ASSERT_EQUAL(stats.freeaddrinfo.called, (statss.ngai > 0 && statss.nsocket > 0)); + CU_ASSERT_EQUAL(stats.sendmsg.called, statss.nsend); + CU_ASSERT_EQUAL(stats.send_all.called, statss.nsend); + CU_ASSERT_EQUAL(stats.recv.called, statss.nrecv); + CU_ASSERT_EQUAL(stats.recv_all.called, statss.nrecv); + } else { + CU_FAIL(); + } + + reinit_stats_network_dns(); + reinit_network_socket_stats(); + + pid = fork(); + if (pid == 0) { + struct stats2_client statss; + SANDBOX_BEGIN; + run_student_tests_wrapper_stats_2_client(&statss); + SANDBOX_END; + // I must exit to let the other process terminate. No test here ! + __real_exit(0); + } else if (pid != -1) { + struct stats2_server statss; + memset(&statss, 0, sizeof(statss)); + SANDBOX_BEGIN; + run_student_tests_wrapper_stats_2_server(&statss); + SANDBOX_END; + int status = 0; + waitpid(pid, &status, 0); + // Let's have a look at the statistics for the server + CU_ASSERT_EQUAL(stats.bind.called, statss.nbind); + CU_ASSERT_EQUAL(stats.socket.called, statss.nsocket); + CU_ASSERT_EQUAL(stats.getaddrinfo.called, statss.ngai); + CU_ASSERT_EQUAL(stats.sendto.called, statss.nsendto); + CU_ASSERT_EQUAL(stats.recvfrom.called, statss.nrecvfrom); + CU_ASSERT_EQUAL(stats.recv_all.called, statss.nrecvfrom); + CU_ASSERT_EQUAL(stats.send_all.called, statss.nsendto); + } else { + CU_FAIL(); + } + + monitored.getaddrinfo = monitored.freeaddrinfo = false; + monitored.accept = monitored.bind = monitored.connect = monitored.listen = monitored.socket = false; + MONITOR_ALL_RECV(monitored, false); + MONITOR_ALL_SEND(monitored, false); + CU_ASSERT_EQUAL(monitored.recv, false); + CU_ASSERT_EQUAL(monitored.recvfrom, false); + CU_ASSERT_EQUAL(monitored.recvmsg, false); + CU_ASSERT_EQUAL(monitored.send, false); + CU_ASSERT_EQUAL(monitored.sendto, false); + CU_ASSERT_EQUAL(monitored.sendmsg, false); + + reinit_stats_network_dns(); + reinit_network_socket_stats(); + CU_ASSERT_EQUAL(stats.accept.called, 0); + CU_ASSERT_EQUAL(stats.bind.called, 0); + CU_ASSERT_EQUAL(stats.connect.called, 0); + CU_ASSERT_EQUAL(stats.listen.called, 0); + CU_ASSERT_EQUAL(stats.recv.called, 0); + CU_ASSERT_EQUAL(stats.recvfrom.called, 0); + CU_ASSERT_EQUAL(stats.recvmsg.called, 0); + CU_ASSERT_EQUAL(stats.recv_all.called, 0); + CU_ASSERT_EQUAL(stats.send.called, 0); + CU_ASSERT_EQUAL(stats.sendto.called, 0); + CU_ASSERT_EQUAL(stats.sendmsg.called, 0); + CU_ASSERT_EQUAL(stats.send_all.called, 0); + CU_ASSERT_EQUAL(stats.socket.called, 0); + + CU_ASSERT_EQUAL(stats.accept.last_return, 0); + CU_ASSERT_EQUAL(stats.bind.last_return, 0); + CU_ASSERT_EQUAL(stats.connect.last_return, 0); + CU_ASSERT_EQUAL(stats.listen.last_return, 0); + CU_ASSERT_EQUAL(stats.recv.last_return, 0); + CU_ASSERT_EQUAL(stats.recvfrom.last_return, 0); + CU_ASSERT_EQUAL(stats.recvmsg.last_return, 0); + CU_ASSERT_EQUAL(stats.send.last_return, 0); + CU_ASSERT_EQUAL(stats.sendto.last_return, 0); + CU_ASSERT_EQUAL(stats.sendmsg.last_return, 0); + CU_ASSERT_EQUAL(stats.socket.last_return, 0); +} + +// Check failures: +// Recall we must use the failures struct, and assign its (call), (call)_ret and (call)_errno fields. +// And we have the fileds FAIL_ALWAYS, FAIL_NEVER, FAIL_FIRST, FAIL_SECOND, FAIL_THIRD and FAIL_TWICE + +int main(int argc, char **argv) +{ + RUN(test_wrapper_stats); +} + diff --git a/ci/test_network_recv/expected_results.txt b/ci/test_network_recv/expected_results.txt new file mode 100644 index 0000000..e0b0e17 --- /dev/null +++ b/ci/test_network_recv/expected_results.txt @@ -0,0 +1,4 @@ +fragmented_recv#SUCCESS#Tests the use of a fragmented recv#1# +fragmented_recv_before#SUCCESS#Tests the use of before-intervals#1# +fragmented_recv_after#SUCCESS#Tests the use of after-intervals#1# +fragmented_recv_realtime#SUCCESS#Tests the use of real-time-intervals#1# diff --git a/ci/test_network_recv/tests.c b/ci/test_network_recv/tests.c new file mode 100644 index 0000000..0dec8b9 --- /dev/null +++ b/ci/test_network_recv/tests.c @@ -0,0 +1,965 @@ +#include +#include +#include +#include +#include +#include + +#include "CTester/CTester.h" +#include "CTester/read_write.h" + +#define BILLION (1000*1000*1000) + +#define TOL_INF_D 0.3 +#define TOL_SUP_D 2.0 +#define TOL_SUP_A (1000*1000) +#define TOL_INF_A 0 + +#define TIMESPEC_ZERO(ts) \ + struct timespec ts; \ + memset(&ts, 0, sizeof(ts)); +#define TS_ZERO(ts) \ + ts.tv_sec = 0; ts.tv_nsec = 0; + +void __real_exit(int status); // Needed as otherwise we'll get a segfault + +struct read_fd_table_t { + size_t n; + struct read_item *items; +}; +struct read_item { + int fd; + const struct read_buffer_t *buf; + unsigned int chunk_id; + size_t bytes_read; + struct timespec last_time; + int64_t interval; +}; + +bool timespec_is_between(const struct timespec *a, const struct timespec *min, const struct timespec *max) +{ + int64_t mint = min->tv_sec; + mint *= BILLION; + mint += min->tv_nsec; + int64_t at = a->tv_sec; + at *= BILLION; + at += a->tv_nsec; + int64_t maxt = max->tv_sec; + maxt *= BILLION; + maxt += max->tv_nsec; + return (mint <= at && at <= maxt); +} + +extern struct read_fd_table_t read_fd_table; + +struct read_item; + +extern bool fd_is_read_buffered(int fd); + +extern ssize_t read_handle_buffer(int fd, void *buf, size_t len, int flags); + +extern int64_t get_time_interval(const struct timespec *pasttime, const struct timespec *curtime); + +extern void getnanotime(struct timespec *res); + +bool is_quasi_equal(int64_t a, int64_t b) +{ + double aa = a, bb = b; + if (a != 0 && b != 0) { + double cc = aa/bb; + return (TOL_INF_D <= cc && cc <= TOL_SUP_D); + } else { + if (a == 0) { + return (TOL_INF_A <= b && b <= TOL_SUP_A); + } else { + return (TOL_INF_A <= a && a <= TOL_SUP_A); + } + } +} + +extern bool wrap_monitoring; + +void test_fragmented_recv() +{ + int64_t MILLION = 1000*1000; + set_test_metadata("fragmented_recv", _("Tests the use of a fragmented recv"), 1); + MONITOR_ALL_RECV(monitored, true); + reinit_network_socket_stats(); + reinit_read_fd_table(); + CU_ASSERT_EQUAL(read_fd_table.n, 0); // "read_fd_table is not empty" + CU_ASSERT_EQUAL(read_fd_table.items, NULL); // "read_fd_table is not empty" + size_t tab1len = 1000; + char *tab1 = malloc(tab1len); + if (!tab1) + CU_FAIL_FATAL("Couldn't allocate memory"); + for (int i = 0; i < 1000; i++) { + tab1[i] = (char)i; + } + struct read_buffer_t rbuf1, rbuf2, rbuf3; + rbuf1.mode = READ_WRITE_BEFORE_INTERVAL; + rbuf1.nchunks = 4; + rbuf1.chunks = malloc(rbuf1.nchunks * sizeof(struct read_bufchunk_t)); + if (!rbuf1.chunks) { + free(tab1); + CU_FAIL_FATAL("Couldn't allocate memory"); + } + rbuf1.chunks[0] = (struct read_bufchunk_t) { + .buf = tab1, + .buflen = 200, + .interval = 2000 + }; + rbuf1.chunks[1] = (struct read_bufchunk_t) { + .buf = (tab1 + 200), + .buflen = 200, + .interval = 3000 + }; + rbuf1.chunks[2] = (struct read_bufchunk_t) { + .buf = (tab1 + 200 + 200), + .buflen = 350, + .interval = 2000 + }; + rbuf1.chunks[3] = (struct read_bufchunk_t) { + .buf = (tab1 + 200 + 200 + 350), + .buflen = 250, + .interval = 2500 + }; + rbuf2.mode = READ_WRITE_AFTER_INTERVAL; + rbuf2.nchunks = 1; + rbuf2.chunks = malloc(rbuf2.nchunks * sizeof(struct read_bufchunk_t)); + if (!rbuf2.chunks) { + free(rbuf1.chunks); + free(tab1); + CU_FAIL_FATAL("Couldn't allocate memory"); + } + rbuf2.chunks[0] = (struct read_bufchunk_t) { + .buf = tab1, + .buflen = tab1len, + .interval = 0 + }; + rbuf3.mode = READ_WRITE_REAL_INTERVAL; + rbuf3.nchunks = 2; + rbuf3.chunks = malloc(rbuf3.nchunks * sizeof(struct read_bufchunk_t)); + if (!rbuf3.chunks) { + free(rbuf2.chunks); + free(rbuf1.chunks); + free(tab1); + CU_FAIL_FATAL("Couldn't allocate memory"); + } + rbuf3.chunks[0] = (struct read_bufchunk_t) { + .buf = tab1, + .buflen = tab1len, + .interval = 4242 + }; + rbuf3.chunks[1] = (struct read_bufchunk_t) { + .buf = tab1, + .buflen = 0, + .interval = 0 + }; + int fd1 = 17, fd2 = 42, fd3 = 0; + CU_ASSERT_EQUAL(set_read_buffer(fd1, &rbuf1), 0); + CU_ASSERT_EQUAL(read_fd_table.n, 1); + CU_ASSERT_TRUE(fd_is_read_buffered(fd1)); + CU_ASSERT_EQUAL(set_read_buffer(fd2, NULL), 0); + CU_ASSERT_EQUAL(read_fd_table.n, 1); + CU_ASSERT_TRUE(fd_is_read_buffered(fd1)); + CU_ASSERT_FALSE(fd_is_read_buffered(fd2)); + CU_ASSERT_EQUAL(set_read_buffer(fd1, NULL), 1); + CU_ASSERT_EQUAL(read_fd_table.n, 0); + CU_ASSERT_FALSE(fd_is_read_buffered(fd1)); + CU_ASSERT_FALSE(fd_is_read_buffered(fd2)); + + struct timespec beforets1, afterts1, beforets2, afterts2, beforets3, afterts3; + CU_ASSERT_EQUAL_FATAL(clock_gettime(CLOCK_REALTIME, &beforets1), 0); + CU_ASSERT_EQUAL_FATAL(set_read_buffer(fd1, &rbuf1), 0); + CU_ASSERT_EQUAL_FATAL(clock_gettime(CLOCK_REALTIME, &afterts1), 0); + CU_ASSERT_EQUAL(read_fd_table.n, 1); + CU_ASSERT_TRUE(fd_is_read_buffered(fd1)); + + CU_ASSERT_EQUAL(set_read_buffer(fd2, &rbuf1), 0); + CU_ASSERT_TRUE(fd_is_read_buffered(fd2)); + CU_ASSERT_EQUAL(read_fd_table.n, 2); + CU_ASSERT_EQUAL(read_fd_table.items[0].buf, &rbuf1); + CU_ASSERT_EQUAL(read_fd_table.items[0].fd, fd1); + CU_ASSERT_EQUAL(read_fd_table.items[1].buf, &rbuf1); + CU_ASSERT_EQUAL(read_fd_table.items[1].fd, fd2); + + CU_ASSERT_EQUAL_FATAL(clock_gettime(CLOCK_REALTIME, &beforets3), 0); + CU_ASSERT_EQUAL(set_read_buffer(fd3, &rbuf3), 0); + CU_ASSERT_EQUAL_FATAL(clock_gettime(CLOCK_REALTIME, &afterts3), 0); + CU_ASSERT_EQUAL(read_fd_table.n, 3); + CU_ASSERT_TRUE(fd_is_read_buffered(fd3)); + CU_ASSERT_EQUAL(read_fd_table.items[0].buf, &rbuf1); + CU_ASSERT_EQUAL(read_fd_table.items[0].fd, fd1); + CU_ASSERT_EQUAL(read_fd_table.items[1].buf, &rbuf1); + CU_ASSERT_EQUAL(read_fd_table.items[1].fd, fd2); + CU_ASSERT_EQUAL(read_fd_table.items[2].buf, &rbuf3); + CU_ASSERT_EQUAL(read_fd_table.items[2].fd, fd3); + + CU_ASSERT_EQUAL(set_read_buffer(fd2, &rbuf2), 1); + CU_ASSERT_EQUAL(read_fd_table.n, 3); + CU_ASSERT_TRUE(fd_is_read_buffered(fd2)); + CU_ASSERT_EQUAL(set_read_buffer(fd2, NULL), 1); + CU_ASSERT_EQUAL(read_fd_table.n, 2); + CU_ASSERT_FALSE(fd_is_read_buffered(fd2)); + CU_ASSERT_EQUAL(set_read_buffer(fd2, NULL), 0); + CU_ASSERT_EQUAL(read_fd_table.n, 2); + CU_ASSERT_FALSE(fd_is_read_buffered(fd2)); + CU_ASSERT_EQUAL(clock_gettime(CLOCK_REALTIME, &beforets2), 0); + CU_ASSERT_EQUAL(set_read_buffer(fd2, &rbuf2), 0); + CU_ASSERT_EQUAL(clock_gettime(CLOCK_REALTIME, &afterts2), 0); + CU_ASSERT_EQUAL(read_fd_table.n, 3); + CU_ASSERT_TRUE(fd_is_read_buffered(fd1)); + CU_ASSERT_TRUE(fd_is_read_buffered(fd2)); + CU_ASSERT_TRUE(fd_is_read_buffered(fd3)); + + CU_ASSERT_EQUAL(read_fd_table.items[0].fd, fd1); + CU_ASSERT_EQUAL(read_fd_table.items[0].buf, &rbuf1); + CU_ASSERT_EQUAL(read_fd_table.items[0].chunk_id, 0); + CU_ASSERT_EQUAL(read_fd_table.items[0].bytes_read, 0); + CU_ASSERT_EQUAL(read_fd_table.items[0].interval, 0); + CU_ASSERT_TRUE(timespec_is_between(&(read_fd_table.items[0].last_time), &beforets1, &afterts1)); + + CU_ASSERT_EQUAL(read_fd_table.items[2].fd, fd2); + CU_ASSERT_EQUAL(read_fd_table.items[2].buf, &rbuf2); + CU_ASSERT_EQUAL(read_fd_table.items[2].chunk_id, 0); + CU_ASSERT_EQUAL(read_fd_table.items[2].bytes_read, 0); + CU_ASSERT_EQUAL(read_fd_table.items[2].interval, 0); + CU_ASSERT_TRUE(timespec_is_between(&(read_fd_table.items[2].last_time), &beforets2, &afterts2)); + + CU_ASSERT_EQUAL(read_fd_table.items[1].fd, fd3); + CU_ASSERT_EQUAL(read_fd_table.items[1].buf, &rbuf3); + CU_ASSERT_EQUAL(read_fd_table.items[1].chunk_id, 0); + CU_ASSERT_EQUAL(read_fd_table.items[1].bytes_read, 0); + fprintf(stderr, "%lld %lld\n", (long long)read_fd_table.items[1].interval, (long long)rbuf3.chunks[0].interval); + CU_ASSERT_EQUAL(read_fd_table.items[1].interval, rbuf3.chunks[0].interval * MILLION); + CU_ASSERT_TRUE(timespec_is_between(&(read_fd_table.items[1].last_time), &beforets3, &afterts3)); + CU_ASSERT_EQUAL(set_read_buffer(fd3, NULL), 1); // We don't need it + CU_ASSERT_EQUAL(read_fd_table.n, 2); + // Correctly set up of the tests: done + // Now, all we have to do is recv the data :-) + reinit_read_fd_table(); + CU_ASSERT_EQUAL(read_fd_table.items, NULL); + CU_ASSERT_EQUAL(read_fd_table.n, 0); + CU_ASSERT_FALSE(fd_is_read_buffered(fd1)); + CU_ASSERT_FALSE(fd_is_read_buffered(fd2)); + CU_ASSERT_FALSE(fd_is_read_buffered(fd3)); + free(tab1); + free(rbuf1.chunks); + free(rbuf2.chunks); + free(rbuf3.chunks); +} + +void test_fragmented_recv_before() +{ + //int64_t MILLION = 1000*1000; + set_test_metadata("fragmented_recv_before", _("Tests the use of before-intervals"), 1); + MONITOR_ALL_RECV(monitored, true); + reinit_network_socket_stats(); + reinit_read_fd_table(); + int mode = READ_WRITE_BEFORE_INTERVAL; + size_t tab1len = 1000 * sizeof(int); + int *tab1 = malloc(tab1len); + if (!tab1) + CU_FAIL_FATAL("Memory"); + for (int i = 0; i < 1000; i++) { + tab1[i] = i; + } + off_t offsets1[] = {200 * sizeof(int), 350 * sizeof(int), 445, 250 * sizeof(int), 200 * sizeof(int) - 445}; + int intervals1[] = {20, 25, 10, 15, 20}; + struct read_buffer_t rbuf1; + rbuf1.mode = mode; + if (create_partial_read_buffer(tab1, 5, offsets1, intervals1, &rbuf1)) { + free(tab1); + CU_FAIL_FATAL("Memory"); + } + int fd1 = 42; + int r = set_read_buffer(fd1, &rbuf1); + if (r < 0) { + free(tab1); + free_partial_read_buffer(&rbuf1); + CU_FAIL_FATAL("Memory"); + } + CU_ASSERT_EQUAL(r, 0); + TIMESPEC_ZERO(ts1); + TIMESPEC_ZERO(ts2); + TIMESPEC_ZERO(ts3); + TIMESPEC_ZERO(ts4); + TIMESPEC_ZERO(ts5); + TIMESPEC_ZERO(ts6); + TIMESPEC_ZERO(ts7); + TIMESPEC_ZERO(ts8); + TIMESPEC_ZERO(ts9); + TIMESPEC_ZERO(ts10); + ssize_t read1 = 0, read2 = 0, read3 = 0, read4 = 0, read5 = 0, read6 = 0, read7 = 0, read8 = 0, read9 = 0, cumread = 0; + int buf1[1010]; + void *buf = buf1; + SANDBOX_BEGIN; + getnanotime(&ts1); + read1 = recv(fd1, buf, 250 * sizeof(int), 0); // Should wait 20msec and return 200*4 bytes, next chunk + cumread += read1; + fprintf(stderr, "1 %d %p\n", (int)cumread, (buf + cumread)); + getnanotime(&ts2); // difference with 1 should be 20msec + read2 = recv(fd1, (buf + cumread), 250 * sizeof(int), 0); // Should wait 25msec and return (50+200)*4 bytes + cumread += read2; + fprintf(stderr, "2 %d %p\n", (int)cumread, (buf + cumread)); + getnanotime(&ts3); // difference with 2 should be 25msec + read3 = recv(fd1, (buf + cumread), 250 * sizeof(int), 0); // Shouldn't wait and return 100*4 bytes, next chunk + cumread += read3; + fprintf(stderr, "3 %d %p\n", (int)cumread, (buf + cumread)); + getnanotime(&ts4); // difference with 3 should be small + read4 = recv(fd1, (buf + cumread), 250 * sizeof(int), 0); // Should wait about 10msec and return 445 bytes, next chunk + cumread += read4; + fprintf(stderr, "4 %d %p\n", (int)cumread, (buf + cumread)); + getnanotime(&ts5); // difference with 4 should be 10msec + struct timespec tss1 = (struct timespec) {.tv_sec = 0, .tv_nsec = 20*1000*1000}; // Shouldn't have any impact + nanosleep(&tss1, NULL); + getnanotime(&ts6); // difference with 5 should be 20msec + read5 = recv(fd1, (buf + cumread), 250 * sizeof(int), 0); // Should wait about 15msec and return 250*4 bytes, next chunk + cumread += read5; + fprintf(stderr, "5 %d %p\n", (int)cumread, (buf + cumread)); + getnanotime(&ts7); // difference with 6 should be 15msec + read6 = recv(fd1, (buf + cumread), 50 * sizeof(int), 0); // Should wait about 20msec and return 50*4 bytes + cumread += read6; + fprintf(stderr, "6 %d %p\n", (int)cumread, (buf + cumread)); + getnanotime(&ts8); // difference with 7 should be 20msec + read7 = recv(fd1, (buf + cumread), 250 * sizeof(int), 0); // Shouldn't wait and return (200*4-50*4-445) = 155 bytes, end + cumread += read7; + fprintf(stderr, "7 %d %p\n", (int)cumread, (buf + cumread)); + getnanotime(&ts9); // difference with 8 should be 0msec + read8 = recv(fd1, (buf + cumread), 250 * sizeof(int), 0); // Shouldn't wait and return 0 bytes, end + cumread += read8; + fprintf(stderr, "8 %d %p\n", (int)cumread, (buf + cumread)); + read9 = recv(fd1, NULL, 250 * sizeof(int), 0); // Shouldn't wait, not read the buffer, and return 0 bytes, end + getnanotime(&ts10); + SANDBOX_END; + CU_ASSERT_EQUAL(read1, 200*sizeof(int)); + CU_ASSERT_EQUAL(read2, (50+200)*sizeof(int)); + CU_ASSERT_EQUAL(read3, 100*sizeof(int)); + CU_ASSERT_EQUAL(read4, 445); + CU_ASSERT_EQUAL(read5, 250*sizeof(int)); + CU_ASSERT_EQUAL(read6, 50*sizeof(int)); + CU_ASSERT_EQUAL(read7, ((200-50)*sizeof(int)-445)); + CU_ASSERT_EQUAL(read8, 0); + CU_ASSERT_EQUAL(read9, 0); + CU_ASSERT_EQUAL(cumread, 1000 * sizeof(int)); + int64_t diff1 = get_time_interval(&ts1, &ts2), + diff2 = get_time_interval(&ts2, &ts3), + diff3 = get_time_interval(&ts3, &ts4), + diff4 = get_time_interval(&ts4, &ts5), + diff5 = get_time_interval(&ts5, &ts6), + diff6 = get_time_interval(&ts6, &ts7), + diff7 = get_time_interval(&ts7, &ts8), + diff8 = get_time_interval(&ts8, &ts9), + diff9 = get_time_interval(&ts9, &ts10); + fprintf(stderr, "%d %d %d %d %d %d %d %d %d\n", (int)read1, (int)read2, (int)read3, (int)read4, (int)read5, (int)read6, (int)read7, (int)read8, (int)read9); + fprintf(stderr, "%ld %ld %ld %ld %ld %ld %ld %ld %ld\n", (long)diff1, (long)diff2, (long)diff3, (long)diff4, (long)diff5, (long)diff6, (long)diff7, (long)diff8, (long)diff9); + /* + * TODO should be used to check that the wait times are correct, + * but it is extremely machine-dependent. + CU_ASSERT_TRUE(is_quasi_equal(diff1, 20*MILLION)); + CU_ASSERT_TRUE(is_quasi_equal(diff2, 25*MILLION)); + CU_ASSERT_TRUE(is_quasi_equal(diff3, 0*MILLION)); + CU_ASSERT_TRUE(is_quasi_equal(diff4, 10*MILLION)); + CU_ASSERT_TRUE(is_quasi_equal(diff5, 20*MILLION)); + CU_ASSERT_TRUE(is_quasi_equal(diff6, 15*MILLION)); + CU_ASSERT_TRUE(is_quasi_equal(diff7, 20*MILLION)); + CU_ASSERT_TRUE(is_quasi_equal(diff8, 0*MILLION)); + CU_ASSERT_TRUE(is_quasi_equal(diff9, 0*MILLION));*/ + for (int i = 0; i < 1000; i++) { + CU_ASSERT_EQUAL(buf1[i], i); + } + for (int i = 0; i < 1000; i++) { + CU_ASSERT_EQUAL(tab1[i], i); + } + free_partial_read_buffer(&rbuf1); + free(tab1); +} + +void test_fragmented_recv_after() +{ + set_test_metadata("fragmented_recv_after", _("Tests the use of after-intervals"), 1); + int64_t MILLION = 1000*1000; + MONITOR_ALL_RECV(monitored, true); + reinit_network_socket_stats(); + reinit_read_fd_table(); + int mode = READ_WRITE_AFTER_INTERVAL; + size_t tab1len = 1000 * sizeof(int); + int *tab1 = malloc(tab1len); + if (!tab1) + CU_FAIL_FATAL("Memory"); + for (int i = 0; i < 1000; i++) + tab1[i] = i; + off_t offsets1[] = {250 * sizeof(int), 150 * sizeof(int), 150 * sizeof(int), 200 * sizeof(int), 150 * sizeof(int), 100 * sizeof(int)}; + int intervals1[] = {20, 20, 20, 20, 20, 20}; + struct read_buffer_t rbuf1; + rbuf1.mode = mode; + if (create_partial_read_buffer(tab1, 6, offsets1, intervals1, &rbuf1)) { + free(tab1); + CU_FAIL_FATAL("Memory"); + } + int fd1 = 42; + int r = set_read_buffer(fd1, &rbuf1); + if (r < 0) { + free(tab1); + free_partial_read_buffer(&rbuf1); + CU_FAIL_FATAL("Memory"); + } + CU_ASSERT_EQUAL(r, 0); + TIMESPEC_ZERO(ts1); + TIMESPEC_ZERO(ts2); + TIMESPEC_ZERO(ts3); + TIMESPEC_ZERO(ts4); + TIMESPEC_ZERO(ts5); + TIMESPEC_ZERO(ts6); + TIMESPEC_ZERO(ts7); + TIMESPEC_ZERO(ts8); + TIMESPEC_ZERO(ts9); + TIMESPEC_ZERO(ts10); + TIMESPEC_ZERO(ts11); + ssize_t read1 = 0, read2 = 0, read3 = 0, read4 = 0, read5 = 0, read6 = 0, read7 = 0, read8 = 0, read9 = 0, cumread = 0; + int errno5 = 0; + errno = 0; + int buf1[1010]; + void *buf = buf1; + SANDBOX_BEGIN; + getnanotime(&ts1); + read1 = recv(fd1, (buf + cumread), 200 * sizeof(int), 0); // Wait 20, return 200 + cumread += read1; + getnanotime(&ts2); + read2 = recv(fd1, (buf + cumread), 200 * sizeof(int), 0); // No wait, return 50 + cumread += read2; + getnanotime(&ts3); + read3 = recv(fd1, (buf + cumread), 200 * sizeof(int), 0); // Wait 20, return 150 + cumread += read3; + getnanotime(&ts4); + struct timespec tss1 = (struct timespec) { + .tv_sec = 0, + .tv_nsec = 20*MILLION + }; + nanosleep(&tss1, NULL); + getnanotime(&ts5); + read4 = recv(fd1, (buf + cumread), 200 * sizeof(int), 0); // No wait, return 150 + cumread += read4; + getnanotime(&ts6); + read5 = recv(fd1, (buf + cumread), 200 * sizeof(int), MSG_DONTWAIT); // No wait, return -1, errno = EAGAIN | EWOULDBLOCK + errno5 = errno; + getnanotime(&ts7); + read6 = recv(fd1, (buf + cumread), 200 * sizeof(int), 0); // Wait, return 200 + cumread += read6; + getnanotime(&ts8); + tss1.tv_nsec = 20*MILLION; + nanosleep(&tss1, NULL); + getnanotime(&ts9); + read7 = recv(fd1, (buf + cumread), 200 * sizeof(int), MSG_DONTWAIT); // No wait, return 150 + cumread += read7; + getnanotime(&ts10); + read8 = recv(fd1, (buf + cumread), 200 * sizeof(int), 0); // Wait, return 100 + cumread += read8; + getnanotime(&ts11); + read9 = recv(fd1, (buf + cumread), 42, 0); // No wait, return 0 + SANDBOX_END; + CU_ASSERT_EQUAL(read1, 200*sizeof(int)); + CU_ASSERT_EQUAL(read2, 50*sizeof(int)); + CU_ASSERT_EQUAL(read3, 150*sizeof(int)); + CU_ASSERT_EQUAL(read4, 150*sizeof(int)); + CU_ASSERT_EQUAL(read5, -1); + CU_ASSERT_TRUE(errno5 == EAGAIN || errno == EWOULDBLOCK); + CU_ASSERT_EQUAL(read6, 200*sizeof(int)); + CU_ASSERT_EQUAL(read7, 150*sizeof(int)); + CU_ASSERT_EQUAL(read8, 100*sizeof(int)); + CU_ASSERT_EQUAL(read9, 0); + CU_ASSERT_EQUAL(cumread, 1000*sizeof(int)); + fprintf(stderr, "%d %d %d %d %d %d %d %d %d\n", (int)read1, (int)read2, (int)read3, (int)read4, (int)read5, (int)read6, (int)read7, (int)read8, (int)read9); + int64_t diff1 = get_time_interval(&ts1, &ts2); // 20msec + int64_t diff2 = get_time_interval(&ts2, &ts3); // 0 + int64_t diff3 = get_time_interval(&ts3, &ts4); // 20 + int64_t diff5 = get_time_interval(&ts5, &ts6); // 0 + int64_t diff6 = get_time_interval(&ts6, &ts7); // 0 + int64_t diff7 = get_time_interval(&ts7, &ts8); // 20 + int64_t diff9 = get_time_interval(&ts9, &ts10); // 0 + int64_t diff10 = get_time_interval(&ts10, &ts11); // 20 + fprintf(stderr, "%ld %ld %ld %ld %ld %ld %ld %ld\n", (long)diff1, (long)diff2, (long)diff3, (long)diff5, (long)diff6, (long)diff7, (long)diff9, (long)diff10); + /* + * TODO Should be used to check wait time, + * but is too much machine-dependent. + CU_ASSERT_TRUE(is_quasi_equal(diff1, 20*MILLION)); + CU_ASSERT_TRUE(is_quasi_equal(diff2, 0*MILLION)); + CU_ASSERT_TRUE(is_quasi_equal(diff3, 20*MILLION)); + CU_ASSERT_TRUE(is_quasi_equal(diff5, 0*MILLION)); + CU_ASSERT_TRUE(is_quasi_equal(diff6, 0*MILLION)); + CU_ASSERT_TRUE(is_quasi_equal(diff7, 20*MILLION)); + CU_ASSERT_TRUE(is_quasi_equal(diff9, 0*MILLION)); + CU_ASSERT_TRUE(is_quasi_equal(diff10, 20*MILLION));*/ + for (int i = 0; i < 1000; i++) { + CU_ASSERT_EQUAL(buf1[i], i); + CU_ASSERT_EQUAL(tab1[i], i); + } + free_partial_read_buffer(&rbuf1); + free(tab1); +} + +/* + * FIXME this test can sometimes fail, as it depends on the real-time property + * of the machine on which it is run. + * TODO Make this independent of the machine. Maybe remove the API... + */ +void test_fragmented_recv_realtime() +{ + set_test_metadata("fragmented_recv_realtime", _("Tests the use of real-time-intervals"), 1); + int64_t MILLION = 1000*1000; + MONITOR_ALL_RECV(monitored, true); + monitored.read = true; // Otherwise it won't work + reinit_network_socket_stats(); + reinit_read_fd_table(); + int mode = READ_WRITE_REAL_INTERVAL; + size_t tab1len = 1000 * sizeof(int); + int *tab1 = malloc(tab1len); + if (!tab1) + CU_FAIL_FATAL("Memory"); + for (int i = 0; i < 1000; i++) + tab1[i] = i; + off_t offsets1[] = {200*sizeof(int), 200*sizeof(int), 200*sizeof(int), 200*sizeof(int), 200*sizeof(int)}; + int intervals1[] = {2*100, 5*100, 1*100, 1*100, 3*100}; + struct read_buffer_t *rbuf1 = create_read_buffer(tab1, 5, offsets1, intervals1, mode); + if (!rbuf1) { + free(tab1); + CU_FAIL_FATAL("Memory"); + } + int fd1 = 42; + int r = set_read_buffer(fd1, rbuf1); + if (r < 0) { + free(tab1); + free_read_buffer(rbuf1); + CU_FAIL_FATAL("Memory"); + } + CU_ASSERT_EQUAL(r, 0); + TIMESPEC_ZERO(tss1); + TIMESPEC_ZERO(ts0); + TIMESPEC_ZERO(ts1); + TIMESPEC_ZERO(ts2); + TIMESPEC_ZERO(ts3); + TIMESPEC_ZERO(ts4); + TIMESPEC_ZERO(ts5); + TIMESPEC_ZERO(ts6); + TIMESPEC_ZERO(ts7); + TIMESPEC_ZERO(ts8); + TIMESPEC_ZERO(ts9); + TIMESPEC_ZERO(ts10); + TIMESPEC_ZERO(ts11); + TIMESPEC_ZERO(ts12); + TIMESPEC_ZERO(ts13); + TIMESPEC_ZERO(ts14); + TIMESPEC_ZERO(ts15); + ssize_t read1 = 0, read2 = 0, read3 = 0, read4 = 0, read5 = 0, read6 = 0, read7 = 0, read8 = 0, read9 = 0, cumread = 0; + int errno3 = 0; + errno = 0; + int buf1[1100]; + memset(buf1, 0, sizeof(buf1)); + void *buf = buf1; + SANDBOX_BEGIN; + getnanotime(&ts0); + + tss1.tv_nsec = 300 * MILLION; + nanosleep(&tss1, NULL); + getnanotime(&ts1); + + read1 = recv(fd1, (buf+cumread), 100*sizeof(int), 0); // No wait, return 100 + cumread += read1; + getnanotime(&ts2); + + tss1.tv_nsec = 200 * MILLION; + nanosleep(&tss1, NULL); + getnanotime(&ts3); + + read2 = read(fd1, (buf+cumread), 150*sizeof(int)); // No wait, return 100 + cumread += read2; + getnanotime(&ts4); + + read3 = recv(fd1, (buf+cumread), 50*sizeof(int), MSG_DONTWAIT); // No wait, return -1, errno + errno3 = errno; + getnanotime(&ts5); + + tss1.tv_nsec = 100 * MILLION; + nanosleep(&tss1, NULL); + getnanotime(&ts6); + + read4 = recv(fd1, (buf+cumread), 50*sizeof(int), 0); // Wait 10, return 50 + cumread += read4; + getnanotime(&ts7); + + tss1.tv_nsec = 200 * MILLION; + nanosleep(&tss1, NULL); + getnanotime(&ts8); + + read5 = recv(fd1, (buf+cumread), 100*sizeof(int), 0); // No wait, return 100 + cumread += read5; + getnanotime(&ts9); + + tss1.tv_nsec = 200 * MILLION; + nanosleep(&tss1, NULL); + getnanotime(&ts10); + + read6 = read(fd1, (buf+cumread), 350*sizeof(int)); // No wait, return 350 + cumread += read6; // 700 + getnanotime(&ts11); + + tss1.tv_nsec = 200 * MILLION; + nanosleep(&tss1, NULL); + getnanotime(&ts12); + + read7 = recv(fd1, (buf+cumread), 200*sizeof(int), MSG_DONTWAIT); // No wait, return 200 + cumread += read7; + getnanotime(&ts13); + + tss1.tv_nsec = 200 * MILLION; + nanosleep(&tss1, NULL); + getnanotime(&ts14); + + read8 = read(fd1, (buf+cumread), 200*sizeof(int)); // No wait, return 100 + cumread += read8; + getnanotime(&ts15); + + read9 = read(fd1, (buf+cumread), 200*sizeof(int)); // No wait, return 0 + SANDBOX_END; + fprintf(stderr, "first test, reads: %d %d %d %d %d %d %d %d %d\n", (int)read1, (int)read2, (int)read3, (int)read4, (int)read5, (int)read6, (int)read7, (int)read8, (int)read9); + CU_ASSERT_EQUAL(read1, 100*sizeof(int)); + CU_ASSERT_EQUAL(read2, 100*sizeof(int)); + CU_ASSERT_EQUAL(read3, -1); + CU_ASSERT_TRUE(errno3 == EAGAIN || EWOULDBLOCK); + CU_ASSERT_EQUAL(read4, 50*sizeof(int)); + CU_ASSERT_EQUAL(read5, 100*sizeof(int)); + CU_ASSERT_EQUAL(read6, 350*sizeof(int)); + CU_ASSERT_EQUAL(read7, 200*sizeof(int)); + CU_ASSERT_EQUAL(read8, 100*sizeof(int)); + CU_ASSERT_EQUAL(read9, 0); + int64_t diff1 = get_time_interval(&ts1, &ts2); // 0 + int64_t diff2 = get_time_interval(&ts3, &ts4); // 0 + int64_t diff3 = get_time_interval(&ts4, &ts5); // 0 + int64_t diff4 = get_time_interval(&ts6, &ts7); // 10 + int64_t diff5 = get_time_interval(&ts8, &ts9); // 0 + int64_t diff6 = get_time_interval(&ts10, &ts11); // 0 + int64_t diff7 = get_time_interval(&ts12, &ts13); // 0 + int64_t diff8 = get_time_interval(&ts14, &ts15); // 0 + int64_t diff11 = get_time_interval(&ts2, &ts3); // 40 + int64_t diff31 = get_time_interval(&ts5, &ts6); // 10 + int64_t diff41 = get_time_interval(&ts7, &ts8); // 20 + int64_t diff51 = get_time_interval(&ts9, &ts10); // 20 + int64_t diff61 = get_time_interval(&ts11, &ts12); // 20 + int64_t diff71 = get_time_interval(&ts13, &ts14); // 20 + fprintf(stderr, "first test, diff1 %ld %ld %ld %ld diff31 %ld %ld %ld %ld diff51 %ld %ld %ld %ld %ld diff8 %ld\n", diff1, diff11, diff2, diff3, diff31, diff4, diff41, diff5, diff51, diff6, diff61, diff7, diff71, diff8); + /* + * TODO Should be used to check wait time, + * but is too much machine-dependent. + CU_ASSERT_TRUE(diff1 <= MILLION); + CU_ASSERT_TRUE(diff2 <= MILLION); + CU_ASSERT_TRUE(diff3 <= MILLION); + CU_ASSERT_TRUE(diff4 <= 10*MILLION); + CU_ASSERT_TRUE(diff5 <= MILLION); + CU_ASSERT_TRUE(diff6 <= MILLION); + CU_ASSERT_TRUE(diff7 <= MILLION); + CU_ASSERT_TRUE(diff8 <= MILLION);*/ + for (int i = 0; i < 1000; i++) { + CU_ASSERT_EQUAL(buf1[i], i); + CU_ASSERT_EQUAL(tab1[i], i); + } + for (int i = 1000; i < 1100; i++) + CU_ASSERT_EQUAL(buf1[i], 0); + free_partial_read_buffer(rbuf1); + // End of test 1 + // Begin of test 2 + off_t offsets2[] = {400*sizeof(int), 400*sizeof(int), 200*sizeof(int)}; + int intervals2[] = {2*100, 4*100, 5*100}; + if (create_partial_read_buffer(tab1, 3, offsets2, intervals2, rbuf1)) { + free(tab1); + free(rbuf1); + } + if (!fd_is_read_buffered(fd1)) { + free(tab1); + free_read_buffer(rbuf1); + CU_FAIL_FATAL("Memory"); + } + r = set_read_buffer(fd1, rbuf1); // Again + CU_ASSERT_EQUAL(r, 1); + TS_ZERO(ts0); + TS_ZERO(ts1); + TS_ZERO(ts2); + TS_ZERO(ts3); + TS_ZERO(ts4); + TS_ZERO(ts5); + TS_ZERO(ts6); + TS_ZERO(ts7); + TS_ZERO(ts8); + TS_ZERO(ts9); + TS_ZERO(ts10); + TS_ZERO(ts11); + TS_ZERO(ts12); + TS_ZERO(ts13); + TS_ZERO(ts14); + TS_ZERO(ts15); + TIMESPEC_ZERO(ts16); + TIMESPEC_ZERO(ts17); + TIMESPEC_ZERO(ts18); + TIMESPEC_ZERO(ts19); + TIMESPEC_ZERO(ts20); + TS_ZERO(tss1); + read1 = 0; read2 = 0; read3 = 0; read4 = 0; read5 = 0; read6 = 0; read7 = 0, read8 = 0, read9 = 0, cumread = 0; + ssize_t read10 = 0, read11 = 0; + memset(buf1, 0, sizeof(buf1)); + SANDBOX_BEGIN; + getnanotime(&ts0); + + tss1.tv_nsec = 100 * MILLION; + nanosleep(&tss1, NULL); + getnanotime(&ts1); + + read1 = recv(fd1, (buf+cumread), 100*sizeof(int), 0); // 100, wait 100 + cumread += read1; + getnanotime(&ts2); + + tss1.tv_nsec = 100 * MILLION; + nanosleep(&tss1, NULL); + getnanotime(&ts3); + + read2 = recv(fd1, (buf+cumread), 200*sizeof(int), 0); // 200, wait 0 + cumread += read2; + getnanotime(&ts4); + + tss1.tv_nsec = 100 * MILLION; + nanosleep(&tss1, NULL); + getnanotime(&ts5); + + read3 = recv(fd1, (buf+cumread), 200*sizeof(int), 0); // 100, wait 0 + cumread += read3; + getnanotime(&ts6); + + tss1.tv_nsec = 100 * MILLION; + nanosleep(&tss1, NULL); + getnanotime(&ts7); + + read4 = recv(fd1, (buf+cumread), 100*sizeof(int), 0); // 100, wait 100 + cumread += read4; + getnanotime(&ts8); + + tss1.tv_nsec = 100 * MILLION; + nanosleep(&tss1, NULL); + getnanotime(&ts9); + + read5 = recv(fd1, (buf+cumread), 100*sizeof(int), 0); // 150, wait 0 + cumread += read5; + getnanotime(&ts10); + + tss1.tv_nsec = 100 * MILLION; + nanosleep(&tss1, NULL); + getnanotime(&ts11); + + read6 = read(fd1, (buf+cumread), 100*sizeof(int)); // 150, wait 0 + cumread += read6; + getnanotime(&ts12); + + tss1.tv_nsec = 100 * MILLION; + nanosleep(&tss1, NULL); + getnanotime(&ts13); + + read8 = read(fd1, (buf+cumread), 100*sizeof(int)); // 100, wait 0, emptied + cumread += read8; + getnanotime(&ts16); + + tss1.tv_nsec = 100 * MILLION; + nanosleep(&tss1, NULL); + getnanotime(&ts17); + + read9 = read(fd1, (buf+cumread), 150*sizeof(int)); // 150, wait 100 + cumread += read9; + getnanotime(&ts18); + + tss1.tv_nsec = 200 * MILLION; + nanosleep(&tss1, NULL); + getnanotime(&ts19); + + read10 = recv(fd1, (buf+cumread), 50*sizeof(int), 0); // 50, wait 0 + cumread += read10; + getnanotime(&ts20); + + read11 = recv(fd1, (buf+cumread), 50*sizeof(int), 0); // 0, wait 0 + SANDBOX_END; + fprintf(stderr, "second test, reads %d %d %d %d %d %d %d %d %d %d\n", (int)read1, (int)read2, (int)read3, (int)read4, (int)read5, (int)read6, (int)read8, (int)read9, (int)read10, (int)read11); + CU_ASSERT_EQUAL(read1, 100*sizeof(int)); + CU_ASSERT_EQUAL(read2, 200*sizeof(int)); + CU_ASSERT_EQUAL(read3, 100*sizeof(int)); + CU_ASSERT_EQUAL(read4, 100*sizeof(int)); + CU_ASSERT_EQUAL(read5, 100*sizeof(int)); + CU_ASSERT_EQUAL(read6, 100*sizeof(int)); + CU_ASSERT_EQUAL(read8, 100*sizeof(int)); + CU_ASSERT_EQUAL(read9, 150*sizeof(int)); + CU_ASSERT_EQUAL(read10, 50*sizeof(int)); + CU_ASSERT_EQUAL(read11, 0); + diff1 = get_time_interval(&ts1, &ts2); + diff2 = get_time_interval(&ts3, &ts4); + diff3 = get_time_interval(&ts5, &ts6); + diff4 = get_time_interval(&ts7, &ts8); + diff5 = get_time_interval(&ts9, &ts10); + diff6 = get_time_interval(&ts11, &ts12); + diff7 = get_time_interval(&ts13, &ts16); + int64_t diff9 = get_time_interval(&ts17, &ts18); + int64_t diff10 = get_time_interval(&ts19, &ts20); + fprintf(stderr, "second test, diff1 %ld %ld %ld diff4 %ld %ld %ld diff7 %ld %ld diff10 %ld\n", (long)diff1, (long)diff2, (long)diff3, (long)diff4, (long)diff5, (long)diff6, (long)diff7, (long)diff9, (long)diff10); + /* + * TODO Should be used to check wait time, + * but is machine-dependent. + CU_ASSERT_TRUE(is_quasi_equal(diff1, 10*MILLION)); + CU_ASSERT_TRUE(is_quasi_equal(diff2, 0*MILLION)); + CU_ASSERT_TRUE(is_quasi_equal(diff3, 0*MILLION)); + CU_ASSERT_TRUE(is_quasi_equal(diff4, 10*MILLION)); + CU_ASSERT_TRUE(is_quasi_equal(diff5, 0*MILLION)); + CU_ASSERT_TRUE(is_quasi_equal(diff6, 0*MILLION)); + CU_ASSERT_TRUE(is_quasi_equal(diff7, 0*MILLION)); + CU_ASSERT_TRUE(is_quasi_equal(diff8, 0*MILLION)); + CU_ASSERT_TRUE(is_quasi_equal(diff9, 10*MILLION)); + CU_ASSERT_TRUE(is_quasi_equal(diff10, 0*MILLION)); */ + for (int i = 0; i < 1000; i++) { + //fprintf(stderr, "%d %d %d\n", i, tab1[i], buf1[i]); + CU_ASSERT_EQUAL(buf1[i], i); + CU_ASSERT_EQUAL(tab1[i], i); + } + for (int i = 1000; i < 1100; i++) + CU_ASSERT_EQUAL(buf1[i], 0); + free_partial_read_buffer(rbuf1); + // End of test 2 + // Begin of test 3 + off_t offsets3[10] = {250*sizeof(int), 100*sizeof(int), 100*sizeof(int), 100*sizeof(int), 100*sizeof(int), 100*sizeof(int), 100*sizeof(int), 50*sizeof(int), 50*sizeof(int), 50*sizeof(int)}; + int intervals3[10] = {2*50, 4*50, 6*50, 1*50, 1*50, 1*50, 2*50, 3*50, 3*50, 1*50}; + if (create_partial_read_buffer(tab1, 10, offsets3, intervals3, rbuf1)) { + free(tab1); + free(rbuf1); + } + r = set_read_buffer(fd1, rbuf1); + CU_ASSERT_EQUAL_FATAL(r, 1); + // TODO check time interval + TS_ZERO(tss1); + read1 = 0; read2 = 0; read3 = 0; read4 = 0; read5 = 0; read6 = 0; + read7 = 0; read8 = 0; read9 = 0; read10 = 0; read11 = 0; cumread = 0; + ssize_t read12 = 0, read13 = 0, read14 = 0; + memset(buf1, 0, sizeof(buf1)); + SANDBOX_BEGIN; + tss1.tv_nsec = 3*50*MILLION; + nanosleep(&tss1, NULL); + + read1 = recv(fd1, (buf+cumread), 100*sizeof(int), 0); + cumread += read1; + + tss1.tv_nsec = 2*50*MILLION; + nanosleep(&tss1, NULL); + + read2 = recv(fd1, (buf+cumread), 100*sizeof(int), 0); + cumread += read2; + + tss1.tv_nsec = 2*50*MILLION; + nanosleep(&tss1, NULL); + + read3 = recv(fd1, (buf+cumread), 100*sizeof(int), 0); + cumread += read3; + + tss1.tv_nsec = 2*50*MILLION; + nanosleep(&tss1, NULL); + + read4 = recv(fd1, (buf+cumread), 100*sizeof(int), 0); // 50 + cumread += read4; + + tss1.tv_nsec = 2*50*MILLION; + nanosleep(&tss1, NULL); + + read5 = recv(fd1, (buf+cumread), 50*sizeof(int), 0); // wait 1*50 + cumread += read5; + + tss1.tv_nsec = 2*50*MILLION; + nanosleep(&tss1, NULL); + + read6 = recv(fd1, (buf+cumread), 20*sizeof(int), 0); + cumread += read6; + + tss1.tv_nsec = 1*50*MILLION; + nanosleep(&tss1, NULL); + + read7 = recv(fd1, (buf+cumread), (30+50)*sizeof(int), 0); + cumread += read7; + + tss1.tv_nsec = 1*50*MILLION; + nanosleep(&tss1, NULL); + + read8 = recv(fd1, (buf+cumread), 200*sizeof(int), 0); + cumread += read8; + + tss1.tv_nsec = 2*50*MILLION; + nanosleep(&tss1, NULL); + + read9 = recv(fd1, (buf+cumread), 50*sizeof(int), 0); + cumread += read9; + + tss1.tv_nsec = 1*50*MILLION; + nanosleep(&tss1, NULL); + + read10 = recv(fd1, (buf+cumread), 100*sizeof(int), 0); + cumread += read10; + + tss1.tv_nsec = 2*50*MILLION; + nanosleep(&tss1, NULL); + + read11 = recv(fd1, (buf+cumread), 100*sizeof(int), 0); // 50 + cumread += read11; + + tss1.tv_nsec = 1*50*MILLION; + nanosleep(&tss1, NULL); + + read12 = recv(fd1, (buf+cumread), 100*sizeof(int), 0); // 50, wait 1*50 + cumread += read12; + + tss1.tv_nsec = 1*50*MILLION; + nanosleep(&tss1, NULL); + + read13 = recv(fd1, (buf+cumread), 50*sizeof(int), 0); + cumread += read13; + + read14 = recv(fd1, (buf+cumread), 50*sizeof(int), 0); + cumread += read14; + SANDBOX_END; + fprintf(stderr, "third test, reads %d %d %d %d %d %d %d %d %d %d %d %d %d %d\n", + (int)read1, (int)read2, (int)read3, (int)read4, (int)read5, + (int)read6, (int)read7, (int)read8, (int)read9, (int)read10, + (int)read11, (int)read12, (int)read13, (int)read14); + CU_ASSERT_EQUAL(read1, 100*sizeof(int)); + CU_ASSERT_EQUAL(read2, 100*sizeof(int)); + CU_ASSERT_EQUAL(read3, 100*sizeof(int)); + CU_ASSERT_EQUAL(read4, 50*sizeof(int)); + CU_ASSERT_EQUAL(read5, 50*sizeof(int)); + CU_ASSERT_EQUAL(read6, 20*sizeof(int)); + CU_ASSERT_EQUAL(read7, 80*sizeof(int)); + CU_ASSERT_EQUAL(read8, 200*sizeof(int)); + CU_ASSERT_EQUAL(read9, 50*sizeof(int)); + CU_ASSERT_EQUAL(read10, 100*sizeof(int)); + CU_ASSERT_EQUAL(read11, 50*sizeof(int)); + CU_ASSERT_EQUAL(read12, 50*sizeof(int)); + CU_ASSERT_EQUAL(read13, 50*sizeof(int)); + CU_ASSERT_EQUAL(read14, 0*sizeof(int)); + for (int i = 0; i < 1000; i++) { + CU_ASSERT_EQUAL(i, buf1[i]); + CU_ASSERT_EQUAL(i, tab1[i]); + } + for (int i = 1000; i < 1100; i++) + CU_ASSERT_EQUAL(buf1[i], 0); + free_read_buffer(rbuf1); +} + +// Check failures: +// Recall we must use the failures struct, and assign its (call), (call)_ret and (call)_errno fields. +// And we have the fileds FAIL_ALWAYS, FAIL_NEVER, FAIL_FIRST, FAIL_SECOND, FAIL_THIRD and FAIL_TWICE + +int main(int argc, char **argv) +{ + RUN( + test_fragmented_recv, + test_fragmented_recv_before, + test_fragmented_recv_after, + test_fragmented_recv_realtime + ); +} + diff --git a/doc/.gitignore b/doc/.gitignore new file mode 100644 index 0000000..f70f32b --- /dev/null +++ b/doc/.gitignore @@ -0,0 +1,3 @@ +_build/ +doxygen_output/ +_build/ diff --git a/doc/Doxyfile b/doc/Doxyfile new file mode 100644 index 0000000..dc6106c --- /dev/null +++ b/doc/Doxyfile @@ -0,0 +1,1451 @@ +# Doxyfile 1.8.5 + +#--------------------------------------------------------------------------- +# Project related configuration options +#--------------------------------------------------------------------------- + +DOXYFILE_ENCODING = UTF-8 + +PROJECT_NAME = "CTester" + +PROJECT_NUMBER = "1.0.0" + +PROJECT_BRIEF = + +PROJECT_LOGO = + +# The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute) path +# into which the generated documentation will be written. If a relative path is +# entered, it will be relative to the location where doxygen was started. If +# left blank the current directory will be used. + +OUTPUT_DIRECTORY = "./doxygen_output" + +CREATE_SUBDIRS = NO + +OUTPUT_LANGUAGE = English + +# If the BRIEF_MEMBER_DESC tag is set to YES doxygen will include brief member +# descriptions after the members that are listed in the file and class +# documentation (similar to Javadoc). Set to NO to disable this. +# The default value is: YES. + +BRIEF_MEMBER_DESC = YES + +# If the REPEAT_BRIEF tag is set to YES doxygen will prepend the brief +# description of a member or function before the detailed description +# +# Note: If both HIDE_UNDOC_MEMBERS and BRIEF_MEMBER_DESC are set to NO, the +# brief descriptions will be completely suppressed. +# The default value is: YES. + +REPEAT_BRIEF = YES + +# This tag implements a quasi-intelligent brief description abbreviator that is +# used to form the text in various listings. Each string in this list, if found +# as the leading text of the brief description, will be stripped from the text +# and the result, after processing the whole list, is used as the annotated +# text. Otherwise, the brief description is used as-is. If left blank, the +# following values are used ($name is automatically replaced with the name of +# the entity):The $name class, The $name widget, The $name file, is, provides, +# specifies, contains, represents, a, an and the. + +ABBREVIATE_BRIEF = + +# If the ALWAYS_DETAILED_SEC and REPEAT_BRIEF tags are both set to YES then +# doxygen will generate a detailed section even if there is only a brief +# description. +# The default value is: NO. + +ALWAYS_DETAILED_SEC = NO + +# If the INLINE_INHERITED_MEMB tag is set to YES, doxygen will show all +# inherited members of a class in the documentation of that class as if those +# members were ordinary class members. Constructors, destructors and assignment +# operators of the base classes will not be shown. +# The default value is: NO. + +INLINE_INHERITED_MEMB = NO + +# If the FULL_PATH_NAMES tag is set to YES doxygen will prepend the full path +# before files name in the file list and in the header files. If set to NO the +# shortest path that makes the file name unique will be used +# The default value is: YES. + +FULL_PATH_NAMES = YES + +# The STRIP_FROM_PATH tag can be used to strip a user-defined part of the path. +# Stripping is only done if one of the specified strings matches the left-hand +# part of the path. The tag can be used to show relative paths in the file list. +# If left blank the directory from which doxygen is run is used as the path to +# strip. +# +# Note that you can specify absolute paths here, but also relative paths, which +# will be relative from the directory where doxygen is started. +# This tag requires that the tag FULL_PATH_NAMES is set to YES. + +STRIP_FROM_PATH = + +# The STRIP_FROM_INC_PATH tag can be used to strip a user-defined part of the +# path mentioned in the documentation of a class, which tells the reader which +# header file to include in order to use a class. If left blank only the name of +# the header file containing the class definition is used. Otherwise one should +# specify the list of include paths that are normally passed to the compiler +# using the -I flag. + +STRIP_FROM_INC_PATH = + +SHORT_NAMES = NO + +# If the JAVADOC_AUTOBRIEF tag is set to YES then doxygen will interpret the +# first line (until the first dot) of a Javadoc-style comment as the brief +# description. If set to NO, the Javadoc-style will behave just like regular Qt- +# style comments (thus requiring an explicit @brief command for a brief +# description.) +# The default value is: NO. + +JAVADOC_AUTOBRIEF = YES + +# If the QT_AUTOBRIEF tag is set to YES then doxygen will interpret the first +# line (until the first dot) of a Qt-style comment as the brief description. If +# set to NO, the Qt-style will behave just like regular Qt-style comments (thus +# requiring an explicit \brief command for a brief description.) +# The default value is: NO. + +QT_AUTOBRIEF = NO + +# The MULTILINE_CPP_IS_BRIEF tag can be set to YES to make doxygen treat a +# multi-line C++ special comment block (i.e. a block of //! or /// comments) as +# a brief description. This used to be the default behavior. The new default is +# to treat a multi-line C++ comment block as a detailed description. Set this +# tag to YES if you prefer the old behavior instead. +# +# Note that setting this tag to YES also means that rational rose comments are +# not recognized any more. +# The default value is: NO. + +MULTILINE_CPP_IS_BRIEF = NO + +# If the INHERIT_DOCS tag is set to YES then an undocumented member inherits the +# documentation from any documented member that it re-implements. +# The default value is: YES. + +INHERIT_DOCS = YES + +# If the SEPARATE_MEMBER_PAGES tag is set to YES, then doxygen will produce a +# new page for each member. If set to NO, the documentation of a member will be +# part of the file/class/namespace that contains it. +# The default value is: NO. + +SEPARATE_MEMBER_PAGES = NO + +# The TAB_SIZE tag can be used to set the number of spaces in a tab. Doxygen +# uses this value to replace tabs by spaces in code fragments. +# Minimum value: 1, maximum value: 16, default value: 4. + +TAB_SIZE = 4 + +# This tag can be used to specify a number of aliases that act as commands in +# the documentation. An alias has the form: +# name=value +# For example adding +# "sideeffect=@par Side Effects:\n" +# will allow you to put the command \sideeffect (or @sideeffect) in the +# documentation, which will result in a user-defined paragraph with heading +# "Side Effects:". You can put \n's in the value part of an alias to insert +# newlines. + +ALIASES = + +TCL_SUBST = + +# Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C sources +# only. Doxygen will then generate output that is more tailored for C. For +# instance, some of the names that are used will be different. The list of all +# members will be omitted, etc. +# The default value is: NO. + +OPTIMIZE_OUTPUT_FOR_C = NO + +OPTIMIZE_OUTPUT_JAVA = NO + +OPTIMIZE_FOR_FORTRAN = NO + +OPTIMIZE_OUTPUT_VHDL = NO + +# Doxygen selects the parser to use depending on the extension of the files it +# parses. With this tag you can assign which parser to use for a given +# extension. Doxygen has a built-in mapping, but you can override or extend it +# using this tag. The format is ext=language, where ext is a file extension, and +# language is one of the parsers supported by doxygen: IDL, Java, Javascript, +# C#, C, C++, D, PHP, Objective-C, Python, Fortran, VHDL. For instance to make +# doxygen treat .inc files as Fortran files (default is PHP), and .f files as C +# (default is Fortran), use: inc=Fortran f=C. +# +# Note For files without extension you can use no_extension as a placeholder. +# +# Note that for custom extensions you also need to set FILE_PATTERNS otherwise +# the files are not read by doxygen. + +EXTENSION_MAPPING = + +# If the MARKDOWN_SUPPORT tag is enabled then doxygen pre-processes all comments +# according to the Markdown format, which allows for more readable +# documentation. See http://daringfireball.net/projects/markdown/ for details. +# The output of markdown processing is further processed by doxygen, so you can +# mix doxygen, HTML, and XML commands with Markdown formatting. Disable only in +# case of backward compatibilities issues. +# The default value is: YES. + +MARKDOWN_SUPPORT = YES + +# When enabled doxygen tries to link words that correspond to documented +# classes, or namespaces to their corresponding documentation. Such a link can +# be prevented in individual cases by by putting a % sign in front of the word +# or globally by setting AUTOLINK_SUPPORT to NO. +# The default value is: YES. + +AUTOLINK_SUPPORT = YES + +# If you use STL classes (i.e. std::string, std::vector, etc.) but do not want +# to include (a tag file for) the STL sources as input, then you should set this +# tag to YES in order to let doxygen match functions declarations and +# definitions whose arguments contain STL classes (e.g. func(std::string); +# versus func(std::string) {}). This also make the inheritance and collaboration +# diagrams that involve STL classes more complete and accurate. +# The default value is: NO. + +BUILTIN_STL_SUPPORT = NO + +# If you use Microsoft's C++/CLI language, you should set this option to YES to +# enable parsing support. +# The default value is: NO. + +CPP_CLI_SUPPORT = NO + +# Set the SIP_SUPPORT tag to YES if your project consists of sip (see: +# http://www.riverbankcomputing.co.uk/software/sip/intro) sources only. Doxygen +# will parse them like normal C++ but will assume all classes use public instead +# of private inheritance when no explicit protection keyword is present. +# The default value is: NO. + +SIP_SUPPORT = NO + +# For Microsoft's IDL there are propget and propput attributes to indicate +# getter and setter methods for a property. Setting this option to YES will make +# doxygen to replace the get and set methods by a property in the documentation. +# This will only work if the methods are indeed getting or setting a simple +# type. If this is not the case, or you want to show the methods anyway, you +# should set this option to NO. +# The default value is: YES. + +IDL_PROPERTY_SUPPORT = YES + +# If member grouping is used in the documentation and the DISTRIBUTE_GROUP_DOC +# tag is set to YES, then doxygen will reuse the documentation of the first +# member in the group (if any) for the other members of the group. By default +# all members of a group must be documented explicitly. +# The default value is: NO. + +DISTRIBUTE_GROUP_DOC = NO + +# Set the SUBGROUPING tag to YES to allow class member groups of the same type +# (for instance a group of public functions) to be put as a subgroup of that +# type (e.g. under the Public Functions section). Set it to NO to prevent +# subgrouping. Alternatively, this can be done per class using the +# \nosubgrouping command. +# The default value is: YES. + +SUBGROUPING = YES + +# When the INLINE_GROUPED_CLASSES tag is set to YES, classes, structs and unions +# are shown inside the group in which they are included (e.g. using \ingroup) +# instead of on a separate page (for HTML and Man pages) or section (for LaTeX +# and RTF). +# +# Note that this feature does not work in combination with +# SEPARATE_MEMBER_PAGES. +# The default value is: NO. + +INLINE_GROUPED_CLASSES = NO + +# When the INLINE_SIMPLE_STRUCTS tag is set to YES, structs, classes, and unions +# with only public data fields or simple typedef fields will be shown inline in +# the documentation of the scope in which they are defined (i.e. file, +# namespace, or group documentation), provided this scope is documented. If set +# to NO, structs, classes, and unions are shown on a separate page (for HTML and +# Man pages) or section (for LaTeX and RTF). +# The default value is: NO. + +INLINE_SIMPLE_STRUCTS = NO + +# When TYPEDEF_HIDES_STRUCT tag is enabled, a typedef of a struct, union, or +# enum is documented as struct, union, or enum with the name of the typedef. So +# typedef struct TypeS {} TypeT, will appear in the documentation as a struct +# with name TypeT. When disabled the typedef will appear as a member of a file, +# namespace, or class. And the struct will be named TypeS. This can typically be +# useful for C code in case the coding convention dictates that all compound +# types are typedef'ed and only the typedef is referenced, never the tag name. +# The default value is: NO. + +TYPEDEF_HIDES_STRUCT = NO + +# The size of the symbol lookup cache can be set using LOOKUP_CACHE_SIZE. This +# cache is used to resolve symbols given their name and scope. Since this can be +# an expensive process and often the same symbol appears multiple times in the +# code, doxygen keeps a cache of pre-resolved symbols. If the cache is too small +# doxygen will become slower. If the cache is too large, memory is wasted. The +# cache size is given by this formula: 2^(16+LOOKUP_CACHE_SIZE). The valid range +# is 0..9, the default is 0, corresponding to a cache size of 2^16=65536 +# symbols. At the end of a run doxygen will report the cache usage and suggest +# the optimal cache size from a speed point of view. +# Minimum value: 0, maximum value: 9, default value: 0. + +LOOKUP_CACHE_SIZE = 0 + +#--------------------------------------------------------------------------- +# Build related configuration options +#--------------------------------------------------------------------------- + +# If the EXTRACT_ALL tag is set to YES doxygen will assume all entities in +# documentation are documented, even if no documentation was available. Private +# class members and static file members will be hidden unless the +# EXTRACT_PRIVATE respectively EXTRACT_STATIC tags are set to YES. +# Note: This will also disable the warnings about undocumented members that are +# normally produced when WARNINGS is set to YES. +# The default value is: NO. + +EXTRACT_ALL = NO + +# If the EXTRACT_PRIVATE tag is set to YES all private members of a class will +# be included in the documentation. +# The default value is: NO. + +EXTRACT_PRIVATE = NO + +# If the EXTRACT_PACKAGE tag is set to YES all members with package or internal +# scope will be included in the documentation. +# The default value is: NO. + +EXTRACT_PACKAGE = NO + +# If the EXTRACT_STATIC tag is set to YES all static members of a file will be +# included in the documentation. +# The default value is: NO. + +EXTRACT_STATIC = NO + +# If the EXTRACT_LOCAL_CLASSES tag is set to YES classes (and structs) defined +# locally in source files will be included in the documentation. If set to NO +# only classes defined in header files are included. Does not have any effect +# for Java sources. +# The default value is: YES. + +EXTRACT_LOCAL_CLASSES = YES + +# This flag is only useful for Objective-C code. When set to YES local methods, +# which are defined in the implementation section but not in the interface are +# included in the documentation. If set to NO only methods in the interface are +# included. +# The default value is: NO. + +EXTRACT_LOCAL_METHODS = NO + +# If this flag is set to YES, the members of anonymous namespaces will be +# extracted and appear in the documentation as a namespace called +# 'anonymous_namespace{file}', where file will be replaced with the base name of +# the file that contains the anonymous namespace. By default anonymous namespace +# are hidden. +# The default value is: NO. + +EXTRACT_ANON_NSPACES = NO + +# If the HIDE_UNDOC_MEMBERS tag is set to YES, doxygen will hide all +# undocumented members inside documented classes or files. If set to NO these +# members will be included in the various overviews, but no documentation +# section is generated. This option has no effect if EXTRACT_ALL is enabled. +# The default value is: NO. + +HIDE_UNDOC_MEMBERS = NO + +# If the HIDE_UNDOC_CLASSES tag is set to YES, doxygen will hide all +# undocumented classes that are normally visible in the class hierarchy. If set +# to NO these classes will be included in the various overviews. This option has +# no effect if EXTRACT_ALL is enabled. +# The default value is: NO. + +HIDE_UNDOC_CLASSES = NO + +# If the HIDE_FRIEND_COMPOUNDS tag is set to YES, doxygen will hide all friend +# (class|struct|union) declarations. If set to NO these declarations will be +# included in the documentation. +# The default value is: NO. + +HIDE_FRIEND_COMPOUNDS = NO + +# If the HIDE_IN_BODY_DOCS tag is set to YES, doxygen will hide any +# documentation blocks found inside the body of a function. If set to NO these +# blocks will be appended to the function's detailed documentation block. +# The default value is: NO. + +HIDE_IN_BODY_DOCS = NO + +# The INTERNAL_DOCS tag determines if documentation that is typed after a +# \internal command is included. If the tag is set to NO then the documentation +# will be excluded. Set it to YES to include the internal documentation. +# The default value is: NO. + +INTERNAL_DOCS = YES + +# If the CASE_SENSE_NAMES tag is set to NO then doxygen will only generate file +# names in lower-case letters. If set to YES upper-case letters are also +# allowed. This is useful if you have classes or files whose names only differ +# in case and if your file system supports case sensitive file names. Windows +# and Mac users are advised to set this option to NO. +# The default value is: system dependent. + +CASE_SENSE_NAMES = YES + +# If the HIDE_SCOPE_NAMES tag is set to NO then doxygen will show members with +# their full class and namespace scopes in the documentation. If set to YES the +# scope will be hidden. +# The default value is: NO. + +HIDE_SCOPE_NAMES = NO + +# If the SHOW_INCLUDE_FILES tag is set to YES then doxygen will put a list of +# the files that are included by a file in the documentation of that file. +# The default value is: YES. + +SHOW_INCLUDE_FILES = YES + +# If the FORCE_LOCAL_INCLUDES tag is set to YES then doxygen will list include +# files with double quotes in the documentation rather than with sharp brackets. +# The default value is: NO. + +FORCE_LOCAL_INCLUDES = NO + +# If the INLINE_INFO tag is set to YES then a tag [inline] is inserted in the +# documentation for inline members. +# The default value is: YES. + +INLINE_INFO = YES + +# If the SORT_MEMBER_DOCS tag is set to YES then doxygen will sort the +# (detailed) documentation of file and class members alphabetically by member +# name. If set to NO the members will appear in declaration order. +# The default value is: YES. + +SORT_MEMBER_DOCS = YES + +# If the SORT_BRIEF_DOCS tag is set to YES then doxygen will sort the brief +# descriptions of file, namespace and class members alphabetically by member +# name. If set to NO the members will appear in declaration order. +# The default value is: NO. + +SORT_BRIEF_DOCS = NO + +# If the SORT_MEMBERS_CTORS_1ST tag is set to YES then doxygen will sort the +# (brief and detailed) documentation of class members so that constructors and +# destructors are listed first. If set to NO the constructors will appear in the +# respective orders defined by SORT_BRIEF_DOCS and SORT_MEMBER_DOCS. +# Note: If SORT_BRIEF_DOCS is set to NO this option is ignored for sorting brief +# member documentation. +# Note: If SORT_MEMBER_DOCS is set to NO this option is ignored for sorting +# detailed member documentation. +# The default value is: NO. + +SORT_MEMBERS_CTORS_1ST = NO + +# If the SORT_GROUP_NAMES tag is set to YES then doxygen will sort the hierarchy +# of group names into alphabetical order. If set to NO the group names will +# appear in their defined order. +# The default value is: NO. + +SORT_GROUP_NAMES = NO + +# If the SORT_BY_SCOPE_NAME tag is set to YES, the class list will be sorted by +# fully-qualified names, including namespaces. If set to NO, the class list will +# be sorted only by class name, not including the namespace part. +# Note: This option is not very useful if HIDE_SCOPE_NAMES is set to YES. +# Note: This option applies only to the class list, not to the alphabetical +# list. +# The default value is: NO. + +SORT_BY_SCOPE_NAME = NO + +# If the STRICT_PROTO_MATCHING option is enabled and doxygen fails to do proper +# type resolution of all parameters of a function it will reject a match between +# the prototype and the implementation of a member function even if there is +# only one candidate or it is obvious which candidate to choose by doing a +# simple string match. By disabling STRICT_PROTO_MATCHING doxygen will still +# accept a match between prototype and implementation in such cases. +# The default value is: NO. + +STRICT_PROTO_MATCHING = NO + +# The GENERATE_TODOLIST tag can be used to enable ( YES) or disable ( NO) the +# todo list. This list is created by putting \todo commands in the +# documentation. +# The default value is: YES. + +GENERATE_TODOLIST = YES + +# The GENERATE_TESTLIST tag can be used to enable ( YES) or disable ( NO) the +# test list. This list is created by putting \test commands in the +# documentation. +# The default value is: YES. + +GENERATE_TESTLIST = YES + +# The GENERATE_BUGLIST tag can be used to enable ( YES) or disable ( NO) the bug +# list. This list is created by putting \bug commands in the documentation. +# The default value is: YES. + +GENERATE_BUGLIST = YES + +# The GENERATE_DEPRECATEDLIST tag can be used to enable ( YES) or disable ( NO) +# the deprecated list. This list is created by putting \deprecated commands in +# the documentation. +# The default value is: YES. + +GENERATE_DEPRECATEDLIST= YES + +# The ENABLED_SECTIONS tag can be used to enable conditional documentation +# sections, marked by \if ... \endif and \cond +# ... \endcond blocks. + +ENABLED_SECTIONS = + +# The MAX_INITIALIZER_LINES tag determines the maximum number of lines that the +# initial value of a variable or macro / define can have for it to appear in the +# documentation. If the initializer consists of more lines than specified here +# it will be hidden. Use a value of 0 to hide initializers completely. The +# appearance of the value of individual variables and macros / defines can be +# controlled using \showinitializer or \hideinitializer command in the +# documentation regardless of this setting. +# Minimum value: 0, maximum value: 10000, default value: 30. + +MAX_INITIALIZER_LINES = 30 + +# Set the SHOW_USED_FILES tag to NO to disable the list of files generated at +# the bottom of the documentation of classes and structs. If set to YES the list +# will mention the files that were used to generate the documentation. +# The default value is: YES. + +SHOW_USED_FILES = YES + +# Set the SHOW_FILES tag to NO to disable the generation of the Files page. This +# will remove the Files entry from the Quick Index and from the Folder Tree View +# (if specified). +# The default value is: YES. + +SHOW_FILES = YES + +# Set the SHOW_NAMESPACES tag to NO to disable the generation of the Namespaces +# page. This will remove the Namespaces entry from the Quick Index and from the +# Folder Tree View (if specified). +# The default value is: YES. + +SHOW_NAMESPACES = YES + +# The FILE_VERSION_FILTER tag can be used to specify a program or script that +# doxygen should invoke to get the current version for each file (typically from +# the version control system). Doxygen will invoke the program by executing (via +# popen()) the command command input-file, where command is the value of the +# FILE_VERSION_FILTER tag, and input-file is the name of an input file provided +# by doxygen. Whatever the program writes to standard output is used as the file +# version. For an example see the documentation. + +FILE_VERSION_FILTER = + +# The LAYOUT_FILE tag can be used to specify a layout file which will be parsed +# by doxygen. The layout file controls the global structure of the generated +# output files in an output format independent way. To create the layout file +# that represents doxygen's defaults, run doxygen with the -l option. You can +# optionally specify a file name after the option, if omitted DoxygenLayout.xml +# will be used as the name of the layout file. +# +# Note that if you run doxygen from a directory containing a file called +# DoxygenLayout.xml, doxygen will parse it automatically even if the LAYOUT_FILE +# tag is left empty. + +LAYOUT_FILE = + +# The CITE_BIB_FILES tag can be used to specify one or more bib files containing +# the reference definitions. This must be a list of .bib files. The .bib +# extension is automatically appended if omitted. This requires the bibtex tool +# to be installed. See also http://en.wikipedia.org/wiki/BibTeX for more info. +# For LaTeX the style of the bibliography can be controlled using +# LATEX_BIB_STYLE. To use this feature you need bibtex and perl available in the +# search path. Do not use file names with spaces, bibtex cannot handle them. See +# also \cite for info how to create references. + +CITE_BIB_FILES = + +#--------------------------------------------------------------------------- +# Configuration options related to warning and progress messages +#--------------------------------------------------------------------------- + +QUIET = NO + +WARNINGS = YES + +WARN_IF_UNDOCUMENTED = YES + +WARN_IF_DOC_ERROR = YES + +WARN_NO_PARAMDOC = NO + +WARN_FORMAT = "$file:$line: $text" + +# Writes to stdout by default +WARN_LOGFILE = + +#--------------------------------------------------------------------------- +# Configuration options related to the input files +#--------------------------------------------------------------------------- + +# The INPUT tag is used to specify the files and/or directories that contain +# documented source files. You may enter file names like myfile.cpp or +# directories like /usr/src/myproject. Separate the files or directories with +# spaces. +# Note: If this tag is empty the current directory is searched. + +INPUT = "../student" + +# This tag can be used to specify the character encoding of the source files +# that doxygen parses. Internally doxygen uses the UTF-8 encoding. Doxygen uses +# libiconv (or the iconv built into libc) for the transcoding. See the libiconv +# documentation (see: http://www.gnu.org/software/libiconv) for the list of +# possible encodings. +# The default value is: UTF-8. + +INPUT_ENCODING = UTF-8 + +# If the value of the INPUT tag contains directories, you can use the +# FILE_PATTERNS tag to specify one or more wildcard patterns (like *.cpp and +# *.h) to filter out the source-files in the directories. If left blank the +# following patterns are tested:*.c, *.cc, *.cxx, *.cpp, *.c++, *.java, *.ii, +# *.ixx, *.ipp, *.i++, *.inl, *.idl, *.ddl, *.odl, *.h, *.hh, *.hxx, *.hpp, +# *.h++, *.cs, *.d, *.php, *.php4, *.php5, *.phtml, *.inc, *.m, *.markdown, +# *.md, *.mm, *.dox, *.py, *.f90, *.f, *.for, *.tcl, *.vhd, *.vhdl, *.ucf, +# *.qsf, *.as and *.js. + +FILE_PATTERNS = "*.h" + +# The RECURSIVE tag can be used to specify whether or not subdirectories should +# be searched for input files as well. +# The default value is: NO. + +RECURSIVE = YES + +# The EXCLUDE tag can be used to specify files and/or directories that should be +# excluded from the INPUT source files. This way you can easily exclude a +# subdirectory from a directory tree whose root is specified with the INPUT tag. +# +# Note that relative paths are relative to the directory from which doxygen is +# run. + +EXCLUDE = + +# The EXCLUDE_SYMLINKS tag can be used to select whether or not files or +# directories that are symbolic links (a Unix file system feature) are excluded +# from the input. +# The default value is: NO. + +EXCLUDE_SYMLINKS = NO + +# If the value of the INPUT tag contains directories, you can use the +# EXCLUDE_PATTERNS tag to specify one or more wildcard patterns to exclude +# certain files from those directories. +# +# Note that the wildcards are matched against the file with absolute path, so to +# exclude all test directories for example use the pattern */test/* + +EXCLUDE_PATTERNS = "*.c" + +# The EXCLUDE_SYMBOLS tag can be used to specify one or more symbol names +# (namespaces, classes, functions, etc.) that should be excluded from the +# output. The symbol name can be a fully qualified name, a word, or if the +# wildcard * is used, a substring. Examples: ANamespace, AClass, +# AClass::ANamespace, ANamespace::*Test +# +# Note that the wildcards are matched against the file with absolute path, so to +# exclude all test directories use the pattern */test/* + +EXCLUDE_SYMBOLS = + +# The EXAMPLE_PATH tag can be used to specify one or more files or directories +# that contain example code fragments that are included (see the \include +# command). + +EXAMPLE_PATH = + +# If the value of the EXAMPLE_PATH tag contains directories, you can use the +# EXAMPLE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp and +# *.h) to filter out the source-files in the directories. If left blank all +# files are included. + +EXAMPLE_PATTERNS = + +# If the EXAMPLE_RECURSIVE tag is set to YES then subdirectories will be +# searched for input files to be used with the \include or \dontinclude commands +# irrespective of the value of the RECURSIVE tag. +# The default value is: NO. + +EXAMPLE_RECURSIVE = NO + +# The IMAGE_PATH tag can be used to specify one or more files or directories +# that contain images that are to be included in the documentation (see the +# \image command). + +IMAGE_PATH = + +# The INPUT_FILTER tag can be used to specify a program that doxygen should +# invoke to filter for each input file. Doxygen will invoke the filter program +# by executing (via popen()) the command: +# +# +# +# where is the value of the INPUT_FILTER tag, and is the +# name of an input file. Doxygen will then use the output that the filter +# program writes to standard output. If FILTER_PATTERNS is specified, this tag +# will be ignored. +# +# Note that the filter must not add or remove lines; it is applied before the +# code is scanned, but not when the output code is generated. If lines are added +# or removed, the anchors will not be placed correctly. + +INPUT_FILTER = + +# The FILTER_PATTERNS tag can be used to specify filters on a per file pattern +# basis. Doxygen will compare the file name with each pattern and apply the +# filter if there is a match. The filters are a list of the form: pattern=filter +# (like *.cpp=my_cpp_filter). See INPUT_FILTER for further information on how +# filters are used. If the FILTER_PATTERNS tag is empty or if none of the +# patterns match the file name, INPUT_FILTER is applied. + +FILTER_PATTERNS = + +# If the FILTER_SOURCE_FILES tag is set to YES, the input filter (if set using +# INPUT_FILTER ) will also be used to filter the input files that are used for +# producing the source files to browse (i.e. when SOURCE_BROWSER is set to YES). +# The default value is: NO. + +FILTER_SOURCE_FILES = NO + +# The FILTER_SOURCE_PATTERNS tag can be used to specify source filters per file +# pattern. A pattern will override the setting for FILTER_PATTERN (if any) and +# it is also possible to disable source filtering for a specific pattern using +# *.ext= (so without naming a filter). +# This tag requires that the tag FILTER_SOURCE_FILES is set to YES. + +FILTER_SOURCE_PATTERNS = + +# If the USE_MDFILE_AS_MAINPAGE tag refers to the name of a markdown file that +# is part of the input, its contents will be placed on the main page +# (index.html). This can be useful if you have a project on for instance GitHub +# and want to reuse the introduction page also for the doxygen output. + +USE_MDFILE_AS_MAINPAGE = + +#--------------------------------------------------------------------------- +# Configuration options related to source browsing +#--------------------------------------------------------------------------- + +# If the SOURCE_BROWSER tag is set to YES then a list of source files will be +# generated. Documented entities will be cross-referenced with these sources. +# +# Note: To get rid of all source code in the generated output, make sure that +# also VERBATIM_HEADERS is set to NO. +# The default value is: NO. + +SOURCE_BROWSER = NO + +# Setting the INLINE_SOURCES tag to YES will include the body of functions, +# classes and enums directly into the documentation. +# The default value is: NO. + +INLINE_SOURCES = NO + +# Setting the STRIP_CODE_COMMENTS tag to YES will instruct doxygen to hide any +# special comment blocks from generated source code fragments. Normal C, C++ and +# Fortran comments will always remain visible. +# The default value is: YES. + +STRIP_CODE_COMMENTS = YES + +# If the REFERENCED_BY_RELATION tag is set to YES then for each documented +# function all documented functions referencing it will be listed. +# The default value is: NO. + +REFERENCED_BY_RELATION = NO + +# If the REFERENCES_RELATION tag is set to YES then for each documented function +# all documented entities called/used by that function will be listed. +# The default value is: NO. + +REFERENCES_RELATION = NO + +# If the REFERENCES_LINK_SOURCE tag is set to YES and SOURCE_BROWSER tag is set +# to YES, then the hyperlinks from functions in REFERENCES_RELATION and +# REFERENCED_BY_RELATION lists will link to the source code. Otherwise they will +# link to the documentation. +# The default value is: YES. + +REFERENCES_LINK_SOURCE = YES + +# If SOURCE_TOOLTIPS is enabled (the default) then hovering a hyperlink in the +# source code will show a tooltip with additional information such as prototype, +# brief description and links to the definition and documentation. Since this +# will make the HTML file larger and loading of large files a bit slower, you +# can opt to disable this feature. +# The default value is: YES. +# This tag requires that the tag SOURCE_BROWSER is set to YES. + +SOURCE_TOOLTIPS = YES + +# If the USE_HTAGS tag is set to YES then the references to source code will +# point to the HTML generated by the htags(1) tool instead of doxygen built-in +# source browser. The htags tool is part of GNU's global source tagging system +# (see http://www.gnu.org/software/global/global.html). You will need version +# 4.8.6 or higher. +# +# To use it do the following: +# - Install the latest version of global +# - Enable SOURCE_BROWSER and USE_HTAGS in the config file +# - Make sure the INPUT points to the root of the source tree +# - Run doxygen as normal +# +# Doxygen will invoke htags (and that will in turn invoke gtags), so these +# tools must be available from the command line (i.e. in the search path). +# +# The result: instead of the source browser generated by doxygen, the links to +# source code will now point to the output of htags. +# The default value is: NO. +# This tag requires that the tag SOURCE_BROWSER is set to YES. + +USE_HTAGS = NO + +# If the VERBATIM_HEADERS tag is set the YES then doxygen will generate a +# verbatim copy of the header file for each class for which an include is +# specified. Set to NO to disable this. +# See also: Section \class. +# The default value is: YES. + +VERBATIM_HEADERS = YES + +#--------------------------------------------------------------------------- +# Configuration options related to the alphabetical class index +#--------------------------------------------------------------------------- + +# If the ALPHABETICAL_INDEX tag is set to YES, an alphabetical index of all +# compounds will be generated. Enable this if the project contains a lot of +# classes, structs, unions or interfaces. +# The default value is: YES. + +ALPHABETICAL_INDEX = YES + +COLS_IN_ALPHA_INDEX = 5 + +# In case all classes in a project start with a common prefix, all classes will +# be put under the same header in the alphabetical index. The IGNORE_PREFIX tag +# can be used to specify a prefix (or a list of prefixes) that should be ignored +# while generating the index headers. +# This tag requires that the tag ALPHABETICAL_INDEX is set to YES. + +IGNORE_PREFIX = + +#--------------------------------------------------------------------------- +# Configuration options related to the HTML output +#--------------------------------------------------------------------------- + +# If the GENERATE_HTML tag is set to YES doxygen will generate HTML output +# The default value is: YES. + +GENERATE_HTML = YES + +# The HTML_OUTPUT tag is used to specify where the HTML docs will be put. If a +# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of +# it. +# The default directory is: html. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_OUTPUT = "./html" + +# The HTML_FILE_EXTENSION tag can be used to specify the file extension for each +# generated HTML page (for example: .htm, .php, .asp). +# The default value is: .html. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_FILE_EXTENSION = .html + +# The HTML_HEADER tag can be used to specify a user-defined HTML header file for +# each generated HTML page. If the tag is left blank doxygen will generate a +# standard header. +# +# To get valid HTML the header file that includes any scripts and style sheets +# that doxygen needs, which is dependent on the configuration options used (e.g. +# the setting GENERATE_TREEVIEW). It is highly recommended to start with a +# default header using +# doxygen -w html new_header.html new_footer.html new_stylesheet.css +# YourConfigFile +# and then modify the file new_header.html. See also section "Doxygen usage" +# for information on how to generate the default header that doxygen normally +# uses. +# Note: The header is subject to change so you typically have to regenerate the +# default header when upgrading to a newer version of doxygen. For a description +# of the possible markers and block names see the documentation. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_HEADER = + +# The HTML_FOOTER tag can be used to specify a user-defined HTML footer for each +# generated HTML page. If the tag is left blank doxygen will generate a standard +# footer. See HTML_HEADER for more information on how to generate a default +# footer and what special commands can be used inside the footer. See also +# section "Doxygen usage" for information on how to generate the default footer +# that doxygen normally uses. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_FOOTER = + +# The HTML_STYLESHEET tag can be used to specify a user-defined cascading style +# sheet that is used by each HTML page. It can be used to fine-tune the look of +# the HTML output. If left blank doxygen will generate a default style sheet. +# See also section "Doxygen usage" for information on how to generate the style +# sheet that doxygen normally uses. +# Note: It is recommended to use HTML_EXTRA_STYLESHEET instead of this tag, as +# it is more robust and this tag (HTML_STYLESHEET) will in the future become +# obsolete. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_STYLESHEET = + +# The HTML_EXTRA_STYLESHEET tag can be used to specify an additional user- +# defined cascading style sheet that is included after the standard style sheets +# created by doxygen. Using this option one can overrule certain style aspects. +# This is preferred over using HTML_STYLESHEET since it does not replace the +# standard style sheet and is therefor more robust against future updates. +# Doxygen will copy the style sheet file to the output directory. For an example +# see the documentation. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_EXTRA_STYLESHEET = + +# The HTML_EXTRA_FILES tag can be used to specify one or more extra images or +# other source files which should be copied to the HTML output directory. Note +# that these files will be copied to the base HTML output directory. Use the +# $relpath^ marker in the HTML_HEADER and/or HTML_FOOTER files to load these +# files. In the HTML_STYLESHEET file, use the file name only. Also note that the +# files will be copied as-is; there are no commands or markers available. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_EXTRA_FILES = + +# The HTML_COLORSTYLE_HUE tag controls the color of the HTML output. Doxygen +# will adjust the colors in the stylesheet and background images according to +# this color. Hue is specified as an angle on a colorwheel, see +# http://en.wikipedia.org/wiki/Hue for more information. For instance the value +# 0 represents red, 60 is yellow, 120 is green, 180 is cyan, 240 is blue, 300 +# purple, and 360 is red again. +# Minimum value: 0, maximum value: 359, default value: 220. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_COLORSTYLE_HUE = 220 + +# The HTML_COLORSTYLE_SAT tag controls the purity (or saturation) of the colors +# in the HTML output. For a value of 0 the output will use grayscales only. A +# value of 255 will produce the most vivid colors. +# Minimum value: 0, maximum value: 255, default value: 100. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_COLORSTYLE_SAT = 100 + +# The HTML_COLORSTYLE_GAMMA tag controls the gamma correction applied to the +# luminance component of the colors in the HTML output. Values below 100 +# gradually make the output lighter, whereas values above 100 make the output +# darker. The value divided by 100 is the actual gamma applied, so 80 represents +# a gamma of 0.8, The value 220 represents a gamma of 2.2, and 100 does not +# change the gamma. +# Minimum value: 40, maximum value: 240, default value: 80. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_COLORSTYLE_GAMMA = 80 + +# If the HTML_TIMESTAMP tag is set to YES then the footer of each generated HTML +# page will contain the date and time when the page was generated. Setting this +# to NO can help when comparing the output of multiple runs. +# The default value is: YES. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_TIMESTAMP = NO + +# If the HTML_DYNAMIC_SECTIONS tag is set to YES then the generated HTML +# documentation will contain sections that can be hidden and shown after the +# page has loaded. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_DYNAMIC_SECTIONS = NO + +# With HTML_INDEX_NUM_ENTRIES one can control the preferred number of entries +# shown in the various tree structured indices initially; the user can expand +# and collapse entries dynamically later on. Doxygen will expand the tree to +# such a level that at most the specified number of entries are visible (unless +# a fully collapsed tree already exceeds this amount). So setting the number of +# entries 1 will produce a full collapsed tree by default. 0 is a special value +# representing an infinite number of entries and will result in a full expanded +# tree by default. +# Minimum value: 0, maximum value: 9999, default value: 100. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_INDEX_NUM_ENTRIES = 100 + +# If the GENERATE_DOCSET tag is set to YES, additional index files will be +# generated that can be used as input for Apple's Xcode 3 integrated development +# environment (see: http://developer.apple.com/tools/xcode/), introduced with +# OSX 10.5 (Leopard). To create a documentation set, doxygen will generate a +# Makefile in the HTML output directory. Running make will produce the docset in +# that directory and running make install will install the docset in +# ~/Library/Developer/Shared/Documentation/DocSets so that Xcode will find it at +# startup. See http://developer.apple.com/tools/creatingdocsetswithdoxygen.html +# for more information. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +GENERATE_DOCSET = NO + +# This tag determines the name of the docset feed. A documentation feed provides +# an umbrella under which multiple documentation sets from a single provider +# (such as a company or product suite) can be grouped. +# The default value is: Doxygen generated docs. +# This tag requires that the tag GENERATE_DOCSET is set to YES. + +DOCSET_FEEDNAME = "Doxygen generated docs" + +# This tag specifies a string that should uniquely identify the documentation +# set bundle. This should be a reverse domain-name style string, e.g. +# com.mycompany.MyDocSet. Doxygen will append .docset to the name. +# The default value is: org.doxygen.Project. +# This tag requires that the tag GENERATE_DOCSET is set to YES. + +DOCSET_BUNDLE_ID = org.doxygen.Project + +# The DOCSET_PUBLISHER_ID tag specifies a string that should uniquely identify +# the documentation publisher. This should be a reverse domain-name style +# string, e.g. com.mycompany.MyDocSet.documentation. +# The default value is: org.doxygen.Publisher. +# This tag requires that the tag GENERATE_DOCSET is set to YES. + +DOCSET_PUBLISHER_ID = org.doxygen.Publisher + +# The DOCSET_PUBLISHER_NAME tag identifies the documentation publisher. +# The default value is: Publisher. +# This tag requires that the tag GENERATE_DOCSET is set to YES. + +DOCSET_PUBLISHER_NAME = Publisher + +# If the GENERATE_HTMLHELP tag is set to YES then doxygen generates three +# additional HTML index files: index.hhp, index.hhc, and index.hhk. The +# index.hhp is a project file that can be read by Microsoft's HTML Help Workshop +# (see: http://www.microsoft.com/en-us/download/details.aspx?id=21138) on +# Windows. +# +# The HTML Help Workshop contains a compiler that can convert all HTML output +# generated by doxygen into a single compiled HTML file (.chm). Compiled HTML +# files are now used as the Windows 98 help format, and will replace the old +# Windows help format (.hlp) on all Windows platforms in the future. Compressed +# HTML files also contain an index, a table of contents, and you can search for +# words in the documentation. The HTML workshop also contains a viewer for +# compressed HTML files. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +GENERATE_HTMLHELP = NO + +# If the GENERATE_QHP tag is set to YES and both QHP_NAMESPACE and +# QHP_VIRTUAL_FOLDER are set, an additional index file will be generated that +# can be used as input for Qt's qhelpgenerator to generate a Qt Compressed Help +# (.qch) of the generated HTML documentation. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +GENERATE_QHP = NO + +# If the GENERATE_ECLIPSEHELP tag is set to YES, additional index files will be +# generated, together with the HTML files, they form an Eclipse help plugin. To +# install this plugin and make it available under the help contents menu in +# Eclipse, the contents of the directory containing the HTML and XML files needs +# to be copied into the plugins directory of eclipse. The name of the directory +# within the plugins directory should be the same as the ECLIPSE_DOC_ID value. +# After copying Eclipse needs to be restarted before the help appears. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +GENERATE_ECLIPSEHELP = NO + +# If you want full control over the layout of the generated HTML pages it might +# be necessary to disable the index and replace it with your own. The +# DISABLE_INDEX tag can be used to turn on/off the condensed index (tabs) at top +# of each HTML page. A value of NO enables the index and the value YES disables +# it. Since the tabs in the index contain the same information as the navigation +# tree, you can set this option to YES if you also set GENERATE_TREEVIEW to YES. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +DISABLE_INDEX = NO + +# The GENERATE_TREEVIEW tag is used to specify whether a tree-like index +# structure should be generated to display hierarchical information. If the tag +# value is set to YES, a side panel will be generated containing a tree-like +# index structure (just like the one that is generated for HTML Help). For this +# to work a browser that supports JavaScript, DHTML, CSS and frames is required +# (i.e. any modern browser). Windows users are probably better off using the +# HTML help feature. Via custom stylesheets (see HTML_EXTRA_STYLESHEET) one can +# further fine-tune the look of the index. As an example, the default style +# sheet generated by doxygen has an example that shows how to put an image at +# the root of the tree instead of the PROJECT_NAME. Since the tree basically has +# the same information as the tab index, you could consider setting +# DISABLE_INDEX to YES when enabling this option. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +GENERATE_TREEVIEW = NO + +# The ENUM_VALUES_PER_LINE tag can be used to set the number of enum values that +# doxygen will group on one line in the generated HTML documentation. +# +# Note that a value of 0 will completely suppress the enum values from appearing +# in the overview section. +# Minimum value: 0, maximum value: 20, default value: 4. +# This tag requires that the tag GENERATE_HTML is set to YES. + +ENUM_VALUES_PER_LINE = 4 + +# If the treeview is enabled (see GENERATE_TREEVIEW) then this tag can be used +# to set the initial width (in pixels) of the frame in which the tree is shown. +# Minimum value: 0, maximum value: 1500, default value: 250. +# This tag requires that the tag GENERATE_HTML is set to YES. + +TREEVIEW_WIDTH = 250 + +# When the EXT_LINKS_IN_WINDOW option is set to YES doxygen will open links to +# external symbols imported via tag files in a separate window. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +EXT_LINKS_IN_WINDOW = NO + +# Use this tag to change the font size of LaTeX formulas included as images in +# the HTML documentation. When you change the font size after a successful +# doxygen run you need to manually remove any form_*.png images from the HTML +# output directory to force them to be regenerated. +# Minimum value: 8, maximum value: 50, default value: 10. +# This tag requires that the tag GENERATE_HTML is set to YES. + +FORMULA_FONTSIZE = 10 + +# Use the FORMULA_TRANPARENT tag to determine whether or not the images +# generated for formulas are transparent PNGs. Transparent PNGs are not +# supported properly for IE 6.0, but are supported on all modern browsers. +# +# Note that when changing this option you need to delete any form_*.png files in +# the HTML output directory before the changes have effect. +# The default value is: YES. +# This tag requires that the tag GENERATE_HTML is set to YES. + +FORMULA_TRANSPARENT = YES + +# Enable the USE_MATHJAX option to render LaTeX formulas using MathJax (see +# http://www.mathjax.org) which uses client side Javascript for the rendering +# instead of using prerendered bitmaps. Use this if you do not have LaTeX +# installed or if you want to formulas look prettier in the HTML output. When +# enabled you may also need to install MathJax separately and configure the path +# to it using the MATHJAX_RELPATH option. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +USE_MATHJAX = YES + +# When MathJax is enabled you can set the default output format to be used for +# the MathJax output. See the MathJax site (see: +# http://docs.mathjax.org/en/latest/output.html) for more details. +# Possible values are: HTML-CSS (which is slower, but has the best +# compatibility), NativeMML (i.e. MathML) and SVG. +# The default value is: HTML-CSS. +# This tag requires that the tag USE_MATHJAX is set to YES. + +MATHJAX_FORMAT = HTML-CSS + +# When MathJax is enabled you need to specify the location relative to the HTML +# output directory using the MATHJAX_RELPATH option. The destination directory +# should contain the MathJax.js script. For instance, if the mathjax directory +# is located at the same level as the HTML output directory, then +# MATHJAX_RELPATH should be ../mathjax. The default value points to the MathJax +# Content Delivery Network so you can quickly see the result without installing +# MathJax. However, it is strongly recommended to install a local copy of +# MathJax from http://www.mathjax.org before deployment. +# The default value is: http://cdn.mathjax.org/mathjax/latest. +# This tag requires that the tag USE_MATHJAX is set to YES. + +MATHJAX_RELPATH = http://cdn.mathjax.org/mathjax/latest + +# The MATHJAX_EXTENSIONS tag can be used to specify one or more MathJax +# extension names that should be enabled during MathJax rendering. For example +# MATHJAX_EXTENSIONS = TeX/AMSmath TeX/AMSsymbols +# This tag requires that the tag USE_MATHJAX is set to YES. + +MATHJAX_EXTENSIONS = + +# The MATHJAX_CODEFILE tag can be used to specify a file with javascript pieces +# of code that will be used on startup of the MathJax code. See the MathJax site +# (see: http://docs.mathjax.org/en/latest/output.html) for more details. For an +# example see the documentation. +# This tag requires that the tag USE_MATHJAX is set to YES. + +MATHJAX_CODEFILE = + +# When the SEARCHENGINE tag is enabled doxygen will generate a search box for +# the HTML output. The underlying search engine uses javascript and DHTML and +# should work on any modern browser. Note that when using HTML help +# (GENERATE_HTMLHELP), Qt help (GENERATE_QHP), or docsets (GENERATE_DOCSET) +# there is already a search function so this one should typically be disabled. +# For large projects the javascript based search engine can be slow, then +# enabling SERVER_BASED_SEARCH may provide a better solution. It is possible to +# search using the keyboard; to jump to the search box use + S +# (what the is depends on the OS and browser, but it is typically +# , /